Commit Graph
5020 Commits
Author SHA1 Message Date
Chris Tate cd3e7b700e Merge perf/shape-overlay-fallback 2026-10-25 20:52:09 -05:00
Chris Tate 6aaaea04e4 Keep one source label per node in the shape analyzer 2026-10-25 20:31:37 -05:00
Chris Tate f8e069daa9 Fix CI 2026-10-25 18:25:29 -05:00
Chris Tate 0b772d69ea Fix CI 2026-10-25 17:04:56 -05:00
Chris Tate e5778cafbb Remove the committed node_modules symlink 2026-10-25 15:57:51 -05:00
Chris Tate 7be83d8e11 Fix the merge and address review feedback 2026-10-25 15:47:57 -05:00
Malte Ubl 922a53fa0c Merge remote-tracking branch 'origin/perf/shape-overlay-fallback' into HEAD
# Conflicts:
#	packages/compiler/src/frontend/shape-inference/static-objects.ts
2026-10-25 13:40:45 -07:00
Malte Ubl cbede2e5fd Merge remote-tracking branch 'origin/perf/static-objects' into HEAD
# Conflicts:
#	packages/compiler/src/frontend/pipeline.ts
#	packages/compiler/src/frontend/shape-inference/overlay.ts
#	tests/harness/npm.test.ts
2026-10-25 13:38:56 -07:00
Chris Tate 88717dd3f5 Address review feedback 2026-10-25 12:16:30 -05:00
Chris Tate e29ea20af1 Address review feedback 2026-10-25 11:02:41 -05:00
Chris Tate 7c0cbce93a Address review feedback 2026-10-25 10:42:26 -05:00
Chris Tate 7721fbd92d Address review feedback 2026-10-25 10:19:26 -05:00
Malte UblandClaude Opus 5.5 0c0077da21 Call constant function tables and cross-module namespace members directly
Static-object specialization for namespace-style JavaScript, with the
shape overlay on (shape-inference/static-objects.ts):

- Functions stored in object literals that a top-level statement creates
  (`ns.registry = { run: function () {} }`) are hoisted to module
  functions, like those stored by assignment.
- `table[key](...)` on exactly one constant object whose members are all
  hoisted functions, with a string key, compares the key against each
  member and calls its function directly; any other key reads the table
  as written. The members do not use `this`, the table has no accessors,
  and comparing a string runs no code.
- A read of a primitive member written in the object's own literal
  (`Kind = { CIRCLE: 1 }`) is replaced by the literal. Members stored
  after the object was created stay reads (they could be read first).
- A read in another JavaScript file of a hoisted namespace member refers
  to it through the defining module's exports: the member joins the
  module's `module.exports = { ... }` literal, and the reading file binds
  the module right after the top-level statement that requires it. This
  applies when the exports object is constant and nothing enumerates,
  reflects on, or lets it escape, and the module does not require the
  reading file.

The overlay's cross-file class types and member function types are now
on by default (SCRIPTC_SHAPE_CROSS_FILE=0 and
SCRIPTC_SHAPE_MEMBER_FUNCTIONS=0 turn them off).

The analysis records computed callee reads as computed reads.

New npm case static-tables (with a JavaScript consumer) and
tests/harness/static-tables.test.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 07:34:04 -07:00
Malte UblandClaude Opus 5.5 0d4954f40c Never let the shape overlay break compilation
With the overlay on, a lowering refusal no longer fails the build. The
annotations the refusal depends on are dropped and the program is loaded
and lowered again, so those declarations compile untyped, as they do
without the overlay:

- Each overlay annotation carries an id (`@scriptcShape #<pos>`).
- shape-inference/fallback.ts blames the annotations of the declarations
  enclosing a refusal and of the declarations its expression names,
  following unannotated functions' returns.
- Rounds that stop converging drop whole files' overlays, then the
  overlay for the build. SC6103 reports what was dropped.

Also:

- The tsc gate's project world is built over the files as written; it
  used to see the overlay's files, which failed the typecheck of three
  npm cases.
- Arrays a builtin creates (match results, `new Array(n)`) are no longer
  typed by the overlay: they may have holes, and the library types them.
- compile() accepts `shapeOverlay: true`.

tests/harness/npm-shape-overlay.test.ts runs the whole npm suite with the
overlay on (npm.test.ts's body moves to npm-suite.ts): every case builds
and runs as in the default build, and a refused build that compiles with
the overlay matches Node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 07:34:03 -07:00
Chris Tate 700526ab17 Merge base branch 2026-10-25 08:19:36 -05:00
Chris Tate d378a4dee7 Keep static-object rewrites where receivers, setters or enumerability differ
- A direct call drops its receiver's evaluation, so a name receiver must
  hold its value whenever the read runs: a parameter, a function
  declaration, or a variable declared with an initializer by an earlier
  top-level statement and never assigned again. A variable read before it
  is assigned (undefined, a TypeError) or in its temporal dead zone (a
  ReferenceError) keeps the member read.
- A store between a constructor and its methods counts as a definition
  only when its receiver is a constant object, which has no setters,
  reached without running code, and its key is not `__proto__`. A store
  that may run a setter keeps the constructor function, since the setter
  could see the prototype without its methods.
- `propertyIsEnumerable` asked of the prototype or the constructor, or
  reached other than as a direct method call, keeps the constructor
  function: assigned methods are enumerable and class methods are not.

The static-ns fixture covers each case and prints what Node prints.
2026-10-25 05:29:55 -05:00
Chris Tate 1b40ac0c64 Merge main 2026-10-25 05:15:58 -05:00
Chris Tate 2abe4a7e65 Undo the main merge on a stacked branch 2026-10-25 05:03:57 -05:00
Chris Tate f07329f569 Fix CI, address review feedback and merge main 2026-10-25 04:57:15 -05:00
Chris Tate 7db7b4dd86 Prove int32 array candidates in one analysis that revises dropped ones (#2196) 2026-10-25 04:39:23 -05:00
Chris Tate fa27df8b06 Speculate floor-mods and products by powers of two as masks and shifts (#2199) 2026-10-25 04:34:53 -05:00
Chris Tate fa02fcd8ae Report which packages and modules would run in a Node island (#1691) 2026-10-25 04:32:22 -05:00
Chris Tate 03b754e891 Convert dynamic values into nested unions in one pass (#1914) 2026-10-25 04:18:21 -05:00
Chris Tate 8f655df65e Hold a Map.get result as the entry's reference until the map can change (#2099) 2026-10-25 03:59:34 -05:00
Chris Tate 4050c0fb5c Merge main and skip package.json files without a scriptc key 2026-10-25 03:58:49 -05:00
Chris Tate e38f7c329d Speculate floor-mods and products by powers of two as masks and shifts
In a speculative integer loop, `v - Math.floor(v / 2^n) * 2^n` computed
the floor division as an arithmetic shift, multiplied it back by 2^n with
overflow and -0 checks, and subtracted with range checks. For a safe
integer v and n in [0, 52] the result is the floor-mod, which is v's low
n bits: the fast copy now computes it as `v & (2^n - 1)`, which lies in
[0, 2^n) and needs no check. A product with a power of two (a literal or
`Math.pow(2, n)`) becomes a shift, with one test that the other factor's
magnitude is at most (2^53 - 1) >> n; its zero result is +0.
2026-10-25 03:56:48 -05:00
Chris Tate c1f61c05c4 Merge main 2026-10-25 03:55:15 -05:00
Chris Tate e99b31dab1 Decode base64 eight characters at a time (#2197) 2026-10-25 03:45:47 -05:00
Chris Tate 9cb9750f92 Set a trusted build aside while a getter runs outside a visit session
A getter or proxy trap that runs during a trusted build outside any
visit session now runs with the build's state set aside, as inside a
session. A conversion the getter starts, such as a recursive union
that opens and ends its own session, no longer clears the mark that
keeps the rest of the outer build from trusting its earlier match.
2026-10-25 03:34:33 -05:00
Chris Tate 342b770cf2 Keep a Map.get result owned when its key can replace the map's holder
The receiver of a borrowed Map.get is read before its key. When the key's
evaluation replaces the field or local that holds the map
(`this.m.get(this.key())` where key() assigns this.m), the read runs on
the old map, which only the statement still holds, and the borrowed
value outlives it. A borrow now also requires the read itself, receiver
and key included, to preserve reference edges; otherwise the read keeps
its own reference.

Corpus 8181 and the emission test cover a key method that replaces the
field.
2026-10-25 03:25:37 -05:00
Chris Tate 975270cef5 Decode base64 eight characters at a time
Buffer.from(text, "base64" | "base64url") decoded each four-character
group with four lookups and a sign test, then stored its three bytes one
at a time. The decoder now first takes complete groups two at a time:
each character's value comes from a table that holds it already shifted
into place within its group's 24 bits, a single bit test covers all
eight characters, and each group's three bytes go out in one four-byte
store whose last byte the next store overwrites. A group with any other
character (padding, whitespace, an invalid character) takes the
previous path, and the two-group step resumes after it, so line-wrapped
text keeps most of the gain. The destination already holds two spare
bytes, which cover the extra store byte.
2026-10-25 03:02:53 -05:00
Chris Tate 7e20a75c8f Split readline input in one scan and copy ASCII strings into buffers whole (#2192) 2026-10-25 02:59:39 -05:00
Chris Tate 19237373ad Compute dev builds' int32 slot analysis on a forked process (#2195) 2026-10-25 02:46:02 -05:00
Chris Tate 669000de97 Compile large sanitized dev programs in shards (#2194) 2026-10-25 02:42:37 -05:00
Chris Tate 39b461f2fd Compile namespace-style npm packages: guarded declarations and var exports (#2172) 2026-10-25 02:37:06 -05:00
Chris Tate f176e868bf Prove int32 array candidates in one analysis that revises dropped ones
selectInt32Arrays proposed int32 array candidates, analyzed the marked
module, and when a candidate dropped, marked the module again without it and
analyzed the result from scratch, until a round kept every candidate it
marked. On tsc-ts the first analysis drops 6 of 8 candidates, and a second
full analysis confirms the other 2.

The analysis now revises the module as candidates drop: a candidate whose
element stores fail (and that keeps no checked stores) has its functions
replaced by the module's functions marked without it, which call the element
readers again, and the fixpoint goes on over the revised functions. The
selection takes the module of the functions the analysis ended with when
marking the module for the candidates it kept proposes exactly those
candidates, in those functions, and the emitter takes over the analysis with
that module's node index, initializer bindings and dynamic stores. A revision
that changes anything else in a function, such as a call of a function no
other call reaches, ends the analysis, and selection falls back to rounds.

SCRIPTC_INT32_VERIFY=1 also runs the rounds and throws unless the selected
module and every fact of its analysis are the same.
2026-10-25 02:32:44 -05:00
Chris Tate ad26bed418 List line-reader among the benchmark workloads that link the dynamic tier
The workload reads its file through readline over fs.createReadStream.
The stream and readline runtime units define dynamic-tier symbols on
their own: a program that only reads a file with createReadStream links
the same 32, as log-summary and inventory-report do.
2026-10-25 02:29:47 -05:00
Chris Tate 0af32aff5a Compute dev builds' int32 slot analysis on a forked process 2026-10-25 02:15:04 -05:00
Chris Tate 84c8949837 Read optional elements of bindings that hold undefined as undefined (#2193) 2026-10-25 02:11:59 -05:00
Chris Tate 6bd675420b Merge main 2026-10-25 02:06:50 -05:00
Chris Tate 3a832394f9 Fetch only the syntax trees lowering reads (#2191) 2026-10-25 02:00:00 -05:00
Chris Tate 8b0921243f Compile large sanitized dev programs in shards 2026-10-25 01:59:32 -05:00
Chris Tate 1f26462fb7 Keep an unchanged executable without selecting the runtime pack (#2187) 2026-10-25 01:41:58 -05:00
Chris Tate 3ef34770bd Read optional elements of bindings that hold undefined as undefined
`a?.[i]` on a binding whose type admits undefined, such as a
`string[] | undefined` parameter, threw "undefined is not representable
in the target union" when the binding held undefined. Element access
replaced an identifier receiver whose storage has an undefined arm with
a checked narrow to its present arm before the optional chain ran, so
the chain saw a receiver that is never nullish and lowered `?.[` as a
plain element read.

Element access now leaves the receiver to the optional chain when the
access carries `?.`; the chain tests the undefined arm and lowers the
access again inside its guard. This applies to arrays, tuples and
record keys. Property access (`a?.length`, `a?.at(0)`) already lowered
through the chain.

Corpus 8330 matches Node's output.
2026-10-25 01:41:31 -05:00
Chris Tate ec766a5269 Journal the Promise.all tuple and AsyncLocalStorage callback thunks (#2190) 2026-10-25 01:40:17 -05:00
Chris Tate db2cb5c99e Split readline input in one scan and copy ASCII strings into buffers whole
readline split redirected input byte by byte, twice per chunk (once to
find whether the chunk ends a line, once to split it). Before splitting
it copied each chunk out of its Buffer and into a scratch buffer, decoded
it into a new string, and copied the unfinished line and the whole chunk
into a joined buffer. Now:

- one scan per chunk: memchr for a CR once, then LF to LF with memchr
  when the chunk has none, and the LF, CR and CRLF split otherwise;
- the unfinished line joins only the first line of the chunk;
- an ASCII chunk with no partial UTF-8 sequence held from the previous
  one is split where it is, and a chunk that is not shared memory is
  read from its own storage. Lines are still copied into their strings
  before the first is delivered.

Buffer.from(string, 'latin1' | 'ascii') encoded character by character;
an ASCII prefix, all of an ASCII string, is now copied with memcpy.

Line events, their order and the CRLF delay are unchanged. Corpus 8344
covers lines joined across chunks, a line longer than a chunk, empty
lines, mixed line endings and UTF-8 split across chunks. The new
line-reader workload reads a 67 MB log through readline over a file
stream.
2026-10-25 01:35:28 -05:00
Chris Tate 6b27ffbf08 Fetch only the syntax trees lowering reads 2026-10-25 01:25:03 -05:00
Chris Tate 2e052854c5 Merge main 2026-10-25 01:21:07 -05:00
Chris Tate 25d0ad9193 Parse JSON into discriminated unions in one pass (#2189) 2026-10-25 01:16:43 -05:00
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