Commit Graph
6750 Commits
Author SHA1 Message Date
Chris Tate f791c1d006 Address review feedback 2026-10-25 14:47:24 -05:00
Chris Tate 6b51aa883f Keep -0 row and column starts out of the paired row-sum nest
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.
2026-10-25 14:46:01 -05:00
Chris Tate 85df48f28f Report either decryption error of a wrong passphrase in corpus 8183 (#2243) 2026-10-25 14:44:25 -05:00
Chris Tate 7222c3e900 Skip module-pass work that changes nothing in the functions it walks
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.
2026-10-25 14:44:05 -05:00
Malte UblandClaude Opus 5.5 6d34f4f0a5 Leave withUnitArm unchanged for pending placeholders
The lowerer's optional-union cache relies on withUnitArm answering for a
placeholder before it completes and recomputing after the revision moves
(lowerer.test.ts). Only the optional-parameter site needs to keep the
placeholder, so the refusal goes back out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:41:21 -07:00
Chris Tate 68942a8a1c Convert Buffers to and from latin1 and hex text a word at a time (#2183) 2026-10-25 14:38:27 -05:00
Chris Tate 7d458303a6 Check type patch files again on a cached configuration, and supply .jsx entries
- 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.
2026-10-25 14:32:00 -05:00
Chris Tate 94d3b691ea Fix wrong npm declarations locally with type patches (#2238) 2026-10-25 14:31:10 -05:00
Malte UblandClaude Opus 5.5 3c95271fc1 Record the TypeScript 7 order baselines of the stack's npm cases
static-objects, static-tables, concat-dynamic-args, evolving-let-index and
the three shape-* cases landed in this stack without preflight/order
baselines, so the TypeScript 7 parity job failed on every PR above them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:30:00 -07:00
Chris Tate be0e13aa1e Run paired row sums two columns per step
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.
2026-10-25 14:28:23 -05:00
Chris Tate d477cd45cd Run global substitutions with a literal replacement through build-time replacers (#2240) 2026-10-25 14:19:37 -05:00
Chris Tate ed125cc384 Wait for mailbox events in ppoll on Linux (#2234) 2026-10-25 14:16:18 -05:00
Chris Tate a5663bdd73 Iterate strings over their bytes (#2233) 2026-10-25 14:14:06 -05:00
Malte UblandClaude Opus 5.5 59d3dfdbfc Keep the compiler self-hosting: a boolean predicate in the concat helper
The self-hosted compiler refused the truthiness test on an
IrType | null | "dyn" value (SC2003, no layout for the union result).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:13:11 -07:00
Malte UblandClaude Opus 5.5 c3a72b2d3f Cut the shape overlay's compile cost: memo across builds, comparison first, one analysis for fold-only rewrites
- memo.ts carries a build's overlay verdicts (refused annotations and files,
  overlay off, the default build's deferred refusals) to the next build of the
  same JavaScript, in the build cache.
- Builds whose only refusals are deferred lower the program without the
  overlay first, and drop only what the added refusals blame; when the overlay
  goes off, that lowering is the build's.
- Method families join only methods a call reaches together (receiver unions,
  extends chains): ZodString's _addCheck returns ZodString, so zod 3 needs no
  reload.
- Refusals in a class's fields or at new C blame annotations that name C.
- Rewrites that only fold constant reads compose with the typed overlay
  planned on the same analysis (one rebuild and analysis instead of two).
- Shape analysis prefetches symbols only; input tracking notes a file's text
  once per tracker.
- A declaration a project type patch edited gets no overlay annotation
  (setShapeOverlayProtectedSpans, for the type-patches layer).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:12:45 -07:00
Chris Tate f82f71e6c5 Iterate and read native arrays from untyped code without type-name scans (#2239) 2026-10-25 14:10:07 -05:00
Malte UblandClaude Opus 5.5 f4825e05a7 Replace a package's declaration files or supply its declarations with type patches
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>
2026-10-25 12:05:52 -07:00
Malte UblandClaude Opus 5.5 a2be7d8bc0 Answer Object.prototype's methods on typed records
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>
2026-10-25 12:05:14 -07:00
Chris Tate 6000485a17 Report either decryption error of a wrong passphrase in corpus 8183
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.
2026-10-25 14:04:10 -05:00
Chris Tate cab0968510 Keep holes and values written through unknown[] views in typed arrays (#1866) 2026-10-25 14:01:03 -05:00
Chris Tate 90605110a5 Adapt the nursery trigger to where cyclic garbage dies (#2231) 2026-10-25 14:00:41 -05:00
Malte UblandClaude Opus 5.5 317d724a7b Keep bound alias instantiations on the lowering pass
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>
2026-10-25 11:59:30 -07:00
Chris Tate 96f69414be Send every dyn kind that can be a cycle candidate through one release path (#2236) 2026-10-25 13:56:15 -05:00
Chris Tate 701173a2ae Read builtin globals through the global object, and support Node's navigator 2026-10-25 13:55:44 -05:00
Chris Tate a40e8f9b38 Read fixed encrypted keys in corpus 8167's wrong-passphrase cases (#2237) 2026-10-25 13:55:13 -05:00
Malte UblandClaude Opus 5.5 a4bc01bafb Count shorthand property reads of an initializer-less JavaScript let
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>
2026-10-25 11:52:19 -07:00
Chris Tate e2bf699e81 Merge main 2026-10-25 13:51:46 -05:00
Malte UblandClaude Opus 5.5 177b2fde49 Cross package values at their declared type where the JS literal type lacks members
An opted-in package's JavaScript types its own values. The lowering types
a consumer's view of such a value from that JavaScript, and the
declaration boundary (decl-projection.ts) casts to the declared type only
where the JavaScript world answers `any`. A function that returns an
object literal has a precise JavaScript type, so it never crossed, even
where that literal type lacks members the declarations promise:

- `{ [key: string]: string }` over `{ size: "12px" }`: the consumer read
  `settings["font"] ?? "x"` through the literal's fixed key set. The
  checker types the miss `any` (a JavaScript literal), but the keyed read
  took the declared fields' common type, so the miss trapped with
  "record has no key" and aborted the process.
- an expando `out.max = ...` on a non-empty literal, which the JavaScript
  checker leaves out of the literal's type: reading `stats(xs).max` was
  refused with SC1090.

A package value whose JavaScript type is an object literal that lacks a
declared property or string index signature now crosses at its declared
type, so missing keys read undefined through the index signature and
expando members are read from the object. An index signature whose values
do not project (a package interface) keeps the value untyped (`as any`),
so its reads are the JavaScript reads. Only programs compiled with
--npm-static whose consumers read such values are rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 11:51:24 -07:00
Chris Tate 734ced4f4f Address review feedback 2026-10-25 13:49:34 -05:00
Chris Tate c5131cb958 Name the byte order mark fixture's directory by its real path
The 8211 program resolved a package written to a fresh directory under
os.tmpdir() and printed the result relative to that directory. On macOS
the temporary directory is a symbolic link (/var to /private/var), so the
resolved real path printed relative to the linked name included the
random directory suffix, which differs between the Node run and the
compiled run. The fixture now names the directory by its real path, and
the result is lib/entry.js on every host.
2026-10-25 13:39:37 -05:00
Chris Tate dec6be6ebd Run global substitutions with a literal replacement through build-time replacers
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.
2026-10-25 13:35:04 -05:00
Chris Tate 4ee477d11e Prepare v0.4.5 release (#2226) v0.4.5 2026-10-25 13:33:57 -05:00
Chris Tate 0664361990 Decode string storage within its length in Buffer encodings (#2232) 2026-10-25 13:30:42 -05:00
Chris Tate 15a183026f Allocate the shared two-byte characters from the system allocator
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.
2026-10-25 13:27:58 -05:00
Chris Tate 1b9a4e1361 Refuse SC9001 cases and merge main 2026-10-25 13:26:27 -05:00
Chris Tate 2a0489a334 Keep the TypeError of receivers that may be null in static-object rewrites
The rewrites decide on a read's non-nullish receivers, since the analysis
also gives an absent key's read undefined (ns.util before ns.util = {...}
runs). A rewrite then removed the property access, so a receiver that was
in fact null or undefined no longer threw.

- A read whose receiver may also be nullish is rewritten to the read as
  written followed by the replacement in a comma expression: a direct
  reference becomes (recv.k, name) and a folded member (recv.k, literal),
  so a nullish receiver throws Node's TypeError and any other receiver
  yields the same value.
- A function table call through such a table reads table[key] first,
  before its arguments, where the call reads it.
- The static-tables case reads, calls and dispatches through receivers
  that are the constant object or undefined, and records the ts7
  baselines of the npm cases added in this change.
2026-10-25 13:23:54 -05:00
Chris Tate ea599922a4 Iterate and read native arrays from untyped code without type-name scans
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.
2026-10-25 13:23:22 -05:00
Malte UblandClaude Opus 5.5 bd1bf39a76 Close recursive record knots through erased generic method members
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>
2026-10-25 11:18:18 -07:00
Chris Tate fb17fc0715 Read fixed encrypted keys in corpus 8167's wrong-passphrase cases
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.
2026-10-25 13:15:28 -05:00
Chris Tate e8a64267cb Call toJSON on foreign elements and merge main 2026-10-25 13:13:02 -05:00
Chris Tate ffbbf1590f Send every dyn kind that can be a cycle candidate through one release path
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.
2026-10-25 13:11:47 -05:00
Chris Tate 13a9476df9 Fix the latin1 decode bounds and merge main 2026-10-25 13:11:33 -05:00
Chris Tate 2a0ada527b Map intersections of equally represented constituents as that type (#2228) 2026-10-25 13:11:01 -05:00
Malte UblandClaude Opus 5.5 ed50daa853 Keep the whole union of an optional parameter that reaches a recursive placeholder
An optional parameter typed by a union that is still being mapped gets a
recursive placeholder: a union id whose arms arrive when the outer frame
finishes. The function-type mapping then armed that placeholder with
undefined, but the placeholder's arm list was still empty, so the result
was a fresh union holding only `undefined`. The parameter lost its record
arm, the same checker type mapped to two different layouts depending on
where the mapping started, and later conversions failed (SC2001, SC2002,
SC2003).

This happens when an interface method takes an options object whose
callbacks are typed by a subinterface of that same interface:
`newSession(options?: { withSession?: (ctx: Replaced) => Promise<void> })`
with `interface Replaced extends CommandContext`.

When the checker spelled the parameter with an undefined constituent, the
placeholder already carries that arm, so the parameter keeps the
placeholder. withUnitArm now refuses a pending placeholder instead of
building the unit-only union.

pi's ExtensionCommandContext is such an interface. It refused
ExtensionAPI.registerCommand, and with it pi's main().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 11:01:57 -07:00
Chris Tate 8f425a8a1b Wait for mailbox events in ppoll on Linux
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.
2026-10-25 13:01:23 -05:00
Chris Tate a88fdacbe2 Iterate strings over their bytes
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.
2026-10-25 13:00:18 -05:00
Chris Tate e24ed12933 Decode string storage within its length in Buffer encodings
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.
2026-10-25 12:48:21 -05:00
Chris Tate b0c01dc341 Adapt the nursery trigger to where cyclic garbage dies
A program's nursery trigger starts at 256 candidates and doubles, up to
4096, after each scheduled mature or full pass whose dead set held more
mature and old objects than an eighth of what the nursery passes freed
since the previous one; it halves after one that held less than a
thirty-second of that. SCR_CYCLE_THRESHOLD and
scr_cyc_set_nursery_threshold pin the trigger as before. The
scheduled-age rule keeps counting collection points in the pinned or
default trigger.
2026-10-25 12:48:07 -05:00
Malte UblandClaude Opus 5.5 c46f70e451 Fix wrong npm declarations locally with type patches
A project can now patch a package's declarations in package.json:
"scriptc": { "typePatches": { "<package>@<range>": { "declarations":
{ "<selector>": "<type>" } } } }. A leading "|" widens the declared type,
any other type replaces it. Selectors name declarations by path
(Type.member, fn(), fn(param), list()[], Env[string], export=(), default,
file.js:local()) and apply to the package's .d.ts, its JSDoc and its
@types twin alike.

Patches edit file text where every consumer reads it: the program host's
source reads (both checker worlds) and the npm-static passes that read
package files themselves. Only type nodes and JSDoc type tags change, and
no edit adds or removes a line. Installed copies are selected by version;
an @types twin by the runtime package's installed version. Patches that do
not apply as written warn (SC8030-SC8033) and the warnings repeat on
executable cache hits. Without the setting nothing changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:27:52 -07:00
Chris Tate 88717dd3f5 Address review feedback 2026-10-25 12:16:30 -05:00