Commit Graph
3540 Commits
Author SHA1 Message Date
Chris Tate eca815da02 Merge main 2026-10-25 10:55:32 -05:00
Chris Tate d93a02d89e Build the large compiler tables on first use (#2202) 2026-10-25 06:55:23 -05:00
Chris Tate b7915b92a7 Merge main 2026-10-25 06:38:44 -05:00
Chris Tate c8b40bd73e Move small number-key maps to the direct index from the second key (#2204) 2026-10-25 06:34:30 -05:00
Chris Tate 3c4df3d9e4 Fill Buffers with multi-byte values, swap byte order and decode utf16le a word at a time (#2205) 2026-10-25 06:30:20 -05:00
Chris Tate b8403b7094 Let LLVM merge the comparisons of a three-way string comparator (#1971) 2026-10-25 06:27:31 -05:00
Chris Tate cac5a19fc0 Fill Buffers with multi-byte values, swap byte order and decode utf16le a word at a time
- fill with a string or Buffer value longer than one byte: writes the
  value once, then doubles the filled prefix with memcpy, as Node does.
  The value is copied with memmove, so a value that is a view of the
  buffer being filled gives Node's result; the previous per-byte loop
  read such a view while overwriting it. The loop also computed a
  division per byte.
- swap16, swap32 and swap64: one load, byte swap and store per group,
  through a local data pointer.
- toString("utf16le" | "ucs2"): four units below 0x80 copy as four bytes,
  and other units outside the surrogate range are written inline (a
  three-byte sequence as one four-byte store). Surrogates keep the
  previous path. The result records its UTF-16 length (one unit per
  input unit), or is marked ASCII, so `.length` does not walk it.
2026-10-25 05:58:42 -05:00
Chris Tate b3c98139e4 Move small number-key maps to the direct index from the second key
A number-keyed Map or Set with keys the direct index allows now moves to it
at its second entry rather than its fifth, so a map over a few small
integer keys (a histogram over 0..3) reads through the inline direct index
instead of the out-of-line linear scan.
2026-10-25 05:48:40 -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 5da6d76085 Run lone array callbacks as loops with their bodies inlined (#1997) 2026-10-25 05:24:07 -05:00
Chris Tate 9abf35f6a2 Build the large compiler tables on first use
The native compiler ran four large module initializers at every startup:
the builtin call signatures (LIB_FN_SIGS), the Node 24 fetch
compatibility profile, and the builtin module function and ambient
surface tables. Together their init functions are about 1.3 MB of code,
and --version and warm cache-hit builds paged them in and built the
tables without reading them.

Each table is now built by a function on its first call and kept in a
module variable:

- libFnSignatures() replaces LIB_FN_SIGS
- node24FetchCompatProfile() and staticFetchMembers() replace
  NODE24_FETCH_COMPAT_PROFILE and the STATIC_* member sets
- builtinModuleFns() and ambientSurfaceFns() replace BUILTIN_MODULE_FNS
  and AMBIENT_SURFACE_FNS

The @scriptc/compiler entry point keeps LIB_FN_SIGS and
NODE24_FETCH_COMPAT_PROFILE as deprecated constants built from the new
functions when the entry module loads. The native compiler does not load
that module.

The surface manifest's coverage notes read the fetch target through the
profile function, so building the manifest module no longer builds the
profile. The table contents are unchanged.
2026-10-25 05:23:35 -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 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 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 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 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 12bbb9bde6 Keep a throttler's pending-call storage within its live calls
A queue that never emptied kept the slots of calls already taken, so its
storage grew with lifetime traffic even when maxPending bounded the live
calls. Before growing, the queue now moves its live calls to the front.
2026-10-25 03:00:30 -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 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 0eef6251de Merge main 2026-10-25 02:28:32 -05:00
Chris Tate 6bd675420b Merge main 2026-10-25 02:06:50 -05:00
Chris Tate 8b0921243f Compile large sanitized dev programs in shards 2026-10-25 01:59:32 -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 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 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 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
Chris Tate e5ba073468 Parse JSON into discriminated unions in one pass
`JSON.parse(text) as T` for a discriminated union of records took the
checked-dynamic route: it parsed the text into a dynamic tree, matched
and copied that tree into records, then freed it. The one-pass schema
parser now builds these unions directly, as record members, array
elements and whole documents. For an object, a shallow scan of its
members finds the discriminant's value, which selects the one record arm
that holds that literal, and the object is parsed with that arm's
schema. An object with a missing, non-string or unknown discriminant, or
any member the arm rejects, declines, and the checked route then gives
the result or the error as before.

The compiler admits unions whose record arms each name their string
literals, every literal naming one arm, plus null and undefined arms.
2026-10-25 00:44:25 -05:00
Chris Tate 713f28dcd2 Merge main 2026-10-25 00:27:44 -05:00
Chris Tate 478feb15cd Call the declared base method for super calls in JavaScript method values
Since 76735a8d9 (#1975), a `super.m()` call in a JavaScript class method
whose `this` is the call-site receiver reflected the base class's whole
prototype and read `m` from it. An instance's member lookup first tests
its property bag, which inherits from its class's prototype data, so once
the base's prototype was reflected, `child[k]` for a method the subclass
overrides found the base's method there: `value[name](arg)` on a subclass
instance called the base method.

While no code reads a prototype method by name, `super.m()` now calls the
declaring class's receiver-independent method value with the receiver, as
the method's [[HomeObject]] lookup answers, without reflecting the base
prototype. Programs that read prototype methods by name keep the
reflected lookup.
2026-10-25 00:26:55 -05:00
Chris Tate 3e54e0edb3 Read fs.promises as a module and check fs read and write arguments as Node does (#2098) 2026-10-25 00:23:19 -05:00
Chris Tate 2269fd9e1f Merge main 2026-10-24 23:48:53 -05:00
Chris Tate 3e115ce08c Cache absent properties in the dynamic tier's property caches
A read site's cache only remembered properties it found on the receiver
or its first prototype. Reads of properties an object lacks, which
validation libraries make constantly (zod reads ctx.skipChecks,
payload.memo, x.aborted and _zod.optin on every check), walked the whole
shape chain and then repeated the lookup on the generic path every time:
7.9 million such reads in one zod benchmark run.

- Read sites call a new probe, scr_js_get_probe, that answers undefined
  for a key the receiver and its chain of plain shaped objects (at most
  one prototype, ending in the default prototype) lack, and caches that
  answer. The entry holds while the receiver's shape, its prototype link,
  and that prototype's shape are unchanged. Its tag and placeholder
  holder make the inline entry-0 test in compiled code miss, so the
  answer always comes from the probe. Method reads keep
  scr_js_load_probe, so a missing method still reaches the slow path and
  its "is not a function" error. Names of Object.prototype members are
  never cached as absent.
- Receivers of one shape with different prototypes (zod's internals
  objects) each keep an entry instead of replacing one another.
- A site whose four-way record is full consults a per-thread table keyed
  by shape and key before walking shapes, and fills it.
2026-10-24 23:47:41 -05:00
Chris Tate 37031fa7c2 Convert a loop's string receiver to UTF-16 at once, and publish it only when the scan holds another string (#2180) 2026-10-24 23:34:50 -05:00
Chris Tate f06f47aca1 Resolve promises with an argument in the rejectUnauthorized corpus program
8179-tls-reject-unauthorized-values.mjs called the resolve function of an
untyped new Promise() with no argument. tsgo types that function as taking
one argument and reported SC0001 for each call, while tsc-ts accepted them,
so the checker-identity comparison failed. The calls now pass undefined.
2026-10-24 23:27:46 -05:00
Chris Tate d2326c4c89 Look up a number Map key on its integer view (#2170) 2026-10-24 23:27:03 -05:00
Chris Tate 863a71e37c Number hoisted speculative leaves per function (#2179) 2026-10-24 23:19:34 -05:00
Chris Tate faa54f6443 Cache only number-key integer views that need no conversion
A Map/Set lookup's integer key view was cached for the function by the key
operand's name. When the view was a conversion emitted at a lookup in a
branch, a later lookup of the same operand (a wide integer constant) reused
it from a block the conversion does not dominate, and LLVM rejected the
module. Only the value's own integer or uint32 view and integral literals
are cached now.
2026-10-24 22:58:41 -05:00