perf(string): fuse accumulator concat chains - #8683
Merged
Merged
Conversation
added 3 commits
August 21, 2026 01:15
Lands #8497 (refs #8410). Recognizes `s = s + a + b + ...` for proven string accumulators in local, captured, and module-global slots, and lowers the accumulator plus its suffix operands into a single rooted `js_string_append_chain` call that either reuses a uniquely-owned accumulator's spare capacity in place or allocates the complete result exactly once. The existing dynamic-add fallback is preserved wherever the string proof is incomplete. Rooting reviewed rather than assumed. Codegen wraps the operands in `with_rooted_group`, adopting the accumulator and each suffix part, then re-reads every one through the group after all lowering that can collect -- reordering alone would leave a stale argument (#8427). The runtime takes the non-collecting `string_storage_alloc_no_collect` path first, so the raw piece pointers cannot go stale there; on the collecting fallback it roots every piece in a `RuntimeHandleScope` before `string_storage_alloc` and then reloads each through `with_const_ptr` instead of reusing the pre-collection raw pointer. In-place reuse is gated on `refcount == 1 && total_blen <= capacity`, which excludes shared (refcount 0) headers, and the aliasing/overlap case has its own fallback test.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
📝 WalkthroughWalkthroughChangesString self-append chains now lower to one String append chain fusion
Estimated code review effort: 4 (Complex) | ~45 minutes Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant Codegen
participant js_string_append_chain
participant StringStorage
Codegen->>js_string_append_chain: pass rooted accumulator and suffix parts
js_string_append_chain->>StringStorage: reuse capacity or allocate complete result
StringStorage-->>js_string_append_chain: resulting string
js_string_append_chain-->>Codegen: return updated string handle
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Lands #8497 (refs #8410) — fuses
s = s + a + b + ...accumulator chains into a single rooted runtime call.Audit
This touches the two things that are non-negotiable in this area — in-place string mutation and raw payload pointers held across an allocation — so I read both paths rather than trusting the suites.
Uniqueness gate. In-place reuse is gated on
(*dest).refcount == 1 && total_blen <= (*dest).capacity. Fresh headers are created shared at refcount 0, so== 1correctly admits only uniquely-owned accumulators, and the capacity bound is checked before the copy. The aliasing/overlap case has its own fallback test (string_append_chain_falls_back_for_overlap_and_dynamic_parts).Rooting, runtime side. The fast path uses
string_storage_alloc_no_collect, so the rawpiece_ptrscannot go stale there. On the collecting fallback it roots every piece in aRuntimeHandleScopebeforestring_storage_alloc, and then reloads each one throughwith_const_ptrrather than reusing the pre-collection raw pointer:That is the #7341 API used as intended — rooting and reloading, not rooting alone.
Rooting, codegen side.
with_rooted_groupadopts the accumulator and each suffix operand, then re-reads every one viareread_emitted/rereadafter all lowering that can collect. Reordering without the re-read is exactly what left a stale argument in #8427; this does the re-read.js_string_append_chainalso matches its pre-existing siblingjs_string_concat_chainexactly in signature andextern "C"convention, so it introduces no new ABI surface.Validation
cargo fmt --all --check: pass (includingstring_payload_access_inventory.py, the project's own borrow instrument)perry-codegen --lib: 1198 passed, 0 failedperry-runtime --lib(RUST_TEST_THREADS=1): 2654 passed, 0 failedNo version-file changes; the author's
changelog.d/fragment carried through.The author's own A/B used the correct discipline — identical
-p perry -p perry-runtime-static -p perry-stdlib-staticset on both arms with archive mtimes verified after each build, five shuffled interleaved repeats, medians reported.Summary by CodeRabbit
Performance
Bug Fixes
Tests