Taking the head entry of a Readable's buffer moved every remaining entry
down one slot, so draining a queue of n buffered chunks cost O(n²). The
buffer now keeps the index of its first entry: a take advances it, a push
at the tail moves the entries down only when at least half the slots are
free at the front (and grows otherwise), and unshift takes a freed front
slot when there is one. The drop, trace and boxing loops walk the entries
from that index.
The stream-buffered-drain runtime workload buffers 200,000 objects and
100,000 Buffers before draining them; corpus 8533 covers reads across
entries, unshift into a freed slot and onto a part-read head, setEncoding
after takes, and object-mode takes, unshifts and pushes.
A root under one 4 KiB unit went to the first open partition from its
name's whose load from the larger roots was below the cap. When only one
open partition was below the cap, every such root went there, and that
partition could hold most of the module, which then was optimized whole
and lost the per-partition cache. The small roots a partition takes now
count toward its cap for the small roots after them, and a small root
that finds no partition below the cap goes to its name's. The larger
roots' placement still ignores the small roots, so a helper that an edit
adds or removes moves no larger root.
events.once(process.stdout, name) with a name typed "drain" returned the
pending promise without evaluating the name, so a call or comma
expression producing "drain" never ran. A name that is not a string
literal now runs before the promise is created.
A write completion callback that returns a value (`() => count++`, a
stored function typed `(): number`) reported SC2020. The stream ignores
the return value, so such a callback is adapted to return nothing, as
before the error-argument form was added.
The stream-shims npm case compiles and matches Node now that write
callbacks take the error argument, so its SC2020 expectation is removed.
A Response body's stream gets a web ReadableStream view when a member the
fetch stream lacks is read (for example `body.tee`). The view's source holds
the stream, and the stream released the view only when that source was its
last holder. While the transfer runs it holds the stream too, so a dropped
body with a view kept a reference count of 2: the unobserved-response
discard never ran, the paused socket was never resumed, and a compressed
response dropped before HTTP EOF kept the process alive.
The stream now releases the view when only the view's source and the live
transfer hold it, and then takes the unobserved-response discard, as a
body without a view does.
The static-abandon-view fixture reads `body.tee` and `body.locked` of a
4 MiB incompressible gzip response from the new /gzip-incompressible route
and drops it. It hung until the test's timeout before this change and now
exits as Node does.
process.memoryUsage() reported external and arrayBuffers as 0. arrayBuffers
is now the storage of the ArrayBuffers, typed arrays, Buffers, DataViews
and SharedArrayBuffers the program holds, and external the same storage
without SharedArrayBuffers, as Node splits them. A view over existing
storage adds nothing. v8.getHeapStatistics().external_memory reads the
same figure as external.
Each ScrBytes owner records the bytes it counts in the struct's padding,
so ScrBytes stays 64 bytes and the offsets compiled code reads hold; a
static assertion pins them. Owners are counted in a per-instance counter,
because their reference counts are not atomic and they are released on
their instance's thread. Shared storage moves its owner's count to a
process-wide atomic counter, since any thread may release it.
- The wrapper check before JSON, util.inspect, structuredClone and deep
equality searched the value's graph recursively with only a depth
limit, so an object with two references to itself took exponentially
many calls, and a wrapper more than 64 levels deep was not found. The
search now keeps a set of the objects it reached and an explicit stack:
each object and array is searched once, at any depth.
- String.prototype, Number.prototype and Boolean.prototype are wrappers of
"", 0 and false, as in Node, so their valueOf and toString answer the
primitive instead of throwing the brand TypeError. String.prototype has
its own `length` of 0.
- A string-named method call on a dynamic receiver in a program with
wrappers read the member twice: once to check for the builtin
String.prototype method and once for the call. The call now reads it
once, and wrapper.isStringMethod checks that value.
- createsWrappers is listed with the lowerer's first-use caches that the
unit journal leaves out.
The wrapper corpus program covers the prototypes' values, a getter read
once per call and console.log of a 40-level shared graph. The refusal
test covers a wrapper 100 levels deep, a wrapper under a shared graph and
util.inspect of a cyclic value.
A call whose name some class declares only as a getter reads the member first on instances of those classes, and keeps the runtime's method dispatch for every other receiver, so n.toFixed(2) still formats a number when a class declares get toFixed(). This replaces the list of builtin method names.
The unit journal requires every scalar of the lowerer to be journaled or
left out by name; `genericIdentityCheckDone` is set only by the
end-of-lowering audit, so a unit must leave it as it was.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`(info.valueInstances ??= new Set()).add(...)` calls a Set method on the
assignment's result, which the compiler fences as a chained Set method call
(SC2020) when it compiles its own source: the zero-cost gate builds the
candidate compiler with the candidate, and it failed at
generic-functions.ts:1358. Record through a helper that spells the same
steps as statements.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Four new fixtures (two corpus programs, two diagnostics programs) get
their preflight/module-order answers recorded; no existing baseline
changes, so the frontend order behavior is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An unpinned generic declaration compared with `===`/`!==` takes its
signature from the other operand (`held === ident`), which clears the pi
`agent-session.ts:590` shape.
That pin alone would answer `false` where JavaScript answers `true` when
the program also holds the declaration at another signature, so a
whole-program check refuses the comparison instead. It covers the direct
form and values compared through `unknown` (the g5 silent divergence:
two instances of one declaration compared unequal), resolving dynamic
operands' provenance only when some declaration is held at more than one
signature.
- generic-functions: `identityComparisonPin` and
`checkGenericIdentityPins` (runs once at the end of lowering, when every
value instance is known); `GenericFnInfo.valueInstances` records the
instances value references produced.
- Corpus: `generic-identity-pin.ts`, `generic-identity-unknown.ts`.
- Diagnostics: `generic-identity-multiple-pins.ts`,
`generic-identity-unknown.ts`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A partial record assertion's view has no slot for any field the literal
omits. An omitted optional field now reads as undefined and answers false
to in, as in Node, instead of reaching a slot the view does not have. The
in test's join takes its predecessor from the block the field load ends
in. A write of an omitted field evaluates the receiver and the value
before the presence check, so the right side's side effects run before
the TypeError. The lowerer unit tests' program lists its source files,
which the partial-assertion scan reads.
A call of a function-valued private static field through a receiver other
than the class's own name (this.#fn(a) in a static accessor) checks the
receiver and calls the stored function with this bound to the receiver,
instead of reading a public "#fn" property. this.#x++, --this.#x and
this.#x op= v check the receiver where the old value is read and update
the field's native storage, instead of a public "#x" property.
Each build stages the runtime artifacts it links from the runtime pack and
hashes each one with SHA-256 unless the build cache records that it was
verified with the file identity it still has. A build with an empty cache
therefore hashed every artifact again: on Linux x64, 40 objects and 4.3 MB
for a Benchmarks Game program, about 25 ms of a 300-1100 ms build.
Staging now also reads and writes runtime-pack.verified.json beside the
pack. It lists each verified artifact with its digest and file identity
(device, inode, size, mtime and ctime), as the cache's record does:
- An artifact is trusted only when one of the two records lists it with
the manifest's digest and size and the identity its file has during the
read. Any change to the file means hashing it again.
- A missing, unreadable or malformed record, or a malformed entry, is
treated as absent.
- The record is written to a new file and renamed over the old one, so a
reader sees a whole record. A write merges the record as it is then.
- When the pack directory cannot be written, the cache's record is the only
one, as before.
The runtime pack packages do not publish the file. The native CLI build
removes the record its probe builds leave in the bundled pack, and
verify-native-cli.mjs rejects a distribution that has one.
A signature adapter forwards the ambient receiver to its original, and
its closure carries the original's RECEIVER_FREE bit. Calls through the
closure's own pointer tested that bit, but the direct entry of a known
adapter target bracketed every call, because the adapter itself counts
as a receiver reader. The direct entry now tests the bit too.
The receiver readers analysis also admits more node kinds that run no
user code: optional chains, record presence and absent fields, dynamic
boxing, array for-of loops, typed array writes, typed array methods and
constructors over typed operands, the module binding check and region
intrinsics, performance.now, Atomics operations, string and number
library calls over primitives, and new Error(message, options) without
an options object.
The previous change avoided offsetting a null pointer with a branch in
scr_arr_alloc_start. The function is inlined into scr_arr_release and
scr_arr_destroy, so the branch changed their control flow, and the
functions that changed since the committed runtime profile was collected
then held 5.2% of its counts, over runtime-profile.test.ts's 5% limit.
The address is now computed as an integer, which is defined for a null
block and leaves the callers' control flow as it was.
A literal import() whose specifier resolves to no module lowered to a
deferred SC2012 refusal. It now rejects when the call runs, as Node does:
a package nothing installed provides rejects with "Cannot find package",
and a relative or absolute path to a file that does not exist rejects with
"Cannot find module" and the file's URL as the error's url property.
Builtins and URL specifiers keep their current lowering.
error.nodeThrow takes an optional fourth argument for the url, which the
runtime sets through scr_throw_node_coded_url.
The TypeError check for a null or undefined source now reads the unit arm the value holds as a dyn null or undefined, instead of converting the whole union to dyn. A union whose data arm has no dyn form, such as a record holding a generator, compiles again instead of failing with SC9001.
Roots under one 4 KiB unit, mostly runtime helpers, no longer count in the
partition loads that balancing compares. They are placed after the larger
roots, each in the first open partition from its name's partition whose
load from the larger roots is within the cap, so where a helper goes
depends only on the larger roots. A helper that an edit adds or removes
used to change the total and the cap and move several other roots, and
every partition they left or joined was compiled again.
A root's size also leaves out the digits of metadata references, which an
edit elsewhere in the module can renumber, so an unchanged function no
longer crosses a unit boundary because its references were renumbered.
A default export without a name of its own (`export default function
(...)`, `export default class`, an `export default` expression) declared no
name, so a change to it left the units of its default imports applied. Each
default export now declares `default` beside its own names, and a change to
it reaches the names that import it.
A flatMap callback's tuple arm appended its elements in the stored field
order, which sorts the names as strings, so a tuple of eleven or more
elements came out as 0, 1, 10, 2, …. The elements now append in index
order.
A generic receiver's member that the instantiation does not store reads
through the checked keyed read, but only for an access that is not a
write. The write test took any binary expression's left operand and any
unary operand for a write, so `options.key ?? fallback`,
`options.key === undefined` and `!options.key` still failed with SC1090.
Only an assignment's target, a delete, and `++`/`--` count as writes now.
The capture into an index-signature record consults a record's kept source
view only when some checked conversion in the program can keep one. That
scan accepted a closed record target and looked through union arms,
promises and arrays, but stopped at an index-signature record or a record
whose fields hold closed records. A conversion into
`Record<string, { x: string }>` keeps each value's undeclared keys, so a
program whose only such conversion had that shape dropped them in the
capture. The scan now looks through record fields and index-signature
values too, memoizing records by shape as it does unions.
scr_arr_alloc_start subtracted the prefix from the dense block even when
the array had no storage, which computes NULL - 0. That is undefined in C,
and the macOS lane's UndefinedBehaviorSanitizer reports it
("applying zero offset to null pointer" at scr_array.c:60) in
scr_json.test.ts's weak-map program. Without a prefix, the function now
returns the block as it is.
libuv's uv_fs_scandir sorts the raw directory-entry bytes with strcmp and
decodes the names only after sorting. Sorting the decoded text instead
reorders names that are not valid UTF-8: the malformed bytes 0x80 and 0xfe
decode to U+FFFD, which sorts after "é", and distinct names that decode to
the same text fall into an arbitrary order. readdir and readdirSync listed
those names in an order Node never produces.
scr_fs_scandir now collects the raw names, sorts them with strcmp, and then
decodes each one into the ScrScandir.
The malformed UTF-8 harness test also prints the unsorted readdir order and
carries names whose byte order and decoded order differ.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>