Commit Graph
2609 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 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 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 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 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 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 a5c7a4e983 Report how well the native compiler's profile matches its functions (#2117) 2026-10-24 17:39:30 -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 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 fba274439c Report constant object graphs in untyped JavaScript (static objects, stage 1) (#2147) 2026-10-24 17:08:34 -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 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 c1deea4d3f Keep integer index arithmetic in integers across nested counted loops (#2113) 2026-10-24 16:34:12 -05:00
Chris Tate 49964dc997 Read prototype methods stored under computed keys on ES5 instances
A member read on a `new F()` instance looked up the name only among the
fields of F's prototype objects. A method stored under a computed key
(`F.prototype[name] = fn`) is kept in the prototype's unknown-key field,
so the read found nothing: the method's body was never visited, and the
objects it writes stayed constant in the report. The read now also takes
the prototype's unknown-key field, as reads of the object's own fields
do.
2026-10-24 16:26:38 -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 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 317162af78 Count repeated calls through constructors, apply and per-value callbacks
The constant-graph pass treats a function whose call edges all run once
at initialization as init-only. Some calls recorded no edge or a
once-only one:

- `new F()` of an ES5 constructor and `f.apply(thisArg, args)` bound the
  arguments without a call edge, so a function also called directly once
  was init-only even when a loop constructed or applied it.
- Callbacks of Array.from, toSorted, replace and replaceAll run once per
  element, comparison or match, but those methods were missing from the
  repeating callback set.
- JSON.parse revivers, JSON.stringify replacers and toJSON methods run
  once per value; their edges at the JSON call site now count as
  repeated, including toJSON calls resolved after the call's handling.

Objects these functions write or allocate were reported as constant, and
their reads as foldable. constant-graphs.test.ts covers each case.
2026-10-24 16:13:05 -05:00
Chris Tate 70f565177e Count profile-annotated functions when the annotation runs
The match report counted the functions with profile counts after the
whole pipeline, so an annotated function that inlining or dead code
elimination later removed was not counted, while the annotation's
mismatch and missing-function warnings were. A pass-instrumentation
callback now counts the annotated functions right after
PGOInstrumentationUse runs, in the one-module and the partitioned
pipelines.

A new test builds helpers that reach the annotation and are then
inlined into main and deleted, and checks that the report counts them.
2026-10-24 16:05:36 -05:00
Chris Tate 69e6c11290 Merge main 2026-10-24 16:03:54 -05:00
Chris Tate 1f621ce2c1 Version string scans over the UTF-16 units of strings that are not ASCII (#2074) 2026-10-24 16:02:58 -05:00
Chris Tate ebeec4818d Prove 32-bit products exact only below 2^53
The exactness test for a product's 32-bit form multiplied the operands'
magnitude bounds in a double and accepted a result of at most 2^53. A
bound of 2^53 + 1 (3 * 3002399751580331) rounds to 2^53 there, so a
product the double rounds took the integer multiply's low bits:
`(3 * 3002399751580331) | 0` printed 1 where Node prints 0. The test is
now strict: a bound below 2^53 is exact, and no bound of 2^53 or more
rounds below it.
2026-10-24 15:58:14 -05:00
Chris Tate 5e7338ad2c Scan checker reply strings with many escapes in one pass
scriptc_ts7_json_value_end searched for the next quote again after each
backslash escape in a string, so a string with many escapes was scanned
once per escape. The quote it found now stays the candidate end until an
escape skips it, and each byte of a string is scanned once by each
search.

A test scans strings of 2,000,000 newline, backslash and quote escapes,
alone and inside an object, and compares the ends with jsonValueEnd's;
before the change it does not finish within 60 s.
2026-10-24 15:56:52 -05:00
Malte UblandClaude Opus 5.5 d837036f87 Report constant object graphs in untyped JavaScript (static objects, stage 1)
SCRIPTC_DEBUG_SHAPES=const reports the constant object graphs shape
inference finds (DESIGN-constant-objects.md, stage 1): objects created by
code that runs at most once (module top level, IIFEs, functions reached
only through one call site from such code) whose fields are written only
there, that never meet a reflective or computed write, and that never
escape. Namespaces (`var ns = ns || {}; ns.util = {}`), revealing
modules, `module.exports` tables, and ES5 constructors' prototype objects
qualify. Each near miss names its rule: allocated repeatedly, created by an
init factory (one object per call site), escapes, reflective or computed
write, literal form, written after init, or lazy cache. The report counts
the member reads and calls a later stage can fold.

The analysis now models ES5 constructors (a function's prototype object,
instances reading through it) and links CommonJS require() and export
bindings to the analyzed module's exports, which it previously read as
nothing. Code generation is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:53:14 -07:00
Chris Tate ea495056d8 Answer 'in' on native Node handles from their declared members (#1949) 2026-10-24 14:51:03 -05:00
Chris Tate e5cdc68bab Store AbortController and AbortSignal handles in class fields (#2125) 2026-10-24 14:32:15 -05:00
Chris Tate 9cb8ed3ce5 Map resolved empty mapped types as the empty object type (#2127) 2026-10-24 14:25:04 -05:00
Chris Tate 7499f9bd39 Keep versioned loops' slow copies free of speculative integer copies (#2137) 2026-10-24 14:14:40 -05:00
Chris Tate 4efcbbdfe4 Report inferred shapes for untyped npm JavaScript (#2036) 2026-10-24 13:49:30 -05:00
Chris Tate 3f03c90e86 Keep versioned loops' slow copies free of speculative integer copies
The copy of a dense-array, byte-window, counted, callback or typed-array
versioned loop that runs when its guard fails no longer gives its loops
speculative integer or int32 kind copies, which emitted each loop again for
a path that rarely runs. Proven integer loops, which emit one copy, are kept
in it.
2026-10-24 13:47:17 -05:00