Commit Graph
4889 Commits
Author SHA1 Message Date
Chris Tate cf399bc1bf Pass the unwinding setting in the serialized IR emission test (#2168) 2026-10-24 21:56:47 -05:00
Chris Tate 42743e85d3 Decode an undefined TextDecoder input as no bytes (#2093) 2026-10-24 21:51:10 -05:00
Chris Tate 7b946ff610 Read Math, JSON and Reflect as real objects in JavaScript (#2006) 2026-10-24 21:50:17 -05:00
Chris Tate 1553247279 Reuse symbol storage keys and checked-dynamic symbol boxes (#2165) 2026-10-24 21:28:36 -05:00
Chris Tate 8a93b888d6 Run the three.js nodes fixture unsanitized in the sanitized lane (#2164) 2026-10-24 21:26:08 -05:00
Chris Tate 79c4984bed Initialize a deferred module binding that a reached body assigns (#2166) 2026-10-24 21:24:43 -05:00
Chris Tate 031c6172f9 Emit serialized IR with the unwinding ABI in the serialized IR emission test
compile() emits textual LLVM on Linux with the unwinding ABI since #2163,
so the test's emission from serialized IR passes the same option.
2026-10-24 21:20:10 -05:00
Chris Tate 5f602ff3b8 Decode asserted optional typed arrays other than Uint8Array
`decoder.decode(view!)` on a `Uint16Array | undefined` (or another typed
array that may be undefined) was refused with SC2020: the assertion keeps
the undefined arm for decode, and the decode input only read the
undefined arm of `Uint8Array | undefined`. The undefined arm of any typed
array that may be undefined now reads as an empty array of the same type,
so a defined input decodes its bytes and an undefined one decodes no
bytes, as in Node.
2026-10-24 21:16:59 -05:00
Chris Tate 8f6f8929dd Merge main 2026-10-24 21:15:49 -05:00
Chris Tate ab93f728ed Initialize a deferred module binding that a reached body assigns
A module binding whose initializer only allocates data (a function, an
arrow function, a literal or a registry object) is initialized only when
a reached body uses the binding. Only reads and updates counted as uses,
so a binding that reached code only assigned was never initialized, and
the assignment, which checks that the binding is initialized and
releases its value, threw "Cannot access '<name>' before
initialization" where Node runs. Assignments now count as uses.

Corpus 8320 assigns two such bindings, one through an assignment
expression, without reading them.
2026-10-24 20:53:18 -05:00
Chris Tate 0eb7cc12e5 Reuse symbol storage keys and checked-dynamic symbol boxes
Every read or write of a symbol-keyed property on a checked-dynamic value
formatted the symbol's address into a new string to find its storage key,
and every conversion of a symbol to a checked value allocated a new box.
Programs that key hot properties by symbols, such as Effect's type ids
checked on each combinator, spent a large share of their time there.

The storage key is a function of the address, so a 256-entry per-thread
cache by address now returns the existing string, released at exit. A
symbol also keeps a weak pointer to its live box: conversions share it,
and the box clears the pointer when it is freed.
2026-10-24 20:51:37 -05:00
Chris Tate e73dbe0bb3 Run the three.js nodes fixture unsanitized in the sanitized lane
The sanitized nodes build compiles one 1.14 GB LLVM module, and its clang
compile peaks at about 16 GB, more than the CI runners have, so the
fixture failed in every san shard that ran it. It now builds without the
sanitizer there, as in the plain lane; every other three.js fixture keeps
it. The fixture goes back under the sanitizer once sanitized dev builds
compile large programs in shards.
2026-10-24 20:41:45 -05:00
Chris Tate 03af6fb9ff Iterate classes with object-literal iterators and close them on early exits (#1860) 2026-10-24 20:30:36 -05:00
Chris Tate f1bc74f934 Give the module namespace handle its own tag (#2161) 2026-10-24 20:29:29 -05:00
Chris Tate 120c65bfbb Read an iterator's next once, and await return()'s value in for await
JavaScript reads an iterator's `next` when the iterator opens and keeps
calling that function, so a `next` the iterator assigns while it runs is
not called. The for-of loop and the spread drain over an object-literal
iterator read the field on every step; they now read it once after the
open (classIteratorNextRead) and call the function they read. The
checked-dynamic spread drain (%iterator.pack.checked) does the same,
using the stepping the for-of and Array.from drains already use.

`for await` over a synchronous iterator closes it on an early exit the
way the async-from-sync iterator does: return()'s result is read for done
and value, and the value is awaited, so a pending promise settles before
the loop continues and a rejected one rejects the loop (after a throw from
the body, the body's error still wins).
2026-10-24 20:24:11 -05:00
Chris Tate e13dfe1ba1 Write slices stored in records that are only read into stack buffers (#2157) 2026-10-24 20:23:12 -05:00
Chris Tate 4b1a6b01d1 Construct through arrays of class values that mix a class and its subclasses (#1868) 2026-10-24 20:18:35 -05:00
Chris Tate bf6bc12056 Represent record-only unions used as Map keys and Set elements as record pointers (#2160) 2026-10-24 20:17:11 -05:00
Chris Tate 304612b7e2 Inline destructured literal results and read ASCII slice ranges inline (#2151) 2026-10-24 20:11:44 -05:00
Chris Tate d64f18d2a8 Evaluate constructor arguments before the class-value check
`new kinds[i](args)` over an array of class values read the element and
narrowed it to the class value before the arguments ran, so an index past
the end threw "is not a constructor" without evaluating them. JavaScript
evaluates the arguments first: the construction is now a call of a lifted
helper (checkedElementConstruction) that takes the element read and the
arguments, in that order, then runs the check and constructs. Keeping the
arguments in one call also keeps a spread's argument pack alive until
every argument has read it.

A checked class-value boundary accepted every subclass's class value into a
base class's slot, so construction through the slot could call a subclass
constructor that takes different arguments with the base constructor's. It
now accepts only the subclasses whose class-value constructor takes the
same arguments (classValueAbiEquals, the rule the classval upcast validator
already applied); any other subclass fails the check with a TypeError.
2026-10-24 20:11:25 -05:00
Chris Tate 1547fd47cd Keep record factory parameters that a closure in the record captures
findRecordFieldProducers counted only direct reads of a parameter, so a
factory such as `(text) => ({ text, upper: () => text.toUpperCase() })`
counted as storing `text` only in its field. A caller's slice passed to
it was written into a stack buffer, and the closure kept by the caller
then read whatever the next iteration wrote there. Captures by closures
and classes now count as reads, so such a factory is not a producer.
2026-10-24 20:10:29 -05:00
Chris Tate 43edb72447 Emit unwinding code for textual LLVM and assembly output on Linux (#2163) 2026-10-24 20:09:47 -05:00
Chris Tate 8032e6c6c6 Free frozen records when a shape union drops their last reference
A frozen record's rc word carries SCR_RC_FROZEN beside its count. The
shared shape-union release compared the whole word, so the last reference
to a frozen record took the shared-reference branch: it decremented the
count and never freed the record or its fields. When an arm is freezable,
the release now compares the count without the frozen bit, as the
record's own release does.
2026-10-24 20:08:30 -05:00
Chris Tate e2db1ef344 Hold Reflect.ownKeys's function value in the Reflect namespace object
Reading `ownKeys` from Reflect read as a value (`const r = Reflect;
r.ownKeys`) gave the member function that throws SC1090 when called,
although the spelled `Reflect.ownKeys` read already has a function
value. The namespace object now holds that function value, so calls
through the alias run and `r.ownKeys === Reflect.ownKeys` is true, as in
Node. The spelled read and the namespace member share one lowering
(reflectMemberValue).
2026-10-24 20:07:36 -05:00
Chris Tate 50421e1cbd Merge main 2026-10-24 20:05:11 -05:00
Chris Tate 7464f12e6b Emit unwinding code for textual LLVM and assembly output on Linux 2026-10-24 20:01:49 -05:00
Chris Tate 1004b07e37 Merge main 2026-10-24 19:59:33 -05:00
Chris Tate afd9c52467 Prove typed array reads in while loops bounded by the length in bounds (#2155) 2026-10-24 19:13:16 -05:00
Chris Tate 89ac800d1c Read the runtime ABI in the runtime pack test fixture from the compiler (#2162) 2026-10-24 19:12:43 -05:00
Chris Tate adeddf8724 Check with a tsc-ts server shared by later builds (opt-in) (#2153) 2026-10-24 19:12:05 -05:00
Chris Tate b7491676bb Read the runtime ABI in the runtime pack test fixture from the compiler 2026-10-24 19:01:18 -05:00
Chris Tate f26cc427c2 Merge main 2026-10-24 18:51:06 -05:00
Chris Tate 76735a8d92 Run private members and super calls over foreign receivers like Node (#1975) 2026-10-24 18:50:35 -05:00
Chris Tate 53bddc6ed7 Give the module namespace handle its own tag 2026-10-24 18:46:36 -05:00
Chris Tate e2d0941e3a Speculate integer loops through pure helper calls, powers of two and byte reads (#2100) 2026-10-24 18:45:59 -05:00
Chris Tate d6c09279a7 Compile createRequire probes for optional addons as deferred refusals (#2015) 2026-10-24 18:41:05 -05:00
Chris Tate 383b69cf2d Represent record-only unions used as Map keys and Set elements as record pointers 2026-10-24 18:39:02 -05:00
Chris Tate e2487106e5 Compare optional string index reads as code units (#1923) 2026-10-24 18:37:12 -05:00
Chris Tate 924fc1055c Find checker reply element ends in C and list dynamic imports from module references (#1885) 2026-10-24 18:34:58 -05:00
Chris Tate ecd103d1fb Keep the while-loop bound off counters written inside expressions
flow follows a while body's statements and applies each increment it
meets as a statement. An increment inside an expression, such as an if
condition's comma expression `if ((i++, i > 0))`, lowers to a statement
nested in the expression; whileBound accepted it as an increment, but
flow never applied it, so a read after it kept the pre-increment bound
and lost its bounds check. whileBound now gives no bound when any
expression in the body holds a write of the counter.
2026-10-24 18:32:42 -05:00
Chris Tate 9e55835014 Compile more npm and CommonJS JavaScript dependencies statically (#2122) 2026-10-24 18:30:56 -05:00
Chris Tate 8b4f83ac02 Collect the native compiler profile on the release commit (#2138) 2026-10-24 18:25:03 -05:00
Chris Tate c8ee28de80 Write slices stored in records that are only read into stack buffers
A slice whose every reader only borrows it is written into a stack buffer
instead of a heap allocation. A slice stored in a record field counted as
kept, so a tokenizer's `const text = source.slice(start, i); const t =
token(kind, text, start); sum += weight(t);` allocated and freed a string
per token even though `t` is read only for `t.text.length`.

A slice local stored directly in a field of a stable record local, built
by a record literal or by a factory that only returns a record literal of
its parameters, is now lent when the record is only projected: its field
reads are lending reads, and it is passed only to record parameters whose
bodies read that field only in lending positions (a fixpoint, like the one
for lent string parameters). A record that is returned, stored, captured or
passed anywhere else keeps the slice on the heap. includes, indexOf and
lastIndexOf on string arrays now lend their search argument.
2026-10-24 18:23:17 -05:00
Chris Tate 3b7b9afae5 Reap the checker process within a millisecond of closing it (#2154) 2026-10-24 18:22:33 -05:00
Chris Tate 6997d8b211 Allocate stream ticks from the small-block allocator, and emit stream events by immortal names (#2152) 2026-10-24 18:22:03 -05:00
Chris Tate ba81871ff0 Run row sums two rows per pass (#2148) 2026-10-24 18:09:22 -05:00
Chris Tate 6b3a47c386 Merge main 2026-10-24 18:07:52 -05:00
Chris Tate 5dc3a051b7 Prove typed array reads in while loops bounded by the length in bounds
findBytesBounds proved element reads only inside for loops, and only for
`counter + c` indexes. A while loop such as

    while (i < len) {
      c = line[right - i]!;   // right = len - 1
      i++;
      if (c === 10) {
        if (i === len) break;
        c = line[right - i]!;
        i++;
      }
      ...
    }

kept a bounds check on every read. The out-of-bounds path also produced a
NaN that LLVM threaded into a following switch, which then stayed an
indirect jump instead of becoming a lookup table.

- A while condition that compares an integer counter with a typed array's
  length (`i < len`, `i <= right`, `len > i`) starts a bound. The body may
  write the counter only by statements that add a positive literal, and
  may not write the receiver. Calls in the body are allowed: they cannot
  write either binding (both are uncaptured locals), and a typed array's
  length is fixed at construction.
- The body is followed in order. An increment raises the bound and the
  counter's minimum by its step. An if condition that compares the counter
  with the length narrows the bound in its branches (`if (i === len) break`
  restores `i < len` after an increment). Branches join to the weaker bound,
  and a statement that advances the counter inside a nested loop or
  labeled block leaves the counter unbounded after it.
- Indexes of the form `L + d - counter` (`len - 1 - i`, `right - i`) are
  proven for both for and while bounds, from the counter's upper bound and
  its minimum.
2026-10-24 18:06:45 -05:00
Chris Tate 29e5bacb9c Pump untyped iterable pipeline sources (#2111) 2026-10-24 18:03:36 -05:00
Chris Tate 8da01d3ab3 State that SCRIPTC_CHECKER_SERVER needs a tsc-ts that serves --listen
The tsc-ts 0.0.9 that scriptc includes rejects --listen, so with it every
build that sets SCRIPTC_CHECKER_SERVER=1 checks with its own tsc-ts
process. The variable's description now says so.
2026-10-24 18:02:25 -05:00