Several analyses the emitter runs per function or per module walked whole
bodies to collect facts from a few node kinds. They now read those kinds
from the module's node index (ir/node-index.ts), which records every node
once in walk order, and the output is unchanged.
- numberGlobalWriters, readsLoopMetrics, closure creation sites
(LazyCaptures) and the ambient receiver readers visit only the kinds
they inspect.
- findLoopArrayBorrows visits only loop statements, and findIntegerViews
returns at once for a function without a loop, where it wants no view.
- foldConstantGlobals records which candidates each function reads during
its write walk instead of walking every function again.
Node recovers the salt length under RSA_PSS_SALTLEN_AUTO without
enforcing the minimum in an RSA-PSS key's parameters, while an explicit
saltLength below it throws and the default saltLength requires the
key's. The corpus program pins that behavior for MGF1 digests equal to
and different from the message digest, through crypto.verify and
createVerify.
assert.fail with a message that is not a string read the message's
hidden locals before they were declared: the call stored the message and
its generated flag in temporaries ahead of the message's declarations,
so `assert.fail(new RangeError('re'))` crashed. Arguments computed from
the message now read it after the declarations.
assert.fail also follows Node's defaults for such values: null and
undefined report "Failed" with generatedMessage true, an Error is thrown
as is, and any other value, falsy ones included, becomes the message as
its string, with generatedMessage set to !value.
Every native exe build that the executable cache does not restore hashed
each selected runtime artifact before staging it, and every exe build
parsed and validated the whole runtime-pack.json manifest to select its
artifacts.
- Staging still copies each artifact. It skips the SHA-256 of an artifact
when the file's identity (device, inode, size, mtime, ctime) is the one
recorded the last time that path matched the same size and digest, and
the identity does not change while the file is read. Other artifacts are
hashed as before, and their identities are recorded after they match.
- A pack's selection for a target, compiler version, feature set, flavor
and mode is stored, keyed by the file identities of package.json and
runtime-pack.json. A stored selection skips parsing and validating the
manifest; the manifest is parsed only when a caller needs it, through
runtimePackManifest.
- The native compiler keeps both in its cache, in the runtime-pack family,
which prune covers.
Speed builds now release arrays through the inline fast path the other
RC families use: an array that stays alive and has no cycle header is
decremented in place, and only the last release or a headered array
calls scr_arr_release.
Reapplies #2017, which #2038 reverted after effect's observability-otlp
fixture crashed in plain builds. The crash was the candidate-buffer
corruption that the previous commit fixes: the cached property sites
change when the collector runs, and with enough environment variables a
pass buffered a string box that was later pooled while still buffered.
The readSync form whose arguments are checked at run time lowers to
fs.readSyncDyn, which the determinism attestation demotes on. List it as
an alias of fs.readSync so the readSync fence witnesses it.
- A speculated write may call a function whose body is one return of
arithmetic, Math.floor and Math.pow over its number parameters. The fast
copy evaluates the expanded body; the double copy still calls the
function. Union tests on the parameters (`number | undefined`) fold
against the wrapped argument.
- `Math.pow(2, n)` and `Math.floor(a / 2^n)` run as checked i64 shifts:
with n an integer in [0, 52], both equal the double results for every
safe integer a.
- Element reads of integer typed arrays are speculative leaves, read before
any check, as string and array reads are.
- A write's other calls of program functions are hoisted: the fast copy
evaluates each once into a local of its own, and a failed check resumes
at a copy of the write that re-runs only the arithmetic over those
locals. This applies only when no call can write a local the write reads.
- k-nucleotide's key chain (`key = bitwiseTrimLeft(key, m) * 4 + seq[i]`)
now runs in i64.
A local initialized from Map.get of a reference value retained the
value and released it at the end of its scope. In the common update
const cur = counts.get(key);
if (cur === undefined) counts.set(key, new Counter());
else cur.num++;
the map is written only on the branch that does not read the local, so
the entry keeps the value alive for every read. A stable local whose
every use comes before any statement that may change a reference edge
(ReferenceEffects) now holds the entry's own reference: no retain on the
read and no release at scope exit. An if statement passes its
condition's state to both branches and joins them; any other statement
that names the local is judged whole. Number keys and inline string keys
take this path, for nullable results and for the stack-boxed results of
union locals.
fs.promises read as a member of the fs module (a binding, an inline
require('fs').promises, destructuring) is the node:fs/promises namespace,
as in Node. It failed at run time before.
A return at the top level of the entry CommonJS module ends its
evaluation, as in Node's module wrapper: preflight admits TS1108 in
CommonJS JavaScript files. A CommonJS module the program requires with a
top-level return is refused with SC1090, because the exports assigned
after the return would stay undefined, which their storage cannot hold.
fs.ReadStream(...) and fs.WriteStream(...) called without new construct
their stream, as Node's stream constructors do.
fs.read, fs.readSync and fs.write check their arguments in Node's order
and with Node's errors: the fd, the buffer, the offset, `length |= 0`,
number and bigint positions (validatePosition), the empty-buffer error
and the buffer window. fs.read accepts a Proxy options object. A
readSync call whose JavaScript arguments are untyped or bigint takes the
same checks at run time (fs.readSyncDyn). fs.write threw no error for an
invalid fd, offset or length before.
Emitting the LLVM text of large programs spent seconds rebuilding the same
sets, rescanning the finished text and walking every function for passes
that change a few of them. The output is unchanged.
- computeMayThrow builds the set of temporal-dead-zone globals once per
module instead of copying it into a new set for every function.
- limitInlineReleases finds the release sites of every family in one walk
over the module text, and rewrites only the parts that hold a function
over its limit.
- The mentioned-function and node:test checks of parking analysis scan the
text with indexOf instead of global regular expressions.
- resolveSelfCaptures rewrites only the functions it changes,
specializeCallbackCalls appends to its write lists instead of copying
them and counts nodes in a walk it already makes, and CaptureFlow reuses
one callback for nested statements.
- The native toolchain test's fixture runtime pack hard-coded ABI v37, so
with this branch's marker (v40) the toolchain refused the pack and the
test saw exit status 1. It now takes RUNTIME_ABI_VERSION and
RUNTIME_ABI_MARKER from runtime-abi.ts, as the native driver test does.
TypedArray.prototype.set with a SharedArrayBuffer view as its source or
target converted every element through a double, even when both arrays
had the same element kind. That cost about 5 ns per byte, and it changed
NaN payloads: a Float32Array signaling NaN came back quieted (7fa00001
became 7fe00001), where Node copies the bytes.
The same element kind now copies the source's bytes with the relaxed
shared stores. A source that overlaps the target range, a view of the
same buffer in a program without workers, is copied out first. Different
element kinds still convert each element.
In Node, `decoder.decode(undefined)` returns "" like `decode()`, and
flushes bytes pending from streamed input. scriptc refused a statically
undefined argument and an argument typed `Uint8Array | undefined` with
SC2020, and `decode(x!)` on an undefined value threw a TypeError.
The decode input lowering now evaluates a statically undefined argument
for its effects and decodes an empty Uint8Array, and reads the undefined
arm of `Uint8Array | undefined` as an empty Uint8Array, in both the
decoder-value and the `new TextDecoder(label).decode(...)` paths. A
non-null assertion passed to decode keeps the undefined it removes,
instead of the checked narrowing other optional standard library
parameters keep. Inputs that may be null are refused as before (`decode(null)`
throws a TypeError in Node).
Reusing the stored diagnostics of the declaration files scriptc supplies
(lib-diagnostics.ts) needs those files' global names to tell whether a
program's scripts could change what checking them reports. The first build
that stored an answer collected them by reading every supplied file's
syntax tree, which a program made only of modules does not otherwise read,
and that build is every cold build. A program of modules cannot collide
with those names, so its answer is now stored without them, and the first
program with scripts that uses the answer collects them and stores them
with it. The entry schema is scriptc.lib-diagnostics.v2.
A cold dev build of the 17-module build benchmark takes 676 ms instead of
689 ms, and of the log-summary benchmark 375 ms instead of 396 ms, with
peak RSS 5.7 MiB and 4.5 MiB lower.
scr_dyn_release returned the last reference of a number or string box to
the free list without scr_cyc_dead, because the release paths never buffer
those boxes. A collector pass can: capture boxes trace their payload, and
scr_xg_release, which settles the cross-generation edges a restricted pass
skipped, calls scr_cyc_on_release on the target whatever its kind. The
pooled block then stayed in its candidate buffer, and the next box that
reused it carried the stale buffer position. Its death swapped another
object into that slot, which corrupted the buffers and crashed a later
scr_cyc_on_dead.
The fast path now takes a buffered box out of its buffer first. The
sanitized runtime does not pool boxes, so only plain builds were affected.
assert.sameValue, assert.notSameValue and assert.compareArray elements treat identical references as the same value and a reference as different from every primitive. Only two distinct references still refuse, because a conversion at the native boundary can copy a reference but never merge two. Assertion messages may be any value and convert with String, as in the upstream helpers.