An executable with at least 4 MiB of program code is linked with 2 MiB
segment alignment and published with one write, so the page cache holds
its text in 2 MiB folios and the runtime advises huge pages for it. A run
then maps every 2 MiB text window it touches whole. Most runs of the
compiler are short: `scriptc --version` peaked at 76 MB and an unchanged
rebuild at 78 MB, against 20 MB and 26 MB with 4 KiB alignment, and cold
and edited builds of tsc-ts took the same time either way.
compile() takes an internal `hugeText: false`, which links with 4 KiB
alignment whatever the code size and is part of the executable cache key.
scripts/build-native-cli.mts passes it. Executables the compiler builds
keep huge text.
The string spare, the main thread's idle trim and the shared-bytes writes
changed runtime functions that hold enough of the committed profile's counts
to fail its staleness check (7.3% against a 5% limit). The profile is
collected again on this change with scripts/refresh-runtime-profile.mjs
(327 raw profiles), with the LLVM helper built from this tree's sources.
- A typed write or transform callback whose encoding parameter is a
pointer union with an undefined arm receives the null pointer directly
when an object-mode write passed no encoding, instead of converting the
encoding value through a dyn box
(scr_stream_chunk_encoding_undefined).
- The stream completion callbacks carry SCR_CLOSURE_KIND_RECEIVER_FREE:
their thunks never read `this`, so compiled callers skip the receiver
bracket.
- The completion thunks borrow the stream from the closure's box instead
of retaining and releasing it on every call; the closure, alive for the
call, holds the box.
- settle_next, entry_convert and clear_buffer_each test their early-out
conditions in inline wrappers before calling the full functions.
A corpus program covers the encoding argument in object mode, byte mode
and transforms.
The jammed loop of jamSaturatingLoops now tests whether every accumulator
is full only when `j & 3` is 3, and keeps the inner loop's own bound. Past
the iteration where every accumulator is full, the copies' ORs leave the
accumulators full and every other value they compute is dead, so running
up to three more iterations changes no result.
The copies' statements now alternate in the jammed loop, and each local's
copies are added next to each other. The vectorizer pairs neighboring
values of the loop's header, which were a copy's real and imaginary parts
in mandelbrot; it now packs the same value of two copies into one vector,
without lane shuffles.
scr_fs_write_once cached whether the calling thread blocks SIGPIPE and
SIGXFSZ in an SCR_TL variable. SCR_TL is empty in builds without thread
instances, so a pool thread's answer applied to the main thread too, and
a later writeSync to a closed pipe ended the process with SIGPIPE
instead of throwing EPIPE. The cache is _Thread_local now. The
fs-write-sync fixture covers a write stream followed by writeSync on a
closed stdout.
A Console instance over process stdio passed a patched write the line and
an error handler, and discarded what the write threw, whatever its
ignoreErrors setting. Node's kWriteToConsole does that only with
ignoreErrors (the default, and the global console). With ignoreErrors
false, from the options or the third constructor argument, it calls
stream.write(line) alone and lets a throw propagate out of the console
method.
The console write path now takes the instance's ignoreErrors, so such an
instance passes only the line and keeps the write's exception pending.
Instances bound to other streams already followed the setting.
The native compiler sends most checker requests one at a time and blocks
in recv for each reply. The server answers most of them in a few
microseconds, and a reader the reply wakes takes tens of microseconds more
per round trip, over about 130,000 round trips on a large program.
scriptc_ts7_read now polls the socket with MSG_DONTWAIT for up to 200 us
before it blocks. Windows keeps its blocking pipe read.
- A worker writes the completion wake byte only when the pipe has none.
The loop reads it, with the queue lock held, when it takes the last
waiting completion. The pipe holds a byte exactly while completions
wait, so a burst of completions costs one write and one read instead of
one write per completion and a read loop per dispatch.
- The fs pool's threads keep SIGPIPE and SIGXFSZ blocked for their
lifetime. scr_fs_write_once checks once per thread whether both are
blocked; on such a thread a write makes no signal-mask calls, and a
write that fails with EPIPE or EFBIG consumes the signal it raised.
Other threads keep the per-write guard.
fs-write-sync.test.ts covers EPIPE and EFBIG from write streams, whose
writes run on the pool's threads, and matches Node's errors.
Three runtime changes that lower peak memory without changing behavior:
- In programs with workers, process.stdout.write and process.stderr.write
of a view into a SharedArrayBuffer read it through a 64 KiB buffer
instead of copying the whole view first, so writing a 32 MB shared view
no longer holds a second 32 MB. Programs without workers keep the direct
write.
- The one-slot string spare keeps freed blocks of 512 bytes to 1 MiB only.
A larger block stayed resident until a string of about its size came
along, which a program that replaces its input with an edited copy never
does.
- The main thread trims its arena when its event loop waits with nothing
ready, once it has carved 16 MB more since its last trim, as a parking
thread does: pages that hold only free blocks go back to the system while
the program waits on its workers.
A union retype between two shape unions (or two class unions) shares the
pointer when both encode their unit arms the same way. Narrowing a field
read past `=== undefined` or `=== null` retypes a union with a unit arm
to one without it, so the encodings differ and each listed arm was
rebuilt: a retain, a tag switch, a second retain and a release, for the
same pointer. The unit encodings only matter when the value may hold a
unit arm. When the retype lists none of the source's unit tags, every
value is a record or class pointer whose arm the target derives from the
record or class itself, so the pointer is the target value.
The rebuild's release also made the pointer a nursery cycle candidate,
and in the native compiler those candidates kept the nursery bypass's
probes of closures and boxes succeeding. Without them, a probe window can
open while young cycles of the type are held by older garbage that the
bypass promoted earlier. The window then frees nothing, the type stays
bypassed, and the garbage waits for a mature pass, which on a cold tsc-ts
build raised the compiler's peak RSS by about 100 MB. A nursery probe
window that opens now makes the next release-triggered pass that would
run at the nursery run at the mature level, which reclaims that garbage
before the window is judged.
- An async object-literal method whose contextual slot returns a non-promise
(`() => void`) keeps its own promise return, and one whose slot returns a
union takes only the union's promise arm (`Promise<T> | void`): adopting
the whole return type gave the method a non-promise type, refused with
SC2001.
- A type predicate whose record target is itself an arm of the element
union keeps that arm's elements as they are and converts the other arms
with its fields, instead of refusing the filter with SC1090.
The jam of a short constant-count loop now leaves the loop as it is when:
- the inner loop's body assigns the accumulator other than by
`acc = acc | C`. An iteration that runs past its exit could clear bits
after the accumulator was full.
- the inner loop's body assigns the inner counter. The copies share the
counter, so one copy's write changed the iterations of the others.
- the inner loop's start or bound reads the outer counter or a local of the
outer iteration. The copies share the inner loop's start and bound, which
read neither the counter's value nor the copies' renamed locals.
The unit test rejects each shape, and the corpus program adds a function
for each that matches Node's output.
The emitter named each TBAA type node `field.<n>`, numbered in the order
the module's functions first use the categories, so an edit that adds a
field or memory category renumbered every later one. Every function that
loads or stores those fields then changed text, and its program partition
was compiled again.
Each type node is now named by its category (`field:<name>`,
`array:header`, `global:<id>`). The set of nodes and their structure are
unchanged, so the generated code is the same.
refresh-cli-layout.mjs kept the raw profile of every cold training build
until the end of the run, about 23 MB per build and 8.7 GB for the 399
builds. When the disk filled, the profile runtime wrote partial files, the
compiler still exited with 0, and reading a partial file threw a RangeError
past the end of its counters.
Each cold profile is now read and deleted when its build finishes, so the
work directory stays under 200 MB. The script throws when a training run
reports `LLVM Profile Error`, naming the build and the write error, and
when a profile is shorter than its header describes.
Adds the corpus program receiver-before-effectful-key.ts, with its corpus
cost and order baseline:
- a tuple read with a runtime index that rebinds the tuple or replaces the
field holding it;
- a keyed read through a finite union of field names whose key rebinds the
record or replaces the field holding it;
- an inherited-key `in` test on a computed null-prototype receiver, which
runs the receiver once.
Each case reads the receiver as evaluated before the key, as in Node.
In TypeScript, tee(), pipeTo() and pipeThrough() on a ReadableStream were
refused with SC2020, and the WritableStream constructor and a
TransformStream with a transformer had no lowering, while JavaScript
programs reach all of them through the web stream classes
(scr_webstreams.c).
A TypeScript program that uses one of them now builds its ReadableStream,
WritableStream and TransformStream values with those classes, as
JavaScript does, and %main installs them, so a native stream such as a
Response body answers the three methods through its web view. A program
that uses only getReader(), cancel(), locked and the identity transform
keeps the native streams and builds to the same binary.
The fallback declarations gain UnderlyingSink and Transformer, the
WritableStream constructor takes a sink and the TransformStream
constructor a transformer and queuing strategies, and their controllers
map to checked-dynamic handles. The fetch profile lists the three methods
as static with the new static-web-streams fetch fixture as their
differential evidence, and keeps them in the set that links the classes.
The surface manifest is regenerated, and the surface-manifest probe that
kept tee() and pipeTo() fenced now fences values() and the async
iterator.
Adds the static-web-streams fixture and the corpus program
web-streams-typed-pipes.ts, with its corpus cost and order baseline.
Node's console writes each formatted line with stream.write(line,
errorHandler), so a replaced process.stdout.write sees a string and a
function; a Buffer write arrives as a Buffer. The stdio write hook carried
only a `wide` bit, so every intercepted chunk became a JavaScript string and
console lines carried a single argument.
Replace that bit with a `flags` int (SCR_STDIO_HOOK_WIDE, _CONSOLE, _BYTES)
and keep the hook signature at four parameters, so the console write path and
the byte paths differ only in what they pass. scr_stdio_intercept now builds a
chunk instead of a string, boxes SCR_STDIO_HOOK_BYTES as a Buffer, and appends
a console error handler for SCR_STDIO_HOOK_CONSOLE. Text callers of the old
string entry point move to a new scr_stdio_write_text.
Corpus: stdout-patch-console-arguments.js.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
A call through an untyped receiver now reads a class instance's field, a getter, or a property added to the instance before it evaluates the arguments, as JavaScript does. An argument that replaces the member no longer changes which function runs.
`x ?? { errors: new Map() }` left the empty map typed Map<any, any>,
which spread through the checker into `new Map(result.errors)` and the
binding holding it, and every use failed with SC2020/SC1090. The
fallback literal now builds as x's record, `new Map(source)` over such
a join copies into the source's layout, an unannotated binding whose
checker type differs only by that residue takes its initializer's
layout, and Map methods on it convert into the receiver's layout. No
map is converted between layouts: each value keeps the one layout it
has.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An async method in an object literal with no return annotation and a
contextual signature inferred its own (narrower) literal type, while its
`return` literal was built at the contextually typed record: the return
statement then had to convert the wider record into the narrower literal,
which fails with SC2002. The method now takes its return type from the
contextual property's call signature, as the arrow form does. Sync
methods are unchanged; they already adopt their slot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A written type predicate whose target is no arm of the element union
but the same fields as one, with a nested union field narrowed
(Model<"a"> from the Model<Api> arm), was refused. The kept elements of
that arm now convert field by field, the nested union by its tag; any
other kept element still throws the predicate TypeError.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An empty binding pattern still performs its one observable step:
RequireObjectCoercible for `{}` and GetIterator for `[]`. Over a statically
non-nullish source that step is invisible, which is why the empty-pattern
lowering emitted nothing; a checked-dynamic source can be null or undefined
at runtime, where V8 throws.
Emit the runtime null/undefined test for dyn sources in the empty-pattern
branch, and let a JS-file empty object pattern take the source-spelling
message dynDestrCheck already produces for non-empty ones.
Test262 (static-adapted-v6, --workers 8), affected chapters before -> after:
language/statements/class/dstr pass 1518 -> 1526, fail 424 -> 416;
language/expressions/class/dstr pass 1518 -> 1526, fail 424 -> 416. No
pass -> fail.
Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
A class whose methods are reachable through `X.prototype` widens its method
signatures to checked values so a JS caller can hand anything across. The
widening replaced every non-void return with DYN, which erased a GENERATOR
method's generator type; the generator branch below then had no generator
channels to build its metadata from and refused the whole class (SC2001 in
`%init`, re-emitted as a runtime fence in a JS file). Every
`assert.sameValue(c.m, C.prototype.m)` variant of the campaign therefore
refused at runtime, 759 of them in the generator-method chapters alone.
A generator's return kind is already a checked-crossable ABI: dynFrom boxes a
generator by reference and dyn calls resume it through the dyn generator ops,
so only the arguments need the widening. Guard the return widening to
non-generator methods.
Test262, 1,110 affected variants, before/after on the candidate
(class/gen-method 52->76, class/async-gen-method 76->128,
class/scope-gen-meth 0->8); no pass->fail and no typed-path delta.
Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
Reading the name of a function stored in a const followed const aliases to
the definition, but stopped at destructuring: `const { cwd } = process`
gave the binding's name instead of Node's "wrappedCwd". A member
destructured from the process global or a builtin module's namespace now
gives the module table's name for it.
An anonymous function wrapped in parentheses (`const f = (function () {})`)
answered an empty name. As in Node, it takes the name of the binding or
property it initializes through the parentheses.
A builtin read as a property (`util.debuglog.name`) answered the name its
shared adapter was first created under, so after `util.debug` it answered
"debug". The name now comes from the property and the module table, and
util.debug's value is named "debuglog", as in Node.
fs.watchFile(path[, options], listener) polls the resolved path every
`interval` milliseconds (5007 by default, `persistent` by default) and
calls its listener with (current, previous) Stats when a poll sees a
difference. The first poll only records the baseline, a path that cannot
be stat'd polls as all-zero Stats, and a stat failure that repeats for
the same errno is not re-reported. One poller per RESOLVED path is
shared by every listener on it, and fs.unwatchFile(path[, listener])
removes the last listener registered with that callback, or every
listener when called without one. The call is a statement, since Node's
StatWatcher has no lowering, and bigint: true is refused.
The implementation is scr_watch_file.c, a new gated runtime unit
switched by moduleUsesFsWatchFile, so programs that do not poll link
none of it. The feature travels the usual chain (runtime-features,
executable-features, the native link info, the executable cache keys,
the runtime-pack matrix) and the validator learns that
fs.unwatchFile's trailing listener slot may be omitted.
Evidence:
- tests/corpus/fs-watch-file-names.ts and fs-watch-file-polling.ts match
Node on stdout, stderr and exit status in the plain and sanitized lanes
- 1564-fs-watch.ts, 1751-options-record-stance.ts and the `fs` corpus stay
green; the new order baselines and corpus cost files are recorded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An fs.watch listener may now take (), (eventType), or
(eventType, filename). On a directory watch the filename is the entry
that changed; a file watch names the watched file's basename. The
parameter must admit null (Node passes null when the platform names
nothing). macOS directory watches that ask for the name are refused with
SC2020 until the FSEvents slice lands.
FSWatcher gains its EventEmitter surface for "change", "error" and
"close" (on, once, addListener, prependListener, prependOnceListener,
off, removeListener). The name and error adapters are emitted per
program because each builds the listener's own parameter shapes, and the
erased `string | null` nullable union takes the payload directly instead
of a union box. The runtime keeps one listener list per event with the
once flag and prepend ordering; a once listener's registration reference
leaves when it fires.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A generic arrow declared inside a function compiles to shared module
instances that took the enclosing values they read as trailing
parameters: a copy, so any binding written in the file was refused,
including a counter the arrow itself increments. When one of its
captures is reassigned, the instance is now a lifted function that
receives every capture through a closure environment, and each call site
calls a closure over its own boxes, the representation ordinary closures
use. Writes from the instance, the frame and other closures meet in the
same box, as in Node. Arrows whose captures never change keep the
trailing parameters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A function value's declared type may name fewer parameters than the
function it holds, and a later widening call then passes arguments the
declared type never spelled. JavaScript's function object carries all of
its parameters, so nothing may be dropped on that path — that would be a
silent divergence from Node.
The narrowed store is already a named refusal (a compiled function value is
one concrete native ABI: "'(string, number) => number' values where
'(string) => number' is expected"), reported at the narrowing site for a
binding and for an object-literal member slot. Record both in the
diagnostics snapshot so the refusal cannot regress into a silent drop.
Pin the shapes that DO keep every argument: a class member whose overloads
spell fewer parameters than its implementation, the member read as a value
(the read keeps the implementation's JS arity), and a value that entered
through an untyped slot and is widened back.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 94d44e947c48dc0184c941d526380ebd1b1b443d)
The `f?.()` branch of the optional-chain lowering forwarded a spread
argument list through completeFuncValueArgs but mapped a plain argument
list itself, so an omitted optional parameter was never padded with the
callee's undefined arm. The lib-boundary arity fence then refused the call
("2 arguments where its lowered signature takes 3"), which is what
`callbacks?.onRetryFinished?.(false, attempt)` hit: an unguarded call
completes its arguments, a guarded one did not.
Run both spellings through the same completion. It evaluates inside the
guard, so a nullish callee still evaluates no argument, and a typed rest
now packs from the guarded call too.
Corpus: optional-chained callbacks with a trailing optional parameter and
with a typed rest, through a present, absent, and undefined receiver.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f574073e2392ec24cd2cfca61651759d3359e603)
A generic arrow or function-expression binding declared inside a function
used to be refused (SC1090) whenever its body read anything from the
enclosing frame, because its monomorphized instances are module functions
with no closure.
Give each instance the frame it needs: every value the binding reads from
the enclosing function becomes a TRAILING parameter of the instance, and
every call site (which sits in the declaring frame or in a closure over it)
passes the current value after the declared arguments. The instance body
binds the same symbol, so it resolves the reference like any parameter.
Sound only while the original binding never changes, so the fence now
covers exactly what a copy cannot stand in for: `this`, `super`,
`new.target`, `arguments`, an enclosing type parameter, an enclosing
generic scope (per-instantiation types), and any binding with a write
anywhere in the file or declared after the binding (temporal dead zone).
Corpus: withClientIdentity/withIdentity, physicalModels (captured Set),
listTwo (captured local function), runTracked (async, call from a nested
closure) and twice (two instances, different capture values). The
generic-value-bindings diagnostics fixture now pins the fences that remain
(a capture the file reassigns, and an enclosing generic scope) instead of
the never-reassigned parameter it used to.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 5b23e6ccf110c66e96fad472265d7b53db0f342a)