SHA-384, SHA-512, SHA-512/224 and SHA-512/256 (createHash, createHmac,
crypto.hash, pbkdf2, hkdf) process their blocks with the fastest kernel
the CPU supports:
- x86-64 with AVX2 and BMI2: the message schedule is computed two words
at a time in vector registers, interleaved with the scalar rounds,
which rotate with RORX.
- x86-64 with AVX-512VL: the same, with vector rotates and three-input
XOR in the schedule.
- arm64 with the SHA-512 extension: SHA512H, SHA512H2, SHA512SU0 and
SHA512SU1.
- Otherwise the portable blocks, now unrolled over a 16-word schedule
ring.
The CPU and OS checks run once and are cached. The update and final
paths pass all their whole blocks to the kernel in one call.
test_sha_kernels.c checks the FIPS 180-4 SHA-384 and SHA-512 vectors
through every kernel the CPU supports and compares each kernel with the
portable blocks over every length from 0 to 1100 bytes and 1 MiB, whole
and in 1-, 7-, 64- and 100-byte updates. Corpus 5779 compares SHA-384,
SHA-512 and HMAC-SHA512 digests over many lengths and update sizes with
Node; tests/harness/corpus-costs/ records its plain and sanitized costs.
emitter.ts had grown to 11.6k lines, and its import block and its large
emission methods were edited by most changes to the LLVM backend, so
concurrent changes conflicted there: 17 merges into main resolved it in
two weeks, more than any other compiler file. 26 LlEmitter methods move,
as is, into functions of the emitter in four files next to it:
emit-module.ts (module parts, helper and library definitions),
emit-statements.ts (the statement switch, try/catch, switch, loop copies),
emit-function.ts (function bodies and generator and async scaffolding) and
receivers.ts (receiver reads and the borrow analyses). Each method keeps
a one-line stub in the class, so callers are unchanged, and imports that
only the moved bodies use move with them.
The move changes no behavior: the zero-cost gate compiles the benchmarks,
the self-hosted compiler and the corpus to identical IR, LLVM and
executables.
Each evaluation of a function-local class creates its own private names,
while one compiled class serves every evaluation. A JavaScript class's
prototype methods are closures per evaluation whose `this` is the
call-site value, so a method taken from one evaluation could be called
with another evaluation's instance (`a.next.call(b)`, a prototype read,
Reflect.get with a receiver). Those calls read and wrote the other
instance's fields, and `#x in this` answered true. Reads and writes of
`this.#x` inside the closures also went through the property bag under
the key "#x" instead of the native field.
Private fields on a checked `this` now go through a brand check: the
receiver must be an instance of the class or a subclass whose class
environment is the closure's own evaluation, which the class's own
binding names. A matching receiver reads and writes the native field;
any other receiver throws Node's TypeError for the read or the write
(after the assigned value evaluates), and `#x in this` answers false.
Method values and accessors taken from typed instances of these classes
resolve to the instance's own evaluation's prototype functions instead of
a native thunk shared by every evaluation.
The classes this applies to are narrowed to the shapes the check covers:
JavaScript class declarations whose private members are instance fields
used only on `this`. Private methods and accessors, class expressions,
and TypeScript classes keep the "function-local classes with private
brands" refusal.
A checked conversion to a union of object types tests each member's
fields with the members' matchers. For a field of type `C | undefined`
holding a class instance, the union's matcher found the instance's
capsule, which is not a capsule of the union itself, and answered no
(or matched the instance's plain-object view, which no class arm
accepts), so no member matched and the conversion threw a TypeError.
workerData read as such a union failed this way in every worker. A union
now matches capsules of other types through its arms, where the class
arm accepts the instance.
A class whose module the program reaches only through `import type`
never registers, and its instance type stayed unmapped: a field such as
`items: Emitted | undefined` reported SC2009, and the class holding it
SC1090. The program holds no instance of such a class, so its instance
type now maps as the structural type it describes, like an interface:
fields of the type hold undefined, object literals and arrays of them.
A JavaScript class outside the program keeps the unmapped type, so its
JSDoc-annotated slots still take the dynamic value.
A holey array converted to a checked-dynamic value keeps its holes (the
conversion records them in the array's presence map), but the runtime
inspector printed every element: `const u: unknown = a` for
`a[2] = 1` printed `[ undefined, undefined, 1 ]`, where Node prints
`[ <2 empty items>, 1 ]`, and `new Array(3)` through `unknown` printed
`[ undefined, undefined, undefined ]`.
The inspector now prints each run of holes as one `<n empty items>`
entry that counts toward the 100-entry limit, as Node's
formatSpecialArray does and as the typed array inspector already does.
- TLSSocket.isSessionReused() is declared in the fallback types and lowers
to false: the TLS client never offers a cached session (no session
resumption is a documented divergence), so no handshake resumes one.
- `o[k] ??= v`, `||=` and `&&=` through a pure index-signature record with
a string key use the keyed read and write `o[k]` and `o[k] = v` lower to.
- Object.assign(instance, record) writes the record's fields into the
class instance's declared fields (same or width-compatible types;
optional source fields only when present) and answers the instance.
- Object.assign({}, instance) whose result the checker types as the class
is refused at compile time: the copy is a plain object, so the checked
cast into the class threw at run time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A thrown Map, Set, typed array, Buffer, function or RegExp reached its
catch binding as a type-erased reference, so inspection printed `{}`,
String() gave "[object Object]", instanceof was false and typeof of a
thrown function was "object". These values now take the checked-dynamic
conversion that thrown records and arrays use, so the catch binding is
the thrown object, as in Node.
The darwin-arm64 runtime pack matrix repeated the common matrix's base
and optional runtime source lists and its variantsFor table, so a new
runtime source needed the same line in both files. The common matrix
now exports BASE_RUNTIME_SOURCES, OPTIONAL_RUNTIME_SOURCES and
variantsFor, and the darwin-arm64 matrix imports them. A comment on the
lists notes that their order is the link order of an executable's
runtime units. Both matrices evaluate to the same objects as before.
compileCInternal built the program's command line from a chain of
conditional spreads, one per optional runtime unit, and wrote the
optional -D flags out twice (the program build and the runtime-object
flags). RUNTIME_UNITS now lists the optional runtime inputs in link
order, one entry per unit: the sources and the condition that selects
them, or a small function for the entries that add vendor objects,
include directories or platform libraries. RUNTIME_DEFINES lists the
optional definitions once, and both flag lists use it. The command
lines keep their order, so the executables do not change.
The comment fragment before the process-events unit, left from a
removed zlib entry, is dropped.
ir/runtime-features.ts kept three parallel lists that every new runtime
feature extended at the same place: the RuntimeFeatures interface fields,
the all-false initializer, and the chain of libCall checks in the scan.
They are now one table, RUNTIME_FEATURE_RULES, with one entry per
feature, sorted by name. An entry lists what sets the feature: a
module-level initial check, libCall names and name prefixes, libCall and
expression predicates, and IR type kinds. A libCall or type that needs
several units appears in each unit's entry. RuntimeFeatures, the scan and
its early exit derive from the table; noRuntimeFeatures lists every
feature once, and TypeScript rejects it when it misses one.
Every corpus program gives the same features as before (3217 programs
compared field by field on their IR), and the module still lowers
statically when the compiler compiles itself.
A thrown array reached its catch binding as a type-erased native array:
`console.log(e)` printed `{}` where Node prints the array, `String(e)`
gave "[object Object]", `Array.isArray(e)` was false and a typed view
such as `(e as number[]).length` threw a TypeError. Thrown records
already crossed the catch boundary as checked-dynamic values that keep
their identity; arrays, and unions over records and arrays, now take the
same conversion, so the catch binding is the thrown object.
node:test printed a failing test's thrown non-Error value with String():
`throw { code: 42 }` reported `[object Object]` where Node prints the
value as util.inspect renders it. The failing-tests section now inspects
thrown numbers, booleans and checked-dynamic values (records, arrays,
null and undefined); a thrown string is quoted as before. A thrown class
instance that is not an Error still prints `[object Object]` (the
limitations page says so).
Adds corpus 6341, extends the thrown-values node:test fixture, and
records 6341's cost.
lower-exprs.ts had grown to 17.1k lines, and its import block and the big
branches of lowerExprInner were edited by about half of all changes to
it, so concurrent changes conflicted there. Its declarations move, as is,
into files under frontend/lowering/expressions/: identifiers,
property-access, typeof, element-access, binary, relational, narrowing,
updates and array-literals. lowerExprInner's identifier, property-access
and typeof branches become functions of their own in those files, called
from the branch. Helpers move with their only users; helpers shared
across files stay in lower-exprs.ts, which re-exports every moved name it
exported, so other modules' imports are unchanged.
The move changes no behavior: the zero-cost gate compiles the benchmarks,
the self-hosted compiler and the corpus to identical IR, LLVM and
executables.
ScrDynHandleTag listed its tags by name with explicit values, so two
changes that each took the next free value merged without a conflict and
produced two tags with one value; only dyn-handle-tags.test.ts noticed.
#1397 (CRYPTO_KEY) and #1259 (ASYNC_DISPOSABLE_STACK) both took 56.
SCR_DYNH_TAGS is now the one list of tags: X(NAME, value) rows, sorted
by name, generate the enum, a _Static_assert per tag that the value is
below SCR_DYNH_CAPACITY, and scr_dynh_tags_distinct, a never-called
switch with a case per tag. Two tags with one value are a duplicate case
value, so every runtime unit that includes scr_dyn.h fails to compile and
names both tags. The values, the names and the generated code are
unchanged. dyn-handle-tags.test.ts reads the rows.
#1399 removed CheckerFacade.declarationsOf and valueDeclarationOf. #1407 and
#1351 merged after it with calls to them, so main does not type-check. Read
symbol.declarations and symbol.valueDeclaration instead, as the rest of the
compiler does.
A construction through a plain function stamps the prototype with the
name of the function in its `constructor` property, for util.inspect.
The stamp pointed at the function's name. A bound function's name
("bound " and the target's name) is a string the bound function owns,
so after the `constructor` property was replaced and the bound function
released, inspecting an instance read freed memory. The stamp now holds
a copy of the name, freed with the stamp at exit.
The corpus program constructs through functions whose prototype's
`constructor` is a bound function (and a bound function of one), then
replaces the property and inspects the instances.
Relational string operators (`<`, `<=`, `>`, `>=`) compared the UTF-8
bytes, which is code-point order. Node compares UTF-16 code units, so the
results differed for characters above U+FFFF against U+E000..U+FFFF and
for lone surrogates against surrogate pairs. The default `sort()`
comparator, `util.inspect` with `sorted: true` and dynamic comparisons
already used UTF-16 order; the default comparator of a dynamic array's
`sort()` did not.
Typed comparisons and the dynamic array sort now use `scr_str_cmp_u16`,
and the IR's `strCmp` node no longer has a `utf16` flag. The two orders
agree unless both of the first differing bytes are at least 0xED, so:
- the inline wrapper decides differing first words, or last words of
equal lengths up to 16 bytes, in byte order when neither word has a
byte of at least 0xED (a SWAR test). Other differing words, and the
first bytes of shorter strings, decide by their first differing byte:
in byte order when one of the two is below 0xED, and by plane when both
are lead bytes from 0xEE to 0xF4 (a supplementary character orders
below U+E000..U+FFFF);
- otherwise a proven-ASCII string on either side compares with `memcmp`
(`scr_str_cmp`), and other pairs call `scr_str_cmp_u16_bytes`. It finds
the first differing byte with words up to byte 32, then with 32-byte
SSE2 or NEON blocks where the target has them. The call is marked as
only reading its arguments, so repeated comparisons of one pair merge.
- `scr_str_cmp_u16` itself takes the same `memcmp` path for proven-ASCII
strings.
The native compiler sorts record fields with `<` and checks the order
with `sort()`, so it reported SC9001 for 5888-inspect-sorted.ts, whose
object has the keys "\u{1F600}" and "\uffff". 5888 runs in the
native-frontend self-host check. Adds corpus 6023.
`globalThis as { [KEY]?: State }` is the usual way for several modules
to share process-wide state. globalThis lowers to a checked-dynamic
value, and asserting a checked-dynamic value to a record or array type
took the checked conversion, which builds a new native value. A write
through the asserted value went to that copy, so other modules never
saw it; string-keyed and index-signature views threw "expected object".
- An assertion of the global object (globalThis, global, also through
`as unknown as`) keeps the checked-dynamic value when the conversion
would copy, so member reads and writes reach the global object.
- File-scope and local bindings of such an assertion hold the global
object (collectGlobals, lowerVarDecl), so an exported
`const state = globalThis as ...` works across modules.
- The divergence scan no longer reports these assertions as SC6001
width copies.
- Runtime: one global-object box per thread (scr_global_object), which
globalThis, global and globalThis.globalThis all answer, and direct
`globalThis[symbol]` accesses use that box's symbol-keyed properties,
so every spelling of the global object sees one property.
Other checked-dynamic values keep the checked conversion: a native
value held as unknown unwraps by identity there, and a checked-dynamic
tree is copied at the documented boundary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>