Conversation
A dirtied function runs while Yoga marks nodes dirty, from style setters, insertChild, removeChild, markDirty, copyStyle and setIsReferenceBaseline. If it throws, the exception unwinds the WebAssembly frames: the stack pointer is not restored, so repeated throws eventually break the module, and dirty propagation stops, so the ancestors stay clean and the next layout misses the change. A throw from insertChild or removeChild also leaves the JS children list out of sync with Yoga's tree. The dirtied function bridge now holds the exception so the Wasm call can finish, and the call that marked the nodes dirty rethrows it once it returns. A Yoga call made from inside a dirtied function keeps its own error, and a WebAssembly.RuntimeError takes precedence over an earlier error. The calls are wrapped only while a dirtied function is set. insertChild and removeChild keep the JS tree in sync when a dirtied function throws, and leave it unchanged when Yoga aborts.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A dirtied function runs while Yoga marks nodes dirty: from style setters,
insertChild,removeChild,markDirty,copyStyle, andsetIsReferenceBaseline. If it throws, the exception unwinds the WebAssembly frames, which causes three problems:main, the module fails withmemory access out of boundsafter about 4,000 throws.insertChild()orremoveChild()skips the JS-side children list and parent update, while Yoga's tree has already changed.Changes
insertChild()andremoveChild()keep the JS tree in sync when a dirtied function throws. When Yoga itself aborts (for example, inserting a child that already has an owner), they leave the JS tree unchanged, as onmain.try/catch) only while at least one dirtied function is set, so code that never sets one runs exactly as before.Two behaviors change when a dirtied function throws:
WebAssembly.RuntimeErrortakes precedence over an earlier plain error, so an abort isn't hidden.#2032 handles the same stack problem for measure functions. The two PRs don't depend on each other.
Test Plan
New tests in
YGDirtiedTest.test.ts:dirtied_func_exception_propagates_to_callerlayout_works_after_repeated_dirtied_func_exceptions: 5,000 throws, then a normal layout is correct. Onmainit fails withmemory access out of bounds.dirtied_func_exception_still_marks_ancestors_dirty: onmain,isDirty()returnsfalsefor the ancestors.dirtied_func_exception_keeps_children_in_syncdirtied_func_exception_is_rethrown_by_the_outermost_calldirtied_func_exception_is_thrown_by_the_call_that_caused_itdirtied_func_runtime_error_takes_precedenceanddirtied_func_runtime_error_keeps_children_in_sync: these throw aWebAssembly.RuntimeErrorfrom JS, because a real abort ends the Jest run.A real abort in
insertChild()itself can't run under Jest, so I checked it with this script in a separate Node process. The result matchesmain, whether or not a dirtied function is set on another node:Abort check
Checks run with Node 20 and emsdk 4.0.23 on an Apple M2:
javascript/:yarn testpasses 38 suites and 581 tests (573 before this change).yarn lintandyarn tscare clean.yarn packsucceeds, and the generated.d.tsfiles are identical tomain.yarn benchmark: the benchmarks don't set a dirtied function, so the wrappers aren't installed. The only change on that path is thetry/catchininsertChild()/removeChild(). Three runs each gave 3.7–5.3 ms per case onmainand 3.0–4.9 ms with this change.yarn format-check-javascriptandyarn format-check-cpppass. I moved my local, untrackedjavascript/.emsdkaside first, because Prettier would otherwise check it. I didn't run the Kotlin and Python checks, which need a local JDK; this change doesn't touch those files.