Main now compiles minified npm packages (#1989), so ni-minified no
longer refuses and the island report lost its packaging class. The
fixture is now ni-bundled, whose build-transform markers still refuse it
as shipped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
WeakMap and WeakSet kept each collection's entries in one list and found
a key by walking it, and every collection's entries for a key in 1,024
shared buckets. Filling a collection and reading it back took quadratic
time: 40,000 keys took 6.7 s against Node's 0.2 s, and 200,000 keys did
not finish.
Each thread now indexes its entries twice, in open-addressing tables with
linear probing and backward-shift deletion: by collection, key and kind,
for get, set, has and delete, and by key pointer, for a key's disposal,
which ends the key's entries in every collection. A collection's entries
and a key's entries are doubly linked, so an entry leaves both in
constant time. The tables grow at half load and are freed when their last
entry goes, so an exiting worker leaves none. 200,000 keys now take
0.24 s (Node 0.30 s).
Closes#1702
Co-authored-by: ejc3 <1694850+ejc3@users.noreply.github.com>
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.
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>
Records read out of a typed Map through an `IterableIterator<T>` method
(`all() { return this.rows.values(); }`) or out of a `Generator<T>` came
back through checked conversions, so the key-order witness treated their
type as built at run time and a self copy (`{ ...current, ...patch }`)
fell back to the declared order.
- A record yield channel keeps its type beside the defaulted `any` return
channel, like number, boolean and string already do: for-of and spread
read the yielded record unchecked.
- `m.values()`/`keys()`/`entries()` on a typed Map or Set flowing into an
iterator-typed slot walks the collection with typed reads. It keeps the
native iterator's protocol: lazy, live, and parked instead of closed by
an early loop exit.
- The witness counts a generator resume as building only its
IteratorResult record, not the value it carries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Math.max, Math.min and Math.hypot over a spread, and the default sort,
convert foreign elements with user code. As in Node, the elements are
taken first, each foreign value retained, so a conversion that changes
the array changes neither the count nor the values the operation sees,
and no slot read before the conversion is released under it. join and
the numeric element read hold the value they convert, and join reads the
length once and stops at a throw.
toSorted of an array with foreign elements now builds a copy of the
full length and reads its holes as undefined, as for other arrays.
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.
Main now reports fenced sites in module top-level code under "runs at
startup"; the use of 'legacy' on line 22 is one of them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
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.
ABI 5 takes a load function from the bootstrap's dispatcher; without it
the smoke program's start fails with an incomplete dispatcher.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>