A callback loop version emits its loop twice. A loop inside either copy
could be versioned again, so a nest of loops that each call a callback
built just before them (`const g = adder(i)`) doubled at every level: a
three-deep nest was emitted seven times per function.
A loop inside a callback version, or the versioned loop itself emitted
again for each copy, is no longer versioned, so a loop nest is emitted at
most twice. The outer loop keeps its version; inner loops keep their
calls as they are.
Since dense-array loops bounded by an invariant limit are versioned, the
test's loop has a speculative site in each dense copy. The test paired
every resume point with the first site's labels; it now checks each
site's resume points against that site's third and fast copies.
A speculative integer loop's failed check used to continue at the failed
statement inside the ordinary copy. That gave the ordinary copy a second
entry, so LLVM saw an irreducible cycle and did not hoist its invariant
loads or keep its values in registers. A loop whose check fails on every
entry, such as an inner loop over fractional fields, then ran every
iteration in that unoptimized copy.
A failed check now continues in a third copy that is entered only from the
fast copy: it re-executes the failed statement, the rest of the body and
the update, then jumps to the ordinary copy's header. The ordinary copy has
a single entry and stays a natural loop.
Each loop site also counts its entries in a row whose fast copy bailed. A
fast copy that completes a speculated write and leaves the loop clears the
count, and after 8 bailing entries in a row the site runs the ordinary copy
only. The count is a hint, read and written with unordered atomics: both
copies give the same results.
A row-major dot product, `const base = r * cols; for (let j = 0; j < cols;
j++) s += w[base + j] * v[j]`, kept one index test per `w` read: the
typed-array loop version guard covered an index `k * counter + terms + c`
only when every loop-invariant term had a proven integer range, and `base`
has none when `cols` is a parameter.
The guard now also accepts a pure loop-invariant term without a proven
range. It evaluates the term once and requires an exact integer (not -0)
between -2^32 and 2^32, then checks the first and last index as for any
affine index. In the fast copy the term has that range, so the index is
integer arithmetic and the reads take no index test. Any other value runs
the checked copy.
Adds a Float32Array matrix-vector product with four accumulators per row
to the typed-array-kernels workload.
A call through a binding that the constant-callback census resolves
keeps a fallback through the closure's code pointer. In a loop that
fallback is an unknown call that takes the closure, so the closure's
code pointer, its captures and a captured `let` were reloaded, and the
`let` stored back, on every iteration of `let s = 0; a.forEach((v) => {
s = (s + v) | 0; })`.
A for, while or do-while loop that calls such a binding now tests once,
before the loop, that the binding's closure has the known code pointer.
The copy where the test holds calls the function directly with no
fallback, and the other copy is the ordinary loop. A closure's code
pointer is set only when the closure is built and the census resolves
only bindings the function never rebinds, so one test covers the loop.
Only bindings declared before the loop qualify, and loops are versioned
up to a size bound.
Number and boolean box payloads get an alias category of their own, so
a store to a captured `let` no longer aliases element loads and other
memory in the loop. With the closure built in the caller, the test folds
after inlining and the captured `let` stays in a register.
The program the lowering compiles serves each file that has JSX at its
own path, with the JSX rewritten to calls (host.addVirtualFile). The
host reported such a path to the checker server as created whenever it
was not virtual yet. The files exist on disk and the server had already
read them for the program that checks them as written, so tsgo kept
their original text, and lowering met the JSX syntax: SC1090 "syntax
'JsxFragment' is not supported yet" in 6822, 6824 and 6825 under
SCRIPTC_CHECKER=tsgo, while tsc-ts reread the files.
A virtual file that replaces a file on disk is now reported as changed,
and both checkers give the same LLVM IR and diagnostics for the JSX
corpus programs.
A counted loop whose limit is an invariant value other than the array's
length, such as `j < n` after `const n = a.length`, gets the same fast copy
as a loop to `a.length`: one test before the loop bounds the limit by
2^31 - 1 and by each indexed array's hole-free prefix, for every array the
loop provably never resizes, and the fast copy reads those slots directly.
The length proof accepts number and boolean writes to class fields, which
run no user code, and borrowed element reads in a fast copy load the slot
without the prefix test or the runtime call. The fast copy's loop body may
hold up to 160 IR nodes.
jsx-source.test.ts compares the JSX rewrite with TypeScript 5's
transpileModule output, so it imports typescript5 as the other test
oracles on the TypeScript 5 island allowlist do. scripts/test-ts7.mjs
did not list it, and the TypeScript 7 parity job failed since #1786.
A binding that may hold a value its declared type omits (a function
parameter fed array elements, which can be holes) and that the checker
narrowed to one record or class arm of a union with more than two arms
was read through a checked extraction of that arm. When the binding held
a hole, a member read or method call threw "undefined is not
representable in the target union ..." for a record arm and "Value is
not an instance of 'Shape'" for a class arm, where Node throws "Cannot
read properties of undefined (reading 'a')".
The read now tests for undefined first and throws Node's TypeError, as
the two-arm path already does.
A generic binding inside a function that a nested function read before
the declaration lowered stores its instance into a TDZ box when the
declaration runs. That value was the instance's closure, which the
backend interns for module functions, so every evaluation of the
declaration stored the same function object. JavaScript makes a new one
each time. The stored value is now a closure over a lifted forwarder that
calls the instance, so each evaluation allocates a new closure; the
forwarder is created once per instance. Async and rest-parameter
bindings read this way report SC1090.
Corpus 7180 checks the identities.
ClassProtocols' identity-flow fixpoint asks the contextual type of each
site whose source carries identity, one round trip at a time, and lowering
asks the resolved signature of each call and the contextual type of each
array literal and conditional it lowers the same way. The checker facade
can now batch them: prefetchContextualTypes for the fixpoint's sites before
it runs, and, for each prefetched wave of reachable roots, the resolved
signatures of its calls and the contextual types of its array literals and
conditionals. A batched query that fails is left out of the memo, so a
direct query still throws where it is made.
The batches use the checker's getContextualTypes and getResolvedSignatures
requests. NODE_BATCH_QUERIES keeps them off until scriptc depends on a
tsc-ts release that answers them; while it is off the prefetches do
nothing.
Keys, primes and generators drop their leading zero bytes before import,
as BN_bin2bn does, and a value still past mbedTLS's 80000-byte limit
throws Node's error or a coded SC2020 error instead of aborting the
process: computeSecret rejects a key longer than the prime as too large,
a long generator is a bad generator, and setPublicKey/setPrivateKey
refuse with SC2020.
Under the RFC 5114 groups, computeSecret rejects a peer key with
key^q mod p != 1 with ERR_CRYPTO_INVALID_KEYTYPE, as OpenSSL's public
key check does, closing small-subgroup confinement of the private key.
The safe-prime groups keep the range check only, as in Node.
Exponents past 8192 bits are split into digits bit by bit, so
generateKeys with a long private key takes linear time.
When an event-loop callback calls an async function or steps a generator
that enters a store before its first await, the store becomes the
callback's at the end of the prefix. On the main slot it now ends with
that callback, as an enterWith there does: the copy schedules the tick
that clears the main slot, so the store does not reach later callbacks.
new tls.TLSSocket(socket, { isServer: true, secureContext, requestCert:
true }) skipped the options walk that refuses requestCert, so the server
asked for no client certificate and accepted every client, even with
rejectUnauthorized: true. With a secureContext, Node still reads the
socket's own options, so a truthy requestCert, SNICallback and
ALPNCallback now throw the fence there too.
A TLS socket's transport kept its 'secure' and 'session' listeners until
the socket was freed. A handshake that fails never fires 'secure', so a
listener that captures the socket kept the socket and its TLS state alive
after it closed. The transport now drops those listeners when the socket
settles, and ignores listeners added after that.
crypto.subtle.wrapKey exports a secret key (raw, raw-secret or jwk) and
encrypts it, and unwrapKey decrypts and imports one, as Node's
webcrypto.js does: the wrap algorithm normalizes through the wrapKey
registry (AES-KW) and then the encrypt registry (AES-GCM, AES-CBC and
AES-CTR with their dictionaries), the wrapping key's algorithm and
usages are checked, and a JWK payload is its JSON text, padded with
spaces to a multiple of 8 bytes for AES-KW. A wrapped JWK that is not
UTF-8 JSON with a kty member rejects with Node's DataError.
AES-KW is RFC 3394 key wrap over the vendored mbedTLS block cipher, in
scr_webcrypto_aes.c beside the other AES modes: wrapping takes a
multiple of 8 bytes and at least 16, unwrapping a multiple of 8 and at
least 24, and the integrity check value is compared in constant time.
The cipher's failures, and unwrapKey's import errors, reject on a later
loop turn as Node's job promises do.
This completes jose's symmetric key management: 7132 runs the real
jose 6.2.12 PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW,
A128KW and A256KW paths (EncryptJWT and jwtDecrypt) against Node.
getPublicKey replaces wrapKey as the refused method in the fenced
fixture and the JavaScript refusal test.
The key and signature errors formatted into fixed stack buffers and
passed snprintf's return value on as the message length. A message
longer than the buffer, such as "Invalid digest: " with a long algorithm
name from crypto.sign or crypto.verify, then copied bytes past the end of
the buffer into the error. The messages are now sized to their whole
text, so a long name appears in full, as in Node.
The received values in these messages also follow Node: strings as
util.inspect quotes them, cut at 128 characters, and large integers with
ERR_OUT_OF_RANGE's separators. A fractional, NaN or infinite
modulusLength reports "It must be an integer", as validateUint32 does.
The private copy of the LLVM text had no extension. Builds that compile it
with the C driver (sanitize builds, targets without the LLVM helper) passed
it to clang directly when the native cache was off, and clang read it as a
linker input, so the build failed. The copy is now named with `.ll`.
Those builds also record the output directory in their native cache proof.
Renaming the private copies to the shared names after the native build
changed that directory, so every unchanged repeat missed the proof and
relinked the executable. The shared names are now installed before the
native build, which compiles and caches copies in a private directory
outside the output directory.