An exported function declaration is a mutable binding, so its module can
replace it; the hooks treated only `let` and `var` exports as live and
snapshotted the function, leaving island imports on the first function.
Bind function exports like `var`, and cover a reassigned function export
in the shared-singleton corpus program.
Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
`+x`, `-x` and `~x` convert their operand with ToNumber, which calls a
user-defined `valueOf`; treating such an argument as inert let the
lowering read the island method after the conversion ran, so the call
reached a method the conversion had replaced. Only a numeric literal
operand is inert now, and the corpus program's `valueOf` replaces the
method it is called through.
A program that uses web streams and Node islands selected one
`scr_async.c` variant by condition count, so the tie between the
one-condition variants left either SCR_NATIVE_REACTIONS or
SCR_NODE_ISLANDS undefined and the hosted-loop symbols unresolved. Add
the combined variant, with a matrix test that pins the selection for
both features together and for each alone.
Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
An island signal registration is now tagged with the raw listener it
installed, so a once registration and an on registration of one function
dispatch through their own listeners instead of both resolving to the plain
function, and the once registration no longer fires indefinitely. The
removal handler forgets the entry whose raw listener is no longer on
process, so a once wrapper that removes itself leaves its runtime
registration behind no longer; duplicate registrations of one function
share a raw listener, and the most recent of those is removed.
refresh() of a timer armed before Node loads re-arms at now + the delay the
timer was created with. Its Node handle was created with the remaining delay
at handoff and a due timer had no handle at all, so the handle is replaced
rather than refreshed.
The signal program registers one function once and on; a new timer program
refreshes a timer most of whose delay passed before Node loaded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A program is nondeterministic only when every artifact that differs also
differs between two baseline builds; a deterministic difference in another
artifact still fails the gate. The runtime-only check now reads the symbols
of changed vendor archives as well as runtime objects.
Records checked from a proxy in a program that freezes untyped values no
longer fail on the proxy's integrity query: a proxy is frozen exactly when
its target is. A sloppy-mode function called with undefined or null
receives the global object that globalThis reads, so its builtin members
such as console answer before anything else reads the global object. The
new corpus programs are named instead of numbered and record their CI
costs.
A method value of a web stream class, such as a saved
WritableStream.prototype.close, called on an instance whose own property of
the same name wraps it, ran the own property again, so the wrapper recursed.
The classes' method values now call the class's method directly
(ScrDynHandleClass.method_invoke); member calls still look at own properties
first.
TransformStream resolved its start promise with a transformer start's
result without adopting a thenable, so both sides started at once and a
rejection was lost. The start promise now resolves as a promise's resolve
function does (scr_dyn_promise_resolve_with): a thenable adopts through its
resolve job, a throwing `then` read rejects, and the stream starts or errors
when it settles, in Node's order.
Lowerer.namesWebStreamClass, a cache made when first needed, is listed in
Lowerer.unjournaled.
The test compiles the three npm package twice, dev and release, and both
cases shared one 600s budget through test.each. Release compiles the whole
library into one LLVM module that scriptc-llvm-codegen optimizes and
codegens in a single process on a single core: 426s of the 477s total run
in the native-archive phase alone. Across sandbox lanes release measured
331, 335, 339, 345, 348, 350, 358, 428, 450, 511, 521 and 581s, so whole-
box speed varies about 1.7x and the slowest lanes sat within 20s of the
budget; the sanitized dev case hit 600s twice. There is no regression, the
work is fixed and single-threaded.
Generate the two cases in a loop so each gets its own timeout: 1200_000ms
for release and for dev under SCRIPTC_SAN, and the unchanged 600_000ms else.
Test names are byte-identical to the .each %s expansion, so
skipUnownedTest keeps assigning each case to the same shard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In JavaScript sources, WritableStream with its default writer and
controller, TransformStream with its controller, pipeTo and pipeThrough,
and TextEncoderStream and TextDecoderStream join the native web stream
classes, following Node 24's webstreams step by step: sinks and
transformers, backpressure and the writers' ready and closed promises,
abort with the controller's signal, the pipe's shared write requests and
its abort signal, and UTF-8 encoding and decoding across chunks settle in
Node's order. The streams take own properties, which a pipe does not use
for its own reads and writes, and the global object answers the classes
by computed name in programs that name them.
A JavaScript class that extends one of these classes keeps the SC1090
refusal, and TextDecoderStream refuses encodings other than UTF-8.
The node24-web-stream lifecycle, errors, queues, transforms, pipes,
pipe-errors, pipe-abort, tee and encoding cases now match Node.
A counted for loop converts its limit to an i64 endpoint. A limit that is
not a literal went through the double every time: converted to double,
clamped to 2^53, rounded with llvm.floor or llvm.ceil and converted back.
At the default target CPU the rounding is a libm call, which a loop such as
`for (let i = values.length - 1; i > at; i--)` paid on every entry even
when `at` was an int32 local. A limit that is an exact integer (an int32
local, an integer view, or a proven range) is its own floor and ceiling and
within ±2^53, so it is now the endpoint directly; other limits keep the
double path.
A function value with more parameters than the slot it flows into is
assignable in TypeScript exactly when the extra trailing parameters are
optional, and a call through the slot passes no value for them, so the
callee sees undefined as it does in JavaScript. funcCoerceAdapter refused
every such pair, so a callback parameter forwarded into a narrower slot
fell through to requireExactShape and failed with SC2003 ("union
conversion has no unambiguous layout mapping") even though the checker
accepted the program.
The adapter now supplies each omitted extra parameter with omittedArgFor
(the undefined arm of the parameter's ABI union, or the checked-dynamic
undefined). A pair whose extra parameter cannot hold undefined stays
refused, and an extra required parameter remains a checker error.
Only pairs that previously returned null reach the new code, so programs
that compiled before are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Response.clone and Request.clone tee the body. The tee branches of a body
read from a JavaScript ReadableStream did not keep that body's chunk check,
so consuming the original or the clone took string chunks as text. The
check is now a flag on the body's stream that its tee branches inherit, and
consuming either branch rejects with Node's TypeError "Received
non-Uint8Array chunk".
When Error.captureStackTrace evaluates a JavaScript constructorOpt (a getter,
a call, or this.constructor with a constructor getter), V8 has already
evaluated the target. The lowering kept the target in a local only when its
expression was not a plain read, so a constructorOpt that assigned the target
variable made the capture install the stack on the new value. The target is
now always kept in a local before the constructorOpt runs.
Under Node.js, publish() deep-freezes everything it reaches, so
Object.isFrozen and Object.isSealed return true and Object.isExtensible
returns false for a published object, array, Map or Set. Native builds
already refused writes into published values, but answered these queries
as for an unpublished value: publication marks an object immortal and sets
no frozen bit.
- record.isFrozen, array.isFrozen and the extensibility test of Maps and
Sets also read an immortal rc word as published, the test of the
published write guards, for the types the program may publish
(PublishedTypes). Other types emit the code they did.
- scr_dyn_is_frozen, scr_dyn_is_sealed and scr_dyn_is_extensible answer
for a published object, array, Map or Set, or a checked view of one,
before materializing the view.
A nullable class field (Tag | null) stores the class pointer itself, whatever form the union takes elsewhere. When the same union was also a Set element or a Map key, it was a boxed union outside class fields, and the frame's cleanup of a frame class released the field's raw pointer with scr_union_release. classReferenceFields now gives each field its storage type (NullableRefFields.storageType), as the heap teardown does, so the scope exit calls the class's release.
A class instance could live in its function's frame only when its class
had scalar fields only and CallLifetimes proved the binding was only read.
Per-call state objects whose fields own arrays, maps and other objects,
and whose methods and helpers store into them, stayed on the heap.
frame-escapes.ts proves which locals never leave their frame: every use
keeps the value in the frame (region-ir.ts keepsInFrame), a const binding
of it in the same function shares its fate, and a closure that captures it
stays in the frame and keeps the captured binding there. Parameter and
capture facts are solved optimistically over the call graph with a
worklist; async functions, generators and module code get none.
A frame class is a standalone class whose constructor does not let this
escape. Its instance now lives in frame storage when it is bound to such a
local, when it is passed in place to a parameter that stays in the
callee's frame, or when closures that stay in the frame capture its
binding. The frame owns the reference fields: they start empty, union and
dyn fields start at undefined, and the binding's scope (or the call's
statement frame) releases them on every exit. A cycle-capable class keeps
its collector header in front of the object, never a candidate.
setTimeout and setInterval passed their delay to the F64 timer slot
unconverted, so a delay typed number | undefined failed with SC2003. Such
a delay is an optional `ms?: number` passed on, or a number[] element
read, whose storage keeps the out-of-range undefined. Node converts the
delay with ToNumber, so undefined is NaN, the omitted delay. A union
delay now goes through lowerOptionalNumber, as Math arguments already
do. Other unions still reach the lib boundary's refusal, and number
delays are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tools field (Tool[]) and the all global ((number | Callbacks)[]) hold unions that canonical mapping maps to the checked dynamic, so the canonical fallback now degrades them: they are dynamic slots again, with SC6104 warnings and attributed operations, as these tests expected before the fallback classified them as exact.
A JavaScript ReadableStream given to Response, Request or fetch as a body
was converted to the string "[object ReadableStream]". The fetch unit now
reads such a stream through a reader of its own. A locked or disturbed
stream gives Node's TypeError, and the chunks must be Uint8Array
when the body is consumed ("Received non-Uint8Array chunk").
instanceof against a web stream class held in a variable or passed as an
argument walked no prototype for handles and answered false. It now starts
at the class prototype of the handle. The fetch unit's own streams, readers
and controllers, such as Response.body, are instances of the JavaScript
classes, so `response.body instanceof ReadableStream` is true again.
These paths, and the fetch reader functions' hand-off to web stream
readers, are in SCR_HANDLE_CLASSES variants of scr_json.c and scr_fetch.c,
which only programs with JavaScript web streams link; other programs that
use fetch build to the same size as on main.