Commit Graph
6750 Commits
Author SHA1 Message Date
Chris Tate a147bb5b52 Read the characters of ASCII strings in versioned scan loops as bytes (#1846) 2026-10-22 17:50:54 -05:00
Chris Tate 5372c69459 Pass { signal } to stream operator callbacks and Duplex.from bodies 2026-10-22 17:50:29 -05:00
Chris Tate 98712c364a Merge main 2026-10-22 17:49:58 -05:00
Malte UblandClaude Opus 5.5 a4287e9122 Keep Linux poller timers in a heap behind one timerfd
The epoll poller gave every armed timer its own timerfd. A keep-alive
HTTP server re-arms and cancels the socket idle timer on every request,
which cost six system calls per request (timerfd_create,
timerfd_settime twice, epoll_ctl twice, close) on top of the read and
the send; Node makes two.

Armed keys now live in a deadline min-heap with a pointer hash, and one
timerfd wakes the loop at the earliest deadline. Arming a later
deadline, re-arming and cancelling make no system call; the timerfd is
reprogrammed only when an earlier deadline arrives or when it fires.
The fd table is indexed by fd number instead of scanned. Delivery is
unchanged: each expired timer reports once (one-shot), now in deadline
order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:48:23 -07:00
Chris Tate 8bc8fc54ca Bound string reads of while loops by their counter tests
With while-loop versions, `while (pos < end && s.charCodeAt(pos) === 32)
pos++` took a version whose guard could never hold. The counter scan
records only typed-array accesses, so the string read fell back to the
statically ranged check. For a counter held in an int32 slot, that check
asks for a length above 2^31 - 2. The loop paid for the guard and ran its
checked copy.

The counter scan now records `s.charCodeAt(i)` and `s.charAt(i)` of a
string like typed-array reads. Their guard compares the counter's bound
with the string's length, as for a typed array. Corpus 7302 adds while
loops over counters below a string's length.
2026-10-22 17:47:26 -05:00
Chris Tate edb2755a9b Merge main 2026-10-22 17:46:30 -05:00
Chris Tate fad298582c Move the emitter's loop optimization state into loop-state.ts (#1585) 2026-10-22 17:44:05 -05:00
Malte UblandClaude Opus 5.5 05e0a19ff4 Spread untyped sources into typed object literals
An object literal that spreads an `any` value into a typed slot
(service.list({ ...c.req.valid("query"), offset: 0 }) where the validated
value is untyped) was refused, because only known record shapes spread.
TypeScript types such a literal `any` and the copy holds whatever own
properties the source has at runtime, so it now builds as a
checked-dynamic object and checks into the expected record like any other
`any` value assigned to that slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:43:33 -07:00
Malte UblandClaude Opus 5.5 268194e18e Read records by keyof keys when their fields differ in type
A keyed read of a record whose declared fields have different types, with
a key the checker types as keyof the record (row[key] in a loop over
Object.keys(patch) as (keyof Row)[]), was refused: the read needed one
common field type. The checker already types such a read as the union of
the field types; when every declared field surfaces in that union, the
existing per-field dispatch wraps each hit into its arm.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:43:28 -07:00
Malte UblandClaude Opus 5.5 fd4f14936f Check cycle bindings only where early code can reach the reading function
The import-cycle analysis treated every deferred reference (a function
body, parameter default or instance field) to a cycle binding as early
once any user code could run before the declaration completed. With a
static evaluation order it now walks the cycle's top-level code in
evaluation order, following stored callables (function and class
declarations, functions and methods stored by initializers) across the
cycle's modules through import aliases, and counts a deferred reference
as early only when that walk reaches it before the declaration ran.
Top-level reads, unknown orders, top-level await and default-exported
declarations keep the previous rule.

This removes the SC1016 refusal on zod 4's `fn[CONSTANT_CATCH] = true`
(a computed key folded from a const's literal type inside a function
nothing calls during the cycle's evaluation), and drops initialization
flags and checks that can never fire.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 15:39:40 -07:00
Chris Tate 289a6f619d Merge main 2026-10-22 17:39:40 -05:00
Chris Tate d8d74dab97 Keep declaration order and override placeholders in record spreads (#1834) 2026-10-22 17:32:44 -05:00
Chris Tate 6132fcf7c2 Dispatch checked-dynamic method calls on method ids (#1851) 2026-10-22 17:25:35 -05:00
Chris Tate 4f6c55a8e6 Narrow filter results through written type predicates with checked conversions (#1819) 2026-10-22 17:21:41 -05:00
Chris Tate 4082dd0189 Check integer-or-NaN indexes without a double round trip (#1872) 2026-10-22 17:21:21 -05:00
Chris Tate 3b3d78f5d3 Emit function bodies on forked workers (#1852) 2026-10-22 17:21:00 -05:00
Chris Tate 5195e2b63a Merge main 2026-10-22 17:19:37 -05:00
Chris Tate 846552b0e2 Support crypto.getCipherInfo, AES-CCM and the AES key wrap ciphers
getCipherInfo(nameOrNid[, options]) runs in a new runtime unit,
scr_cipher_info.c, over a table that scripts/gen-cipher-info.mjs generates
from Node 24.22's getCipherInfo. The table covers every cipher OpenSSL
names, including the ones without a native implementation. Lookups match
names case-insensitively and accept OpenSSL's nids. The unit applies
cipher.js's argument checks and GetCipherInfo's keyLength and ivLength
rules, and returns the info object in Node's key order. CipherInfo maps
to a checked-dynamic value, so TypeScript and JavaScript code receive
Node's object. Reading the nid Node leaves out for the SIV and SM4 GCM,
CCM and XTS ciphers, where the declared type gives number, throws a
TypeError.

createCipheriv and createDecipheriv support aes-128/192/256-ccm (and the
id-aesN-ccm names) over OpenSSL's ccm128 steps. They follow Node's tag and
IV lengths, the plaintextLength requirement, the message-length caps and
the one-update rule, and report a failed authentication from final().

They also support the RFC 3394 and RFC 5649 key wrap ciphers (aes-N-wrap,
aesN-wrap, id-aesN-wrap, the -pad forms and the -inv ciphers) over
wrap128.c's routines, with Node's lengths, integrity failures and
one-update rule.

The new names follow the existing ones in the cipher table, so the lookup
cost of the existing ciphers is unchanged.
2026-10-22 17:18:54 -05:00
Chris Tate 3520eaa09d Merge main 2026-10-22 17:17:27 -05:00
Chris Tate 81bf6cb0c7 Check integer-or-NaN indexes without a double round trip
An array or typed-array index that is an exact integer or NaN, such as an
element read that may miss (`t[i]!`), a `charCodeAt`, or a sum of those, is
a valid index when it is ordered and below the length. The index check now
tests that with one ordered compare and the integer, taken from the value's
32-bit view or from one conversion, instead of converting the index back to a
double and comparing it.

Number locals that are declared once with an initializer, are not captured
or boxed, and are only written such values carry the same fact, so
`const c = t[i]!; table[c]` and `(h * 31 + c) | 0` take the integer paths
that the inline read already takes.
2026-10-22 17:14:28 -05:00
Chris Tate 491703c40f Merge main 2026-10-22 17:13:25 -05:00
Chris Tate 5f5cf8f5be Keep aliases of evolving empty arrays on the array they alias (#1812) 2026-10-22 17:12:17 -05:00
Malte Ubl 69cbdc2d94 Merge remote-tracking branch 'origin/main' into up/key-order-spreads 2026-10-22 15:11:56 -07:00
Chris Tate 1d8d19e634 Run stream events and callbacks in Node's AsyncLocalStorage contexts
Stream events and callbacks ran with no store. Node's streams run each
step in the context of the code that triggers it: a step deferred with
process.nextTick (resume, emitReadable, endReadable, afterWrite, the
destroy and finish steps) in the context that scheduled it, and other
events in the context of the push(), write(), end(), destroy() or pipe()
call that emits them. Native-backed streams run their events in the
context of their handle.

The runtime now captures the active context where it defers a stream
step and enters it around the step's dispatch:
- the stream tick queue keeps each tick's scheduling context;
- the runtime's own process.nextTick jobs (zlib completions, pipeline and
  finished steps, readline) run in the context they were queued in, as
  user ticks already did;
- fs read and write stream requests keep the context of the call that
  started them, so a stream's open, reads, writes and close follow its
  creation;
- zlib streams keep their creation context and run each engine step's
  completion in it.

In programs that use AsyncLocalStorage, stream.finished and
stream.pipeline callbacks are bound to the context of their call, like
the other completion callbacks. Programs that do not use
AsyncLocalStorage compile to the same IR.

child_process and http2 events are unchanged.

Corpus 7383 and 7384.
2026-10-22 17:05:08 -05:00
Chris Tate 5772f58cad Skip emitter analysis walks whose answers are already known (#1861) 2026-10-22 17:04:12 -05:00
Chris Tate 9b0ec269ac Select AST children by wire slot in the generated getters (#1870) 2026-10-22 17:01:35 -05:00
Chris Tate cdc7b384d4 Convert doubles below 2^63 to int32 with one conversion (#1871) 2026-10-22 17:01:09 -05:00
Chris Tate b2bb7be0b4 Support the env option of spawnSync (#1825) 2026-10-22 17:00:17 -05:00
Chris Tate ffaf14c22c Merge main and treat what the token scanner does not model as an executable edit 2026-10-22 16:59:12 -05:00
Chris Tate 159740c685 Convert doubles below 2^63 to int32 with one conversion
ToInt32 and ToUint32 of a double that no range proves an integer took the
i64 conversion only up to 2^53 in magnitude, and decoded the double's bits
above that. Every double between 2^53 and 2^63 is an integer, so the i64
conversion is exact there too, and its low 32 bits are the result. The bit
decoding now handles only NaN, the infinities and magnitudes from 2^63.

Multiplicative hashes such as `((h ^ b) * 16777619) >>> 0` and
`(h * 1000003 + b) | 0`, whose products usually pass 2^53, convert on the
fast path.
2026-10-22 16:57:09 -05:00
Chris Tate 8bc4586b9a Select AST children by wire slot in the generated getters 2026-10-22 16:56:13 -05:00
Chris Tate 57c10fc767 Build Sets, Maps and weak collections from checked-dynamic iterables (#1856) 2026-10-22 16:53:29 -05:00
Chris Tate 3bbb88c43c Construct through arrays of class values that mix a class and its subclasses
`const kinds = [Dog, Cat, Animal]; new kinds[0]()` now works as in Node:

- TypeScript: the checker types the element as the common base
  (`typeof Animal`), and an element read keeps the `undefined` an index
  past the end reads. The construction now narrows the class value out of
  that union, and an index past the end throws Node's TypeError
  ("kinds[5] is not a constructor"). Before, it refused with SC2001.
- JavaScript: such a literal is a checked-dynamic array stored in a slot
  of the base's class values, and the check accepted only the base's own
  class value, so storing it threw "expected class constructor".
  A class value of a subclass now checks into its base class's slot, as a
  subclass instance already checks into a base-class instance slot.
  Classes without subclasses keep the exact check.
2026-10-22 16:51:25 -05:00
Chris Tate b03cb004ad Send an http client request's head and body in one write (#1867) 2026-10-22 16:50:41 -05:00
Chris Tate 631d4dcc30 Keep record types acyclic when only result-slot stores write their fields (#1865) 2026-10-22 16:50:21 -05:00
Chris Tate 74a958408f Compile chalk 5 and hot-shots 8 from built-in shims with callable class instances (#1836) 2026-10-22 16:49:19 -05:00
Chris Tate 9df9cfc82f Keep literal key order for records a narrowing throw arm types (#1837) 2026-10-22 16:48:21 -05:00
Chris Tate 36db0613cc Bound the compiler's cyclic garbage to twice its live heap (#1863) 2026-10-22 16:46:32 -05:00
Chris Tate 7a5fdae1bd Send an http client request's head and body in one write
request.end(body) on a request whose head was not sent yet wrote the
head and then the body. On a socket that was already connected (a
reused keep-alive connection), each write is sent at once, so the two
went out as separate segments, and the server could handle the head and
answer before the body arrived. Node sends the head together with the
body. When the client and the server run in one program, the server's
'end' and 'close' for the request and the client's reading of the
response then depended on which socket the poller reported first: corpus
5409-http-held-request-body printed the client's line before the
request's 'end' and 'close' in a macOS dev build.

The head and a body known at end() now go out as one write, as Node
sends them (the server's response already did).
2026-10-22 16:45:59 -05:00
Malte Ubl 56abb3d77a Merge ni-2-node-island-report (#1691) into the Node islands base 2026-10-22 14:45:33 -07:00
Malte Ubl 7851907c0e Merge ni/13-libnode-packs (#1684) into the Node islands base 2026-10-22 14:45:29 -07:00
Chris Tate c271aa85aa Count holes written through dynamic views of typed arrays 2026-10-22 16:45:21 -05:00
Chris Tate fa62abce4d Keep record types acyclic when only result-slot stores write their fields
The backend gives every function that returns a complete record literal a
result-slot entry, `%into.<fn>(...params, dst)`, which stores the literal's
fields into caller frame storage. The cycle analysis counted those stores
as writes after construction, so a record type that refers to itself and
is never written after construction carried the cycle collector's header,
its live count and its candidate checks in release builds, even when
nothing called the entry. Dev builds, which add no entries, left it
acyclic.

The destination is frame storage of a caller that only projects the value
(stack-objects.ts): no object can point to it, so its fields cannot lie on
a cycle. computeCycleMutability now skips those stores, under the same
condition emit-statements.ts already uses to emit them as initializations
(resultSlotStore in the new result-slots.ts). Every other store still
makes its field mutable. The same facts mark such fields as never
replaced, so reads of them borrow instead of retaining.

A program that rebuilds a tree of 2^21 - 1 object literals 12 times runs
in 1.19 s instead of 1.35 s and peaks at 68 MB instead of 84 MB: each node
is 32 bytes instead of 40.
2026-10-22 16:45:06 -05:00
Chris Tate 5dacb5bed3 Keep running maximum and minimum updates as branches (#1826) 2026-10-22 16:42:08 -05:00
Chris Tate d7717ff1e9 Type empty array literals by their slots, and report package diagnostics accurately (#1799) 2026-10-22 16:40:40 -05:00
Chris Tate 753c64febc Test for-in key presence in the overflow map instead of a fresh key list (#1859) 2026-10-22 16:35:49 -05:00
Chris Tate bb34d61b55 Read optional elements at a proven integer index without a round trip (#1845) 2026-10-22 16:32:32 -05:00
Chris Tate ee865bcb0d Support the legacy createCipheriv modes and crypto.checkPrime/generatePrime (#1832) 2026-10-22 16:32:12 -05:00
Malte Ubl 5cb2329780 Merge remote-tracking branch 'origin/main' into ni-2-node-island-report 2026-10-22 14:31:26 -07:00
Malte UblandClaude Opus 5.5 1d94942bf9 Treat three timing-dependent island ordering programs as races
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>
2026-10-22 14:31:17 -07:00