The hosted loop's scheduling entries (a tick, a timer or an immediate goes
through the bootstrap's helpers; a checkpoint drains ticks and microtasks)
and island process-event listeners called the bridge on whatever stack the
runtime was on: the main thread's for the first checkpoint, or an async
fiber's. JavaScript then ran there, and a collection it triggered failed
V8's "Check failed: isolate_->IsOnCentralStack()" (node-island-handles,
about 1 run in 15 sanitized: promise reactions run by the first checkpoint
on the main stack allocated a microtask). They now run on Node's stack like
every other entry, below the mark when they come from a fiber.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
structuredClone in an island of a value holding native records, class
instances or objects (interceptor objects V8's serializer cannot clone)
clones a plain copy of that data, as Node clones the objects themselves.
An island array of primitives or strings (or arrays of those) returned
directly or through a promise crosses as a copy, which the checked cast
then meets, instead of failing the cast as an object handle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
g01 (twenty fs.stat round trips against a 2 ms timer), p10 (two 0 ms
timers armed on either side of an island call) and s06 (fs callbacks and
timers pending when Node loads) each failed one CI run in a different
lane: their order depends on how long the island call or the I/O takes,
which differs between the sides and grows under ASan. Their native runs
now have to produce the lines of one of Node's outputs, like the other
race programs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI's island lanes fall back to the development pack (dev-pack.json: Node
24.22.1, no package.json) until the libnode-packs cache exists, so the
smoke test, which verifies a @scriptc/libnode-<platform> package against
the 24.22.0 pin, failed there. It now runs only when SCRIPTC_LIBNODE names
a package directory. The pack package versions follow the 0.4.2 release
(scripts/sync-versions.mjs), which pin.test.ts checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sanitized seed compiles the compiler's 700 MB LLVM module with one clang
process under AddressSanitizer. The compile peaks near 14 GB while the Node
process that emitted the module still holds about 6 GB, which exceeds the
runner's 16 GB of memory. The job now enlarges swap to 24 GB before it builds
the seed, as the sanitized emitter job does.
Release builds no longer import runtime bitcode, so program code called scr_map_retain_v for every Map read out of a container and scr_map_size for every size read. A Map retain now uses the emitted increment helper, as string retains do, and Map.size reads the live count through an internal helper.
A number parameter that every call site proves an int32 reaches the
function's i32 entry. One that some caller passes as an unproven number,
such as a value read from a worker message or parsed from text, kept its
double, so every loop it bounds compared an integer counter with a double
limit and the values derived from it stayed doubles.
When such a parameter bounds a numeric loop (arithmetic, element and
field reads and writes, Math calls, no allocation or other calls), the
function now gets a copy of its body. Its own body first tests
each such parameter for an int32 that is not -0 and, when all pass, calls
the copy with `p | 0`, the same double. The copy's only caller proves
those parameters int32s, so it gets the i32 entry and integer loops.
Other values run the body as before.
Only plain synchronous functions of at most 2048 IR nodes are versioned:
not class members or generated helpers, not capturing, not the entry,
not dynamic-tier, not calling themselves, and at most 16 per module. A
function holding the only call site of another function is kept whole
unless that callee only computes a number or boolean from scalar reads:
a second call site can stop LLVM from inlining the callee and leave the
records it builds in memory. Library builds and modules that observe
call stacks are left as they are.
tsgo has no getContextualTypes or getResolvedSignatures. Each prefetch
asked for them anyway, and a failed chunk was split and retried down to
single nodes, so a tsgo build paid about two failed requests per
prefetched node. The checker facade now remembers a batch method the
server answers with "unknown API method" and asks those queries node by
node from then on.
A typed record encoded from its field hooks queued a plain-object field on
the visit stack and went on to the next field, so getters on later fields
ran before the properties of earlier ones were read. Node's serializer
finishes each field, nested properties included, before the next.
The record path now drains the visit stack down to where it started after
each field, so properties are read in Node's order. Corpus 7139 posts a
record whose fields hold getter-bearing objects and prints the read order.
A function expression stored in a function type with more parameters
folds `arguments.length` to that type's parameter count, the count every
call through the slot passes. A named expression that calls itself
through its own name passes only its declared parameters there, so no
constant answers both calls. Such a read now reports SC1090 with the
reason, and expressions that do not refer to their own name keep the
fold.
The trip count that bounds a follower's range was the driver's span over
its step, computed in doubles. A span above 2^53 - 1 can round down: from
-(2^53 - 1) to below 2^52 - 1 the span 3 * 2^52 - 3 becomes 3 * 2^52 - 4,
so a follower that reaches 2^53 got the range limit 2^53 - 1 and ran as an
unchecked integer. Such a span now bounds no trip count, and the follower
is tested at run time (or the loop is refused when every value is known).
Below it the quotient rounds to at least its floor, and the follower's
products and sums are exact within the safe range.