Commit Graph
6750 Commits
Author SHA1 Message Date
Chris Tate 3e1af48ea3 Convert a loop's string receiver to UTF-16 at once only when the loop can read enough of it (#2212) 2026-10-25 08:17:39 -05:00
Chris Tate b1a6814796 Prove typed-array loop bounds in worker programs when a view has shared storage
A typed-array loop version in a worker program tested its bounds and
its views' ownership together: every view had to have no shared
storage. A loop over views of a SharedArrayBuffer always failed that
guard and ran the checked copy, testing every element's index.

The guard now tests the bounds first and the ownership after. When the
bounds hold and every view is unshared, the loop runs the owned copy as
before. When a view is shared, it runs a new third copy whose accesses
skip their bounds checks and keep the shared-or-local path, without
alias scopes. Versions with a UTF-16 units copy, and programs without
workers, are unchanged.

Corpus 8350 runs such loops over shared, unshared and mixed views, to
counts within and past the views and over subarrays, with a worker
writing through shared views. The emission test checks the split guard
and the shared copy's accesses.
2026-10-25 08:06:24 -05:00
Chris Tate 4d27b1a000 Call replace callbacks with unannotated parameters directly (#2207) 2026-10-25 08:05:35 -05:00
Chris Tate b28af56ba8 Skip the weak-key table when freeing objects that were never keys
Once a program creates a weak collection, every object freed afterwards
called into the weak-key table to remove entries keyed by it, although
almost none are keys: zod's validation freed 16.6 million objects against
23 keys. A per-thread bit filter indexed by the object's address now marks
every address that entered the table (cleared when the table empties),
and the free paths test it inline, so an object whose bit is clear makes
no call.
2026-10-25 07:53:02 -05:00
Chris Tate 2823840134 Miss non-object receivers early in the property probes and inline the entry check
Since the probes began caching absent properties (#2186), an untyped
property read that reaches the load or get probe costs more than before:

- Receivers that are not ordinary objects, such as class instances, have
  no shape and always miss, but they set up the probe's frame first. The
  exported probes now return the miss for them before calling the probe
  body, which stays out of line.
- Each matching entry was answered through an out-of-line call
  (scr_js_entry_answer). It is inlined into the probe again.

dynamic-properties executed 3.1% more instructions than before #2186; it
now executes 0.4% fewer.
2026-10-25 07:49:06 -05:00
Chris Tate 390b404251 Keep a union loop binding in the debug-variables fixture
The fixture's loop iterated `[6, 7] as (number | undefined)[]` so that
its binding kept the union layout the LLDB check reads through
`item->tag` and `item->slot.arm0`. Since (T | undefined)[] stores T
elements, an array that never holds undefined gives a `number` binding,
and the check failed with "item is not a pointer".

The loop now iterates `[6, 7, undefined]`, whose binding needs the
union, and the check also reads the undefined arm's tag.
2026-10-25 07:46:24 -05:00
Chris Tate fa4a3b21ea Merge base branch 2026-10-25 07:43:19 -05:00
Chris Tate 63a99cc4d9 Convert a loop's string receiver to UTF-16 at once only when the loop can read enough of it
A versioned loop over a non-ASCII string passes the range of indexes it
reads: a counted loop's start and limit, or a while loop's counter and
bound. The runtime converts the string at once when that range has at
least one unit per 16 bytes of it, and otherwise counts the loop as one
indexed read, so a loop over a line of a long source text no longer
keeps a UTF-16 copy of all of it. The publish call is weighted as
unlikely, since loops re-entered over one string skip it.
2026-10-25 07:37:50 -05:00
Chris Tate fce38bcbcf Merge base branch and fix 2026-10-25 07:35:31 -05:00
Chris Tate 5247d2dfdd List bench/regex-replacers among the dynamic-tier workloads
Its replace callbacks receive checked-dynamic values where the lib types
their parameters any: captures a match can leave unset, and results the
checker types any.
2026-10-25 07:28:48 -05:00
Chris Tate 8752390f65 Merge base branch and fix 2026-10-25 07:27:31 -05:00
Chris Tate 8a08f65ebf Leave timing and cache placement variables out of the frontend cache key (#2211) 2026-10-25 07:25:47 -05:00
Chris Tate 4f07fbd66f Merge base branch and fix 2026-10-25 07:22:16 -05:00
Chris Tate 5cfcc8fb51 Merge base branch and fix 2026-10-25 07:18:13 -05:00
Chris Tate 4ba4e68695 Emit dev builds' function bodies on the emit workers (#2206) 2026-10-25 07:14:09 -05:00
Chris Tate 7d17f2a758 Merge base branch and fix 2026-10-25 07:13:36 -05:00
Chris Tate 89f8b39e5b Merge base branch and fix 2026-10-25 07:08:45 -05:00
Chris Tate b95b07c537 Have tsc-ts read its library files from disk when it can (#2208) 2026-10-25 07:06:21 -05:00
Chris Tate 6157fa93ab Merge base branch and fix 2026-10-25 07:02:48 -05:00
Chris Tate 5fc6fea249 Merge base branch 2026-10-25 06:57:33 -05:00
Chris Tate d93a02d89e Build the large compiler tables on first use (#2202) 2026-10-25 06:55:23 -05:00
Chris Tate 365d14ee1c Merge base branch 2026-10-25 06:52:17 -05:00
Chris Tate c80945ec21 Leave timing and cache placement variables out of the frontend cache key
The native frontend cache key included every SCRIPTC_* variable, so
turning SCRIPTC_TIMING on or off, or changing SCRIPTC_CACHE_DIR,
SCRIPTC_CACHE_MAX_MB, SCRIPTC_EMIT_WORKERS or
SCRIPTC_EMIT_WORKER_FUNCTIONS, reran the frontend on an unchanged
program. The frontend's output does not depend on these variables, so
the key now leaves them out. Every other SCRIPTC_* variable still keys
the entry.
2026-10-25 06:50:19 -05:00
Chris Tate c2e6c962e8 Build the sanitized three.js nodes fixture in its own CI job
On the san 1/20 shard the sanitized nodes build shares the runner with the
corpus's other two workers and the side queue. It took 944 s and 860 s on
#2194's runs and passed 1200 s on main's 7fa88083, failing the test timeout.

The Linux san corpus now skips that one case
(SCRIPTC_THREE_SANITIZED_NODES_JOB), and a three_nodes_san job runs it alone
with the corpus's LeakSanitizer settings. The required test check waits for
the new job. Every other three.js case, the plain nodes build and the macOS
suites are unchanged.
2026-10-25 06:49:46 -05:00
Chris Tate d8f2874860 Keep replace callback parameters checked-dynamic where parseInt or parseFloat expects another type
parseFloat and the text of parseInt take a string, and the radix of parseInt
takes a number. An unannotated offset passed as their text, or a capture passed
as the radix, is no longer bound to its value's type; it stays checked-dynamic,
so the call compiles as it did before the binding.
2026-10-25 06:42:25 -05:00
Chris Tate 928aad993d Add island packages to the judged set without a union spread
The self-hosted compiler refuses spreading 'number[] | Set<string>' into
a string[] literal (SC1090), so pnpm build:native-compiler failed on
loadFrontend. The judged set now starts from the library packages and
adds each island package in a loop.
2026-10-25 06:40:14 -05:00
Chris Tate b7915b92a7 Merge main 2026-10-25 06:38:44 -05:00
Chris Tate c8b40bd73e Move small number-key maps to the direct index from the second key (#2204) 2026-10-25 06:34:30 -05:00
Chris Tate 24ef53ac23 Have tsc-ts read its library files from disk when it can
Every file tsc-ts reads goes through scriptc's file system callbacks,
including the ~80 files of tsc-ts's own default library directory: one
round trip from the checker to the compiler and back for each. Those
files belong to the installed tsc-ts (the build caches key on its
executable's identity), and no build serves them differently.

When tsc-ts takes --default-libraries-from-disk, the native compiler
passes it to the checker it starts and to the shared checker server, so
tsc-ts reads those files itself; every other path keeps the callbacks.
Released tsc-ts versions reject the flag, so checker-features.ts asks
each tsc-ts executable once (tsc-ts --api --default-libraries-from-disk
with an empty input exits with 0 only when it knows the flag) and keeps
the answer in the native cache, keyed by the executable's identity. With
a tsc-ts that does not know the flag, builds run as before.

Edit builds of small programs through the shared checker server are
about 18 ms faster, and 6-9 ms faster without it.
2026-10-25 06:32:18 -05:00
Chris Tate ceb29078dc Skip comments when renaming a range's debug ids 2026-10-25 06:31:51 -05:00
Chris Tate 1a0d58d1eb Merge main and move to ABI 45 2026-10-25 06:31:07 -05:00
Chris Tate 3c4df3d9e4 Fill Buffers with multi-byte values, swap byte order and decode utf16le a word at a time (#2205) 2026-10-25 06:30:20 -05:00
Chris Tate b8403b7094 Let LLVM merge the comparisons of a three-way string comparator (#1971) 2026-10-25 06:27:31 -05:00
Chris Tate 627ebd86a5 Call replace callbacks with unannotated parameters directly
TypeScript types the parameters of an inline replace callback after the
match `any`. An unannotated parameter that is never reassigned, whose
every read means the same for a string or number as for `any`, and that
receives a capture every match sets, the offset or the subject now takes
that value's type. String-search callbacks get the same treatment for the
offset and the subject.

scr_regex_replace_fn calls the closure directly in more cases:
- checked-dynamic parameters, which receive the boxed value Node passes at
  their position (a capture or undefined, the offset, the subject, the
  groups object, then undefined);
- `string | undefined` capture parameters, which receive NULL when the
  match leaves the capture unset;
- a checked-dynamic result, converted with ToString;
- up to six parameters.

Callbacks of the earlier typed form compile to the same call, with a
kinds argument of 0, and run the same per-match work.
2026-10-25 06:16:32 -05:00
Chris Tate 872c12e247 Emit dev builds' function bodies on the emit workers 2026-10-25 05:59:55 -05:00
Chris Tate cac5a19fc0 Fill Buffers with multi-byte values, swap byte order and decode utf16le a word at a time
- fill with a string or Buffer value longer than one byte: writes the
  value once, then doubles the filled prefix with memcpy, as Node does.
  The value is copied with memmove, so a value that is a view of the
  buffer being filled gives Node's result; the previous per-byte loop
  read such a view while overwriting it. The loop also computed a
  division per byte.
- swap16, swap32 and swap64: one load, byte swap and store per group,
  through a local data pointer.
- toString("utf16le" | "ucs2"): four units below 0x80 copy as four bytes,
  and other units outside the surrogate range are written inline (a
  three-byte sequence as one four-byte store). Surrogates keep the
  previous path. The result records its UTF-16 length (one unit per
  input unit), or is marked ASCII, so `.length` does not walk it.
2026-10-25 05:58:42 -05:00
Chris Tate 4eb3a16a0b Match the Promise.all tuple adapter by its key-derived name in the chunked-emission test
The adapter is named from its key since the helpers moved to
contentSymbol, so the test no longer finds the counter name
sc_all_tuple_0. It now matches any sc_all_tuple_ name; the serial and
chunked emissions are still compared in full.
2026-10-25 05:52:34 -05:00
Chris Tate b3c98139e4 Move small number-key maps to the direct index from the second key
A number-keyed Map or Set with keys the direct index allows now moves to it
at its second entry rather than its fifth, so a map over a few small
integer keys (a histogram over 0..3) reads through the inline direct index
instead of the out-of-line linear scan.
2026-10-25 05:48:40 -05:00
Chris Tate edae637526 Support named properties and Symbol.species on typed arrays held as untyped values (#2068) 2026-10-25 05:48:27 -05:00
Chris Tate a7c4c01bc8 Merge main 2026-10-25 05:43:48 -05:00
Chris Tate 1e9afec1d4 Reclaim completed socket writes before growing the pending-write queue
The queue of writes with buffered bytes dropped completed entries only
when the buffer emptied. On a socket whose buffer stays nonempty while
old writes complete and new ones arrive, it kept every write since the
buffer last emptied. Appending to a full queue now first moves the
pending entries to the front, so its size follows the writes still
pending.
2026-10-25 05:42:02 -05:00
Chris Tate 0e92be2bf7 Poll worker cancellation between blocks of typed-array loop iterations
In a worker program, every loop without a pending test on each iteration
decremented a cancellation budget in its body and polled the exception
flag every 64 iterations. The poll's early return made each loop an
early-exit loop, which LLVM's loop vectorizer does not vectorize.

A counted for loop (integer counter, invariant limit), or a loop to a
typed array's length, whose body reads or writes a typed-array element
outside nested loops and closures, now runs as blocks of at most 64
iterations: the outer header tests the counter against the limit, the
inner loop runs to the nearer of the limit and 64 steps on, and the poll
follows each block. A typed array's length is fixed, so each block takes
it as its limit. The loop polls as often as the budget did, and the
inner loop's only exit besides break and return is its bound. The block
bound lives in one slot shared by the loop's speculative copies, which
resume iterations in each other's bodies. Other loops keep the budget,
which costs less in loops of a few iterations.

An elementwise Float64Array loop in a worker-enabled program vectorizes
again: 949 ms to 691 ms. A byte-classifying loop over a 100M-byte
Uint8Array takes 13 ms instead of 141 ms.

New corpus program: native-worker-terminate-counted-loops.ts.
2026-10-25 05:41:44 -05:00
Chris Tate cfa9f81cfd Share storage between T[] and (T | undefined)[] (#2080) 2026-10-25 05:40:56 -05:00
Chris Tate 7fa8808361 Skip NULL children and key-cache walks when disposing dynamic values (#2201) 2026-10-25 05:37:06 -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 303bfcd922 Merge main 2026-10-25 05:25:41 -05:00
Chris Tate 5da6d76085 Run lone array callbacks as loops with their bodies inlined (#1997) 2026-10-25 05:24:07 -05:00
Chris Tate 9abf35f6a2 Build the large compiler tables on first use
The native compiler ran four large module initializers at every startup:
the builtin call signatures (LIB_FN_SIGS), the Node 24 fetch
compatibility profile, and the builtin module function and ambient
surface tables. Together their init functions are about 1.3 MB of code,
and --version and warm cache-hit builds paged them in and built the
tables without reading them.

Each table is now built by a function on its first call and kept in a
module variable:

- libFnSignatures() replaces LIB_FN_SIGS
- node24FetchCompatProfile() and staticFetchMembers() replace
  NODE24_FETCH_COMPAT_PROFILE and the STATIC_* member sets
- builtinModuleFns() and ambientSurfaceFns() replace BUILTIN_MODULE_FNS
  and AMBIENT_SURFACE_FNS

The @scriptc/compiler entry point keeps LIB_FN_SIGS and
NODE24_FETCH_COMPAT_PROFILE as deprecated constants built from the new
functions when the entry module loads. The native compiler does not load
that module.

The surface manifest's coverage notes read the fetch target through the
profile function, so building the manifest module no longer builds the
profile. The table contents are unchanged.
2026-10-25 05:23:35 -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