Commit Graph
5027 Commits
Author SHA1 Message Date
Chris Tate d8f2874860 Keep replace callback parameters checked-dynamic where parseInt or parseFloat expects another type
parseFloat and the text of parseInt take a string, and the radix of parseInt
takes a number. An unannotated offset passed as their text, or a capture passed
as the radix, is no longer bound to its value's type; it stays checked-dynamic,
so the call compiles as it did before the binding.
2026-10-25 06:42:25 -05:00
Chris Tate 627ebd86a5 Call replace callbacks with unannotated parameters directly
TypeScript types the parameters of an inline replace callback after the
match `any`. An unannotated parameter that is never reassigned, whose
every read means the same for a string or number as for `any`, and that
receives a capture every match sets, the offset or the subject now takes
that value's type. String-search callbacks get the same treatment for the
offset and the subject.

scr_regex_replace_fn calls the closure directly in more cases:
- checked-dynamic parameters, which receive the boxed value Node passes at
  their position (a capture or undefined, the offset, the subject, the
  groups object, then undefined);
- `string | undefined` capture parameters, which receive NULL when the
  match leaves the capture unset;
- a checked-dynamic result, converted with ToString;
- up to six parameters.

Callbacks of the earlier typed form compile to the same call, with a
kinds argument of 0, and run the same per-match work.
2026-10-25 06:16:32 -05:00
Chris Tate edae637526 Support named properties and Symbol.species on typed arrays held as untyped values (#2068) 2026-10-25 05:48:27 -05:00
Chris Tate cfa9f81cfd Share storage between T[] and (T | undefined)[] (#2080) 2026-10-25 05:40:56 -05:00
Chris Tate 7fa8808361 Skip NULL children and key-cache walks when disposing dynamic values (#2201) 2026-10-25 05:37:06 -05:00
Chris Tate 5da6d76085 Run lone array callbacks as loops with their bodies inlined (#1997) 2026-10-25 05:24:07 -05:00
Chris Tate cbc062a35f Skip NULL children and key-cache walks when disposing dynamic values
Disposing a checked-dynamic node released each entry's getter and setter
and the node's prototype and symbol tables through scr_dyn_release, though
almost all of them are NULL: in a zod validation loop, over half of all
dyn releases. Dispose now tests each child first.

A pooled object also walked all its slots again on reuse, to take the key
bytes it kept back from the pool's key budget. It now records that total
when it is pooled, and reuse subtracts it.
2026-10-25 05:03:04 -05:00
Chris Tate f8b1f9ffc3 Keep a spread tuple alive until all its fields are packed into the rest
A spread tuple's first field carries the statements that evaluate the
tuple into a hidden local, and its later fields read that local. When
lowerArraySpreadItems stored the first field as one item's expression,
the local's frame ended with that store, so a tuple returned by a call
was released before its later fields were read (a use-after-free under
SCRIPTC_SAN=1). The field's statements now join the packing sequence at
the item's position, whose frame lasts until every item is stored.
2026-10-25 05:02:40 -05:00
Chris Tate 256fbdba20 Keep the helper for callbacks that read a local class's captures
A lone array callback in a local class's method could read a binding the
method reaches through its class's captures. Those bindings are shared by
every method of the class, so another method can assign one while the
loop runs, but the copied loop read the value once when the call started.
stableTarget now counts a method's class captures as inherited, so such
a callback keeps its helper and reads the current value on every call.
2026-10-25 04:54:28 -05:00
Chris Tate c3d778b615 Skip fork discovery in programs that never name node:child_process (#2200) 2026-10-25 04:53:04 -05:00
Chris Tate 7db7b4dd86 Prove int32 array candidates in one analysis that revises dropped ones (#2196) 2026-10-25 04:39:23 -05:00
Chris Tate fa27df8b06 Speculate floor-mods and products by powers of two as masks and shifts (#2199) 2026-10-25 04:34:53 -05:00
Chris Tate 534c08dd2f Pack spread tuple fields together with other spreads into optional-element rests
A call that spread a fixed tuple and also an array, a Set or another
iterable into a `(T | undefined)[]` rest parameter took the general rest
path, which converts every value to T, so an undefined tuple field threw
("undefined is not representable in the target union"). Such calls now
pack every item in argument order through lowerArraySpreadItems, which
takes values already lowered (the tuple's fields) beside argument nodes
and keeps undefined values as present undefined elements.

A tuple element typed only `undefined`, as in `...["b", undefined]`, has
a `null | undefined` field. Stored into an array that keeps undefined
elements it is now evaluated and stored as undefined, where it was an
internal compiler error; the checker admits it there only as undefined.
2026-10-25 04:34:24 -05:00
Chris Tate fa02fcd8ae Report which packages and modules would run in a Node island (#1691) 2026-10-25 04:32:22 -05:00
Chris Tate 03b754e891 Convert dynamic values into nested unions in one pass (#1914) 2026-10-25 04:18:21 -05:00
Chris Tate 080fe5e5e0 Record the TypeScript order baseline for the typed-array constructor kind fixture 2026-10-25 04:16:32 -05:00
Chris Tate 0d4df6ff62 Skip fork discovery in programs that never name node:child_process
forkTargetPaths asked the checker for the symbol of every identifier
callee in every implementation file, on each root-discovery pass and twice
more in preflight, to find calls of child_process.fork. A call is a fork
only through a binding whose import or alias chain names the module in a
module specifier, or through a '#' specifier a package.json "imports"
field maps.

namesChildProcess reads every program file once per program, outside
Node's type declarations and scriptc's own declaration files, which
declare the module and alias it only as node:child_process. Implementation
files are read from their syntax trees, which discovery fetches anyway;
other files as the host serves them to the server (the new
Ts7Program.getSourceText), without fetching their syntax trees. When no
file holds "child_process" in its text, an escape or line continuation
that spells it, or an "imports" entry that names it, forkTargetPaths
answers no targets without querying the checker.

The discovered fork targets are unchanged.
2026-10-25 04:14:58 -05:00
Chris Tate 8f655df65e Hold a Map.get result as the entry's reference until the map can change (#2099) 2026-10-25 03:59:34 -05:00
Chris Tate 45171ea02c Use a built-in typed-array constructor as its own species before the species symbol exists
A typed array whose constructor property is set to another built-in
typed-array constructor now creates that kind from map, filter, slice
and subarray when the program has not evaluated Symbol.species.
2026-10-25 03:59:10 -05:00
Chris Tate 4050c0fb5c Merge main and skip package.json files without a scriptc key 2026-10-25 03:58:49 -05:00
Chris Tate e38f7c329d Speculate floor-mods and products by powers of two as masks and shifts
In a speculative integer loop, `v - Math.floor(v / 2^n) * 2^n` computed
the floor division as an arithmetic shift, multiplied it back by 2^n with
overflow and -0 checks, and subtracted with range checks. For a safe
integer v and n in [0, 52] the result is the floor-mod, which is v's low
n bits: the fast copy now computes it as `v & (2^n - 1)`, which lies in
[0, 2^n) and needs no check. A product with a power of two (a literal or
`Math.pow(2, n)`) becomes a shift, with one test that the other factor's
magnitude is at most (2^53 - 1) >> n; its zero result is +0.
2026-10-25 03:56:48 -05:00
Chris Tate c1f61c05c4 Merge main 2026-10-25 03:55:15 -05:00
Chris Tate e99b31dab1 Decode base64 eight characters at a time (#2197) 2026-10-25 03:45:47 -05:00
Chris Tate 9cb9750f92 Set a trusted build aside while a getter runs outside a visit session
A getter or proxy trap that runs during a trusted build outside any
visit session now runs with the build's state set aside, as inside a
session. A conversion the getter starts, such as a recursive union
that opens and ends its own session, no longer clears the mark that
keeps the rest of the outer build from trusting its earlier match.
2026-10-25 03:34:33 -05:00
Chris Tate 342b770cf2 Keep a Map.get result owned when its key can replace the map's holder
The receiver of a borrowed Map.get is read before its key. When the key's
evaluation replaces the field or local that holds the map
(`this.m.get(this.key())` where key() assigns this.m), the read runs on
the old map, which only the statement still holds, and the borrowed
value outlives it. A borrow now also requires the read itself, receiver
and key included, to preserve reference edges; otherwise the read keeps
its own reference.

Corpus 8181 and the emission test cover a key method that replaces the
field.
2026-10-25 03:25:37 -05:00
Chris Tate 2b11f14a04 Test that typed-array element descriptors are read through their getters
Object.defineProperty on a typed-array index reads its descriptor with
ToPropertyDescriptor (scr_dyn_descriptor_fields_read): every field once,
in specification order, through its getter, and a primitive descriptor
throws a TypeError. 7753 now also checks that the stored value is the
value getter's result, read once; that a getter-produced writable: false
is refused after the fields are read in order; and that null, booleans,
BigInts and symbols are refused as descriptors.
2026-10-25 03:21:25 -05:00
Chris Tate 493785a121 Test switch labels in copied callbacks that return early
A lone array callback whose body has an early return is copied into an
exit block, and its statements are renamed by a separate path from the
single-return case. The new test covers labeled switches, a nested
labeled switch and breaks to both labels in map and some callbacks that
return early. Both calls run as loops, and the IR validator accepts
every renamed break.
2026-10-25 03:20:44 -05:00
Chris Tate 975270cef5 Decode base64 eight characters at a time
Buffer.from(text, "base64" | "base64url") decoded each four-character
group with four lookups and a sign test, then stored its three bytes one
at a time. The decoder now first takes complete groups two at a time:
each character's value comes from a table that holds it already shifted
into place within its group's 24 bits, a single bit test covers all
eight characters, and each group's three bytes go out in one four-byte
store whose last byte the next store overwrites. A group with any other
character (padding, whitespace, an invalid character) takes the
previous path, and the two-group step resumes after it, so line-wrapped
text keeps most of the gain. The destination already holds two spare
bytes, which cover the extra store byte.
2026-10-25 03:02:53 -05:00
Chris Tate 7e20a75c8f Split readline input in one scan and copy ASCII strings into buffers whole (#2192) 2026-10-25 02:59:39 -05:00
Chris Tate 19237373ad Compute dev builds' int32 slot analysis on a forked process (#2195) 2026-10-25 02:46:02 -05:00
Chris Tate 669000de97 Compile large sanitized dev programs in shards (#2194) 2026-10-25 02:42:37 -05:00
Chris Tate 39b461f2fd Compile namespace-style npm packages: guarded declarations and var exports (#2172) 2026-10-25 02:37:06 -05:00
Chris Tate f176e868bf Prove int32 array candidates in one analysis that revises dropped ones
selectInt32Arrays proposed int32 array candidates, analyzed the marked
module, and when a candidate dropped, marked the module again without it and
analyzed the result from scratch, until a round kept every candidate it
marked. On tsc-ts the first analysis drops 6 of 8 candidates, and a second
full analysis confirms the other 2.

The analysis now revises the module as candidates drop: a candidate whose
element stores fail (and that keeps no checked stores) has its functions
replaced by the module's functions marked without it, which call the element
readers again, and the fixpoint goes on over the revised functions. The
selection takes the module of the functions the analysis ended with when
marking the module for the candidates it kept proposes exactly those
candidates, in those functions, and the emitter takes over the analysis with
that module's node index, initializer bindings and dynamic stores. A revision
that changes anything else in a function, such as a call of a function no
other call reaches, ends the analysis, and selection falls back to rounds.

SCRIPTC_INT32_VERIFY=1 also runs the rounds and throws unless the selected
module and every fact of its analysis are the same.
2026-10-25 02:32:44 -05:00
Chris Tate ad26bed418 List line-reader among the benchmark workloads that link the dynamic tier
The workload reads its file through readline over fs.createReadStream.
The stream and readline runtime units define dynamic-tier symbols on
their own: a program that only reads a file with createReadStream links
the same 32, as log-summary and inventory-report do.
2026-10-25 02:29:47 -05:00
Chris Tate 0af32aff5a Compute dev builds' int32 slot analysis on a forked process 2026-10-25 02:15:04 -05:00
Chris Tate 84c8949837 Read optional elements of bindings that hold undefined as undefined (#2193) 2026-10-25 02:11:59 -05:00
Chris Tate 6bd675420b Merge main 2026-10-25 02:06:50 -05:00
Chris Tate 3a832394f9 Fetch only the syntax trees lowering reads (#2191) 2026-10-25 02:00:00 -05:00
Chris Tate 8b0921243f Compile large sanitized dev programs in shards 2026-10-25 01:59:32 -05:00
Chris Tate 1f26462fb7 Keep an unchanged executable without selecting the runtime pack (#2187) 2026-10-25 01:41:58 -05:00
Chris Tate 3ef34770bd Read optional elements of bindings that hold undefined as undefined
`a?.[i]` on a binding whose type admits undefined, such as a
`string[] | undefined` parameter, threw "undefined is not representable
in the target union" when the binding held undefined. Element access
replaced an identifier receiver whose storage has an undefined arm with
a checked narrow to its present arm before the optional chain ran, so
the chain saw a receiver that is never nullish and lowered `?.[` as a
plain element read.

Element access now leaves the receiver to the optional chain when the
access carries `?.`; the chain tests the undefined arm and lowers the
access again inside its guard. This applies to arrays, tuples and
record keys. Property access (`a?.length`, `a?.at(0)`) already lowered
through the chain.

Corpus 8330 matches Node's output.
2026-10-25 01:41:31 -05:00
Chris Tate ec766a5269 Journal the Promise.all tuple and AsyncLocalStorage callback thunks (#2190) 2026-10-25 01:40:17 -05:00
Chris Tate db2cb5c99e Split readline input in one scan and copy ASCII strings into buffers whole
readline split redirected input byte by byte, twice per chunk (once to
find whether the chunk ends a line, once to split it). Before splitting
it copied each chunk out of its Buffer and into a scratch buffer, decoded
it into a new string, and copied the unfinished line and the whole chunk
into a joined buffer. Now:

- one scan per chunk: memchr for a CR once, then LF to LF with memchr
  when the chunk has none, and the LF, CR and CRLF split otherwise;
- the unfinished line joins only the first line of the chunk;
- an ASCII chunk with no partial UTF-8 sequence held from the previous
  one is split where it is, and a chunk that is not shared memory is
  read from its own storage. Lines are still copied into their strings
  before the first is delivered.

Buffer.from(string, 'latin1' | 'ascii') encoded character by character;
an ASCII prefix, all of an ASCII string, is now copied with memcpy.

Line events, their order and the CRLF delay are unchanged. Corpus 8344
covers lines joined across chunks, a line longer than a chunk, empty
lines, mixed line endings and UTF-8 split across chunks. The new
line-reader workload reads a 67 MB log through readline over a file
stream.
2026-10-25 01:35:28 -05:00
Chris Tate 6b27ffbf08 Fetch only the syntax trees lowering reads 2026-10-25 01:25:03 -05:00
Chris Tate 25d0ad9193 Parse JSON into discriminated unions in one pass (#2189) 2026-10-25 01:16:43 -05:00
Chris Tate 9d7f0182e5 Keep CommonJS wrapper names and redeclared exports out of namespace rewrites
- A guarded declaration of a name the CommonJS wrapper defines (`var
  exports = exports || {}`, and `module`, `require`, `__filename`,
  `__dirname`, `arguments`) reads the wrapper's value, not undefined, so
  it keeps its text instead of becoming a fresh object.
- The write scan counts destructuring assignments, and destructuring
  for-in/for-of targets, as writes, so a hoisted function that sets the
  namespace before its guarded declaration keeps the declaration as it
  is.
- An exported `var` that a later declaration of the same name sets
  again is not settled at the export: importers would read the
  redeclared value, where Node copies the first one into
  module.exports. It stays refused (SC1090), as before. The npm case
  var-export-redeclared pins it.
2026-10-25 01:04:55 -05:00
Chris Tate a54e0811d0 Journal the Promise.all tuple and AsyncLocalStorage callback thunks 2026-10-25 01:04:31 -05:00
Chris Tate 5d3e0c7a8b Support X509Certificate fields and methods, TLS certificates and tls.Server contexts (#2052) 2026-10-25 01:00:24 -05:00
Chris Tate 3243e843b3 Call the declared base method for super calls in JavaScript method values (#2188) 2026-10-25 00:57:31 -05:00
Chris Tate 4963dcf8ec Cache absent properties in the dynamic tier's property caches (#2186) 2026-10-25 00:46:10 -05:00