6388 Commits
Author SHA1 Message Date
Chris Tate 12e284aa2a Merge pull request #2560 from vercel-labs/ctate/give-intel-macos-d388b2
Give the Intel macOS release build 90 minutes
v0.4.6
2026-10-29 10:08:21 -05:00
Chris Tate 851d3362c2 Give the Intel macOS release build 90 minutes 2026-10-29 08:55:52 -05:00
Chris Tate 4af5844f52 Prepare v0.4.6 release (#2539) 2026-10-29 06:57:21 -05:00
Chris Tate a2e1251759 Prepare v0.4.6 release 2026-10-29 06:02:00 -05:00
Chris Tate 02d7230c66 Link the scriptc CLI without huge text (#2524) 2026-10-29 01:47:52 -05:00
Chris Tate 10479ce2e0 Link the scriptc CLI without huge text
An executable with at least 4 MiB of program code is linked with 2 MiB
segment alignment and published with one write, so the page cache holds
its text in 2 MiB folios and the runtime advises huge pages for it. A run
then maps every 2 MiB text window it touches whole. Most runs of the
compiler are short: `scriptc --version` peaked at 76 MB and an unchanged
rebuild at 78 MB, against 20 MB and 26 MB with 4 KiB alignment, and cold
and edited builds of tsc-ts took the same time either way.

compile() takes an internal `hugeText: false`, which links with 4 KiB
alignment whatever the code size and is part of the executable cache key.
scripts/build-native-cli.mts passes it. Executables the compiler builds
keep huge text.
2026-10-29 01:01:21 -05:00
Chris Tate 8971d694b1 Poll the checker server's socket before a reply read blocks (#2520) 2026-10-29 00:26:13 -05:00
Chris Tate b260a13dda Make fewer syscalls per fs thread-pool completion and pool-thread write (#2519) 2026-10-29 00:20:58 -05:00
Chris Tate a1458145fc Keep the pointer when a narrowing drops a pointer union's unit arm (#2517) 2026-10-29 00:20:01 -05:00
Chris Tate 50aa42883f Cache the pool-thread signal check per OS thread
scr_fs_write_once cached whether the calling thread blocks SIGPIPE and
SIGXFSZ in an SCR_TL variable. SCR_TL is empty in builds without thread
instances, so a pool thread's answer applied to the main thread too, and
a later writeSync to a closed pipe ended the process with SIGPIPE
instead of throwing EPIPE. The cache is _Thread_local now. The
fs-write-sync fixture covers a write stream followed by writeSync on a
closed stdout.
2026-10-29 00:18:11 -05:00
Chris Tate 4cfd4f3320 Records: nested-arm filter narrowing, async object-method returns, untyped Map layouts (#2509) 2026-10-28 23:56:59 -05:00
Chris Tate c0ef82865d Run short constant-count loops together when their inner exits saturate (#2493) 2026-10-28 23:49:18 -05:00
Chris Tate 9698c42c3b Name alias type nodes by category (#2516) 2026-10-28 23:44:06 -05:00
Chris Tate 5f27c4d233 Exit cleanly with Node loaded and keep nested entries on Node's stack (NI-7c, NI-3b) (#1905) 2026-10-28 23:43:21 -05:00
Chris Tate 26e458932e Poll the checker server's socket before a reply read blocks
The native compiler sends most checker requests one at a time and blocks
in recv for each reply. The server answers most of them in a few
microseconds, and a reader the reply wakes takes tens of microseconds more
per round trip, over about 130,000 round trips on a large program.

scriptc_ts7_read now polls the socket with MSG_DONTWAIT for up to 200 us
before it blocks. Windows keeps its blocking pipe read.
2026-10-28 23:39:17 -05:00
Chris Tate f909bdf6c8 Make fewer syscalls per fs thread-pool completion and pool-thread write
- A worker writes the completion wake byte only when the pipe has none.
  The loop reads it, with the queue lock held, when it takes the last
  waiting completion. The pipe holds a byte exactly while completions
  wait, so a burst of completions costs one write and one read instead of
  one write per completion and a read loop per dispatch.
- The fs pool's threads keep SIGPIPE and SIGXFSZ blocked for their
  lifetime. scr_fs_write_once checks once per thread whether both are
  blocked; on such a thread a write makes no signal-mask calls, and a
  write that fails with EPIPE or EFBIG consumes the signal it raised.
  Other threads keep the per-write guard.

fs-write-sync.test.ts covers EPIPE and EFBIG from write streams, whose
writes run on the pool's threads, and matches Node's errors.
2026-10-28 23:34:34 -05:00
Chris Tate 0faade48ae Test that receivers are evaluated once and before effectful keys (#2514) 2026-10-28 23:25:09 -05:00
Chris Tate a35bf813db Keep the pointer when a narrowing drops a pointer union's unit arm
A union retype between two shape unions (or two class unions) shares the
pointer when both encode their unit arms the same way. Narrowing a field
read past `=== undefined` or `=== null` retypes a union with a unit arm
to one without it, so the encodings differ and each listed arm was
rebuilt: a retain, a tag switch, a second retain and a release, for the
same pointer. The unit encodings only matter when the value may hold a
unit arm. When the retype lists none of the source's unit tags, every
value is a record or class pointer whose arm the target derives from the
record or class itself, so the pointer is the target value.

The rebuild's release also made the pointer a nursery cycle candidate,
and in the native compiler those candidates kept the nursery bypass's
probes of closures and boxes succeeding. Without them, a probe window can
open while young cycles of the type are held by older garbage that the
bypass promoted earlier. The window then frees nothing, the type stays
bypassed, and the garbage waits for a mature pass, which on a cold tsc-ts
build raised the compiler's peak RSS by about 100 MB. A nursery probe
window that opens now makes the next release-triggered pass that would
run at the nursery run at the mature level, which reclaims that garbage
before the window is judged.
2026-10-28 23:23:07 -05:00
Chris Tate d73086bf0e Read each layout training profile when its build finishes (#2515) 2026-10-28 23:19:26 -05:00
Chris Tate 5f7a5dc883 Keep an async method's promise in non-promise slots; convert filtered arms beside an exact one
- An async object-literal method whose contextual slot returns a non-promise
  (`() => void`) keeps its own promise return, and one whose slot returns a
  union takes only the union's promise arm (`Promise<T> | void`): adopting
  the whole return type gave the method a non-promise type, refused with
  SC2001.
- A type predicate whose record target is itself an arm of the element
  union keeps that arm's elements as they are and converts the other arms
  with its fields, instead of refusing the filter with SC1090.
2026-10-28 23:10:05 -05:00
Chris Tate a64b5b0820 Keep saturating loops whose copies cannot share the inner loop
The jam of a short constant-count loop now leaves the loop as it is when:

- the inner loop's body assigns the accumulator other than by
  `acc = acc | C`. An iteration that runs past its exit could clear bits
  after the accumulator was full.
- the inner loop's body assigns the inner counter. The copies share the
  counter, so one copy's write changed the iterations of the others.
- the inner loop's start or bound reads the outer counter or a local of the
  outer iteration. The copies share the inner loop's start and bound, which
  read neither the counter's value nor the copies' renamed locals.

The unit test rejects each shape, and the corpus program adds a function
for each that matches Node's output.
2026-10-28 23:01:16 -05:00
Chris Tate 01a9cce36d Name alias type nodes by category
The emitter named each TBAA type node `field.<n>`, numbered in the order
the module's functions first use the categories, so an edit that adds a
field or memory category renumbered every later one. Every function that
loads or stores those fields then changed text, and its program partition
was compiled again.

Each type node is now named by its category (`field:<name>`,
`array:header`, `global:<id>`). The set of nodes and their structure are
unchanged, so the generated code is the same.
2026-10-28 22:50:15 -05:00
Chris Tate 686c34bd25 Store tty.isatty and process.cwd as function values (#2494) 2026-10-28 22:46:19 -05:00
Chris Tate 269ef9f0bc Read each layout training profile when its build finishes
refresh-cli-layout.mjs kept the raw profile of every cold training build
until the end of the run, about 23 MB per build and 8.7 GB for the 399
builds. When the disk filled, the profile runtime wrote partial files, the
compiler still exited with 0, and reading a partial file threw a RangeError
past the end of its counters.

Each cold profile is now read and deleted when its build finishes, so the
work directory stays under 200 MB. The script throws when a training run
reports `LLVM Profile Error`, naming the build and the write error, and
when a profile is shorter than its header describes.
2026-10-28 22:43:27 -05:00
Chris Tate 6f2a18d16c Test262 group B: generator method ABI, empty-pattern throws (#2505) 2026-10-28 22:41:17 -05:00
Chris Tate 3e7528ca86 pi wave 4: path values, union delete, NonNullable unions, BigInt stats, optional-param function values, live accessor literals, clearer stdlib-alias errors (#2497) 2026-10-28 22:33:16 -05:00
Chris Tate c3474cfa7a Test that receivers are evaluated once and before effectful keys
Adds the corpus program receiver-before-effectful-key.ts, with its corpus
cost and order baseline:
- a tuple read with a runtime index that rebinds the tuple or replaces the
  field holding it;
- a keyed read through a finite union of field names whose key rebinds the
  record or replaces the field holding it;
- an inherited-key `in` test on a computed null-prototype receiver, which
  runs the receiver once.

Each case reads the receiver as evaluated before the key, as in Node.
2026-10-28 22:30:01 -05:00
Chris Tate 26b216d360 Mark the borrowed array read fallback as read-only (#2503) 2026-10-28 22:28:17 -05:00
Chris Tate 0b7b678fac Grow the ASCII run before walking for forward index reads (#2502) 2026-10-28 22:27:56 -05:00
Chris Tate 91b5d267b9 Keep frozen checked records frozen, and give sloppy-mode this the global object (#2453) 2026-10-28 22:02:18 -05:00
Chris Tate 761fac8344 Put program constructors in .init_array on ELF (#2499) 2026-10-28 22:00:56 -05:00
Malte UblandClaude Opus 5.5 8e74d0518d Give an untyped empty Map the layout of the value it joins
`x ?? { errors: new Map() }` left the empty map typed Map<any, any>,
which spread through the checker into `new Map(result.errors)` and the
binding holding it, and every use failed with SC2020/SC1090. The
fallback literal now builds as x's record, `new Map(source)` over such
a join copies into the source's layout, an unannotated binding whose
checker type differs only by that residue takes its initializer's
layout, and Map methods on it convert into the receiver's layout. No
map is converted between layouts: each value keeps the one layout it
has.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:55:51 -07:00
Malte UblandClaude Opus 5.5 2acd199fbe Give an async object-literal method its contextual return type
An async method in an object literal with no return annotation and a
contextual signature inferred its own (narrower) literal type, while its
`return` literal was built at the contextually typed record: the return
statement then had to convert the wider record into the narrower literal,
which fails with SC2002. The method now takes its return type from the
contextual property's call signature, as the arrow form does. Sync
methods are unchanged; they already adopt their slot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:55:50 -07:00
Malte UblandClaude Opus 5.5 9eaa210e1f Narrow filtered union elements into a record with an arm's fields
A written type predicate whose target is no arm of the element union
but the same fields as one, with a nested union field narrowed
(Model<"a"> from the Model<Api> arm), was refused. The kept elements of
that arm now convert field by field, the nested union by its tag; any
other kept element still throws the predicate TypeError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:55:50 -07:00
Chris Tate 2ae08e4002 Route more packages and values through Node islands (NI-4c–4g) (#1904) 2026-10-28 21:53:40 -05:00
Chris Tate 1555a6161f pi conversions and layouts: class/collection/error views, merged promise arms, recursive checked records (#2486) 2026-10-28 21:52:11 -05:00
Malte UblandClaude 5.5 083d8e7aa3 Record the ts7 baselines of the new corpus programs
Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
2026-10-28 19:51:10 -07:00
Malte UblandClaude 5.5 34c39e5443 Throw on empty patterns over checked-dynamic sources
An empty binding pattern still performs its one observable step:
RequireObjectCoercible for `{}` and GetIterator for `[]`. Over a statically
non-nullish source that step is invisible, which is why the empty-pattern
lowering emitted nothing; a checked-dynamic source can be null or undefined
at runtime, where V8 throws.

Emit the runtime null/undefined test for dyn sources in the empty-pattern
branch, and let a JS-file empty object pattern take the source-spelling
message dynDestrCheck already produces for non-empty ones.

Test262 (static-adapted-v6, --workers 8), affected chapters before -> after:
language/statements/class/dstr pass 1518 -> 1526, fail 424 -> 416;
language/expressions/class/dstr pass 1518 -> 1526, fail 424 -> 416. No
pass -> fail.

Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
2026-10-28 19:51:10 -07:00
Malte UblandClaude 5.5 460bd6fb38 Keep a generator method's generator ABI through the prototype-access widening
A class whose methods are reachable through `X.prototype` widens its method
signatures to checked values so a JS caller can hand anything across. The
widening replaced every non-void return with DYN, which erased a GENERATOR
method's generator type; the generator branch below then had no generator
channels to build its metadata from and refused the whole class (SC2001 in
`%init`, re-emitted as a runtime fence in a JS file). Every
`assert.sameValue(c.m, C.prototype.m)` variant of the campaign therefore
refused at runtime, 759 of them in the generator-method chapters alone.

A generator's return kind is already a checked-crossable ABI: dynFrom boxes a
generator by reference and dyn calls resume it through the dyn generator ops,
so only the arguments need the widening. Guard the return widening to
non-generator methods.

Test262, 1,110 affected variants, before/after on the candidate
(class/gen-method 52->76, class/async-gen-method 76->128,
class/scope-gen-meth 0->8); no pass->fail and no typed-path delta.

Co-Authored-By: Claude 5.5 <noreply@anthropic.com>
2026-10-28 19:51:09 -07:00
Chris Tate e506d19cd6 Name destructured builtins, parenthesized functions and util.debuglog as Node does
Reading the name of a function stored in a const followed const aliases to
the definition, but stopped at destructuring: `const { cwd } = process`
gave the binding's name instead of Node's "wrappedCwd". A member
destructured from the process global or a builtin module's namespace now
gives the module table's name for it.

An anonymous function wrapped in parentheses (`const f = (function () {})`)
answered an empty name. As in Node, it takes the name of the binding or
property it initializes through the parentheses.

A builtin read as a property (`util.debuglog.name`) answered the name its
shared adapter was first created under, so after `util.debug` it answered
"debug". The name now comes from the property and the module table, and
util.debug's value is named "debuglog", as in Node.
2026-10-28 21:50:00 -05:00
Chris Tate cd04e38cec Mark the borrowed array read fallback as read-only
A borrowed element read of a reference array falls back to
scr_arr_borrow_ref when the element is outside the dense fast path. The
call carried no memory attributes, so LLVM assumed it could write any
memory: a loop holding such a read reloaded the array's header on every
iteration, after inlining too. The fallback only reads the array (a hole
or an out-of-range index traps), so it now carries the attributes of the
other array read fallbacks, and the header loads stay hoisted.
2026-10-28 21:38:18 -05:00
Chris Tate 52e3dadc20 Match Node for BigIntStats dates, bigint option effects and path namespace spreads
- A BigIntStats Date member is built from its truncated millisecond member,
  as Node's getters do, where the Stats getters round the fractional one: a
  timestamp at least 0.5 ms past a millisecond no longer reads one
  millisecond later than Node.
- statSync, lstatSync and fstatSync evaluate a bigint option whose value can
  have an effect (`bigint: (count++, true)`), in member order after the path
  and before the stat; an exception it throws is the call's.
- An object spread of a node:path namespace in JavaScript copies the module's
  members, read through the module value so the copy holds the same
  functions: an ES module namespace with its keys sorted and `default`, and
  posix, win32 and the default import in Node's member order.
- scr_lib.h declares the BigIntStats readers, so every runtime name the
  backend can emit has a prototype in scr_runtime.h.
2026-10-28 21:16:57 -05:00
Chris Tate abb87f4dd7 Grow the ASCII run before walking for forward index reads
A scanner reading a mostly ASCII text front to back (slices and
positioned startsWith at increasing positions) walked from the hot
cursor on every read within 64 units of it, since the near-cursor walk
came before the ASCII-run growth. A forward read past the run now grows
the run first. When a non-ASCII character ends the run, a nearby read
walks from the run's end or the cursor and starts a new run where it
lands.
2026-10-28 21:13:01 -05:00
Chris Tate 45f4f0d47e Refresh the native compiler's function layout and optimization profile (#2498) 2026-10-28 21:12:43 -05:00
Chris Tate cbd6b6ee2d Register and unregister sockets, clients and agent entries in constant time (#2496) 2026-10-28 20:56:27 -05:00
Chris Tate 3b520e4fb2 Merge main 2026-10-28 20:50:23 -05:00
Chris Tate d10d5578af Put program constructors in .init_array on ELF
The helper's program target options left UseInitArray off, so a program
constructor went to .ctors. The C startup files and lld run only
.init_array entries, so such a constructor never ran. Runtime units
already set the option (runtime-unit.cpp); program code now does too.
No program emits a constructor today, so executables are unchanged.

native-constructors.test.ts emits a constructor through a single object,
split code generation and program partitions, checks that it lands in
.init_array on ELF, and runs it.
2026-10-28 20:37:15 -05:00
Malte Ubl db617bd594 Merge remote-tracking branch 'origin/main' into fix/pi-conversions-layouts 2026-10-28 18:30:14 -07:00
Malte UblandClaude Opus 5.5 ea4f54691a Test that a for..of beside never[] evaluates its source once
The tag test and the walk share one evaluation: a call in the loop head
appears in the IR exactly once, and the loop walks the value the test saw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:30:09 -07:00
Chris Tate e0c692593c Evaluate a for..of source beside never[] once, keep collection views through a second view, and combine typed promises in untyped JavaScript
- lowerForOfEmptyArrayArm binds the `Set | Map | string | never[]` source
  to a hidden local before the tag test, so a call or getter in the loop
  head runs once and the loop walks the value the test saw.
- markTarget marks a structural view as a collection view when its source
  is already one, and reruns the fixpoint when a view marked earlier
  becomes a collection view, so a Map or Set handed through two views keeps
  answering through the collection.
- Promise.all in a JavaScript source whose call types as Promise<any[]>
  (an unannotated parameter) runs the engine's Promise.all over the entries
  as checked-dynamic values when they are typed promises. A merged
  `Promise<number | string>[]` argument reaches that call since union
  promise arms merge.

Corpus 8419 and 6956 and the js-own-tostring npm fixture cover each case.
2026-10-28 20:22:14 -05:00