Commit Graph
6750 Commits
Author SHA1 Message Date
Chris Tate 9d7f0182e5 Keep CommonJS wrapper names and redeclared exports out of namespace rewrites
- A guarded declaration of a name the CommonJS wrapper defines (`var
  exports = exports || {}`, and `module`, `require`, `__filename`,
  `__dirname`, `arguments`) reads the wrapper's value, not undefined, so
  it keeps its text instead of becoming a fresh object.
- The write scan counts destructuring assignments, and destructuring
  for-in/for-of targets, as writes, so a hoisted function that sets the
  namespace before its guarded declaration keeps the declaration as it
  is.
- An exported `var` that a later declaration of the same name sets
  again is not settled at the export: importers would read the
  redeclared value, where Node copies the first one into
  module.exports. It stays refused (SC1090), as before. The npm case
  var-export-redeclared pins it.
2026-10-25 01:04:55 -05:00
Chris Tate a54e0811d0 Journal the Promise.all tuple and AsyncLocalStorage callback thunks 2026-10-25 01:04:31 -05:00
Chris Tate 5d3e0c7a8b Support X509Certificate fields and methods, TLS certificates and tls.Server contexts (#2052) 2026-10-25 01:00:24 -05:00
Chris Tate 3243e843b3 Call the declared base method for super calls in JavaScript method values (#2188) 2026-10-25 00:57:31 -05:00
Chris Tate 4963dcf8ec Cache absent properties in the dynamic tier's property caches (#2186) 2026-10-25 00:46:10 -05:00
Chris Tate e5ba073468 Parse JSON into discriminated unions in one pass
`JSON.parse(text) as T` for a discriminated union of records took the
checked-dynamic route: it parsed the text into a dynamic tree, matched
and copied that tree into records, then freed it. The one-pass schema
parser now builds these unions directly, as record members, array
elements and whole documents. For an object, a shallow scan of its
members finds the discriminant's value, which selects the one record arm
that holds that literal, and the object is parsed with that arm's
schema. An object with a missing, non-string or unknown discriminant, or
any member the arm rejects, declines, and the checked route then gives
the result or the error as before.

The compiler admits unions whose record arms each name their string
literals, every literal naming one arm, plus null and undefined arms.
2026-10-25 00:44:25 -05:00
Chris Tate 713f28dcd2 Merge main 2026-10-25 00:27:44 -05:00
Chris Tate 478feb15cd Call the declared base method for super calls in JavaScript method values
Since 76735a8d9 (#1975), a `super.m()` call in a JavaScript class method
whose `this` is the call-site receiver reflected the base class's whole
prototype and read `m` from it. An instance's member lookup first tests
its property bag, which inherits from its class's prototype data, so once
the base's prototype was reflected, `child[k]` for a method the subclass
overrides found the base's method there: `value[name](arg)` on a subclass
instance called the base method.

While no code reads a prototype method by name, `super.m()` now calls the
declaring class's receiver-independent method value with the receiver, as
the method's [[HomeObject]] lookup answers, without reflecting the base
prototype. Programs that read prototype methods by name keep the
reflected lookup.
2026-10-25 00:26:55 -05:00
Chris Tate 3e54e0edb3 Read fs.promises as a module and check fs read and write arguments as Node does (#2098) 2026-10-25 00:23:19 -05:00
Chris Tate be7fb37235 Expect the get probe at the native-array length site in the emission test
The length read in the keyed and length emission test is a get site,
which now calls scr_js_get_probe.
2026-10-25 00:16:42 -05:00
Chris Tate c1cc26687a Correct the native compiler's nursery trigger comment (#2185) 2026-10-25 00:11:11 -05:00
Chris Tate 7141af5cc9 Merge main 2026-10-25 00:07:23 -05:00
Chris Tate 944cae84a8 Keep an unchanged executable without selecting the runtime pack
An unchanged rebuild that kept its output still selected the runtime pack's
artifacts first, reading the stored selection, and computed the identity of
every selected artifact to open the executable cache.

- The executable cache key names the pack by its package.json text, the
  manifest's identity (its text on darwin), and the link features and
  flavor. Together they fix which artifacts the selection names.
- The cache selects the artifacts only when it needs their identities, in
  restore() and trace(). keep() checks the recorded entry, which lists them.
- A build that does not keep or restore its executable selects the pack
  before code generation, as before.
2026-10-25 00:06:13 -05:00
Chris Tate 204fb202b6 Keep speculative copies in typed-array loops over shared storage (#2184) 2026-10-24 23:57:11 -05:00
Chris Tate 2269fd9e1f Merge main 2026-10-24 23:48:53 -05:00
Chris Tate 3e115ce08c Cache absent properties in the dynamic tier's property caches
A read site's cache only remembered properties it found on the receiver
or its first prototype. Reads of properties an object lacks, which
validation libraries make constantly (zod reads ctx.skipChecks,
payload.memo, x.aborted and _zod.optin on every check), walked the whole
shape chain and then repeated the lookup on the generic path every time:
7.9 million such reads in one zod benchmark run.

- Read sites call a new probe, scr_js_get_probe, that answers undefined
  for a key the receiver and its chain of plain shaped objects (at most
  one prototype, ending in the default prototype) lack, and caches that
  answer. The entry holds while the receiver's shape, its prototype link,
  and that prototype's shape are unchanged. Its tag and placeholder
  holder make the inline entry-0 test in compiled code miss, so the
  answer always comes from the probe. Method reads keep
  scr_js_load_probe, so a missing method still reaches the slow path and
  its "is not a function" error. Names of Object.prototype members are
  never cached as absent.
- Receivers of one shape with different prototypes (zod's internals
  objects) each keep an entry instead of replacing one another.
- A site whose four-way record is full consults a per-thread table keyed
  by shape and key before walking shapes, and fills it.
2026-10-24 23:47:41 -05:00
Chris Tate 182a108270 Correct the native compiler's nursery trigger comment 2026-10-24 23:41:28 -05:00
Chris Tate 468ef7692b Drop the refused never-promise instanceof case from the maybe-promise fences
instanceof Promise on values that are never promises now compiles to
false after the operand is evaluated, so the diagnostics fixture no longer
lists it as refused. 8326-never-promise-instanceof covers records, arrays,
dates, collections, regular expressions, byte arrays and unions with null,
and a call operand that still runs.
2026-10-24 23:35:56 -05:00
Chris Tate 37031fa7c2 Convert a loop's string receiver to UTF-16 at once, and publish it only when the scan holds another string (#2180) 2026-10-24 23:34:50 -05:00
Chris Tate 546e37f210 Run the native compiler's nursery passes every 1024 candidates (#2181) 2026-10-24 23:31:54 -05:00
Chris Tate f06f47aca1 Resolve promises with an argument in the rejectUnauthorized corpus program
8179-tls-reject-unauthorized-values.mjs called the resolve function of an
untyped new Promise() with no argument. tsgo types that function as taking
one argument and reported SC0001 for each call, while tsc-ts accepted them,
so the checker-identity comparison failed. The calls now pass undefined.
2026-10-24 23:27:46 -05:00
Chris Tate 20041b4262 Keep speculative copies in typed-array loops over shared storage
In a program that can start a worker, a typed-array loop version's guard
requires thread-owned storage, so every view of a SharedArrayBuffer runs
the version's checked copy. That copy gave its loops no speculative or
int32 kind copies, on the assumption that it rarely runs, so loops over
shared views, such as k-nucleotide's frequency count in every worker, ran
only the double copy. A version whose guard requires thread-owned
storage now keeps those copies in its checked copy.
2026-10-24 23:27:15 -05:00
Chris Tate d2326c4c89 Look up a number Map key on its integer view (#2170) 2026-10-24 23:27:03 -05:00
Chris Tate d775fdee8c Merge main 2026-10-24 23:26:30 -05:00
Chris Tate f716dec756 Convert Buffers to and from latin1 and hex text a word at a time
- toString("latin1"): counts the high bytes eight at a time, copies
  words of ASCII bytes, and writes other bytes from a table of their
  UTF-8 pairs without branches. ASCII-only results are marked ASCII, and
  other results record their UTF-16 length (one unit per byte), so
  `.length` does not walk the string.
- Buffer.from(text, "latin1" | "ascii" | "binary"): copies text known to
  be ASCII, copies words of ASCII bytes, and decodes eight bytes of one-
  and two-byte sequences in one word, storing each output byte without
  branches. Longer sequences take the previous path.
- Buffer.from(text, "hex"): reads digit values from a table.
- toString("hex") and toString("base64"/"base64url") mark their results
  ASCII, so a later decode, length or index read skips the scan for
  non-ASCII bytes.
2026-10-24 23:26:01 -05:00
Chris Tate 863a71e37c Number hoisted speculative leaves per function (#2179) 2026-10-24 23:19:34 -05:00
Chris Tate 77307e15b4 Infer untyped JavaScript collection types once, without re-entering (#2177) 2026-10-24 23:03:33 -05:00
Chris Tate 6a29947998 Run the native compiler's nursery passes every 1024 candidates
The native compiler now starts with the cycle collector's nursery trigger
at 1024 new candidates instead of 256. scr_cyc_set_nursery_threshold sets
the trigger that SCR_CYCLE_THRESHOLD sets, on the pattern of
scr_cyc_set_growth_cap; the environment variable still takes precedence.

The compiler sets it from a constructor in its host object, before its
modules initialize. Set from main, the trigger leaves the build's
collection work where it was: the cost depends on the collector's first
passes. Programs keep the default trigger.

A cold tsc-ts build takes 4% less wall time (12 paired runs) and its peak
RSS goes from 1.41 GB to 1.31 GB. The program's IR is unchanged.
2026-10-24 22:59:02 -05:00
Chris Tate faa54f6443 Cache only number-key integer views that need no conversion
A Map/Set lookup's integer key view was cached for the function by the key
operand's name. When the view was a conversion emitted at a lookup in a
branch, a later lookup of the same operand (a wide integer constant) reused
it from a block the conversion does not dominate, and LLVM rejected the
module. Only the value's own integer or uint32 view and integral literals
are cached now.
2026-10-24 22:58:41 -05:00
Chris Tate add5f4869b Convert a loop's string receiver to UTF-16 at once, and publish it only when the scan holds another string 2026-10-24 22:53:15 -05:00
Chris Tate 688c1e03a1 Keep undefined results, literal text and hoisted vars in the shape overlay
- A function's results include undefined when a call can run off the end
  of its body or reach a bare return, so the overlay's return type keeps
  undefined and a caller's branch on it stays reachable. `new` of an ES5
  constructor ignores primitive results, and functions called with `new`
  get no @returns tag.
- Package JSDoc tags are neutralized only in comments: string, template
  and regular expression literals and JSX text keep their text.
- A branch replaced as unreachable keeps the var names it declares that
  its scope declares nowhere else, so references outside it read
  undefined. A branch that declares a function in a block is kept.
- The shape-overlay-facts npm case covers all three; four files of the
  overlay change are formatted.
2026-10-24 22:52:52 -05:00
Chris Tate c21c26495d Start integer loops from the integers their starts carry (#2174) 2026-10-24 22:46:20 -05:00
Chris Tate f4eda0fb6c Number hoisted speculative leaves per function 2026-10-24 22:43:27 -05:00
Chris Tate be878b4eb9 Freeze class instances held in untyped values (#2061) 2026-10-24 22:43:12 -05:00
Chris Tate 33f63655a0 Infer untyped JavaScript collection types once, without re-entering
The inference reads types while it runs: resolving a candidate's
constructor through a destructured require (const { TextDecoder } = util)
asks for the namespace's type, which asked for the inferred collection
types again and restarted the inference until the stack overflowed. The
lowerer now stores an empty table before inferring, so reads during the
inference see no inferred types, and stores the result after.
2026-10-24 22:34:45 -05:00
Chris Tate 0fbb47382d Read elements of native arrays passed to untyped code by index (#2171) 2026-10-24 22:32:09 -05:00
Chris Tate 65ef34218b Prove a typed-array loop's views disjoint and give each its own alias scope (#2167) 2026-10-24 22:26:22 -05:00
Malte UblandClaude Opus 5.5 22b686c7da Compile pre-module constructor functions as classes and namespace functions as direct functions
With the shape overlay on, a first round of structural rewrites runs before
the typed overlay (shape-inference/static-objects.ts, stage 2b of
DESIGN-constant-objects.md), proven by the constant object graphs:

- An ES5 constructor stored by a top-level statement whose prototype is a
  constant object of plain methods becomes a class declaration with those
  methods, when the program cannot tell: the function is only ever
  constructed and never escapes, its prototype is never replaced, nothing
  enumerates instances with for-in or reflects on the prototype or the
  function, nothing reads their names or sources, the bodies are
  strict-mode safe, and only definitions run between the constructor and
  its last method.
- A constant namespace member holding a `this`-free function expression
  becomes a function declaration, and reads of the member in the same file
  whose receiver is exactly that constant object refer to it directly.

The rounds rerun the analysis on the rebuilt program, the overlay now also
covers the program's own JavaScript files, and the build report counts the
rewrites. A JavaScript switch whose case tests lower to another type than
the discriminant compares with === instead of being refused.

Namespace benchmark (n=20000): 1.55 s -> 0.42-0.47 s, output identical to
Node (typed TypeScript twin 85 ms, Node 21 ms).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:25:14 -07:00
Malte UblandClaude Opus 5.5 9d3365c99b Compile namespace-style npm packages: guarded declarations and var exports
Two refusals kept pre-module JavaScript packages from compiling:

- `var ns = ns || {}` (and `?? {}`): the read in the variable's own
  initializer made the checker type the namespace `any`, and requirers
  then failed to reach the binding (SC1030). In a module, the read sees
  the hoisted `undefined` whenever nothing else writes the variable, so a
  new package-source normalization respells the declaration as its
  default (`var ns = ({})`) when no other declaration, assignment, update,
  function, class, parameter or local of that name exists in the file and
  no earlier top-level statement mentions it.
- `module.exports = { ns }` with a `var` binding was refused as exporting
  a mutable binding by reference. A `var` (or `let`) whose initialized
  declaration precedes the export statement at top level and that nothing
  reassigns exports exactly the value Node copies, so it now aliases like
  a const.

The namespace benchmark (results/design-shape-inference/nsbench) now
compiles and matches Node; the new npm case legacy-namespaces pins a
namespace library with guarded declarations, constructor functions with
prototype methods, enums, a revealing module and a handler registry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:24:57 -07:00
Malte UblandClaude Opus 5.5 4a8a6567ca Add the shape overlay: typed JavaScript from analysis-proven facts (opt-in)
The shape overlay (DESIGN.md, approved route) rewrites the JavaScript of
opted-in npm packages with JSDoc types the shape analysis proves: record
typedefs for non-escaping literal sites (optional fields for spread
sources), class field, parameter, return and local types, receiver casts,
and branches the analysis proved unreachable replaced by checks. The
lowering trusts only annotations carrying the overlay's marker; package
JSDoc stays a hint (its type tags are neutralized in overlaid files).

- Inferred-fact checks: every checked conversion lowered in an overlaid
  file records its site, and a failure throws a distinct internal error
  ("scriptc internal error: an inferred type was contradicted at <file:line>
  … This is a bug in scriptc's shape analysis, not in the program's data")
  naming the opt-outs; SCRIPTC_SHAPE_CHECK_LOG makes checks log instead
  (validation mode).
- Opt-outs: `--no-shape-overlay`, and `"scriptc": {"packages": {"<name>":
  {"shapes": false}}}` per package.
- Build report: SC6103 counts the declarations typed, the checks inserted
  and the branches replaced.
- `x instanceof Promise` on plain data folds to false.

The overlay runs only with SCRIPTC_SHAPE_OVERLAY=1 while it is new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:24:51 -07:00
Chris Tate b08efe27e5 Complete consecutive synchronous writes in one afterWrite step (#2169) 2026-10-24 22:24:33 -05:00
Chris Tate fe8ba8900f Start integer loops from the integers their starts carry 2026-10-24 22:16:05 -05:00
Malte UblandClaude Opus 5.5 d1432eeb18 Compile namespace-style npm packages: guarded declarations and var exports
Two refusals kept pre-module JavaScript packages from compiling:

- `var ns = ns || {}` (and `?? {}`): the read in the variable's own
  initializer made the checker type the namespace `any`, and requirers
  then failed to reach the binding (SC1030). In a module, the read sees
  the hoisted `undefined` whenever nothing else writes the variable, so a
  new package-source normalization respells the declaration as its
  default (`var ns = ({})`) when no other declaration, assignment, update,
  function, class, parameter or local of that name exists in the file and
  no earlier top-level statement mentions it.
- `module.exports = { ns }` with a `var` binding was refused as exporting
  a mutable binding by reference. A `var` (or `let`) whose initialized
  declaration precedes the export statement at top level and that nothing
  reassigns exports exactly the value Node copies, so it now aliases like
  a const.

The namespace benchmark (results/design-shape-inference/nsbench) now
compiles and matches Node; the new npm case legacy-namespaces pins a
namespace library with guarded declarations, constructor functions with
prototype methods, enums, a revealing module and a handler registry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:13:26 -07:00
Chris Tate 4241ca533f Read elements of native arrays passed to untyped code by index
A typed array such as a number[] reaches untyped code as a capsule of the
native array. A keyed read `xs[i]` there missed the inline cache, which
answers only dyn arrays, and went to the member helper, which converted
the index to a string; the capsule's key lookup then parsed the string
back into a number for every element.

Keyed sites now read an element of a native number[] capsule in place
when the index is an integer below the array's hole-free prefix, and
`length` sites read the native array's length. Other capsule elements
(holes, out-of-range and fractional indices, arrays of other element
types) are answered by the keyed probe through scr_dyn_index_get without
forming the key, or by the helper as before.
2026-10-24 21:59:28 -05:00
Chris Tate cf399bc1bf Pass the unwinding setting in the serialized IR emission test (#2168) 2026-10-24 21:56:47 -05:00
Chris Tate d6c8b0f260 Close the afterWrite batch whenever an afterWrite tick runs
Node's afterWriteTick sets afterWriteTickInfo to null before it runs the
callbacks, whichever tick it is. The runtime cleared the queued batch
only when the running tick was that batch, so a write made from an
earlier tick's callback joined a later queued tick with the same
callback and completed ahead of nextTicks queued between them. Any
afterWrite tick now closes the batch, and such a write queues its own.
2026-10-24 21:52:18 -05:00
Chris Tate 42743e85d3 Decode an undefined TextDecoder input as no bytes (#2093) 2026-10-24 21:51:10 -05:00
Chris Tate f025b52433 Declare a typed-array loop version's alias scopes where its fast copy starts
The guard proves the views disjoint for one entry of the loop. An
enclosing loop may pass other views on its next pass, such as two
buffers swapped after every pass, so one pass's write and the next
pass's read can be the same bytes under different scopes. Without a
declaration, a scope holds across the whole function, and LLVM could
apply one entry's no-alias facts to another's accesses once it unrolled
the enclosing loop.

Each fast copy now begins with llvm.experimental.noalias.scope.decl for
each of its scopes. A declared scope holds from its declaration, and
LLVM gives each copy of the declaration its own scopes when it unrolls
or duplicates the code around it.

Corpus 8182 adds ping-pong passes over swapped Float64Array and
Int32Array views; the emission test checks the declarations.
2026-10-24 21:50:36 -05:00
Chris Tate 7b946ff610 Read Math, JSON and Reflect as real objects in JavaScript (#2006) 2026-10-24 21:50:17 -05:00
Chris Tate d4e8c4f283 Merge main 2026-10-24 21:46:05 -05:00