Commit Graph
3635 Commits
Author SHA1 Message Date
Chris Tate cd38fd2248 Bridge events whose typed listeners take no parameters
An event that typed code only listened to with a listener taking no
parameters had no bridge entry: the scan marked a listener as typed only
when one of its parameters was not a checked-dynamic value. Emitting the
event through an `any` reference then threw instead of calling the
listener. A TypeScript listener with no parameters now marks the event as
typed. A JavaScript function without parameters still registers as before,
since it can read `arguments`.
2026-10-21 11:03:19 -05:00
Malte Ubl f798f6736c Merge remote-tracking branch 'origin/main' into opt/emitter-mixed-events 2026-10-21 07:53:10 -07:00
Malte UblandClaude Opus 5.5 3f3e90b846 Drop the EventEmitter method-values follow-up
Revert 3a27a92cb ("Forward at most four emit arguments through
EventEmitter method values") and 0cf62ebec ("Answer EventEmitter methods
read as values off compiled emitters"). Under the scope-B plan,
EventEmitter methods read as values, `EventEmitter.prototype.on` in
JavaScript and constructor-function emitters belong to Node islands, not
the native compiler. The typed/untyped listener sharing and the
catchable throw in place of the trap stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 07:53:10 -07:00
Chris Tate 0237d70df0 Add profile-guided optimization (#1635) 2026-10-21 08:43:57 -05:00
Chris Tate 7f9371c4b2 Write merged profiles through a uniquely named temporary file
merge-profiles wrote the merged profile to `<output>.tmp` and renamed it
into place. Opening that fixed path followed an existing link there and
truncated its target. The temporary file is now created exclusively with
createUniqueFile next to the output, as emission does, and is removed when
writing fails or the stream reports an error.
2026-10-21 08:40:46 -05:00
Chris Tate 538bd2b948 Walk IR child lists with loops instead of per-node closures (#1654) 2026-10-21 08:37:47 -05:00
Chris Tate 721f5d46ef Sort by several keys without calling a chained comparator (#1652) 2026-10-21 08:35:40 -05:00
Chris Tate 9cf8662099 Walk IR child lists with loops instead of per-node closures
everyExprChild, everyStmtChild, mapExprChildren and mapStmtChildren passed
an arrow function to every() or map() for each child list. In the native
compiler that arrow is a closure allocated and released at every visited
node, and each element passes through Array.prototype.every's callback
adapter. The child lists are now walked with index loops in the same
order, so every IR pass that traverses the module visits the same nodes
with less work per node.
2026-10-21 08:35:02 -05:00
Chris Tate f58c321c07 Sort by several keys without calling a chained comparator
A comparator that chains key differences with `||`, such as
`(b.score ?? 0) - (a.score ?? 0) || a.id - b.id`, ran through the
comparator sort: TimSort called the closure once per comparison. Each
term orders one number field, ascending or descending, and a term that is
0 or NaN falls through to the next, so the chain orders elements by their
keys with the first deciding. With no key NaN or -0 that order is
consistent, and a stable sort by the keys gives the comparator sort's
permutation.

The recognizer now accepts such chains of up to eight terms over number
fields of records and class instances, and a key may read an optional
number field with a number default (`x.k ?? 0`), also as a single key. The
runtime reads and checks every key of every element before it moves a
slot, then runs stable keyed passes from the last key to the first. A NaN
or -0 key, or a chain with any other term, keeps the comparator sort.
2026-10-21 08:31:35 -05:00
Chris Tate 1ee5c84cad Read BYTES_PER_ELEMENT on narrowed-away and untyped typed arrays (#1639) 2026-10-21 04:24:36 -05:00
Chris Tate 8b4e74d050 Type empty arrays filled by later writes inside functions by their reads (#1632) 2026-10-21 04:23:15 -05:00
Chris Tate d60d7dbbc7 Skip the allocator's per-slice MADV_DODUMP calls when no core can be written (#1638) 2026-10-21 04:22:43 -05:00
Chris Tate c7569cf502 Limit profile-driven imports between partitions 2026-10-21 04:21:34 -05:00
Chris Tateandsaboursepideh 916303beff Read BYTES_PER_ELEMENT on narrowed-away and untyped typed arrays
`a instanceof Uint8Array || (... a.BYTES_PER_ELEMENT === 1)`, the
@noble/hashes isBytes guard, narrows `a` to never on the right side when
the guard is specialized for a Uint8Array argument, so the read missed
the typed-array member lowering; it now answers the element size from
the receiver's storage. The checked-dynamic read of BYTES_PER_ELEMENT on
a typed array or Buffer (an untyped argument) answered undefined; it now
answers the element size, as each kind's prototype does in Node.

Refs #1437

Co-authored-by: saboursepideh <25549001+saboursepideh@users.noreply.github.com>
2026-10-21 04:21:00 -05:00
Chris Tate 472fe057ec Mark allocator slices dumpable only when the kernel would write a core
Each class slice a thread opens took one madvise(MADV_DODUMP) call,
which takes the mmap lock for writing, and the threads of a worker
executable open hundreds of slices as they start. The slices are now
made dumpable only when the kernel would write a core of the process:
when kernel.core_pattern starts with '|' or '@', or when RLIMIT_CORE is
above 0. The reservation stays MADV_DONTDUMP in every case.
2026-10-21 04:19:17 -05:00
Chris Tate 17db4cd443 Resolve evolving-array reads through the file's identifier index
The evolving-array analysis walked the binding's whole function and
resolved the symbol of every identifier in it, once per empty-array
binding, so a function with many such bindings cost quadratic time. It now
takes the file's identifiers spelled like the binding from the per-file
index (identifiersNamed), keeps those inside the binding's function, and
resolves only them.
2026-10-21 04:19:04 -05:00
Chris Tate e65ebfd2c7 Fail the zero-cost gate when it compares nothing (#1637) 2026-10-21 03:50:00 -05:00
Chris Tate cb75c51eda Fail the zero-cost gate when it compares nothing
A benchmark workload or the self-hosted compiler that fails to build
with both checkouts now fails the gate, and so does a run in which no
program built with both. Before this, a run whose builds all failed the
same way, such as with a helper of another version, reported PASS. A
build error now prints in full under its program; its first line is
often a warning.
2026-10-21 03:45:36 -05:00
Chris Tate 8065be3ccd Add profile-guided optimization 2026-10-21 03:13:45 -05:00
Chris Tate 046c30bbb9 Convert possibly undefined indexes of element writes with ToNumber (#1634) 2026-10-21 03:08:52 -05:00
Chris Tateandejc3 b237cf1ff0 Convert possibly undefined indexes of element writes with ToNumber
An element write whose index the checker types as a number but whose
storage may hold undefined (a for...of variable over an array that may
have holes) converted the index with ToNumber only on plain-array writes.
Typed-array writes, compound and update writes, element assignments used
as values and element targets in destructuring assignment now convert it
the same way, as the element reads already do, instead of refusing it as
a non-number key.

Fixes #1622

Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
2026-10-21 03:04:59 -05:00
Chris Tate 1f554fbc75 Compile the typed-array callback methods (#1633) 2026-10-21 03:01:01 -05:00
Chris Tateandejc3 2078392a52 Compile the typed-array callback methods
forEach, map, filter, some, every, find, findIndex, findLast,
findLastIndex, reduce and reduceRight compile on every typed-array kind
through one shared helper per method shape. The receiver, the callback and
reduce's initial value evaluate in order before the helper runs; the
helper reads the length once and each element when it visits it, so the
callback's writes show in later elements. Callbacks receive the element,
its index and the typed array (reduce's receive the accumulator first).
map and filter return a new typed array of the receiver's kind, map
storing each result with that kind's element conversion, and a Buffer's
map and filter return a Buffer. reduce without an initial value throws
Node's TypeError on an empty typed array. A thisArg keeps the fence, as
for arrays.

Fixes #1620

Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
2026-10-21 02:55:08 -05:00
Chris Tate 095acffb46 Compile generic arrow bindings inside functions that use no enclosing frame (#1627) 2026-10-21 02:54:32 -05:00
Chris Tate 3e08632467 Refuse values of generic bindings declared inside functions
Each call of a function makes a new function object for a generic arrow
or function expression bound inside it, so `make() !== make()` when make
returns the binding, and such values are distinct Map and Set keys. The
compiled instances are shared module functions, which cannot give that
identity, so a value use of such a binding keeps an SC1090 fence that
names the reason. Calls of the binding still compile.
2026-10-21 02:51:36 -05:00
Chris Tateandejc3 009a01d074 Type empty arrays filled by later writes inside functions by their reads
An unannotated `const xs = []` (or `let`) inside a function, which
TypeScript types by the elements written before each read, takes the array
type its settled reads have: records pushed in a loop give a record array,
numbers a number[], strings a string[]. Reads inside closures, which the
checker leaves unsettled, do not count. When settled reads disagree or the
binding is reassigned, the binding is a dynamic array, as the same binding
already is at module scope. Before, such a binding was always number[] and
pushes of other values were refused.

Fixes #1623

Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
2026-10-21 02:40:20 -05:00
Chris Tate b3f62a7206 Test records returned across an npm package boundary from different shapes (#1631) 2026-10-21 02:38:43 -05:00
Chris Tateanddevin-ai-integration[bot] 3592a9b7c1 Test records returned across an npm package boundary from different shapes
A JavaScript package method that returns one of two literal shapes (a
result reply or a failure reply) through one declared interface with
optional members hands its consumer the members of the shape it built,
including the ones only one shape has. scriptc 0.3.4 dropped them (the
failure reply arrived as { version, id } and read as a result); the
corpus program keeps that case covered: direct and looped calls, an
async method and a sync one, JSON output, Object.keys and `in`.

Fixes #1314

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-21 02:34:46 -05:00
Chris Tate 87cb214a41 Answer typed-array member probes and destructure into typed-array elements (#1630) 2026-10-21 02:33:14 -05:00
Chris Tateandsaboursepideh e3d31c2a65 Answer typed-array member probes and destructure into typed-array elements
A typed array or Buffer in a compiled program is always a built-in kind
(programs cannot subclass them, add own properties or change their
prototypes), so Node 24's prototype chains for them are known exactly.

- A read of a member the type does not declare (in JavaScript, or under
  @ts-ignore) answers undefined when that chain lacks the name, as Node's
  GetV does: the base64 and hex feature probes (`typeof bytes.toHex`) that
  libraries such as @noble/hashes make. Names Node has, including the
  Buffer prototype's members on a Uint8Array that may hold a Buffer, keep
  the fence.
- `"name" in bytes` with a literal member name answers from the same
  chains, and `bytes.BYTES_PER_ELEMENT` is the element size.
- Array destructuring assignment into typed-array elements
  (`[high[i], low[i]] = [h, l]`) evaluates the receiver, the index and the
  value in order and writes the element as `b[i] = v` does.

Refs #1437

Co-authored-by: saboursepideh <25549001+saboursepideh@users.noreply.github.com>
2026-10-21 02:28:56 -05:00
Chris Tate 00ee68f421 Compile Math.max and Math.min with spreads among other arguments (#1629) 2026-10-21 02:21:37 -05:00
Chris Tateandejc3 89beb65cc6 Compile Math.max and Math.min with spreads among other arguments
Math.max and Math.min with a spread and other arguments (`Math.max(...xs,
5)`) pack their arguments, in order and with each spread flattened, into
one number[] that the runtime folds, as Math.hypot already does. The fold
keeps NaN poisoning, -0 below +0 and the empty fold's infinities, and the
arguments evaluate left to right before it. The refusal hint now names
the supported forms.

Fixes #1621

Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
2026-10-21 02:17:47 -05:00
Chris Tateandejc3 45d3d02bf0 Compile generic arrow bindings inside functions that use no enclosing frame
A generic arrow or function expression bound inside a function or method
compiles when it uses nothing from the enclosing function's frame: no
`this`, `super`, `arguments` or `new.target`, and no local, parameter or
type parameter of an enclosing function. Its instantiations are module
functions, as for a module-scope binding, and may call the binding itself
and read module-scope bindings. Instances of bindings in functions and
blocks take the declaration's position in their names, so two functions
may declare bindings of the same name. Bindings that use the enclosing
frame keep their SC1090 fence.

Fixes #1624

Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
2026-10-21 01:58:22 -05:00
Chris Tate 7ae0445cb6 Partition programs with multi-line dispatch tables as text (#1626) 2026-10-21 01:11:31 -05:00
Chris Tate 267b77c994 Partition programs with multi-line dispatch tables as text
The emitter writes interface dispatch tables one element per line. The
helper's text partitioner accepted only single-line globals, so any
program with such a table (every partitioned program with class
interfaces) fell back to parsing the whole module before splitting it
with LLVM. The text partitioner now reads a global whose aggregate
initializer opens at the end of its line and closes on a line that holds
only the closing bracket.

Those programs now parse their partitions concurrently and keep the text
partitioner's placement, so an edit to one function changes only that
function's partition again.
2026-10-21 01:08:13 -05:00
Chris Tate f7044b0812 Store number-key map entries in 16 bytes and size the direct index near the largest key (#1614) 2026-10-21 00:36:58 -05:00
Chris Tate 3434aacec4 Pick the iteration step from the key kind for checked collections
A typed number-key Map or Set that is boxed and cast back to
Map<unknown, unknown> or Set<unknown> keeps its own storage, with
16-byte entries. Inline iteration over a checked collection used the
24-byte step, so it tested the wrong words for liveness and read past
the entry array: a Set skipped entries and a Map aborted with "map
iteration index out of range".

The iteration step for a collection with checked keys now reads the
map's key kind and takes the number-key step for SCR_MAP_KEY_F64 and the
24-byte step otherwise. Keys and values are at bytes 0 and 8 in both
layouts, so the slot reads are unchanged. Typed collections keep their
static step, and their IR is unchanged. scr_map.h asserts the key kind's
offset and SCR_MAP_KEY_F64's value.

set-checked-storage.ts iterates a number-key Set and a hashed number-key
Map through their checked views, with deleted entries.
2026-10-21 00:35:00 -05:00
Chris Tate 0bb7411b62 Keep properties JavaScript adds to a child process, and await it as a thenable (#1606) 2026-10-21 00:18:33 -05:00
Chris Tate faa5cb601a Route defineProperty replacements of child methods through the child's box, and size the getter-only error to the name
- The scan for properties JavaScript adds to children now also records `Reflect.defineProperty`, `Object.defineProperty` and `Object.defineProperties` on a child, and element writes (`child["kill"] = ...`). A key that is not a literal sends every member through the child's box. A typed call to a method replaced through `Reflect.defineProperty` then calls the replacement instead of the native method.
- The "Cannot set property x of #<ChildProcess> which has only a getter" message is built at its exact length. Before, a name that overflowed the 160-byte buffer passed the untruncated length to the throw, which read past the buffer.
- Corpus 6611 adds a module that replaces kill through Reflect.defineProperty and writes to a getter-only property with a 300-character name; 6610 and 6611 get their cost files.
2026-10-21 00:15:04 -05:00
Chris Tate 009e73e873 Cache zone offsets and UTC date fields, and return small integers' text without allocating (#1475) 2026-10-21 00:13:25 -05:00
Chris Tate acfa46a919 Decide a Date's zone name at the instant, not from the offset cache
- Date toString and toTimeString now decide the zone name's daylight flag at the instant itself, as V8's LocalTimezone does. The offset cache's segments hold offsets only, as V8's do. Before, a merged segment kept the DST flag of the read that created it, so a zone that left daylight time without changing its offset could print the summer name after the change: Africa/Algiers on 21 October 1977, at +0100 on both sides.
- Corpus date-zone-intervals.ts reads Algiers across that day in three orders; the names match Node.
2026-10-21 00:11:47 -05:00
Chris Tate 2c502c0a27 Answer 511 for an empty event-loop-delay histogram's percentiles (#1618) 2026-10-21 00:10:13 -05:00
Chris Tate 00840aaca6 Merge main 2026-10-21 00:08:02 -05:00
Chris Tate 4004d4c9d0 Answer 511 for an empty event-loop-delay histogram's percentiles
Node creates monitorEventLoopDelay's histogram with a lowest discernible
value of 1000 ns, so an empty histogram's percentile() returns 511, the
highest value its first bucket holds. The runtime returned 0.

6397-event-loop-metrics.ts now skips a sampling window that has no
recorded delta. A delta needs two samples after reset(), and a loop that
wakes late runs only one sample in a window, so on a loaded machine the
program printed an empty window under Node and compiled alike. The
program also prints percentile(50) after each reset.
2026-10-21 00:06:54 -05:00
Chris Tate 0c27def467 Run child accessors on the child, and find child property writes in every JavaScript file
- A getter or setter that JavaScript defines on a child process now runs with the child as `this`, instead of the hidden object that holds the child's added properties, for reads, writes and member calls. A write to a getter-only accessor throws "Cannot set property x of #<ChildProcess> which has only a getter".
- The scan for properties JavaScript writes onto children now covers every JavaScript file of a program that loads child_process, so a module that patches a child it received counts even when it never names child_process.
- The scan's cache and the program's "writes onto children" flag live on the Lowerer instead of a WeakMap and a WeakSet.
- Corpus 6611 covers a module that adds a replaced method, an accessor pair and a getter-only accessor to a child it did not spawn.
2026-10-20 23:41:21 -05:00
Chris Tate 19c88fedb4 Iterate untyped values with for await, and delegate with yield* in async generators (#1564) 2026-10-20 23:25:24 -05:00
Chris Tate a1c55207fc Merge main 2026-10-20 23:21:29 -05:00
Chris Tate 99c63271e9 Cache zone offsets with V8's date-cache algorithm
- The zone-offset cache now follows Node's V8 date cache (DateCache::LocalOffsetInMs): 32 segments of instants with one offset each, the 19-day lookahead, merges of segments with the same offset, a binary search of up to five probes for a change point, and least-recently-used replacement. Local Date getters then answer as Node's do in every order of reads, including the order-dependent answers around Tucumán's two offset changes in June 2004. The previous single-interval cache differed from Node in about half of the seeded orders tried.
- Scattered reads hit a cached segment more often: a getter benchmark over consecutive and scattered instants runs in 169 ms instead of 314 ms (America/New_York; Node 567 ms).
- Corpus date-zone-intervals.ts adds seeded orders of reads around the Tucumán changes and across three years in four zones.
2026-10-20 23:12:10 -05:00
Chris Tate ddc7fe8b0d Store number-key map entries in 16 bytes and size the direct index near the largest key 2026-10-20 23:01:43 -05:00
Chris Tate 4dd62802c8 Bound loop counters compared as i + c with a bound (#1463) 2026-10-20 22:56:44 -05:00