Commit Graph
3062 Commits
Author SHA1 Message Date
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 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 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 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 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 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
Chris Tate 2783b96100 Round a loop bound to an integer before taking the counter's offset off it
- Narrowing `x + c < b` and its other comparison forms subtracted the offset from the bound in floating point. A fractional bound could then round across an integer: `x + 2147483648 > -(2 ** -60)` gave the minimum -2147483647, which leaves out -2147483648. The bound now rounds to an integer first, and the offset comes off in exact integer arithmetic. Integer bounds give the same facts as before.
- A unit test covers that comparison.
2026-10-20 22:54:32 -05:00
Chris Tate f31b97ee64 Pin the zone-offset cache's answers across a double offset change to Node's
- The zone-offset cache keeps an offset over 19 days when both ends agree, as V8's date cache does. America/Argentina/Tucuman changed its offset twice within 12 days in June 2004, so in Node the answers between the two changes depend on the order of the reads: ascending reads keep the May offset through June 12, and descending reads see the June offset.
- The cache's comment now states that behavior, and corpus date-zone-intervals.ts reads those days in both orders. Its output matches Node for each order.
2026-10-20 22:32:08 -05:00
Chris Tate 43f1c445e8 Keep counters a for loop bounds in integer shadows (#1491) 2026-10-20 22:31:47 -05:00
Chris Tate 6bf550e11f Treat bindings a for loop's condition or update writes as unbounded
- The bounded-loop analysis collected writes from the loop body only. A binding the condition writes (`i < ((source = values[i]), values).length`) then passed as stable, so a cursor copied from it took the source's entry range, and its integer shadow truncated fractional values. The condition and the update are now scanned too, and a binding either one writes is neither a stable bound nor a copy source.
- Corpus 5686 adds a copy whose source the condition writes with fractional values.
2026-10-20 22:29:56 -05:00
Chris Tate b602cdc76e Start small arrays with three inline slots 2026-10-20 22:01:41 -05:00
Chris Tate e5af4def98 Promisify untyped functions at run time (#1502) 2026-10-20 21:54:51 -05:00
Chris Tate 012fdb3fb0 Merge main 2026-10-20 21:52:19 -05:00
Chris Tate 69ef7da7f8 Run death-triggered full collections at the event loop's idle moments (#1522) 2026-10-20 21:48:02 -05:00
Chris Tate 44c586b1ee Run a waiting death pass at the idle moment even after the heap shrank 2026-10-20 21:44:46 -05:00
Chris Tate 24eb054395 Compile the hot-shots StatsD client over UDP (#1568) 2026-10-20 21:29:48 -05:00
Chris Tate b811317f26 Keep String method arguments read before another argument's conversion runs
concat, substr and the other typed String and Number extra methods
convert their arguments after evaluating all of them. When an argument's
conversion can run program code (an object's toString or valueOf), a
variable read of the receiver or another argument is now kept in a local
as well, so a conversion that assigns the variable leaves the
already-evaluated argument unchanged, as in Node.
2026-10-20 21:22:29 -05:00
Chris Tate bde7afa980 Compile undici from a built-in implementation (#1425) 2026-10-20 21:14:37 -05:00
Chris Tate d59d8ff6f2 Merge main and count declaration writes to parameters 2026-10-20 21:05:19 -05:00
Chris Tate eb8a148605 Merge main 2026-10-20 21:01:40 -05:00
Chris Tate 53171c282d Read and write dynamic-tier properties through shapes and inline caches (#1396) 2026-10-20 20:52:12 -05:00
Chris Tate 70e0af8094 Keep shapes off objects that threads share
A published graph is immortal and shared by pointer across threads, but a
shape names a node of one thread's transition tree, which ends with that
thread. A worker that read a published object through a cache left its
shape in the object; after the worker exited, the main thread's reads
followed it into freed memory. Immortal objects now stay unshaped, and
publishing (postMessage's shared graphs and @scriptc/threads publish)
clears a shape the object already has.
2026-10-20 20:44:14 -05:00
Chris Tate 94aa7065f1 Compile the remaining corpus programs with the native compiler (#1418) 2026-10-20 20:29:41 -05:00
Malte UblandClaude Opus 5.5 da13375a8b Keep properties JavaScript adds to a child process, and await it as a thenable
execa 5 mixes then/catch/finally into a real ChildProcess with
Reflect.defineProperty, replaces kill and adds cancel by assignment, and
awaits the child.

- A new optional runtime unit, scr_dyn_handle_props.c, keeps those
  properties in a side table keyed by the native child. Wrapper ops for
  the child tag look there first for reads, member calls and writes.
  defineProperty and getOwnPropertyDescriptor use it, and await adopts a
  child with a callable then. Class methods read as values call the
  class's own method.
- In JavaScript files, writes onto typed children, reads and calls of
  added names, method values for bind/call/apply, and members no typed
  lowering claims go through the child's checked-dynamic box.
- Only programs whose JavaScript adds properties to children link the
  unit and install the table; everything else compiles as before.
- A setTimeout delay that may be a boolean converts with ToNumber, and
  `if (t.unref)` over a timer handle folds to true (execa's lib/kill.js).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 18:28:00 -07:00
Chris Tate 6e278b8487 Name the spread expression in not-iterable errors for values held as any (#1587) 2026-10-20 20:25:52 -05:00
Chris Tate 5a1882957d Convert Promise and record results and record arguments in function adapters (#1556) 2026-10-20 20:21:38 -05:00
Chris Tate 81bc045524 Spin briefly before a mailbox wait blocks, and signal after the unlock (#1400) 2026-10-20 20:20:14 -05:00
Chris Tate 08c7930958 Support crypto.createECDH and ECDH.convertKey (#1589) 2026-10-20 20:18:22 -05:00
Chris Tate 7ccd9fb3ac Name functions stored in JavaScript object literals as Node (#1591) 2026-10-20 20:17:19 -05:00
Chris Tate f32c309baa Declare function adapter scratch locals in the adapter
A function value whose result is a union of records, assigned to a slot
that returns one record type, lifts each record arm into the slot's
record. The lift declares a scratch local, which went into the function
that requested the adapter, so calling the adapted function failed IR
validation. The adapter body now lowers in its own function context and
the adapter declares those locals.
2026-10-20 20:16:46 -05:00
Chris Tate fae56ff409 Run exec on regular expressions held as any (#1600) 2026-10-20 20:16:04 -05:00
Chris Tate b04166a8f0 Read runtime keys of package enum objects as checked-dynamic values
The enum rewrite turns a package's numeric enum into one object holding
its names and its reverse mapping, so the object's fields have different
types. A reverse read with a runtime key, such as
DiagLogLevel[DiagLogLevel.WARN] in @opentelemetry/api, then failed with
SC1090 ("the declared fields have no one common type").

A keyed read of such a record already answers a checked-dynamic value in
JavaScript files. It now does the same where the checker types the read
'any'. The new npm case tsc-enum-reverse-reads matches Node, as does the
opentelemetry-api case again.
2026-10-20 20:04:06 -05:00