Commit Graph
2458 Commits
Author SHA1 Message Date
Chris Tate ffd760bfa2 Merge main 2026-10-18 04:55:11 -05:00
Chris Tate 5093ab9a7a Add markAsUncloneable and worker[Symbol.asyncDispose] (#1269) 2026-10-18 04:41:04 -05:00
Chris Tate 5e069c69d2 Convert errors held as any with Error.prototype.toString (#1270) 2026-10-18 04:33:57 -05:00
Chris Tate c88981a466 Describe why mock.timers' tick leaves a fired timer queued
scr_mt_tick removes a fired entry only while it is still the queue's
first entry, as Node's MockTimers.tick does. A callback that queues an
entry sorting before it, such as an immediate due at now - 1, leaves the
fired timer queued, so it runs again, as it does in Node. The comment
now says so and names the fixture that checks the order against Node.
2026-10-18 04:32:24 -05:00
Chris Tate 58096a768c Define scr_uncloneable_count with an initializer
packages/runtime/test/common-symbols.test.ts requires runtime globals to
be definitions, not tentative definitions that some toolchains emit as
common symbols; scr_uncloneable_count now starts at 0 explicitly.
2026-10-18 04:19:12 -05:00
Chris Tate 6d80207e4c Convert errors held as any with Error.prototype.toString
Errors that reach checked-dynamic code are objects carrying the reserved
"%error" marker, with name and message either own or on their prototype.
Several string conversions handled them differently from Node:

- `"" + error`, `error + ""` and `error == "TypeError: ..."` converted
  through the plain-object path and answered "[object Object]".
- Template literals and Array join read name and message as own members
  only, so a builtin error whose name is inherited printed its message
  alone.
- String(error) used the native error, which did not see name and message
  assigned through the dynamic view.

scr_dyn_error_string implements Error.prototype.toString over the
encoding, reading both members through the prototype, and the template
renderer, Array join, ToPrimitive, ToString and the numeric conversion use
it. Assigning a string name or message through the dynamic view now also
updates the native error, as assigning stack and cause already did.
2026-10-18 04:07:51 -05:00
Chris Tate f9682f3edf Skip WebAssembly irreducible control flow analysis on reducible functions (#1264) 2026-10-18 04:07:36 -05:00
Chris Tate ab1c271dde Support the regex v flag (#1268) 2026-10-18 04:01:38 -05:00
Chris Tate a18335e6c4 Revert "Return a worker's memory when it exits without sharing any of it" (#1267) 2026-10-18 04:00:54 -05:00
Chris Tate a0b1e93dec Merge main 2026-10-18 03:52:20 -05:00
Chris Tate 4f3bf565d6 Key the differential oracle cache on the inherited environment (#1266) 2026-10-18 03:51:05 -05:00
Chris Tate 51777dee63 Add markAsUncloneable and worker[Symbol.asyncDispose]
markAsUncloneable(value) marks a plain object, a record, an error or a
class instance (scr_json.c); structuredClone and postMessage then throw
DataCloneError "Cannot clone object of unsupported type." for it, also
where it is nested. As in Node, the mark has no effect on primitives,
arrays, typed arrays and ArrayBuffers. Marked values are retained until
the thread exits; with nothing marked, the clone paths add one branch.

worker[Symbol.asyncDispose]() terminates the worker and resolves with
undefined after its 'exit' event, so `await using` disposes a Worker at
the end of its block. The Worker handle's symbol_get op answers the
well-known symbol: the symbol unit registers Symbol.asyncDispose with the
dyn core when the program names it, and scr_dyn_is_async_dispose_symbol
tells the worker unit, so worker programs that never name the symbol do
not link the symbol unit.

static-support.json adds rows for markAsUncloneable, isInternalThread
(already lowered as false and declared) and worker[Symbol.asyncDispose]().

Corpus 5990-5991 match Node v24.22.0; corpus-costs.json records their
costs.
2026-10-18 03:43:06 -05:00
Chris Tate d375212a32 Add BroadcastChannel and the remaining Node 24 worker_threads surfaces (#1189) 2026-10-18 03:38:36 -05:00
Chris Tate 84916f25e4 Support the regex v flag
Regex literals and new RegExp calls accept the v (unicode sets) flag, which
the vendored engine implements: class subtraction and intersection, nested
classes, \q{...} string literals and properties of strings. The flag uses
unicode mode for pattern decoding and string iteration, and flags read in
getter order (dgimsuvy). Regex literals now also report their flags in
getter order, as Node does for /x/yg, and the flag getters (global,
unicodeSets and the rest) compile on statically typed regexes.

In v mode the engine rejected `-` and doubled punctuation outside a
character class, and the `\-` escape inside \q{...}. It now applies the
class-only checks inside classes and \q{...} only, and parses \q{...}
contents with the class escapes, matching Node. The d flag is still
refused.
2026-10-18 03:37:40 -05:00
Chris Tate 3fd1d2bde6 Revert "Return a worker's memory when it exits without sharing any of it (#1258)"
This reverts commit 2385fc4fff.
2026-10-18 03:37:07 -05:00
Chris Tate 1899b5f6ae Key the differential oracle cache on the inherited environment
The Node oracle runs with the harness's environment, but its cached
verdict was keyed only on SCRIPTC_TEST_ENV among the variables. A
program whose output reads NODE_ENV or another variable reused a
verdict recorded under a different value. The key now covers every
inherited variable except the test runner's worker numbers, which
differ between workers of one run.

Closes #51
Co-authored-by: Patrick Wozniak <4481570+patlux@users.noreply.github.com>
2026-10-18 03:28:59 -05:00
Chris Tate d7ac6d3165 Merge main 2026-10-18 03:17:25 -05:00
Chris Tate 86e3354373 Free large dead cycle sets in address order (#1265) 2026-10-18 02:47:30 -05:00
Chris Tate 85ed5fdb5a Lower Date.prototype.toJSON with Node's null for invalid dates (#1255) 2026-10-18 02:30:06 -05:00
Chris Tate 0a5e2442df Read .groups on matchAll rows in JavaScript callbacks (#1262) 2026-10-18 02:26:22 -05:00
Chris Tate f7a9fa988d Free large dead cycle sets in address order
A cycle pass tears down its dead set in the order the walk found it, so
the LIFO free lists handed the blocks of a large dead structure to the
next allocations scattered over every page it used, and the structures
built from them missed the cache on most objects. Sets of at least 16384
objects are now torn down in descending address order
(scr_mem_order_for_free, a bitmap of block indices per size class), so the
free lists hand the blocks back in ascending order, the order the bump
pointers carve fresh memory in.

A full pass takes every candidate buffer, so markGray now clears each
reached object's buffered mark in the header write it makes anyway,
instead of a separate loop over the candidates.
2026-10-18 02:24:53 -05:00
Chris Tate 7c0db7d2b3 Merge main 2026-10-18 02:07:18 -05:00
Chris Tate fe47b3861f Add Float16Array (#1249) 2026-10-18 02:06:17 -05:00
Chris Tate 8e677e5053 Merge main 2026-10-18 02:04:31 -05:00
Chris Tate 2385fc4fff Return a worker's memory when it exits without sharing any of it (#1258) 2026-10-18 01:54:28 -05:00
Chris Tate c7ffd12705 Give querystring.parse results a null prototype (#1263) 2026-10-18 01:48:07 -05:00
Chris Tate 24096c6e67 Remove the leftover always-false isFloat16Array probe
The util.types brands answered isFloat16Array with false for every value.
Float16Array now answers it from the value's storage kind, ahead of that
probe, so this drops the unreachable entry and the declaration that
returns boolean.
2026-10-18 01:41:19 -05:00
Chris Tate 3414144f86 Lower mapped types over a generic function's type parameters (#1256) 2026-10-18 01:39:00 -05:00
Chris Tate 752d58ee8a Answer in for class methods and accessors on instances with runtime prototypes (#1261) 2026-10-18 01:38:22 -05:00
Chris Tate d8f707a35e Drop the util.types.isFloat16Array entry main already adds 2026-10-18 01:35:31 -05:00
Chris Tate 588e16d8c7 Skip WebAssembly irreducible control flow analysis on reducible functions
LLVM's WebAssembly pass that gives multiple-entry loops a single entry
first records, for every block, every block it can reach. Its time and
memory grow with the square of a function's block count even when the
function has no such loop. In the three.js nodes fixture a module
initializer of 40,000 blocks spent four minutes and 22 GB in it, which
set the helper's peak and most of its wall time.

The helper now runs the pass only on functions whose control flow graph
is irreducible, and WebAssembly object emission leaves out the module
pass that collects external symbols for assembly declarations, so each
function's machine code is freed once it is printed. Optimized
partitions are compiled through the thin backend's pre-codegen hook to
use the same pipeline. Objects are unchanged.
2026-10-18 01:30:29 -05:00
Chris Tate 3999311319 Give the discard test's trim run an array of its own
check_discard ran trim_allocate again for its returned-regions case, which
writes the blocks into trim_blocks, the array check_trim had freed. Where
glibc maps an 8 MB block by itself (its adaptive mmap threshold, which the
allocator's heap policy leaves alone unless transparent huge pages are in
madvise mode), the free unmaps it and the worker thread's first write
faults. The case now allocates the array again and frees it.

The check that a worker takes the free blocks set aside compared the
highest block it carved with that of the worker before it, which took its
blocks from free lists below the slice's frontier, and it passed when a
worker never took them. Each run now counts the blocks it allocates past
the highest block of the class so far: at most the bump room the slice had
left, against all of them when a worker does not take the free blocks. The
residency bound after a discard allows for pre-fault requests still in
flight.
2026-10-18 01:30:10 -05:00
Chris Tate 8a2fbeec35 Give querystring.parse results a null prototype
querystring.parse and querystring.decode build their result with a null
prototype, as Node does: util.inspect prints the [Object: null prototype]
base, Object.getPrototypeOf answers null, ToPrimitive throws a TypeError,
and Object.prototype methods are not reachable through the result.
2026-10-18 01:26:35 -05:00
Chris Tate 6cdff93888 Merge main 2026-10-18 01:24:45 -05:00
Chris Tate 53a8b0af56 Window long source lines in diagnostic code frames (#1260) 2026-10-18 01:23:48 -05:00
Chris Tate 5d56b57a80 Read .groups on matchAll rows in JavaScript callbacks
An inline JavaScript callback receives a matchAll row as a dynamic
array. The groups projection now reads its captures through dynamic
index reads, and throws Node's TypeError for null and undefined rows.
Rows kept by filter, slice, toReversed and toSorted, or picked by find,
findLast and at, trace to the drain's regex; a reassigned callback
parameter does not. { groups } patterns serve dynamic rows and pattern
parameters. The groups record has a null prototype.

maybeNarrow takes the receiver's declared shape from its non-nullable
type, so a JavaScript read of a nonparticipating group keeps its
undefined arm instead of narrowing to string.
2026-10-18 01:22:45 -05:00
Chris Tate 9750d3994a Merge main 2026-10-18 01:21:52 -05:00
Chris Tate 9498d93c52 Merge main 2026-10-18 01:18:53 -05:00
Chris Tate aa9c6d2be9 Answer in for class methods and accessors on instances with runtime prototypes
`"method" in instance` on a class instance held as `any` answered false
for the class's methods, getters and setters when the program also uses
instanceof or Object.getPrototypeOf on the class. Those uses attach the
class's prototype data to the instance's snapshot, and that object holds
only the methods the program reads through the prototype; the literal-key
`in` then searched it instead of the class.

When a literal key names a method or accessor of a boxed class with
prototype data, the class dynamic-dispatch pass now evaluates `in`
through the computed-key form, which answers class members from the class
itself.
2026-10-18 01:17:39 -05:00
Chris Tate 98cbdb9a4f Merge main 2026-10-18 01:16:36 -05:00
Chris Tate d60978acb2 Match Node 24 in util.format, util.types and util.inspect (#1229) 2026-10-18 01:16:04 -05:00
Chris Tate 0309ff63cb Window long source lines in diagnostic code frames
renderDiagnostics printed every frame line whole and rebuilt the
file's line index for each diagnostic. On minified sources with
thousands of diagnostics the joined report exceeded V8's string length
and `scriptc coverage` crashed with RangeError: Invalid string length.
Frame lines longer than 400 characters now show a 400-character window
around the diagnostic's column, with "…" where text is cut, and the line
index is built once per file. Every diagnostic is still rendered.

Closes #201
Co-authored-by: Max B <32987678+techfreaque@users.noreply.github.com>
2026-10-18 01:00:47 -05:00
Chris Tate 54ab14d604 Keep declared methods of generic class instances after a computed-key write (#1215) 2026-10-18 01:00:16 -05:00
Chris Tate dc1cb7c1d9 Merge main 2026-10-18 00:53:58 -05:00
Chris Tate b06e06f871 Merge main 2026-10-18 00:53:19 -05:00
Chris Tate 8077a9bfff Add DisposableStack (#1248) 2026-10-18 00:50:48 -05:00
Chris Tate 3f84d6475d Merge main 2026-10-18 00:50:24 -05:00
Chris Tate b15226075e Settle async generator completions on Node's microtask turns (#1251) 2026-10-18 00:48:44 -05:00
Chris Tate add07dba3d Return a worker's memory when it exits without sharing any of it
On Linux, a worker thread that exits without having made any object it
allocated reachable from another thread gives back all of its memory: the
pages it carved, including ProgramLifetime instances and other objects it
never freed, and the system blocks it still holds.

A worker carves past each slice's frontier while the arena's free lists and
returned regions are set aside, takes from those free lists only once a
slice has no bump room left, and records the system blocks it allocates.
Messages and intern tables that share published graphs by pointer, and the
process-wide key arrays of typed records, mark the thread as sharing; so
does a remote free of one of its blocks. When a worker that did not share
parks, the trim helper returns its pages, frees its system blocks, and
restores the arena to its state at the claim, with the free blocks the
worker took free again. A worker that shared keeps its memory as before.

SCR_SA_DISCARD=0 keeps every worker's memory; SCR_SA_DISCARD=poison makes
the discarded pages inaccessible instead, for tests.
2026-10-18 00:46:59 -05:00
Chris Tate 58e6396a1d Merge main 2026-10-18 00:42:37 -05:00