Commit Graph
3837 Commits
Author SHA1 Message Date
Chris Tate d99f355182 Give the native layout and metadata emission test 600 seconds (#2368) 2026-10-26 21:36:39 -05:00
Chris Tate 87565abc16 Merge main 2026-10-26 20:57:59 -05:00
Chris Tate 9d0ca5f641 Give the native layout and metadata emission test 600 seconds
The sanitized run of "native LLVM layout and metadata emission (llvm)"
takes 170 to 300 seconds in its CI shard beside the shard's other tests,
against the suite's 300-second test timeout. On a quiet 8-vCPU machine it
takes 130 to 160 seconds.
2026-10-26 20:50:23 -05:00
Chris Tate cd606e96ff Key baseline cache entries by their program's inputs
A recorded baseline entry now holds a hash of its program's inputs: the
entry file and its relative imports (paths and contents) and the
optimization level. --baseline-cache uses an entry only when the program
still hashes the same, so a program or an import that changed since the
record builds its baseline, instead of a recorded build error or artifact
hash of the old sources settling the comparison as both-fail or identical.
Records take schema 2; older records are ignored.
2026-10-26 19:53:49 -05:00
Malte UblandClaude Opus 5.5 d0047c813f Shard the zero-cost gate and reuse baseline work across runs
The full gate takes 30 to 75 minutes on one Sandbox, which made it the
longest step of a validation round. It can now be split and merged:

- --shard=i/n splits the bench and corpus programs (the self-hosted
  compiler runs in shard 1, or alone with --set=self), and
  scripts/zero-cost-merge.mjs merges the shards' --json reports into one
  verdict, recomputing the compile-time intervals over every shard.
- --record-baseline-cache records the baseline's artifact hashes once;
  --baseline-cache skips the baseline build of every program whose
  candidate artifacts hash the same. The record is tied to the built
  baseline checkout, toolchain and paths.
- The candidate-source self build overlaps the comparison builds on
  hosts with room for it.

pnpm test:sandbox can reuse a prepared snapshot's LLVM helper and runtime
pack (SCRIPTC_SANDBOX_NATIVE_REFERENCE) when their inputs are identical,
and retries rate-limited Sandbox creation with backoff.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 17:43:44 -07:00
Chris Tate 2c5b3b48d4 Version loops around nested loops only for accesses inside them (#2364) 2026-10-26 19:35:06 -05:00
Chris Tate ad7846bcfa Search ASCII strings from a start index without index conversion (#2362) 2026-10-26 19:18:50 -05:00
Chris Tate ca1a6b5bed Version loops around nested loops only for accesses inside them
A typed-array loop version runs a checked copy and one or two fast copies
of the loop, and each copy repeats every nested loop together with the
nested loop's own version. A loop whose guard proves only accesses outside
its nested loops, which run once per pass, now runs as written when a
nested loop accesses typed arrays; the nested loops still version their own
accesses. The loop versions as before when the guard proves the bounds of
an access inside a nested loop, or, in a worker program, the ownership of
an access inside a nested while loop, which never versions for ownership
alone.

Spectral-norm's paired row sums no longer copy their column loop for the
row loop's two stores. The worker program's au and atu functions are
102 KB of LLVM text instead of 236 KB each, and the program 519 KB instead
of 786 KB.

Corpus typed-array-nested-loop-versions covers row loops that store past
the end or at negative, fractional and NaN rows, views the nested loop
writes, and a nested while loop.
2026-10-26 19:05:12 -05:00
Chris Tate 7be9803076 Build node:zlib on the zlib Node bundles
Re-lands #2355, reverted in #2358. #2361 fixed the CI failure it exposed.
2026-10-26 18:40:07 -05:00
Chris Tate 4dddb94373 Revert "Build node:zlib on the zlib Node bundles" (#2358) 2026-10-26 18:38:38 -05:00
Chris Tate 80a5d9a820 Read missing elements through non-null asserted operands as Node does (#2331) 2026-10-26 18:35:37 -05:00
Chris Tate 1245b18469 Search ASCII strings from a start index without index conversion
String.prototype.indexOf took its byte-search fast path on a proven-ASCII
receiver only when the start index was not positive. A positive start went
through the general path: ToIntegerOrInfinity, the surrogate-needle check,
and UTF-16 to byte conversions in both directions, although byte offsets
are UTF-16 indexes in an ASCII string. The fast path now covers every start
index: a positive start truncates and clamps to the length, where only the
empty needle is found, and the search runs from that byte offset.
2026-10-26 18:22:22 -05:00
Chris Tate 8464f2a7c5 Check the native stack guard in one function per call cycle (#2360) 2026-10-26 18:20:07 -05:00
Chris Tate 19b0b40486 Address review feedback 2026-10-26 18:01:27 -05:00
Chris Tate c75c95d864 Check the native stack guard in one function per call cycle
Every unbounded recursion repeats some cycle of the call graph, so a
stack check in one function of each cycle bounds it. stackCheckedFunctions
now picks a feedback vertex set of the stack-check graph greedily (the
function with the most caller-callee pairs first) instead of checking
every function on a cycle, and adds a check where a run of unchecked cycle
functions would exceed 32 calls, so the stack used between two checks
stays of the same order as before.

Worker entry polls follow: a synchronous function on a cycle that may
throw needs no entry poll, since a descent that never returns ends at a
checked function of its cycle. NoThrow contracts keep the model in which
every function on a cycle checks, a superset that does not depend on the
choice.
2026-10-26 17:47:58 -05:00
Chris Tate 7e225c5d87 Merge main 2026-10-26 17:33:06 -05:00
Chris Tate a82a0dda93 Revert "Build node:zlib on the zlib Node bundles"
This reverts #2355. The sanitized three.js nodes CI job has failed on every main commit since it landed.
2026-10-26 17:05:37 -05:00
Chris Tate 3c83fcb89c Allocate object groups that die together in arenas (#2348) 2026-10-26 16:54:31 -05:00
Chris Tate 2d557bc87b Test a scalar optional left of ?? in JavaScript on the typed path (#2345) 2026-10-26 16:54:11 -05:00
Chris Tate 1b2e591a02 Keep the typed ?? path to literal defaults 2026-10-26 16:18:58 -05:00
Chris Tate 9f47d74e21 Poll the loops of a nest on every iteration in ELF worker executables (#2351) 2026-10-26 16:04:06 -05:00
Chris Tate 4dd32da391 Lower discarded logical expressions over void conditionals as branches (#2356) 2026-10-26 15:56:00 -05:00
Chris Tate bfd3cf2daf Build node:zlib on the zlib Node bundles (#2355) 2026-10-26 15:48:37 -05:00
Chris Tate 6d8f0558ff Read int32-slot arrays at their width in integer clones 2026-10-26 15:45:29 -05:00
Chris TateandMathurAditya724 3417a5adc4 Lower a discarded logical over a void conditional as a branch
`skip || (flag ? warnA() : warnB())`, with void calls in both arms, as
React's development warnings write it, failed with SC9001 "ternary must
not be void": the statement lowered the logical as a value, and a
conditional of void calls has none. A logical whose result is discarded
now lowers as `if (!skip) ...` (or `if (skip) ...` for `&&`) when its
right operand needs no value: its type maps to void, or it is a logical
or comma expression whose last operand needs none. This holds in
statements and in a for loop's initializer and update, and for a
generic callback's conditional whose type parameter maps to void.

Closes #2322

Co-authored-by: MathurAditya724 <57684218+MathurAditya724@users.noreply.github.com>
2026-10-26 15:25:52 -05:00
Chris Tate 133551d76e Build node:zlib on the zlib Node bundles
The runtime vendored zlib 1.3.1 and archived it at -Os, and host C-lane
builds linked the system libz, so deflate, raw deflate and gzip output
differed from Node's at every level. The vendored copy is now Node
v24.22.0's deps/zlib (Chromium's zlib, 1.3.2.1-motley-285e94b), built at
-O3 with the units and defines Node's zlib.gyp selects per target: the
SSSE3 Adler-32 and SSE2 inflate and slide-hash code on x86-64, the NEON
and ARMv8 CRC-32 code on arm64, and the portable code on wasm32. The SIMD
units check the CPU at run time and give the same output, so compressed
bytes match Node's on every target.

zlibInputs (runtime-pack-common/scripts/zlib-inputs.mjs for the runtime
packs, vendor-inputs.ts for Zig cross builds and the sanitized runtime)
holds the per-target table, and a unit test keeps the two identical.
Every C-lane driver now links the vendored objects instead of -lz.
Chromium's BSD license ships beside zlib's in the runtime packs.

Corpus 8405 compares compressed bytes with Node across levels,
strategies, memLevel, windowBits, dictionaries and flushing streams;
8406 covers chunked inflation with small input pieces and output chunks.
2026-10-26 15:12:51 -05:00
Chris Tate 1d72466f51 Read int32-slot arrays at their width in integer clones
An integer clone reads a number array's element as an 8-byte double. A
number array proven to hold only int32s keeps 4-byte int32 slots
(SCR_ELEM_I32), so the clone read such an array at the wrong width: it
returned wrong values, and near the array's end it read past the slots.

The clone now tests the array's element kind: int32 slots load as 4-byte
int32s, double slots load as doubles and keep the exact-int32 test, and
any other storage bails to the ordinary body.

New corpus program: integer-clone-int32-slots.ts.
2026-10-26 15:00:12 -05:00
Chris Tate f1e27f2e44 Poll the loops of a nest on every iteration in ELF worker executables 2026-10-26 14:31:32 -05:00
Chris Tate de3a75ff81 Push localized failure down to the failing component of a declared type (#2307) 2026-10-26 13:10:05 -05:00
Chris Tate 21c199ce0d Keep the layout of arrays and records passed to wider parameter types (#2329) 2026-10-26 13:08:31 -05:00
Chris Tate 4cacdfe821 Merge main 2026-10-26 12:38:20 -05:00
Chris Tate 34c2f9851d Have the native CLI packages depend on their LLVM helper and runtime pack (#2333) 2026-10-26 11:47:34 -05:00
Chris Tate 18755e0599 Allocate object groups that die together in arenas 2026-10-26 11:41:21 -05:00
Chris Tate 2f13edd058 Merge main 2026-10-26 11:36:06 -05:00
Chris Tate de6cdf1156 Add the shape overlay: typed JavaScript from analysis-proven facts (opt-in) (#2176) 2026-10-26 11:33:47 -05:00
Chris Tate a5971450ec Tear down self-recursive records and classes in one loop (#2343) 2026-10-26 11:14:35 -05:00
Chris Tate d1963dc8a5 Test a scalar optional left of ?? in JavaScript on the typed path
In JavaScript, `counts.get(key) ?? 0` on a Map the compiler types as
Map<string, number> is typed `any` by the checker, so `??` was lowered
on checked-dynamic values: the `number | undefined` read converted to a
dynamic value, which allocated a heap union for every read, and the test
ran on that value.

When the left of such a `??` is a union of one number, boolean or string
arm with undefined or null, and the default has the same type, the test
and the default now run on the typed path, where the read needs no heap
union, and only the result converts to a dynamic value. Other defaults
keep the dynamic path.

New corpus program: untyped-nullish-scalar-defaults.mjs.
2026-10-26 11:12:57 -05:00
Chris Tate 09a73ab6ed Fix Array.prototype calls on native arrays, typeof on ambient names, and net connections to Unix sockets (#2326) 2026-10-26 10:56:58 -05:00
Chris Tate 6e4fdf95d0 Link the exported bootstrap package's helper and runtime pack dependencies
The native bootstrap's package checks copy the seed's distribution into a
package directory outside the workspace and, since the CLI packages depend
on their LLVM helper and runtime pack, drop the bundled copies. Nothing in
that directory resolved @scriptc/llvm-linux-x64-gnu, so
verify-native-cli.mjs failed with 'Cannot find module'. The test now links
both packages into the directory's node_modules, as pnpm links a workspace
package's dependencies.
2026-10-26 10:56:27 -05:00
Chris Tate d5dc896576 Merge main 2026-10-26 10:53:46 -05:00
Chris Tate 9b96314c20 Tear down self-recursive records and classes in one loop
The teardown of a type with children of its own type called itself for
each child: a call, its register saves and the segment bookkeeping per
node, 45 instructions per node in a binary tree.

A record or standalone class whose every reference field holds its own
type and that has no collector header now tears down in one loop. A child
whose last reference the dead node held goes onto a pending list linked
through its own count word, which it no longer needs, and the loop takes
the list's first node next, so nodes are visited parent first. Each node
frees its block as before (dispose observers, the RC audit, blocks of
other arenas), the calling thread's blocks go into the teardown segment
in visit order, and the segment goes onto the free list in one step, so a
structure built parent first takes the blocks in its allocation order.
Shared and immortal children keep their release. The loop needs no depth
budget. Other teardowns keep the recursive form.
2026-10-26 10:42:41 -05:00
Chris Tate 4c5c0823ce Address review feedback 2026-10-26 10:20:11 -05:00
Chris Tate 928c30e908 Bind thisArg in the array callback methods of dynamic arrays
forEach, map, filter, some, every, find, findIndex, findLast,
findLastIndex and flatMap on a dynamic array, and through
Array.prototype.<method>.call on a native array, called the callback
without its thisArg, so `this` in the callback was not the second
argument. These methods now bind `this` to the thisArg, or undefined
when there is none, once per call, around all of the method's callbacks.
2026-10-26 10:19:57 -05:00
Chris Tate b926722ad3 Evaluate writes and indices through missing asserted elements in Node's order
`xs[i]!.name = v` threw while it evaluated the receiver, so a missing
element skipped the value's side effects and hid an error the value threw.
Node evaluates the array, the index, the key and the value, then the write
throws "Cannot set properties of undefined". Such a write, in statement or
value position, now evaluates them in that order and then tests the
element: a missing one throws Node's TypeError, a present one takes the
write. The array and the index are held only when the key or the value
can assign a binding or field, and a key or value whose evaluation is
unobservable stays in the write. A key of any number or string value is
named in the message, since the write holds it.

An index that replaces the array binding or field (`xs[(xs = other, 0)]!`)
ran before the member access and the string conversion read the array, so
they read the replacement. The array is now held before such an index, as
JavaScript reads it first; an index that cannot assign keeps the plain
read.

Corpus 7395 covers both orders.
2026-10-26 10:16:54 -05:00
Chris Tate acb2d23df5 Attribute slot causes to the owner only for types without their own slot 2026-10-26 07:59:53 -05:00
Chris Tate 52a228d0f5 Keep declarations a removed branch shares with another 2026-10-26 07:50:59 -05:00
Chris Tate d1a0bea84e Have the native CLI packages depend on their LLVM helper and runtime pack
Each @scriptc/cli-<platform> package bundled its platform's LLVM helper
and runtime pack, which are also published as @scriptc/llvm-<platform> and
@scriptc/runtime-<platform>. The CLI packages now depend on those packages
at the same version (workspace:*, which pnpm pack writes as the exact
version) and leave them out: cli-linux-x64-gnu goes from 115 MB to 60 MB
packed.

- build-native-cli.mts --platform-packages, which each CLI package's
  build:native passes, writes a manifest without llvm_package, runtime_pack
  and runtime_packs.
- loadNativeToolchain resolves a missing helper or host runtime pack as
  Node resolves the package from the compiler's directory: nested in the
  platform package, beside it (hoisted, or pnpm's virtual store) or higher
  up. It requires the compiler's version, and an absent or mismatched
  package fails with an error that names it.
- installNativeCli records where the package manager put both packages in
  the installed manifest, with the same checks.
- verify-native-cli.mjs requires the exact dependencies of a platform
  package and checks the resolved packages' versions.
- package-native-cli.mjs bundles the helper and runtime pack into the
  standalone archives, which stay self-contained.
- smoke-native-install.mjs installs the helper and runtime pack tarballs
  with the platform package; the bootstrap's exported package drops its
  bundled copies first.
- release.yml publishes the CLI packages only once every helper and
  runtime pack package resolves on npm, and scriptc only once the CLI
  packages resolve, each wait at most 25 minutes. A rerun skips the
  uploads npm already accepted and waits again.
2026-10-26 07:48:11 -05:00
Malte Ubl 572e7e3eac Merge remote-tracking branch 'origin/lf/l4-expressions' into lf/l5-polish
# Conflicts:
#	packages/compiler/src/frontend/lowering/lowerer.ts
2026-10-26 05:37:00 -07:00
Malte UblandClaude Opus 5.5 ec9624997a Attribute the dynamic pieces of pushed-down types in SC6101 and the slot operation counts
A pushed-down slot's dynamic operations act on its failing components,
whose checker types are not degraded themselves. The lenient mapping
records those components with the declared type they belong to; SC6101
names them ('hook' is a dynamic piece of 'Plugin[]'), and the operation
counts go to the owning slot. Causes read a binding's declared type, not
its narrowed flow type. The coverage test uses its own fixture, so it does
not depend on which interface members main can map.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 05:35:58 -07:00
Chris Tate fe244085d4 Run native async generators as stackless coroutines
An async generator whose body suspends only at its own awaits and yields
now compiles to an LLVM switched coroutine, as async functions of that
kind already do, instead of running on a fiber with its own stack. Its
steps run on the stack that resumes it (the loop's after an await), its
awaits suspend through scr_coro_await_prepare and scr_coro_hop_prepare,
and its yields store OUT through scr_coro_yield_* before suspending.

- scr_async_gen_new_stackless creates the generator. The stackless hop
  settles an async generator's state as the fiber hop does, and a step
  that would start below the stack guard throws a RangeError, which
  rejects its request.
- Calling an async generator's next, return or throw no longer counts as
  parking the caller: they queue a request and run the body to its next
  suspension, which returns to the caller. Bodies that consume async
  generators can now be stackless too.
- An untyped await (async.awaitDyn) has a native coroutine form,
  extracted by scr_coro_await_dyn_value, so async bodies that await
  untyped values can be stackless.
- An async generator's return through a finally region takes its await
  turn through the coroutine when the body is one.

Debug builds, libraries, worker programs and Windows executables keep
fibers, as for stackless async functions.
2026-10-26 07:28:50 -05:00