Skip to content

runtime: literal-aware concat, inline operand checks, pointer push gate (string-build -21%) - #13450

Merged
proggeramlug merged 3 commits into
mainfrom
runtime-string-concat-slice3
Oct 29, 2026
Merged

proggeramlug merged 3 commits into
mainfrom
runtime-string-concat-slice3

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

What

String slice 3 for 30-string-build / 24-map-set: three generic operation fixes on the "lit" + n + "lit" build and the pointer push.

  • A. operand checks on live bits. ToPrimitive and the Symbol check on a + operand sit behind one inline test of the value's bits (plain number, heap string and inline string are primitive and never a Symbol). A literal, a proven runtime string or a value that is non-pointer by construction carries no check at all. Evaluation order is unchanged (head pair converted, then the Symbol checks, then each later part).
  • B. literal operands as constant data. New runtime entry js_string_concat_parts: a literal operand is {address of its text in the program image, byte_len | utf16_len << 32} (the text is the image-resident string object the binary already carries, nothing new is emitted); value operands are {bits, -1}. Exact-u32 numbers are formatted straight into the result (digit count sizes the allocation, a two-digit-at-a-time formatter writes at the copy position), small pieces copy without libc memcpy, surrogate canonicalization runs only when a piece carries a lone surrogate. Same single collection point as js_string_concat_chain (no-collect allocation, else root the heap parts). "lit" + number uses js_string_concat_lit_value_box, which returns what js_string_concat_value_box returns and consults the concat memo exactly where today's header-flag admission does (image-resident literals fail that test today, so executables never consult it; library outputs, whose literals are created at run time, still do; codegen passes that fact in meta bit 63).
  • C. pointer push gate. The perf(codegen): put the numeric array push's GC bookkeeping behind one live test (push_num 0.149 -> 0.069) #7839-style header test for pointers: the existing pointer arm admitted only POINTER_TAG; it now also admits a heap string. One header test (SIDE|ALL_POINTERS, raw-f64 and element-shape bits clear) plus one refcount read skip the layout note, the numeric note and the demote (the demote still runs for an unshared string). The write barrier is unchanged.

Also: scripts/gc_root_dominance_check.py lists the two new collecting entries; runtime_abi.tsv regenerated; a pre-existing duplicate byte_end field in stmt/indexed_owner_loop_tests.rs (main's cargo test -p perry-codegen --lib did not compile) is fixed.

Result (qb9, x86-64-v3, KEEP_SYMBOLS, merged with origin/main d38e538)

witness Ir main Ir head delta cycles:u n5 median
30-string-build 9,134,276,718 7,195,048,006 -21.2% -22.2%
24-map-set 4,527,485,516 3,981,182,244 -12.1% -12.9%
31-json 19,461,087,478 19,391,274,014 -0.4% +0.2% (noise)

n5 instructions (medians): 30: -21.5%, 24: -12.1%, 31: -0.4%. Node parity 22/22. GC_LOG schedule and stdout byte-identical to main on 30, 24 and 31 (minus pause time); n5 max RSS 30: ~239.1 MiB both arms, 24: ~28.2 MiB both. .text +273 bytes on 30, +2.9 KB on 24 (size).

Per-op (30, 4M calls): js_string_concat_chain 535 Ir/call + ToPrimitive 65 + Symbol 24 = 624 -> js_string_concat_parts 314 Ir/call (digits inlined). The design predicted 125; the floor (-21.3%) is what landed. The remaining cost is the allocation fast path (~70 Ir inline), the per-part classify loop, and the digit pairs.

Tests

  • runtime: string 252 (244 + 8 new, parts vs chain on every value kind, surrogate pairs across literal/value boundaries, empty operands, 32 parts, long results, the cold rooted copy, lit_value_box vs value_box incl. memo eligible/ineligible, exhaustive digit formatter 0..10^7 + 200k random u32), JSON 171.
  • codegen lib 2585 (new: literal parts carry no Symbol check; calls only in cat.* arms; admitted push arm has no note calls, refcount-gated demote, slow arm identical; the admission branch must conjoin the header proof), integration 23.
  • gap: test_gap_jst5_concat_parts (Symbol operand throws and in which order, valueOf/toString/Symbol.toPrimitive across 3+ parts, -0/NaN/Infinity/large/negative ints, surrogate pairs and lone surrogates across pieces, empty operands, 15..3M byte results) and test_gap_jst5_push_gate (holey, shrunk/grown, popped/sorted/spliced, frozen/sealed/non-extensible, non-writable length, prototype setter, subclass, Proxy, unique accumulator pushed then appended, GC pressure between pushes). A string/concat/template/sso/surrogate/primitive gap subset (86 files): 80 pass, the same 6 fail on main and head (message-wording differences, not a regression).
  • sabotage, each caught: A's inline test (a different tag in the skip set) fails the Symbol/valueOf lines of the concat gap test and the IR test; C's refcount test (never demote) fails the unique-accumulator lines of the push gap test; C's header test (admit on tag alone) fails the_admission_branch_is_conditioned_on_the_header_proof.
  • Windows: cargo xwin build -p perry-runtime-static + sidecars on perry_runtime.lib pass.

Startup (exact callgrind Ir, n3 per arm, deterministic, ASLR off, symmetric control)

Both arms: x86-64-v3, KEEP_SYMBOLS, same paths/env, Node stdout parity. Raw counts, then the accepted symmetric address-independent-key control (the JMS landing-009 measurement-only patch applied identically to main and head: constant hashes for canonical_keys by_addr/edges, shapes indices/families/by_facts, path registry, perex_cache; seed/nonce/maps normalization of the JMS fixtures; the fn_metadata part is not needed since main no longer keys names by address). Controlled head <= main on every witness.

witness raw main raw head delta controlled main controlled head delta
hello 140,644 140,644 0 144,909 144,909 0
commander 21,223,242 21,221,523 -1,719 111,079,762 111,078,898 -864
commander --help 22,930,004 22,931,513 +1,509 117,388,292 117,387,659 -633
fastify 163,395,296 163,266,212 -129,084 2,914,897,499 2,914,763,789 -133,710
Richards 42,279,470 42,279,363 -107 369,153,053 369,152,939 -114

The one raw positive (commander --help +1,509) is the address-keyed layout artifact in canonical_keys/hashbrown probes (call counts of every changed function equal; executed concat work is lower); under the control it is -633. The product fix (stable identity for canonical-key/shape tables) is a separate lane.

…te (string-build -21%)

A: ToPrimitive and Symbol checks on + operands run behind one inline test of the live bits (plain number, heap string, inline string skip), and a literal or provably non-pointer operand carries none.
B: js_string_concat_parts takes literal operands as rodata address + lengths; two-digit integer formatter written straight into the result; js_string_concat_lit_value_box for a literal prefix + number, consulting the concat memo exactly where the header-flag test admits it today.
C: the pointer-push gate admits a heap string: one header test, refcount read, demote only for an unshared string; write barrier unchanged.
@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Caution

Review failed

The pull request is closed.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@proggeramlug
proggeramlug marked this pull request as ready for review October 29, 2026 04:06
@proggeramlug
proggeramlug merged commit 88ed1e5 into main Oct 29, 2026
88 checks passed
@proggeramlug
proggeramlug deleted the runtime-string-concat-slice3 branch October 29, 2026 04:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant