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.
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.
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.
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.
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.
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.
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.
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.
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.
Library exports and callbacks passed text only as a pointer and length.
profile_format 2 adds the cstring class, a NUL-terminated `const char *`
(an i32 pointer in Wasm reactors), for export parameters, export results
and callback parameters. Parameters are copied in and decoded as UTF-8,
with U+FFFD for malformed bytes; a NULL pointer is the SC4012
host-contract trap. Results live in the result arena like string
results. A result or callback argument containing U+0000 traps instead
of being truncated.
Closes#615
Co-authored-by: ailuoku6 <23209876+ailuoku6@users.noreply.github.com>
An async generator that fell off its end, with a return type other than
void, settled its last next() one microtask turn after Node, so for await
loops over it also ended a turn late. The appended implicit return now
marks the completion as settled, like a bare `return;`.
return() at a suspended yield ran the generator's finally blocks before
awaiting the return value. The request now awaits the value first and
resumes the body on the next turn, and the completion that follows settles
without a further turn, also when a finally block yields or awaits first.
An explicit `return x` reached during that unwind still takes its own turn
to await x.
Array.fromAsync accepts typed async generator sources: each next() result
is awaited and its value used as is, a throwing or rejecting mapping
function closes the generator, and a rejected next() rejects the result on
the turn Node rejects it.
Object.setPrototypeOf and Object.create threw "Object prototype may only be
an Object or null" without the value Node appends. The message now ends
with the value's string form, as in Node: "...: 7", "...: undefined",
"...: Symbol(tag)".
A prototype or receiver that is a valid object of a kind these functions
do not handle yet (a function or Map prototype for Object.create, a Map
prototype or receiver for Object.setPrototypeOf) threw the same TypeError.
It now throws an Error that says the operation is not supported for that
value.
Float16Array is a native typed-array kind with IEEE 754 binary16 storage.
Stores round the number to nearest-even in one step from the double, as
Node does, including overflow to Infinity, subnormals, -0 and NaN; reads
widen exactly. The runtime converts with integer operations, so no target
needs half-precision hardware or compiler-rt support.
Construction, element reads and writes, ArrayBuffer views, fill, set,
subarray, slice, both sort orders, constructor values and checked-dynamic
storage work as for the other typed arrays. Math.f16round,
DataView.prototype.getFloat16 and setFloat16, and util.types.isFloat16Array
compile.
new DisposableStack() compiles to a native stack handle. use, adopt, defer,
move, dispose, disposed and [Symbol.dispose] are members of its dynamic
view, and the stack works with `using`. Disposal runs the resources in
reverse order, calls use() resources' dispose methods with the resource as
`this`, and raises a SuppressedError chain when more than one disposal
throws; a single failure is rethrown as thrown. Calls on a disposed stack
throw Node's ReferenceError, and invalid receivers, resources and
callbacks throw Node's TypeErrors.
use() of a class instance registers a function that calls the class's
[Symbol.dispose] method, and returns the instance at its static type; use()
of a union that includes class instances is refused. The stack is
collected with its callbacks when a callback captures the stack.
x instanceof DisposableStack compiles. Handles can expose symbol-keyed
members through a new symbol_get hook.
Chunks from net sockets (including socket.read()), http messages and
upgrade heads, child_process output, dgram messages, http2 streams and
TLS sessions were byte arrays without the Buffer brand. String
conversion of them joined the byte values ("112,105,110,103") instead
of decoding UTF-8, so `body += chunk` and template literals differed
from Node. They are now Buffers, as in Node.
Closes#903
Co-authored-by: João Carlos Magalhães de Castro <88864312+JohnC0de@users.noreply.github.com>
`value.valueOf()` on a dynamic receiver was refused at compile time, a
class's own toString was not reached by `value.toString()`, and classes
could not declare a `[Symbol.toPrimitive]` method.
- `value.valueOf()` lowers to dyn.valueOf, which answers each kind's own
valueOf: a primitive itself, a Date its time value, an object with a
valueOf member that member's result, and any other object itself. A
null-prototype dictionary throws "<spelling> is not a function", and
null and undefined throw Node's property-read TypeError.
- When a boxed class declares valueOf or toString, `value.valueOf()` and
`value.toString(argument)` call the class's method for its instances
and keep the runtime call for every other value.
- Classes can declare `[Symbol.toPrimitive](hint)`. The dynamic
conversions call it with "default" for `+` and `==`, "number" for the
numeric operators and conversions, and "string" for template literals,
String() and property keys, and throw Node's TypeError when it returns
an object.
Buffer.write(text, "utf16le") zero-extended an ASCII run eight bytes per
step with shifts and masks in 64-bit words. Where the target has vector
unpacks (SSE2 on x86-64, NEON on AArch64) it now widens sixteen bytes
per step: one load, a test of the high bits, and two unpacked stores.
The word loop stays for the remaining bytes and for other targets. A
scanner that fills a Uint16Array of code units this way, as tsc-ts does
once per source file, spends about half as long in the copy.
- util.format %d and %i format bigint, symbol, null and undefined arguments
- util.format %j prints [Circular] for circular arguments
- util.types answers the remaining brand checks
- util.inspect prints WeakMap, WeakSet, Date and numeric typed array values as Node does
- util.inspect lowers the breakLength, compact and sorted options