Commit Graph
1194 Commits
Author SHA1 Message Date
Chris Tate f31f856783 Convert object literals with accessors to unknown again (#1506) 2026-10-20 08:55:35 -05:00
Chris Tate fe589fdddf Run SHA-384 and SHA-512 with AVX2, AVX-512VL or the arm64 SHA-512 instructions (#1464) 2026-10-20 08:53:46 -05:00
Chris Tate e1c27176a6 Throw checked-dynamic primitives as primitives so catch typeof tests see them (#1510) 2026-10-20 04:19:32 -05:00
Chris Tate e1e45f12b9 Give converted object literals their accessors
An object literal with get/set accessors converted to `any` or `unknown`
listed its reserved closure slots (`%get:x`) as data keys, so a dynamic
read of the accessor answered undefined without calling the getter, and
Object.keys and JSON.stringify listed the slot instead of the property.

The checked-dynamic copy now defines each accessor as one enumerable,
configurable property whose getter and setter are the slot closures, in
declaration order: the record shape records its accessor key order
(`accessorOrder`). Live views of such records build that copy, and
checking a copy back into the record reads the closures from the
accessor's getter and setter. Reads, writes, Object.keys,
Object.entries, JSON.stringify and property descriptors answer as in
Node.
2026-10-20 03:55:43 -05:00
Malte UblandClaude Opus 5.5 b74224f31c Throw checked-dynamic primitives as primitives so catch typeof tests see them
A value thrown from `unknown` storage (`throw values[i]`) that holds a
number, string or boolean now throws as that primitive, like a statically
typed `throw 5`. Before, it threw as a checked-dynamic reference, so
`typeof e === "number"` (and "string"/"boolean") on the catch
binding answered false and the narrowed reads were unreachable. A
primitive has no identity to keep, and reading the binding as `unknown`
converts it back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 00:50:32 -07:00
Chris Tate d5d9a1185a Compile process.send presence, process.exitCode reads, wider stream hooks, void race entries and promisify of callbacks (#1429) 2026-10-20 02:18:40 -05:00
Chris Tate f6f4ad1b00 Give errors held as any their own stack accessor (#1450) 2026-10-20 02:05:17 -05:00
Chris Tate 0c3a7ae73e Keep error stacks through computed keys, clones and throwing getters
A read through a computed key now counts as a stack read when the program
contains the string "stack", so `e[key]` captures frames like `e.stack`
does. A key the program assembles from other strings or from data it
reads keeps the static answer.

structuredClone of an error held as `any` copies a stack that was already
formatted as a non-enumerable data property, as it did before the stack
became an accessor.

When a name or message getter throws while the stack is formatted, the
stack accessor propagates the exception instead of reading the missing
string.
2026-10-20 02:00:21 -05:00
Chris Tate 7551434bd7 Support freezing and redefining fields of class instances (#1385) 2026-10-20 01:57:15 -05:00
Chris Tate 9791aa587e Return parked workers' memory while the event loop waits, after a delay that grows
The trim helper returned parked workers' memory once no worker had run for
100 ms. A compiler in watch mode starts its workers again for each rebuild
and faulted the returned pages back in, and a rebuild that runs on the main
thread alone had the discards run alongside it.

- The delay starts at 1 s (SCR_SA_DISCARD_DELAY_MS) and doubles each time
  workers start within ten delays of a discard, up to
  SCR_SA_DISCARD_DELAY_MAX_MS (10 s by default).
- Once the event loop of the thread that is not a worker runs, the helper
  discards one arena at a time, and only while that loop has been in one
  wait with nothing ready for a step (scr_sa_loop_waiting, called by the
  event loop around its sleep). A loop that does not wait gets the
  discards SCR_SA_DISCARD_DELAY_MAX_MS after the delay.
- Programs without a running event loop discard after the delay, as before.
2026-10-20 01:50:55 -05:00
Chris Tate f77298ea82 Merge main 2026-10-20 01:49:45 -05:00
Chris Tate ca65f57626 Speed up native string character tests, optional-string switches, fromCharCode and long-string length (#1498) 2026-10-20 01:20:11 -05:00
Malte Ubl 25e40afa83 Merge remote-tracking branch 'origin/main' into opt/bc-node-surface 2026-10-19 23:16:41 -07:00
Chris Tate ba8849a269 Speed up native string character tests, optional-string switches, fromCharCode and long-string length
`s.charAt(i) === "c"`, and `s[i] === "c"` where the index is proven in
bounds, built a one-character string to compare with the literal. When
the literal is one UTF-16 code unit, the comparison now reads
`s.charCodeAt(i)` and compares the number. charCodeAt converts the index
the same way charAt does, and an index out of range answers NaN, which
equals no unit, as "" equals no one-unit literal. A switch on
`s.charAt(i)` whose case tests are all one-unit literals switches on the
code unit the same way.

A switch on a `string | undefined` value with string literal cases, such
as an array element or an optional field, lowered to a chain of union
equalities. It now tests for undefined, which matches no case, and
dispatches the string through the string switch: by byte length, then by
the literal's leading bytes.

`String.fromCharCode(a, b, ...)` with up to sixteen arguments passes the
codes to the runtime in a stack array instead of a heap f64[], and up to
64 code units encode into a stack buffer instead of a heap scratch block.

`.length` of a string that is not ASCII counts its UTF-16 units. The
string index keeps four entries, so a program that read the lengths of
more long texts than that in turn (the text of every source file) walked
each whole string on every read. Lengths of non-ASCII strings longer than
64 bytes now go into a 1024-slot table keyed by address, allocated by
the first length it records. A slot is dropped when the string is freed
or reallocated and when an append changes it in place, which is where
the index drops its facts.
2026-10-20 01:11:36 -05:00
Chris Tate 12d0215c4d Link the child-process unit only in programs that spawn (#1403) 2026-10-20 00:18:21 -05:00
Chris Tate 8df5e8d48a Accept readline's dispose, instanceof, dynamic streams and non-string prompts (#1401) 2026-10-20 00:13:26 -05:00
Chris Tate 98a7d3b33d Merge main 2026-10-20 00:08:29 -05:00
Chris Tate 5cf51c46b0 Read x.constructor on EventEmitter and stream instances (#1442) 2026-10-20 00:06:32 -05:00
Chris Tate 63cb11ff83 Keep DataView's own behavior through unknown (#1470) 2026-10-19 23:59:13 -05:00
Chris Tate d321ea5780 Leave the error marker out of own property names
Object.getOwnPropertyNames, Reflect.ownKeys and
Object.getOwnPropertyDescriptors on errors held as `any` or `unknown`
listed "%error", the key that marks the checked-dynamic error encoding.
Node lists only the error's properties. The marker is now left out of
own property names; the runtime's own checks still read it.
2026-10-19 23:52:46 -05:00
Chris Tate e134489edc Unroll the MD5 block function (#1465) 2026-10-19 23:51:35 -05:00
Chris Tate ee8d320fdb Widen an unjudged backlog threshold with its level's growth fraction (#1462) 2026-10-19 23:51:08 -05:00
Chris Tate b07a3bd5f2 Size encoded messages to their nodes and wake a channel's owner once per batch (#1468) 2026-10-19 23:48:19 -05:00
Chris Tate cdd78b4502 Answer in on typed arrays held as any
`in` on a typed array, Buffer or DataView held as `any` or `unknown`
answered false for every key: indices, `length`, prototype methods and
Object.prototype names.

On a typed array, a canonical numeric key (one that ToString(ToNumber(key))
reproduces, or "-0") now answers by bounds: an integral index, not -0,
below the length, so "1.5", "-0", "NaN" and "Infinity" answer false. Other
keys answer true for members of %TypedArray%.prototype, Buffer.prototype on
a Buffer, the constructors' BYTES_PER_ELEMENT and Object.prototype. A
DataView has no indexed elements and answers DataView.prototype's members
and Object.prototype's.
2026-10-19 23:36:14 -05:00
Chris Tate bc9ef21b90 Keep DataView's own behavior through unknown
A DataView reached as a checked-dynamic value behaved like a Uint8Array
over its bytes: it printed as `Uint8Array(2) [ 0, 0 ]`, its string and
tag were the typed array's, `.length` and index reads answered from the
bytes, and typed array methods ran on it. It now inspects as Node's
`DataView { [byteLength], [byteOffset], [buffer] }`, answers
"[object DataView]", has no length or indexed elements, and throws
"is not a function" for typed array methods.
2026-10-19 23:28:21 -05:00
Chris Tate 3d00b5b837 Size encoded messages to their nodes and wake a channel's owner once per batch
A message encoder started its node array at 16 nodes of 184 bytes and
kept the array, with its unused capacity, until the message was
received. A port that queued many messages held about 3 KB of zeroed
memory per message: 200,000 queued numbers took about 590 MB, and the
page faults made postMessage cost about 1.3 µs. The node array now
starts at 4 nodes and is trimmed to the message's node count when
encoding ends.

scr_channel_post posted a wake event, and with it a pipe write and a
condition broadcast, to the receiving thread's mailbox for every
message. The owner's loop counts a port with queued messages as ready,
so only a message that finds the queue empty needs to wake it.

bench/channel-messages, added here, posts 200,000 messages (numbers and
small objects) on a MessageChannel before the port delivers any.
2026-10-19 23:24:56 -05:00
Chris Tate 95bdd7305c Unroll the MD5 block function
scr_md5_block ran its 64 steps as one loop that chose the round
function, message index and rotation per step, and the update path
called it once per block. scr_md5_blocks unrolls the steps with the
message word, constant and rotation inlined, processes every whole block
of an update in one call, and keeps each step's critical path to the
round function of b, an add, the rotate and the add of b: a + m + K is
summed first, G is written as (b & d) + (c & ~d) so that its c & ~d half
joins that sum, and an empty asm statement keeps the compiler from moving
the constant's add after the round function. MD5, HMAC-MD5 and md5-sha1
use it.

Corpus 5775 compares MD5, md5-sha1 and HMAC-MD5 digests over every
length from 0 to 520 bytes in five update sizes and over 1 MiB with
Node; tests/harness/corpus-costs/ records its plain and sanitized costs.
2026-10-19 23:22:51 -05:00
Chris Tate 72e22ed0ed Run SHA-384 and SHA-512 blocks with AVX2, AVX-512VL or the arm64 SHA-512 instructions
SHA-384, SHA-512, SHA-512/224 and SHA-512/256 (createHash, createHmac,
crypto.hash, pbkdf2, hkdf) process their blocks with the fastest kernel
the CPU supports:

- x86-64 with AVX2 and BMI2: the message schedule is computed two words
  at a time in vector registers, interleaved with the scalar rounds,
  which rotate with RORX.
- x86-64 with AVX-512VL: the same, with vector rotates and three-input
  XOR in the schedule.
- arm64 with the SHA-512 extension: SHA512H, SHA512H2, SHA512SU0 and
  SHA512SU1.
- Otherwise the portable blocks, now unrolled over a 16-word schedule
  ring.

The CPU and OS checks run once and are cached. The update and final
paths pass all their whole blocks to the kernel in one call.

test_sha_kernels.c checks the FIPS 180-4 SHA-384 and SHA-512 vectors
through every kernel the CPU supports and compares each kernel with the
portable blocks over every length from 0 to 1100 bytes and 1 MiB, whole
and in 1-, 7-, 64- and 100-byte updates. Corpus 5779 compares SHA-384,
SHA-512 and HMAC-SHA512 digests over many lengths and update sizes with
Node; tests/harness/corpus-costs/ records its plain and sanitized costs.
2026-10-19 23:22:23 -05:00
Chris Tate 7e0106722b Widen an unjudged backlog threshold with its level's growth fraction
The backlog threshold of a level backs off by doublings as backlog passes
find little to free, but only passes that had candidates count. A program
whose mature passes have no candidates while its heap grows keeps the base
threshold, a quarter of the live heap, while those same passes widen the
level's growth fraction. When it then reads the structure it built, the
mature buffer fills with live objects and the first backlog pass walks a
quarter of the live heap to find nothing. In tsc-ts's program build on
Sentry, that pass ran inside the loader walk and took 9 to 18 ms.

Until a pass at a level has had candidates, the backlog threshold now uses
the level's growth fraction doublings. The first pass with candidates
judges the level, and the threshold follows its own yield from then on.
2026-10-19 23:18:55 -05:00
Chris Tate 60f5c852e0 Print holes in arrays reached through unknown as Node does
A holey array converted to a checked-dynamic value keeps its holes (the
conversion records them in the array's presence map), but the runtime
inspector printed every element: `const u: unknown = a` for
`a[2] = 1` printed `[ undefined, undefined, 1 ]`, where Node prints
`[ <2 empty items>, 1 ]`, and `new Array(3)` through `unknown` printed
`[ undefined, undefined, undefined ]`.

The inspector now prints each run of holes as one `<n empty items>`
entry that counts toward the 100-entry limit, as Node's
formatSpecialArray does and as the typed array inspector already does.
2026-10-19 22:31:09 -05:00
Chris Tate 80355af8ea Give errors held as any their own stack accessor
In Node every Error has an own, non-enumerable `stack` accessor, shared by
all errors, that formats the stack on first access. Errors that reached
checked-dynamic code read `stack` as undefined: the error encoding carried
a stack only when a typed read had already formatted one. Reads on
instances of Error subclasses found no class arm for it, and assigning
`stack` on a subclass instance, typed or through `any`, did not change
what a typed read answered.

The encoding now lists `stack` first among its own properties as a
non-enumerable accessor. Its getter formats the native error's stack on
first access, so the heading is the name and message at that point, and
its setter stores a string in the native error, which typed reads share.
Any other value replaces the accessor with a data property. Objects that
inherit from an error read the error's stack, and a getter called on any
other receiver answers undefined.

A `stack` read on a checked-dynamic value counts as a stack observation,
so such programs capture frames as they do for typed reads. Dynamic reads
and writes of `stack` on Error subclass instances use the native stack;
non-string values are kept with the instance's own properties.
2026-10-19 22:26:32 -05:00
Chris Tate 0b00ecfc23 Read x.constructor on EventEmitter and stream instances
`x.constructor` on a value typed as EventEmitter or a stream class
stopped at SC2020. The runtime now records each instance's Node class
name: fs streams as ReadStream and WriteStream, child process stdio as
Socket, worker stdio as ReadableWorkerStdio and WritableWorkerStdio, and
Hash/Hmac stream views as Hash and Hmac (zlib streams, the base classes
and program subclasses already did). The name is the one Node's leak
warning and argument-type errors print, so those messages now name the
same class as Node.

`x.constructor.name` reads that name (scr_emitter_class_name).
`x.constructor === C` for EventEmitter or a stream class tests the
instance's own class: C's interval, none of its direct subclasses', and
C's name, as fs, zlib, crypto, child process and worker streams share the
base classes' vtables. `x.constructor` as a value is the class object of
EventEmitter or the stream class the instance belongs to; for instances
of the other runtime classes and of program subclasses it reports SC1090
at run time.
2026-10-19 21:44:33 -05:00
Malte UblandClaude Opus 5.5 fa64a7cf5a Merge main into opt/bc-node-surface
Main split lower-exprs.ts into expression families; the typeof and __dirname changes move to expressions/typeof.ts and expressions/identifiers.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:42:15 -07:00
Chris Tate 3ae4172f4d Keep thrown arrays' identity and inspect thrown values in test failures
A thrown array reached its catch binding as a type-erased native array:
`console.log(e)` printed `{}` where Node prints the array, `String(e)`
gave "[object Object]", `Array.isArray(e)` was false and a typed view
such as `(e as number[]).length` threw a TypeError. Thrown records
already crossed the catch boundary as checked-dynamic values that keep
their identity; arrays, and unions over records and arrays, now take the
same conversion, so the catch binding is the thrown object.

node:test printed a failing test's thrown non-Error value with String():
`throw { code: 42 }` reported `[object Object]` where Node prints the
value as util.inspect renders it. The failing-tests section now inspects
thrown numbers, booleans and checked-dynamic values (records, arrays,
null and undefined); a thrown string is quoted as before. A thrown class
instance that is not an Error still prints `[object Object]` (the
limitations page says so).

Adds corpus 6341, extends the thrown-values node:test fixture, and
records 6341's cost.
2026-10-19 21:06:20 -05:00
Malte UblandClaude Opus 5.5 20ab1c0531 Merge main into opt/bc-node-surface
Resolve the conflicts with main's process.features value, the WebCrypto dynamic types, the NODE_COMPAT_VERSION import and the symbol.declarations API. Renumber this branch's corpus programs to 6420-6429, write the new limitations partial one sentence per line, and regenerate the compatibility outputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:02:41 -07:00
Chris Tate 912bf6e9e9 Fail the runtime build when two handle tags share a value (#1430) 2026-10-19 20:33:19 -05:00
Chris Tate b56d1c9ba7 Fail the runtime build when two handle tags share a value
ScrDynHandleTag listed its tags by name with explicit values, so two
changes that each took the next free value merged without a conflict and
produced two tags with one value; only dyn-handle-tags.test.ts noticed.
#1397 (CRYPTO_KEY) and #1259 (ASYNC_DISPOSABLE_STACK) both took 56.

SCR_DYNH_TAGS is now the one list of tags: X(NAME, value) rows, sorted
by name, generate the enum, a _Static_assert per tag that the value is
below SCR_DYNH_CAPACITY, and scr_dynh_tags_distinct, a never-called
switch with a case per tag. Two tags with one value are a duplicate case
value, so every runtime unit that includes scr_dyn.h fails to compile and
names both tags. The values, the names and the generated code are
unchanged. dyn-handle-tags.test.ts reads the rows.
2026-10-19 20:27:22 -05:00
Chris Tate 8d4737dfdb Merge main 2026-10-19 20:27:03 -05:00
Chris Tate bb6270efc1 Fix silent divergences: constructor returns, arr && arr[0], ES5 class names, builtin function values, uninitialized let writes, module-export this, 1 ** Infinity (#1351) 2026-10-19 19:56:36 -05:00
Chris Tate 950ec9ee85 Give prototype name stamps their own copy of the name
A construction through a plain function stamps the prototype with the
name of the function in its `constructor` property, for util.inspect.
The stamp pointed at the function's name. A bound function's name
("bound " and the target's name) is a string the bound function owns,
so after the `constructor` property was replaced and the bound function
released, inspecting an instance read freed memory. The stamp now holds
a copy of the name, freed with the stamp at exit.

The corpus program constructs through functions whose prototype's
`constructor` is a bound function (and a bound function of one), then
replaces the property and inspects the instances.
2026-10-19 19:51:44 -05:00
Chris Tate 4770b3ffa7 Fix pi's small compile gaps (#1348) 2026-10-19 19:50:07 -05:00
Chris Tate adb879f678 Compile the real jose root entry: string durations, chained builders and navigator (#1407) 2026-10-19 19:45:13 -05:00
Chris Tate 31475715a0 Compare strings in UTF-16 code-unit order (#1424) 2026-10-19 19:04:23 -05:00
Malte UblandClaude Opus 5.5 503154e387 Compile the boot-path Node surface: process.send presence, process.exitCode reads, wider stream hooks, void race entries, RequestOptions and promisify of callbacks
- `if (process.send)`, `Boolean(process.send)`, `!process.send` and `typeof process.send` answer whether the process has an IPC channel (a fork channel this binary opened, or NODE_CHANNEL_FD from a Node.js parent). The same holds for process.disconnect.
- In a program without fork targets, `process.send?.(...)` answers undefined, and a direct call throws Node's TypeError when there is no channel. A Node.js parent's channel has no lowering, so a call fences when it runs. `process.send!(...)` lowers like the direct call.
- process.exitCode reads answer the assigned code, or undefined. Only programs that read it record assignments, including process.exit(code) before the 'exit' listeners run. Other programs keep the untracked write.
- A stream subclass hook may declare a supertype of Node's argument (`_write(chunk: Buffer | string, ...)`): the wrapper takes Node's Buffer and widens it.
- Promise.race accepts a Promise<void> entry when the result holds undefined.
- http.RequestOptions and https.RequestOptions as declared types ride checked-dynamic storage, like SpawnOptions.
- util.promisify over a typed callback-last function value (a bound method, a declaration, an arrow) builds a native promisified function. A truthy error rejects, the value fulfills, and a synchronous throw rejects.
- The bundled Node declarations gain the callback-only generic promisify overloads. Before this, `promisify(obj.close.bind(obj))` matched crypto.randomFill's overload and reported "Expected 1-3 arguments".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 16:46:46 -07:00
Chris Tate 68f930bf53 Keep the global object in type assertions of globalThis (#1423) 2026-10-19 18:42:22 -05:00
Chris Tate 53af3dc930 Compare strings in UTF-16 code-unit order
Relational string operators (`<`, `<=`, `>`, `>=`) compared the UTF-8
bytes, which is code-point order. Node compares UTF-16 code units, so the
results differed for characters above U+FFFF against U+E000..U+FFFF and
for lone surrogates against surrogate pairs. The default `sort()`
comparator, `util.inspect` with `sorted: true` and dynamic comparisons
already used UTF-16 order; the default comparator of a dynamic array's
`sort()` did not.

Typed comparisons and the dynamic array sort now use `scr_str_cmp_u16`,
and the IR's `strCmp` node no longer has a `utf16` flag. The two orders
agree unless both of the first differing bytes are at least 0xED, so:

- the inline wrapper decides differing first words, or last words of
  equal lengths up to 16 bytes, in byte order when neither word has a
  byte of at least 0xED (a SWAR test). Other differing words, and the
  first bytes of shorter strings, decide by their first differing byte:
  in byte order when one of the two is below 0xED, and by plane when both
  are lead bytes from 0xEE to 0xF4 (a supplementary character orders
  below U+E000..U+FFFF);
- otherwise a proven-ASCII string on either side compares with `memcmp`
  (`scr_str_cmp`), and other pairs call `scr_str_cmp_u16_bytes`. It finds
  the first differing byte with words up to byte 32, then with 32-byte
  SSE2 or NEON blocks where the target has them. The call is marked as
  only reading its arguments, so repeated comparisons of one pair merge.
- `scr_str_cmp_u16` itself takes the same `memcmp` path for proven-ASCII
  strings.

The native compiler sorts record fields with `<` and checks the order
with `sort()`, so it reported SC9001 for 5888-inspect-sorted.ts, whose
object has the keys "\u{1F600}" and "\uffff". 5888 runs in the
native-frontend self-host check. Adds corpus 6023.
2026-10-19 18:10:54 -05:00
Malte UblandClaude Opus 5.5 6255c9351b Keep the global object in type assertions of globalThis
`globalThis as { [KEY]?: State }` is the usual way for several modules
to share process-wide state. globalThis lowers to a checked-dynamic
value, and asserting a checked-dynamic value to a record or array type
took the checked conversion, which builds a new native value. A write
through the asserted value went to that copy, so other modules never
saw it; string-keyed and index-signature views threw "expected object".

- An assertion of the global object (globalThis, global, also through
  `as unknown as`) keeps the checked-dynamic value when the conversion
  would copy, so member reads and writes reach the global object.
- File-scope and local bindings of such an assertion hold the global
  object (collectGlobals, lowerVarDecl), so an exported
  `const state = globalThis as ...` works across modules.
- The divergence scan no longer reports these assertions as SC6001
  width copies.
- Runtime: one global-object box per thread (scr_global_object), which
  globalThis, global and globalThis.globalThis all answer, and direct
  `globalThis[symbol]` accesses use that box's symbol-keyed properties,
  so every spelling of the global object sees one property.

Other checked-dynamic values keep the checked conversion: a native
value held as unknown unwraps by identity there, and a checked-dynamic
tree is copied at the documented boundary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 15:52:52 -07:00
Chris Tate 79ecacfb5d Merge main 2026-10-19 17:08:37 -05:00
Chris Tate c82b6d8188 Merge main 2026-10-19 17:06:58 -05:00
Chris Tate c7329845b1 Merge main 2026-10-19 16:48:01 -05:00