A pushed-down slot's dynamic operations act on its failing components,
whose checker types are not degraded themselves. The lenient mapping
records those components with the declared type they belong to; SC6101
names them ('hook' is a dynamic piece of 'Plugin[]'), and the operation
counts go to the owning slot. Causes read a binding's declared type, not
its narrowed flow type. The coverage test uses its own fixture, so it does
not depend on which interface members main can map.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A string held in a binding or returned by a call can be a missing element
read (undefined), so `head + PAD[j]` and `at(i) + PAD[j]` still add to NaN
when both are missing. Only string literals, templates and sums with such
an operand now count as always a string.
- `readdirSync(path, { recursive })` folds only the `true` and `false`
keywords. Any other flag expression, including one typed as a literal,
evaluates at run time for its effects and exceptions.
A speculative integer loop's fast copy exits to a copy that resumes the
failed iteration, and both read the header fields of the views they
access. GVN's load PRE moved the fast copy's read of a view's data
pointer ahead of the exit branch, to make the resume copy's read
redundant, and put a copy on the exit edge. The loop then kept its first
iteration in the loop, so the overflow checks of a value whose range is
known after one iteration stayed in every iteration, with a reload of the
data pointer.
A view held by a const local or a parameter that the loop does not rebind
is the same view in every iteration, and its length, data pointer and
shared storage never change. The loop's entry now reads them once, and the
element accesses of every copy use those values.
The store a constant choice emits for its arms used the first arm's
index expression, with the ranges proven inside that arm. When the index
is the compared local, `j === 0` narrows it to [0, 0] there, so the one
store, which also runs on the final else's path, converted the index to
an int32 from that range. For `j` = 4294967297 that conversion has no
defined result, and the bounds check that follows could accept it: an
out-of-bounds write could reach a valid element.
The store now reads such an index through the first comparison's
operand: the same local, read before the chain branches, so its range
holds on every path. An index that is another local is not narrowed by
the comparisons and is unchanged.
7515-net-socket-backpressure's server picks each connection's handling from
`mode` when it accepts it. The lookup client's 70000 bytes fit in the
kernel's buffers, so its 'drain', and the step that sets `mode` to "pause",
can run before the server accepts that connection. On macOS the accept can
come in a later kqueue batch: the server then paused the lookup socket,
never read its EOF, and the open connection kept the program running after
server.close(). The program now waits for that accept before it changes
`mode`. Its output is unchanged.
An if/else-if chain that compares one number local with integer literals
and stores a number literal to the same typed-array element in every arm,
including a final else, such as
if (x === 97) a[i] = 0;
else if (x === 116) a[i] = 1;
else if (x === 99) a[i] = 2;
else a[i] = 3;
is now emitted as one store of an entry of a constant table indexed by
the local's value (constant-choices.ts). On input whose cases do not
repeat in a pattern, such as the bases of a DNA sequence, the branches
mispredict; LLVM keeps them, because cases this far apart are too sparse
for its own switch tables.
The chain needs at least three distinct values within 256 of each other.
The table covers the cases' span, or the local's proven integer range
when that range has at most 256 values, in which case no test is
emitted. Otherwise a value that is not an exact integer in the table's
span (NaN, a fraction, or a value outside it) stores the final else's
literal, chosen with a select. The first arm wins for a repeated
value, and -0 selects the arm for 0, as strict equality does. The store
is the arms' own (the same receiver and index), converted to the
element's type as each arm's would be. Debug builds keep the arms.
k-nucleotide's transform of the input sequence uses this form: its
median wall time drops from 2.71 s to 2.21 s.
bridgePlainUnwindCalls rewrites the module text after emission on Linux.
It split the text with a lookahead regular expression, ran a regular
expression over every line to find mentions of raising functions, and
split every function that has calls into lines to test each one.
- The text splits before each `define` line with indexOf.
- Mentions of raising functions are found by scanning for `@` and reading
the name that follows it.
- A function without a plain call of a raising function rewrites only the
lines that hold `call `, marking them nounwind. A function with such a
call takes the line-by-line path as before.
The emitted text is unchanged.
A limited pass (scr_cyc_collect_set, which freeing a LifetimeRegion runs
over the objects the region owned) takes only the candidates in its set.
Its walk can still reach other candidates, and one that is referenced from
outside is blackened by scan and promoted to the next generation while it
keeps its slot in its old generation's buffer. scr_cyc_on_dead finds a
candidate's buffer by its generation, so when that object died it removed
another buffer's entry and left its own slot pointing at the freed block,
which the next pass then read.
Such a candidate is no longer promoted: it keeps the generation of the
buffer it is in until a pass takes it.
The frontend reads the same children of the same nodes many times: on a
tsc-ts build, `expression` is read 29.6M times over 253K nodes, `name`
16.4M times over 176K and `text` 26.8M times over 456K. Each read went
through AstFile.namedChildAt (the file check, the child mask, the packed
child table) and AstFile.node (the node table), and `text` through the
wire's kind switch and string table.
- AstNode.childNodeAt keeps the child nodes it has read, by wire slot, in
a per-node array sized to the node's highest named child.
- AstNode.text keeps the text it has read.
- AstFile's packed child table is a Uint32Array from the start (empty
until the first named child is read), so namedChildAt no longer
retains and releases an optional value on every call.
Cold tsc-ts build with the native compiler, 16 interleaved rounds: lower
31.68 -> 30.46 s, wall 78.81 -> 76.56 s (faster in every round); peak RSS
+105 MB, which the frontend-dispose heap trim keeps below main's peak.
The element-independent callback paths read the slot after the callback:
- filter appended the slot as it was after the predicate;
- find and findLast read the element only once the predicate held.
A predicate that overwrote or removed the current slot therefore kept or
returned the new value, where JavaScript keeps the element it read before
calling the predicate. An unfused filter over proven-present elements also
stored the placeholder it passed to a predicate with an unused element
parameter.
- filter, find and findLast keep a snapshot of the slot, read before the
callback runs (arraySlotSnapshot): the value, or the dynamic value of an
element outside the element type. filter appends that snapshot as it
was stored, and find returns it, with the checked conversion for such an
element. Fused loops take the same snapshot before a stage whose
callback ignores the element.
- Leaving an ignored element unread now applies only to arrays that can
hold elements outside their element type (elementMayBeForeign) and are
not proven present. Every other array reads the element as before, so
its filter stores the element it read.
The bootstrap test builds its own seed when CI shares none, and that build
still applied the committed function layout, so its LLVM listed functions in
a different order from the native compiler's own emission. Both seed builds
now share one environment, which leaves the layout out.
A presence test of a literal key that a record shape does not declare
read the receiver as the record and answered undefined. In an optional
chain that conversion runs before the chain can short-circuit, so
`missing?.["a;b"] === undefined` threw a TypeError for a missing value
stored dynamically, where Node answers true. An optional read keeps the
optional chain's lowering, which reads a nullish receiver as undefined
and the undeclared key of a present one as undefined.
The native bootstrap compares the seed's LLVM, which the Node-hosted
compiler emits, with the LLVM the native compiler emits for itself. The
seed's build applied the committed function layout and the native
compiler has none, so the two listed the same functions in different
orders. The layout only orders functions, so the seed is built without it.
- A deferred class with static field initializers is reached where its
statement evaluates: its file's %init retries it there
(retryDeferredAtDeclaration), so its initializers lower in place. A failed
retry reports nothing, restores the types it degraded, and leaves the
class deferred; no later flush registers it, so it reports at its
reference instead of registering after its initializers' place. Before, a
class a later reference registered never ran its static initializers.
- A generic class that registers on retry registers the units of its
static methods. Before, a call to one was an undeclared function
(SC9001).
- A statement that lowers again after degrading a type keeps the
diagnostics of the function-local class bodies it lowered the first time:
the retry reuses those bodies instead of lowering them again.
- retriableDiags and failedTypes are a Set and a Map owned by the lowerer
instead of weak collections, and the degraded types of a mode are copied
without an empty-array fallback, which the self-hosted compiler could not
lower.
- The coverage test of a class rejected behind a receiver probe pins its
refusal under --tier=static-only, as the diagnostics corpus does; under
localized failure the class compiles.
A binding that a function declared earlier captures is pre-declared as a
TDZ box, and its declaration's assign initializes the box. The declaration
dropped the pre-declaration before lowering its initializer. When the
initializer failed with a degradable type (a cast whose type does not
compile) and the statement commit point lowered the statement again, the
retry no longer saw the box: it declared a new local and initialized that,
and a later call of the function threw ReferenceError.
The pre-declaration is now dropped after the initializer lowers, for
variable, class and generic-function declarations.