Commit Graph
4873 Commits
Author SHA1 Message Date
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 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
Chris Tate fcc81c034c Read the final result's value only for sync pipeline sources
for await reads the value of a done result only through the
async-from-sync wrapper; an async iterator's done result is not read.
The source's generator read it for both, so an async iterator whose
done result has a throwing value getter rejected the pipeline. It now
reads and awaits the final value only when the iterator is sync.
2026-10-24 17:59:56 -05:00
Chris Tate 9c670e8d93 Count the arguments a function receives when it is called through a value (#1880) 2026-10-24 17:58:41 -05:00
Chris Tate 6856dcf285 Lower each closure argument of an implicit-any call once (#2150) 2026-10-24 17:56:00 -05:00
Chris Tate 05bc5299b0 Reap the checker process within a millisecond of closing it
Closing a checker process checked for its exit once and then slept 10 ms
before checking again, although the process exits a fraction of a
millisecond after its input closes. The checks now start 0.1 ms apart and
double, so the process is reaped as soon as it exits. The total wait before
the kill stays at about 50 ms (51.1 ms).
2026-10-24 17:54:15 -05:00
Chris Tate c0a4496900 Check with a tsc-ts server shared by later builds (opt-in)
With SCRIPTC_CHECKER_SERVER=1 on Linux and macOS, the native compiler's
checker connections go to a shared `tsc-ts --api --listen` server, which
keeps the files it parsed for the next build. When the variable is unset,
nothing changes.

checkerServerName names the server by a digest of the tsc-ts executable's
identity, the working directory, the file system callbacks, the timing
flag and the TSC_TS_* environment. scriptc_ts7_attach takes the server's
lock file with a non-blocking flock, connects to its socket in this
user's private server directory ($XDG_RUNTIME_DIR/scriptc or
/tmp/scriptc-<uid>), and starts the server when none answers: in its own
process group, with its standard streams on /dev/null and no other
descriptor of the build, with --idle-exit 300 and TSC_TS_LISTEN_MEMORY=2048
unless the environment sets it. A build that finds the server in use,
cannot start or reach it, or gets no answer to a ping checks with its own
tsc-ts process, as without the variable; so does a tsc-ts without
--listen, which exits as it starts. The syntax-only source parser, which
reads no libraries, always uses its own process and leaves the server to
the program's checker.
2026-10-24 17:50:45 -05:00
Chris Tate e25d871a34 Allocate stream ticks from the small-block allocator, and emit stream events by immortal names
Each deferred stream step (afterWrite, emitReadable, resume and the
others) queued a ScrStreamTick and a process.nextTick entry, both from
libc calloc and free. They now come from the runtime's small-block
allocator, as do the nextTick entries scheduled through
scr_next_tick and scr_process_write_callback.

The stream unit emitted each event with a newly allocated name string.
The names it emits ("data", "end", "drain", "finish" and the others)
are now immortal static strings, so an emit allocates no name.
2026-10-24 17:45:59 -05:00
Chris Tate 587b8f40c0 Type untyped JavaScript Maps, Sets and arrays from their uses (#2089) 2026-10-24 17:45:47 -05:00
Chris Tate da738a78ba Construct readline.Interface and builtin classes bound through require() members in JavaScript (#2095) 2026-10-24 17:43:27 -05:00
Chris Tate a5c7a4e983 Report how well the native compiler's profile matches its functions (#2117) 2026-10-24 17:39:30 -05:00
Chris Tate 01f5a3455a Test row-pair overlaps only for nests of two or more rows
The paired copy's test read the stored and read arrays' headers whatever
the row range, so a nest whose arrays the program assigns only for a
nonempty range dereferenced their NULL headers on an empty range, where
the loop as written never reads them. The overlap tests now run only
when the nest has at least two rows, and an array that is not there
answers that it is not disjoint, so the loop runs as written.
2026-10-24 17:38:44 -05:00
Chris Tate 43ff8a1122 Inline destructured literal results and read ASCII slice ranges inline
- A function whose whole body is `return [a, b]` or `return { a, b }`,
  with number, boolean or string fields, is now inlined where its result
  is only destructured (`const [head, tail] = split(text, at)`). The
  parameters are declared from the arguments, then each field straight
  into its destructured binding, in the literal's order; an unread field
  still evaluates into a binding of its own. No record is built, and a
  slice field read only through its length or char codes becomes a
  scalar slice that copies nothing. Arguments may own their value, but
  as for numeric records nothing inside an argument may hold a
  temporary of its own.
- A scalar slice of a proven-ASCII string computes its range inline:
  the bounds clamp over the byte length exactly as scr_str_slice_range
  does, and no boundary can split a surrogate pair. Other strings keep
  the runtime call, which gains the same ASCII fast path.
- value-records' tuples section, `[head, tail] = split(...)` read only
  through lengths, goes from two heap slices per step to none.
2026-10-24 17:35:25 -05:00
Chris Tate 6e4bfb3c91 Await the final value of an untyped sync pipeline source
for await over a sync iterator awaits the value of the result that ends
it too, so a sync iterator that returns a rejected promise rejects the
pump. The source's generator skipped that value and reported success.
It now awaits a final value that is an object (a promise included)
before it stops, without closing the finished iterator.
2026-10-24 17:34:40 -05:00