Commit Graph
6750 Commits
Author SHA1 Message Date
Chris Tate d294af682a Fix the static-objects order test 2026-10-25 17:53:23 -05:00
Chris Tate 5b40498c22 Keep asserted values in a map the lowerer owns
compiler-weak-collections.test.ts refuses weak collections in compiler
sources: the compiled compiler must not depend on which of its values can
serve as weak keys. The lowerer owns the assertedValues cache for its
pass, so an ordinary Map holds it.
2026-10-25 17:52:20 -05:00
Chris Tate 27c00f6577 util: runtime format strings, inspect.custom and colors, untyped promisify (#1911) 2026-10-25 17:46:31 -05:00
Chris Tate b63dc67f9e Let boxable function arms ride a checked-dynamic union (#2269) 2026-10-25 17:44:23 -05:00
Chris Tate cc08abcbe7 Match the pending-flag fallback edge after the callback's receiver block
An indirect callback call now gives the closure an undefined receiver when
it reads one, so the fallback's edge into the pending-flag phi comes from
the receiver block instead of callback.indirect. The flag is still false
on the direct call and true on the fallback.
2026-10-25 17:42:53 -05:00
Chris Tate b372816337 Address review feedback 2026-10-25 17:39:01 -05:00
Chris Tate 46785f8eba Address review feedback 2026-10-25 17:35:38 -05:00
Chris Tate fad9cbdf57 Keep U+11A7 after a Hangul LV syllable in NFC and NFKC (#2266) 2026-10-25 17:31:45 -05:00
Chris Tate d33b083a3b Cache the zone offset at UTC midnights for local-time conversions
Local setters and the field constructor convert a local time value to
UTC by bracketing it with the zone offset a day before and after, then
checking the candidate instants: three to five localtime(3) calls per
conversion. In glibc each call takes the zone lock and, for dates past
the zone file's last transition, parses the zone's rule string again.

The offsets now come from a per-thread cache of offsets at UTC midnights:
a day whose two midnights agree has that offset throughout, given at
most one transition per day, which the conversion already assumes. A
day with a transition still asks localtime for the instant. A change of
process.env.TZ clears the cache.
2026-10-25 17:30:44 -05:00
Malte UblandClaude Opus 5.5 0d27008899 Classify zero-cost gate differences instead of failing every runtime change
Any change to the runtime's C sources makes every executable differ, so
the gate failed even when every program's IR and LLVM were byte-identical.
The gate now classifies each difference outside --allow:

- runtime-only: IR and LLVM identical, the same LLVM helper, runtime pack
  objects that differ between the checkouts (bitcode alone records the
  checkout path, so it does not count), and every symbol the executable
  gains, loses or resizes defined by a changed runtime object. It passes
  with a note that lists the changed runtime functions the program links
  (from the release and speed bitcode, through llvm-dis) and the size
  change.
- nondeterministic: the baseline builds the program again, and its two
  builds differ in the same artifacts. It passes with a note.
- differs: anything else; it fails as before.

--strict fails runtime-only and nondeterministic programs too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:26:18 -07:00
Malte UblandClaude Opus 5.5 1733a4c84b Name new corpus programs instead of numbering them
Parallel changes kept taking the same next corpus number (6992 twice, the
Node island programs renumbered several times). The harness never needed
the numbers: it discovers every entry by glob, and 818 programs already
have descriptive names. AGENTS.md now makes a name, without a number, the
convention for new programs and keeps the numbered ones as they are.

The two scripts that sampled programs by number skipped named programs.
They now key a named program by a stable hash of its name, so they sample
it at the same rate; the numbered programs they pick are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:17:09 -07:00
Malte UblandClaude Opus 5.5 09de11920c Keep the receiver read in every JavaScript function for now
Skipping it in functions without `this` missed methods that read the
receiver through super (8302-class-super-method-values,
prototype-method-overrides) and changes the default build's dynamic-tier
counts. It changes the default JavaScript lowering, so it gets its own PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:15:13 -07:00
Malte UblandClaude Opus 5.5 7bc082da29 Give every numbered registry one source of truth and a check
Parallel branches kept picking the same numbers: dyn handle tags, SC
diagnostic codes, and the values the LLVM backend copies from runtime
enums.

- Diagnostic codes: codes.ts lists every active, retired and reserved code.
  ScrDiagnostic.code is typed by it, and codes.test.ts fails on a code
  without a row, a retired code in use, an unused row, or rows out of
  order. The Node island codes open PRs already use are reserved.
- Dyn handle tags: a new tag is added by name to SCR_DYNH_NEW_TAGS and the
  enum numbers it after the largest pinned value, so branches never pick
  a number. The pinned values are unchanged; value 63, never assigned, is
  listed as unassigned, and the test fails on any other gap.
- The backend's copies of ScrDynKind, ScrMessageKind, ScrBytesElem, the
  closure function_kind bits (now named SCR_CLOSURE_KIND_* in C) and the
  %ScrArr spare flags are checked against their C declarations.
- scripts/registry-claims.mjs lists the codes and pinned tags open PRs
  claim and reports collisions; it finds handle tag 81 claimed by three
  open PRs today.

Every numeric value is unchanged, so IR, LLVM and executables are
byte-identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:14:59 -07:00
Chris Tate 8be991aeca Skip worker cancellation polls in short loops inside another loop (#2261) 2026-10-25 17:14:42 -05:00
Chris Tate bddb5c7205 Address review feedback 2026-10-25 17:13:16 -05:00
Malte UblandClaude Opus 5.5 062700bdde Convert asserted values that no ordinary conversion reaches
`x as T` erases (or, record to record, converts), and the value then meets
its slot with x's representation. Where no conversion reaches the slot
the program was refused:

- a union whose arms only partly convert into T (`api as Partial<Record<
  string, S>>` on `S | Partial<Record<K, S>> | undefined`) failed at the
  next typed slot with SC2003;
- a record or array whose member types the assertion narrows (`models as
  Model<"one">[]` on a union-element array, `item as Narrow` with a
  narrower field) failed with SC2002 or SC2003.

The lowerer now remembers the asserted type of an erased value, and a slot
that would refuse it converts it as asserted: the arms T excludes throw
the catchable TypeError of a lying assertion and the others convert, and
records and arrays take the checked deep conversion, which validates the
value member by member and names the deepest failing path, as a cast of an
unknown value does. The conversions run only where the slot would
otherwise refuse the value, so everything that already lowered keeps its
code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:10:31 -07:00
Chris Tate 67c38b5361 Keep the whole union of an optional parameter that reaches a recursive placeholder (#2241) 2026-10-25 17:10:27 -05:00
Malte UblandClaude Opus 5.5 5e843317af Add scriptc types patch and scriptc types list
`scriptc types patch <package> <selector> <type> [--range] [--dry-run]`
finds the installed package from the working directory, shows every
declaration the selector matches (declaration files, JSDoc, @types
package) with its type before and after, and adds the patch to the
nearest package.json: into an entry whose range covers the installed
version, or under ^<version>. Nothing is written when the selector
matches nothing. `scriptc types list` shows each patch and what it
matches. Both CLI hosts run the same implementation with their own
parser. The TypeError note for a wrong declaration now names the command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:07:09 -07:00
Malte UblandClaude Opus 5.5 56e8417746 Name the package declaration and its patch when a declared-type check fails
A value that contradicts a type an npm package declares used to throw
"expected number at $, got undefined" and nothing else. Such a check now
names the declaration and the type patch that admits the value:

    profilekit@3.1.0 declares Profile.email as string (index.d.ts:7)
    if that declaration is wrong, patch it in package.json: ...

Origins are found for values a package function returns under a written
return type, for properties of records typed by a package-declared type
(record projections into such a type are keyed by it), and for
declaration-boundary casts, whose origin the projection records from the
declaration-world checker. Generic declarations get none: their types
come from the program's own instantiation.

Checked conversions carry dynCheck.origin, an index into
IrModule.declOrigins; the site's existing cold exception block calls
scr_type_origin_note (new runtime unit behind the typeOrigins feature),
which appends the note to the pending TypeError and derives the selector
suffix and the admitted kind from the failure. Union extractions get the
note per arm at compile time. The hot path is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:06:50 -07:00
Chris Tate 48e87d342f Run the linker job directly for host Linux links with lld (#2253) 2026-10-25 17:05:57 -05:00
Chris Tate 0b772d69ea Fix CI 2026-10-25 17:04:56 -05:00
Chris Tate f1a69d9bb0 Probe hashed number-key maps without normalizing integer keys (#2264) 2026-10-25 17:03:41 -05:00
Chris Tate c7fc9a580e Fix CI 2026-10-25 17:03:23 -05:00
Chris Tate f4936d742c Report kept direct references before the checks that skip them silently
The direct-reference rewrite tested pureReceiver and runsAfter before the
effectFree and createdBefore checks that report why a reference is kept,
so a receiver with effects or a read that may run before the member is
stored was skipped without its report line. The reporting checks now come
first; every check still keeps the reference as written.
2026-10-25 17:02:53 -05:00
Malte UblandClaude Opus 5.5 171214b2c2 Discard the result of void generic methods called through class-backed interfaces
A generic method called through an interface value that classes
implement dispatches by name on the checked-dynamic receiver, and the
result converts to the call's instantiated return type. For a void
method, that conversion (`unknown` to `void`) was refused (SC1100). The
result is now discarded, as JavaScript does.

The dispatch helper selected overriding subclasses' erased instances with
a ternary, which has no void form (an internal compiler error once the
call got that far). A void method now selects them with if statements.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:02:26 -07:00
Malte UblandClaude Opus 5.5 3375304945 Erase lambdas that implement a generic method through their context
A lambda with no type parameters of its own, contextually typed by a
generic signature (`send: (m) => …` for an interface's `send<T>(m:
Message<T>): string`), takes that signature's type parameters: its
parameter is `Message<T>`. A generic lambda written with its own type
parameters already lowers once, erased, with them bound to `unknown`, but
this one tried to map `Message<T>` with `T` unbound and was refused
(SC2009).

It now lowers the same way, erased, binding the contextual signature's
type parameters to `unknown`. Parameters bound in the enclosing scope
keep their bindings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:02:24 -07:00
Malte UblandClaude Opus 5.5 be0e825ac1 Map any parameters of interface method and call signatures as checked values
A parameter a TypeScript program types `any` maps to the checked-dynamic
slot for function declarations and spelled function types, but not for
method or call signatures of interfaces and type literals. So
`interface Tool { execute(id: string, params: any): Promise<string> }`
was refused (SC2009), along with every value of the interface, whenever
no class implementing it gave it a class view.

Method and call signatures in program source now take the same rule.

pi's ToolDefinition declares `execute(toolCallId: string, params:
Static<TParams>, …)`. Under --node-islands, typebox's types resolve to
`any`, so the parameter is `any`. The refusal rejected the AgentSession
class through its `_customTools: ToolDefinition[]` field, and
CreateAgentSessionRuntimeResult with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:02:20 -07:00
Malte UblandClaude Opus 5.5 605e312e78 Let boxable function arms ride a checked-dynamic union
A union with a checked-dynamic arm (`unknown`, fetch's AbortSignal)
maps to the checked-dynamic value when every other arm is one the
checked-dynamic tree holds faithfully. Function arms were excluded, so
`Progress | AbortSignal | undefined` was refused (SC2009).

The tree boxes a function whose parameters check in and whose result
converts out by reference (canBoxFuncIntoDyn), and `typeof x ===
"function"` narrows it back out to the typed function. Such arms now
join the collapse. The corpus program checks calls, identity (`===`
against the original), and the other arm's members against Node.

pi's SessionManager.listAll takes `onProgressOrSignal?: SessionListProgress
| AbortSignal`. The refusal rejected the whole SessionManager class.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:02:19 -07:00
Malte UblandClaude Opus 5.5 52abff7c14 Clone union records whose override is an empty array literal
Spreading a union of records and replacing a shared field keeps each
value's variant (lowerUnionRecordClone), but only when every override's
type lifts into the field. An empty array literal is typed `never[]`,
which maps to nothing, so `{ ...entry, content: [] }` over a union fell
back to the ordinary literal path. That path cannot build a union of
spread records, and the literal was refused (SC2001).

An empty array literal now builds an empty array of each arm's own
element type. This covers a field that is an array and a field that is a
union with exactly one array arm (`string | Part[]`).

pi's session-manager repairs messages with missing content this way
(`return [{ ...message, content: [] }]`). The refusal rejected the whole
SessionManager class.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:02:07 -07:00
Chris Tate e7a088f904 Key the executable cache by the function layout's contents
The early executable cache key covered the profile but not the function
layout, so a build that added, changed or edited a layout could restore
the executable built with the previous order. The key now includes the
digest of the layout file's bytes. An unreadable layout fails the build
with SC3002 before the cache lookup.
2026-10-25 16:58:11 -05:00
Chris Tate 5f76d41a71 Keep U+11A7 after a Hangul LV syllable in NFC and NFKC
libunicode composes an LV syllable with U+11A7 as if it were a trailing
consonant (U+11A8..U+11C2), which drops it: "가ᆧ".normalize()
gave "가" where Node gives "가ᆧ". U+11A7 is a class-0 vowel
that composes with nothing, so for the composing forms the code points
between occurrences of it normalize on their own and it is kept as it
is. The guard sits in front of the vendored library (scr_unicode_normalize),
for String.prototype.normalize and the NFC step of IDNA mapping.

8009-normalize-hangul-jungseong-o-yae prints the code points of all four
forms for U+11A7 after LV and LVT syllables, conjoining jamo, other
starters and combining marks.
2026-10-25 16:57:11 -05:00
Chris Tate 8d485dcf5f Convert promise and function values into a union's arm of their kind (#2257) 2026-10-25 16:56:50 -05:00
Malte UblandClaude Opus 5.5 b3d7adb1a0 Remove the committed node_modules symlink
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 14:50:39 -07:00
Malte UblandClaude Opus 5.5 3d886579b2 Remove the committed node_modules symlink
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 14:50:35 -07:00
Chris Tate 60c89fd456 Keep record values that subtype reduction folds into an inferred return type (#2069) 2026-10-25 16:50:31 -05:00
Malte Ubl f10f7c42b4 Merge remote-tracking branch 'origin/perf/shape-nsbench-folds' into r5q/shape-nsbench-folds 2026-10-25 14:50:19 -07:00
Malte UblandClaude Opus 5.5 98e67128c1 static-stores: this.tag on a literal made at the call stays a read
The object literal is created in the consumer's call, so the base's
created-before check keeps the read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 14:49:25 -07:00
Malte Ubl 83c8896886 Merge branch 'r5q/shape-overlay-soundness' into r5q/shape-nsbench-folds 2026-10-25 14:49:25 -07:00
Malte UblandClaude Opus 5.5 14e4c7d1ef Keep static-object rewrites when propertyIsEnumerable asks another class's prototype
The analysis reads F.prototype as ⊤ beside the class prototype objects it
tracks, so every class looked queried by any G.prototype.propertyIsEnumerable
call. The query reaches a class's prototype only when G may be that class's
function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 14:49:24 -07:00
Malte UblandClaude Opus 5.5 5a3d6d7903 Call function tables without boxing or reference counting
Values typed `F | undefined` for a function type F are now plain closure
pointers (nullable-pointer unions), so a keyed read of a
`Record<string, F>` or a Map.get no longer allocates a union box. Keyed
record reads, string-keyed Map.get and checked extractions lend the stored
closure as a receiver or callee when ReferenceWrites proves the call cannot
write the locations it was read from, and optional-chain receivers lend
under the same proof. Owned record overflow reads use the inline entry
lookup.

Indirect closure calls skip the ambient receiver bracket for closures
whose bodies never observe it (new ScrClosure.function_kind bit
SCR_FN_RECEIVER_FREE; signature adapters that only forward the receiver
copy it from their original), and a callValue that forwards `dyn.this`
needs no bracket at all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 14:49:00 -07:00
Chris Tate bdc90f90cb Merge main 2026-10-25 16:48:31 -05:00
Chris Tate 3c057b501f Lay out the native compiler's functions by the code each kind of run executes
A warm rebuild ran about 3.5 MB of the native compiler's code, but the code
was spread over its 69 MB text, so 64 KB fault-around mapped about 20 MB of
it. The emitter can now take a function layout: a list of functions in
tiers. It writes the listed definitions, generated helpers included, first
and by tier, each in a `layout-<tier>` partition group. The LLVM helper
compiles each partition's definitions in text order, so the listed ones
come first in their partitions, and a helper that places functions by
group keeps a tier's definitions in the same partitions. The rest keep
their order and their source-file groups.

- compile() takes a `functionLayout` file (`<tier> <symbol>` lines,
  gzip-compressed when the name ends in .gz). Keys leave out module
  numbers.
- build-native-cli.mts uses the committed packages/compiler/native/cli.layout.gz,
  or the file SCRIPTC_NATIVE_LAYOUT names; an empty value and instrumented
  builds use none.
- scripts/refresh-cli-layout.mjs builds an instrumented compiler, builds
  the runtime workloads and a corpus slice cold and then unchanged, and
  reads the functions each run executed from the raw profiles. Tier 1 is
  `--version` and rebuilds that hit both caches; tier 2 is the other builds.
- The release workflow's cli-profile job also collects a layout on the
  release commit, and the CLI builds use it. Names of closures and
  generated helpers change with nearby edits, so a committed layout
  matches fewer functions as main moves on.
- Forked lowering and emission workers reset the profile counters after
  fork and write their profile before _exit, so instrumented builds also
  count the work done in workers. This applies to refresh-cli-profile.mjs
  too.

Executables the compiler produces are unchanged.
2026-10-25 16:44:46 -05:00
Chris Tate 247f43c0f0 Mark emitted functions sanitize_address only in sanitized builds (#2251) 2026-10-25 16:44:16 -05:00
Chris Tate a0c1358f0e Map distributed conditional members as the concrete union does
Three fixes to distributing a conditional member over a generic body's
bound parameter:

- Consulting the binding counts as a context resolution, so a mapping
  that drops an all-never part is no longer cached as context-free and
  reused by a recursive shape for a binding whose member is inhabited.
- An `unknown` or `any` result absorbs the other results, as the
  checker's union does, so the member is checked-dynamic. Before, the
  union kept a `dyn` arm, and the build stopped with SC9001.
- `undefined` and `null` results become the union's unit arms, as in the
  concrete instantiation. Before, `undefined` mapped on its own to a
  `void` arm, which also failed with SC9001.

Corpus generic-conditional-member-units covers the `unknown` and the
`undefined`/`null` members.
2026-10-25 16:42:11 -05:00
Chris Tate 8c579a793b Check only the files whose diagnostics the tsc gate keeps (#2263) 2026-10-25 16:39:45 -05:00
Chris Tate 3a900ed9b6 Keep the poll in nested loops whose counter can stop changing at 2^53
The range analysis caps a counter that steps by 1 at 2^53, where
`j + 1 === j`, so a loop such as `for (let j = 2 ** 53 - 2;
j < 2 ** 53 + 10; j++)` has a range of three values at its condition and
was taken for a loop of three runs. It never ends, and without its poll
worker.terminate() could not stop it. A loop's runs now count as bounded
only when a step from every value in its counter's range gives a
different value.
2026-10-25 16:38:27 -05:00
Chris Tate 35161d1883 Cross package values at their declared type where the JS literal type lacks members (#2262) 2026-10-25 16:38:00 -05:00
Chris Tate bcbae8a497 Address review feedback 2026-10-25 16:36:06 -05:00
Chris Tate e3f02c73c9 Probe hashed number-key maps without normalizing integer keys
A number-key Map or Set read whose key has an integer view
(sc_map_entry_f64_int) fell back to the same hashed probe as any double
key, which tests for NaN and -0 before hashing. The fallback is now
sc_map_entry_f64_hashed_int, which takes the integer and converts it to
the key word directly: an integer is never NaN, and zero converts to +0.

Both hashed probes test the absent-key filter only before scanning a map
that keeps no bucket table. The filter keeps two of its 64 bits per key,
so it rejects few absent keys once a map has a bucket table, and the
probe's first bucket read answers those.

The map layout and the runtime lookup are unchanged. Corpus 8331 covers
integers the compiler tracks as integers on hashed maps and absent keys on
small bucket tables, and matches Node's output.
2026-10-25 16:32:27 -05:00
Chris Tate 8dd019fd38 Merge main 2026-10-25 16:32:09 -05:00