A getter or proxy trap that runs during a trusted build outside any
visit session now runs with the build's state set aside, as inside a
session. A conversion the getter starts, such as a recursive union
that opens and ends its own session, no longer clears the mark that
keeps the rest of the outer build from trusting its earlier match.
The receiver of a borrowed Map.get is read before its key. When the key's
evaluation replaces the field or local that holds the map
(`this.m.get(this.key())` where key() assigns this.m), the read runs on
the old map, which only the statement still holds, and the borrowed
value outlives it. A borrow now also requires the read itself, receiver
and key included, to preserve reference edges; otherwise the read keeps
its own reference.
Corpus 8181 and the emission test cover a key method that replaces the
field.
Buffer.from(text, "base64" | "base64url") decoded each four-character
group with four lookups and a sign test, then stored its three bytes one
at a time. The decoder now first takes complete groups two at a time:
each character's value comes from a table that holds it already shifted
into place within its group's 24 bits, a single bit test covers all
eight characters, and each group's three bytes go out in one four-byte
store whose last byte the next store overwrites. A group with any other
character (padding, whitespace, an invalid character) takes the
previous path, and the two-group step resumes after it, so line-wrapped
text keeps most of the gain. The destination already holds two spare
bytes, which cover the extra store byte.
`a?.[i]` on a binding whose type admits undefined, such as a
`string[] | undefined` parameter, threw "undefined is not representable
in the target union" when the binding held undefined. Element access
replaced an identifier receiver whose storage has an undefined arm with
a checked narrow to its present arm before the optional chain ran, so
the chain saw a receiver that is never nullish and lowered `?.[` as a
plain element read.
Element access now leaves the receiver to the optional chain when the
access carries `?.`; the chain tests the undefined arm and lowers the
access again inside its guard. This applies to arrays, tuples and
record keys. Property access (`a?.length`, `a?.at(0)`) already lowered
through the chain.
Corpus 8330 matches Node's output.
readline split redirected input byte by byte, twice per chunk (once to
find whether the chunk ends a line, once to split it). Before splitting
it copied each chunk out of its Buffer and into a scratch buffer, decoded
it into a new string, and copied the unfinished line and the whole chunk
into a joined buffer. Now:
- one scan per chunk: memchr for a CR once, then LF to LF with memchr
when the chunk has none, and the LF, CR and CRLF split otherwise;
- the unfinished line joins only the first line of the chunk;
- an ASCII chunk with no partial UTF-8 sequence held from the previous
one is split where it is, and a chunk that is not shared memory is
read from its own storage. Lines are still copied into their strings
before the first is delivered.
Buffer.from(string, 'latin1' | 'ascii') encoded character by character;
an ASCII prefix, all of an ASCII string, is now copied with memcpy.
Line events, their order and the CRLF delay are unchanged. Corpus 8344
covers lines joined across chunks, a line longer than a chunk, empty
lines, mixed line endings and UTF-8 split across chunks. The new
line-reader workload reads a 67 MB log through readline over a file
stream.
- A guarded declaration of a name the CommonJS wrapper defines (`var
exports = exports || {}`, and `module`, `require`, `__filename`,
`__dirname`, `arguments`) reads the wrapper's value, not undefined, so
it keeps its text instead of becoming a fresh object.
- The write scan counts destructuring assignments, and destructuring
for-in/for-of targets, as writes, so a hoisted function that sets the
namespace before its guarded declaration keeps the declaration as it
is.
- An exported `var` that a later declaration of the same name sets
again is not settled at the export: importers would read the
redeclared value, where Node copies the first one into
module.exports. It stays refused (SC1090), as before. The npm case
var-export-redeclared pins it.
`JSON.parse(text) as T` for a discriminated union of records took the
checked-dynamic route: it parsed the text into a dynamic tree, matched
and copied that tree into records, then freed it. The one-pass schema
parser now builds these unions directly, as record members, array
elements and whole documents. For an object, a shallow scan of its
members finds the discriminant's value, which selects the one record arm
that holds that literal, and the object is parsed with that arm's
schema. An object with a missing, non-string or unknown discriminant, or
any member the arm rejects, declines, and the checked route then gives
the result or the error as before.
The compiler admits unions whose record arms each name their string
literals, every literal naming one arm, plus null and undefined arms.
Since 76735a8d9 (#1975), a `super.m()` call in a JavaScript class method
whose `this` is the call-site receiver reflected the base class's whole
prototype and read `m` from it. An instance's member lookup first tests
its property bag, which inherits from its class's prototype data, so once
the base's prototype was reflected, `child[k]` for a method the subclass
overrides found the base's method there: `value[name](arg)` on a subclass
instance called the base method.
While no code reads a prototype method by name, `super.m()` now calls the
declaring class's receiver-independent method value with the receiver, as
the method's [[HomeObject]] lookup answers, without reflecting the base
prototype. Programs that read prototype methods by name keep the
reflected lookup.
A read site's cache only remembered properties it found on the receiver
or its first prototype. Reads of properties an object lacks, which
validation libraries make constantly (zod reads ctx.skipChecks,
payload.memo, x.aborted and _zod.optin on every check), walked the whole
shape chain and then repeated the lookup on the generic path every time:
7.9 million such reads in one zod benchmark run.
- Read sites call a new probe, scr_js_get_probe, that answers undefined
for a key the receiver and its chain of plain shaped objects (at most
one prototype, ending in the default prototype) lack, and caches that
answer. The entry holds while the receiver's shape, its prototype link,
and that prototype's shape are unchanged. Its tag and placeholder
holder make the inline entry-0 test in compiled code miss, so the
answer always comes from the probe. Method reads keep
scr_js_load_probe, so a missing method still reaches the slow path and
its "is not a function" error. Names of Object.prototype members are
never cached as absent.
- Receivers of one shape with different prototypes (zod's internals
objects) each keep an entry instead of replacing one another.
- A site whose four-way record is full consults a per-thread table keyed
by shape and key before walking shapes, and fills it.
8179-tls-reject-unauthorized-values.mjs called the resolve function of an
untyped new Promise() with no argument. tsgo types that function as taking
one argument and reported SC0001 for each call, while tsc-ts accepted them,
so the checker-identity comparison failed. The calls now pass undefined.
A Map/Set lookup's integer key view was cached for the function by the key
operand's name. When the view was a conversion emitted at a lookup in a
branch, a later lookup of the same operand (a wide integer constant) reused
it from a block the conversion does not dominate, and LLVM rejected the
module. Only the value's own integer or uint32 view and integral literals
are cached now.
Two refusals kept pre-module JavaScript packages from compiling:
- `var ns = ns || {}` (and `?? {}`): the read in the variable's own
initializer made the checker type the namespace `any`, and requirers
then failed to reach the binding (SC1030). In a module, the read sees
the hoisted `undefined` whenever nothing else writes the variable, so a
new package-source normalization respells the declaration as its
default (`var ns = ({})`) when no other declaration, assignment, update,
function, class, parameter or local of that name exists in the file and
no earlier top-level statement mentions it.
- `module.exports = { ns }` with a `var` binding was refused as exporting
a mutable binding by reference. A `var` (or `let`) whose initialized
declaration precedes the export statement at top level and that nothing
reassigns exports exactly the value Node copies, so it now aliases like
a const.
The namespace benchmark (results/design-shape-inference/nsbench) now
compiles and matches Node; the new npm case legacy-namespaces pins a
namespace library with guarded declarations, constructor functions with
prototype methods, enums, a revealing module and a handler registry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A typed array such as a number[] reaches untyped code as a capsule of the
native array. A keyed read `xs[i]` there missed the inline cache, which
answers only dyn arrays, and went to the member helper, which converted
the index to a string; the capsule's key lookup then parsed the string
back into a number for every element.
Keyed sites now read an element of a native number[] capsule in place
when the index is an integer below the array's hole-free prefix, and
`length` sites read the native array's length. Other capsule elements
(holes, out-of-range and fractional indices, arrays of other element
types) are answered by the keyed probe through scr_dyn_index_get without
forming the key, or by the helper as before.
Node's afterWriteTick sets afterWriteTickInfo to null before it runs the
callbacks, whichever tick it is. The runtime cleared the queued batch
only when the running tick was that batch, so a write made from an
earlier tick's callback joined a later queued tick with the same
callback and completed ahead of nextTicks queued between them. Any
afterWrite tick now closes the batch, and such a write queues its own.
The guard proves the views disjoint for one entry of the loop. An
enclosing loop may pass other views on its next pass, such as two
buffers swapped after every pass, so one pass's write and the next
pass's read can be the same bytes under different scopes. Without a
declaration, a scope holds across the whole function, and LLVM could
apply one entry's no-alias facts to another's accesses once it unrolled
the enclosing loop.
Each fast copy now begins with llvm.experimental.noalias.scope.decl for
each of its scopes. A declared scope holds from its declaration, and
LLVM gives each copy of the declaration its own scopes when it unrolls
or duplicates the code around it.
Corpus 8182 adds ping-pong passes over swapped Float64Array and
Int32Array views; the emission test checks the declarations.
A Map or Set operation on a number key that the emitter knows is an
integer (exactInteger: an integer view, a range proof or a literal) now
looks the key up through sc_map_entry_f64_int, which tests the map's
direct index with an unsigned compare of that integer. Before, the
lookup converted the double back to an integer and compared it with the
original to prove it integral. The hashed probe still takes the double,
so hashing and SameValueZero are unchanged.
Node's onwrite queues one afterWrite nextTick for a synchronous write
completion and lets later synchronous completions with the same callback
join it (afterWriteTickInfo.count); writes without a callback share one.
The step then checks 'drain' once, calls the callback once per write and
runs finishMaybe once. The runtime queued a stream tick and a nextTick
entry per write, which ran the callbacks of later writes after nextTicks
queued between them. The queued afterWrite tick now counts the writes
that join it, as Node's does.
`decoder.decode(view!)` on a `Uint16Array | undefined` (or another typed
array that may be undefined) was refused with SC2020: the assertion keeps
the undefined arm for decode, and the decode input only read the
undefined arm of `Uint8Array | undefined`. The undefined arm of any typed
array that may be undefined now reads as an empty array of the same type,
so a defined input decodes its bytes and an undefined one decodes no
bytes, as in Node.
A loop that reads one typed array and writes another kept every element
in memory: separate views can share a buffer, so LLVM had to assume a
store to one could change the other's elements. A loop of eight lanes
held in Float64Arrays (zr[k], zi[k], tr[k], ti[k]) ran as scalar loads
and stores of each lane on every step.
When a typed-array loop version covers at least two views and writes at
least one of them, its guard now also requires every pair of the views'
byte ranges to be disjoint: one ends at or before the other starts. In
the fast copy, each view's element reads and writes carry an alias scope
of their own and no-alias with the others' scopes, so LLVM keeps the
elements in registers and vectorizes across lanes. Views that share
bytes, or one array passed twice, take the checked copy as before. The
scopes are string-named, written inline, and named after the function
and the version, so every copy of a loop has its own. Worker programs
scope only views the guard proves thread-owned.
Corpus 8182 runs such loops over disjoint, adjacent, overlapping and
identical views, views of different element types over one buffer, and
empty views.