The temps a regex replace or replaceAll consumed (the subject that a
previous substitution in a chain produced, a computed replacement) were
released at the end of the statement. A chain of substitutions over a
large string therefore held every intermediate result until the last one
returned. The operands now live in a frame of their own that is released
right after the call, so a chain holds at most its current subject and
result.
isFallbackDtsPath runs for every type lowering maps (isStdlibFile), and
since the fallback declarations moved into per-module files (#2256) it
asked declarationPath for the fallback module directory each time. Without
an installed declaration root, each answer resolved
@scriptc/compiler/scriptc-node-fallback.d.ts through Node's module
resolver. Lowering three.js's buffers-animation fixture went from 6.3 s to
43 s. declarationPath now keeps its answers by name until the declaration
root changes.
The native sanitizer build chose the variants to compile by artifact path.
Since the runtime pack stores identical objects once, two variants of a
unit can share a path, so it compiled such a unit for both variants and
linked it twice (duplicate symbols in the sanitized native bootstrap).
selectedRuntimeUnits now returns each selected unit with its variant;
selectRuntimePackArtifacts and the sanitizer both use it.
Staging the native CLI's assets ran llvm-strip unconditionally and failed
on hosts without it. Without llvm-strip on PATH, tsc-ts now keeps its
symbols and the build prints a warning; the release hosts install LLVM.
Node passes a stream's one onwrite function to every _write and _writev
call, and a new function to every _transform call. A write or writev
option callback now receives the same closure on every call
(scr_stream_write_closure), kept or not, so a Set, WeakSet or WeakMap
finds it again. A reused transform, flush, final, construct or destroy
callback is first forgotten by weak collections, as at its free, so a
WeakSet or WeakMap that recorded the previous call's callback does not
find it on the next call.
A local that only holds exact integers or NaN, such as one assigned from
an integer typed-array read, now keeps whether it is NaN beside its
integer view. Its reads, and sums, differences and products of them,
carry an i64 that holds the integer whenever the number is not NaN, so
an index check or a counted loop's guard tests that integer and the
ordered bit instead of converting the double back and checking that it
is an exact integer.
Each call of a typed or untyped write, writev, transform, flush, final,
construct or destroy option callback allocated a closure and a box holding
the stream for its `callback` argument. The emitted thunks now take it
from scr_stream_done_closure, which keeps the last one on the stream state and
hands it out again while the stream holds its only reference and it has
no own properties. A callback the user kept (stored, or passed to
setImmediate or a promise) is not reused: the next call gets a new
closure, and the kept one keeps its own stream reference.
The cached closure holds the stream, so the stream drops it when it
emits 'close' and traces it, which lets the cycle collector reclaim a
stream dropped before it closes.
RUNTIME_ABI_VERSION goes from 46 to 47 for scr_stream_done_closure.
- A string held in a binding or returned by a call can be a missing element
read (undefined), so `head + PAD[j]` and `at(i) + PAD[j]` still add to NaN
when both are missing. Only string literals, templates and sums with such
an operand now count as always a string.
- `readdirSync(path, { recursive })` folds only the `true` and `false`
keywords. Any other flag expression, including one typed as a literal,
evaluates at run time for its effects and exceptions.
A speculative integer loop's fast copy exits to a copy that resumes the
failed iteration, and both read the header fields of the views they
access. GVN's load PRE moved the fast copy's read of a view's data
pointer ahead of the exit branch, to make the resume copy's read
redundant, and put a copy on the exit edge. The loop then kept its first
iteration in the loop, so the overflow checks of a value whose range is
known after one iteration stayed in every iteration, with a reload of the
data pointer.
A view held by a const local or a parameter that the loop does not rebind
is the same view in every iteration, and its length, data pointer and
shared storage never change. The loop's entry now reads them once, and the
element accesses of every copy use those values.
The store a constant choice emits for its arms used the first arm's
index expression, with the ranges proven inside that arm. When the index
is the compared local, `j === 0` narrows it to [0, 0] there, so the one
store, which also runs on the final else's path, converted the index to
an int32 from that range. For `j` = 4294967297 that conversion has no
defined result, and the bounds check that follows could accept it: an
out-of-bounds write could reach a valid element.
The store now reads such an index through the first comparison's
operand: the same local, read before the chain branches, so its range
holds on every path. An index that is another local is not narrowed by
the comparisons and is unchanged.
An if/else-if chain that compares one number local with integer literals
and stores a number literal to the same typed-array element in every arm,
including a final else, such as
if (x === 97) a[i] = 0;
else if (x === 116) a[i] = 1;
else if (x === 99) a[i] = 2;
else a[i] = 3;
is now emitted as one store of an entry of a constant table indexed by
the local's value (constant-choices.ts). On input whose cases do not
repeat in a pattern, such as the bases of a DNA sequence, the branches
mispredict; LLVM keeps them, because cases this far apart are too sparse
for its own switch tables.
The chain needs at least three distinct values within 256 of each other.
The table covers the cases' span, or the local's proven integer range
when that range has at most 256 values, in which case no test is
emitted. Otherwise a value that is not an exact integer in the table's
span (NaN, a fraction, or a value outside it) stores the final else's
literal, chosen with a select. The first arm wins for a repeated
value, and -0 selects the arm for 0, as strict equality does. The store
is the arms' own (the same receiver and index), converted to the
element's type as each arm's would be. Debug builds keep the arms.
k-nucleotide's transform of the input sequence uses this form: its
median wall time drops from 2.71 s to 2.21 s.