Commit Graph
4443 Commits
Author SHA1 Message Date
Chris Tate dbbefae54c Mark typed-array header loads invariant (#2016) 2026-10-23 12:21:01 -05:00
Chris Tate d891a245ab Clone Response and Request bodies by teeing their streams (#2008) 2026-10-23 12:16:46 -05:00
Chris Tate 4dcb83c418 Free a typed teardown's blocks in the order it met them (#1849) 2026-10-23 12:09:37 -05:00
Chris Tate 75c210a472 Update tsc-ts to 0.0.9 and reuse bundled declaration diagnostics (#2005) 2026-10-23 12:04:00 -05:00
Chris Tate bcb697ada3 Give the tsgo/tsc-ts IR parity job 60 minutes (#1879) 2026-10-23 11:51:09 -05:00
Chris Tate b92fb63043 Mark typed-array header loads invariant
A typed array's length, data pointer and shared backing are fixed when
the view is constructed. Each element access loads the data pointer
beside the length, ahead of its bounds branch. LLVM then sinks the data
pointer load into the in-bounds block, and LICM refuses to hoist a load
that runs only on some iterations. A loop whose body branches or stores
an element therefore reloaded the data pointer for every access.

Header loads now carry `!invariant.load` next to their alias tag, so
the data pointer and length leave such loops. Debug builds emit neither.
2026-10-23 11:49:45 -05:00
Chris Tate 7eb90051a2 Give corpus scratch paths a per-process name (#2010) 2026-10-23 11:42:17 -05:00
Chris Tate 891a1a46a9 Keep fixed layouts for JavaScript classes next to freezes and unrelated accessors (#2012) 2026-10-23 11:26:42 -05:00
Chris Tate 6828c23b50 Error tee branches when the source errors with no read pending
The tee learned a source error only through a pending read's error
steps, so a source that errored while both branches held buffered
chunks and no read was pending left the branches readable: the next read
returned a chunk where Node rejects. The tee now watches its source's
error, as the streams spec reacts to the source reader's closed promise,
and errors both branches in a microtask (resolving the cancel promise
unless both branches were canceled). A read's error steps only end the
read, and a clone of an already errored body errors both branches the
same way.
2026-10-23 11:20:05 -05:00
Malte UblandClaude Sonnet 5.5 fab4b403e9 Give corpus scratch paths a per-process name
The differential and LLVM differential suites can run the same corpus
program's Node oracle at the same time in one shard. Programs whose
scratch directory or file was derived only from argv[1] collided, and
one oracle removed the paths under the other. Their names now include
the pid, as 1403 and 5567 already do. Printed output never carries the
name.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 09:07:41 -07:00
Malte Ubl ce06a3377d Merge remote-tracking branch 'origin/main' into ci/tsgo-timeout 2026-10-23 09:01:36 -07:00
Chris Tate 5f22ee7146 Run two-counter loops and element copies over number arrays without per-element checks (#1991) 2026-10-23 11:00:35 -05:00
Chris Tate 6e849eeaef Read string-keyed record entries without allocating under ?? and ! (#2007) 2026-10-23 11:00:10 -05:00
Chris Tate 2d4aac0976 Read Web Headers pairs without copying them, and cache their sorted list (#2000) 2026-10-23 10:50:13 -05:00
Malte UblandClaude Opus 5.5 e84f656172 Clone Response and Request bodies by teeing their streams
Response.clone() and Request.clone() on checked-dynamic handles answered
'res.clone is not a function' (hono's etag middleware hashes
res.clone().body). The runtime now implements the streams spec's
ReadableStreamDefaultTee over native streams and clones the body with it,
so the original and the clone each read every chunk. A used or locked body
throws undici's TypeErrors, the clone's headers are a copy with the same
guard, and a cloned Request's signal follows the original's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 08:47:43 -07:00
Chris Tate 998a385bde Mark constructed headers canonical only when every value is trimmed
The Headers and Response constructors combine duplicate entries (a: x,
a: "") into one value with the separator, which can end in its space
("x, "). They marked the pairs canonical anyway, so a copy took them
unchanged where Node trims the copied value ("x,"). The constructors
now mark the pairs canonical only when every value is already trimmed;
otherwise copies normalize them pair by pair.
2026-10-23 10:45:26 -05:00
Chris Tate ecbfa766ef Store number arrays of proven int32s in 4-byte slots across direct calls (#1960) 2026-10-23 10:42:51 -05:00
Chris Tate c11253b228 Reject other classes' capsules inline in class-table brand probes (#2001) 2026-10-23 10:35:30 -05:00
Chris Tate 5fd47eac77 Search proven-ASCII strings backwards by byte in lastIndexOf (#2002) 2026-10-23 10:34:16 -05:00
Chris Tate d3c8155c85 Reuse the bundled declaration files' diagnostics across builds 2026-10-23 10:32:15 -05:00
Chris Tate 14678cf4eb Keep arrays a present-undefined store may reach out of the dense fast copy
A present undefined that the fast copy stores leaves its slot out of the
hole-free prefix the entry test read. The matcher left out only the array
named by the store, so a store through another name for the same array,
such as `const b = a` followed by `(b as (number | undefined)[])[i] =
undefined`, kept `a` covered, and the fast copy read the slot as a number.

Such a store now leaves out every covered array it may reach: its own, and
every other array unless that array or the store's binding is a local
array no other binding holds. A store into a field reaches every array
except those local ones.
2026-10-23 10:29:35 -05:00
Chris Tate 11426b4fc2 Read string-keyed record entries as stack unions under projections
A keyed read of a string-indexed record whose values are numbers or
booleans (`counts[key]` on a `Record<string, number>`, typed
`number | undefined`) allocated a heap union for every read, also when
the only consumer was `??`, `!` or a comparison with undefined. The
runtime already returns the entry's value and whether it is present;
the box came from wrapping that result.

Such a read is now a stack union source: under a projection, the read
writes the tag and bits of the declared field or index-signature entry
the key names, or the undefined arm for a missing key, into a private
stack box, and allocates nothing. This applies when every hit is a
number or boolean arm of the result union, or an inline field of the
same union, and also to an argument a callee only projects (the helper
that `x!` lowers to). Other consumers keep the heap union.

dictionary-walks allocates no union instead of 363,359 and runs 14.6%
faster; no other runtime benchmark's code changes.

Corpus 7390.
2026-10-23 10:26:22 -05:00
Chris Tate b6e8e149b5 Trim combined header values again when copying after an empty append
Appending an empty value to an existing header leaves the separator's
space at the end of the combined value ("x, "). Node keeps that in the
Headers object but trims it when the value is copied into another
Headers, Request or Response ("x,"). The copy fast path copied a
canonical object's pairs as they were, so the copy kept the space. Such
an append now marks the pairs as no longer canonical, and copies
normalize them pair by pair.
2026-10-23 10:25:37 -05:00
Chris Tate 949f4d51de Keep loop-carried locals written from int32 element reads int32 (#2003) 2026-10-23 10:17:54 -05:00
Chris Tate 779893eef8 Address review feedback on int32 arrays across calls 2026-10-23 10:15:37 -05:00
Chris Tate 6ebdec28e2 Update tsc-ts to 0.0.9
tsc-ts 0.0.9 answers getDiagnosticsOfCheckedFiles, which checks only the
listed files of a program and answers their semantic diagnostics and the
global diagnostics. The release-age exclusions admit it with its platform
packages.
2026-10-23 10:14:37 -05:00
Malte UblandClaude Opus 5.5 a705a3fd09 Reject other classes' capsules inline in class-table brand probes
A class table (property bags, normalization, computed reads) tests the
receiver's capsule key against every compiled class in turn. A capsule
boxed by another class has another key pointer, and each such probe called
scr_dyn_typed_ref_is_key to compare the names. The probe now rejects it
inline when the key length or its last eight bytes differ, and calls the
out-of-line comparison only for a likely match, so capsules boxed with an
equal key elsewhere still match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 08:04:00 -07:00
Malte UblandClaude Opus 5.5 7b8a07e0f2 Search proven-ASCII strings backwards by byte in lastIndexOf
lastIndexOf built the receiver's UTF-16 index map and converted positions
both ways even when the receiver is proven ASCII, where byte offsets are
UTF-16 indexes (indexOf already has this fast path). A short id such as
"bk_00042".lastIndexOf("_") cost about 200 instructions; the Hono
fixture's list endpoint does it 80 times per request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 08:04:00 -07:00
Malte UblandClaude Opus 5.5 056dda3633 Read Web Headers pairs without copying them, and cache their sorted list
Headers kept its name/value pairs in a string array and read every element
through scr_arr_get_ref (index checks, retain, release). forEach and the
iterators re-sorted and rescanned the whole list for every step, and every
Response or Headers built from another Headers object re-validated and
re-normalized each copied pair. The Hono fixture spends 12-15% of a request
there.

- Pair loops read borrowed elements of the dense array (sf_pair_at).
- The sort-and-combine list is cached on the Headers object and dropped by
  every mutation, so live iteration keeps Node's semantics.
- get() of a single value returns the stored string.
- Copying a Headers object whose pairs are canonical (built by the
  constructor or a Response) into an empty list copies the pairs; wire
  response headers keep the per-pair path because they may repeat names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 08:03:59 -07:00
Chris Tate e771de1cfb Check cycle bindings only where early code can reach the reading function (#1882) 2026-10-23 10:02:29 -05:00
Chris Tate a23ce3043c Keep loop-carried locals written from int32 element reads int32
The int32 array proof assumes its candidates' elements are int32 and
asks the integer range analysis whether every stored value is one. A
local assigned in a loop from an element read of a candidate array
(`const t = next[at]!; next[at] = at; at = t`) never closed under the
int32 bound, because the loop-carried closure had no case for element
reads, so arrays that store such cursors kept double slots and every
index converted from a double. Element reads now take the slot range in
that closure, as field reads and calls already did.

The index-cursors runtime workload walks an array of indices with such a
cursor.
2026-10-23 09:53:05 -05:00
Malte UblandClaude Opus 5.5 7c33b55435 Keep fixed layouts for JavaScript classes next to freezes and unrelated accessors
Two rules sent every JavaScript class family to field-creation tracking:

- Any Object.freeze, Object.seal or Object.preventExtensions of a value
  other than a fresh literal. These never create or remove a property, and
  a class instance keeps its integrity state beside its fields: a typed
  call sets the CLASS_ATTRIBUTES word that TypeScript classes use with the
  same fixed layouts, and through an untyped value the runtime refuses the
  operation on every class instance (SC2020). Only Reflect.deleteProperty
  stays a removal of any name.
- Any getter or setter. An accessor only matters when it shares a name
  with a field of the instances that see it, or when a subclass accessor
  can intercept a base constructor's field assignment. Other accessors
  live on the prototype beside the fields.

In zod 3, ParseStatus and ParseInputLazyPath now keep fixed layouts (2 of
the 39 tracked classes; the ZodType family stays tracked because its
constructor creates own properties before _def). z.array(Order).parse
over 500 orders: 270.7M -> 235.8M instructions per round (-12.9%).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 07:39:29 -07:00
Chris Tate dddcb339aa Revert "Cache untyped property access in programs that declare classes" (#1996) 2026-10-23 09:35:24 -05:00
Chris Tate 0f8dd276d9 Reach a class's methods from its class expression's inner name in static initializers
A named class expression's own name (const C = class Impl { static x =
Impl.m(); }) names the class inside its body as the binding it is stored
in names it outside. The reachability walk followed only stored binding
names, so a static initializer calling a method through the inner name
did not reach that method, and its early reads of the other cycle
member's bindings lost their TDZ checks: the program read the binding
before its declaration ran (a crash), where Node throws ReferenceError.
The inner name now maps to its class expression.
2026-10-23 09:24:28 -05:00
Chris Tate 885a829959 Compile minified npm packages such as marked (#1989) 2026-10-23 09:07:41 -05:00
Chris Tate a10c183b13 Revert "Cache untyped property access in programs that declare classes (#1995)" 2026-10-23 09:05:55 -05:00
Chris Tate 17c069667e Settle a var declared by a for-in or for-of loop only inside the loop body
A for-in or for-of loop stores its var binding only when it iterates. The
early-read analysis counted the loop as an unconditional store for every
later statement, so after a loop that ran zero times a closure read the
binding as initialized (typeof answered "string" where Node answers
"undefined"). The loop now settles the reads in its body; later reads
settle at the next unconditional store.
2026-10-23 09:03:59 -05:00
Chris Tate da2173d54b Cache untyped property access in programs that declare classes (#1995) 2026-10-23 08:47:07 -05:00
Chris Tate 4b60ac0de9 Copy class-view property bags entry by entry (#1976) 2026-10-23 08:43:19 -05:00
Chris Tate fa22b991cc Return ASCII header values as their own ByteString (#1978) 2026-10-23 08:42:17 -05:00
Chris Tate 8eedab9fde Cache untyped property access in programs that declare classes
A program that declares a class reads and writes untyped properties
through helpers that first select the receiver's property bag. The bag
helpers return jsvals, so the helpers read the bag through js.toDyn;
keyed helpers test each class's key table between the key's coercion and
the read; and a program with subclasses normalizes the receiver
(value = %dyn.class.normalize(value)) at the start of each helper. The
inline-cache recognizer accepted none of these forms, so every property
site in such a program called its helper. It now looks through dyn/jsval
conversions of the receiver, accepts capsule branches before a keyed
helper's final read, and accepts a rebinding of the receiver to a bag of
itself. Plain objects and arrays then read and write through the site's
cache, as they do in programs without classes.

bench/dynamic-properties (new) exercises these sites.
2026-10-23 08:41:40 -05:00
Malte Ubl 3be0a1e468 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-23 06:38:28 -07:00
Malte Ubl 2365c04417 Merge remote-tracking branch 'origin/main' into HEAD 2026-10-23 06:38:10 -07:00
Malte Ubl be7a194bfd Merge remote-tracking branch 'origin/main' into HEAD 2026-10-23 06:33:24 -07:00
Chris Tate 8476f68136 Support writev on Transform and PassThrough, and runtime stream option values (#1944) 2026-10-23 07:53:59 -05:00
Chris Tate d1b63c782c Keep every field when an object converts to a union with an empty-record arm (#1992) 2026-10-23 07:53:37 -05:00
Chris Tate 1221104388 Merge main and fix CI 2026-10-23 07:50:09 -05:00
Chris Tate 5c015be6be Try wider record arms first when a dyn value converts to a union
Records are width-tolerant, and a union's arms were tried in canonical
order, which sorts record arms by shape id. When a `{}` arm, or an arm
whose fields are all optional, came first, it took every object, so a
value converted to the union lost the fields only a wider arm has:

    const pairs = [["a", { publicKeyEncoding: 5 }], ["b", {}]];
    for (const [, v] of pairs) console.log(Object.keys(v));

printed [] twice, where Node prints [ 'publicKeyEncoding' ] and [].
A checked cast to `{ p: number; q: string } | { p?: number }` dropped
`q` the same way.

The checked conversion now tries record arms with more required fields
first, then those with more fields, and keeps the canonical order
otherwise. The union's tags are unchanged.
2026-10-23 07:47:26 -05:00
Chris Tate 65513ac93d Merge main 2026-10-23 07:47:12 -05:00
Chris Tate da1dae0625 Run two-counter loops and element copies over number arrays without per-element checks 2026-10-23 07:20:08 -05:00