forEach, map, filter, some, every, find, findIndex, findLast,
findLastIndex and flatMap on a dynamic array, and through
Array.prototype.<method>.call on a native array, called the callback
without its thisArg, so `this` in the callback was not the second
argument. These methods now bind `this` to the thisArg, or undefined
when there is none, once per call, around all of the method's callbacks.
A limited pass (scr_cyc_collect_set, which freeing a LifetimeRegion runs
over the objects the region owned) takes only the candidates in its set.
Its walk can still reach other candidates, and one that is referenced from
outside is blackened by scan and promoted to the next generation while it
keeps its slot in its old generation's buffer. scr_cyc_on_dead finds a
candidate's buffer by its generation, so when that object died it removed
another buffer's entry and left its own slot pointing at the freed block,
which the next pass then read.
Such a candidate is no longer promoted: it keeps the generation of the
buffer it is in until a pass takes it.
The test kept one live 48-byte block every 1,000 allocations, which on a
system with 64 KiB pages leaves a live block on every page, so no page held
only free blocks and the resident-memory check failed. It now keeps one
block per four pages of blocks, from the system's page size.
The native compiler's frontend-dispose sweep frees about 450 MB on a cold
tsc-ts build, but the size-class allocator keeps the freed blocks on their
classes' free lists, which only later blocks of the same size reuse. Code
generation allocates other sizes, so its pages stacked on top of the freed
heap and set the build's peak resident memory.
- scr_heap_trim (scr_alloc.c) returns the pages of the calling thread's
size-class slices that hold only free blocks (MADV_DONTNEED) and the
system allocator's free pages (malloc_trim). In the single-threaded
allocator the blocks of a returned page leave the free list and the
class carves past its frontier instead; the remaining free blocks are
relinked in address order. Worker runtimes return the calling thread's
arena pages as a parking thread does.
- scr_collect_cycles_phase, the full sweep after a phase's data died,
calls it; the native compiler calls that sweep when it disposes the
frontend.
- test_heap_trim.c checks that the pages go back, that the blocks still
allocated keep their contents, and that new blocks overlap none of
them, with and without worker arenas (another thread allocating and
freeing remotely throughout).
Cold tsc-ts build, 12 interleaved rounds: peak RSS 1407 -> 1247 MB
(-160 MB, -11.4%, every round); wall 87.1 -> 85.9 s (median).
- Index walks (slice, substring, substr, startsWith/endsWith with a
position, lastIndexOf results) on a non-ASCII receiver of 64 KiB or
more: once they would have paid for one pass over it, the receiver's
index entry keeps every unit's byte offset (a 32-bit base per 64 units
and one byte per unit), and indexes map with two loads. Before that, a
walk within 64 units of the hot cursor starts from it, without the index
search. The index entry stays 128 bytes on 64-bit targets.
- lastIndexOf on non-ASCII receivers tests the needle's first byte before
comparing, as the ASCII path does.
- normalize: a quick check (UAX #15) returns the receiver when every code
point is inert for the form: unchanged when normalized alone, combining
class 0, and, in the ranges checked, never the second element of a
composition. Other text normalizes segment by segment, each starting at
a starter, so runs of inert code points copy as they are and a lone
precomposed letter's result is kept per form. The per-code-point facts
come from unicode_normalize, 256 code points at a time.
Node for macOS on arm64 is compiled by clang, which contracts a multiply
and an add within one expression into a fused multiply-add. Its sin, cos,
tan, asin, acos, atan, sinh, asinh, acosh, atanh, exp, expm1, log, log1p
and log10 differ from the unfused results in the last bit for some inputs,
so corpus 7717 and 7718 failed on macOS. scr_ieee754.c now sets
FP_CONTRACT ON for Apple arm64 targets. Other targets keep it off and
compile to the same code as before.
A checked cast of a dynamic value to a record or array (value as T) built a
separate native copy: writes through the result missed the source and every
other holder, and === was false, where Node's as does nothing at run time.
Every such cast now runs the declaration boundary's adoption leniently
(dyn.adoptShared): each source object or array something else still holds
becomes, in place, a live capsule of the native value built from it. A node
that is not plain data, or that the value holds twice, keeps the copy. A
source nothing else can hold converts outright, which keeps the fused
JSON.parse(text) as T: a fresh JSON.parse or response.json(), workerData read
at one site, the payload of the program's one message listener, and a cast
whose result serves one primitive member read.
Data-only object and array literals in untyped slots the program declares
(unknown or any variables, fields, parameters, returns, unknown[] elements),
thrown literals and their members are built as dynamic objects, so their
later casts adopt them too.
A boxed native value cast to another native layout keeps its cached copy
(object model decision D3); the harness test pins it.
Ported from the fix/dyn-record-identity prototype.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When an untyped destination function returned an async iterable, the
promise form settled its result without a payload, and code that read the
awaited value as an untyped value dereferenced NULL. The promise form for
an untyped destination now resolves to undefined when the destination
returned no promise, or its promise carries no value, as Node does.
A pipeline stage that arrives as an untyped value now follows Node's
stage ladder for more shapes. As the first stage, a function is a source
function: the pipeline calls it with { signal } while it is set up and
pumps its result, which must be iterable (ERR_INVALID_RETURN_VALUE
otherwise), and a { readable, writable } pair pumps its readable side.
After the first stage, the pipeline call is lowered once per combination
of its first three untyped stages being functions or not, and a typeof
test picks the branch at run time. A function stage is called with the
previous stage and { signal }; before the last stage its result must be
async iterable, and as the last stage a promise resolves the pipeline's
value and an async iterable feeds the PassThrough pipeline() returns
(SCR_PIPE_DEST_DYN). Any other value that is not a stream becomes
Duplex.from of it, which the next stage reads from without the previous
stage being piped into it, and Duplex.from's ERR_INVALID_ARG_TYPE for
the values it rejects.
An untyped stage followed by an untyped function stage hands its value to
that stage through a cell created per pipeline call, so the function
receives the previous stage's own iterator, as Node's makeAsyncIterable
answers it, instead of an async generator over it.
Untyped web streams, pairs after the first stage, and function stages
that return a stream throw the not-supported error.
RUNTIME_ABI_VERSION goes from 45 to 46 for the new stream exports.
The single object model keeps an object's layout id in the high half of its
rc word, beside the program-lifetime and frozen bits. Class ids already live
there; this names the encoding and moves the runtime's tests that may see an
object with an id onto the count:
- scr_rc_layout_id reads the 30-bit layout id; scr_rc_class_id is now that id.
- scr_rc_immortal tests the count, and scr_rc_frozen and Object.freeze of a
record use it instead of comparing the whole word with SIZE_MAX.
- Publication writes only the count of any object with a layout id, so an
immortal object keeps its id, as a class instance keeps its class id.
- classIdRcWord refuses a class id outside the layout id range.
No object other than a class instance carries an id yet, so behavior and
emitted code are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`scriptc types patch <package> <selector> <type> [--range] [--dry-run]`
finds the installed package from the working directory, shows every
declaration the selector matches (declaration files, JSDoc, @types
package) with its type before and after, and adds the patch to the
nearest package.json: into an entry whose range covers the installed
version, or under ^<version>. Nothing is written when the selector
matches nothing. `scriptc types list` shows each patch and what it
matches. Both CLI hosts run the same implementation with their own
parser. The TypeError note for a wrong declaration now names the command.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Local setters and the field constructor convert a local time value to
UTC by bracketing it with the zone offset a day before and after, then
checking the candidate instants: three to five localtime(3) calls per
conversion. In glibc each call takes the zone lock and, for dates past
the zone file's last transition, parses the zone's rule string again.
The offsets now come from a per-thread cache of offsets at UTC midnights:
a day whose two midnights agree has that offset throughout, given at
most one transition per day, which the conversion already assumes. A
day with a transition still asks localtime for the instant. A change of
process.env.TZ clears the cache.
A value that contradicts a type an npm package declares used to throw
"expected number at $, got undefined" and nothing else. Such a check now
names the declaration and the type patch that admits the value:
profilekit@3.1.0 declares Profile.email as string (index.d.ts:7)
if that declaration is wrong, patch it in package.json: ...
Origins are found for values a package function returns under a written
return type, for properties of records typed by a package-declared type
(record projections into such a type are keyed by it), and for
declaration-boundary casts, whose origin the projection records from the
declaration-world checker. Generic declarations get none: their types
come from the program's own instantiation.
Checked conversions carry dynCheck.origin, an index into
IrModule.declOrigins; the site's existing cold exception block calls
scr_type_origin_note (new runtime unit behind the typeOrigins feature),
which appends the note to the pending TypeError and derives the selector
suffix and the admitted kind from the failure. Union extractions get the
note per arm at compile time. The hot path is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
libunicode composes an LV syllable with U+11A7 as if it were a trailing
consonant (U+11A8..U+11C2), which drops it: "가ᆧ".normalize()
gave "가" where Node gives "가ᆧ". U+11A7 is a class-0 vowel
that composes with nothing, so for the composing forms the code points
between occurrences of it normalize on their own and it is kept as it
is. The guard sits in front of the vendored library (scr_unicode_normalize),
for String.prototype.normalize and the NFC step of IDNA mapping.
8009-normalize-hangul-jungseong-o-yae prints the code points of all four
forms for U+11A7 after LV and LVT syllables, conjoining jamo, other
starters and combining marks.
Values typed `F | undefined` for a function type F are now plain closure
pointers (nullable-pointer unions), so a keyed read of a
`Record<string, F>` or a Map.get no longer allocates a union box. Keyed
record reads, string-keyed Map.get and checked extractions lend the stored
closure as a receiver or callee when ReferenceWrites proves the call cannot
write the locations it was read from, and optional-chain receivers lend
under the same proof. Owned record overflow reads use the inline entry
lookup.
Indirect closure calls skip the ambient receiver bracket for closures
whose bodies never observe it (new ScrClosure.function_kind bit
SCR_FN_RECEIVER_FREE; signature adapters that only forward the receiver
copy it from their original), and a callValue that forwards `dyn.this`
needs no bracket at all.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A computed read through a snapshot of the global object taken in a
function (`const g = globalThis; return g[name];`) threw SC2020 for
builtin globals: the scan that decides whether a module installs the
builtin-global lookup runs before function bodies are lowered, when the
snapshot's alias is not registered yet. The scan now also recognizes a
variable whose only declaration initializes it with the global object.
navigator.language and navigator.languages cache their answers, but
only taking the navigator object registered their release at exit, so a
program that read just those members left them live and a sanitized
build exited with status 99. The first cached value now registers the
release.
Corpus 8161 reads through a function-local snapshot, and 8168 reads
navigator.language, navigator.languages and navigator.platform without
the navigator object.