Skip to content

Replace manual memory management with FinalizationRegistry#1908

Closed
NickGerleman wants to merge 1 commit into
react:mainfrom
NickGerleman:export-D95014519
Closed

Replace manual memory management with FinalizationRegistry#1908
NickGerleman wants to merge 1 commit into
react:mainfrom
NickGerleman:export-D95014519

Conversation

@NickGerleman

Copy link
Copy Markdown
Contributor

Summary:
Yoga's JavaScript API currently requires users to manually call
node.free(), node.freeRecursive(), or config.free() to release
WASM memory. This is error-prone and unusual for a JavaScript library.

This diff uses FinalizationRegistry (available in all modern JS
engines) to automatically free WASM memory when JS objects are garbage
collected, removing this footgun entirely.

Changes:

  • Added FinalizationRegistry instances for Node and Config in
    wrapAssembly.ts. When a NodeImpl/ConfigImpl is constructed, it
    is registered with the appropriate registry. The weak reference
    callback receives the WASM pointer and calls the appropriate C free
    function.
  • The Node finalizer calls YGNodeFinalize (not YGNodeFree) to
    safely deallocate without disconnecting nodes from their
    owner/children, since an entire tree may be garbage collected
    together.
  • Removed free() and freeRecursive() from the public Node type
    and free() from the public Config type.
  • Removed Yoga.Node.destroy() and Yoga.Config.destroy() factory
    methods.
  • Fixed setDirtiedFunc to use WeakRef(this) instead of capturing
    this directly in the closure stored in the dirtied func Map. The
    previous () => dirtiedFunc(this) pattern created a strong reference
    from the Map to the Node, preventing GC of detached nodes with a
    dirtied func set.
  • Updated gentest-javascript.js to stop generating try/finally
    cleanup blocks and let root declarations in test prologues/epilogues.
  • Removed all free()/freeRecursive()/config.free() calls from 25
    generated test files and 9 hand-written test files.
  • Re-signed all generated test files with signedsource.
  • Updated README.md to remove manual memory management documentation.

Differential Revision: D95014519

@vercel

vercel Bot commented Mar 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
yoga-website Ready Ready Preview, Comment Mar 6, 2026 9:50am

Request Review

@meta-codesync

meta-codesync Bot commented Mar 3, 2026

Copy link
Copy Markdown

@NickGerleman has exported this pull request. If you are a Meta employee, you can view the originating Diff in D95014519.

NickGerleman added a commit to NickGerleman/yoga that referenced this pull request Mar 6, 2026
Summary:
Pull Request resolved: react#1908

Yoga's JavaScript API currently requires users to manually call
`node.free()`, `node.freeRecursive()`, or `config.free()` to release
WASM memory. This is error-prone and unusual for a JavaScript library.

This diff uses `FinalizationRegistry` (available in all modern JS
engines) to automatically free WASM memory when JS objects are garbage
collected, removing this footgun entirely.

Changes:
- Added `FinalizationRegistry` instances for `Node` and `Config` in
  `wrapAssembly.ts`. When a `NodeImpl`/`ConfigImpl` is constructed, it
  is registered with the appropriate registry. The weak reference
  callback receives the WASM pointer and calls the appropriate C free
  function.
- The Node finalizer calls `YGNodeFinalize` (not `YGNodeFree`) to
  safely deallocate without disconnecting nodes from their
  owner/children, since an entire tree may be garbage collected
  together.
- Removed `free()` and `freeRecursive()` from the public `Node` type
  and `free()` from the public `Config` type.
- Removed `Yoga.Node.destroy()` and `Yoga.Config.destroy()` factory
  methods.
- Fixed `setDirtiedFunc` to use `WeakRef(this)` instead of capturing
  `this` directly in the closure stored in the dirtied func Map. The
  previous `() => dirtiedFunc(this)` pattern created a strong reference
  from the Map to the Node, preventing GC of detached nodes with a
  dirtied func set.
- Updated `gentest-javascript.js` to stop generating `try`/`finally`
  cleanup blocks and `let root` declarations in test prologues/epilogues.
- Removed all `free()`/`freeRecursive()`/`config.free()` calls from 25
  generated test files and 9 hand-written test files.
- Re-signed all generated test files with signedsource.
- Updated `README.md` to remove manual memory management documentation.

Differential Revision: D95014519
NickGerleman added a commit to NickGerleman/yoga that referenced this pull request Mar 6, 2026
Summary:
Pull Request resolved: react#1908

Yoga's JavaScript API currently requires users to manually call
`node.free()`, `node.freeRecursive()`, or `config.free()` to release
WASM memory. This is error-prone and unusual for a JavaScript library.

This diff uses `FinalizationRegistry` (available in all modern JS
engines) to automatically free WASM memory when JS objects are garbage
collected, removing this footgun entirely.

Changes:
- Added `FinalizationRegistry` instances for `Node` and `Config` in
  `wrapAssembly.ts`. When a `NodeImpl`/`ConfigImpl` is constructed, it
  is registered with the appropriate registry. The weak reference
  callback receives the WASM pointer and calls the appropriate C free
  function.
- The Node finalizer calls `YGNodeFinalize` (not `YGNodeFree`) to
  safely deallocate without disconnecting nodes from their
  owner/children, since an entire tree may be garbage collected
  together.
- Removed `free()` and `freeRecursive()` from the public `Node` type
  and `free()` from the public `Config` type.
- Removed `Yoga.Node.destroy()` and `Yoga.Config.destroy()` factory
  methods.
- Fixed `setDirtiedFunc` to use `WeakRef(this)` instead of capturing
  `this` directly in the closure stored in the dirtied func Map. The
  previous `() => dirtiedFunc(this)` pattern created a strong reference
  from the Map to the Node, preventing GC of detached nodes with a
  dirtied func set.
- Updated `gentest-javascript.js` to stop generating `try`/`finally`
  cleanup blocks and `let root` declarations in test prologues/epilogues.
- Removed all `free()`/`freeRecursive()`/`config.free()` calls from 25
  generated test files and 9 hand-written test files.
- Re-signed all generated test files with signedsource.
- Updated `README.md` to remove manual memory management documentation.

Differential Revision: D95014519
NickGerleman added a commit to NickGerleman/yoga that referenced this pull request Mar 6, 2026
Summary:
Pull Request resolved: react#1908

Yoga's JavaScript API currently requires users to manually call
`node.free()`, `node.freeRecursive()`, or `config.free()` to release
WASM memory. This is error-prone and unusual for a JavaScript library.

This diff uses `FinalizationRegistry` (available in all modern JS
engines) to automatically free WASM memory when JS objects are garbage
collected, removing this footgun entirely.

Changes:
- Added `FinalizationRegistry` instances for `Node` and `Config` in
  `wrapAssembly.ts`. When a `NodeImpl`/`ConfigImpl` is constructed, it
  is registered with the appropriate registry. The weak reference
  callback receives the WASM pointer and calls the appropriate C free
  function.
- The Node finalizer calls `YGNodeFinalize` (not `YGNodeFree`) to
  safely deallocate without disconnecting nodes from their
  owner/children, since an entire tree may be garbage collected
  together.
- Removed `free()` and `freeRecursive()` from the public `Node` type
  and `free()` from the public `Config` type.
- Removed `Yoga.Node.destroy()` and `Yoga.Config.destroy()` factory
  methods.
- Fixed `setDirtiedFunc` to use `WeakRef(this)` instead of capturing
  `this` directly in the closure stored in the dirtied func Map. The
  previous `() => dirtiedFunc(this)` pattern created a strong reference
  from the Map to the Node, preventing GC of detached nodes with a
  dirtied func set.
- Updated `gentest-javascript.js` to stop generating `try`/`finally`
  cleanup blocks and `let root` declarations in test prologues/epilogues.
- Removed all `free()`/`freeRecursive()`/`config.free()` calls from 25
  generated test files and 9 hand-written test files.
- Re-signed all generated test files with signedsource.
- Updated `README.md` to remove manual memory management documentation.

Differential Revision: D95014519
NickGerleman added a commit to NickGerleman/yoga that referenced this pull request Mar 6, 2026
Summary:
Pull Request resolved: react#1908

Fixes react#1818
Fixes react#1572

Yoga's JavaScript API currently requires users to manually call
`node.free()`, `node.freeRecursive()`, or `config.free()` to release
WASM memory. This is error-prone and unusual for a JavaScript library.

This diff uses `FinalizationRegistry` (available in all modern JS
engines) to automatically free WASM memory when JS objects are garbage
collected, removing this footgun entirely.

Changes:
- Added `FinalizationRegistry` instances for `Node` and `Config` in
  `wrapAssembly.ts`. When a `NodeImpl`/`ConfigImpl` is constructed, it
  is registered with the appropriate registry. The weak reference
  callback receives the WASM pointer and calls the appropriate C free
  function.
- The Node finalizer calls `YGNodeFinalize` (not `YGNodeFree`) to
  safely deallocate without disconnecting nodes from their
  owner/children, since an entire tree may be garbage collected
  together.
- Removed `free()` and `freeRecursive()` from the public `Node` type
  and `free()` from the public `Config` type.
- Removed `Yoga.Node.destroy()` and `Yoga.Config.destroy()` factory
  methods.
- Fixed `setDirtiedFunc` to use `WeakRef(this)` instead of capturing
  `this` directly in the closure stored in the dirtied func Map. The
  previous `() => dirtiedFunc(this)` pattern created a strong reference
  from the Map to the Node, preventing GC of detached nodes with a
  dirtied func set.
- Updated `gentest-javascript.js` to stop generating `try`/`finally`
  cleanup blocks and `let root` declarations in test prologues/epilogues.
- Removed all `free()`/`freeRecursive()`/`config.free()` calls from 25
  generated test files and 9 hand-written test files.
- Re-signed all generated test files with signedsource.
- Updated `README.md` to remove manual memory management documentation.

Differential Revision: D95014519
NickGerleman added a commit to NickGerleman/yoga that referenced this pull request Mar 6, 2026
Summary:
Pull Request resolved: react#1908

Fixes react#1818
Fixes react#1572

Yoga's JavaScript API currently requires users to manually call
`node.free()`, `node.freeRecursive()`, or `config.free()` to release
WASM memory. This is error-prone and unusual for a JavaScript library.

This diff uses `FinalizationRegistry` (available in all modern JS
engines) to automatically free WASM memory when JS objects are garbage
collected, removing this footgun entirely.

Changes:
- Added `FinalizationRegistry` instances for `Node` and `Config` in
  `wrapAssembly.ts`. When a `NodeImpl`/`ConfigImpl` is constructed, it
  is registered with the appropriate registry. The weak reference
  callback receives the WASM pointer and calls the appropriate C free
  function.
- The Node finalizer calls `YGNodeFinalize` (not `YGNodeFree`) to
  safely deallocate without disconnecting nodes from their
  owner/children, since an entire tree may be garbage collected
  together.
- Removed `free()` and `freeRecursive()` from the public `Node` type
  and `free()` from the public `Config` type.
- Removed `Yoga.Node.destroy()` and `Yoga.Config.destroy()` factory
  methods.
- Fixed `setDirtiedFunc` to use `WeakRef(this)` instead of capturing
  `this` directly in the closure stored in the dirtied func Map. The
  previous `() => dirtiedFunc(this)` pattern created a strong reference
  from the Map to the Node, preventing GC of detached nodes with a
  dirtied func set.
- Updated `gentest-javascript.js` to stop generating `try`/`finally`
  cleanup blocks and `let root` declarations in test prologues/epilogues.
- Removed all `free()`/`freeRecursive()`/`config.free()` calls from 25
  generated test files and 9 hand-written test files.
- Re-signed all generated test files with signedsource.
- Updated `README.md` to remove manual memory management documentation.

Reviewed By: cipolleschi

Differential Revision: D95014519
Summary:
Pull Request resolved: react#1908

Fixes react#1818
Fixes react#1572

Yoga's JavaScript API currently requires users to manually call
`node.free()`, `node.freeRecursive()`, or `config.free()` to release
WASM memory. This is error-prone and unusual for a JavaScript library.

This diff uses `FinalizationRegistry` (available in all modern JS
engines) to automatically free WASM memory when JS objects are garbage
collected, removing this footgun entirely.

Changes:
- Added `FinalizationRegistry` instances for `Node` and `Config` in
  `wrapAssembly.ts`. When a `NodeImpl`/`ConfigImpl` is constructed, it
  is registered with the appropriate registry. The weak reference
  callback receives the WASM pointer and calls the appropriate C free
  function.
- The Node finalizer calls `YGNodeFinalize` (not `YGNodeFree`) to
  safely deallocate without disconnecting nodes from their
  owner/children, since an entire tree may be garbage collected
  together.
- Removed `free()` and `freeRecursive()` from the public `Node` type
  and `free()` from the public `Config` type.
- Removed `Yoga.Node.destroy()` and `Yoga.Config.destroy()` factory
  methods.
- Fixed `setDirtiedFunc` to use `WeakRef(this)` instead of capturing
  `this` directly in the closure stored in the dirtied func Map. The
  previous `() => dirtiedFunc(this)` pattern created a strong reference
  from the Map to the Node, preventing GC of detached nodes with a
  dirtied func set.
- Updated `gentest-javascript.js` to stop generating `try`/`finally`
  cleanup blocks and `let root` declarations in test prologues/epilogues.
- Removed all `free()`/`freeRecursive()`/`config.free()` calls from 25
  generated test files and 9 hand-written test files.
- Re-signed all generated test files with signedsource.
- Updated `README.md` to remove manual memory management documentation.

Reviewed By: cipolleschi

Differential Revision: D95014519
@meta-codesync

meta-codesync Bot commented Mar 6, 2026

Copy link
Copy Markdown

This pull request has been merged in e00b766.

wjq990112 added a commit to wjq990112/better-yoga that referenced this pull request Jun 26, 2026
…y refactor

Upstream yoga react#1908 ("Replace manual memory management with FinalizationRegistry")
removed Node.free/freeRecursive, Config.free and the static Yoga.Node.destroy on
main. better-yoga forks main, so consumers written for the stable yoga-layout@3.2.x
(ink, vue-tui) crash with "node.free is not a function" on teardown.

Re-add the four release APIs, adapted to the FinalizationRegistry model:
- Node.free: unregister the GC finalizer, sync JS parent/child pointers, call
  _YGNodeFree, then null _ptr (idempotent guard against double-free)
- Node.freeRecursive: JS-side depth-first recursion (native YGNodeFreeRecursive
  would leave descendant wrappers registered and double-free after GC)
- Config.free: unregister + _YGConfigFree + idempotent
- Yoga.Node.destroy(node): 3.2.x compat alias delegating to node.free()

Adds tests/YGNodeFreeTest.test.ts (6 cases).

Claude-Session: https://claude.ai/code/session_013wsNEdvdkhb89Uj9sQf9LT
wjq990112 added a commit to wjq990112/better-yoga that referenced this pull request Jun 27, 2026
Squash of the docs/rebrand-better-yoga-layout branch:
- fix: restore Node.free/freeRecursive, Config.free and the static
  Yoga.Node.destroy removed by upstream's FinalizationRegistry refactor
  (react#1908); adds tests/YGNodeFreeTest.test.ts (6 cases)
- chore(release): bump better-yoga-layout 0.2.0 -> 0.2.1
- docs(readme): rebrand to better-yoga-layout

Claude-Session: https://claude.ai/code/session_013wsNEdvdkhb89Uj9sQf9LT
wjq990112 added a commit to wjq990112/better-yoga that referenced this pull request Jun 27, 2026
…out; release 0.2.1 (#2)

* chore(release): bump better-yoga-layout to 0.2.0

Bump version 0.1.0 -> 0.2.0 for the first public release carrying the
performance work (dynamic measurement cache + edge/axis fast-paths +
ThinLTO). Pin publishConfig.registry to the public npm registry so the
package is never accidentally published to an internal mirror.

* docs(readme): rebrand to better-yoga-layout with optimizations & benchmarks

Rewrite the project README and npm package README for the better-yoga fork,
and add a Chinese translation (README.zh-CN.md, cross-linked).

- Rename yoga-layout -> better-yoga-layout; npm + MIT badges (drop Maven/SPM,
  which this fork does not publish).
- Position as a drop-in replacement for yoga-layout with identical behavior.
- Document the three optimization themes with commit links: dynamic
  measurement cache (70c0142), edge/axis fast-paths, ThinLTO by default.
- Add the three-way benchmark table (upstream Yoga vs better-yoga vs Taffy)
  with an honest framing: 1.5-1.9x vs upstream on typical trees, super-deep
  shown with context, plus a caveats section on the remaining algorithmic gap
  vs Taffy.
- Credit upstream Yoga / Meta (MIT).

* fix: restore manual-free APIs removed by upstream FinalizationRegistry refactor

Upstream yoga react#1908 ("Replace manual memory management with FinalizationRegistry")
removed Node.free/freeRecursive, Config.free and the static Yoga.Node.destroy on
main. better-yoga forks main, so consumers written for the stable yoga-layout@3.2.x
(ink, vue-tui) crash with "node.free is not a function" on teardown.

Re-add the four release APIs, adapted to the FinalizationRegistry model:
- Node.free: unregister the GC finalizer, sync JS parent/child pointers, call
  _YGNodeFree, then null _ptr (idempotent guard against double-free)
- Node.freeRecursive: JS-side depth-first recursion (native YGNodeFreeRecursive
  would leave descendant wrappers registered and double-free after GC)
- Config.free: unregister + _YGConfigFree + idempotent
- Yoga.Node.destroy(node): 3.2.x compat alias delegating to node.free()

Adds tests/YGNodeFreeTest.test.ts (6 cases).

Claude-Session: https://claude.ai/code/session_013wsNEdvdkhb89Uj9sQf9LT

* chore(release): bump better-yoga-layout to 0.2.1

Claude-Session: https://claude.ai/code/session_013wsNEdvdkhb89Uj9sQf9LT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants