scripts/refresh-runtime-profile.mjs on Linux x64 against the hardened
runtime pack: the runtime benchmark workloads (48 workloads, 3 runs
each) plus an instrumented tsc-ts self-check trained 333 raw profiles,
merged with --sparse --keep-functions so the profile names only runtime
functions (76,604 bytes gzipped). runtime-profile.test.ts and
hardening.test.ts pass against the rebuilt pack.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main dropped import bitcode from the runtime pack build (c1ce07e56);
the hardening flags this branch adds to the runtime units and the
zlib/brotli/mbedtls archives are unaffected, so the merged build keeps
the hardening imports and main's bitcode-free unit plan.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Node answers a Buffer from fs.readFileSync (path and fd), fs.promises.readFile
and FileHandle#readFile, so Buffer.isBuffer, .constructor, inspect output and
Buffer's numeric search semantics apply to the bytes. scr_lib.c returned
unbranded bytes and diverged.
Branding inside the shared unit would change the executable of every typed
program that links it, so the brand lives in a SCR_FS_BUFFER_BRAND build of
scr_lib.c, selected per program the way SCR_FS_WATCH_FILE is: a runtime
feature (moduleUsesFsBufferBrand) selects it only when the module reads file
bytes through a typed read and can observe their Buffer-ness (Buffer.isBuffer,
inspect of a Uint8Array-typed bytes value, boxing to a dynamic value, or one
of the numeric searches). The default object stays byte-identical.
tests/corpus/fs-buffer-brand.ts covers the sync, fd, promise and callback
reads, unknown boxing, inspect and the searches; it matches Node byte for byte
plain and sanitized.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runtime pack shipped ICU's full icudt78l.dat (33.1 MB), and every
Intl executable gets a copy beside it. scr_intl.c answers its Intl forms
with ICU's break iterators, number and date formatters, calendars and
locale IDs, which never read the charset converters or the collation,
language, region, time zone name, unit, spell-out and transliterator
trees: 21.5 MB of the file.
- The pack build writes icudt78l.dat without those items
(ICU_DATA_DROPPED in icu-inputs.mjs). filterIcuData rewrites the data
file's table of contents to the remaining items, and the result is
checked against a pinned sha256 and size (11,653,984 B).
- tests/harness/icu-data-filter.test.ts runs every @requires-icu corpus
program under 47 locales and 3 time zones with the pack's data, with
the full data and in Node, and fails when the outputs differ, so an
Intl form that reads a dropped category fails there.
- intl-word-segmenter-dictionaries.ts segments Chinese, Japanese, Thai,
Lao, Khmer and Burmese text, which ICU breaks with the dictionaries in
brkitr/.
Program builds have not imported runtime bitcode since fc38e6279, but
every runtime pack still built and listed it: release units through the
helper's prune-import, speed units as runtime-unit's second output. That
is 24 MB of the linux-x64-gnu pack.
Building that bitcode also cost scr_glibc_math its runtime profile: the
pruned import module keeps the floor, ceil and trunc ifuncs without their
resolvers and fails verification, and runtime-unit built it before the
object, so the whole speed unit fell back to a direct compile. The build
printed only the first line of the helper's stderr, a CPU feature
warning.
- runtime-unit's --bitcode is optional (feature
"runtime-unit-optional-bitcode"), and pack builds no longer pass it or
run prune-import. Speed objects come out as before, scr_glibc_math's now
with the runtime profile.
- Pack manifests list no bitcode, and the compiler no longer validates the
field. verify and share-artifacts check objects only.
- A unit the helper can't emit reports the helper's JSON diagnostic.
- The zero-cost gate names changed runtime functions from the objects'
disassembly instead of the bitcode.
Chunked emission replays every numbered helper a range creates on the
merging emitter. The jsval entry thunks (dyn.jsEntryThunk) were missing
from the replayed dyn helpers, so a range that boxed a closure with a
jsval body failed to merge.
The committed runtime profile named the runtime ABI marker
scr_runtime_abi_v47, which ABI 48 renames to scr_runtime_abi_v48. The
profile's record now carries the new name; its hash and counts are
unchanged.
Adding scr_stats_of_path and scr_stats_zero to scr_lib.c moved every
program's executable: scr_lib.o is linked everywhere, so its new symbols
and strings shifted addresses even for programs that never watch a file.
The two entry points now live in an SCR_FS_WATCH_FILE build of scr_lib.c,
the matrix's variant for programs whose feature set has watchFile, and the
poll unit calls the renamed scr_stats_poll_path. Programs that do not poll
link the default object unchanged: compiled without the define, scr_lib.o
is byte-identical to the base (verified by comparing the two objects).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A record view's slot lookup answers NULL for a field a partial view lacks
(record-views.ts), but only a program that asserts a record literal to a
wider type ever asks for such a slot. Testing for the sentinel inside the
default scr_record_view_slot put a branch on a hot accessor of every
program that links a view: the zero-cost gate found fifteen typed
programs whose executables changed with byte-identical IR and LLVM.
Build the unit twice instead, as the fs.watchFile work does for its unit.
The default object is again byte-for-byte the one it was, and the
SCR_RECORD_VIEW_PARTIAL build — the one whose slot answers NULL — is
selected by the program's link features. `recordViewPartial` joins the
runtime features, set by a `partialView` dynCheck and carried through the
link features, the executable options and the early (cache-key) features,
so a cached executable can never link the other build. The runtime pack
gains the eight builds that pair the flag with the unit's existing
dynTier, webStreams and nodeIsland combinations; every program without a
partial assertion keeps the default object.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The checked-dynamic tier needs two more optional units: Array.prototype's
generic algorithms over array-like receivers (scr_dyn_array_like.c) and the
Number/Boolean prototype objects as values (scr_dyn_builtin_prototype.c).
Both are listed as optional runtime sources under a predicate of their own
(all(<feature>, "dynInvoke")), so they link only into a program whose IR
uses the matching libCall, and neither is an always-linked unit. Their
symbol prefixes are in DYNAMIC_TIER_PREFIXES, so a fully typed executable
may not carry them either.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scr_js_wrapper.c constructs `new String/Number/Boolean` wrappers and
ToObject of a primitive as ordinary dictionary-mode objects whose
prototype is the unit's String, Number or Boolean prototype, with the
[[PrimitiveValue]] slot on the object's source identity. The unit also
answers instanceof, Object.prototype.toString, the util.types brands,
the String method receiver, and the guard of the graph-walking consumers
that cannot read the slot yet. A new `primitiveWrapper` runtime feature
(any `wrapper.` libCall) links it, so a program that never creates a
wrapper links no byte of the unit and the zero-cost gate keeps fully
typed programs clean. The `wrapper.*` signatures, symbols and dispatch
case are registered here; no frontend emits them yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every new runtime entry this wave added landed in a unit that baseline
programs link (scr_lib.c's write/copyFile flags, scr_bytes.c's
string_decoder dyn forms, scr_events.c's prepend registries and
stdin.isRaw, scr_async_dyn.c's uncaught prepend, scr_net.c's server.off).
A changed unit changes its object's build-id and symbol metadata even when
every retained function is byte-identical, so 1517 corpus programs
reported a runtime change for code they never call.
Each new entry now lives where only a program that uses it can reach it:
- writeFile's `flag` option and the fs/promises twins move to
scr_fs_flags.c (feature fsFlags); copyFile's mode form reuses
scr_fs_cp.c's reflink walk, so it is that unit's SCR_FS_FLAGS build.
- string_decoder's checked-dynamic chunk forms move to
scr_bytes_strdec_dyn.c (feature strdecDyn).
- the prepend registrations, stdin.isRaw and server.off are the
SCR_PROCESS_EXTRAS builds of scr_events.c and scr_async_dyn.c and the
SCR_NET_SERVER_OFF build of scr_net.c (feature processExtras).
The prepend entry points reuse the append registration and move the new
entry to the front, so no existing function changes and every default
object stays byte-identical. The Windows copyFile arm stays beside the
Win32 path helpers in scr_lib.c, inside its _WIN32 block.
The source build (the -O1 -fsanitize=address lane) compiles these units
from their sources rather than from the prebuilt pack, so RUNTIME_DEFINES
names the same three builds the pack matrix selects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The typed write slot, the drain probes and the run-time-typed chunk write all
live in scr_events.c, a unit every program links. Unconditionally they changed
the default object, so executables of programs that never touch the write slot
changed byte for byte (three corpus programs differed runtime-only). Typed
programs must stay byte-identical.
Keep the default object exactly as it was and put the new code behind
SCR_STDIO_WRITE_SLOT, the way SCR_STREAM_WRITE_ERRORS gates the stream unit:
the shared write body, the run-time-typed chunk entry point and Node's
anonymous write function exist only in that build, and scr_stdio_method keeps
its own body everywhere else.
The lowering sites that reach the slot (the write method as a value, its
replacement, a patched program's typed writes) now emit a distinct marker lib
call, process.stdioSlot, which is the same scr_process_stdio at run time. The
feature scan turns it, together with process.stdioWriteDyn, into the
stdioWriteSlot runtime feature, which selects the matching runtime pack variant
and defines SCR_STDIO_WRITE_SLOT for the executable. The pack matrix enumerates
the combinations with stdioExtended and nodeIsland explicitly, so each existing
variant keeps its defines and its object.
Both new lib fns join USES_TIMERS_LIB_FNS: the write callbacks they queue run
from a nextTick, and a program whose only stdio use is the write slot would
otherwise drop the callback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`writable.write(chunk, cb)` with `cb` passed as a function VALUE lost the
completion's error: `lowerWriteCb` checked only the callback's syntax and
lowered every other callback through `lowerExprExpecting(cbArg,
funcOf([], VOID))`, which adapts a one-parameter function down to zero
parameters. The native binary therefore printed `undefined` where Node
prints `null`, or the write's own error (`ERR_STREAM_WRITE_AFTER_END` /
`ERR_STREAM_DESTROYED` for late and destroyed writes).
Lower such callbacks through a new `writable.writeEcb` libCall
`(recv, mode, chunk, encoding, cb)`: a zero-parameter callback keeps the
existing synchronous path, and a callback that declares the error (a dyn
value, or `null | Error`) is wrapped by `scr_stream_ecb_wrap` with the
compiler-built adapter that delivers the error the write completion
carries. The stream unit publishes that error only in its
`SCR_STREAM_WRITE_ERRORS` build, selected by the new `streamWriteErrors`
runtime feature (rule: any `writable.writeEcb`). Every other build
compiles `SCR_WCB_CALL` to the same plain call, so their objects are
unchanged.
The destruction path also matched Node: `afterWrite` calls `cb(null)`
even for a stream destroyed after the write was queued (Node 24
`internal/streams/writable`), so the destroyed `SCR_ST_AFTER_WRITE` tick
passes NULL rather than the stream's stored error.
Corpus: `tests/corpus/writable-error-callbacks.ts` covers success, an
erroring write, a write after end, and a write on a destroyed stream.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The committed Linux x64 runtime profile had 2.9% of its counts on
functions changed since it was collected, scr_dyn_key_set the most.
scripts/refresh-runtime-profile.mjs collects it again on main (331 raw
profiles); changed functions now hold 0.0% of its counts.
- scr_intl.c resolves the default locale as V8 does: ICU's POSIX locale
(LC_ALL=C, C.UTF-8, POSIX) becomes en-US, so numbers keep their grouping
separators, and other defaults round-trip through their BCP 47 tag. The
number formatter, the date pattern generator, the date formatter and the
default word segmenter use that locale.
- The date formatter moves the Gregorian calendar's Julian cutover to the
start of ECMAScript time, so dates before October 1582 print their
proleptic Gregorian fields, as in Node.
- icu.numFormatDefault, icu.dateLocaleString and icu.dateLocaleTimeString
are in the may-throw set: a missing ICU data file throws an Error into the
surrounding catch.
- `x.toLocaleString(undefined)` reads the receiver before the argument runs.
- The linux-x64-gnu runtime pack ships the ICU headers scr_intl.c includes
beside its ICU archive (manifest `headers`, verified like the other
artifacts). The native compiler's sanitized build compiles scr_intl.c
against them and refuses with SC3003 when they are missing or changed.
The C-source path (sanitized builds) links the pack's ICU archive and
uses those headers, instead of building ICU with Zig's C++ driver.
- The macOS pack omits scr_intl.c, scr_intl.h declares ScrDyn, and the
library-mode teachings test uses a locale argument, which still refuses
with SC2020.
Corpus programs pin the C and C.UTF-8 locales, pre-1582 dates and the
receiver order against Node. intl-missing-data.test.ts covers the throw.
sanitizer.test.ts covers the header lookup.
The string spare, the main thread's idle trim and the shared-bytes writes
changed runtime functions that hold enough of the committed profile's counts
to fail its staleness check (7.3% against a 5% limit). The profile is
collected again on this change with scripts/refresh-runtime-profile.mjs
(327 raw profiles), with the LLVM helper built from this tree's sources.
fs.watchFile(path[, options], listener) polls the resolved path every
`interval` milliseconds (5007 by default, `persistent` by default) and
calls its listener with (current, previous) Stats when a poll sees a
difference. The first poll only records the baseline, a path that cannot
be stat'd polls as all-zero Stats, and a stat failure that repeats for
the same errno is not re-reported. One poller per RESOLVED path is
shared by every listener on it, and fs.unwatchFile(path[, listener])
removes the last listener registered with that callback, or every
listener when called without one. The call is a statement, since Node's
StatWatcher has no lowering, and bigint: true is refused.
The implementation is scr_watch_file.c, a new gated runtime unit
switched by moduleUsesFsWatchFile, so programs that do not poll link
none of it. The feature travels the usual chain (runtime-features,
executable-features, the native link info, the executable cache keys,
the runtime-pack matrix) and the validator learns that
fs.unwatchFile's trailing listener slot may be omitted.
Evidence:
- tests/corpus/fs-watch-file-names.ts and fs-watch-file-polling.ts match
Node on stdout, stderr and exit status in the plain and sanitized lanes
- 1564-fs-watch.ts, 1751-options-record-stance.ts and the `fs` corpus stay
green; the new order baselines and corpus cost files are recorded
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pack builds the ICU archive with the same Zig C++ recipe, adds
scr_intl.c as an optional unit under the intl predicate, and publishes
the archive under the id "icu". Its data file (icudt78l.dat) is not a
link input: the pack lists it in a new manifest "data" array, and every
Intl build copies it next to the executable it links (verifyArtifact
checked, atomic copy, size-checked before reuse). A pack that omits
scr_intl.c refuses an Intl program with SC3002 instead of linking
undefined scr_intl_ references.
The pack fetches both ICU inputs from the pinned release assets: the
source tarball (verified, cached, unpacked by the system tar) for the
archive, and the data zip (sha256 and size pinned twice) for the data,
unpacked by a small stored/deflate reader. Nothing is embedded in a
binary. The runtime fingerprint keeps covering the vendored trees, the
pinned ICU version keys the archive path, and the pack matrix gains the
icu archive with the intl predicate.
The isolated native host (native/compiler.ts) links outside the in-process
linker and writes its executables synchronously, so it installs the same
data artifacts itself (installNativeRuntimeData); the stored pack
selection gains a schema version so a selection cached before the data
list existed is discarded rather than trusted. Nothing selects a data
artifact for a program without the intl feature.
The sanitized lane links the pack's ICU archive as shipped. ICU is C++
and compiles with Zig's C++ driver for the packs' glibc floor, so it cannot
ride the sanitizer lane's per-TU C rebuild, and instrumenting ~600 C++ TUs
would add a multi-minute build to the first Intl program in every sanitized
lane. scr_intl.c itself still compiles instrumented with every other runtime
unit, and AddressSanitizer's interceptors cover ICU's allocations.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Node answers every locale-dependent Intl form from ICU, so a compiled
binary that must match it byte for byte has to run the same release over
the same data. vendor-inputs.ts pins ICU_VERSION = "78.3" beside the
other vendored versions and vendor-inputs.test.ts asserts it against
process.versions.icu, the oracle the differential corpus compares with.
ICU is not vendored. Its source tarball (icu4c-78.3-sources.tgz,
27,977,255 B) and its prebuilt data zip (icu4c-78.3-data-bin-l.zip,
12,836,659 B) come from unicode-org/icu's official release-78.3 assets,
each pinned by version and sha256 beside ICU_VERSION and mirrored in the
runtime packs' icu-inputs.mjs, with a unit test that keeps the two
identical. The tarball carries ICU's own sources.txt lists, which stay
the list of record: icuSourceList() reads them so the archive holds
exactly the units ICU's autoconf build compiles. ICU_CXX_TARGET names
the glibc floor the archive is built for.
The data (icudt78l.dat, 33,107,232 B) is shipped beside the executable,
never embedded: 970 source files and 33 MiB of compiled CLDR tables are
a download, not a reviewable tree in this repository.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Node answers a BigIntStats when the stat call is passed `bigint: true`: the
same snapshot, read through a bigint member table. Every number member of
Stats reads as a bigint — the integers, the `*Ms` millisecond members (the
truncated nanosecond quotient, as the binding computes them) and the
BigIntStats-only `*Ns` nanosecond members (sec * 1e9 + nsec, exactly) —
while only the four time members stay Dates. scriptc refused the option
outright.
The representation is static, chosen by the option literal: the lowerer
validates `bigint` against the call's result type (so
`{ bigint: true, throwIfNoEntry: false }` still narrows
`BigIntStats | undefined`), refuses a runtime-valued option and an option
that disagrees with the destination, and keeps every other option key
fenced. fstatSync joins statSync/lstatSync on the same path, with the
descriptor evaluated before the options.
The IR gains a `bigintStats` handle kind beside `stats`: the same RC
family, the same non-traced leaf and non-dynamic handle treatment, so no
shape, tracing or dyn-check behavior moves for any program that never asks
for one. Its member table is a `bigstats.*` lib-call family whose Date
members reuse the existing stat reads.
The runtime support is its own unit, scr_stats_bigint.c: the snapshot
layout moved into scr_stats_private.h so the bigint getters can read the
same fields without scr_lib.c carrying their code. The unit is gated on the
existing `bigint` feature in both the runtime-pack matrix and the
compiler's from-source unit table (the sanitized lane links from that one),
and `-ffunction-sections` keeps a bigint program that never reads a member
from linking any of the getters. No typed program's IR or LLVM changes.
tests/corpus/7018-bigint-stats.ts pins the member table, the Date/bigint
split, ms/ns agreement, arithmetic on a member, lstat over a symlink,
fstatSync over a descriptor, the throwIfNoEntry union, and the plain Stats
answers of the un-optioned and `bigint: false` forms against Node.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
path.posix, path.win32 and the bare path module are one module object
each, the handle process.getBuiltinModule answers, so require('path/posix'),
path.posix and the target platform's bare path compare as Node's do, a
namespace beside other values in one slot is untyped, and path[family]
reads that object. Functions read through them take checked arguments:
each string parameter passes Node's validateString under its own name
(basename's suffix first), resolve validates paths[i] only as far as its
last-first walk reads them, parse answers an untyped { root, dir, base,
ext, name }, format follows _format over any object, and
toNamespacedPath answers a non-string as it is. A JavaScript call whose
arguments are not strings, or omit one, goes through the same checks.
Typed slots keep the typed function values.
path.win32.relative lowercases both resolved paths with
String.prototype.toLowerCase and walks them by UTF-16 unit, comparing
segments instead when lowercasing changed a length, as Node does. It
folded ASCII only, so a path with İ or Ⱥ in a different case gave a
longer relative path. The function moves into its own runtime unit,
scr_path_win32_relative.c, which links with the regex unit whose case
mapping it uses. scr_path.c stays free of it, so library and
cross-target builds that link the path functions without the regex unit
still link.
A JavaScript binding whose inferred type holds a union of functions and
arrays (a test table of [fn, cases] rows, or one destructured row) takes
the checked-dynamic kind that dynFallbackType gives the unmappable
spellings of such tables. Its rows keep their functions' names, and
for-of, forEach and destructuring read the items as untyped values.
TypeScript bindings keep their typed layout.
scr_str_case_conv scans ASCII text for the first letter to convert eight
bytes at a time, then converts the rest eight bytes at a time; the tail
and big-endian targets take the byte loop. With a refreshed profile, the
byte-at-a-time scan compiled worse for short strings with nothing to
convert, which made validate-errors slower.
The runtime profile is collected again on this change with
scripts/refresh-runtime-profile.mjs (326 raw profiles). Changed
functions hold 0.0% of its counts, against 3.7% for the previous one.
tests/corpus/string-case-ascii-words.ts covers ASCII strings of every
length from 0 to 40, with letters at block boundaries and next to the
bytes just outside A-Z and a-z.