Commit Graph
128 Commits
Author SHA1 Message Date
Chris Tate 9621b895ce Define new runtime globals with initializers so library localization hides them
scr_cyc_types, scr_str_scan and scr_wtf8_lone were tentative definitions.
The macOS runtime build emits tentative definitions as COMMON symbols, and
Mach-O `ld -r` writes COMMON symbols back out as commons, which
-exported_symbols_list does not demote. Every runtime-localized library
archive therefore defined them next to its declared API, failing the
library-multi M1 and M7 symbol checks on macOS. Linux clang defaults to
-fno-common, so the same declarations were ordinary definitions there and
the checks passed.

Initialize the three globals explicitly, as scr_sa and
scr_stdio_write_hook already are, and add a runtime test that compiles
every runtime source with -fcommon in the plain and worker configurations
and expects no COMMON symbols, so a new tentative definition fails on
Linux too.
2026-10-14 19:48:01 -05:00
Chris Tate cfe28b7ad1 Move scriptc to Node 24.22.0 (#989) 2026-10-14 18:49:17 -05:00
Chris Tate d2a84e4a41 Write typed JSON.stringify arrays from their slots and append fixed text inline
The emitted JSON writers read each array element through scr_arr_state
and scr_arr_get_ref, retaining and releasing it, and appended every
bracket, comma and key through a runtime call. Arrays whose indices below
the length all hold values now serialize from their slots
(scr_arr_dense_slots) with borrowed elements; other arrays keep the
element walk. Fixed text is stored inline when it fits the buffer's spare
capacity, booleans write their literal inline, and numbers are formatted
directly into the buffer.
2026-10-14 18:05:50 -05:00
Chris Tate 9835ad0d75 Move scriptc to Node 24.22.0
Pin Node v24.22.0 (commit f14cd3aa7bf9, Undici 7.29.1, OpenSSL 3.5.8) in
.node-version, Dockerfile.sandbox, the docs matrix fallback, the
compatibility pin, the fetch profile, and process.version. A node-pin test
checks that these pins agree. The Node compatibility snapshot is
regenerated from the v24.22.0 API docs; the new stream_iter chapter maps
to node:stream/iter, and the SuiteContext rows no longer match the
TestContext evidence.

Runtime and compiler changes follow Node 24.22 behavior:

- fs.read, fs.readSync, and FileHandle.read validate the position before
  the zero-length return.
- fs.statfs results include frsize; Windows reads
  FileFsFullSizeInformation and reports bfree and bavail separately.
- fs.utimes, futimes, and lutimes keep nanosecond precision.
- ServerResponse sets writableFinished where 'finish' is emitted and runs
  end() callbacks before the 'finish' listeners while the socket is still
  attached; a request read to 'end' closes before its response.
- socket.write(string, 'buffer') throws ERR_UNKNOWN_ENCODING.
- URL protocol-setter rejections keep the current URL, the host setter
  no longer leaves a "/." escape when it adds a port to a "//" path, and
  new URLSearchParams(null) parses "null".
- util.stripVTControlCharacters uses Node 24.22's ANSI pattern,
  util.styleText accepts #RGB and #RRGGBB colors, and
  util.types.isDataView has its JS name and length.
- structuredClone and AbortSignal.any use the "cannot be converted to
  sequence" message.
- crypto.getCiphers, getHashes, and constants match Node 24.22.
- dns.lookup, net.connect, and tls.connect throw ERR_INVALID_ARG_VALUE for
  hosts with null bytes.
- fs.readFile validates options.buffer and refuses its valid forms;
  httpValidation and certificateCompression refuse by name.
- url.domainToASCII, url.domainToUnicode, and url.parse hostnames follow
  ada 4.0: ASCII domains are lowercased without checking their xn--
  labels, the RFC 5893 rule checks how LTR labels end and how RTL labels
  start, a Punycode label whose decoded form begins with "xn--" fails, and
  ToUnicode decodes only labels that pass the ToASCII checks. The url
  legacy test cases are regenerated with Node 24.22.

Corpus programs 5275-5279 cover the new behavior. 1640, 2283, 2599, and
3136 are updated where they pinned Node 24.15 output.
2026-10-14 17:27:02 -05:00
Chris Tate 47223bdfaa Use plain wording in docs, comments and test names (#981) 2026-10-14 12:25:43 -05:00
Chris Tate d25fa587ae Add the legacy url API and url.domainToASCII/domainToUnicode (#973) 2026-10-14 12:08:16 -05:00
Chris Tate 3f8e0bcb13 Use plain wording in docs, comments and test names 2026-10-14 11:43:44 -05:00
Chris Tate 5ef650b5e7 Merge main into the legacy url API 2026-10-14 11:30:47 -05:00
Chris Tate 23124ccdb0 Lower typed-array sort and toSorted 2026-10-14 11:15:37 -05:00
Chris Tate bd9a994060 Add the legacy url API and url.domainToASCII/domainToUnicode 2026-10-14 08:10:02 -05:00
Chris Tate 6fe81f0ce6 Sort by key for recognized numeric comparators 2026-10-14 06:48:48 -05:00
Chris Tate e932714bfa Shrink the cycle-collector object header to 8 bytes 2026-10-14 04:53:07 -05:00
Chris Tate e3d3380032 Add path.matchesGlob 2026-10-14 03:54:52 -05:00
Chris Tate e646b347de Complete node:os and add path.parse and path.format 2026-10-14 00:25:07 -05:00
Chris Tate ea6428bad6 Keep the first elements of small appended arrays in the array's block 2026-10-13 23:36:22 -05:00
Chris Tate d99e97cb70 Merge main into lone surrogate strings 2026-10-13 22:20:07 -05:00
Chris Tate 7f4f2a76fc Speed up regex replace, match and capture reads 2026-10-13 20:30:01 -05:00
Chris Tate 844ef14771 Keep lone surrogates in strings 2026-10-13 19:48:11 -05:00
Chris Tate 947f665896 Store boolean arrays as one byte per element 2026-10-13 18:45:47 -05:00
Chris Tate cbae29834a Speed up BigInt arithmetic, text conversion and small-value code (#928) 2026-10-13 13:07:19 -05:00
Chris Tate 9ed57127e5 Fix native self-host of bigint trees and guard BigInt text sizes 2026-10-13 12:29:07 -05:00
Chris Tate bf40cf4255 Compile regex literals to native matchers 2026-10-13 11:30:19 -05:00
Chris Tate 462d2c3151 Answer reflective reads of typed records without snapshots (#925) 2026-10-13 10:47:53 -05:00
Chris Tate 7763bd8927 Speed up BigInt arithmetic, text conversion and small-value code 2026-10-13 10:26:49 -05:00
Chris Tate a4bb3ff3ce Keep hole-free prefix and int32 parameter upkeep off hot paths (#922) 2026-10-13 09:43:22 -05:00
Chris Tate e04b701c1c Answer reflective reads of typed records without snapshots 2026-10-13 09:40:38 -05:00
Chris Tate 6790082742 Keep hole-free prefix and int32 parameter upkeep off hot paths 2026-10-13 09:11:57 -05:00
Chris Tate 9153285118 Probe bypassed collector types and tear down typed children directly 2026-10-13 08:50:06 -05:00
Chris Tate 1aeb04241f Reuse regex literal sites and scan plain patterns and string replacements directly 2026-10-13 07:48:24 -05:00
Chris Tate 5df589706b Skip array state bytes below a hole-free prefix 2026-10-13 02:13:46 -05:00
Chris Tate 55626fb233 Merge pull request #897 from vercel-labs/ctate/run-pr-test-f6809a
Run the PR test shards on Linux
2026-10-12 20:22:19 -05:00
Chris Tate b10c0af911 Sort dense arrays without the comparator sort snapshot 2026-10-12 18:01:14 -05:00
Chris Tate 644722e696 Run the PR test shards on Linux 2026-10-12 15:21:39 -05:00
Malte Ubl f04681f0b6 Reject absent map keys through a filter in the map header
Every live entry's hash sets two bits of a new 64-bit ScrMap.filter, so a
lookup whose bits are not both set is absent without loading the bucket
table or the entries. String-keyed lookups in tsc-ts are mostly misses
(Map.get 67% on Excalidraw, has 87%), and the filter rejects 84-88% of
the misses on non-empty maps. Deletions leave their bits; rebuilds,
compaction and clear recompute the filter from the live entries.

Lookups also return the entry index straight from the probe instead of
reading the bucket again. The filter sits after nlive, so the backend's
%ScrMapIx fields for entries and the direct index move by one.
2026-10-12 07:22:52 -07:00
Chris Tate 72cc828ab0 Merge main into perf/compact-array-header-2 2026-10-12 01:12:21 -05:00
Malte Ubl fa55ce6b07 Keep the RC entry points of array elements in one shared descriptor
Every SCR_ELEM_REF array stored its element type's retain, release and trace entry points in its own header (24 bytes), so ScrArr was 80 bytes: an 80-byte block for arrays of acyclic elements and a 96-byte block for cycle-capable ones. The three pointers now live in one constant ScrArrOps descriptor per element type, which the compiler emits once per module (@sc_arrops.*) and the runtime keeps static, and the header holds a pointer to it plus a 4-byte `cyc` flag beside `elem` that the inline RC fast paths test. ScrArr is 64 bytes: arrays take 64- and 80-byte blocks.

scr_arr_new_ref(retain, release, trace, cap) becomes scr_arr_new_ops(ops, cap); scr_set_to_arr_ref takes the result's descriptor from its caller instead of building an array from the set's key adapters. The backend names the %ScrArr fields in SCR_ARR_FIELD. The layout change bumps the runtime ABI marker to scr_runtime_abi_v13.
2026-10-11 19:46:48 -07:00
Chris Tate 31aaefb4c6 Merge main into perf/map-get-inline-retain 2026-10-11 21:31:11 -05:00
Malte Ubl 6778826d8f Keep the array growth sequence; round only exact construction to its block
Rounding every dense capacity up to its allocator block changed the growth sequence (a 48-byte block holds 5 slots, so storage went 48/96/192/384 B instead of 48/80/144/288/576 B), which moved array storage into other size classes: +3.6% simulated L1 misses on the tsc-ts self-check and +2.6%/+4.3% wall on the self-check and TypeORM. Growth is back to 4, 8, 16, ...; only exactly sized construction fills its block. Adds a runtime test for exact construction followed by appends, index writes and length extension over the spare capacity.
2026-10-11 16:30:22 -07:00
Malte Ubl eae8e02481 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-10-11 13:27:25 -07:00
Malte Ubl 112add3dce Merge remote-tracking branch 'origin/perf/gc-trace-null-skip-main' into HEAD
# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-10-11 10:17:20 -07:00
Malte Ubl 796013f9d2 Merge remote-tracking branch 'origin/perf/heap-layout-main' into HEAD
# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-10-11 10:17:07 -07:00
Malte Ubl fca66ec655 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-10-11 10:16:59 -07:00
Malte Ubl d770871069 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-10-11 10:16:52 -07:00
Chris Tate 727bdb7227 Merge pull request #849 from vercel-labs/perf/gc-widen-faster
Widen the old level's growth fraction two doublings at a time
2026-10-11 12:07:34 -05:00
Malte Ubl 554f3177ab Walk sampled bypass candidates at the nursery level in the cycle test
check_nursery_bypass's last phase releases WINDOW * SAMPLE dead rings of a
nursery-bypassed type and expects the sampled one in SAMPLE to complete a
nursery window, which turns the bypass off. That is exactly one window of
sampled candidates, so it held only while no older pass ran during the
phase. Under the growth cap's default of 16 (main's SCR_CYCLE_GROWTH_CAP,
with the cap's checks now running before the bypass checks) the promoted
rings trigger mature passes that also sweep sampled nursery candidates:
the nursery counted 4544 of its 8192 walks, kept the bypass, and the check
failed at the default and 64 caps (caps 1 and 2 passed).

The phase now runs a nursery pass after each release, as the mature check
already runs passes at exact levels, so every sampled candidate is walked
by its own level. The collector is unchanged. The check still fails with a
sticky bypass decision and without sampling, at caps 16 and 1.
2026-10-11 09:57:08 -07:00
Malte Ubl def6f86a0e Let collector types bypass the generations where they never die
Nearly every long-lived object becomes a cycle candidate once, soon after
it is built: the constructing frame's release leaves it held only by the
structure it was stored into. A nursery pass then walks it (about 75 ns per
object), it survives, and it is re-buffered for the mature level, whose
next pass walks it again. In tsc-ts single-threaded, nursery passes walked
6.0M objects on Excalidraw and 36.5M on Sentry and freed 7%; 99% of what
they walked was promoted. Most of that walking goes to types whose objects
never die in a young cycle: SymbolLinks, NodeLinks and Symbol were 2.0M of
Excalidraw's candidates and the collector freed none of them, while young
garbage comes from a few types (inference contexts and mappers, closures,
some arrays). Types with a collector yield under 0.5% are 42-55% of what
nursery passes walk.

So the collector now keeps per-thread statistics per type and level. Each
ScrCycType gains a `kind` index: emitted descriptors number themselves from
32 (FIRST_EMITTED_CYC_KIND / SCR_CYC_KIND_EMITTED), the runtime's own
families have fixed kinds below that, 0 keeps no statistics. For the
nursery and mature levels, a pass counts the candidates of each kind it
walks and the objects of each kind it frees. After every 8192 walked
candidates a kind bypasses that level for the next window if fewer than 1
in 128 of them were freed. A release that buffers an object at a level its
type bypasses promotes it past the level first, and a nursery pass that
re-buffers a survivor into a bypassed mature level moves it to the old
level. One candidate in 16 stays put, so the yield keeps being measured
and a type whose objects start dying young again is walked again after
one window.

Only the generation an object is judged in changes. Any assignment is
sound (a restricted pass counts edges from older objects as external), a
bypassing candidate stays buffered, and full passes walk everything they
did before, so the old level's bounds hold unchanged. Cyclic garbage made
only of objects of a bypassing type waits for the next pass at an older
level. Programs that never complete a window (8192 walked candidates of one
type) are collected exactly as before.

The descriptor layout is part of the emitted-code/runtime ABI, so the
program-object marker moves to scr_runtime_abi_v12.

Pass log (instrumented build, single-threaded, one run each, with the
preceding null-slot change): all collector passes Excalidraw 606 -> 313 ms
(nursery 435 -> 153), TypeORM 629 -> 383 ms, tsc-ts self-check 377 -> 215
ms, Sentry 7.61 -> 5.75 s (nursery 2.92 -> 1.04 s, mature 1.17 -> 1.02 s;
the full pass is unchanged at 3.6 s).

tsc-ts (Linux sandbox, interleaved, medians; 10 rounds, Sentry 5 rounds
pinned to 8 CPUs; both changes against int/best-v6): single-threaded
Excalidraw -8.0%, TypeORM -4.2%, self-check -4.3%, Sentry -5.3%;
--checkers 8 -12.2%, -0.8%, -2.3%, -1.6%. Peak RSS -0.8% to +0.6%.
Diagnostics identical.

Tests: test_cycle gains check_nursery_bypass, check_mature_bypass and
check_rebuffer_past_mature (a test-only entry point, scr_cyc_pass_at under
SCR_CYC_TESTING, runs passes at exact levels); each fails under the
corresponding mutation (no bypass, a sticky decision, no sampling, no freed
or walked counts, a bypassing candidate left unbuffered, survivors
re-buffered into a bypassed level). Corpus 4690 builds a long-lived
declaration graph large enough for both levels to bypass and lets objects
of the same classes die in cycles; under --sanitize the exit RC audit
proves they are reclaimed.
2026-10-11 09:39:57 -07:00
Malte Ubl 1a14263b5f Move rarely used array storage out of line
Every ScrArr carried sixteen words, seven of them (sparse slots, their
length and capacity, noncanonical numeric properties with length and
capacity, and match-result metadata) used only by sparse arrays, arrays
with numeric non-index properties and regex match results. They now live in
a ScrArrExt the array points at, allocated on first use: a dense array
shrinks from 128 to 80 bytes, and a cycle-capable one (with its 16-byte
collector header) from 144 to 96.

Runtime readers go through a shared empty ext; writers allocate it. The
backend mirror loses the seven fields, tagged-template arrays are emitted
with a NULL ext, live stream commits swap the ext pointer (which swapped all
seven fields before), and the cached typed-view refresh swaps sparse slots
and properties through scr_arr_swap_indexed_ext, keeping each array's
metadata as before. The program-object ABI marker moves to
scr_runtime_abi_v11.

A corpus program (4610-array-ext-dyn-refresh.ts, with its order baseline) pins numeric non-index properties of a dynamically typed array through its refreshed typed view, sparse slots written directly and through a live reference, truncation, and regex match metadata read directly, through `any` and after a copy.
2026-10-11 09:38:27 -07:00
Malte Ubl 6f488e13aa Shrink the cycle-collector header to 16 bytes
Every cycle-capable object carried a 32-byte header: trace and teardown function pointers, a 32-bit color, the buffered flag, the generation and a 64-bit candidate index. The two function pointers are the same for every object of a type, so the header now points at one constant ScrCycType { trace, free_fn } descriptor per type, and the remaining fields pack into the second word: u8 color, u8 gen, u16 buffered, u32 buf_index (candidate buffers stop at 2^31 entries). wasm32 pads before the descriptor, so every object-relative offset is the same on all targets.

Emitted shapes define their descriptor next to the trace and teardown functions; inline allocation stores one header word; the inline release reads buffered at obj-6 and the inline free reads the generation as an i8 at obj-7. Runtime families allocate with static descriptors. The program-object ABI marker moves to scr_runtime_abi_v10.

tsc-ts (interleaved, 5 rounds): peak RSS -8% to -11% on every app and mode; single-threaded wall -0.3% to -5.5% on top of the typed-rest-spread change.
2026-10-11 09:38:26 -07:00
Malte Ubl 4a513b9fd7 Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-10-11 09:37:04 -07:00
Malte Ubl d56348effd Merge remote-tracking branch 'origin/main' into HEAD
# Conflicts:
#	packages/compiler/test/ts7/baselines/order-parity.json
2026-10-11 09:36:22 -07:00