Commit Graph
4679 Commits
Author SHA1 Message Date
Malte UblandClaude Opus 5.5 0496f0eb25 Lower Array.from(typedArray, mapper)
`Array.from(bytes, f)` had no lowering — only the mapper-less
`toArray` — so every mapper over a typed array fenced. It lifts a loop
that reads each element through the typed array itself, because Node
iterates live: a mapper that writes the source is visible to the
indexes it has not visited yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:37:51 -07:00
Malte UblandClaude Opus 5.5 a4a84f59da Resolve a promise-or-unit operand in Promise.resolve
`Promise.resolve(p)` over `Promise<void> | void` — an async callback's
declared result — was refused: the maybe-promise detection required
every non-promise arm to carry a value, while a nullish arm is exactly
what `Promise.resolve` fulfills as undefined. `promiseOrUnitUnionOf`
names that shape, and the resolution reuses the existing adopt-or-
fulfill path, so the promise arm passes through unchanged
(`Promise.resolve(p) === p`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:37:50 -07:00
Malte UblandClaude Opus 5.5 bf28595035 Lower promise methods called through an optional chain
`p?.catch(h)`, `p?.then(h)` and a guarded chain tail (`f()?.then(h)`)
were refused: the promise-method lowering rejected every
`questionDotToken` instead of asking whether the optional chain was
currently being lowered, so the guarded body never reached it. It uses
`chainBlocked` now, like every other lowering that rides the chain.

The call that ENDS a chain checker-types as the chain's undefined-armed
union (`Promise<T> | undefined`) while this lowering builds the promise
arm, which `finishOptionalChain` wraps back into the union. The
handler's result type takes that arm, so the handler is typed against
the promise it actually handles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:37:50 -07:00
Malte UblandClaude Opus 5.5 279d42e562 Lower a discarded undefined catch handler
`Promise<void>.catch(() => undefined)` tripped the IR validator's
bare-unitLit rule: the handler body lowered as a bare expression, and
`undefined` has no standalone IR value. The handler's value is
discarded, so lower the body as a statement — the same rule a
discarded `undefined;` statement already follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:37:49 -07:00
Malte UblandClaude Opus 5.5 efc22a2fd3 Box user module namespaces as dynamic values
A `file:` module namespace handed to typed code now boxes as Node's
namespace object instead of the "converting typed values to 'unknown'"
fence: `const u: unknown = ns`, `take(ns)`, a `Promise<typeof
import("./m")>` widened to `Promise<unknown>` (or `Promise<unknown> |
undefined`), and `await import("./m")` in an `unknown` slot all see one
non-extensible null-prototype object per module whose own properties are
the module's exports in Node's sorted order. They are enumerable
read-only accessors whose reads call the compiled namespace getter, so
they stay live, and the object carries Symbol.toStringTag "Module",
Node's strict-mode write TypeErrors, the not-extensible add error and
the ToPrimitive throw. `node:` builtins have no compiled export table
and keep their SC1101 fence.

The object is built by a new gated runtime unit,
packages/runtime/src/scr_module_namespace_dyn.c, selected by the
`moduleNamespaceDyn` runtime feature (`module.namespaceDyn` in the IR).
It adds no field to ScrDynHandleOps and no branch to a shared unit: the
core reaches it through the ordinary SCR_DYN_OBJ paths, so key listing,
for-in, `in`, JSON, identity, property reads and assignment all reuse
the existing dispatch. The compiler reuses the getter lifted for
module.namespaceValue (existing programs' IR is unchanged) and adds
moduleNamespacePromiseDynValue, one lifted async mapper per module for
the promise slots.

Zero cost: a program that never boxes a namespace links exactly what it
did. The zero-cost gate reports every program identical, with
byte-identical IR and LLVM; the runtime pack's only difference is the
new unit (0 changed runtime functions), and among the namespace corpus
programs only 8412 links it.

Evidence: tests/corpus/8412-module-namespace-untyped is a differential
program against Node in both the plain and sanitized lanes, and the
diagnostics snapshots and TypeScript 7 baselines stay unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:37:45 -07:00
Chris Tate 35eda071b2 Give JavaScript fetch bodies the members of a web ReadableStream (#2459) 2026-10-28 06:41:21 -05:00
Chris Tate 0f47b9cbab Answer builtin globals read through a sloppy-mode function's this by key
A sloppy-mode function called with undefined or null as this reads the
global object. Element reads of that this, or of a variable it initializes,
now reach the program's lookup too: a literal key (`this["Map"]`) adds that
name, and a computed key (`this[name]`) installs the lookup for every
declared global, as `globalThis[name]` does. Before, a literal key read the
opaque identity token and a computed key threw SC2020.
2026-10-28 06:05:39 -05:00
Chris Tate 98f5926119 Update bodyUsed when a body's web view starts a read, and link the view for untyped uses
After a fetch body's handle gained its web view, a read, cancel or pipe
started on the view left Response.bodyUsed false until the view's pull
reached the body, where Node's bodyUsed reads the stream's disturbed state
at once. bodyUsed now also answers the view's disturbed state, which the
web streams' bridge reports (ScrWebStreamBridge.disturbed).

A program that names none of the web stream classes linked them only for
member accesses the checker resolves to ReadableStream, so body.tee() or
for await over a body inside a function whose parameters have no type
threw a TypeError. The same member names on an untyped receiver, and
for await over an untyped value, now link the classes in a program that
links the fetch unit.

Adds corpus program fetch-body-web-stream-helpers.mjs, with its corpus
cost and preflight/order baseline, and bodyUsed cases to
fetch-body-web-stream-linked.mjs.
2026-10-28 05:29:42 -05:00
Chris Tate d6bb226f48 Pass numbers to directly called untyped functions as jsvals (#2458) 2026-10-28 03:59:31 -05:00
Chris Tate 0325b921aa Compile pre-module constructor functions as classes and namespace functions as direct functions (#2175) 2026-10-28 03:57:06 -05:00
Chris Tate e85af20b7e Defer evaluating Node island modules until the program uses them (NI-7) (#1901) 2026-10-28 03:53:45 -05:00
Chris Tate 17efed329f Give JavaScript fetch bodies the members of a web ReadableStream
In JavaScript, a Response or Request body answered getReader, cancel and
locked, and every other ReadableStream member was undefined: tee,
pipeTo, pipeThrough, values and for await threw TypeErrors where Node's
body is a ReadableStream.

In programs that link the web stream classes, the body's handle now
stands for a ReadableStream once any other member is used
(sf_stream_web_view): a stream with a zero high-water mark whose native
source reads the body (scr_webstream_readable_over). From then on every
member goes to it, the class's method values and getters run on it
through the alias's view (scr_dyn_handle_class_receiver), and text(),
clone(), new Response(body) and fetch uploads read the body through it,
so its lock and bodyUsed follow it as in Node. A body locked when the
view is made keeps the view locked until the lock is released. Once
only the view's source holds the body, the body lets the view go.

The compiler links the classes into a program whose JavaScript reads a
ReadableStream member that the fetch profile lists as unsupported by the
native stream, or iterates a ReadableStream with for await, and that
names none of the classes: %main calls webstream.install first.
Programs without web streams build to the same binaries as before.

Adds corpus programs fetch-body-web-stream-linked.mjs,
fetch-body-web-stream-unlinked.mjs and fetch-body-web-stream-network.mjs,
with their corpus costs and preflight/order baselines.
2026-10-28 03:44:57 -05:00
Chris Tate 916a99753e Update coverage snapshots for functions that no longer read the receiver
A JavaScript function that never refers to `this` no longer reads the
call receiver at entry. The coverage report counted that read as the
statement's dynamic operation, so these fixtures now report fewer
statements in the dynamic tier: in js-gap/gap.js, the JSDoc-typed
`typed` and `untyped` were both dynamic only through the read, and
`untyped` is now dynamic through its arithmetic alone.
2026-10-28 03:16:46 -05:00
Chris Tate ba3e160f69 Keep union locals copied between locals in private storage (#2457) 2026-10-28 03:08:49 -05:00
Chris Tate 98c178d093 Check the stores of for-in and for-of heads before converting a constructor
A `for (k in o)` or `for (k of a)` whose head is an expression stores into
it on every iteration. The strict-mode check of constructor and method
bodies now treats such a head like the target of `=`: a property store
not made through `this`, a store into a read-only global, and a store into
an undeclared name keep the constructor function, since sloppy mode drops
or completes those stores where a class body throws.

The static-objects-order fixture gains loops.js, whose methods store from
a for-in head into an undeclared name and from a for-of head into a
property of a string.
2026-10-28 03:08:04 -05:00
Chris Tate c9c8224aa2 Merge main 2026-10-28 02:45:59 -05:00
Chris Tate e8fdfa6a72 Pass native closures, promises and objects into Node islands by reference (NI-5) (#1900) 2026-10-28 02:40:19 -05:00
Chris Tate 722c449ae8 Raise through cached unwind plans on x86-64 Linux (#2455) 2026-10-28 02:22:18 -05:00
Chris Tate 33272914de Pass numbers to directly called untyped functions as jsvals 2026-10-28 02:20:29 -05:00
Chris Tate 98006639b7 Read the receiver first when an element write's index can rebind it
An element write of a `T | undefined` binding holds a computed index in a
temporary ahead of its two stores, which then read the receiver after the
index. When the index can rebind the receiver (`a[(a = b, 0)] = u`), the
write now keeps reading the receiver first, so it stores into the array
the receiver held before the index ran.

The key conversion of a union index (a sequence that declares a temporary
and may read the constant "undefined" key) no longer counts as running
other code, so such an index is still held.
2026-10-28 02:19:44 -05:00
Chris Tate 0cf5e87b03 Keep union locals copied between locals in private storage
A union local that is copied to another local (`const t = pp[p0]; p0 = t`)
was a whole-value use, so both locals held heap boxes: every number[]
read held in such a local allocated a `%ScrUnion`, and every copy
retained and released it.

Call lifetimes now record statement copies between union locals
(copiedLocals) instead of rejecting the source. Local union storage
takes a copied local when every local it is copied to also has private
storage, so the copy snapshots the tag and bits and the source's box
stays private. Copies in expression position and copies of parameters
keep the heap path.

An element assignment of a `T | undefined` binding with a computed index
holds the index in one temporary ahead of its two stores, so the key
conversion of a union index declares its temporary once.
2026-10-28 01:56:14 -05:00
Chris Tate d7198f016f Treat the zlib ordering program as a timer race
g05-zlib starts a zlib.gzip from a 0 ms island timer and arms a 2 ms
timer on the native side. The 0 ms timer's callback runs before the 2 ms
timer is due, so the gzip job races that timer: if the job completes on
libuv's thread pool before the timer's turn, its callback runs first.
Node orders the two the other way only because its first gzip usually
takes longer than the wait. With zlib already loaded and the thread pool
started, Node prints i-zlib first in about a quarter of runs. On fast
macOS hosts the native runs took the other order.

Mark it `// @order: race`, as g01 (fs), g03 (child process) and g04
(dns) already are, so its native runs must produce the lines of one of
Node's outputs in any order, and name thread-pool work among the races
the harness describes.
2026-10-28 01:47:01 -05:00
Chris Tate 30d611e9da Read a stream body to its end when its queue filled before the read
text(), json(), bytes() and the other body readers of a Response or
Request whose body is a ReadableStream asked the stream's source for more
chunks after draining its queue only when the stream had a zero
high-water mark. When the queue had filled before the read began (a timer
ran after the Response was made), the reader took the queued chunk and
then waited for a close that no pull would bring, and the read never
settled. The reader now pulls whenever it empties the queue, so it reads
to the end, as Node does. This applies to streams from ReadableStream.from,
from an underlying source, and to JavaScript ReadableStreams.

Adds corpus programs fetch-body-queued-before-read.ts and
fetch-body-queued-before-read-untyped.mjs, with their corpus costs and
preflight/order baselines.
2026-10-28 00:23:51 -05:00
Chris Tate d5ac31f812 Raise through cached unwind plans on x86-64 Linux
scr_unwind_raise walks the stack with plans cached per return address.
The first raise through a return address finds the function's FDE
through the .eh_frame_hdr of the object that contains it, runs the CFI
up to the call, and finds the call site's landing pad in the LSDA; the
plan records the frame's CFA rule, where its callee-saved registers are
saved, and the landing pad. Later raises look the plans up, restore the
registers, and jump to the first landing pad. Plans are inserted under
a lock and read without one.

Landing pads continue the unwind with scr_unwind_resume instead of
`resume`, so the walk goes on from their frame. A frame whose CFI,
pointer encodings or personality the plans do not express, or a return
address without an FDE, leaves the unwind to libgcc's forced unwind
from the frame the walk started at, as before. Other Linux targets keep
libgcc's unwinder; their scr_unwind_resume calls _Unwind_Resume.

bench:runtime gains layered-errors, a workload whose errors are thrown
below small functions that hold references and caught per entry in a
loop, and the corpus gains unwind-landing-pad-chains, which pins landing
pads that continue the unwind, including in frames LLVM inlines into a
caller whose handler takes the exception.
2026-10-28 00:22:04 -05:00
Chris Tate 68ddd68210 Keep the elements of InlineArray fields in their instances (#2380) 2026-10-27 23:55:57 -05:00
Chris Tate e3f079e12b Give integer clones borrowed records and worker programs (#2450) 2026-10-27 23:50:34 -05:00
Chris Tate efdd53912d Answer builtin globals read through a sloppy-mode function's this
A sloppy-mode function called with undefined or null as this reads the
global object, which answers builtin globals through the program's lookup.
Modules whose sloppy-mode functions read builtin globals through this, or
through a variable it initializes, now install that lookup; it answers
those names, or every declared global when a module reads the global
object by computed names.
2026-10-27 23:43:34 -05:00
Chris Tate 5ec343312a Keep process's emitter methods as Node shows them in Node islands (#2452) 2026-10-27 23:22:26 -05:00
Chris Tate 1278729267 Merge main 2026-10-27 23:21:09 -05:00
Chris Tate d03c4b9b57 Merge main 2026-10-27 23:16:08 -05:00
Chris Tate 41e7950ea9 Keep frozen checked records frozen, and give sloppy-mode this the global object
Typed records checked from frozen untyped values stay frozen, as the
objects they copy are. A sloppy-mode function called with undefined or null
as this receives the global object that globalThis reads.

The record check reads the source's integrity with a query that answers for
every value: a proxy answers as its target, and values without a modeled
integrity level, which Object.freeze refuses, are not frozen. Object.isFrozen
keeps its refusal of those values.
2026-10-27 23:07:13 -05:00
Chris Tate 82ba2ef6ed Merge main 2026-10-27 23:02:33 -05:00
Chris Tate 18a47fedd5 Run npm packages in Node islands: object handles and compiler routing (NI-4) (#1899) 2026-10-27 22:42:29 -05:00
Chris Tate 08c7b72c2d Keep process's emitter methods as Node shows them in Node islands
The island host replaced process.on, process.addListener and
process.prependListener with separate wrappers, and process.emit with its
own, as own enumerable properties of process. Island code could see the
difference: Object.keys(process) listed the four, process.on was no
longer process.addListener, prependListener was named addListener, and
each was an own property where Node inherits it.

One wrapper now serves on and addListener, and prependListener and emit
keep their names. They are defined on process's own prototype, which
Node gives process below EventEmitter.prototype, with
EventEmitter.prototype's attributes. process has no own property for
them, so Object.keys, Object.hasOwn and for...in see what they see in
Node. for...in lists a prototype's keys before its parent's, so the
keys EventEmitter.prototype lists before the wrapped ones (_events,
_eventsCount, _maxListeners, setMaxListeners, getMaxListeners) are
defined there too, with the same values, in their order.

6891 compares the identities, names, lengths, own-ness, Object.keys and
for...in over process against Node.
2026-10-27 22:33:01 -05:00
Chris Tate 93081ea679 Host the runtime's event loop on Node's libuv once Node loads (NI-3) (#1696) 2026-10-27 22:19:38 -05:00
Chris Tate d5c215c684 Merge main 2026-10-27 22:03:19 -05:00
Chris Tate 506683f16b Distribute conditional members over a generic body's bound parameter (#2258) 2026-10-27 21:56:52 -05:00
Chris Tate 9df673b23e Keep unloaded packages out of import() graphs and reject those imports (#1526) 2026-10-27 21:53:19 -05:00
Chris Tate 9e91b7d912 Merge main 2026-10-27 21:44:40 -05:00
Chris Tate 14e109a34f Merge main 2026-10-27 21:42:17 -05:00
Malte UblandClaude 5.5 96601855ba Merge ni/i4-deferred into ni/i5-shared-modules
Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
2026-10-27 19:35:45 -07:00
Malte UblandClaude 5.5 16f44273e4 Merge ni/i3-exports into ni/i4-deferred
Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
2026-10-27 19:35:32 -07:00
Malte UblandClaude 5.5 42df54873d Merge ni/i2-handles into ni/i3-exports
Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
2026-10-27 19:35:12 -07:00
Malte UblandClaude 5.5 c959443a80 Exercise the unary island-call fix from the corpus program
The corpus case passed `new Swapper(tricky)` straight to `+`, which the
argument check already treated as effectful, so it never distinguished
the two lowerings. Bind the swapper to a local: an identifier is what
the old check called inert, and the program now prints "replaced
replaced" without the fix and matches Node with it. The class also used
a parameter property, which Node's strip-only mode rejects.

Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
2026-10-27 19:34:46 -07:00
Chris Tate 9bb4692959 Convert more structural values: class views, promise arms, index-signature records, tuple unions (#2442) 2026-10-27 21:29:07 -05:00
Chris Tate 37bdea8162 Pin constrained generic members and probe through assertions (#2443) 2026-10-27 21:27:21 -05:00
Chris Tate d4d7995d68 Treat a call that never returns as an exit in presence proofs (#2445) 2026-10-27 21:17:38 -05:00
Chris Tate d97e816848 Keep integer clones off records that may be views
A checked conversion can answer a record view: an object of the target
record type whose fields live in the record it views, at that record's
offsets (record-views.ts). An integer clone read the fields of a borrowed
record at its own shape's offsets, so for a view it read the view's unused
slots and returned wrong results.

Record shapes that may be views are no longer references a clone reads, so
functions that read them keep the ordinary body, which reads a view's fields
through the original record. The emitter now plans integer clones after
record views.

integer-recursion-records.ts gains a recursive sum over a view.
2026-10-27 21:15:37 -05:00
Malte Ubl ad45f05ca8 Merge branch 'ni/i4-deferred' into ni/i5-shared-modules 2026-10-27 19:14:39 -07:00
Malte Ubl 8c4eb85122 Merge branch 'ni/i3-exports' into ni/i4-deferred 2026-10-27 19:14:34 -07:00