The compiler compiles itself, and a Map keyed by another Map is outside
the key domains it compiles (SC2009 in the self-hosted frontend). The
Lowerer's table is now keyed by the declared alias reference: each entry
lists the instances that instantiate it with their ts-level bindings maps,
and the resolver picks the current instance's by identity.
The paired nest reads its bounds masked to [0, 2^25 - 1], which turns a
-0 start into +0, and a term can tell the two apart (1 / i on the first
row). The bounds test now requires 1 / b > 0, which -0 fails, so such a
nest runs as written. Corpus 8355 adds row and column starts of -0 with
terms that see the sign.
The LLVM emitter's module passes walk every function, and on large programs
most of them find nothing to change in most functions. Each pass now
avoids work whose result it does not use, with the same output:
- scalarizeLiteralResults built, for every function, a set of all locals
and all module globals before looking for a producer call. It now
collects the names a fresh binding must avoid when it makes the first
one.
- specializeCallbackCalls walked every body five times: rebindings, node
counts and definitions, call keys, untouched bodies, and callers. One
walk now records each body's node count, rebindings, written values and
helper calls in walk order; the later steps read those records and walk
only the bodies the rewrite changed and the copies it made.
- holdCharLocalsAsUnits skips its two object walks in functions without a
candidate local, and its mention count where no candidate is declared
from a charAt or an optional read (the only ones it can hold).
- resolveSelfCaptures only asks CaptureFlow whether bindings are
unchanged, so its flow collects the facts of the bodies those questions
reach instead of scanning every body.
- copyStableCaptures reads the function references of the bodies it kept
from its CaptureFlow facts instead of walking them again, and makes the
set of local ids a copy must avoid only when it makes a copy.
- versionLoopBoundParameters counts call sites only when a function has
loop-bound parameters.
- pairRowSums and inlineExpressionFunctions look for a row sum or an
inlinable call before rebuilding a body.
- The project configuration records the local files a type patch names,
with whether each existed. A configuration read again from an unchanged
package.json is reused only while every answer still holds, so creating
or deleting a local declaration file between builds in one process
validates again.
- A types patch supplies the declaration TypeScript probes for a .jsx
JavaScript entry (x.d.ts for x.jsx), as it does for .js, .mjs and .cjs.
- Remove the two tests/fixtures/effect4/node_modules links the previous
commit added; that directory is ignored and pnpm creates them.
A paired row-sum nest now also takes two columns per step: each row adds
the first column's term and then the second's, and an odd column count
runs its last column on its own, so every sum keeps its order of
additions. The nest runs paired when all four bounds are integers in
[0, 2^25 - 1]; it reads them masked to that range, which proves both
counters' ranges, so its counted loops need no run-time guard or second
copy of the body.
Corpus 8355 covers the column counts and bounds, and corpus 8356 covers
worker loops over shared views that run past a view's end or write
overlapping views of one buffer.
A scriptc.typePatches entry can now replace a declaration file of the
package (or of its @types twin, written @types/<name>/<path>) with a
local .d.ts ("files"), or give the package a declaration entry ("types").
A supplied entry answers the package's types file and the declarations
TypeScript probes beside its JavaScript entries, and exists for the
program host, the resolver and npm-static's untyped-package test, so a
package without declarations becomes a typed package. Declaration patches
still apply on top. A files target the installed copy does not ship is
SC8031.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Records inherit toString, toLocaleString, valueOf, hasOwnProperty,
isPrototypeOf and propertyIsEnumerable. Calls of these methods by name
run an own callable entry when the record's type allows one and the
record holds it, and otherwise the built-in with the record as receiver;
toLocaleString, valueOf and isPrototypeOf on records were refused, and
toString ignored an own entry. Keyed reads of records of functions answer
the built-in as a native function value when the key misses: bound to the
record when called at once, with an undefined `this` otherwise. The
backend fuses the read with its fallback, so hits cost nothing extra.
Records of other value types keep reading undefined for these names; the
limitations page says so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With a fresh key and IV each run, Node's OpenSSL now and then accepts
the padding of a wrong-passphrase decryption and rejects the decrypted
structure instead: ERR_OSSL_UNSUPPORTED rather than ERR_OSSL_BAD_DECRYPT,
in about 1 run in 100. The program now prints both as one decryption
error.
The compiler is compiled by scriptc and keeps caches on their owners
instead of weak collections (compiler-weak-collections.test.ts).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An initializer-less JavaScript `let` whose writes all agree gets a scalar
slot when every read already has that flow type (inferredEvolvingLetType).
The read scan resolved each identifier with getSymbolAtLocation, which
answers the property symbol for a shorthand property name, so the
`width` in `return { width }` was not seen as a read. A function that
assigns the let on one path only (`let width; if (tty) width = 80;
return { width };`) then stored it in a number slot: the path that never
assigns returned 80 or 0 instead of undefined. The capture scan missed a
closure that reads the let through a shorthand the same way.
Both scans now resolve a shorthand name to the binding it reads, so such
a let keeps its checked-dynamic slot and reads undefined until assigned,
as in Node. TypeScript lets are unaffected (the inference applies to
JavaScript only).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A global replace or replaceAll of a regex literal called the compiled
matcher once per match: each call set up its frame and seek again, and the
runtime then copied the gap and the replacement. A substitution of a
literal inside the compiled subset with an ASCII replacement without $
patterns now gets a replacer built for that pattern and replacement. It
appends each gap and the replacement to the output and continues the
search after the match, as the runtime's loop does (one past an empty
match), without returning. Gaps of up to 16 bytes are one 16-byte copy,
and the search after a match is a 16-byte scan instead of a memchr call.
scr_regex_replace_with runs the replacer on an ASCII subject; a subject
outside ASCII keeps the runtime's loop. Plain-text patterns, which the
runtime searches as strings, get no replacer.
New corpus program: 8351-regex-literal-replacers.ts.
The one-character strings of U+0080..U+07FF that string iteration shares
between threads were allocated from the calling thread's memory. On Linux a
worker that exits without sharing anything returns that memory, so the table
kept strings that a later read found discarded. They now come from the system
allocator, as other state that outlives a thread does.
Untyped code that loops over a native array (`for (const ch of checks)` in
zod, over arrays the compiled code built) paid for the loop before its
first element:
- Every check of whether a typed capsule is a generator compared its type
name against "async-generator<" with strncmp, and native array type
names also start with 'a'. Iterator creation made several such checks.
The test now looks at the second byte before comparing.
- scr_dyn_iterator called the builtin values() through the general call
path for a native array; it now creates the native iterator directly,
as it already did for untyped arrays.
- Read sites of `length` and `next` call scr_js_get_probe_native, which
answers a native array's `length` and a native iterator's `next`, where
the generic read built the array's checked view or looked the method up
by name. Other read sites keep scr_js_get_probe, so their receivers that
are not ordinary objects still miss without a further test.
A record or union that refers back to itself only closes its recursive
knot when the frame did not resolve anything through context-sensitive
hooks; otherwise the same checker type could answer differently per
instantiation, so the knot is refused. Erasing a generic method member
(`send<T>(message: Message<T>): string`) resolves the method's own type
parameters to `unknown`, and that resolution counted as context-sensitive.
Any recursive interface declaring a generic method was therefore refused:
`interface Sub extends Base { send<T>(m: Message<T>): string }` reached
from `Base { child?: Sub }`, or a list node with a generic method beside
its `next` field.
The member erasure now records its own type parameters, and resolving one
of them no longer counts. Consulting an enclosing binding still does, so a
generic method that mentions an outer type parameter keeps the fence. The
erased member's mapping is not memoized under the member's type, since the
same type maps to nothing outside the erasure.
pi's ReplacedSessionContext (sendMessage<T>) extends ExtensionCommandContext,
whose methods take callbacks over ReplacedSessionContext. The fence refused
ExtensionAPI.registerCommand and, through it, pi's main().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cases that decrypt with a wrong passphrase used keys encrypted
afresh on each run. A wrong key decrypts to random bytes, and about one
run in two hundred those bytes pass the padding check, so OpenSSL fails
at the ASN.1 step with a longer error than "bad decrypt" and the
program's output changes. The section that reads encrypted keys now
uses fixed encryptions of one P-256 key, so each case fails the same
way on every run.
The inline dyn release sorted a surviving value into three groups by
kind: arrays and objects tested the candidate-buffer word and then called
scr_dyn_release_survivor, capsules and handles called it directly, and
functions and proxies appended to the candidate buffer inline. All six
kinds now take one path: a value already in a candidate buffer, or never
one, returns inline, and scr_dyn_release_survivor, which now also covers
functions and proxies, decides the rest. Behavior is unchanged, and each
inlined release site keeps one kind test and one call.
Effect's executable text is 10% smaller and it runs 5.8% faster; zod
runs 3.8% faster, and bench:runtime's dynamic-properties 14% faster.
A thread waiting for its mailbox slept on a condition variable. The
kernel then places the woken thread without a hint, so with every CPU
busy it can queue behind a running thread until that thread is preempted
or blocks: a worker program's phase barrier (post to each worker, wait
for every reply) lost about 1 ms per phase, and up to 5 ms, on 4 CPUs.
On Linux the wait now sleeps in ppoll(2) on the mailbox's wake pipe,
which every post already writes. A pipe write is a synchronous wakeup:
the woken thread can run on the poster's CPU, which the poster frees
when it waits in turn. Bytes left by signals whose events were already
taken are drained under the lock first. Other platforms keep the
condition variable.
for-of, spread, Array.from, yield* and [Symbol.iterator]() over a string
walked a UTF-16 index. Each step mapped the index to a byte offset, built
the character, and measured its UTF-16 length to advance.
They now walk a byte cursor over the WTF-8 data:
- The cpAtByte intrinsic reads the character that starts at the cursor.
ASCII bytes take the one-character table inline; other characters call
scr_str_cp_at_byte. byteLength advances the cursor by the character's
byte count. A lone surrogate is its own three-byte character, so it
stays a one-unit character, as in Node.
- Spread and Array.from of a string call scr_str_code_points, which
counts the characters, allocates the array once and fills it.
- The one-character strings of U+0080..U+07FF are made immortal on first
use and shared, as the ASCII ones are static.
The cpAt intrinsic, which no lowering emits any more, is removed.
String storage can hold bytes that are not well-formed UTF-8: an HTTP
header value keeps the raw bytes a client sent. Encoding such a string
read past its end.
- scr_bytes_next_cp decoded any high byte as the lead of a sequence and
read up to three bytes after it without a bound. It now takes the
storage length, decodes only complete, well-formed sequences, and
returns any other byte as U+0000..U+00FF, advancing by one. That is
each such byte's value in Node, which decodes header values as latin1.
This covers latin1, ascii, binary and utf16le encoding, hex and base64
of non-ASCII text (which narrow through latin1), Buffer#write and
TextEncoder#encodeInto.
- base64 decoding read one byte past its input when the eight-character
groups ended exactly at the end.
The runtime's bytes unit test (built with ASan) encodes ill-formed
storage at every offset within a word and random high bytes, and
compares latin1, ascii and utf16le with a scalar reference. A corpus
program sends such header bytes to a server and encodes them as Node
does.