Commit Graph
2707 Commits
Author SHA1 Message Date
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 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 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 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 76735a8d92 Run private members and super calls over foreign receivers like Node (#1975) 2026-10-24 18:50:35 -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 e2487106e5 Compare optional string index reads as code units (#1923) 2026-10-24 18:37:12 -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 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 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 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 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 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
Chris Tate bac7a80377 Raise slow CI job time limits and prune closed pull request caches (#1994) 2026-10-24 17:33:56 -05:00
Chris Tate cc6b114468 Take the argument count before parameters bind
A function that reads arguments.length and is called through a value
reads the count its adapter stored, then clears the slot. That read ran
after the parameter prologue, so a parameter binding that threw would
leave the slot set for the next direct call. The count is now taken, and
the slot cleared, before any parameter binds.
2026-10-24 17:23:08 -05:00
Chris Tate bc9fdb9c08 Answer stream, StringDecoder and handle-object members in untyped code as Node does (#2105) 2026-10-24 17:11:01 -05:00
Chris Tate 40adc2d161 Hoist a call only when its arguments write no binding
A hoisted call runs before the rest of its write. An argument such as
`i++` in `acc = acc * 3 + i + tally(i++)` would then move that write
ahead of the write's earlier read of `i`, so such a call stays where it
is and the write is not speculated.
2026-10-24 17:06:14 -05:00
Chris Tate 9406ba5b89 Run row sums two rows per pass
A loop nest that sums one term per row,

  for (let i = start; i < end; i++) {
    let t = 0;
    for (let j = from; j < to; j++) t += term;
    out[i] = t;
  }

where the term only reads numbers, the counters and typed-array
elements, now runs two rows per pass when both row bounds are int32,
the start is at most the end, and the stored array shares no byte with
any array the term reads. Each row keeps its order of additions, so each
sum has the bits of the loop as written; an odd row count runs its last
row as written, and any other case runs the nest as written. Element
reads that do not depend on the row are read once per column for both
rows. With two independent sums in the inner loop, neither waits on the
other's addition, and LLVM computes both rows' terms in one vector.

bytes.disjoint compares two typed arrays' element ranges in memory.
2026-10-24 17:04:14 -05:00
Chris Tate 65e62fe1cb Compare integer values without converting them to doubles (#2060) 2026-10-24 17:02:42 -05:00
Chris Tate 8d9705e22d Run CommonJS top-level return, untyped assert forms, and untyped instanceof Object as Node does (#2101) 2026-10-24 17:01:39 -05:00
Chris Tate c37963b8aa Let objects inherit from JavaScript class prototypes (#2078) 2026-10-24 16:58:49 -05:00
Chris Tate 02ec0e86f6 Address review findings in the npm and CommonJS interop batch
- Intl.Segmenter accepts an options object literal made only of
  `granularity` entries again, including the empty `{}` main accepted;
  with none the segmenter is a grapheme segmenter, and a repeated key
  takes its last value, as in JavaScript.
- Namespace reads of a CommonJS module refuse a name that any module it
  re-exports assigns inside a function, not only the module itself:
  Node's namespace keeps the values from the end of evaluation.
- The named-import link check treats `module.exports` as an export every
  CommonJS module provides, as Node 24 does, instead of throwing its
  missing-export SyntaxError.
- The npm cases cjs-esm-named and cjs-lexer-aligned, which the batch no
  longer refuses at build time, are listed with the run-time SC1090 their
  CommonJS exports-object reads report.
2026-10-24 16:54:24 -05:00
Chris Tate eeaf622fbe Merge main 2026-10-24 16:51:57 -05:00
Chris Tate 12e16cdb32 Find weak collection entries through hashed indexes (#2004) 2026-10-24 16:40:37 -05:00
Chris Tate 9b64c7388d Resume hoisted-call writes reached from earlier bails, and shift only by exact powers of two
- A speculated write with hoisted calls resumes over its stored call
  results only when its own check failed. Reached in the resume copy
  from an earlier write's failed check, its calls never ran on that
  iteration, so it now runs as written: the calls happen once and the
  arithmetic reads their fresh results.
- A literal divisor is a power of two only when it is exactly 2^k with k
  in [0, 52]. Math.log2 rounds 2^52 + 1 to 52, so `Math.floor(v / (2^52 + 1))`
  was emitted as a shift by 52.
- A helper expands only when every call in its body is of another helper
  that expands, so an expansion never moves a call with effects ahead of
  an argument read. Hoisting already refused such writes; the
  classification now says so itself.
2026-10-24 16:38:12 -05:00
Chris Tate c1deea4d3f Keep integer index arithmetic in integers across nested counted loops (#2113) 2026-10-24 16:34:12 -05:00
Chris Tate d9fdd81ee9 Construct X509Certificate bound through a require() member
`const X = require("crypto").X509Certificate; new X(data)` now resolves
to the builtin class, so the standard-library constructor fence reported
SC2020 for it before the X509Certificate lowering ran. That lowering now
runs ahead of the fence. Corpus 8231 constructs a certificate through the
binding.
2026-10-24 16:32:40 -05:00
Chris Tate e22cdf358b Decode an untyped stream unshift's string chunk with its encoding
`s.unshift("e9", "hex")` on a native stream held as an untyped value
dropped the encoding argument and put the string's UTF-8 bytes at the
front. A byte-mode stream now takes a string chunk with an encoding as the
bytes Buffer.from(chunk, encoding) holds, as Node's
readableAddChunkUnshift converts it, and an unknown encoding throws
ERR_UNKNOWN_ENCODING. Object-mode streams keep the string as the entry.
2026-10-24 16:28:43 -05:00
Chris Tate f645e8e702 Run a prototype's constructor getter on the instance
An instance's `constructor` read that reaches a class prototype's own property now reads it with the
instance as the receiver (Reflect.get), so an accessor's getter sees the instance as `this`, as in
Node, instead of the prototype.
2026-10-24 16:27:15 -05:00
Chris Tate bffd887145 Keep the native receiver for private accessors and private method values
A JavaScript prototype method value runs over the call-site receiver when its private members are
fields (brand-checked reads and writes) or private methods it calls. A private accessor, or a private
method read as a value, would read a public property of the same name, so such a method keeps its
native receiver, as before. A super method call with spread arguments over the call-site receiver
now spreads them into the dynamic call instead of rejecting them.
2026-10-24 16:26:20 -05:00
Chris Tate e00d3fd911 Check comparison operands and walk instanceof chains as Node does
- The assert comparison methods read as function values (equal,
  strictEqual, deepEqual, partialDeepStrictEqual and their negations)
  throw Node's ERR_MISSING_ARGS TypeError, `The "actual" and "expected"
  arguments must be specified`, when called with fewer than two
  arguments, where `[assert.equal][0]()` passed.
- Untyped `instanceof Object` starts at the value's prototype, so
  Object.prototype itself, whose prototype is null, is not an Object.
- Untyped `instanceof Function` follows the whole prototype chain through
  objects and functions: an object whose prototype is a function, or a
  function whose prototype was set to such an object, is a Function.
2026-10-24 16:22:03 -05:00
Chris Tate 6a8ba89d9f Make Dates mutable objects with Node's setters and conversions (#2072) 2026-10-24 16:21:06 -05:00
Chris Tate 49d99d7764 Rewrite tests of held character locals inside other optional reads
An optional read tested against a one-unit literal is replaced by a test
of its code unit, built from the read's receiver and index. When that
receiver or index tested a held character local, as in
`src[c === "a" ? pos + 1 : pos] === "b"`, the inner test was kept as it
was and still compared the local as a string|undefined union while the
local's binding became a number, which failed IR validation. The same
held for a held local's own initializer. The receiver, index and temps of
both reads are now rewritten first.
2026-10-24 16:20:33 -05:00
Chris Tate af33be8046 Treat a collection exported through an export list as escaping
A reference in `export { m }` resolved to the export's alias symbol, not to
the local binding, so the inference did not see the export and typed the
collection from its local uses. An importing module then used it as an
untyped Map and failed to compile (SC9001). An unrenamed export specifier
now resolves to the binding it exports, which refuses the inference as
`export const` does. Corpus 7985 adds `export { listed, renamed as alias }`.
2026-10-24 16:16:49 -05:00
Chris Tate c98c1fe345 Test powers of two exactly before treating a product as exact
The product of two exact integers was treated as exact everywhere when one
of them was a constant power of two, tested with Number.isInteger(Math.log2).
Math.log2 rounds to 52 for 2^52 - 1, so a product by 2^52 - 1 that passes
2^53 was compared as its integer instead of its rounded double. The test now
compares 2 ** Math.round(Math.log2(value)) with the value. Corpus 7983 adds
products by 2^52 - 1 compared with products by 4.
2026-10-24 16:15:53 -05:00