[web_ui] Optimize skwasm text layout and path decoding to eliminate dynamic boxing churn under Wasm#186978
Conversation
…ynamic boxing churn under Wasm Avoids heap allocations and GC sweeps caused by dynamic boxing of primitive integers (struct allocations) inside high-frequency paragraph layout, segmenter, and line-breaking loops. 1. Added zero-allocation conversion extensions to raw_memory.dart: - Pointer<Uint8>.asUint8List - Pointer<Int8>.asUint8List (with explicit bitwise masking & 0xFF to guarantee signed-to-unsigned range conversion) - Pointer<Uint32>.asUint32List These extensions copy native array buffers directly into unboxed Dart typed list classes via flat, zero-allocation loops. 2. Optimized skwasm paragraph.dart: - Replaced generic closure List<int>.generate loop in _addSegmenterData with clean Pointer.asUint8List extension. - Replaced generic List<int>.generate loop in missing font code point search with codePointBuffer.asUint32List. 3. Optimized skwasm path.dart: - Replaced generic List<int>.generate loop inside toSvgString with buffer.asUint8List. 4. Optimized line_breaker.dart: - Replaced generic Set<int> lookups (_kNewlines, _kSpaces) inside breakLinesUsingV8BreakIterator with top-level pattern-based switch expressions (_isNewline, _isSpace). These compile directly to raw WebAssembly control-flow jump tables operating on unboxed registers. Fixes flutter#186972
|
It looks like this pull request may not have tests. Please make sure to add tests or get an explicit test exemption before merging. If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. If you believe this PR qualifies for a test exemption, contact "@test-exemption-reviewer" in the #hackers channel in Discord (don't just cc them here, they won't see it!). The test exemption team is a small volunteer group, so all reviewers should feel empowered to ask for tests, without delegating that responsibility entirely to the test exemption group. |
This comment was marked as outdated.
This comment was marked as outdated.
There was a problem hiding this comment.
Code Review
This pull request optimizes memory operations in the web engine by introducing pointer extensions for typed list conversion and replaces set-based lookups with switch expressions in the line breaker to prevent integer boxing in dart2wasm. Feedback was provided regarding missing documentation for the new public extensions, as required by the repository style guide.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request replaces List<int>.generate and Set<int> lookups with specialized pointer extensions and switch expressions to avoid integer boxing in dart2wasm. Feedback focuses on adhering to Dart naming conventions by using the to... prefix for methods that create copies, using /// for all documentation comments per the Flutter style guide, and optimizing data copies using setRange with asTypedList.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request optimizes buffer handling and lookup operations within the web engine. Specifically, it replaces manual list generation with asTypedList for memory efficiency and converts Set-based lookups to switch expressions to avoid primitive boxing in dart2wasm. Feedback indicates that the removal of the codeUnitList variable in paragraph.dart may cause a compilation error in subsequent logic, and a suggestion was made to restore the variable using the optimized approach with explicit type casting.
|
test-exempt: code refactor with no semantic change |
|
Do the "Comparative Telemetry Matrix" numbers come from any of our existing benchmarks? If not, can we add one? |
eyebrowsoffire
left a comment
There was a problem hiding this comment.
I think overall, this change seems fine to me, but I noticed the following:
In the PR description, everything described in section 1 seems inaccurate or outdated:
Direct Zero-Allocation, Zero-Copy Memory Views:
Replaced generic closures and List.generate copy loops inside paragraph.dart and path.dart with the built-in asTypedList() view from dart:ffi.
Because the resulting typed list views are consumed strictly synchronously (by utf8.decode and FallbackFontService.addMissingCodePoints) before the stack scope is restored, utilizing direct views is 100% safe and achieves the absolute theoretical ceiling of 0 heap allocations and 0 byte copies.
For the Pointer buffer inside path.dart's toSvgString, the pointer is cast to unsigned bytes first via .cast().asTypedList(length). This reinterprets signed bytes to positive unsigned bytes (
0
to
255
) entirely at the compiler level with 0 runtime cost, ensuring complete safety under utf8.decode without any custom translation loops.
This is not using the "built-in asTypedList() view from dart:ffi". It is still doing a copy, it's just copying to a Uint8List instead of a List<int>. The other two comments talking about "utilizing direct views" and using cast<Uint8> are just not accurate, this change does neither of those things.
In addition, I think the performance metrics that are reported here in the "Comparative Telemetry Matrix" aren't particularly convincing to me. In situations where there is a very marginal improvement that could be due to noise, it celebrates it as a win, and then in the case where it shows a marginal regression it just dismisses it as "comparable". Both situations are probably just noise but it's hard to say off of a single benchmark run.
That being said, the analysis of the output wasm bytecode is pretty convincing that it's better to use typed lists instead of List<int> for this purpose, so this is probably a good change overall. We might just want to change the PR description (and therefore the eventual commit message) to be a bit more accurate before merging.
we have a couple of existing test suites and benchmarks that track this pipeline:
The dynamic telemetry matrix reported earlier was captured during long paragraph iterations in the browser. However, as @eyebrowsoffire notes, benchmark intervals can be subject to baseline noise. The absolute proof for this optimization lies in the WebAssembly Text (WAT) bytecode diff, which confirms the complete elimination of heap allocation instructions (struct.new $BoxedInt) from the compiler output inside the layout loops. |
|
autosubmit label was removed for flutter/flutter/186978, because - The status or check suite Mac tool_integration_tests_4_5 has failed. Please fix the issues identified (or deflake) before re-applying this label. |
yjbanov
left a comment
There was a problem hiding this comment.
LGTM if LGT @eyebrowsoffire
|
autosubmit label was removed for flutter/flutter/186978, because - The status or check suite Mac mac_unopt has failed. Please fix the issues identified (or deflake) before re-applying this label. |
|
autosubmit label was removed for flutter/flutter/186978, because - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label. |
flutter/flutter@e70534d...b05a9d7 2026-05-29 engine-flutter-autoroll@skia.org Roll Skia from 47155534833e to d9d6b440c4e7 (1 revision) (flutter/flutter#187301) 2026-05-29 engine-flutter-autoroll@skia.org Roll Skia from f93ed13d77fb to 47155534833e (4 revisions) (flutter/flutter#187291) 2026-05-29 kevmoo@users.noreply.github.com [web_ui] Optimize skwasm text layout and path decoding to eliminate dynamic boxing churn under Wasm (flutter/flutter#186978) 2026-05-29 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from SBpmmPxqx3lAvGojE... to jMR_VXQi07kAk8vbR... (flutter/flutter#187279) 2026-05-29 burak.karahan@mail.ru Remove Material import from sliver tree rendering test (flutter/flutter#187000) 2026-05-29 bdero@google.com [Impeller] Remove the y_coord_scale Y-flip plumbing (flutter/flutter#187224) 2026-05-29 31859944+LongCatIsLooong@users.noreply.github.com Add/remove overlay child RenderObject from the tree in `attach`/`detach` (flutter/flutter#186564) 2026-05-29 engine-flutter-autoroll@skia.org Roll Skia from fcfe5975c945 to f93ed13d77fb (4 revisions) (flutter/flutter#187273) 2026-05-28 evanwall@buffalo.edu Handle complex RSE rendering in the uber SDF pipeline (flutter/flutter#186434) 2026-05-28 engine-flutter-autoroll@skia.org Roll Dart SDK from 082191101fcc to 683322426411 (2 revisions) (flutter/flutter#187270) 2026-05-28 mvincentong@gmail.com Clarify route transition animations (flutter/flutter#186552) 2026-05-28 116356835+AbdeMohlbi@users.noreply.github.com document that the default Key is null and explain proper usage in list diffing (flutter/flutter#185197) 2026-05-28 srawlins@google.com [flutter_tools] Use super parameters in missed spots (flutter/flutter#186197) 2026-05-28 mr_nadeem_iqbal@yahoo.com docs: Document MediaQueryData.alwaysUse24HourFormat on macOS, Windows, Linux, web (#160664) (flutter/flutter#186642) 2026-05-28 engine-flutter-autoroll@skia.org Roll Skia from 5493e4c144cd to fcfe5975c945 (3 revisions) (flutter/flutter#187256) 2026-05-28 30870216+gaaclarke@users.noreply.github.com Shares opengles golden context (flutter/flutter#187243) 2026-05-28 jason-simmons@users.noreply.github.com Update the Curl CIPD package in .ci.yaml to version 8.20.0 (flutter/flutter#187133) 2026-05-28 737941+loic-sharma@users.noreply.github.com Improve SizedBox's docs (flutter/flutter#187208) 2026-05-28 bdero@google.com [Impeller] Support instanced rendering across all backends (flutter/flutter#186653) 2026-05-28 43054281+camsim99@users.noreply.github.com [Android] Reset system UI visibility flags when setting edge-to-edge mode (flutter/flutter#187207) 2026-05-28 engine-flutter-autoroll@skia.org Roll Skia from a38708fb7926 to 5493e4c144cd (7 revisions) (flutter/flutter#187241) 2026-05-28 30870216+gaaclarke@users.noreply.github.com Turned on impeller by default on macos (flutter/flutter#186546) 2026-05-28 mvincentong@gmail.com Clarify lazy scroll extent docs (flutter/flutter#186864) 2026-05-28 mdebbar@google.com [web] Fix WebParagraph locales test (flutter/flutter#186813) 2026-05-28 engine-flutter-autoroll@skia.org Roll Packages from 4b424d7 to 10cbdc5 (3 revisions) (flutter/flutter#187238) 2026-05-28 engine-flutter-autoroll@skia.org Roll Dart SDK from f3db7b7d9801 to 082191101fcc (8 revisions) (flutter/flutter#187235) 2026-05-28 engine-flutter-autoroll@skia.org Roll Skia from 32acea791248 to a38708fb7926 (1 revision) (flutter/flutter#187221) 2026-05-28 bdero@google.com [Flutter GPU] Add r32Float and remove Apple-only XR pixel formats (flutter/flutter#187069) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC boetger@google.com,stuartmorgan@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Avoids heap allocations and GC sweeps caused by dynamic boxing of primitive integers (struct allocations) inside high-frequency paragraph layout, segmenter, and line-breaking loops.
Optimized Local Typed List Copy Loops:
List<int>.generatecopy loops insideparagraph.dartandpath.dartwith specialized local pointer extensions (toUint8List/toUint32List).Pointer.asTypedListis not supported at runtime underdart2wasmin the Web Engine's external memory layout (since FFI allocations are mapped to Skwasm's C++ heap rather than the standard Dart heap, leaving the global FFI helper null at runtime).Uint8List/Uint32List) and copy elements using unboxed, register-level pointer lookups.length - 1down to0). Compiling this to WebAssembly translates the loop condition to a direct comparison against a constant zero register (i64.ge_stoi64.const 0), eliminating a register load instruction (local.get $length) inside the high-frequency loop body on every iteration.dart2wasm, the copy loops are fully inlined and compile to raw WebAssembly array loads and stores (i32.load/i32.store) with 0 generic closure callbacks and 0 dynamic integer boxing ($BoxedInt heap allocations).Pointer<Int8>buffers (such as inpath.dart'stoSvgString), the copy block explicitly masks values with& 0xFFto convert them to positive unsigned bytes (FormatExceptionissues underutf8.decodeat zero memory overhead.Pattern-Based Switch Expression Comparisons:
Set<int>lookup collections (_kNewlines,_kSpaces) insidebreakLinesUsingV8BreakIteratorwith pattern-matching switch expressions (_isNewline,_isSpace).dart2wasm, this completely eliminates the dynamic boxing of checked primitivecodeUnitintegers into heap-allocated$BoxedIntwrappers (originally required for genericSet.containslookup parameters).i64.eqandbr_if) rather than an indirect jump table (br_table), avoiding binary size bloat while executing entirely on CPU registers with 0 heap allocations.Fixes #186972