Short string concatenations are a common allocation churn pattern in JS
execution. Many of these results are immediately observed as flat
strings, so the intended win is to avoid spending GC and flattening work
on an intermediate representation that does not carry its weight.
Microbenchmark:
const n = 10_000_000;
function bench(a, b) {
let total = 0;
for (let i = 0; i < n; ++i)
total += (a + b).length;
if (total !== n * 2)
throw new Error(String(total));
}
bench("a", "b");
Measured with hyperfine against the same build with the fast path
disabled:
baseline: 822.7 ms +/- 25.7 ms
optimized: 385.5 ms +/- 21.5 ms
speedup: 2.13 +/- 0.14 times faster
Teach the PrimitiveString substring creation path to return the
VM's preallocated single-character ASCII strings instead of always
allocating a deferred Substring.
This keeps one-code-unit ASCII substrings on the same fast path as
direct string creation, including callers like charAt and indexed
string property access.
Return JS::Substring objects from the builtin regexp exec and split
paths instead of eagerly copying UTF-16 slices into new strings.
Matches, captures, and split pieces can now point back at the original
input until someone asks for the string contents.
Add focused runtime coverage for UTF-16 captures and regex split
captures so these lazy slices stay exercised.
Introduce JS::Substring as a lazily materialized PrimitiveString
variant that stores an originating string plus a UTF-16 offset and
length. This makes substring creation cheap while still reifying to
a normal string when character data is requested.
Track which short strings actually live in the VM caches so lazily
resolved ropes and substrings do not evict unrelated cached strings
when they are finalized. Add focused unit tests for nested ranges,
rope-backed substrings, surrogate boundaries, and cache behavior.