Commit Graph
5153 Commits
Author SHA1 Message Date
Chris Tate d4a677525d Build the sanitized three.js nodes fixture in its own CI job (#2210) 2026-10-25 16:16:34 -05:00
Chris Tate 0ee74478fd Run paired row sums two columns per step (#2247) 2026-10-25 16:15:20 -05:00
Chris Tate 062d04668d Batch the program's identifier symbols before collection (#2254) 2026-10-25 16:08:26 -05:00
Chris Tate 110396ee2d Replace a package's declaration files or supply its declarations with type patches (#2245) 2026-10-25 16:03:41 -05:00
Chris Tate 9cfeb2408f Count shorthand property reads of an initializer-less JavaScript let (#2252) 2026-10-25 15:54:08 -05:00
Chris Tate 94446a086a Answer Object.prototype's methods on typed records (#2244) 2026-10-25 15:43:30 -05:00
Chris Tate 224e040aac Map generic alias casts inside generic bodies with the instance's arguments (#2227) 2026-10-25 15:32:14 -05:00
Chris Tate 677c550fdb Merge main 2026-10-25 15:28:35 -05:00
Chris Tate e842da2ceb Batch the program's identifier symbols before collection 2026-10-25 15:24:38 -05:00
Chris Tate c3880c89b0 Skip module-pass work that changes nothing in the functions it walks (#2250) 2026-10-25 15:20:10 -05:00
Chris Tate 20574b1932 Merge main 2026-10-25 15:18:27 -05:00
Chris Tate 8d5c85448f Close recursive record knots through erased generic method members (#2246) 2026-10-25 15:07:46 -05:00
Chris Tate c21fc01201 Key bound alias instances by the declared alias
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.
2026-10-25 14:49:55 -05:00
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
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
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
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 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
Chris Tate 734ced4f4f Address review feedback 2026-10-25 13:49:34 -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 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
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