Commit Graph
4696 Commits
Author SHA1 Message Date
Chris Tate 32752f768e Merge main 2026-10-24 12:19:43 -05:00
Chris Tate aa4e046046 Emit function bodies on up to eight workers (#2118) 2026-10-24 12:04:33 -05:00
Chris Tate fe394f8c30 Keep an unchanged executable on its first Linux rebuild (#2112) 2026-10-24 11:57:52 -05:00
Chris Tate 40f0b6d9e0 Refresh the native compiler's optimization profile (#2120) 2026-10-24 11:51:12 -05:00
Chris Tate 557daa3e9d Refresh the native compiler's optimization profile
The committed profile was collected before about 200 commits that changed
12,800 lines of the compiler. Functions whose control flow changed since are
optimized as without a profile, so its gain had mostly gone: a cold tsc-ts
speed build was 2.7% faster with it than with no profile, against 5% to 6%
with a fresh one.

Regenerated with scripts/refresh-cli-profile.mjs on this commit.
2026-10-24 11:47:19 -05:00
Chris Tate c09385c7ab Emit function bodies on up to eight workers
The native compiler forked four worker processes to emit the function
bodies of a large module. It now forks one per available core, up to
eight. SCRIPTC_EMIT_WORKERS still sets the count. The output is the same
text at any count.
2026-10-24 11:29:56 -05:00
Chris Tate 022428ead6 Write the first stdin chunk after both readers start in the 'readable' EOF test (#2109) 2026-10-24 10:30:31 -05:00
Chris Tate 205f099957 Keep an unchanged executable on its first Linux rebuild
On Linux the executable cache stored an executable only from a key's second
link on, so the first unchanged rebuild after a build linked again, and only
the build after that restored the executable.

- A key's first link on Linux is traced, in the same verbose link. Instead
  of storing the executable, the cache records the output's file identity
  together with the link's inputs once the output is in place.
- keep() answers an unchanged rebuild whose output still has that identity
  and whose inputs are unchanged. The build then neither links nor copies:
  the output stays as it is.
- A rebuild whose output changed or is missing links again; from that second
  link on, the executable is stored as before and restored where needed.
- The cache records the output after every Linux build that places one, so
  the next unchanged rebuild keeps it.

darwin keeps its existing behavior.
2026-10-24 10:26:38 -05:00
Chris Tate 7406ec63f4 Read the node index in more LLVM emission analyses (#2108) 2026-10-24 10:19:42 -05:00
Chris Tate 72eb9318bf Analyze int32 slots in queue order, after fixing an order-dependent narrowing (#2107) 2026-10-24 10:02:34 -05:00
Chris Tate 38730910c8 Write the first stdin chunk after both readers start in the 'readable' EOF case
A reader that starts after the second chunk arrives reads both chunks in
one 'readable' event. On loaded test workers Node started that late while
the compiled program did not, so the two outputs differed. The first chunk
now waits 500 ms.
2026-10-24 09:56:15 -05:00
Chris Tate aa1c26845c Cache untyped property access in programs that declare classes (reland) (#2102) 2026-10-24 09:55:54 -05:00
Chris Tate 648e8e05c6 Read the node index in more LLVM emission analyses
Several analyses the emitter runs per function or per module walked whole
bodies to collect facts from a few node kinds. They now read those kinds
from the module's node index (ir/node-index.ts), which records every node
once in walk order, and the output is unchanged.

- numberGlobalWriters, readsLoopMetrics, closure creation sites
  (LazyCaptures) and the ambient receiver readers visit only the kinds
  they inspect.
- findLoopArrayBorrows visits only loop statements, and findIntegerViews
  returns at once for a function without a loop, where it wants no view.
- foldConstantGlobals records which candidates each function reads during
  its write walk instead of walking every function again.
2026-10-24 09:50:02 -05:00
Chris Tate 580e8ac723 Analyze int32 slots in queue order, after fixing an order-dependent narrowing 2026-10-24 09:34:59 -05:00
Chris Tate 48cbf3a89e Verify runtime pack artifacts once per file identity, and store pack selections
Every native exe build that the executable cache does not restore hashed
each selected runtime artifact before staging it, and every exe build
parsed and validated the whole runtime-pack.json manifest to select its
artifacts.

- Staging still copies each artifact. It skips the SHA-256 of an artifact
  when the file's identity (device, inode, size, mtime, ctime) is the one
  recorded the last time that path matched the same size and digest, and
  the identity does not change while the file is read. Other artifacts are
  hashed as before, and their identities are recorded after they match.
- A pack's selection for a target, compiler version, feature set, flavor
  and mode is stored, keyed by the file identities of package.json and
  runtime-pack.json. A stored selection skips parsing and validating the
  manifest; the manifest is parsed only when a caller needs it, through
  runtimePackManifest.
- The native compiler keeps both in its cache, in the runtime-pack family,
  which prune covers.
2026-10-24 09:20:05 -05:00
Chris Tate e579f62556 Inline the array release fast path in speed builds (#2104) 2026-10-24 09:18:35 -05:00
Chris Tate 15e8c0cfac Place each source file's functions in the same partitions (#2083) 2026-10-24 09:08:38 -05:00
Chris Tate 905a290817 Copy typed array sets of the same element kind on shared memory as bytes (#2094) 2026-10-24 09:07:57 -05:00
Chris Tate 6aec284766 Inline the array release fast path under --optimization=speed
Speed builds now release arrays through the inline fast path the other
RC families use: an array that stays alive and has no cycle header is
decremented in place, and only the last release or a headered array
calls scr_arr_release.
2026-10-24 08:50:22 -05:00
Chris Tate 0dab283512 Cut repeated work in LLVM module emission (#2097) 2026-10-24 08:41:40 -05:00
Chris Tate 427e383b5e Cache untyped property access in programs that declare classes (reland)
Reapplies #2017, which #2038 reverted after effect's observability-otlp
fixture crashed in plain builds. The crash was the candidate-buffer
corruption that the previous commit fixes: the cached property sites
change when the collector runs, and with enough environment variables a
pass buffered a string box that was later pooled while still buffered.
2026-10-24 08:22:30 -05:00
Chris Tate 6a8c7056ca Run the int32 slot analysis once per module the emitter emits (#2086) 2026-10-24 08:18:58 -05:00
Chris Tate cc27555171 Dispatch untyped calls on plain arrays without the method ladder (#2085) 2026-10-24 08:18:16 -05:00
Chris Tate 21570a963c Cut repeated work in LLVM module emission
Emitting the LLVM text of large programs spent seconds rebuilding the same
sets, rescanning the finished text and walking every function for passes
that change a few of them. The output is unchanged.

- computeMayThrow builds the set of temporal-dead-zone globals once per
  module instead of copying it into a new set for every function.
- limitInlineReleases finds the release sites of every family in one walk
  over the module text, and rewrites only the parts that hold a function
  over its limit.
- The mentioned-function and node:test checks of parking analysis scan the
  text with indexOf instead of global regular expressions.
- resolveSelfCaptures rewrites only the functions it changes,
  specializeCallbackCalls appends to its write lists instead of copying
  them and counts nodes in a walk it already makes, and CaptureFlow reuses
  one callback for nested statements.
2026-10-24 07:07:20 -05:00
Chris Tate 6a4a1bdec9 Collect the supplied files' global names only for programs with scripts (#2092) 2026-10-24 06:46:07 -05:00
Chris Tate 38c30d2e91 Merge main 2026-10-24 06:45:47 -05:00
Chris Tate fe3f52d14c Merge main 2026-10-24 06:44:55 -05:00
Chris Tate b17268ff74 Take number and string boxes out of the candidate buffers before pooling them (#2091) 2026-10-24 06:30:38 -05:00
Chris Tate 314024a8c7 Copy typed array sets of the same element kind on shared memory as bytes
TypedArray.prototype.set with a SharedArrayBuffer view as its source or
target converted every element through a double, even when both arrays
had the same element kind. That cost about 5 ns per byte, and it changed
NaN payloads: a Float32Array signaling NaN came back quieted (7fa00001
became 7fe00001), where Node copies the bytes.

The same element kind now copies the source's bytes with the relaxed
shared stores. A source that overlaps the target range, a view of the
same buffer in a program without workers, is copied out first. Different
element kinds still convert each element.
2026-10-24 06:29:47 -05:00
Chris Tate 486e92a628 Collect the supplied files' global names only for programs with scripts
Reusing the stored diagnostics of the declaration files scriptc supplies
(lib-diagnostics.ts) needs those files' global names to tell whether a
program's scripts could change what checking them reports. The first build
that stored an answer collected them by reading every supplied file's
syntax tree, which a program made only of modules does not otherwise read,
and that build is every cold build. A program of modules cannot collide
with those names, so its answer is now stored without them, and the first
program with scripts that uses the answer collects them and stores them
with it. The entry schema is scriptc.lib-diagnostics.v2.

A cold dev build of the 17-module build benchmark takes 676 ms instead of
689 ms, and of the log-summary benchmark 375 ms instead of 396 ms, with
peak RSS 5.7 MiB and 4.5 MiB lower.
2026-10-24 06:11:16 -05:00
Chris Tate d66564ca69 Keep class methods native when element writes store data under their names (#2026) 2026-10-24 05:59:00 -05:00
Chris Tate 8a02468073 Take number and string boxes out of the candidate buffers before pooling them
scr_dyn_release returned the last reference of a number or string box to
the free list without scr_cyc_dead, because the release paths never buffer
those boxes. A collector pass can: capture boxes trace their payload, and
scr_xg_release, which settles the cross-generation edges a restricted pass
skipped, calls scr_cyc_on_release on the target whatever its kind. The
pooled block then stayed in its candidate buffer, and the next box that
reused it carried the stale buffer position. Its death swapped another
object into that slot, which corrupted the buffers and crashed a later
scr_cyc_on_dead.

The fast path now takes a buffered box out of its buffer first. The
sanitized runtime does not pool boxes, so only plain builds were affected.
2026-10-24 05:57:26 -05:00
Chris Tate 0a9cfa5efd Merge main 2026-10-24 05:25:59 -05:00
Chris Tate 7278c3c467 Read a union's discriminant once in checked casts (#2073) 2026-10-24 04:51:36 -05:00
Chris Tate c652e418f6 Read the discriminant again after an arm's check fails
An arm's field check can run a getter that changes the discriminant. The
checked cast now reads the discriminant again after each failed arm;
when its case changed, the tests continue arm by arm after the failed
arm, so the cast picks the arm the arm-by-arm tests pick.
2026-10-24 04:10:52 -05:00
Chris Tate 64f3bb8fbe Give the capped Linux test workers the heap they had before the cap (#2084) 2026-10-24 03:49:00 -05:00
Chris Tate 0f90e07603 Process non-ASCII URL hosts with UTS #46 as Node does (#2088) 2026-10-24 02:48:24 -05:00
Chris Tate 66726267fc Share untyped module bindings that combine with strings and numbers (#2087) 2026-10-24 02:35:45 -05:00
Chris Tate 0e0bedca01 Forward pointer-union wraps of call arguments to the callee's parameter proof (#2048) 2026-10-24 01:56:42 -05:00
Chris Tate 937db54656 Raise the capped Linux test scope to 15 GB of RAM
The sanitized self-hosting native-frontend build needs about 15 GB by
itself: its clang compile of the whole frontend under ASan peaks at
10.5 GB beside a 3.8 GB compile in Node. In the 14 GB scope, with the
shard's other work running beside it, the kernel killed that clang
intermittently, so SC5004 reported clang's warnings and no error (SAN
16/20 and 19/20). The scope now caps RAM at 15 GB, still below the
runner's 16 GB, and may still swap past it.
2026-10-24 01:42:23 -05:00
Chris Tate 9496e42968 Keep a pointer-wrapped argument on the heap when its callee keeps the narrowed payload
A union parameter's proof covered the union, not its payload: narrowing it
and storing, returning or pushing the result, a narrowed nullish read, and a
store into a nullable-pointer field all counted as projections. That holds
for a boxed union, whose payload is a separate object. A record passed
through a nullable-pointer union is that payload, so forwarding it to such a
callee let a record in its caller's frame escape the frame.

A pointer-wrapped argument now needs a second fact from the callee's union
parameter: its payload stays in place too. The payload is in place when it
is only tested, narrowed for a field read (directly or through the checked
narrowing a field read of a `T | undefined` parameter compiles to), or passed
on to a parameter that is proved in turn. The facts that other arguments use
are unchanged.

Corpus 7663 has callees that push (directly and through a non-null
assertion), default, store in a global record field or a class field, or
pass on the narrowed record, and one that only reads it.
2026-10-24 01:27:44 -05:00
Chris Tate 1e90263517 Support Symbol.toStringTag class fields and getters (#2037) 2026-10-24 01:21:32 -05:00
Chris Tate 20339d9c27 Process non-ASCII URL hosts with UTS #46 as Node does 2026-10-24 01:13:00 -05:00
Chris Tate 70c2b8246d Share untyped module bindings that combine with strings and numbers
A JavaScript module binding whose initializer joins an untyped value with
a string, number or boolean through `||`, `&&`, `??` or `?:`
(`export const APP_NAME = pkg.appConfig?.name || "app"`) stayed a local of
the module's initializer, so reads from other modules and functions
failed with "a binding form with no lowering". Such initializers now
register the binding in checked-dynamic module storage, like the untyped
initializers they combine.

pi-coding-agent's config.js declares APP_NAME, APP_TITLE and isBundledNode
this way.

Refs #1777.
2026-10-24 01:03:18 -05:00
Chris Tate 373ebd9706 Initialize import cycles that re-export a member with export \* (#2081) 2026-10-24 00:55:34 -05:00
Chris Tate f713cd631c Run the int32 slot analysis once per module the emitter emits
Release emission selects int32 arrays by analyzing each module
selectInt32Arrays marks (provenInt32Arrays) until every candidate it
marks is kept, then constructed LlEmitter on the returned module, whose
constructor built a node index, found the initializer bindings and the
dynamic field stores, and ran analyzeInt32Slots on that same module
again. On tsc-ts the constructor's call alone took about 3 s.

Each round now analyzes its module with the inputs the constructor
gives the analysis (one node index, the initializer bindings and the
dynamic field stores read through it), and the emitter of the module
selectInt32Arrays returns takes over the last round's analysis and
inputs instead of computing them again. The rounds also read the node
index instead of walking the bodies. The emitted LLVM is unchanged.
2026-10-24 00:52:48 -05:00
Chris Tate 98e3ff52b3 Reuse linked executables on Linux (#2077) 2026-10-24 00:50:58 -05:00
Chris Tate 2764288376 Dispatch untyped calls on plain arrays without the method ladder
An untyped call of an array method went through the whole dispatch ladder
on every call: the receiver-kind tests for handles, proxies, typed refs,
objects, functions and primitives, then the array section's prologue
(the hole scan and the frozen, sealed and extensibility tables) before
the method itself.

A dense array without holes, named properties or a prototype of its
own, whose length and elements are writable, now runs push, pop, shift,
at, includes and indexOf directly from the call site's cached method id.
An argument that could run user code (a non-number index, a fromIndex)
still takes the ladder, as does every other receiver. A statement-position
push calls an entry that does not box the new length.

Calls on arrays that the direct path refuses now also follow Node:
- an array's own method, or one on its custom prototype chain
  (Object.setPrototypeOf), runs instead of the builtin;
- push on a sealed or non-extensible array throws "Cannot add property
  N, object is not extensible", and pop or shift on a sealed array
  "Cannot delete property 'N' of [object Array]".
2026-10-24 00:43:56 -05:00
Chris Tate 68a9dea3fa Give the capped Linux test workers the heap they had before the cap 2026-10-24 00:42:45 -05:00
Chris Tate 6715609695 Place each source file's functions in the same partitions
Partitions were filled by a hash of each function's name, so most calls
crossed partitions and the thin link imported their small callees into
nearly every partition. The emitter now writes a "; scriptc-group" line
before the functions of each source file, and the text partitioner places
a group's functions together: a group larger than the mean partition, or
than 1 MiB, is divided into a power of two of chunks by name, and chunks
go, largest first, to the lighter of two partitions chosen by hash. The
1 MiB bound keeps the code a partition optimizes together, and the
inlining within it, from growing when a program has fewer partitions.
Modules with fewer than 32 partitions, and functions without a group,
keep their name-hash placement: with few partitions per core, the balance
of the largest partitions decides the build time more than the imports.
The placement still depends only on the module text and the partition
count.
2026-10-24 00:34:25 -05:00