Merge main

This commit is contained in:
Chris Tate
2026-10-22 03:24:23 -05:00
201 changed files with 4288 additions and 1262 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea
| `region-builds` | cpu | a watch-mode build loop over `LifetimeRegion`: each build binds a program in a new region and releases the previous one, which a cache keeps referenced for two builds |
| `template-render` | cpu | template literals, escaping, `map`/`join` |
| `thread-slots`, `thread-slots-cache` | cpu | worker threads reading one published type graph from an `InternTable` and keeping per-thread state for each type they visit: a `ThreadSlots` family, or a direct-mapped cache by id in front of a `Map` by id (the `-cache` entry); under Node.js both are per thread |
| `typed-array-kernels` | cpu | element-wise loops that run to one typed array's length and index others at the same position: `Float64Array` axpy, `Float32Array` blend, `Uint8Array` mask, wrapping `Int32Array` add, RGBA to gray |
| `typed-array-kernels` | cpu | element-wise loops that run to one typed array's length and index others at the same position: `Float64Array` axpy, `Float32Array` blend, `Uint8Array` mask, wrapping `Int32Array` add, `Uint8ClampedArray` brighten, RGBA to gray |
| `typed-array-scan`, `typed-array-scan-workers` | cpu | element-by-element scan loops over `Uint8Array`, `Uint16Array`, `Int32Array` and `Float64Array`; the second entry is a worker-enabled executable (it can start a worker), so each access also checks for shared storage |
| `unicode-lexer` | cpu | a hand-written lexer over a 2.7M-unit bundled module whose comments and strings are in several languages: `charCodeAt` scans, line starts |
| `validate-errors` | cpu | throw/catch on a fraction of inputs, small call chains |
@@ -17,6 +17,10 @@ function addWrap(a: Int32Array, b: Int32Array, out: Int32Array): void {
for (let i = 0; i < out.length; i++) out[i] = (a[i]! + b[i]!) | 0;
}
function brighten(src: Uint8ClampedArray, gain: number, out: Uint8ClampedArray): void {
for (let i = 0; i < out.length; i++) out[i] = src[i]! * gain + 8;
}
function gray(rgba: Uint8Array, out: Uint8Array): void {
for (let i = 0; i < out.length; i++) {
const j = i * 4;
@@ -38,6 +42,8 @@ const masked = new Uint8Array(n);
const ia = new Int32Array(n);
const ib = new Int32Array(n);
const io = new Int32Array(n);
const pixels = new Uint8ClampedArray(n);
const bright = new Uint8ClampedArray(n);
const rgba = new Uint8Array(n * 4);
const luma = new Uint8Array(n);
let state = 2463534242;
@@ -56,6 +62,7 @@ for (let i = 0; i < n; i++) {
key[i] = next() & 255;
ia[i] = next() | 0;
ib[i] = next() | 0;
pixels[i] = next() & 255;
}
for (let i = 0; i < rgba.length; i++) rgba[i] = next() & 255;
@@ -64,6 +71,7 @@ let f64Sum = 0;
let f32Sum = 0;
let byteSum = 0;
let intSum = 0;
let brightSum = 0;
let lumaSum = 0;
for (let r = 0; r < rounds; r++) {
// Change one input element per round so no round repeats the last.
@@ -72,21 +80,25 @@ for (let r = 0; r < rounds; r++) {
fa[at] = r;
data[at] = r & 255;
ia[at] = r * 65537;
pixels[at] = r & 255;
rgba[at * 4] = r & 255;
axpy(1.5, x, y, d);
blend((r & 15) / 16, fa, fb, fo);
mask(data, key, masked);
addWrap(ia, ib, io);
brighten(pixels, 0.75 + (r & 7) / 8, bright);
gray(rgba, luma);
const probe = (r * 104729) & (n - 1);
f64Sum += d[probe]!;
f32Sum += fo[probe]!;
byteSum += masked[probe]!;
intSum = (intSum + io[probe]!) | 0;
brightSum += bright[probe]!;
lumaSum += luma[probe]!;
}
console.log(`float64 ${f64Sum.toFixed(3)}`);
console.log(`float32 ${f32Sum.toFixed(3)}`);
console.log(`uint8 ${byteSum}`);
console.log(`int32 ${intSum}`);
console.log(`clamped ${brightSum}`);
console.log(`gray ${lumaSum}`);
@@ -19,4 +19,7 @@
A call on an ECDH value whose arguments no @types/node overload accepts (`getPublicKey("HEX")`) is refused at compile time, as is `computeSecret(view, encoding)`: Node reads that encoding as the input encoding and answers a Buffer where @types/node declares a string.
ECDH property reads and writes throw a coded SC2020 error.
Other public-key operations remain unsupported.
The WebCrypto `crypto` global, which node:crypto also exports as `webcrypto`, supports `randomUUID()` and `getRandomValues()`, also read as values from untyped JavaScript.
Its `subtle` object, also node:crypto's `subtle` export, supports `generateKey()` and `exportKey()` for HMAC and AES keys, `importKey()` for HMAC, AES, PBKDF2, and HKDF keys, `encrypt()` and `decrypt()` with AES-GCM, AES-CBC, and AES-CTR on native targets, `deriveBits()` and `deriveKey()` with PBKDF2 and HKDF, HMAC `sign()` and `verify()`, and SHA-1 and SHA-2 `digest()`.
Secret keys import and export in the `raw`, `raw-secret`, and `jwk` formats; other SubtleCrypto methods, algorithms, and key formats are refused.
Native derivations finish before their deferred callbacks, so computation blocks the caller.
@@ -21,6 +21,7 @@
Stream constructors accept a `signal` option in an options object literal, and `stream.addAbortSignal` accepts any stream.
A `transform` or `flush` option callback whose completion callback types `data` as `any`, as @types/node's `TransformCallback` does, may pass a Buffer, a string, or nothing; any other value throws a TypeError where the callback is called, instead of Node's `ERR_INVALID_ARG_TYPE` stream error.
`node:readline` and `node:readline/promises` (also reached as `readline.promises`) interfaces, from `createInterface(options)` or `createInterface(input, output, completer, terminal)`, read `process.stdin` or a readable stream: `question`, `prompt`, `write` (with a key object literal of `name`, `ctrl`, `meta`, and `shift`), `pause`, `resume`, `'line'`/`'close'`/`'pause'`/`'resume'`/`'error'`/`'history'`/`'SIGINT'`/`'SIGTSTP'`/`'SIGCONT'` listeners, and `for await` compile.
`on`, `once`, and `addListener` return the interface, so registrations chain.
The `history`, `historySize`, `removeHistoryDuplicates`, `completer`, `tabSize`, and `escapeCodeTimeout` options are validated as in Node.
With `terminal: true`, or with no `terminal` option and `process.stdout` as the output when it is a TTY, the interface runs Node's terminal mode: the editing keys, the kill ring, undo and redo, history recording and recall, Tab completion through the completer, Ctrl+C, Ctrl+D, and Ctrl+Z, `TERM=dumb`, and the same escape sequences written to the output.
The output's `'resize'` event does not redraw the line.
@@ -1,4 +1,5 @@
- **Local class expressions:** Static members, decorators, private brands, generic members, and references through the class's internal name are unsupported.
- **Local class expressions:** Static fields and static blocks in TypeScript files, decorators, private brands, generic members, and references through the class's internal name are unsupported.
Static methods read the enclosing function's bindings and are inherited by subclasses.
Supported expressions preserve fresh constructor identity and shared captures across constructors, methods, accessors, and arrow fields.
Local inheritance requires a compiled base class.
Constructors retain identity, `typeof`, `name`, and `length` through `unknown`; cast back to their exact type before construction.
+29 -29
View File
@@ -12442,9 +12442,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented by a dedicated static compiler/runtime path.",
"verification": "test-backed"
}
},
{
@@ -12465,9 +12465,9 @@
"inherited": true
},
"static": {
"status": "refused",
"detail": "Explicitly rejected by a named compiler diagnostic.",
"verification": "explicit-refusal"
"status": "partial",
"detail": "Implemented by a dedicated static compiler/runtime path.",
"verification": "test-backed"
}
},
{
@@ -79656,9 +79656,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -79680,7 +79680,7 @@
},
"static": {
"status": "supported",
"detail": "Implemented by a dedicated static compiler/runtime path.",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
@@ -79703,7 +79703,7 @@
},
"static": {
"status": "supported",
"detail": "Implemented by a dedicated static compiler/runtime path.",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
@@ -80001,9 +80001,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80024,9 +80024,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80047,9 +80047,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80139,9 +80139,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80162,9 +80162,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -80208,9 +80208,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented scriptc module-loader subset.",
"verification": "test-backed"
}
},
{
@@ -19316,6 +19316,8 @@
"tests/corpus/7055-cipher-streams.ts",
"tests/corpus/7056-cipher-stream-errors.ts",
"tests/corpus/7057-cipher-streams.mjs",
"tests/corpus/7122-webcrypto-node-crypto.ts",
"tests/corpus/7123-webcrypto-node-crypto.cjs",
"tests/corpus/node24-crypto-kdf-errors.ts",
"tests/corpus/node24-crypto-kdf-options.ts",
"tests/corpus/node24-crypto-kdf.ts",
@@ -21224,8 +21226,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:crypto"
"status": "partial",
"evidence": "compiler-dedicated:subtle",
"tests": [
"tests/corpus/7122-webcrypto-node-crypto.ts",
"tests/corpus/7123-webcrypto-node-crypto.cjs"
]
},
"anchorSource": "ancestor"
},
@@ -21247,8 +21253,12 @@
"inherited": true
},
"static": {
"status": "refused",
"evidence": "surface-manifest:unsupported:node-builtin.crypto.webcrypto"
"status": "partial",
"evidence": "compiler-dedicated:crypto.webcrypto",
"tests": [
"tests/corpus/7122-webcrypto-node-crypto.ts",
"tests/corpus/7123-webcrypto-node-crypto.cjs"
]
},
"anchorSource": "exact"
},
@@ -106652,8 +106662,6 @@
"tests/corpus/1534-crypto-hash-chain.ts",
"tests/corpus/2556-crypto-introspection.ts",
"tests/corpus/2890-crypto-static-utility.ts",
"tests/corpus/5442-crypto-random-values.ts",
"tests/corpus/5443-crypto-random-arguments.js",
"tests/corpus/6204-crypto-get-random-values.ts",
"tests/corpus/6205-crypto-get-random-values.js",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
@@ -106661,11 +106669,20 @@
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/corpus/7120-webcrypto-random-uuid.ts",
"tests/corpus/7121-webcrypto-crypto-object.mjs",
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/7125-webcrypto-key-errors.mjs",
"tests/corpus/7126-webcrypto-aes.ts",
"tests/corpus/7127-webcrypto-aes-errors.mjs",
"tests/corpus/7128-webcrypto-derive.ts",
"tests/corpus/7129-webcrypto-derive-errors.mjs",
"tests/corpus/node24-crypto-kdf-errors.ts",
"tests/corpus/node24-crypto-kdf-options.ts",
"tests/corpus/node24-crypto-kdf.ts",
"tests/corpus/node24-crypto-sha2.ts",
"tests/fixtures/node-types/webcrypto-hmac.ts",
"tests/fixtures/node-types/webcrypto-keys.ts",
"tests/harness/npm-static.test.ts"
]
},
@@ -110123,8 +110140,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.Crypto",
"tests": [
"tests/corpus/7120-webcrypto-random-uuid.ts",
"tests/corpus/7121-webcrypto-crypto-object.mjs",
"tests/corpus/6293-webcrypto-digest.ts"
]
},
"anchorSource": "exact"
},
@@ -110147,8 +110169,10 @@
},
"static": {
"status": "supported",
"evidence": "compiler-dedicated:crypto.getRandomValues",
"evidence": "compiler-feature:webcrypto.crypto.getRandomValues",
"tests": [
"tests/corpus/7120-webcrypto-random-uuid.ts",
"tests/corpus/7121-webcrypto-crypto-object.mjs",
"tests/corpus/6204-crypto-get-random-values.ts",
"tests/corpus/6205-crypto-get-random-values.js"
]
@@ -110174,11 +110198,12 @@
},
"static": {
"status": "supported",
"evidence": "compiler-dedicated:crypto.randomUUID",
"evidence": "compiler-feature:webcrypto.crypto.randomUUID",
"tests": [
"tests/corpus/5442-crypto-random-values.ts",
"tests/corpus/5443-crypto-random-arguments.js",
"tests/corpus/1358-crypto-random.ts"
"tests/corpus/7120-webcrypto-random-uuid.ts",
"tests/corpus/7121-webcrypto-crypto-object.mjs",
"tests/corpus/6204-crypto-get-random-values.ts",
"tests/corpus/6205-crypto-get-random-values.js"
]
},
"anchorSource": "exact"
@@ -110234,6 +110259,7 @@
"status": "partial",
"evidence": "compiler-feature:webcrypto.CryptoKey",
"tests": [
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
@@ -110262,6 +110288,7 @@
"status": "partial",
"evidence": "compiler-feature:webcrypto.cryptoKey.algorithm",
"tests": [
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
@@ -110290,6 +110317,7 @@
"status": "partial",
"evidence": "compiler-feature:webcrypto.extractable",
"tests": [
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
@@ -110318,6 +110346,7 @@
"status": "partial",
"evidence": "compiler-feature:webcrypto.type",
"tests": [
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
@@ -110346,6 +110375,7 @@
"status": "partial",
"evidence": "compiler-feature:webcrypto.usages",
"tests": [
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs"
@@ -110516,8 +110546,23 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle.decrypt",
"tests": [
"tests/corpus/7128-webcrypto-derive.ts",
"tests/corpus/7129-webcrypto-derive-errors.mjs",
"tests/corpus/7126-webcrypto-aes.ts",
"tests/corpus/7127-webcrypto-aes-errors.mjs",
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/7125-webcrypto-key-errors.mjs",
"tests/fixtures/node-types/webcrypto-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/fixtures/node-types/webcrypto-hmac.ts"
]
},
"anchorSource": "exact"
},
@@ -110539,8 +110584,23 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle.deriveBits",
"tests": [
"tests/corpus/7128-webcrypto-derive.ts",
"tests/corpus/7129-webcrypto-derive-errors.mjs",
"tests/corpus/7126-webcrypto-aes.ts",
"tests/corpus/7127-webcrypto-aes-errors.mjs",
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/7125-webcrypto-key-errors.mjs",
"tests/fixtures/node-types/webcrypto-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/fixtures/node-types/webcrypto-hmac.ts"
]
},
"anchorSource": "exact"
},
@@ -110562,8 +110622,23 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle.deriveKey",
"tests": [
"tests/corpus/7128-webcrypto-derive.ts",
"tests/corpus/7129-webcrypto-derive-errors.mjs",
"tests/corpus/7126-webcrypto-aes.ts",
"tests/corpus/7127-webcrypto-aes-errors.mjs",
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/7125-webcrypto-key-errors.mjs",
"tests/fixtures/node-types/webcrypto-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/fixtures/node-types/webcrypto-hmac.ts"
]
},
"anchorSource": "exact"
},
@@ -110588,6 +110663,13 @@
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle.digest",
"tests": [
"tests/corpus/7128-webcrypto-derive.ts",
"tests/corpus/7129-webcrypto-derive-errors.mjs",
"tests/corpus/7126-webcrypto-aes.ts",
"tests/corpus/7127-webcrypto-aes-errors.mjs",
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/7125-webcrypto-key-errors.mjs",
"tests/fixtures/node-types/webcrypto-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
@@ -110662,8 +110744,23 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle.encrypt",
"tests": [
"tests/corpus/7128-webcrypto-derive.ts",
"tests/corpus/7129-webcrypto-derive-errors.mjs",
"tests/corpus/7126-webcrypto-aes.ts",
"tests/corpus/7127-webcrypto-aes-errors.mjs",
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/7125-webcrypto-key-errors.mjs",
"tests/fixtures/node-types/webcrypto-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/fixtures/node-types/webcrypto-hmac.ts"
]
},
"anchorSource": "exact"
},
@@ -110685,8 +110782,23 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle.exportKey",
"tests": [
"tests/corpus/7128-webcrypto-derive.ts",
"tests/corpus/7129-webcrypto-derive-errors.mjs",
"tests/corpus/7126-webcrypto-aes.ts",
"tests/corpus/7127-webcrypto-aes-errors.mjs",
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/7125-webcrypto-key-errors.mjs",
"tests/fixtures/node-types/webcrypto-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/fixtures/node-types/webcrypto-hmac.ts"
]
},
"anchorSource": "exact"
},
@@ -110731,8 +110843,23 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webcrypto"
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle.generateKey",
"tests": [
"tests/corpus/7128-webcrypto-derive.ts",
"tests/corpus/7129-webcrypto-derive-errors.mjs",
"tests/corpus/7126-webcrypto-aes.ts",
"tests/corpus/7127-webcrypto-aes-errors.mjs",
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/7125-webcrypto-key-errors.mjs",
"tests/fixtures/node-types/webcrypto-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
"tests/corpus/6294-webcrypto-js-values.mjs",
"tests/corpus/6295-jose-hs256/main.mjs",
"tests/fixtures/node-types/webcrypto-hmac.ts"
]
},
"anchorSource": "exact"
},
@@ -110757,6 +110884,13 @@
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle.importKey",
"tests": [
"tests/corpus/7128-webcrypto-derive.ts",
"tests/corpus/7129-webcrypto-derive-errors.mjs",
"tests/corpus/7126-webcrypto-aes.ts",
"tests/corpus/7127-webcrypto-aes-errors.mjs",
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/7125-webcrypto-key-errors.mjs",
"tests/fixtures/node-types/webcrypto-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
@@ -110788,6 +110922,13 @@
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle.sign",
"tests": [
"tests/corpus/7128-webcrypto-derive.ts",
"tests/corpus/7129-webcrypto-derive-errors.mjs",
"tests/corpus/7126-webcrypto-aes.ts",
"tests/corpus/7127-webcrypto-aes-errors.mjs",
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/7125-webcrypto-key-errors.mjs",
"tests/fixtures/node-types/webcrypto-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
@@ -110842,6 +110983,13 @@
"status": "partial",
"evidence": "compiler-feature:webcrypto.subtle.verify",
"tests": [
"tests/corpus/7128-webcrypto-derive.ts",
"tests/corpus/7129-webcrypto-derive-errors.mjs",
"tests/corpus/7126-webcrypto-aes.ts",
"tests/corpus/7127-webcrypto-aes-errors.mjs",
"tests/corpus/7124-webcrypto-secret-keys.ts",
"tests/corpus/7125-webcrypto-key-errors.mjs",
"tests/fixtures/node-types/webcrypto-keys.ts",
"tests/corpus/6291-webcrypto-hmac-sign-verify.ts",
"tests/corpus/6292-webcrypto-hmac-errors.ts",
"tests/corpus/6293-webcrypto-digest.ts",
+7 -4
View File
@@ -187,16 +187,18 @@
"stdlib.set": ["tests/corpus/typed-collection-iterators.ts", "tests/corpus/typed-collection-iterator-references.ts", "tests/corpus/checked-collection-iterators.ts"],
"stdlib.headers": ["tests/harness/fetch-conformance.test.ts"],
"stdlib.request": ["tests/harness/fetch-conformance.test.ts"],
"stdlib.webcrypto.subtle": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"],
"stdlib.webcrypto.CryptoKey": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"],
"stdlib.webcrypto.subtle": ["tests/corpus/7128-webcrypto-derive.ts", "tests/corpus/7129-webcrypto-derive-errors.mjs", "tests/corpus/7126-webcrypto-aes.ts", "tests/corpus/7127-webcrypto-aes-errors.mjs", "tests/corpus/7124-webcrypto-secret-keys.ts", "tests/corpus/7125-webcrypto-key-errors.mjs", "tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"],
"stdlib.webcrypto.CryptoKey": ["tests/corpus/7124-webcrypto-secret-keys.ts", "tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"],
"stdlib.response": ["tests/harness/fetch-conformance.test.ts"],
"stdlib.readable-stream": ["tests/harness/fetch-conformance.test.ts"],
"stdlib.abort": ["tests/harness/fetch-conformance.test.ts"]
},
"features": [
{ "chapter": "webcrypto", "symbols": ["subtle.importKey", "subtle.sign", "subtle.verify", "subtle.digest"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs", "tests/fixtures/node-types/webcrypto-hmac.ts"] },
{ "chapter": "webcrypto", "symbols": ["subtle.importKey", "subtle.generateKey", "subtle.exportKey", "subtle.encrypt", "subtle.decrypt", "subtle.deriveBits", "subtle.deriveKey", "subtle.sign", "subtle.verify", "subtle.digest"], "status": "partial", "evidence": ["tests/corpus/7128-webcrypto-derive.ts", "tests/corpus/7129-webcrypto-derive-errors.mjs", "tests/corpus/7126-webcrypto-aes.ts", "tests/corpus/7127-webcrypto-aes-errors.mjs", "tests/corpus/7124-webcrypto-secret-keys.ts", "tests/corpus/7125-webcrypto-key-errors.mjs", "tests/fixtures/node-types/webcrypto-keys.ts", "tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs", "tests/fixtures/node-types/webcrypto-hmac.ts"] },
{ "chapter": "webcrypto", "signatures": ["Class: `SubtleCrypto`", "`subtle` Type: {SubtleCrypto}"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] },
{ "chapter": "webcrypto", "signatures": ["Class: `CryptoKey`", "`cryptoKey.algorithm`", "`extractable` Type: {boolean}", "`type` Type: {string} One of `'secret'`, `'private'`, or `'public'`.", "`usages` Type: {string\\[]}"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] },
{ "chapter": "webcrypto", "signatures": ["Class: `CryptoKey`", "`cryptoKey.algorithm`", "`extractable` Type: {boolean}", "`type` Type: {string} One of `'secret'`, `'private'`, or `'public'`.", "`usages` Type: {string\\[]}"], "status": "partial", "evidence": ["tests/corpus/7124-webcrypto-secret-keys.ts", "tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] },
{ "chapter": "webcrypto", "signatures": ["Class: `Crypto`"], "status": "partial", "evidence": ["tests/corpus/7120-webcrypto-random-uuid.ts", "tests/corpus/7121-webcrypto-crypto-object.mjs", "tests/corpus/6293-webcrypto-digest.ts"] },
{ "chapter": "webcrypto", "symbols": ["crypto.randomUUID", "crypto.getRandomValues"], "status": "supported", "evidence": ["tests/corpus/7120-webcrypto-random-uuid.ts", "tests/corpus/7121-webcrypto-crypto-object.mjs", "tests/corpus/6204-crypto-get-random-values.ts", "tests/corpus/6205-crypto-get-random-values.js"] },
{ "chapter": "globals", "signatures": ["`navigator`"], "status": "partial", "evidence": ["tests/fixtures/node-types/navigator.ts", "tests/harness/project-config.test.ts", "tests/corpus/6331-jose-root-entry/main.ts"] },
{ "chapter": "globals", "signatures": ["`userAgent` Type: {string}"], "status": "supported", "evidence": ["tests/fixtures/node-types/navigator.ts", "tests/harness/project-config.test.ts"] },
{ "chapter": "globals", "signatures": ["Class: `CryptoKey`", "Class: `SubtleCrypto`"], "status": "partial", "evidence": ["tests/corpus/6291-webcrypto-hmac-sign-verify.ts", "tests/corpus/6292-webcrypto-hmac-errors.ts", "tests/corpus/6293-webcrypto-digest.ts", "tests/corpus/6294-webcrypto-js-values.mjs", "tests/corpus/6295-jose-hs256/main.mjs"] },
@@ -413,6 +415,7 @@
{ "symbols": ["assert.deepEqual", "assert.notDeepEqual"], "status": "partial", "evidence": ["tests/corpus/5513-assert-deep-equal-loose.js", "tests/corpus/5514-assert-deep-equal-typed.ts", "tests/corpus/5516-assert-deep-equal-edges.js"] },
{ "symbols": ["assert", "assert.ok", "assert.equal", "assert.notEqual", "assert.strictEqual", "assert.notStrictEqual", "assert.deepStrictEqual", "assert.notDeepStrictEqual", "assert.fail", "assert.match", "assert.doesNotMatch", "assert.throws", "assert.rejects", "assert.ifError"], "status": "partial", "evidence": ["tests/corpus/1600-assert-passing.ts", "tests/corpus/1604-assert-deep-structures.ts", "tests/corpus/1609-assert-async.ts", "tests/corpus/1721-assert-throws-regex-class.ts", "tests/corpus/2936-static-loose-equality.ts", "tests/corpus/5515-assert-deep-strict-typed-messages.ts"] },
{ "chapter": "crypto", "symbols": ["Cipheriv", "cipher.update", "cipher.final", "cipher.setAAD", "cipher.getAuthTag", "cipher.setAutoPadding", "Decipheriv", "decipher.update", "decipher.final", "decipher.setAAD", "decipher.setAuthTag", "decipher.setAutoPadding"], "status": "partial", "evidence": ["tests/corpus/5250-cipher-aes-gcm.ts", "tests/corpus/5251-cipher-aes-cbc.ts", "tests/corpus/5252-cipher-aes-ctr.ts", "tests/corpus/5253-cipher-chacha20-poly1305.ts", "tests/corpus/5254-cipher-errors.js", "tests/corpus/5255-cipher-encodings.cjs", "tests/corpus/5256-cipher-deprecation.cjs", "tests/corpus/5257-cipher-vectors.ts", "tests/corpus/5258-cipher-handles.js", "tests/corpus/5259-cipher-chunked.ts", "tests/corpus/7055-cipher-streams.ts", "tests/corpus/7056-cipher-stream-errors.ts", "tests/corpus/7057-cipher-streams.mjs", "tests/fixtures/node-types/cipher.ts"] },
{ "chapter": "crypto", "symbols": ["crypto.webcrypto", "subtle"], "status": "partial", "evidence": ["tests/corpus/7122-webcrypto-node-crypto.ts", "tests/corpus/7123-webcrypto-node-crypto.cjs"] },
{ "chapter": "crypto", "symbols": ["ECDH", "ecdh.computeSecret", "ecdh.generateKeys", "ecdh.getPrivateKey", "ecdh.getPublicKey", "ecdh.setPrivateKey", "ecdh.setPublicKey"], "status": "partial", "evidence": ["tests/corpus/6700-ecdh-shared-secret.ts", "tests/corpus/6701-ecdh-formats.ts", "tests/corpus/6702-ecdh-errors.js", "tests/corpus/6703-ecdh-private-keys.ts", "tests/corpus/6704-ecdh-set-public-key.cjs", "tests/corpus/6705-ecdh-handles.ts", "tests/fixtures/node-types/ecdh.ts"] },
{ "chapter": "errors", "symbols": ["ERR_CRYPTO_ECDH_INVALID_FORMAT", "ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY"], "status": "partial", "evidence": ["tests/corpus/6702-ecdh-errors.js"] },
{ "symbols": ["crypto.createHash", "Hash", "hash.update", "hash.digest", "hash.copy", "crypto.createHmac", "Hmac", "hmac.update", "hmac.digest", "crypto.hash", "crypto.pbkdf2", "crypto.pbkdf2Sync"], "status": "partial", "evidence": ["tests/corpus/5760-crypto-hash-stream.ts", "tests/corpus/5768-crypto-hash-unknown.ts", "tests/corpus/5769-crypto-hash-unknown.cjs", "tests/corpus/5761-crypto-hash-stream-states.ts", "tests/corpus/5762-crypto-hash-stream.cjs", "tests/corpus/2890-crypto-static-utility.ts", "tests/corpus/node24-crypto-sha2.ts", "tests/corpus/5435-crypto-hash-algorithms.ts", "tests/corpus/5436-crypto-hash-names.js", "tests/corpus/5437-crypto-hmac-algorithms.ts", "tests/corpus/5438-crypto-kdf-digests.ts", "tests/corpus/5440-crypto-hash-encodings.js", "tests/corpus/5441-crypto-hash-output-length.ts", "tests/harness/npm-static.test.ts"] },
+1 -1
View File
@@ -20,7 +20,7 @@
"test:test262": "tsx tests/test262/run.ts",
"test:test262:fetch": "node tests/test262/fetch.mjs",
"test:cache-identity": "node tests/harness/cache-identity.mjs",
"test:lowering-order": "node scripts/lowering-order.mjs --fail-on names",
"test:lowering-order": "node scripts/lowering-order.mjs --fail-on content",
"test:sandbox": "node scripts/sandbox-test.mjs",
"test:sandbox:image": "node scripts/sandbox-image.mjs",
"test:watch": "vitest",
+91 -12
View File
@@ -544,12 +544,15 @@ declare var DOMException: {
readonly prototype: DOMException;
};
/* WebCrypto's HMAC slice (scr_webcrypto.c): crypto.subtle's importKey for
* raw HMAC keys, HMAC sign/verify and the SHA-1/SHA-2 digests, typed as
/* WebCrypto's secret-key slice (scr_webcrypto.c): crypto.subtle's
* importKey, generateKey and exportKey for HMAC and AES keys (importKey
* also for PBKDF2 and HKDF keys), AES-GCM/CBC/CTR encrypt and decrypt,
* PBKDF2 and HKDF deriveBits and deriveKey, HMAC sign/verify and the SHA-1/SHA-2 digests, typed as
* exactly that surface (the declarations mirror @types/node's webcrypto
* shapes). Every other SubtleCrypto method and crypto member is absent,
* so a use is an honest type error. CryptoKey values are native handles
* whose four members read through the handle. */
* shapes), plus the global's randomUUID and getRandomValues. Every other
* SubtleCrypto method and crypto member is absent, so a use is an honest
* type error. CryptoKey values are native handles whose four members read
* through the handle. */
declare var crypto: Crypto;
/* The Crypto and SubtleCrypto constructors (feature probes only) stay
* `unknown`; the values keep this declaration order. */
@@ -582,9 +585,44 @@ interface HmacImportParams extends Algorithm {
hash: HashAlgorithmIdentifier;
length?: number;
}
interface HmacKeyGenParams extends Algorithm {
hash: HashAlgorithmIdentifier;
length?: number;
}
interface AesKeyGenParams extends Algorithm {
length: number;
}
interface AesGcmParams extends Algorithm {
additionalData?: BufferSource;
iv: BufferSource;
tagLength?: number;
}
interface AesCbcParams extends Algorithm {
iv: BufferSource;
}
interface AesCtrParams extends Algorithm {
counter: BufferSource;
length: number;
}
interface Pbkdf2Params extends Algorithm {
hash: HashAlgorithmIdentifier;
iterations: number;
salt: BufferSource;
}
interface HkdfParams extends Algorithm {
hash: HashAlgorithmIdentifier;
info: BufferSource;
salt: BufferSource;
}
interface AesDerivedKeyParams extends Algorithm {
length: number;
}
interface KeyAlgorithm {
name: string;
}
interface AesKeyAlgorithm extends KeyAlgorithm {
length: number;
}
interface HmacKeyAlgorithm extends KeyAlgorithm {
hash: KeyAlgorithm;
length: number;
@@ -623,10 +661,37 @@ interface JsonWebKey {
x?: string;
y?: string;
}
/* The 'jwk' form type-checks like @types/node's; the compiler refuses it
* (the JsonWebKey import is not implemented). */
interface SubtleCrypto {
decrypt(
algorithm: AesGcmParams | AesCbcParams | AesCtrParams,
key: CryptoKey,
data: BufferSource,
): Promise<ArrayBuffer>;
deriveBits(
algorithm: Pbkdf2Params | HkdfParams,
baseKey: CryptoKey,
length?: number | null,
): Promise<ArrayBuffer>;
deriveKey(
algorithm: Pbkdf2Params | HkdfParams,
baseKey: CryptoKey,
derivedKeyType: AlgorithmIdentifier | AesDerivedKeyParams | HmacImportParams,
extractable: boolean,
keyUsages: readonly KeyUsage[],
): Promise<CryptoKey>;
digest(algorithm: AlgorithmIdentifier, data: BufferSource): Promise<ArrayBuffer>;
encrypt(
algorithm: AesGcmParams | AesCbcParams | AesCtrParams,
key: CryptoKey,
data: BufferSource,
): Promise<ArrayBuffer>;
exportKey(format: "jwk", key: CryptoKey): Promise<JsonWebKey>;
exportKey(format: Exclude<KeyFormat, "jwk">, key: CryptoKey): Promise<ArrayBuffer>;
generateKey(
algorithm: AesKeyGenParams | HmacKeyGenParams,
extractable: boolean,
keyUsages: readonly KeyUsage[],
): Promise<CryptoKey>;
importKey(
format: "jwk",
keyData: JsonWebKey,
@@ -651,6 +716,21 @@ interface SubtleCrypto {
}
interface Crypto {
readonly subtle: SubtleCrypto;
getRandomValues<
T extends
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| BigInt64Array
| BigUint64Array,
>(
typedArray: T,
): T;
randomUUID(): `${string}-${string}-${string}-${string}-${string}`;
}
/* Capability-gated globals harness code probes behind feature guards
@@ -2784,8 +2864,7 @@ declare module "crypto" {
b: ArrayBufferView | ArrayBufferLike,
): boolean;
/* WebCrypto's getRandomValues (also the webcrypto object's): integer
* typed arrays, filled in place and returned. The crypto GLOBAL stays
* unknown in fallback builds. */
* typed arrays, filled in place and returned. */
type RandomValuesArray =
| Int8Array
| Uint8Array
@@ -2797,9 +2876,9 @@ declare module "crypto" {
| BigInt64Array
| BigUint64Array;
export function getRandomValues<T extends RandomValuesArray>(typedArray: T): T;
export const webcrypto: {
getRandomValues<T extends RandomValuesArray>(typedArray: T): T;
};
/* The WebCrypto global's object and its SubtleCrypto. */
export const webcrypto: Crypto;
export const subtle: SubtleCrypto;
export function randomFillSync<T extends ArrayBufferView | ArrayBufferLike>(
buffer: T,
offset?: number,
@@ -109,6 +109,8 @@ function match(
ranges,
initialized: findInitializedByteLoopBindings(f).get(loop) ?? new Set(),
proven: new Set(),
workers: false,
owned: new Set(),
});
}
@@ -250,6 +252,79 @@ test("loops to a typed array's length version the other typed arrays they index"
expect(rebound).toContain("bytes.index.invalid");
});
test("worker programs test each receiver's shared storage once in the guard", () => {
// for (let i = 0; i < n; i++) out[i] = src[i] * 2, where either view may
// be shared.
const loop = counted([
set(ref("out", f64s), ref("i"), bin("*", get(ref("src", f64s), ref("i")), num(2))),
]);
const kernel = fn([loop]);
const mod: IrModule = {
irVersion: 17,
sourceFile: loc.file,
entry: "__main",
workers: true,
functions: [
{ name: "__main", params: [], returnType: VOID, locals: [], body: [], loc },
kernel,
],
};
const body = /^define internal [^\n]*@sc_bf_kernel[^]*?^}/m.exec(emitLlvmModule(mod))![0];
const guard = body.slice(0, body.search(/^bytes\.version\.(fast|slow)\d+:$/m));
expect(guard.match(/icmp eq ptr %\S+, null/g)).toHaveLength(2);
const fast = /^bytes\.version\.fast\d+:$[^]*?^bytes\.version\.done\d+:$/m.exec(body)![0];
expect(fast).not.toContain("bytes.shared");
expect(fast).not.toMatch(/(load|store) atomic double/);
// The checked copy keeps the shared-memory path for each access.
const slow = body.slice(body.search(/^bytes\.version\.slow\d+:$/m), body.indexOf(fast));
expect(slow).toContain("load atomic double");
expect(slow).toContain("store atomic double");
});
test("index temps of a version's counter are integers in the fast copy", () => {
// for (let i = 0; i < out.length; i++) { const j = i * 4; out[i] = src[j] + src[j + 1] }
const length: IrExpr = {
kind: "bytesIntrinsic",
method: "length",
receiver: ref("out", f64s),
args: [],
type: F64,
loc,
};
const loop = counted(
[
{ kind: "varDecl", localId: "at", init: bin("*", ref("i"), num(4)), loc },
set(
ref("out", f64s),
ref("i"),
bin(
"+",
get(ref("src", f64s), ref("at")),
get(ref("src", f64s), bin("+", ref("at"), num(1))),
),
),
],
length,
);
const mod: IrModule = {
irVersion: 17,
sourceFile: loc.file,
entry: "__main",
functions: [
{ name: "__main", params: [], returnType: VOID, locals: [], body: [], loc },
fn([loop], [local("at", F64)]),
],
};
const body = /^define internal [^\n]*@sc_bf_kernel[^]*?^}/m.exec(emitLlvmModule(mod))![0];
const fast = /^bytes\.version\.fast\d+:$[^]*?^bytes\.version\.done\d+:$/m.exec(body)![0];
expect(body).toContain("alloca i64 ; integer at");
expect(fast).toMatch(/mul nsw i64 %\S+, 4/);
expect(fast).toMatch(/add nsw i64 %\S+, 1/);
expect(fast).not.toContain("fmul");
expect(fast).not.toContain("fptosi");
expect(fast).not.toContain("bytes.index");
});
test("Float16Array accesses version in elements and share the element tag", () => {
const f16s: IrType = { kind: "bytes", elem: "f16" };
const withHalves = (body: IrStmt[]): IrFunction => {
@@ -6,7 +6,7 @@ import { byteNumberAccess } from "../../ir/byte-numbers.js";
import { everyExprChild, everyStmtChild } from "../../ir/traverse.js";
import { exactInteger, widenInteger } from "./integer-values.js";
import { emitCountedLoopLimit, emitCountedLoopSafety } from "./counted-loops.js";
import { emitBytesHeaderLength } from "./expr-bytes.js";
import { emitBytesHeaderLength, emitBytesSharedStorage } from "./expr-bytes.js";
type Node = IrExpr | IrStmt;
type VarRef = IrExpr & { kind: "varRef" };
@@ -41,8 +41,13 @@ interface AffineCheck {
export interface BytesLoopVersion {
/** Typed-array accesses that are in bounds whenever the guard holds. */
bounds: ReadonlySet<Node>;
/** Worker programs: typed-array accesses whose views the guard proves
* thread-owned (no shared storage). */
owned: ReadonlySet<Node>;
/** Root receiver bindings, read once before the loop. */
receivers: { ref: VarRef; global: boolean }[];
/** Receivers the guard requires to have no shared storage. */
ownedReceivers: number[];
fixed: FixedCheck[];
affine: AffineCheck[];
/** The counted loop whose iteration space the affine checks cover. */
@@ -66,6 +71,11 @@ export interface BytesLoopFacts {
initialized: ReadonlySet<string>;
/** Accesses already proved in bounds without a guard. */
proven: ReadonlySet<Node>;
/** Worker programs: an access of a view that may be shared tests the
* view's shared storage first. */
workers: boolean;
/** Accesses already proved thread-owned by an enclosing version. */
owned: ReadonlySet<Node>;
}
const MAX_COST = 400;
@@ -87,6 +97,11 @@ const INVARIANT_OPS = new Set(["+", "-", "*", "&", "|", "^", "<<", ">>", ">>>"])
* and last values in bounds, such as `out[base + i]` for `i < n` or
* `view.getUint32(i * 4)`.
*
* In worker programs, every access of a view that may be shared tests its
* shared storage. A view's storage is also fixed at construction, so the
* same guard tests each invariant receiver once, and the fast copy reads
* and writes thread-owned views directly.
*
* Only small synchronous loops are versioned (nested loops included), and
* the emitter bounds how deeply versions nest. The checked loop runs
* whenever the guard fails, and keeps every out-of-bounds read, ignored
@@ -314,10 +329,19 @@ export function matchBytesLoopVersion(
}
return index;
};
// Worker programs: accesses through invariant receivers, proven or not,
// become thread-owned when the guard finds no shared storage.
const owned = new Map<number, Node[]>();
const access = (node: Node, array: IrExpr, index: IrExpr, width: number): void => {
if (facts.proven.has(node)) return;
const ref = root(array);
if (!ref) return;
if (facts.workers && !facts.owned.has(node)) {
const receiver = receiverOf(ref);
const nodes = owned.get(receiver);
if (nodes) nodes.push(node);
else owned.set(receiver, [node]);
}
if (facts.proven.has(node)) return;
const shape = counter !== null ? affine(index) : null;
if (shape && shape.counter !== 0) {
const operands = new Set<IrExpr>();
@@ -383,9 +407,15 @@ export function matchBytesLoopVersion(
});
const fixedKept = [...fixed.values()].slice(0, MAX_CHECKS);
const affineKept = affineGroups.slice(0, MAX_CHECKS - fixedKept.length);
if (fixedKept.length === 0 && affineKept.length === 0) return null;
const ownedKept = [...owned.entries()].slice(0, MAX_CHECKS);
if (fixedKept.length === 0 && affineKept.length === 0 && ownedKept.length === 0) return null;
const bounds = new Set<Node>();
const ownedNodes = new Set<Node>();
const used = new Set<number>();
for (const [receiver, nodes] of ownedKept) {
used.add(receiver);
for (const node of nodes) ownedNodes.add(node);
}
const ranges = new Map<IrExpr, IntegerRange>();
const counterLow = Math.max(counted?.range.min ?? 0, -bound),
counterHigh = Math.min(counted?.range.max ?? 0, bound);
@@ -397,7 +427,6 @@ export function matchBytesLoopVersion(
used.add(group.check.receiver);
for (const node of group.nodes) bounds.add(node);
for (const operand of group.operands) {
if (facts.ranges.get(operand)) continue;
const shape = shapes.get(operand)!;
const a = shape.counter * counterLow,
b = shape.counter * counterHigh;
@@ -408,7 +437,14 @@ export function matchBytesLoopVersion(
min += term.sign > 0 ? r.min : -r.max;
max += term.sign > 0 ? r.max : -r.min;
}
if (counterLow <= counterHigh) ranges.set(operand, { min, max });
if (counterLow > counterHigh) continue;
// The guard's bound on the counter can narrow a range the operand
// already has (a loop to a length proves only `i < 2^53`), so that
// `i * 4` in the fast copy is exact integer arithmetic.
const known = facts.ranges.get(operand);
if (!known) ranges.set(operand, { min, max });
else if (min > known.min || max < known.max)
ranges.set(operand, { min: Math.max(min, known.min), max: Math.min(max, known.max) });
}
}
// Renumber the receivers the kept checks use.
@@ -420,7 +456,9 @@ export function matchBytesLoopVersion(
(counted.range.min < -bound || counted.range.max > bound);
return {
bounds,
owned: ownedNodes,
receivers: order.map((index) => receivers[index]!),
ownedReceivers: ownedKept.map(([receiver]) => renumber.get(receiver)!),
fixed: fixedKept.map((g) => ({
receiver: renumber.get(g.check.receiver)!,
end: g.check.end,
@@ -439,7 +477,8 @@ export function matchBytesLoopVersion(
}
/** Branches to `fast` when every access the version proves is in bounds for
* every iteration, and to `slow` otherwise (including a counted loop whose
* every iteration and every view it proves thread-owned has no shared
* storage, and to `slow` otherwise (including a counted loop whose
* integer form is not established, or a module receiver not yet set). */
export function emitBytesLoopVersionGuard(
host: LlvmEmitterContext,
@@ -473,6 +512,11 @@ export function emitBytesLoopVersionGuard(
return wide;
});
const conditions: string[] = [];
for (const index of version.ownedReceivers) {
const owned = B.tmp();
B.line(`${owned} = icmp eq ptr ${emitBytesSharedStorage(host, receivers[index]!)}, null`);
conditions.push(owned);
}
for (const check of version.fixed) {
const ok = B.tmp();
B.line(`${ok} = icmp ugt i64 ${lengths[check.receiver]}, ${check.end}`);
@@ -128,6 +128,8 @@ export function emitFunction(emitter: LlEmitter, fn: IrFunction): string {
emitter.stableCaptureIds =
fn.captures !== undefined ? emitter.lazyCaptures.stableCaptures(fn.name) : new Set();
emitter.integerLoopBindings.clear();
emitter.integerTemps.clear();
emitter.integerTempsEnabled = emitter.debug === null && !fn.async && !fn.generator;
emitter.countedLoopsEnabled = emitter.debug === null && !fn.async && !fn.generator;
emitter.speculativeLoopsEnabled = emitter.countedLoopsEnabled;
emitter.speculativeFunction = fn.name;
@@ -228,6 +230,7 @@ export function emitFunction(emitter: LlEmitter, fn: IrFunction): string {
emitter.integerLoopRanges = rangeFacts.loopRanges;
emitter.int32Elements = slotFacts.elements ?? null;
emitter.bytesBounds = findBytesBounds(numericFn, emitter.integerRanges);
emitter.bytesOwned = new Set();
// Debug builds keep every number local a double for the debugger's
// variable view. Lazily boxed captured parameters keep their double,
// which the box is created from, and so do plain copies of a capture or
@@ -50,6 +50,7 @@ import { emitBytesLoopVersionGuard } from "./bytes-loop-versions.js";
import { mangleLocal } from "../mangle.js";
import { LlvmUnsupportedError } from "./unsupported.js";
import type { LlValue } from "./expr-context.js";
import { widenInteger } from "./integer-values.js";
import { SCALAR_UNION_RESULT } from "./int32-abi.js";
import { emitLiteralSwitch } from "./switch-dispatch.js";
import {
@@ -267,6 +268,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void {
else emitter.toFrame(v);
B.line(`store ${emitter.llType(b.type)} ${v.name}, ptr ${b.slot}`);
emitter.storeIntegerView(s.localId, v, s.init);
storeIntegerTemp(emitter, s.localId, b, v);
if (isRefCounted(b.type)) {
emitter.scopes[emitter.scopes.length - 1]!.push({ slot: b.slot, type: b.type });
emitter.noteLastUseMove(s, b);
@@ -517,6 +519,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void {
s.index,
s.value,
emitter.bytesBounds.has(s),
emitter.bytesOwned.has(s),
);
break;
}
@@ -970,6 +973,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void {
done = B.newLabel("bytes.version.done");
emitBytesLoopVersionGuard(emitter, version, fast, slow);
const originalBounds = emitter.bytesBounds,
originalOwned = emitter.bytesOwned,
originalRanges = emitter.integerRanges;
emitter.bytesLoopVersioning.add(s);
try {
@@ -978,6 +982,8 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void {
B.br(done);
B.startBlock(fast);
emitter.bytesBounds = new Set([...originalBounds, ...version.bounds]);
if (version.owned.size > 0)
emitter.bytesOwned = new Set([...originalOwned, ...version.owned]);
if (version.ranges.size > 0)
emitter.integerRanges = new Map([...originalRanges, ...version.ranges]);
if (version.counted?.guarded) emitter.provenCountedGuards.add(s);
@@ -985,6 +991,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void {
B.br(done);
} finally {
emitter.bytesBounds = originalBounds;
emitter.bytesOwned = originalOwned;
emitter.integerRanges = originalRanges;
emitter.provenCountedGuards.delete(s);
emitter.bytesLoopVersioning.delete(s);
@@ -1539,6 +1546,48 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void {
* Abrupt completions inside tryBody/catchBody ride the finallyStack:
* returns inline copies at the return site, while break/continue use the
* shared region walk before branching to their resolved target. */
/** A constant number local whose initializer has an exact integer form
* (such as `const j = i * 4` in a typed-array loop version) keeps that
* integer in an i64 slot beside the double, so reads such as `px[j + 1]`
* index with integer arithmetic instead of converting the double back.
* Every other declaration of the local drops the integer: the same
* declaration is emitted once per loop copy, and a copy must not read the
* integer another copy stored. */
function storeIntegerTemp(
emitter: LlEmitter,
localId: string,
b: ReturnType<LlEmitter["binding"]>,
v: LlValue,
): void {
const local = b.local;
if (
!emitter.integerTempsEnabled ||
b.kind !== "local" ||
!local ||
local.type.kind !== "f64" ||
local.mutable ||
local.tdz === true ||
emitter.captureIds.has(localId) ||
!v.integer
) {
emitter.integerTemps.delete(localId);
return;
}
const B = emitter.B;
let slot = emitter.integerTemps.get(localId)?.slot;
if (slot === undefined) {
slot = B.slot();
B.entryAllocas.push(`${slot} = alloca i64 ; integer ${local.name}`);
}
B.line(`store i64 ${widenInteger(emitter, v.integer)}, ptr ${slot}`);
emitter.integerTemps.set(localId, {
slot,
type: "i64",
signed: true,
range: v.integer.range,
});
}
export function emitTryCatch(emitter: LlEmitter, s: IrStmt & { kind: "tryCatch" }): void {
const B = emitter.B;
const hasCatch = s.catchBody !== null;
+20 -2
View File
@@ -1081,6 +1081,17 @@ export class LlEmitter {
string,
{ slot: string; type: "i32" | "i64"; signed?: boolean; range?: { min: number; max: number } }
>();
/** Constant number locals whose latest emitted declaration stored an
* exact integer beside the double, such as `const j = i * 4` in a
* typed-array loop version: an i64 slot and the integer's range. */
integerTemps = new Map<
string,
{ slot: string; type: "i64"; signed: true; range: IntegerRange }
>();
/** Integer temps are kept only in optimized synchronous functions: an
* async or generator body resumes with its locals in its frame, which
* an entry alloca is not part of. */
integerTempsEnabled = false;
countedLoopsEnabled = false;
/** Speculative integer loops (speculative-integers.ts) run in this function. */
speculativeLoopsEnabled = false;
@@ -1125,6 +1136,9 @@ export class LlEmitter {
* guard already passed. */
readonly provenCountedGuards = new Set<IrStmt>();
bytesBounds: ReadonlySet<IrExpr | IrStmt> = new Set();
/** Worker programs: typed-array accesses whose views an enclosing version
* guard proved thread-owned. */
bytesOwned: ReadonlySet<IrExpr | IrStmt> = new Set();
integerViews = new Map<string, string>();
/** Module globals the current function owns outright (initializer
* bindings): their function-local slot, which stands in for the global. */
@@ -3836,6 +3850,8 @@ export class LlEmitter {
ranges: this.integerRanges,
initialized,
proven: this.bytesBounds,
workers: this.mod.workers === true,
owned: this.bytesOwned,
});
}
@@ -6196,8 +6212,9 @@ export class LlEmitter {
expr?: IrExpr,
inBounds = false,
invalidNaN = false,
owned = false,
): LlValue {
return emitBytesGet(this, elem, receiver, index, expr, inBounds, invalidNaN);
return emitBytesGet(this, elem, receiver, index, expr, inBounds, invalidNaN, owned);
}
emitToUint32(value: string, expr?: IrExpr, uint32?: string): string {
@@ -6260,8 +6277,9 @@ export class LlEmitter {
indexExpr?: IrExpr,
expr?: IrExpr,
inBounds = false,
owned = false,
): void {
return emitBytesSet(this, elem, receiver, index, value, indexExpr, expr, inBounds);
return emitBytesSet(this, elem, receiver, index, value, indexExpr, expr, inBounds, owned);
}
emitBytesIntrinsic(e: IrExpr & { kind: "bytesIntrinsic" }): LlValue {
@@ -176,6 +176,17 @@ export function emitBytesLength(
return { name: out, type: F64 };
}
/** A view's shared backing (null for a buffer this thread owns). It is
* set at construction, before script code can see the view. */
export function emitBytesSharedStorage(host: LlvmEmitterContext, receiver: string): string {
const B = host.B;
const slot = B.tmp(),
storage = B.tmp();
B.line(`${slot} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 9`);
B.line(`${storage} = load ptr, ptr ${slot}${bytesHeaderAlias(host)}`);
return storage;
}
/** Worker programs: an element of a view of a SharedArrayBuffer (`shared` is
* fixed before script code can see the view) is read and written with one
* relaxed atomic access of its width, as the runtime's shared accessors do;
@@ -186,12 +197,9 @@ export function emitBytesLength(
function emitSharedBytesSplit(host: LlvmEmitterContext, receiver: string): string {
const B = host.B;
host.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
const slot = B.tmp(),
storage = B.tmp(),
const storage = emitBytesSharedStorage(host, receiver),
isShared = B.tmp(),
expected = B.tmp();
B.line(`${slot} = getelementptr inbounds %ScrBytes, ptr ${receiver}, i64 0, i32 9`);
B.line(`${storage} = load ptr, ptr ${slot}${bytesHeaderAlias(host)}`);
B.line(`${isShared} = icmp ne ptr ${storage}, null`);
B.line(`${expected} = call i1 @llvm.expect.i1(i1 ${isShared}, i1 false)`);
const shared = B.newLabel("bytes.shared"),
@@ -209,15 +217,17 @@ export function emitBytesGet(
expr?: IrExpr,
inBounds = false,
invalidNaN = false,
owned = false,
): LlValue {
const B = host.B;
// Any invalid index traps (the array runtime's discipline).
if (!invalidNaN) return emitBytesLoad(host, elem, receiver, index, expr, inBounds, undefined);
if (!invalidNaN)
return emitBytesLoad(host, elem, receiver, index, expr, inBounds, undefined, owned);
// The numeric view of an ordinary read: an invalid index never touches
// storage and reads as NaN — ToNumber of Node's undefined.
const invalid = B.newLabel("bytes.read.invalid");
const done = B.newLabel("bytes.read.done");
const loaded = emitBytesLoad(host, elem, receiver, index, expr, inBounds, invalid);
const loaded = emitBytesLoad(host, elem, receiver, index, expr, inBounds, invalid, owned);
const ok = B.newLabel("bytes.read.ok");
B.br(ok);
B.startBlock(ok);
@@ -260,8 +270,10 @@ function emitBytesLoad(
expr: IrExpr | undefined,
inBounds: boolean,
invalid: string | undefined,
owned: boolean,
): LlValue {
if (host.mod.workers) {
// `owned`: an enclosing version guard proved the view thread-owned.
if (host.mod.workers && !owned) {
const idx = emitBytesIndex(host, receiver, index, expr, invalid, inBounds);
return emitSharedOrLocalLoad(host, elem, receiver, idx);
}
@@ -515,13 +527,14 @@ export function emitBytesSet(
indexExpr?: IrExpr,
expr?: IrExpr,
inBounds = false,
owned = false,
): void {
const B = host.B;
// Invalid integer-indexed writes are ignored after evaluating the RHS.
const done = B.newLabel("bytes.store.done");
const data = host.emitBytesData(receiver);
const idx = emitBytesIndex(host, receiver, index, indexExpr, done, inBounds);
if (host.mod.workers && elem === "f16") {
if (host.mod.workers && !owned && elem === "f16") {
host.declare(`declare void @scr_bytes_shared_set(ptr, ${host.sizeType}, double)`);
const local = emitSharedBytesSplit(host, receiver);
B.line(
@@ -532,10 +545,24 @@ export function emitBytesSet(
}
let stored: string | null;
if (elem === "u8c") {
host.declare(`declare double @scr_bytes_to_u8_clamp(double)`);
const clamped = B.tmp();
// ToUint8Clamp, as scr_bytes_to_u8_clamp computes it: NaN and values
// at most 0 store 0, values at least 255 store 255, and values between
// round to the nearest integer, ties to even. Adding and subtracting
// 2^52 rounds a value in (0, 255) that way in the default rounding mode,
// with no call or branch, so loops of clamped stores can vectorize.
const positive = B.tmp(),
high = B.tmp(),
shifted = B.tmp(),
rounded = B.tmp(),
upper = B.tmp(),
clamped = B.tmp();
stored = B.tmp();
B.line(`${clamped} = call double @scr_bytes_to_u8_clamp(double ${value.name})`);
B.line(`${positive} = fcmp ogt double ${value.name}, 0.0`);
B.line(`${high} = fcmp oge double ${value.name}, 255.0`);
B.line(`${shifted} = fadd double ${value.name}, 0x4330000000000000`);
B.line(`${rounded} = fsub double ${shifted}, 0x4330000000000000`);
B.line(`${upper} = select i1 ${high}, double 255.0, double ${rounded}`);
B.line(`${clamped} = select i1 ${positive}, double ${upper}, double 0.0`);
B.line(`${stored} = fptoui double ${clamped} to i32`);
} else
stored =
@@ -572,7 +599,7 @@ export function emitBytesSet(
ty = "i32";
operand = stored!;
}
if (host.mod.workers) {
if (host.mod.workers && !owned) {
// A shared view's element takes a relaxed atomic store of its width.
const local = emitSharedBytesSplit(host, receiver);
const p = B.tmp();
@@ -721,6 +748,7 @@ export function emitBytesIntrinsic(
e.args[0],
host.bytesBounds.has(e),
e.invalidNaN === true,
host.bytesOwned.has(e),
);
case "slice":
return call(
@@ -59,7 +59,8 @@ export function emitLiteralExpr(
throw new InternalCompilerError(`llvm emitter bug: bare unitLit '${e.unit}'`);
case "varRef": {
host.materializeSplitLocal(e.localId);
const integerSlot = host.integerLoopBindings.get(e.localId);
const integerSlot =
host.integerLoopBindings.get(e.localId) ?? host.integerTemps.get(e.localId);
if (integerSlot !== undefined) {
const integer = B.tmp();
const number = B.tmp();
@@ -448,6 +448,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"buffer.poolFromArray": "scr_buffer_from_arr",
"buffer.poolFromDyn": "scr_buffer_from_dyn_pooled",
"buffer.poolFromDynRef": "scr_buffer_from_dyn_ref_pooled",
"buffer.poolFromDynOffset": "scr_buffer_from_dyn_offset_pooled",
"buffer.poolSize": "scr_buffer_pool_size",
"buffer.setPoolSize": "scr_buffer_set_pool_size",
"buffer.byteLenDyn": "scr_buffer_byte_length_dyn",
@@ -615,10 +616,17 @@ export const LIB_FN_SYMS: Record<string, string> = {
"crypto.keyObjectInput": "scr_crypto_key_object_input",
"crypto.isKeyObject": "scr_crypto_is_key_object",
"webcrypto.importKey": "scr_webcrypto_import_key",
"webcrypto.generateKey": "scr_webcrypto_generate_key",
"webcrypto.exportKey": "scr_webcrypto_export_key",
"webcrypto.encrypt": "scr_webcrypto_encrypt",
"webcrypto.decrypt": "scr_webcrypto_decrypt",
"webcrypto.deriveBits": "scr_webcrypto_derive_bits",
"webcrypto.deriveKey": "scr_webcrypto_derive_key",
"webcrypto.sign": "scr_webcrypto_sign",
"webcrypto.verify": "scr_webcrypto_verify",
"webcrypto.digest": "scr_webcrypto_digest",
"webcrypto.isCryptoKey": "scr_webcrypto_is_crypto_key",
"webcrypto.object": "scr_webcrypto_object",
"crypto.randomUUIDv7": "scr_crypto_random_uuid_v7",
"crypto.pbkdf2": "scr_crypto_pbkdf2",
"crypto.hkdf": "scr_crypto_hkdf",
@@ -1038,6 +1046,9 @@ export const LIB_FN_SYMS: Record<string, string> = {
"atomics.op": "scr_atomics_op",
"atomics.notify": "scr_atomics_notify",
"atomics.wait": "scr_atomics_wait",
"atomics.opDyn": "scr_atomics_op_dyn",
"atomics.waitDyn": "scr_atomics_wait_dyn",
"atomics.notifyDyn": "scr_atomics_notify_dyn",
"worker.new": "scr_worker_new",
"worker.isMainThread": "scr_context_is_main",
"worker.threadId": "scr_context_thread_number",
@@ -1574,6 +1585,11 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
// ecdh.setPublicKey reports DEP0031 on the next tick.
"crypto.ecdhNew",
// WebCrypto results settle through the deferred crypto queue.
"webcrypto.generateKey",
"webcrypto.encrypt",
"webcrypto.decrypt",
"webcrypto.deriveBits",
"webcrypto.deriveKey",
"webcrypto.sign",
"webcrypto.verify",
"webcrypto.digest",
@@ -84,8 +84,9 @@ export interface NativeLinkFeatures {
foreignFfi: boolean;
nodeTest: boolean;
tls: boolean;
/** crypto.createCipheriv/createDecipheriv: scr_cipher.c plus the mbedTLS
* archive. */
/** crypto.createCipheriv/createDecipheriv and WebCrypto's encrypt and
* decrypt: scr_cipher.c plus the mbedTLS archive (and, with webcrypto,
* scr_webcrypto_aes.c). */
cipher: boolean;
/** crypto.createECDH and ECDH.convertKey: scr_ecdh.c plus the mbedTLS
* archive. */
@@ -243,8 +243,9 @@ export interface CcOptions {
* The unit also compiles whenever `tls` does — scr_tls.c consults its
* default-set override and shared Windows-certificate enumerator. */
tlsCa?: boolean;
/** crypto.createCipheriv/createDecipheriv: compiles scr_cipher.c and links
* the mbedTLS archive (shared with tls). */
/** crypto.createCipheriv/createDecipheriv and WebCrypto's encrypt and
* decrypt: compiles scr_cipher.c (and, with webcrypto,
* scr_webcrypto_aes.c) and links the mbedTLS archive (shared with tls). */
cipher?: boolean;
/** crypto.createECDH and ECDH.convertKey: compiles scr_ecdh.c and links
* the mbedTLS archive (shared with tls). */
@@ -297,6 +297,8 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
unit((u) => u.opts.foreignFfi, "scr_ffi_queue.c"),
unit((u) => u.opts.nodeTest, "scr_test.c"),
unit((u) => u.opts.webcrypto, "scr_webcrypto.c"),
// The crypto global's object extends scr_async_dyn.c's.
unit((u) => u.opts.webcrypto && u.opts.dynAsync, "scr_webcrypto_object.c"),
// The CA-store unit rides its own gate OR the tls one: scr_tls.c
// references its default-set override unconditionally.
unit((u) => u.tlsCa, "scr_tls_ca.c"),
@@ -308,6 +310,7 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
...(u.tls ? [u.rt(join(u.rtDir, "scr_tls.c"))] : []),
...(u.cipher ? [u.rt(join(u.rtDir, "scr_cipher.c"))] : []),
...(u.ecdh ? [u.rt(join(u.rtDir, "scr_ecdh.c"))] : []),
...(u.cipher && u.opts.webcrypto ? [u.rt(join(u.rtDir, "scr_webcrypto_aes.c"))] : []),
u.tlsArchive,
// mbedTLS's win32 entropy poll is BCryptGenRandom (bcrypt.h).
// The unconditional win32 libs above do not carry it.
@@ -208,6 +208,14 @@ export function moduleWasiUnavailableSurface(
};
return false;
}
if (node.fn === "webcrypto.encrypt" || node.fn === "webcrypto.decrypt") {
found = {
surface:
"WebCrypto encrypt and decrypt (the wasm32-wasi runtime does not include the mbedTLS library)",
loc,
};
return false;
}
if (node.fn === "crypto.ecdhNew" || node.fn === "crypto.ecdhConvertKey") {
found = {
surface: "ECDH (the wasm32-wasi runtime does not include the mbedTLS library)",
+1 -2
View File
@@ -174,8 +174,7 @@ class ContractChecker {
return `${owner}.${method[2]}`;
}
if (fn?.sourceName !== undefined && fn.sourceName !== "<anonymous>") return fn.sourceName;
if (fn !== undefined && /^%fn\.[0-9a-f]{12}$/.test(fnName))
return `the function at ${this.where(fn.loc)}`;
if (fn !== undefined && /^%fn\d+$/.test(fnName)) return `the function at ${this.where(fn.loc)}`;
return fnName.replace(/^%/, "");
}
@@ -9,7 +9,6 @@ import {
type SrcLoc,
typeEquals,
} from "../../ir/ir.js";
import { keyedHelperName } from "./stable-names.js";
import { numLit, varRef } from "../../ir/build.js";
import { typeKey } from "../type-mapper.js";
import type { Lowerer } from "./lowerer.js";
@@ -126,7 +125,7 @@ export function lowerArrayFill(
writeNull;
let name = lowerer.arrHofHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%arr.fill", key);
name = "%arr.fill." + lowerer.arrHofHelpers.size;
lowerer.arrHofHelpers.set(key, name);
const a = varRef("a.0", arrType, loc);
const n = varRef("n.0", F64, loc);
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import { InternalCompilerError } from "../../errors.js";
import { dynUndefinedExpr, helperLoc, varRef } from "../../ir/build.js";
import {
@@ -253,7 +252,7 @@ export function lowerSafeIndexRead(
let name = lowerer.arrHofHelpers.get(key);
if (!name) {
const at = helperLoc(loc);
name = keyedHelperName(lowerer, "%arr.idxOr", key);
name = `%arr.idxOr.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
const arrT = arr.type;
lowerer.liftedFns.push({
@@ -299,7 +298,7 @@ export function lowerSafeBytesRead(
let name = lowerer.arrHofHelpers.get(key);
if (!name) {
const at = helperLoc(loc);
name = keyedHelperName(lowerer, "%bytes.idxOr", key);
name = `%bytes.idxOr.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
const b = varRef("b.0", bytesT, at);
const i = varRef("i.0", F64, at);
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import {
BYTES_U8,
BOOL,
@@ -73,7 +72,7 @@ export function lowerOptionalNumberPredicate(
const key = `number.optionalPredicate:${fn}:${typeKey(widened)}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%number.optionalPredicate", key);
helper = `%number.optionalPredicate.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const input = varRef("value.0", widened, loc);
lowerer.liftedFns.push({
@@ -60,10 +60,14 @@ export function lowerAtomicsCall(
// has a concrete typed-array binding. Validate the actual lowered storage.
const array = lowerer.lowerExpr(source);
const type = array.type;
// An untyped array (a JavaScript `workerData` or field): the runtime
// checks its kind and throws Node's TypeError for any other value.
const dynamic = type?.kind === "dyn";
if (
type?.kind !== "bytes" ||
!["i8", "u8", "i16", "u16", "i32", "u32"].includes(type.elem) ||
((member === "wait" || member === "notify") && type.elem !== "i32")
!dynamic &&
(type?.kind !== "bytes" ||
!["i8", "u8", "i16", "u16", "i32", "u32"].includes(type.elem) ||
((member === "wait" || member === "notify") && type.elem !== "i32"))
) {
lowerer.noLowering(
`Atomics.${member} over this array`,
@@ -82,7 +86,7 @@ export function lowerAtomicsCall(
if (member === "wait")
return {
kind: "libCall",
fn: "atomics.wait",
fn: dynamic ? "atomics.waitDyn" : "atomics.wait",
args: [array, index, arg(2, 0), arg(3, Infinity)],
type: STRING,
loc,
@@ -90,14 +94,14 @@ export function lowerAtomicsCall(
if (member === "notify")
return {
kind: "libCall",
fn: "atomics.notify",
fn: dynamic ? "atomics.notifyDyn" : "atomics.notify",
args: [array, index, arg(2, Infinity)],
type: F64,
loc,
};
return {
kind: "libCall",
fn: "atomics.op",
fn: dynamic ? "atomics.opDyn" : "atomics.op",
args: [array, index, arg(2, 0), arg(3, 0), number(operation)],
type: F64,
loc,
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { recordToEnvPairs } from "./environment.js";
import { lowerChildArgsArg } from "./child-process.js";
import { boolLit, numLit, varRef } from "../../../ir/build.js";
@@ -219,7 +218,7 @@ function execFileAsyncHelper(
const key = shell ? "execAsync" : "execFileAsync";
const existing = lowerer.valueHelpers.get(key);
if (existing) return { name: existing, shapeId, envShapeId };
const name = keyedHelperName(lowerer, `%${key}`, key);
const name = `%${key}.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const recT: IrType = { kind: "record", shapeId };
const envRecT: IrType = { kind: "record", shapeId: envShapeId };
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { recordToEnvPairs } from "./environment.js";
import { lowerChildArgsArg } from "./child-process.js";
import { boolLit, numLit, strLit, varRef } from "../../../ir/build.js";
@@ -542,7 +541,7 @@ function execStdioModeHelper(
const key = `cp.stdiomode:${shapeId}:${fd}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, `%cp.stdioMode.${fd}`, key);
const name = `%cp.stdioMode.${fd}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
const optsT: IrType = { kind: "record", shapeId };
@@ -7,6 +7,7 @@ import { type Lowerer, own } from "../lowerer.js";
import { locOf } from "../../program.js";
import { CIPHER_TYPE_NAMES } from "../../type-mapper.js";
import { CRYPTO_CIPHERS, CRYPTO_CURVES, CRYPTO_HASHES } from "../crypto-tables.js";
import { stdlibGlobalNameOf } from "../surfaces.js";
import {
BOOL,
BYTES_U8,
@@ -1317,8 +1318,10 @@ export function lowerCryptoGetRandomValues(
}
/** Whether `receiver` names a WebCrypto Crypto object: the `crypto`
* global (also through globalThis) or node:crypto's `webcrypto`. */
* global (also through globalThis or an alias of it) or node:crypto's
* `webcrypto`. */
export function isWebCryptoReceiver(lowerer: Lowerer, receiver: ts.Expression): boolean {
if (stdlibGlobalNameOf(lowerer, receiver) === "crypto") return true;
if (ts.isIdentifier(receiver)) {
if (receiver.text === "crypto" && lowerer.isStdlibGlobal(receiver, "crypto")) return true;
const imported = lowerer.builtinImportOf(receiver);
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import * as ts from "../../ts7/adapter.js";
import { type Lowerer } from "../lowerer.js";
import { locOf } from "../../program.js";
@@ -85,7 +84,7 @@ export function lowerDisposableStackUseCall(
const key = `disposableStack.use:${className}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%disposableStack.use", key);
helper = `%disposableStack.use.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const context = newFnCtx(false, null, null, VOID);
lowerer.fnStack.push(context);
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { dynUndefinedExpr, boolLit, numLit, varRef } from "../../../ir/build.js";
import * as ts from "../../ts7/adapter.js";
import { type Lowerer, ladderFenceExpr } from "../lowerer.js";
@@ -443,7 +442,7 @@ export function lowerStringOrBytesWrite(
const key = `fs.${append ? "append" : "write"}:${dataType.unionId}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%fs.writeData", key);
helper = `%fs.writeData.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const body: IrStmt[] = [];
for (let tag = 0; tag < arms.length; tag++) {
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { isDateJsonSource } from "./dates.js";
import { dynUndefinedExpr, varRef } from "../../../ir/build.js";
import { InternalCompilerError } from "../../../errors.js";
@@ -254,7 +253,7 @@ function lowerOptionalStringifyRoot(
const key = `json.optionalString:${value.type.unionId}:${JSON.stringify(indent)}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%json.optionalString", key);
helper = `%json.optionalString.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const input = varRef("value.0", value.type, loc);
const serialized: IrExpr = {
@@ -72,17 +72,14 @@ export function lowerNumberStaticCall(
return { kind: "libCall", fn, args: [arg], type: BOOL, loc };
}
if (member === "parseFloat" || member === "parseInt") {
const want = member === "parseFloat" ? 1 : 2;
if (call.arguments.nodes.length !== want) {
lowerer.noLowering(
`Number.${member} with ${call.arguments.nodes.length} argument${call.arguments.nodes.length === 1 ? "" : "s"}`,
call,
member === "parseInt" ? "pass an explicit radix: Number.parseInt(s, 10)" : undefined,
);
const count = call.arguments.nodes.length;
if (count < 1 || count > (member === "parseFloat" ? 1 : 2)) {
lowerer.noLowering(`Number.${member} with ${count} argument${count === 1 ? "" : "s"}`, call);
}
// Number.parseFloat/Number.parseInt ARE the global parsers: over a
// string argument they lower to the same static parse (parseInt with
// its explicit radix); only the coercing argument shapes are refused.
// its radix, or radix 0 when it is missing); only the coercing argument
// shapes are refused.
const probed = tryLowerExpression(lowerer, call.arguments.nodes[0]!);
if (probed !== null && isStringLike(lowerer, probed.type)) {
const parsed = lowerStaticNumberGlobal(lowerer, member, call, call.arguments.nodes, loc);
@@ -101,7 +98,10 @@ export function lowerNumberStaticCall(
};
if (member === "parseFloat")
return { kind: "libCall", fn: "num.parseFloat", args: [text], type: F64, loc };
const radix = lowerer.lowerExprExpecting(call.arguments.nodes[1]!, F64);
const radixNode = call.arguments.nodes[1];
const radix: IrExpr = radixNode
? lowerer.lowerExprExpecting(radixNode, F64)
: { kind: "numLit", value: 0, type: F64, loc };
return { kind: "libCall", fn: "num.parseInt", args: [text, radix], type: F64, loc };
}
const argText =
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { numLit, strLit, varRef } from "../../../ir/build.js";
import * as ts from "../../ts7/adapter.js";
import { type Lowerer } from "../lowerer.js";
@@ -220,7 +219,7 @@ export function lowerOsCpusCall(lowerer: Lowerer, call: ts.CallExpression, loc:
const key = `os.cpus:${typeKey(result)}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%os.cpus", key);
helper = `%os.cpus.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const index = varRef("i.0", F64, loc);
const field = (k: number): IrExpr => ({
@@ -347,7 +346,7 @@ export function lowerPathParseCall(
const key = `${fn}:${typeKey(result)}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%path.parse", key);
helper = `%path.parse.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const parts = varRef("parts.0", arrayOf(STRING), loc);
lowerer.liftedFns.push({
@@ -449,7 +448,7 @@ export function lowerPathFormatCall(
const key = `${fn}:${typeKey(recordT)}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%path.format", key);
helper = `%path.format.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const object = varRef("object.0", recordT, loc);
const read = (name: string): IrExpr => {
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import * as ts from "../../ts7/adapter.js";
import { type Lowerer } from "../lowerer.js";
import { locOf } from "../../program.js";
@@ -331,7 +330,7 @@ function lowerPerfTimelineCall(
const key = `perf.${many ? "entries" : timeline.fn}:${typeKey(result)}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, `%perf.${many ? "entries" : member}`, key);
helper = `%perf.${many ? "entries" : member}.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const argsRef = varRef("a.0", DYN, loc);
const whichRef = varRef("w.0", F64, loc);
@@ -462,7 +461,7 @@ export function perfEntryTag(
const key = `perf.tag:${entryT.shapeId}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%perf.tag", key);
helper = `%perf.tag.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const e = varRef("e.0", entryT, loc);
lowerer.liftedFns.push({
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { stdinKeypressListener } from "./readline.js";
import { envSnapshotHelper, lowerProcessLoadEnvFile } from "./environment.js";
import { dynUndefinedExpr, boolLit, numLit, strLit, varRef } from "../../../ir/build.js";
@@ -1562,7 +1561,7 @@ export function lowerProcessMethodCall(
const key = `${prefix}usage.diff:${prevT.shapeId}:${t.shapeId}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, `%${prefix}usage.diff`, key);
helper = `%${prefix}usage.diff.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const pRef: IrExpr = { kind: "varRef", localId: "p.0", type: prevT, loc };
const fieldOf = (name: string): IrExpr => ({
@@ -1637,7 +1636,7 @@ export function lowerProcessMethodCall(
const key = `process.memoryUsage:${t.shapeId}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%process.memoryUsage", key);
helper = `%process.memoryUsage.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
lowerer.liftedFns.push({
name: helper,
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { checkedPromiseAll, thenReader } from "../checked-promise-all.js";
import { InternalCompilerError } from "../../../errors.js";
import * as ts from "../../ts7/adapter.js";
@@ -56,7 +55,7 @@ function lowerTuplePromiseAll(
const key = `${elems.map((e) => typeKey(e.type)).join(",")}->${typeKey(resultT.inner)}`;
let name = lowerer.arrHofHelpers.get(`promise.allTuple:${key}`);
if (name === undefined) {
name = keyedHelperName(lowerer, "%promise.allTuple", `promise.allTuple:${key}`);
name = `%promise.allTuple.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(`promise.allTuple:${key}`, name);
const ctx = newFnCtx(false, null, null, resultT.inner);
ctx.isAsync = true;
@@ -558,7 +557,7 @@ export function lowerPromiseStaticCall(
const key = `promise.try:${typeKey(f.type)}:${args.map((a) => typeKey(a.type)).join(",")}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%promise.try", key);
helper = `%promise.try.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
const fRef: IrExpr = { kind: "varRef", localId: "f.0", type: f.type, loc };
const argLocals = args.map((a, i) => ({ id: `a${i}.0`, name: `a${i}`, type: a.type }));
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import * as ts from "../../ts7/adapter.js";
import { type Lowerer } from "../lowerer.js";
import { varRef } from "../../../ir/build.js";
@@ -171,7 +170,7 @@ function promisifyMaker(lowerer: Lowerer, shape: CallbackShape, loc: SrcLoc): st
const key = `promisify:${typeKey(shape.fnT)}=>${typeKey(shape.resultT)}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const base = keyedHelperName(lowerer, "%promisify", key);
const base = `%promisify.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, base);
const { fnT, resultT, args, callbackT, callbackParamT, inner, error } = shape;
const promiseT = resultT.ret;
@@ -353,7 +353,8 @@ function lowerPromptArg(lowerer: Lowerer, node: ts.Expression): { value: IrExpr;
* - on/once/addListener for 'line' ((input: string) or no parameters),
* 'error' ((err: Error) or no parameters), 'history' ((history:
* string[]) or no parameters), and 'close'/'pause'/'resume'/'SIGINT'/
* 'SIGTSTP'/'SIGCONT' (no parameters), as statements.
* 'SIGTSTP'/'SIGCONT' (no parameters); as an expression the call yields
* the interface, so registrations chain.
* Everything else the lib declares fences member-qualified. Null for
* non-Interface receivers. */
export function lowerReadlineMethodCall(
@@ -505,14 +506,19 @@ export function lowerReadlineMethodCall(
`${RL_EVENT_LIST} are the supported readline events`,
);
}
if (!ts.isExpressionStatement(call.parent)) {
lowerer.unsupported(
"SC1090",
call,
"chaining readline listener registration (register each listener as its own statement)",
);
}
const recv = receiver();
// As an expression (`rl.on("line", f).on("close", g)`), the call
// registers the listener and yields the interface, as Node's on() does.
const recvLocal = ts.isExpressionStatement(call.parent)
? undefined
: lowerer.declareHiddenLocal("%readlineListener", F64);
const saved: IrStmt[] =
recvLocal === undefined
? []
: [{ kind: "varDecl", localId: recvLocal.id, init: receiver(), loc }];
const recv: IrExpr =
recvLocal === undefined
? receiver()
: { kind: "varRef", localId: recvLocal.id, type: F64, loc };
const cb = lowerer.lowerExpr(args[1]!);
const want =
event === "line"
@@ -547,13 +553,21 @@ export function lowerReadlineMethodCall(
: `'${event}' listeners with parameters or a return value (use ())`,
);
}
return {
const register: IrExpr = {
kind: "libCall",
fn: "rl.on",
args: [recv, numLit(ev, loc), cb, boolLit(name === "once", loc)],
type: VOID,
loc,
};
if (recvLocal === undefined) return register;
return {
kind: "seqExpr",
stmts: [...saved, { kind: "exprStmt", expr: register, loc }],
result: { kind: "varRef", localId: recvLocal.id, type: F64, loc },
type: F64,
loc,
};
}
lowerer.noLowering(
`readline.Interface.${name}`,
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { isBuiltinClassInstance } from "./receiver-types.js";
import { strLit, varRef, dynUndefinedExpr } from "../../../ir/build.js";
import { staticTextDecoderEncoding } from "../text-decoder-encoding.js";
@@ -130,7 +129,7 @@ function stringDecoderHelper(
const key = `strdec.${op}`;
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, `%strdec.${op}`, key);
const name = `%strdec.${op}.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const recT: IrType = { kind: "record", shapeId };
@@ -85,7 +85,7 @@ export function lowerTimersPromisesSetInterval(
const genT = timersPromisesIntervalType(lowerer, expr);
if (genT === null) lowerer.badType(expr, lowerer.typeOf(expr));
const yielded = lowerer.coerceInto(valueNode, value, genT.yieldT);
const fnName = lowerer.closureName(loc, "_tpInterval");
const fnName = `%fn${lowerer.lambdaCounter++}_tpInterval`;
const msParam: IrLocal = { id: "%tp.ms", name: "delay", type: F64, mutable: false };
const valueParam: IrLocal = { id: "%tp.value", name: "value", type: genT.yieldT, mutable: false };
const promiseVoid: IrType = { kind: "promise", inner: VOID };
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { nodeThrowExpr, countedFor, strLit, varRef, dynUndefinedExpr } from "../../../ir/build.js";
import * as ts from "../../ts7/adapter.js";
import { type Lowerer, own } from "../lowerer.js";
@@ -293,7 +292,7 @@ function searchParamsFromTuples(
const key = `sp.fromTuples:${row.shapeId}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%sp.fromTuples", key);
helper = `%sp.fromTuples.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const pairsT = init.type;
const sp = (): IrExpr => varRef("sp.0", SEARCH_PARAMS_T, loc);
@@ -373,7 +372,7 @@ function lowerOptionalStringSearchParams(
const key = `sp.optionalString:${init.type.unionId}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%sp.optionalString", key);
helper = `%sp.optionalString.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const value = varRef("init.0", init.type, loc);
lowerer.liftedFns.push({
@@ -679,7 +678,7 @@ function lowerSpForEachCall(
const key = `${arity}:${typeKey(fnRet)}`;
let helper = lowerer.spHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%sp.forEach", key);
helper = `%sp.forEach.${lowerer.spHofHelpers.size}`;
lowerer.spHofHelpers.set(key, helper);
lowerer.liftedFns.push(buildSpForEachFn(helper, arity, fnRet, loc));
}
@@ -5,7 +5,6 @@
* spirit of process.memoryUsage(): approximate values, not V8 heap
* figures (a compiled binary embeds no V8 engine). Every other member
* fences at its use with the by-design reason (builtinFenceHintOf). */
import { keyedHelperName } from "../stable-names.js";
import { numLit } from "../../../ir/build.js";
import * as ts from "../../ts7/adapter.js";
import { type Lowerer } from "../lowerer.js";
@@ -56,7 +55,7 @@ export function lowerV8HeapStatisticsCall(
const key = `v8.getHeapStatistics:${t.shapeId}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%v8.getHeapStatistics", key);
helper = `%v8.getHeapStatistics.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const sample = (index: number): IrExpr => ({
kind: "libCall",
@@ -5,34 +5,57 @@ import { BOOL, DYN, type IrExpr, type IrLibFn } from "../../../ir/ir.js";
import { lowerDynObjectLiteral } from "../expressions/object-literals.js";
import { stdlibGlobalNameOf } from "../surfaces.js";
import { WEBCRYPTO_DYN_TYPES } from "../../type-mapper.js";
import { isWebCryptoReceiver } from "./crypto.js";
/* WebCrypto's HMAC slice (scr_webcrypto.c): `crypto.subtle.importKey`,
* `sign`, `verify` and `digest` called on the crypto global (also through
* globalThis) lower to runtime entry points that take the argument vector
* as one dyn array and answer a promise, so Web IDL conversion, algorithm
* normalization and every error stay Node-shaped at run time. CryptoKey
/* WebCrypto's secret-key slice (scr_webcrypto.c, with encrypt and
* decrypt in scr_webcrypto_aes.c): `crypto.subtle`'s `importKey`,
* `generateKey`, `exportKey`, `encrypt`, `decrypt`, `deriveBits`,
* `deriveKey`, `sign`, `verify` and `digest`
* called on the crypto global (also through
* globalThis, node:crypto's webcrypto and node:crypto's subtle) lower to
* runtime entry points that take the argument vector as one dyn array and
* answer a promise, so Web IDL conversion, algorithm normalization and
* every error stay Node-shaped at run time. CryptoKey
* values are native handles; their type maps to dyn and their members
* are handle reads. Other SubtleCrypto methods are refused by name. */
const SUBTLE_LOWERED: ReadonlyMap<string, IrLibFn> = new Map([
["importKey", "webcrypto.importKey"],
["generateKey", "webcrypto.generateKey"],
["exportKey", "webcrypto.exportKey"],
["encrypt", "webcrypto.encrypt"],
["decrypt", "webcrypto.decrypt"],
["deriveBits", "webcrypto.deriveBits"],
["deriveKey", "webcrypto.deriveKey"],
["sign", "webcrypto.sign"],
["verify", "webcrypto.verify"],
["digest", "webcrypto.digest"],
]);
const SUBTLE_HINT =
"the native WebCrypto surface is crypto.subtle.importKey('raw', ...) for HMAC keys, sign and verify with HMAC, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle";
"the native WebCrypto surface is crypto.subtle's importKey, generateKey and exportKey for HMAC and AES keys (importKey also for PBKDF2 and HKDF keys), encrypt and decrypt with AES-GCM, AES-CBC or AES-CTR, deriveBits and deriveKey with PBKDF2 or HKDF, sign and verify with HMAC, and digest with SHA-1, SHA-256, SHA-384 or SHA-512, each called directly on crypto.subtle";
/** Whether `node` is `crypto.subtle` on the WebCrypto global. */
/** Whether `node` is a SubtleCrypto object: `subtle` on a WebCrypto
* Crypto object (the global, globalThis.crypto or node:crypto's
* `webcrypto`) or node:crypto's own `subtle` export, named or through
* the namespace. */
function isSubtleReceiver(lowerer: Lowerer, node: ts.Expression): boolean {
while (ts.isParenthesizedExpression(node)) node = node.expression;
return (
ts.isPropertyAccessExpression(node) &&
!node.questionDotToken &&
node.name.text === "subtle" &&
stdlibGlobalNameOf(lowerer, node.expression) === "crypto"
);
if (ts.isIdentifier(node)) {
const imported = lowerer.builtinImportOf(node);
return imported?.module === "crypto" && imported.member === "subtle";
}
if (
!ts.isPropertyAccessExpression(node) ||
node.questionDotToken ||
node.name.text !== "subtle"
) {
return false;
}
if (stdlibGlobalNameOf(lowerer, node.expression) === "crypto") return true;
if (isWebCryptoReceiver(lowerer, node.expression)) return true;
const member = lowerer.builtinMemberOf(node);
return member?.module === "crypto" && member.member === "subtle";
}
/** `crypto.subtle.<method>(...args)`: the lowered methods become their
@@ -51,17 +74,6 @@ export function lowerWebCryptoCall(lowerer: Lowerer, call: ts.CallExpression): I
if (call.arguments.nodes.some(ts.isSpreadElement)) {
lowerer.noLowering(`crypto.subtle.${member} with spread arguments`, call, SUBTLE_HINT);
}
// JsonWebKey imports are not implemented: a literal 'jwk' format is
// refused here (the runtime refuses a computed one).
const format = call.arguments.nodes[0];
if (
member === "importKey" &&
format !== undefined &&
ts.isStringLiteralLike(format) &&
format.text === "jwk"
) {
lowerer.noLowering("crypto.subtle.importKey with the 'jwk' key format", format, SUBTLE_HINT);
}
const loc = locOf(call);
const args = call.arguments.nodes.map((arg) =>
ts.isObjectLiteralExpression(arg)
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import { forwardedTupleType, pureForwardedValues, stripTypeWrappers } from "./typed-arguments.js";
import { dynUndefinedExpr } from "../../ir/build.js";
import { InternalCompilerError } from "../../errors.js";
@@ -754,7 +753,7 @@ function holeReadingLift(lowerer: Lowerer, from: IrType, to: IrType, loc: SrcLoc
const key = `arr.holes:${typeKey(elem)}`;
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%arr.holes", key);
const name = `%arr.holes.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
lowerer.liftedFns.push(
buildArrayConversion(
@@ -849,10 +849,7 @@ export function collectSignatureInner(lowerer: Lowerer, decl: ts.FunctionDeclara
lowerer.genericFnsBySymbol.set(symbol, {
decl,
baseName: nameText,
qualifiedName: lowerer.qualify(
ts.getSourceFileOfNode(decl),
nsPathPrefix(lowerer, decl) + nameText,
),
qualifiedName: lowerer.qualify(ts.getSourceFileOfNode(decl), nsPathPrefix(decl) + nameText),
typeParams: [],
instances: new Map(),
implicitParams: implicit,
@@ -900,7 +897,7 @@ export function collectSignatureInner(lowerer: Lowerer, decl: ts.FunctionDeclara
// "%default" spelling ('%' cannot appear in a user identifier).
name: lowerer.qualify(
ts.getSourceFileOfNode(decl),
nsPathPrefix(lowerer, decl) + (decl.name?.text ?? "%default"),
nsPathPrefix(decl) + (decl.name?.text ?? "%default"),
),
params,
returnType,
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import { nodeThrowExpr, varRef } from "../../ir/build.js";
import {
BOOL,
@@ -123,7 +122,7 @@ export function checkedClassAssertion(
const key = `classassert:${typeKey(source.type)}:${typeKey(target)}`;
let name = lowerer.coercions.retags.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%class.assert", key);
name = `%class.assert.${lowerer.coercions.retags.size}`;
lowerer.coercions.retags.set(key, name);
const input = varRef("value.0", source.type, loc);
const body: IrStmt[] = [];
@@ -229,7 +228,7 @@ export function checkedClassUnionAssertion(
const key = `classunionassert:${typeKey(value.type)}:${typeKey(target)}`;
let name = lowerer.coercions.retags.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%class.assert", key);
name = `%class.assert.${lowerer.coercions.retags.size}`;
lowerer.coercions.retags.set(key, name);
const input = varRef("value.0", value.type, loc);
const locals = [{ id: "value.0", name: "value", type: value.type, mutable: false }];
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import * as ts from "../ts7/adapter.js";
import { BOOL, typeKey, type IrExpr, type IrStmt, type IrType } from "../../ir/ir.js";
import { nodeThrowExpr, varRef } from "../../ir/build.js";
@@ -133,7 +132,7 @@ export function narrowClassUnion(
const key = `classUnion:${source.className}:${target.unionId}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%class.union", key);
helper = `%class.union.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const input = varRef("value.0", source, loc);
// Rank by ancestry before sorting: returning zero for unrelated pairs
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { dynUndefinedExpr } from "../../../ir/build.js";
import { buildFunctionAdapter } from "./builders.js";
import { InternalCompilerError } from "../../../errors.js";
@@ -34,7 +33,7 @@ export function funcReturnWidthAdapter(
const key = `fn:${typeKey(fromT.ret.elem)}:${typeKey(toT.ret.elem)}`;
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%fn.width", key);
const name = `%fn.width.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
lowerer.freshClosureAdapters.add(name);
// The returned closure's body: call the captured original, width-map.
@@ -164,7 +163,7 @@ function restPackingAdapter(
const key = `fnrestadapt:${typeKey(fromT)}:${typeKey(toT)}`;
const existing = lowerer.coercions.retags.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%fn.adapt", key);
const name = `%fn.adapt.${lowerer.coercions.retags.size}`;
lowerer.coercions.retags.set(key, name);
lowerer.freshClosureAdapters.add(name);
const fixed = fromT.params.length - 1;
@@ -221,7 +220,7 @@ function funcIntoDynRestAdapter(
const key = `fnadapt:${typeKey(fromT)}:${typeKey(toT)}`;
const existing = lowerer.coercions.retags.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%fn.adapt", key);
const name = `%fn.adapt.${lowerer.coercions.retags.size}`;
lowerer.coercions.retags.set(key, name);
lowerer.freshClosureAdapters.add(name);
const params: IrParam[] = [{ localId: "rest.0", name: "args", type: DYN }];
@@ -377,7 +376,7 @@ export function funcCoerceAdapter(
const key = `fnadapt:${typeKey(fromT)}:${typeKey(toT)}`;
const existing = lowerer.coercions.retags.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%fn.adapt", key);
const name = `%fn.adapt.${lowerer.coercions.retags.size}`;
lowerer.coercions.retags.set(key, name);
lowerer.freshClosureAdapters.add(name);
const params: IrParam[] = toT.params.map((t, i) => ({
@@ -605,7 +604,7 @@ export function spawnResFnAdapter(
const key = `fnspawn:${typeKey(fromT)}:${typeKey(toT)}`;
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%fnval.spawnres", key);
const name = `%fnval.spawnres.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
lowerer.freshClosureAdapters.add(name);
const params: IrParam[] = toT.params.map((p, i) => ({
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { constructedValue } from "../constructor-returns.js";
import { dynUndefinedExpr, varRef } from "../../../ir/build.js";
import type { FieldLift } from "./structural-plans.js";
@@ -254,7 +253,7 @@ export function unionRecordWidthHelper(
const key = `unionWidth:${fromId}:${target.shapeId}`;
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%union.record", key);
const name = `%union.record.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const type: IrType = { kind: "union", unionId: fromId };
const value = varRef("value.0", type, loc);
@@ -313,7 +312,7 @@ export function recordWidthHelper(
const key = `rec:${fromId}:${toId}`;
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%rec.width", key);
const name = `%rec.width.${lowerer.valueHelpers.size}`;
// Interned BEFORE the body builds: a recursive nested-width field
// (self-referential shapes) resolves to this helper itself.
lowerer.valueHelpers.set(key, name);
@@ -342,7 +341,7 @@ export function tupleArrayWidthHelper(
const key = `tuparr:${fromId}:${typeKey(toT.elem)}`;
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%tup.arr", key);
const name = `%tup.arr.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const fromT: IrType = { kind: "record", shapeId: fromId };
const t: IrExpr = { kind: "varRef", localId: "t.0", type: fromT, loc };
@@ -389,7 +388,7 @@ export function emptyArrayLiftHelper(
const key = `emptyarr:${typeKey(fromT.elem)}:${typeKey(toT.elem)}`;
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%arr.empty", key);
const name = `%arr.empty.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const a: IrExpr = { kind: "varRef", localId: "a.0", type: fromT, loc };
lowerer.liftedFns.push({
@@ -453,7 +452,7 @@ export function arrayWidthHelper(
const key = `arr:${typeKey(fromElem)}:${typeKey(toElem)}`;
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%arr.width", key);
const name = `%arr.width.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const arrT: IrType = { kind: "array", elem: fromElem };
const outT: IrType = { kind: "array", elem: toElem };
@@ -493,7 +492,7 @@ export function objRecordWidthHelper(
const key = `obj:${className}:${toId}`;
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%obj.width", key);
const name = `%obj.width.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const fromT: IrType = { kind: "object", className };
lowerer.liftedFns.push(buildRecordProjection(lowerer, name, fromT, to, plan, loc));
@@ -518,7 +517,7 @@ export function recordClassWidthHelper(
const key = `cls:${fromId}:${className}`;
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%cls.width", key);
const name = `%cls.width.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
lowerer.noteEdge(`%${className}.constructor`);
const fromT: IrType = { kind: "record", shapeId: fromId };
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { buildUnionNarrow, unionNarrowFailureMessage } from "../union-narrow.js";
import { isSafeToRepeat } from "../expressions/evaluation-safety.js";
import { nodeThrowExpr } from "../../../ir/build.js";
@@ -148,7 +147,7 @@ export function strandedUnitTrap(
const key = `strandunit:${typeKey(expected)}:${expr.type.kind}`;
let name = lowerer.coercions.retags.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%unit.strand", key);
name = `%unit.strand.${lowerer.coercions.retags.size}`;
lowerer.coercions.retags.set(key, name);
lowerer.liftedFns.push({
name,
@@ -228,7 +227,7 @@ export function strandedCoercionTrap(
const key = `strand:${expected.unionId}:${typeKey(src)}`;
let name = lowerer.coercions.retags.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%union.strand", key);
name = `%union.strand.${lowerer.coercions.retags.size}`;
lowerer.coercions.retags.set(key, name);
const toT: IrType = { kind: "union", unionId: expected.unionId };
lowerer.liftedFns.push({
@@ -276,7 +275,7 @@ export function recordUnionWrapHelper(
const key = `recordWrap:${source.shapeId}:${toId}`;
const existing = lowerer.coercions.retags.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%record.wrap", key);
const name = `%record.wrap.${lowerer.coercions.retags.size}`;
lowerer.coercions.retags.set(key, name);
lowerer.liftedFns.push(
buildRecordUnionWrap(name, source, to, plan, loc, (lift, value, dst) =>
@@ -308,7 +307,7 @@ export function recordUnionRouteHelper(
(from, dst) => lowerer.widthLiftPlan(from, dst),
);
if (!plan) return null;
const name = keyedHelperName(lowerer, "%record.route", key);
const name = `%record.route.${lowerer.coercions.retags.size}`;
lowerer.coercions.retags.set(key, name);
lowerer.liftedFns.push(
buildRecordUnionWrap(name, source, to, plan, loc, (lift, value, dst) =>
@@ -353,7 +352,7 @@ export function unionRetagHelper(
});
const key = `${fromId}:${toId}:${stranded.join(".")}`;
const existing = lowerer.coercions.retags.get(key);
const name = existing ?? keyedHelperName(lowerer, "%union.retag", key);
const name = existing ?? `%union.retag.${lowerer.coercions.retags.size}`;
if (!existing) {
lowerer.coercions.retags.set(key, name);
lowerer.liftedFns.push(
@@ -411,7 +410,7 @@ export function narrowedArmHelper(
lowerer.coercions.checkedNarrows.add(existing);
return existing;
}
const name = keyedHelperName(lowerer, "%union.narrow", key);
const name = `%union.narrow.${lowerer.coercions.narrows.size}`;
lowerer.coercions.narrows.set(key, name);
lowerer.coercions.checkedNarrows.add(name);
const fn = buildUnionNarrow(name, from, target, loc, (type) => lowerer.fmt(type));
@@ -588,7 +587,7 @@ export function deferredReadHelper(
const key = `deferred:${fromId}:${tag}`;
const existing = lowerer.coercions.narrows.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%deferred.read", key);
const name = `%deferred.read.${lowerer.coercions.narrows.size}`;
lowerer.coercions.narrows.set(key, name);
const dflt: IrExpr =
target.kind === "bool"
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import * as ts from "../ts7/adapter.js";
import { InternalCompilerError } from "../../errors.js";
import { typeKey } from "../type-mapper.js";
@@ -198,7 +197,7 @@ export function ingestCollection(
const key = `ingest:${input.collection ? typeKey(input.collection) + ":" + input.selection : "cursor:" + typeKey(input.element)}:${typeKey(destination)}:${mapper ? typeKey(mapper.type) : "copy"}`;
let name = lowerer.arrHofHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%collection.ingest", key);
name = `%collection.ingest.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
const context = newFnCtx(false, null, null, destination);
lowerer.fnStack.push(context);
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { boolLit, countedFor, numLit, varRef } from "../../../ir/build.js";
import { transformStmtList } from "../../../ir/traverse.js";
import type { ScrDiagnostic } from "../../../diagnostics/diagnostic.js";
@@ -507,7 +506,7 @@ function arrayHofHelper(
const key = `${method}:${typeKey(elem)}:${typeKey(fnRet)}:${arity}:${typeKey(outElem)}${site}${fastT ? ":split" : ""}${present ? ":present" : ""}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, `%arr.${method}`, key);
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
lowerer.arrHofInfo.set(name, { method, elem, fnRet, arity, outElem, present, split: !!fastT });
const fn = buildArrayHofFn(
@@ -620,7 +619,7 @@ function arrayUnionHofHelper(
const key = `union:${method}:${receiverT.unionId}:${typeKey(callbackT)}:${typeKey(resultT)}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%arr.union", key);
const name = `%arr.union.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
const receiver = varRef("a.0", receiverT, loc);
const callback = varRef("f.0", callbackT, loc);
@@ -1205,7 +1204,7 @@ function findHelper(
const key = `${method}:${typeKey(elem)}:${typeKey(resultT)}:${typeKey(fnRet)}:${arity}${site}${present ? ":present" : ""}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, `%arr.${method}`, key);
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
const arrT = arrayOf(elem);
const valueT = callbackValueType(lowerer, elem, present);
@@ -1305,7 +1304,7 @@ function findIndexHelper(
const key = `${method}:${typeKey(elem)}:${typeKey(fnRet)}:${arity}${site}${fastT ? ":split" : ""}${present ? ":present" : ""}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, `%arr.${method}`, key);
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
if (!last)
lowerer.arrHofInfo.set(name, {
@@ -1389,7 +1388,7 @@ function someEveryHelper(
const key = `${method}:${typeKey(elem)}:${typeKey(fnRet)}:${arity}${site}${fastT ? ":split" : ""}${present ? ":present" : ""}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, `%arr.${method}`, key);
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
lowerer.arrHofInfo.set(name, {
method,
@@ -1588,7 +1587,7 @@ function flatMapHelper(
const key = `flatMap:${typeKey(elem)}:${typeKey(fnRet)}:${arity}${site}${fastT ? ":split" : ""}${present ? ":present" : ""}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%arr.flatMap", key);
const name = `%arr.flatMap.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
lowerer.arrHofInfo.set(name, {
method: "flatMapArray",
@@ -1830,7 +1829,7 @@ function reduceHelper(
const key = `${method}:${typeKey(elem)}:${typeKey(accT)}:${typeKey(fnRet)}:${arity}:${hasInit ? "init" : "seed"}${site}${fastT ? ":split" : ""}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, `%arr.${method}`, key);
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
if (method === "reduce")
lowerer.arrHofInfo.set(name, {
@@ -2122,7 +2121,7 @@ export function lowerArraySortCall(
const key = `${method}:${typeKey(elem)}:${arity}${native ? ":native" : ""}${keyed ? `:keyed${order}${reads ? `:field${reads.length}` : ""}` : ""}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, `%arr.${method}`, key);
helper = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
const undefinedTag = elem.kind === "union" ? lowerer.armTag(elem.unionId, UNDEFINED_T) : -1;
lowerer.liftedFns.push(
@@ -2168,7 +2167,7 @@ function sortKeyReader(
const cacheKey = `sortKey:${typeKey(elem)}:${sortKeysId(reads.map((read) => ({ order: 1, read })))}`;
let reader = lowerer.arrHofHelpers.get(cacheKey);
if (!reader) {
reader = keyedHelperName(lowerer, "%arr.sortKey", cacheKey);
reader = `%arr.sortKey.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(cacheKey, reader);
lowerer.liftedFns.push(buildSortKeyFn(reader, elem, reads, loc));
}
@@ -2309,7 +2308,7 @@ function dynFlatMapHelper(lowerer: Lowerer, elemT: IrType, arity: number, loc: S
const key = `dynflatmap:${typeKey(elemT)}:${arity}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%dyn.flatmap", key);
const name = `%dyn.flatmap.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
lowerer.liftedFns.push(buildDynFlatMapFn(name, elemT, arity, loc));
return name;
@@ -2482,7 +2481,7 @@ function dynFilterHelper(lowerer: Lowerer, elemT: IrType, arity: number, loc: Sr
const key = `dynfilter:${typeKey(elemT)}:${arity}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%dyn.filter", key);
const name = `%dyn.filter.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
lowerer.liftedFns.push(buildDynFilterFn(name, elemT, arity, loc));
return name;
@@ -2677,7 +2676,7 @@ export function lowerTuplePredicateCall(
const key = `tuple.${method}:${typeKey(receiver.type)}:${typeKey(fnArg.type)}:${typeKey(resultT)}:${arity}${callbackSite(fnArg)}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, `%tuple.${method}`, key);
helper = `%tuple.${method}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
const v = varRef("v.0", elem, loc);
const out = varRef("out.0", resultT, loc);
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { identityPreservingWidening } from "../coercions/identity.js";
import { countedFor, numLit, varRef } from "../../../ir/build.js";
import {
@@ -29,7 +28,7 @@ export function arrayConcatHelper(
const key = `concat:${typeKey(elem)}:${shape.map((type) => (type ? `a:${typeKey(type)}` : "e")).join(";")}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%arr.concat", key);
const name = `%arr.concat.${lowerer.arrHofHelpers.size}`;
const arrT = arrayOf(elem);
const params = [
{ localId: "a.0", name: "a", type: arrT },
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { generatorDrain } from "../iterator-adapters.js";
import { identityPreservingWidening } from "../coercions/identity.js";
import { liftedFreshSpread } from "../coercions/fresh-spread.js";
@@ -257,7 +256,7 @@ function appendWidenedArrayValues(
const key = `insertValues:${typeKey(sourceType.elem)}:${typeKey(elem)}`;
let name = lowerer.arrHofHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%arr.insertValues", key);
name = `%arr.insertValues.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
const outType = arrayOf(elem);
const valueType = arrayValueType(lowerer, source.type.elem);
@@ -1036,7 +1035,7 @@ export function lowerArrayFromCall(
const key = `fromArray:${typeKey(arrT.elem)}:${typeKey(outElem)}:${typeKey(fnRet)}:${arity}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%arr.fromArray", key);
helper = `%arr.fromArray.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
lowerer.liftedFns.push(
buildArrayFromArrayFn(lowerer, helper, arrT.elem, outElem, fnRet, arity, loc),
@@ -1123,7 +1122,7 @@ export function lowerArrayFromCall(
const key = `fromLen:${typeKey(fnRet)}:${arity}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%arr.fromLen", key);
helper = `%arr.fromLen.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
lowerer.liftedFns.push(buildArrayFromLenFn(helper, fnRet, arity, loc));
}
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { liftedFreshSpread } from "../coercions/fresh-spread.js";
import { lowerArrayIteratorValue } from "../iterator-adapters.js";
import { identityPreservingWidening } from "../coercions/identity.js";
@@ -1617,7 +1616,7 @@ function arraySearchHelper(
const key = `${method}:${typeKey(elem)}:${typeKey(valueT)}:${typeKey(needleT)}:${directNeedle}:${typeKey(fromT)}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, `%arr.${method}`, key);
const name = `%arr.${method}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
const arrT = arrayOf(elem);
const resultT = method === "includes" ? BOOL : F64;
@@ -1973,7 +1972,7 @@ export function lowerTupleReadMethodCall(
let helper = lowerer.arrHofHelpers.get(key);
const resultType = method === "includes" ? BOOL : F64;
if (!helper) {
helper = keyedHelperName(lowerer, `%tuple.${method}`, key);
helper = `%tuple.${method}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
const params = [
{ localId: "a.0", name: "a", type: receiver.type },
@@ -2035,7 +2034,7 @@ export function lowerTupleReadMethodCall(
const key = `tuple.slice:${typeKey(receiver.type)}:${args.length}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%tuple.slice", key);
helper = `%tuple.slice.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
const params = [receiver, ...args].map((arg, i) => ({
localId: i === 0 ? "a.0" : `arg.${i}`,
@@ -2126,7 +2125,7 @@ export function lowerTupleReadMethodCall(
const key = `tuple.${method}:${typeKey(receiver.type)}:${typeKey(fnArg.type)}:${typeKey(outElem)}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, `%tuple.${method}`, key);
helper = `%tuple.${method}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
const out = varRef("out.0", outType, loc);
const result = varRef("r.0", ret, loc);
@@ -2254,7 +2253,7 @@ function lowerArrayAtCall(
const key = `at:${typeKey(elem)}:${typeKey(resultT)}:${typeKey(index.type)}`;
let name = lowerer.arrHofHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%arr.at", key);
name = `%arr.at.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
const arrT = arrayOf(elem);
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { boolLit, countedFor, numLit, varRef } from "../../../ir/build.js";
import {
BOOL,
@@ -852,7 +851,7 @@ class PipelineFusion {
if (!built) return null;
let name = this.helpers.get(built.key);
if (!name) {
name = keyedHelperName(this.lowerer, "%arr.fused", built.key);
name = `%arr.fused.${this.helpers.size}`;
this.helpers.set(built.key, name);
this.added.push({ ...built.fn, name });
}
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { boolLit, dynUndefinedExpr, numLit, varRef } from "../../../ir/build.js";
import { BUF_NUM_METHODS } from "./buffer-numeric-methods.js";
import * as ts from "../../ts7/adapter.js";
@@ -78,7 +77,7 @@ function lowerBytesSortCall(
const cacheKey = `bytes.${method}:${bytesT.elem}:${key}`;
let helper = lowerer.arrHofHelpers.get(cacheKey);
if (!helper) {
helper = keyedHelperName(lowerer, `%bytes.${method}`, cacheKey);
helper = `%bytes.${method}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(cacheKey, helper);
lowerer.liftedFns.push(
buildBytesSortFn(helper, bytesT, arity, hasComparator, copyFirst, order, loc),
@@ -271,7 +270,7 @@ function bytesCallbackHelper(
const key = `bytes.${method}:${bytesT.elem}:${typeKey(resultT)}:${typeKey(fnRet)}:${arity}:${hasInit ? "init" : ""}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, `%bytes.${method}`, key);
const name = `%bytes.${method}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
const reduce = method === "reduce" || method === "reduceRight";
const backward = method === "reduceRight" || method === "findLast" || method === "findLastIndex";
@@ -1753,8 +1752,9 @@ function knownBufferProducer(
/** Buffer.from over a checked-dynamic source and encoding, both already in
* repeatable locals: Node's fromString takes utf8 for an absent, non-string
* or empty encoding and throws ERR_UNKNOWN_ENCODING for an unknown name;
* other sources ignore the encoding. */
* or empty encoding and throws ERR_UNKNOWN_ENCODING for an unknown name; an
* ArrayBuffer takes the second argument as its byte offset; other sources
* ignore it. */
function dynBufferFromParts(source: IrExpr, encoding: IrExpr, loc: SrcLoc): IrExpr {
const utf8: IrExpr = { kind: "strLit", value: "utf8", type: STRING, loc };
const isString = (x: IrExpr): IrExpr => ({
@@ -1796,10 +1796,12 @@ function dynBufferFromParts(source: IrExpr, encoding: IrExpr, loc: SrcLoc): IrEx
type: BYTES_U8,
loc,
},
// An ArrayBuffer takes the second argument as its byte offset
// (scr_buffer_from_dyn_offset_pooled); other values convert with utf8.
else_: {
kind: "libCall",
fn: "buffer.poolFromDynRef",
args: [source, utf8],
fn: "buffer.poolFromDynOffset",
args: [source, encoding],
type: BYTES_U8,
loc,
},
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import * as ts from "../../ts7/adapter.js";
import type { Lowerer } from "../lowerer.js";
import { isJsSourceFile, locOf } from "../../program.js";
@@ -286,7 +285,7 @@ export function truthyFilterCallback(
const key = `filterBoolean:${typeKey(valueT)}`;
let name = lowerer.arrHofHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%arr.filterBoolean", key);
name = `%arr.filterBoolean.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
const value = varRef("v.0", valueT, loc);
lowerer.liftedFns.push({
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { countedFor, numLit, varRef } from "../../../ir/build.js";
import * as ts from "../../ts7/adapter.js";
import type { Lowerer } from "../lowerer.js";
@@ -105,7 +104,7 @@ export function lowerGroupByStaticCall(
const key = `map.groupBy:${typeKey(items.type)}:${typeKey(f.type)}`;
let helper = lowerer.mapHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%map.groupBy", key);
helper = `%map.groupBy.${lowerer.mapHofHelpers.size}`;
lowerer.mapHofHelpers.set(key, helper);
lowerer.liftedFns.push(buildGroupByFn(lowerer, helper, items.type, f.type, resultT, loc));
}
@@ -142,7 +141,7 @@ export function lowerGroupByStaticCall(
const key = `object.groupBy:${typeKey(items.type)}:${typeKey(f.type)}`;
let helper = lowerer.mapHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%object.groupBy", key);
helper = `%object.groupBy.${lowerer.mapHofHelpers.size}`;
lowerer.mapHofHelpers.set(key, helper);
lowerer.liftedFns.push(buildGroupByFn(lowerer, helper, items.type, f.type, resultT, loc));
}
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { nodeThrowExpr, boolLit, countedFor, numLit, strLit, varRef } from "../../../ir/build.js";
import { InternalCompilerError } from "../../../errors.js";
import * as ts from "../../ts7/adapter.js";
@@ -233,7 +232,7 @@ function lowerMapIterDrainCall(
const key = `${method}:${typeKey(mapT.key)}:${typeKey(mapT.value)}`;
let helper = lowerer.mapHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, `%map.${method}`, key);
helper = `%map.${method}.${lowerer.mapHofHelpers.size}`;
lowerer.mapHofHelpers.set(key, helper);
lowerer.liftedFns.push(buildMapIterDrainFn(helper, mapT, method, elemT, tupleT, loc));
}
@@ -349,7 +348,7 @@ export function mapEntriesArray(lowerer: Lowerer, receiver: IrExpr): IrExpr {
const key = `entries:${typeKey(mapT.key)}:${typeKey(mapT.value)}`;
let helper = lowerer.mapHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%map.entries", key);
helper = `%map.entries.${lowerer.mapHofHelpers.size}`;
lowerer.mapHofHelpers.set(key, helper);
lowerer.liftedFns.push(
buildMapIterDrainFn(helper, mapT, "entries", tupleT, tupleT, receiver.loc),
@@ -520,7 +519,7 @@ export function lowerMapForEachCall(
const key = `${typeKey(mapT.key)}:${typeKey(mapT.value)}:${arity}:${typeKey(fnRet)}`;
let helper = lowerer.mapHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%map.forEach", key);
helper = `%map.forEach.${lowerer.mapHofHelpers.size}`;
lowerer.mapHofHelpers.set(key, helper);
lowerer.liftedFns.push(buildCollectionForEachFn(helper, mapT, arity, fnRet, loc));
}
@@ -694,7 +693,7 @@ export function lowerSetForEachCall(
const key = `${typeKey(setT.elem)}:${arity}:${typeKey(fnRet)}`;
let helper = lowerer.setHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%set.forEach", key);
helper = `%set.forEach.${lowerer.setHofHelpers.size}`;
lowerer.setHofHelpers.set(key, helper);
lowerer.liftedFns.push(buildCollectionForEachFn(helper, setT, arity, fnRet, loc));
}
@@ -746,7 +745,7 @@ function lowerSetCombineCall(
const key = `${name}:${typeKey(setT.elem)}:${typeKey(otherT)}`;
let helper = lowerer.setHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, `%set.${name}`, key);
helper = `%set.${name}.${lowerer.setHofHelpers.size}`;
lowerer.setHofHelpers.set(key, helper);
lowerer.liftedFns.push(buildSetCombineFn(helper, name, setT, otherT, loc));
}
@@ -1358,7 +1357,7 @@ export function mapFromSeedValue(
const key = `checked-seed:${typeKey(mapT)}`;
let name = lowerer.mapHofHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%map.checkedSeed", key);
name = `%map.checkedSeed.${lowerer.mapHofHelpers.size}`;
lowerer.mapHofHelpers.set(key, name);
const source = varRef("source.0", DYN, loc);
const entries = varRef("entries.0", DYN, loc);
@@ -1458,7 +1457,7 @@ export function mapFromSeedValue(
const key = `seed:${typeKey(mapT.key)}:${typeKey(mapT.value)}:${elem.shapeId}`;
let helper = lowerer.mapHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%map.seed", key);
helper = `%map.seed.${lowerer.mapHofHelpers.size}`;
lowerer.mapHofHelpers.set(key, helper);
lowerer.liftedFns.push(buildMapSeedFn(mapT, helper, seed.type, elem, kT, vT, loc));
}
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { nullProtoRecordShape, recordNullProtoTest } from "../null-prototype.js";
import { countedFor, numLit, varRef } from "../../../ir/build.js";
import { objectEnumerationReceiver } from "../object-enumeration-receiver.js";
@@ -185,7 +184,7 @@ export function objectIterOverIndexShape(
const key = `obj.${member}:ovf:${argIr.shapeId}:${typeKey(resultT)}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, `%obj.${member}`, key);
helper = `%obj.${member}.${lowerer.arrHofHelpers.size}`;
const outRef = varRef("out.0", resultT, loc);
const rRef = varRef("r.0", argIr, loc);
@@ -609,7 +608,7 @@ function lowerTypedObjectFromEntriesCall(
const key = `obj.fromEntries:${argIr.elem.shapeId}:${resultT.shapeId}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%obj.fromEntries", key);
helper = `%obj.fromEntries.${lowerer.arrHofHelpers.size}`;
const tupleT = argIr.elem as IrType & { kind: "record" };
const tRef = varRef("t.0", tupleT, loc);
@@ -713,7 +712,7 @@ function lowerFromEntriesStringRows(
const key = `obj.fromEntries:strrows:${resultT.shapeId}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%obj.fromEntries", key);
helper = `%obj.fromEntries.${lowerer.arrHofHelpers.size}`;
const tRef = varRef("t.0", rowT, loc);
const rowLen: IrExpr = {
@@ -932,7 +931,7 @@ export function lowerRecordOvfCaptureHelper(
const key = `ovf:${fromId}:${toId}`;
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%rec.capture", key);
const name = `%rec.capture.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const fromT: IrType = { kind: "record", shapeId: fromId };
const toT: IrType = { kind: "record", shapeId: toId };
@@ -1205,7 +1204,7 @@ export function lowerObjectAssignIndexShape(
const key = `assign:${targetIr.shapeId}:${plan.fromId}`;
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%obj.assign", key);
const name = `%obj.assign.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const toT: IrType = { kind: "record", shapeId: targetIr.shapeId };
const fromT: IrType = { kind: "record", shapeId: plan.fromId };
@@ -1475,7 +1474,7 @@ export function lowerIndexMergeHelper(
.join(",");
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%rec.merge", key);
const name = `%rec.merge.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const toT: IrType = { kind: "record", shapeId: toId };
const ksT = arrayOf(STRING);
@@ -1806,7 +1805,7 @@ export function lowerEnvToPairsHelper(
const key = `env.pairs:${shapeId}`;
const existing = lowerer.valueHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%env.pairs", key);
const name = `%env.pairs.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const recT: IrType = { kind: "record", shapeId };
const arrT = arrayOf(STRING);
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { boolLit, numLit, varRef } from "../../../ir/build.js";
import { InternalCompilerError } from "../../../errors.js";
import * as ts from "../../ts7/adapter.js";
@@ -967,7 +966,7 @@ function lowerFusedChain(
const key = `iter:${srcKey}:${stageKeys.join(",")}:${terminal}:${termArgs.map((a) => typeKey(a.type)).join(",")}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, `%iter.${terminal}`, key);
helper = `%iter.${terminal}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
lowerer.liftedFns.push(
buildIterChainFn(
@@ -1039,7 +1038,7 @@ function lowerHelperObject(
const key = `iterHelper:${stage.kind}:${srcKey}:${typeKey(srcElem)}:${typeKey(arg.type)}:${typeKey(outT)}`;
let name = lowerer.arrHofHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, `%iter.${stage.kind}`, key);
name = `%iter.${stage.kind}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
lowerer.liftedFns.push(
buildHelperGenFn(lowerer, name, source, srcElem, stage, arg.type, outT, resultType, loc),
@@ -1059,7 +1058,7 @@ function internBudgetChecker(lowerer: Lowerer, loc: SrcLoc, srcT: GenType | null
const key = srcT === null ? "iter:budget" : `iter:budget:close:${typeKey(srcT)}`;
let name = lowerer.arrHofHelpers.get(key);
if (name !== undefined) return name;
name = srcT === null ? "%iter.budget" : keyedHelperName(lowerer, "%iter.budget.close", key);
name = srcT === null ? "%iter.budget" : `%iter.budget.close.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
const v = (): IrExpr => ({ kind: "varRef", localId: "v.0", type: F64, loc });
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { matchAllRowsReadByElements } from "./match-row-uses.js";
import {
boolLit,
@@ -160,7 +159,7 @@ export function lowerStringSplitCall(
const key = `str.split:${receiver.type.kind === "union" ? typeKey(receiver.type) : receiver.type.kind}:${typeKey(separator.type)}:${typeKey(limit.type)}:${absent}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%str.split", key);
helper = `%str.split.${lowerer.valueHelpers.size}`;
const values = [receiver, separator, limit];
const params = values.map((value, index) => ({
localId: `arg.${index}`,
@@ -396,7 +395,7 @@ function stringIndexHelperCall(
const key = `str.index:${method}:${typeKey(receiver.type)}:${typeKey(index.type)}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%str.index", key);
helper = `%str.index.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const rawReceiver = varRef("arg.0", receiver.type, loc);
const rawIndex = varRef("arg.1", index.type, loc);
@@ -674,7 +673,7 @@ export function lowerStringPaddingCall(
const key = `str.pad:${method}:${values.map((value) => typeKey(value.type)).join(":")}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%str.pad", key);
helper = `%str.pad.${lowerer.valueHelpers.size}`;
const params = values.map((value, index) => ({
localId: `arg.${index}`,
name: `arg${index}`,
@@ -920,12 +919,12 @@ export function lowerRegexMethodCall(
})();
if ((receiverKind === "string" || nullableStringRecv) && name === "match") {
const arg0 = call.arguments.nodes[0];
if (!arg0 || lowerer.mapTypeOf(lowerer.typeOf(arg0))?.kind !== "regex") return null; // string-pattern match: the SC2020 fence
if (!arg0 || !isRegexPatternArgument(lowerer, arg0)) return null; // string-pattern match: the SC2020 fence
if (call.arguments.nodes.length !== 1) return null;
const receiver = nullableStringRecv
? lowerMethodReceiver(lowerer, access.expression, STRING, access.name.text)
: lowerReceiver();
const re = lowerer.lowerExprExpecting(arg0, { kind: "regex" });
const re = lowerRegexPatternArgument(lowerer, arg0);
// RegExpMatchArray | null maps to the string[] | null union by
// itself; intern it directly when the checker's spelling doesn't —
// or when it maps to something WIDER (an optional-chain call node
@@ -983,10 +982,10 @@ export function lowerRegexMethodCall(
// 0, exactly Node. Never throws.
if (receiverKind === "string" && name === "search") {
const arg0 = call.arguments.nodes[0];
if (!arg0 || lowerer.mapTypeOf(lowerer.typeOf(arg0))?.kind !== "regex") return null; // string-pattern form: the SC2020 fence
if (!arg0 || !isRegexPatternArgument(lowerer, arg0)) return null; // string-pattern form: the SC2020 fence
if (call.arguments.nodes.length !== 1) return null;
const receiver = lowerReceiver();
const re = lowerer.lowerExprExpecting(arg0, { kind: "regex" });
const re = lowerRegexPatternArgument(lowerer, arg0);
return { kind: "regexIntrinsic", method: "search", receiver, args: [re], type: F64, loc };
}
if (receiverKind === "string" && (name === "replace" || name === "replaceAll")) {
@@ -1310,7 +1309,7 @@ export function lowerStringMethodCall(
const key = `str.positions:${entry.method}:${typeKey(needle.type)}:${typeKey(position.type)}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%str.positions", key);
helper = `%str.positions.${lowerer.valueHelpers.size}`;
const params = [receiver, needle, position].map((arg, index) => ({
localId: `arg.${index}`,
name: `arg${index}`,
@@ -1412,7 +1411,7 @@ export function lowerStringMethodCall(
const key = `str.positions:${entry.method}:${args.map((arg) => typeKey(arg.type)).join(":")}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%str.positions", key);
helper = `%str.positions.${lowerer.valueHelpers.size}`;
const params = [receiver, ...args].map((arg, index) => ({
localId: `arg.${index}`,
name: `arg${index}`,
@@ -1921,3 +1920,31 @@ export function lowerDynPrimitiveMethodCall(
);
return seq(stmts, result);
}
/** Whether the pattern of `match` or `search` is a RegExp, or a RegExp that
* may be undefined (`s.match(patterns[i])` under noUncheckedIndexedAccess). */
function isRegexPatternArgument(lowerer: Lowerer, node: ts.Expression): boolean {
const t = lowerer.mapTypeOf(lowerer.typeOf(node));
if (t?.kind === "regex") return true;
if (t?.kind !== "union") return false;
const arms = lowerer.unions.get(t.unionId)?.arms ?? [];
return (
arms.length === 2 &&
arms.some((arm) => arm.kind === "regex") &&
arms.some((arm) => arm.kind === "undefinedT")
);
}
/** The pattern of `match` or `search` as a RegExp: an undefined pattern is
* `new RegExp(undefined)`, the empty pattern, as in Node. */
function lowerRegexPatternArgument(lowerer: Lowerer, node: ts.Expression): IrExpr {
const regex: IrType = { kind: "regex" };
const empty: IrExpr = {
kind: "regexLit",
pattern: "(?:)",
flags: "",
type: regex,
loc: locOf(node),
};
return lowerOptionalArgument(lowerer, node, regex, empty);
}
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import * as ts from "../../ts7/adapter.js";
import {
BOOL,
@@ -163,7 +162,7 @@ function lowerStringReplacementValues(
const key = `str.${method}:${typeKey(replacement.type)}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, `%str.${method}`, key);
helper = `%str.${method}.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const subject = varRef("s.0", STRING, loc);
const needle = varRef("search.0", STRING, loc);
@@ -1,5 +1,4 @@
/* Binary operators: arithmetic, comparisons and equality, logical and nullish pairs, and string conversion for concatenation. */
import { keyedHelperName } from "../stable-names.js";
import * as ts from "../../ts7/adapter.js";
import { cleanFuncAdaptable } from "../coercions/function-adapters.js";
import { type Lowerer, jsFuncNameOf } from "../lowerer.js";
@@ -802,7 +801,7 @@ function nullProtoToString(lowerer: Lowerer, e: IrExpr, t: IrType & { kind: "rec
const key = `nullProtoToString:${t.shapeId}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%rec.tostr", key);
helper = `%rec.tostr.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const r = varRef("r.0", t, loc);
lowerer.liftedFns.push({
@@ -549,6 +549,9 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc:
const callable = lowerer.lowerBuiltinCallableValue(bi, loc);
if (callable) return callable;
}
// node:crypto's webcrypto is the WebCrypto global's object.
if (bi.module === "crypto" && bi.member === "webcrypto")
return { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc };
// JavaScript sources: an otherwise unsupported builtin VALUE is
// the same identity-token story as stdlib globals above (the
// harness adds worker_threads.Worker to its identity Set).
@@ -721,7 +724,7 @@ export function lowerIdentifierExpr(lowerer: Lowerer, expr: ts.Identifier, loc:
return { kind: "closure", fnName: name, captures: [], type, loc };
}
if (lowerer.isStdlibGlobal(expr, "crypto"))
return { kind: "libCall", fn: "crypto.native", args: [], type: DYN, loc };
return { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc };
const nativeAlias = stdlibGlobalNameOf(lowerer, expr);
if (nativeAlias === "Buffer" && expr.text !== "Buffer") {
return { kind: "strLit", value: "[builtin Buffer]", type: STRING, loc };
@@ -1,4 +1,3 @@
import { keyedHelperName } from "../stable-names.js";
import { dateJsonType, isDateJsonSource } from "../builtins/dates.js";
import { dynUndefinedExpr, numLit, strLit, varRef } from "../../../ir/build.js";
import { pathModuleValue } from "../lower-builtin-values.js";
@@ -407,7 +406,7 @@ export function lowerDynObjectLiteral(
if (!(err instanceof PoisonError) || !pureMember) throw err;
const fence = lowerer.deferToRuntimeFence(propDiagsBefore, prop, {
kind: "closure",
name: () => lowerer.closureName(loc, "_dynfence"),
name: () => `%fn${lowerer.lambdaCounter++}_dynfence`,
returnType: VOID,
type: { kind: "func", params: [], ret: VOID },
});
@@ -462,7 +461,7 @@ export function lowerDynObjectLiteral(
if (!(err instanceof PoisonError)) throw err;
const fence = lowerer.deferToRuntimeFence(convDiagsBefore, prop, {
kind: "closure",
name: () => lowerer.closureName(loc, "_dynfence"),
name: () => `%fn${lowerer.lambdaCounter++}_dynfence`,
returnType: VOID,
type: { kind: "func", params: [], ret: VOID },
});
@@ -2957,7 +2956,7 @@ function lowerDeclaredSpreadMerge(
const key = `declmerge-source:${type.shapeId}:${source.shapeId}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%rec.declmerge", key);
helper = `%rec.declmerge.${lowerer.valueHelpers.size}`;
const sourceType = source.value.type;
const destination = varRef("out.0", type, loc);
const receiver = varRef("source.0", sourceType, loc);
@@ -240,7 +240,7 @@ export function lowerOptionalChain(
const receiver = lowerer.runtimeOptionalSourceValue(recvNode, loweredReceiver) ?? loweredReceiver;
if (receiver.type.kind === "dyn") {
if (dotNode === expr && ts.isCallExpression(expr)) {
const id = lowerer.closureName(loc, "", "chain");
const id = `chain.${lowerer.chainCounter++}`;
const callee: IrExpr = { kind: "chainRecv", id, type: DYN, loc };
const stmts: IrStmt[] = [];
let guarded = receiver;
@@ -298,7 +298,7 @@ export function lowerOptionalChain(
);
}
if (dotNode === expr && ts.isElementAccessExpression(expr)) {
const id = lowerer.closureName(loc, "", "chain");
const id = `chain.${lowerer.chainCounter++}`;
const reference: IrExpr = { kind: "chainRecv", id, type: DYN, loc };
lowerer.chainRecvByNode.set(recvNode, reference);
lowerer.chainHandled.add(dotNode);
@@ -331,7 +331,7 @@ export function lowerOptionalChain(
dotNode === expr.expression
) {
const m = dotNode.name.text;
const id = lowerer.closureName(loc, "", "chain");
const id = `chain.${lowerer.chainCounter++}`;
const recvRef: IrExpr = { kind: "chainRecv", id, type: DYN, loc: locOf(recvNode) };
lowerer.chainRecvByNode.set(recvNode, recvRef);
lowerer.chainHandled.add(dotNode);
@@ -358,7 +358,7 @@ export function lowerOptionalChain(
}
return { kind: "optChain", id, receiver, body, type: DYN, loc };
}
const id = lowerer.closureName(loc, "", "chain");
const id = `chain.${lowerer.chainCounter++}`;
const reference: IrExpr = { kind: "chainRecv", id, type: DYN, loc };
lowerer.chainRecvByNode.set(recvNode, reference);
lowerer.chainHandled.add(dotNode);
@@ -420,7 +420,7 @@ export function lowerOptionalChain(
rest.length === 1
? rest[0]!
: { kind: "union" as const, unionId: lowerer.unions.transform(def, rest) };
const id = lowerer.closureName(loc, "", "chain");
const id = `chain.${lowerer.chainCounter++}`;
// A single present arm binds its payload. Multiple present arms bind
// the original tagged value, then retag inside the guarded body. The
// nullish arms never enter that body, and each record keeps its identity.
@@ -1,5 +1,4 @@
/* Property reads (`a.b`): lowerExprInner's property-access branch, intrinsic and union properties, field reads and targets, and match-group reads. */
import { keyedHelperName } from "../stable-names.js";
import { staticallySelected } from "../static-typeof.js";
import * as ts from "../../ts7/adapter.js";
import { lowerHistogramProperty } from "../builtins/performance.js";
@@ -164,6 +163,7 @@ import { lowerUnionFieldRead } from "./union-fields.js";
import { errorPropertyRead } from "../error-methods.js";
import { isSafeToDiscard, isSafeToRepeat } from "./evaluation-safety.js";
import { isZlibStreamMember, lowerZlibStreamBytesWritten } from "../builtins/compression.js";
import { isWebCryptoReceiver } from "../builtins/crypto.js";
import { isFsStreamMember, lowerFsStreamProperty } from "../builtins/fs-streams.js";
import { keyedFieldRead } from "./keyed-field-targets.js";
import { promisePrototypeValue } from "../thenables.js";
@@ -844,8 +844,16 @@ export function lowerPropertyAccessExpr(
if (runtimeOptionalClassField(lowerer, handled, expr)) return handled;
return iteratorResultValueRead(lowerer, handled, expr) ?? lowerer.maybeNarrow(handled, expr);
}
// `globalThis.crypto` is the WebCrypto object the bare `crypto` answers.
if (
!expr.questionDotToken &&
stdlibGlobalNameOf(lowerer, expr.expression) === "globalThis" &&
stdlibGlobalNameOf(lowerer, expr) === "crypto"
) {
return { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc };
}
// `globalThis.<name>` that no lowering above claimed
// (globalThis.crypto, globalThis.SubtleCrypto, globalThis.localStorage
// (globalThis.SubtleCrypto, globalThis.localStorage
// — the harness's capability-conditional knownGlobals adds): the
// PROPERTY spelling of the identifier chokepoint's JS rule — a stdlib
// global taken as a VALUE in a JavaScript source is the same opaque
@@ -2785,7 +2793,7 @@ export function lowerGroupsProjection(
const key = `regexgroups:${shapeId}:${groups.map((g) => `${g.name}=${g.index}`).join(",")}:${recvKey}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%regex.groups", key);
helper = `%regex.groups.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const body: IrStmt[] = [];
const locals: IrLocal[] = [{ id: "m.0", name: "m", type: recv.type, mutable: true }];
@@ -2961,6 +2969,9 @@ export function lowerFieldRead(lowerer: Lowerer, expr: ts.PropertyAccessExpressi
if (
receiverIr?.kind !== "object" &&
propSym &&
// The WebCrypto object's generic getRandomValues reads off its
// dynamic object.
!isWebCryptoReceiver(lowerer, expr.expression) &&
isGenericCallableMemberType(lowerer.checker.getTypeOfSymbol(propSym), lowerer.checker) &&
// CLASS members keep the class-path fences (a poisoned class's own
// diagnostics, the bound-method fence above).
@@ -1,5 +1,4 @@
/* instanceof and in, including private-name and runtime-key in. */
import { keyedHelperName } from "../stable-names.js";
import * as ts from "../../ts7/adapter.js";
import { builtinClassInstanceOfAny, builtinClassKind } from "../builtins/builtin-class-dyn.js";
import {
@@ -648,7 +647,7 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc
const key = `instanceof.union:${unionId}:${target.def.name}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%instanceof.union", key);
helper = `%instanceof.union.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const value: IrExpr = { kind: "varRef", localId: "value.0", type: left.type, loc };
const body: IrStmt[] = [];
@@ -1848,7 +1847,7 @@ function lowerRuntimeKeyIn(
const hkey = `haskey:${recvT.shapeId}`;
let helper = lowerer.valueHelpers.get(hkey);
if (!helper) {
helper = keyedHelperName(lowerer, "%rec.haskey", hkey);
helper = `%rec.haskey.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(hkey, helper);
const recT: IrType = { kind: "record", shapeId: recvT.shapeId };
@@ -1,6 +1,4 @@
import { staticNameBindings } from "./static-function-names.js";
import { positionTag } from "./structural-positions.js";
import { keyedHelperName } from "./stable-names.js";
import * as ts from "../ts7/adapter.js";
import { bodyReadsArguments } from "../arguments-usage.js";
import type { Lowerer } from "./lowerer.js";
@@ -131,6 +129,10 @@ export interface GenericInstance {
tsBindings?: Map<ts.Symbol, ts.Type>;
/** Rendered type arguments ("<number, string>") for diagnostics. */
typeArgsText: string;
/** A factory returning one of its local classes through a declared
* constructor type: that type (by id) and the class value it maps to
* while the body lowers (Lowerer.instanceResultClass). */
resultClass?: { declared: number; value: IrType };
/** Implicit instances only: param symbol → the call site's (widened)
* checker type, consulted by the Lowerer's typeOf while this instance's
* body lowers (the implicit twin of `bindings`). */
@@ -231,10 +233,7 @@ export function collectGenericSignature(lowerer: Lowerer, decl: ts.FunctionDecla
lowerer.genericFnsBySymbol.set(symbol, {
decl,
baseName: nameText,
qualifiedName: lowerer.qualify(
ts.getSourceFileOfNode(decl),
nsPathPrefix(lowerer, decl) + nameText,
),
qualifiedName: lowerer.qualify(ts.getSourceFileOfNode(decl), nsPathPrefix(decl) + nameText),
typeParams,
instances: new Map(),
});
@@ -473,7 +472,8 @@ function localClassResultInstance(
params: ParamShape[],
retTs: ts.Type,
): GenericInstance | null {
if (!localClassOfResult(lowerer, info, retTs)) return null;
const resultClass = localClassOfResult(lowerer, info, retTs);
if (!resultClass) return null;
const tsBindings = new Map<ts.Symbol, ts.Type>();
const bindings = lowerer.inferTypeParamBindings(expr, info, rsig, tsBindings);
const bindingKey = info.typeParams
@@ -488,12 +488,37 @@ function localClassResultInstance(
tsBindings,
});
if (inst.returnType !== pending) return inst;
const returnType = withInstanceScope(lowerer, info, inst, () => lowerer.mapTypeOf(retTs));
// A result typed by a declared constructor type rather than the class
// (`Struct<S>`) is the class value of the local class the body returns.
const declaredResult = info.decl.type
? lowerer.checker.getTypeFromTypeNode(info.decl.type)
: undefined;
const viaConstructorType = retTs.getSymbol()?.valueDeclaration !== resultClass;
const returnType = withInstanceScope(lowerer, info, inst, () => {
if (!viaConstructorType) return lowerer.mapTypeOf(retTs);
// The local class's own constructor type: a class expression's type is
// its constructor type, named or not (`return class { ... } as Struct<S>`).
const classSymbol =
!ts.isClassExpression(resultClass) && resultClass.name
? lowerer.checker.getSymbolAtLocation(resultClass.name)
: undefined;
const classType = ts.isClassExpression(resultClass)
? lowerer.checker.getTypeAtLocation(resultClass)
: classSymbol
? lowerer.checker.getTypeOfSymbol(classSymbol)
: undefined;
return classType ? lowerer.mapTypeOf(classType) : null;
});
if (!returnType) {
fenceGenericSignatureResult(lowerer, expr, retTs);
lowerer.badType(expr, retTs);
}
inst.returnType = returnType;
if (viaConstructorType && returnType.kind === "classval") {
if (declaredResult) inst.resultClass = { declared: declaredResult.id, value: returnType };
if (lowerer.typeParamBindings === null) lowerer.localClassValueTypes.set(retTs.id, returnType);
return inst;
}
// Values of the class outside the factory (`const b = new B()`, `b.read()`)
// carry the instantiated checker type; record which class it names. A
// call inside another generic instance can have a type that depends on
@@ -568,12 +593,102 @@ function localClassOfResult(
type: ts.Type,
): ts.ClassLikeDeclaration | null {
const symbol = type.getSymbol();
if (!symbol || !(symbol.flags & ts.SymbolFlags.Class)) return null;
const decl = symbol.valueDeclaration;
if (!decl || !(ts.isClassExpression(decl) || ts.isClassDeclaration(decl))) return null;
for (let parent = decl.parent; parent && !ts.isSourceFile(parent); parent = parent.parent)
if (parent === info.decl) return decl;
return null;
const decl = symbol && symbol.flags & ts.SymbolFlags.Class ? symbol.valueDeclaration : undefined;
if (decl && (ts.isClassExpression(decl) || ts.isClassDeclaration(decl)))
return declaredInside(decl, info.decl) ? decl : null;
// A result typed by a constructor type (`Struct<S>`, `new () => S`) whose
// every return gives back the one local class, through parentheses and
// assertions (`return Base as Struct<S>`).
// A mixin (a class extending one of the function's parameters) keeps the
// mixin instantiation, which keys on the argument class.
if (lowerer.checker.getConstructSignatures(type).length === 0) return null;
const returned = returnedLocalClass(lowerer, info);
return returned && !extendsParameter(lowerer, returned, info) ? returned : null;
}
function extendsParameter(
lowerer: Lowerer,
decl: ts.ClassLikeDeclaration,
info: GenericFnInfo,
): boolean {
const params = new Set<ts.Symbol>();
const fn = info.decl as { parameters?: { nodes: readonly ts.ParameterDeclaration[] } };
for (const param of fn.parameters?.nodes ?? []) {
const symbol = ts.isIdentifier(param.name)
? lowerer.checker.getSymbolAtLocation(param.name)
: undefined;
if (symbol !== undefined) params.add(symbol);
}
for (const clause of decl.heritageClauses?.nodes ?? [])
for (const base of clause.types.nodes) {
const symbol = ts.isIdentifier(base.expression)
? lowerer.checker.getSymbolAtLocation(base.expression)
: undefined;
if (symbol !== undefined && params.has(symbol)) return true;
}
return false;
}
function declaredInside(node: ts.Node, owner: ts.Node): boolean {
for (let parent = node.parent; parent && !ts.isSourceFile(parent); parent = parent.parent)
if (parent === owner) return true;
return false;
}
/** The local class every return of `info`'s body gives back, or null when a
* return gives anything else (or nothing). Nested functions and classes keep
* their own returns. */
function returnedLocalClass(lowerer: Lowerer, info: GenericFnInfo): ts.ClassLikeDeclaration | null {
const fn = info.decl as ts.Node & { body?: ts.Node };
const body = fn.body;
if (!body) return null;
const classOf = (node: ts.Expression): ts.ClassLikeDeclaration | null => {
let expr = node;
while (
ts.isParenthesizedExpression(expr) ||
ts.isAsExpression(expr) ||
ts.isSatisfiesExpression(expr) ||
ts.isNonNullExpression(expr)
)
expr = expr.expression;
if (ts.isClassExpression(expr)) return declaredInside(expr, info.decl) ? expr : null;
if (!ts.isIdentifier(expr)) return null;
const declaration = lowerer.checker.getSymbolAtLocation(expr)?.valueDeclaration;
if (!declaration || !declaredInside(declaration, info.decl)) return null;
if (ts.isClassDeclaration(declaration)) return declaration;
// A `const` bound to a class expression (`const C = class { ... }; return C`).
if (
ts.isVariableDeclaration(declaration) &&
ts.getCombinedNodeFlags(declaration) & ts.NodeFlags.Const &&
declaration.initializer
) {
let init: ts.Expression = declaration.initializer;
while (ts.isParenthesizedExpression(init)) init = init.expression;
if (ts.isClassExpression(init)) return init;
}
return null;
};
if (!ts.isBlock(body)) return classOf(body as ts.Expression);
let found: ts.ClassLikeDeclaration | null = null;
let valid = true;
const visit = (node: ts.Node): void => {
if (
!valid ||
ts.isFunctionLike(node) ||
ts.isClassDeclaration(node) ||
ts.isClassExpression(node)
)
return;
if (ts.isReturnStatement(node)) {
const returned = node.expression ? classOf(node.expression) : null;
if (!returned || (found !== null && found !== returned)) valid = false;
else found = returned;
return;
}
ts.forEachChild(node, visit);
};
ts.forEachChild(body, visit);
return valid ? found : null;
}
/** Runs `fn` under an instance's type bindings and local-class names, as
@@ -588,14 +703,17 @@ function withInstanceScope<T>(
const prevTsBindings = lowerer.typeParamTsBindings;
const previousPath = lowerer.genericInstantiationPath;
const prevContext = lowerer.instantiationContext;
const prevResultClass = lowerer.instanceResultClass;
lowerer.typeParamBindings = inst.bindings;
lowerer.typeParamTsBindings = inst.tsBindings ?? null;
lowerer.genericInstantiationPath = inst.path;
lowerer.instantiationContext = `instantiating '${info.baseName}' with ${inst.typeArgsText}`;
lowerer.instanceResultClass = inst.resultClass ?? null;
lowerer.localClassInstantiations.push({ owner: info.decl, name: inst.name });
try {
return fn();
} finally {
lowerer.instanceResultClass = prevResultClass;
lowerer.localClassInstantiations.pop();
lowerer.typeParamBindings = prevBindings;
lowerer.typeParamTsBindings = prevTsBindings;
@@ -656,7 +774,7 @@ function internGenericInstance(
const typeArgsText = `<${rendered.length > 80 ? rendered.slice(0, 77) + "..." : rendered}>`;
inst = {
path,
name: keyedHelperName(lowerer, `${info.qualifiedName}%`, key, ""),
name: `${info.qualifiedName}%${info.instances.size}`,
ordinal: info.instances.size,
params,
returnType,
@@ -2134,7 +2252,7 @@ function internImplicitInstance(
const declared = dynamicBroadPromise ? null : implicitDeclaredReturn(lowerer, info);
inst = {
path,
name: keyedHelperName(lowerer, `${info.qualifiedName}%`, key, ""),
name: `${info.qualifiedName}%${info.instances.size}`,
ordinal: info.instances.size,
params: shapes,
// The promise callers rely on before the body settles it: the
@@ -2322,7 +2440,7 @@ export function implicitLocalFnInfoOf(
baseName: name,
qualifiedName: lowerer.qualify(
ts.getSourceFileOfNode(decl),
nsPathPrefix(lowerer, stmt ?? decl, decl) + `${name}%l${positionTag(lowerer, decl)}`,
nsPathPrefix(stmt ?? decl, decl) + `${name}%l${ts.tokenStartOfNode(decl)}`,
),
typeParams: [],
instances: new Map(),
@@ -2418,7 +2536,7 @@ export function objLitGenericFnInfoOf(
baseName: name,
qualifiedName: lowerer.qualify(
ts.getSourceFileOfNode(fnNode),
`%ol${positionTag(lowerer, fnNode)}.${name}`,
`%ol${ts.tokenStartOfNode(fnNode)}.${name}`,
),
typeParams,
instances: new Map(),
@@ -2564,7 +2682,7 @@ export function bindingGenericFnInfoOf(
baseName: name,
qualifiedName: lowerer.qualify(
ts.getSourceFileOfNode(decl),
nsPathPrefix(lowerer, stmt ?? decl, decl) +
nsPathPrefix(stmt ?? decl, decl) +
(topLevel ? name : `${name}%l${ts.tokenStartOfNode(decl)}`),
),
typeParams,
@@ -23,7 +23,6 @@
* The entry handles that itable.find and itable.insert answer stay inside
* these lowerings: f64 locals that only reach toBool and itable.value.
* Any other use of the class or its members as values is refused. */
import { keyedHelperName } from "./stable-names.js";
import { dynUndefinedExpr } from "../../ir/build.js";
import { BOOL, F64, STRING, funcOf, typeKey } from "../../ir/ir.js";
import type { IrExpr, IrFunction, IrStmt, IrType, SrcLoc } from "../../ir/ir.js";
@@ -448,11 +447,7 @@ function helperName(lowerer: Lowerer, member: InternTableMember, signature: stri
}
function registerHelper(lowerer: Lowerer, member: InternTableMember, signature: string): string {
const name = keyedHelperName(
lowerer,
`%${INTERN_TABLE_CLASS}.${member}`,
`${member}|${signature}`,
);
const name = `%${INTERN_TABLE_CLASS}.${member}.${lowerer.internTableHelpers.size}`;
lowerer.internTableHelpers.set(`${member}|${signature}`, name);
return name;
}
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import { dynUndefinedExpr, varRef } from "../../ir/build.js";
import {
BOOL,
@@ -87,7 +86,7 @@ function dynIteratorAdapter(
const key = `iterAdapt:${typeKey(expected)}`;
let name = lowerer.valueHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%iter.adapt", key);
name = `%iter.adapt.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const locals: IrLocal[] = [];
const local = (id: string, type: IrType, mutable = false): IrExpr => {
@@ -314,7 +313,7 @@ export function lowerCollectionIteratorValue(
loc,
};
if (!typeEquals(yielded.type, type.yieldT)) return null;
name = keyedHelperName(lowerer, "%coll.iter", key);
name = `%coll.iter.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
lowerer.liftedFns.push({
name,
@@ -412,7 +411,7 @@ export function lowerBytesIteratorValue(
const key = `bytesIter:${method}:${typeKey(bytesT)}:${typeKey(type)}`;
let name = lowerer.valueHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%bytes.iter", key);
name = `%bytes.iter.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const list = varRef("list.0", bytesT, loc);
const index = varRef("index.0", F64, loc);
@@ -518,7 +517,7 @@ export function lowerStringIteratorValue(
const key = `strIter:${typeKey(type)}`;
let name = lowerer.valueHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%str.iter", key);
name = `%str.iter.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const text = varRef("text.0", STRING, loc);
const index = varRef("index.0", F64, loc);
@@ -678,7 +677,7 @@ function generatorRetypeAdapter(
type: resumeType(expected.nextT),
loc,
};
name = keyedHelperName(lowerer, "%gen.retype", key);
name = `%gen.retype.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
lowerer.liftedFns.push({
name,
@@ -795,7 +794,7 @@ export function generatorDrain(
const outRef = varRef("out.0", arrT, loc);
const value = lowerer.coerceToExpected(genValue(genRef, loc), elem);
if (!typeEquals(value.type, elem)) return null;
name = keyedHelperName(lowerer, "%gen.drain", key);
name = `%gen.drain.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
lowerer.liftedFns.push({
name,
@@ -857,7 +856,7 @@ export function generatorDrain(
if (!raw) return null;
const value = lowerer.coerceToExpected(raw, elem);
if (!typeEquals(value.type, elem)) return null;
name = keyedHelperName(lowerer, "%gen.drain", key);
name = `%gen.drain.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
lowerer.liftedFns.push({
name,
@@ -979,7 +978,7 @@ export function lowerArrayIteratorValue(
};
}
if (!yielded || !typeEquals(yielded.type, type.yieldT)) return null;
name = keyedHelperName(lowerer, "%arr.iter", key);
name = `%arr.iter.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
lowerer.liftedFns.push({
name,
@@ -21,7 +21,6 @@
* files only, and an empty answer leaves every mapping unchanged, so
* TypeScript programs and JavaScript that never uses these patterns
* compile exactly as before. */
import { keyedHelperName } from "./stable-names.js";
import * as ts from "../ts7/adapter.js";
import { isJsSourceFile, locOf } from "../program.js";
import { BOOL, DYN, type IrExpr, type IrStmt } from "../../ir/ir.js";
@@ -216,10 +215,10 @@ export function lowerValueReturningConstructorNew(
"spread arguments when constructing a class whose constructor returns a value",
);
}
const key = `ctorValue:${lowerer.positions.of(decl)}`;
const key = `ctorValue:${ts.getSourceFileOfNode(decl).fileName}:${ts.tokenStartOfNode(decl)}`;
let name = lowerer.valueHelpers.get(key);
if (name === undefined) {
name = keyedHelperName(lowerer, "%ctorValue", key);
name = `%ctorValue.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const savedClass = lowerer.currentClass;
lowerer.currentClass = null;
@@ -10,7 +10,6 @@
* JSON module is ordinary storage holding that object — destructuring
* copies its properties at the require, exactly like Node. ES-module JSON
* imports keep their own record (Node's ESM loader caches separately). */
import { keyedHelperName } from "./stable-names.js";
import * as ts from "../ts7/adapter.js";
import type { Lowerer } from "./lowerer.js";
import { PoisonError } from "./lowerer.js";
@@ -85,7 +84,7 @@ export function collectJsonRequires(lowerer: Lowerer, parts: FileParts[]): void
);
throw new PoisonError();
}
const id = keyedHelperName(lowerer, "%g.jsonRequire", jsonSf.fileName);
const id = `%g.jsonRequire.${lowerer.jsonRequireModules.size}`;
const global: IrGlobal = { id, name: "%json", type: mapped, mutable: true };
const loadedId = `${id}%loaded`;
lowerer.globalsList.push(global, {
@@ -14,7 +14,6 @@
* Any other use of the class or its methods as values is refused. The
* module is marked lifetimeRegions, which moves every ProgramLifetime
* class into counted regions. */
import { keyedHelperName } from "./stable-names.js";
import { F64, VOID, funcOf, typeKey } from "../../ir/ir.js";
import type { IrExpr, IrFunction, IrStmt, IrType, SrcLoc } from "../../ir/ir.js";
import { experimentalImportOf, lifetimeRegionMemberOf } from "../experimental-import.js";
@@ -153,7 +152,7 @@ function runHelper(lowerer: Lowerer, fnType: IrType & { kind: "func" }, loc: Src
const key = typeKey(fnType);
const known = lowerer.regionRunHelpers.get(key);
if (known !== undefined) return known;
const name = keyedHelperName(lowerer, `%${LIFETIME_REGION_CLASS}.run`, key);
const name = `%${LIFETIME_REGION_CLASS}.run.${lowerer.regionRunHelpers.size}`;
lowerer.regionRunHelpers.set(key, name);
const regionT = lifetimeRegionType(lowerer);
const ret = fnType.ret;
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import { InternalCompilerError } from "../../errors.js";
/* The node:assert lowering (a spoke module like lower-dgram.ts): the
* callable module form (`assert(x)` through a default import or a CJS
@@ -668,7 +667,7 @@ function partialEqHelper(lowerer: Lowerer, ta: IrType, tb: IrType, loc: SrcLoc):
const key = `assert.pdeq:${typeKey(ta)}|${typeKey(tb)}`;
const existing = lowerer.assertHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%assert.pdeq", key);
const name = `%assert.pdeq.${lowerer.assertHelpers.size}`;
lowerer.assertHelpers.set(key, name);
const a = (): IrExpr => varRef("a.0", ta, loc);
const b = (): IrExpr => varRef("b.0", tb, loc);
@@ -1281,7 +1280,7 @@ function deepCheckCall(
const key = `assert.dcheck:${variant}:${negated}:${typeKey(a.type)}:${typeKey(b.type)}`;
let name = lowerer.assertHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%assert.dcheck", key);
name = `%assert.dcheck.${lowerer.assertHelpers.size}`;
lowerer.assertHelpers.set(key, name);
const pa = varRef("a.0", a.type, loc);
const pb = varRef("b.0", b.type, loc);
@@ -1845,7 +1844,7 @@ function assertThrowsHelper(
const key = `assert.${mode}:${recvIsPromise ? "p" : "f"}:${typeKey(recvType)}:${sig}`;
const existing = lowerer.assertHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, `%assert.${mode}`, key);
const name = `%assert.${mode}.${lowerer.assertHelpers.size}`;
lowerer.assertHelpers.set(key, name);
const caughtRef = (): IrExpr => varRef("e.0", CAUGHT, loc);
@@ -2294,7 +2293,7 @@ function ifErrorHelper(lowerer: Lowerer, t: IrType & { kind: "union" }, loc: Src
const key = `assert.iferr:${typeKey(t)}`;
const existing = lowerer.assertHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%assert.iferr", key);
const name = `%assert.iferr.${lowerer.assertHelpers.size}`;
lowerer.assertHelpers.set(key, name);
const def = lowerer.unions.get(t.unionId);
if (!def) throw new InternalCompilerError(`assert.ifError over unknown union ${t.unionId}`);
@@ -2465,7 +2464,7 @@ function deepEqHelper(lowerer: Lowerer, t: IrType, loc: SrcLoc, loose = false):
const key = `assert.${prefix}:${typeKey(t)}`;
const existing = lowerer.assertHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, `%assert.${prefix}`, key);
const name = `%assert.${prefix}.${lowerer.assertHelpers.size}`;
lowerer.assertHelpers.set(key, name);
const a = (): IrExpr => varRef("a.0", t, loc);
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import { isForwardedTupleSpread, lowerForwardingApply } from "./typed-arguments.js";
import { guardNullProto, nullProtoRecordShape } from "./null-prototype.js";
import { lowerDynStreamMemberCall } from "./dyn-streams.js";
@@ -1543,9 +1542,10 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
lowerer.fenceStaticReadableStreamMember(expr.expression, "call") ??
lowerDisposableStackUseCall(lowerer, expr, expr.expression) ??
lowerer.lowerStdlibMethodCall(expr, expr.expression) ??
// crypto.getRandomValues on the WebCrypto global (untyped in
// fallback builds, so before the dyn receiver path) or webcrypto.
lowerWebCryptoGetRandomValues(lowerer, expr, expr.expression) ??
// crypto.getRandomValues / randomUUID on the WebCrypto global
// (untyped in fallback builds, so before the dyn receiver path) or
// webcrypto.
lowerWebCryptoMethodCall(lowerer, expr, expr.expression) ??
// Dyn receivers (JSON.parse-derived `unknown`/`any` values) —
lowerDynReceiverMethodCall(lowerer, expr, expr.expression) ??
@@ -2095,7 +2095,7 @@ function lowerOptionalNumberDefault(
const key = `number.optionalDefault:${widened.unionId}:${dflt}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%number.optionalDefault", key);
helper = `%number.optionalDefault.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const input = varRef("value.0", widened, loc);
lowerer.liftedFns.push({
@@ -2204,7 +2204,7 @@ function primitiveConversionHandler(
const key = `then.${name}:${typeKey(inner)}`;
let fnName = lowerer.arrHofHelpers.get(key);
if (fnName === undefined) {
fnName = keyedHelperName(lowerer, `%then.${name}`, key);
fnName = `%then.${name}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, fnName);
const value = varRef("v.0", inner, loc);
let body: IrExpr;
@@ -2373,7 +2373,7 @@ function lowerBigIntConstructorValue(lowerer: Lowerer, arg: IrExpr, loc: SrcLoc)
const key = `bigint.scalar:${unionId}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%bigint.scalar", key);
helper = `%bigint.scalar.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const value = varRef("value.0", arg.type, loc);
const body: IrStmt[] = [];
@@ -3235,7 +3235,7 @@ function lowerScalarUnionNumber(
const key = `number.scalar:${arg.type.unionId}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%number.scalar", key);
helper = `%number.scalar.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const value = varRef("value.0", arg.type, loc);
const nan: IrExpr = {
@@ -3280,7 +3280,7 @@ function lowerOptionalStringNumber(
const key = `${label}.optionalString:${arg.type.unionId}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, `%${label}.optionalString`, key);
helper = `%${label}.optionalString.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const value = varRef("value.0", arg.type, loc);
lowerer.liftedFns.push({
@@ -4707,7 +4707,7 @@ function lowerDefaultToStringCall(
const key = `objToStr:${typeKey(recv.type)}${missing ? `:${missing}` : ""}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%obj.tostr", key);
helper = `%obj.tostr.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const o = varRef("o.0", recv.type, loc);
lowerer.liftedFns.push({
@@ -5394,7 +5394,7 @@ function loweredTemplateStrings(
const site = lowerer.globalsList.length;
const globalId = `%g.templateStrings.${site}`;
const readyId = `%g.templateStringsReady.${site}`;
getter = keyedHelperName(lowerer, "%templateStrings.get", key);
getter = `%templateStrings.get.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, getter);
lowerer.globalsList.push(
{ id: globalId, name: "%templateStrings", type, mutable: true },
@@ -5575,7 +5575,7 @@ export function lowerPromiseMethodCall(
// IS the spec's identity/thrower/no-op substitution for all three
// members. An argument expression, when present, is undefined/null
// by construction — nothing to evaluate.
const fnName = lowerer.closureName(loc, `_${member}pass`);
const fnName = `%fn${lowerer.lambdaCounter++}_${member}pass`;
const funcType: IrType & { kind: "func" } = { kind: "func", params: [promT], ret: promT };
const fnCtx = newFnCtx(true, null, funcType, inner);
fnCtx.isAsync = true;
@@ -5672,7 +5672,7 @@ export function lowerPromiseMethodCall(
// Evaluate both callback expressions before the wrapper runs, in source order.
const fulfilled = fulfilledAbsent ? null : boxHandler(fulfilledNode, true);
const rejected = rejectedAbsent ? null : boxHandler(rejectedNode, false);
const fnName = lowerer.closureName(loc, "_then2");
const fnName = `%fn${lowerer.lambdaCounter++}_then2`;
const paramTypes: IrType[] = [
promT,
...(fulfilled ? [fulfilled.value.type] : []),
@@ -5921,7 +5921,7 @@ export function lowerPromiseMethodCall(
);
};
const resultT: IrType & { kind: "promise" } = { kind: "promise", inner: DYN };
const fnName = lowerer.closureName(loc, "_then");
const fnName = `%fn${lowerer.lambdaCounter++}_then`;
const funcType: IrType & { kind: "func" } = {
kind: "func",
params: [promT, DYN],
@@ -6016,7 +6016,7 @@ export function lowerPromiseMethodCall(
);
}
const R = resultT.inner;
const fnName = lowerer.closureName(loc, "_then");
const fnName = `%fn${lowerer.lambdaCounter++}_then`;
const funcType: IrType & { kind: "func" } = {
kind: "func",
params: [promT, cb.type],
@@ -6119,7 +6119,7 @@ export function lowerPromiseMethodCall(
"finally callbacks with parameters or a return value (use () => { ... })",
);
}
const fnName = lowerer.closureName(loc, "_finally");
const fnName = `%fn${lowerer.lambdaCounter++}_finally`;
const funcType: IrType & { kind: "func" } = {
kind: "func",
params: [promT, cb.type],
@@ -6222,7 +6222,7 @@ export function lowerPromiseMethodCall(
}
if (cb.type.kind === "dyn") {
const resultT: IrType & { kind: "promise" } = { kind: "promise", inner: DYN };
const fnName = lowerer.closureName(loc, "_catchdyn");
const fnName = `%fn${lowerer.lambdaCounter++}_catchdyn`;
const funcType: IrType & { kind: "func" } = {
kind: "func",
params: [promT, DYN],
@@ -6352,7 +6352,7 @@ export function lowerPromiseMethodCall(
);
}
const R = resultT.inner;
const fnName = lowerer.closureName(loc, "_catch");
const fnName = `%fn${lowerer.lambdaCounter++}_catch`;
const funcType: IrType & { kind: "func" } = { kind: "func", params: [promT], ret: resultT };
const fnCtx = newFnCtx(true, null, funcType, R);
fnCtx.isAsync = true;
@@ -6589,7 +6589,7 @@ function filterNarrowHelper(
const key = `filterNarrow:${test}:${typeKey(elem)}:${typeKey(outElem)}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%arr.filterNarrow", key);
const name = `%arr.filterNarrow.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
const arrT = arrayOf(elem);
@@ -6835,7 +6835,7 @@ export function recordKeysArrayCall(
const key = `obj.keys:${argIr.shapeId}:${typeKey(resultT)}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%obj.keys", key);
helper = `%obj.keys.${lowerer.arrHofHelpers.size}`;
const ref: IrExpr = { kind: "varRef", localId: "r.0", type: argIr, loc };
const outRef: IrExpr = { kind: "varRef", localId: "out.0", type: resultT, loc };
lowerer.arrHofHelpers.set(key, helper);
@@ -6905,7 +6905,7 @@ function recordHasOwnHelper(lowerer: Lowerer, shapeId: string, loc: SrcLoc): str
const key = `obj.hasOwn:${shapeId}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const helper = keyedHelperName(lowerer, "%obj.hasOwn", key);
const helper = `%obj.hasOwn.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
const shape = lowerer.shapes.get(shapeId)!;
const recT: IrType = { kind: "record", shapeId };
@@ -6990,7 +6990,7 @@ function classAssignHelper(
const key = `obj.assignClass:${className}:${srcShapeId}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const helper = keyedHelperName(lowerer, "%obj.assignClass", key);
const helper = `%obj.assignClass.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
const sShape = lowerer.shapes.get(srcShapeId)!;
const oT: IrType = { kind: "object", className };
@@ -7058,7 +7058,7 @@ function recordAssignHelper(
const key = `obj.assign:${targetShapeId}:${srcShapeId}`;
const existing = lowerer.arrHofHelpers.get(key);
if (existing) return existing;
const helper = keyedHelperName(lowerer, "%obj.assign", key);
const helper = `%obj.assign.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
const sShape = lowerer.shapes.get(srcShapeId)!;
const tShape = lowerer.shapes.get(targetShapeId)!;
@@ -8515,7 +8515,7 @@ function lowerObjectStaticCall(
const key = `obj.${member}:${argIr.shapeId}:${typeKey(resultT)}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, `%obj.${member}`, key);
helper = `%obj.${member}.${lowerer.arrHofHelpers.size}`;
const recT = argIr;
const ref: IrExpr = { kind: "varRef", localId: "r.0", type: recT, loc };
const outRef: IrExpr = { kind: "varRef", localId: "out.0", type: resultT, loc };
@@ -9151,7 +9151,7 @@ function lowerInlineArrowBind(
const length = firstDefault < 0 ? arrow.parameters.nodes.length : firstDefault;
const key = `arrow.bind:${typeKey(type)}:${length}`;
const existing = lowerer.arrHofHelpers.get(key);
const name = existing ?? keyedHelperName(lowerer, "%arrow.bind", key);
const name = existing ?? `%arrow.bind.${lowerer.arrHofHelpers.size}`;
if (!existing) {
lowerer.arrHofHelpers.set(key, name);
lowerer.freshClosureAdapters.add(name);
@@ -10080,7 +10080,7 @@ function lowerUnionObjectDynFieldCall(
const receiverRef = varRef(receiverLocal.id, receiverT, loc);
let helper = lowerer.unionCallHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%union.call", key);
helper = `%union.call.${lowerer.unionCallHelpers.size}`;
lowerer.unionCallHelpers.set(key, helper);
const receiverRef = varRef("this.0", receiverT, loc);
const branch = (plan: (typeof plans)[number]): IrStmt[] => {
@@ -10173,7 +10173,7 @@ function unionObjectMethodHelper(
const key = `${receiverT.unionId}:${method}:${shapes.map((shape) => `${shape.mode}:${typeKey(shape.type)}`).join(",")}:${typeKey(resultT)}`;
const existing = lowerer.unionCallHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%union.call", key);
const name = `%union.call.${lowerer.unionCallHelpers.size}`;
lowerer.unionCallHelpers.set(key, name);
// A probe that catches a refusal while the body lowers must not leave
// the name registered for a helper that was never emitted.
@@ -10367,7 +10367,7 @@ export function lowerStaticNumberGlobal(
const key = `parseInt.optional:${unionT.unionId}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%parseInt.optional", key);
helper = `%parseInt.optional.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const value: IrExpr = { kind: "varRef", localId: "value.0", type: unionT, loc };
const radixRef: IrExpr = { kind: "varRef", localId: "radix.0", type: F64, loc };
@@ -10493,20 +10493,40 @@ export function lowerStaticNumberGlobal(
return null;
}
/** `crypto.getRandomValues(view)` on the WebCrypto global or webcrypto. */
function lowerWebCryptoGetRandomValues(
/** `crypto.getRandomValues(view)` and `crypto.randomUUID()` on the
* WebCrypto global or webcrypto. Other argument counts (JavaScript only)
* invoke the dynamic object's method, which ignores extra arguments and
* throws Node's missing-argument TypeError. */
function lowerWebCryptoMethodCall(
lowerer: Lowerer,
call: ts.CallExpression,
access: ts.PropertyAccessExpression,
): IrExpr | null {
const member = access.name.text;
if (
access.name.text !== "getRandomValues" ||
(member !== "getRandomValues" && member !== "randomUUID") ||
call.questionDotToken ||
access.questionDotToken ||
!isWebCryptoReceiver(lowerer, access.expression)
)
return null;
return lowerCryptoGetRandomValues(lowerer, call);
const args = call.arguments.nodes;
if (member === "getRandomValues" && args.length === 1) {
return lowerCryptoGetRandomValues(lowerer, call);
}
if (member === "randomUUID" && args.length === 0) {
return { kind: "libCall", fn: "crypto.randomUUID", args: [], type: STRING, loc: locOf(call) };
}
if (args.some(ts.isSpreadElement)) return null;
return {
kind: "dynInvoke",
recv: lowerer.lowerExprExpecting(access.expression, DYN),
method: member,
calleeName: v8CalleeText(access),
args: args.map((arg) => lowerer.lowerExprExpecting(arg, DYN)),
type: DYN,
loc: locOf(call),
};
}
/** A then handler's promise-armed result (`.then(() => cached ?? load())`
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import { lowerValueReturningConstructorNew } from "./js-dynamic-objects.js";
import { adaptPromiseExecutor } from "./promise-executor-adapter.js";
import { dynUndefinedExpr, nodeThrowExpr, varRef } from "../../ir/build.js";
@@ -2405,7 +2404,8 @@ export function collectClassShapeInner(
for (const member of decl.members.nodes) {
if (
(functionLocalClass(decl) || checkedStatics) &&
isJsSourceFile(ts.getSourceFileOfNode(decl)) &&
(isJsSourceFile(ts.getSourceFileOfNode(decl)) ||
(functionLocalClass(decl) && ts.isMethodDeclaration(member))) &&
(ts.isClassStaticBlockDeclaration(member) ||
ts
.getModifiers(member)
@@ -4664,7 +4664,7 @@ export function genericClassInstanceType(
);
}
const ordinal = gci.instances.size;
const name = keyedHelperName(lowerer, `${gci.family.def.name}%`, key, "");
const name = `${gci.family.def.name}%${ordinal}`;
const entry: { name: string; info: ClassInfo | null; poisoned?: boolean } = { name, info: null };
gci.instances.set(key, entry);
const bindings = new Map<ts.Symbol, IrType>();
@@ -5873,9 +5873,10 @@ export function lowerClassExpressionInfo(
decoratorNodesOf(member).length ||
(!isJsSourceFile(ts.getSourceFileOfNode(expr)) &&
(ts.isClassStaticBlockDeclaration(member) ||
ts
.getModifiers(member)
?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword))) ||
(!ts.isMethodDeclaration(member) &&
ts
.getModifiers(member)
?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword)))) ||
(ts.isMethodDeclaration(member) && member.typeParameters?.nodes.length),
))
) {
@@ -7911,7 +7912,7 @@ export function classIteratorDrainCall(
const key = `${cit.className}:${typeKey(outElem)}`;
let name = lowerer.iterDrainHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%iter.drain", key);
name = `%iter.drain.${lowerer.iterDrainHelpers.size}`;
lowerer.iterDrainHelpers.set(key, name);
const recvT: IrType = { kind: "object", className: cit.className };
const recvRef: IrExpr = { kind: "varRef", localId: "r.0", type: recvT, loc };
@@ -8025,7 +8026,7 @@ export function classIteratorRestDrainCall(
const key = `it:${cit.iterT.className}`;
let name = lowerer.iterDrainHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%iter.drain", key);
name = `%iter.drain.${lowerer.iterDrainHelpers.size}`;
lowerer.iterDrainHelpers.set(key, name);
const itRef: IrExpr = { kind: "varRef", localId: "it.0", type: cit.iterT, loc };
const outRef: IrExpr = { kind: "varRef", localId: "out.0", type: outT, loc };
@@ -10533,7 +10534,7 @@ export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
const key = `x509.record:${isBytes ? "bytes" : "str"}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%x509.record", key);
helper = `%x509.record.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const dataT = data.type;
const dRef: IrExpr = { kind: "varRef", localId: "d.0", type: dataT, loc };
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import { dynUndefinedExpr, boolLit } from "../../ir/build.js";
/* The dgram/dns-surface lowering (node:dgram + node:dns — a spoke module
* like lower-server.ts): module-function calls (dgram createSocket, dns
@@ -358,7 +357,7 @@ function checkedDynPromise(
const key = `dns.promise:${typeKey(declared)}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%dns.promise", key);
helper = `%dns.promise.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const promiseT: IrType = { kind: "promise", inner: DYN };
lowerer.liftedFns.push({
@@ -892,7 +891,7 @@ export function lowerPromisifiedDnsCall(
const key = `dnsPromise:${JSON.stringify(value)}`;
let name = lowerer.valueHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%dnsPromise", key);
name = `%dnsPromise.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
lowerer.liftedFns.push({
name,
@@ -42,7 +42,6 @@
* emit-overrides block at the bottom of this file (per-event
* monomorphization of the override body, dispatch on the dynamic class,
* super.emit as the prototype chain). */
import { keyedHelperName } from "./stable-names.js";
import { checkKeypressListener } from "./builtins/readline.js";
import * as ts from "../ts7/adapter.js";
import type { Lowerer } from "./lowerer.js";
@@ -632,7 +631,7 @@ function lowerFlexListenerCall(
const key = `emitter.${registering ? "onFlex" : "offDyn"}:${typeKey(recvT)}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, `%emitter.${registering ? "onFlex" : "offFlex"}`, key);
helper = `%emitter.${registering ? "onFlex" : "offFlex"}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
const params: IrParam[] = [
{ localId: "r.0", name: "r", type: recvT },
@@ -851,7 +850,7 @@ function lowerDynListenerCall(
const existing = lowerer.arrHofHelpers.get(key);
let helper = existing;
if (!helper) {
helper = keyedHelperName(lowerer, `%emitter.${registering ? "onDyn" : "offDyn"}`, key);
helper = `%emitter.${registering ? "onDyn" : "offDyn"}.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
const check: IrStmt = {
kind: "exprStmt",
@@ -1001,7 +1000,7 @@ export function lowerDynamicEmitterEmit(
const key = `emitter.dynEmit:${typeKey(recvT)}:${k}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%emitter.dynEmit", key);
helper = `%emitter.dynEmit.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
const params: IrParam[] = [
{ localId: "r.0", name: "r", type: recvT },
@@ -1093,7 +1092,7 @@ export function lowerDynamicEmitterOn(
const key = `emitter.dynOn:${typeKey(recvT)}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%emitter.dynOn", key);
helper = `%emitter.dynOn.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
const params: IrParam[] = [
{ localId: "r.0", name: "r", type: recvT },
@@ -2360,7 +2359,7 @@ function emitDispatchExpr(
const key = `emitter.emitDispatch:${typeKey(recvT)}:${name}`;
let helper = lowerer.arrHofHelpers.get(key);
if (!helper) {
helper = keyedHelperName(lowerer, "%emitter.emitDispatch", key);
helper = `%emitter.emitDispatch.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, helper);
const params: IrParam[] = [
{ localId: "r.0", name: "r", type: recvT },
@@ -2425,7 +2424,7 @@ function eventsOnceBoxer(
const key = `eventsOnce:${typeKey(fnType)}`;
let name = lowerer.valueHelpers.get(key);
if (name === undefined) {
name = keyedHelperName(lowerer, "%events.onceArgs", key);
name = `%events.onceArgs.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const params: IrParam[] = tuple.map((type, i) => ({
localId: `%once.a${i}`,
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import { lowerWebAssemblyValue, refuseWebAssemblyUse } from "./builtins/webassembly.js";
import { dynUndefinedExpr, nodeThrowExpr, numLit, strLit, varRef } from "../../ir/build.js";
import { InternalCompilerError } from "../../errors.js";
@@ -2275,11 +2274,7 @@ export function cjsAccessorSnapshotOf(
if (!lexerVisibleAccessors(ts.getSourceFileOfNode(getter)).includes(getter)) return null;
const call = cjsAccessorCall(lowerer, getter, locOf(getter));
if (call === null) return null;
const at = locOf(getter);
const snapshot = {
id: keyedHelperName(lowerer, "%g.cjsSnapshot", `${at.file}:${at.start}:${at.end}`),
type: call.type,
};
const snapshot = { id: `%g.cjsSnapshot.${lowerer.cjsAccessorSnapshots.size}`, type: call.type };
lowerer.globalsList.push({
id: snapshot.id,
name: "%cjsSnapshot",
@@ -2304,7 +2299,7 @@ export function cjsAccessorSnapshotStores(
if (getters.length === 0) return [];
let readyId = lowerer.cjsSnapshotReady.get(dep);
if (readyId === undefined) {
readyId = keyedHelperName(lowerer, "%g.cjsSnapshotReady", dep.fileName);
readyId = `%g.cjsSnapshotReady.${lowerer.cjsSnapshotReady.size}`;
lowerer.globalsList.push({ id: readyId, name: "%cjsSnapshotReady", type: BOOL, mutable: true });
lowerer.cjsSnapshotReady.set(dep, readyId);
}
@@ -270,7 +270,7 @@ export function lowerLambda(
const nameIdent =
!ts.isArrowFunction(node) && node.name && ts.isIdentifier(node.name) ? node.name : null;
const baseName = nameIdent ? nameIdent.text : "";
const fnName = lowerer.closureName(locOf(node), baseName ? `_${baseName}` : "");
const fnName = `%fn${lowerer.lambdaCounter++}${baseName ? `_${baseName}` : ""}`;
// Named function expressions/declarations can self-reference by name; an
// object-literal method's name is a PROPERTY, not a binding — no self.
const selfSymbol =
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import {
dynUndefinedExpr,
boolLit,
@@ -269,7 +268,7 @@ function errorInspectHelper(
const key = `insp.userError:${typeKey(t)}`;
const existing = lowerer.inspectHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%util.insp", key);
const name = `%util.insp.${lowerer.inspectHelpers.size}`;
lowerer.inspectHelpers.set(key, name);
const v = (): IrExpr => varRef("v.0", t, loc);
const r = (): IrExpr => varRef("r.0", F64, loc);
@@ -1146,7 +1145,7 @@ function inspectHelper(lowerer: Lowerer, t: IrType, loc: SrcLoc): string {
const key = `insp:${typeKey(t)}`;
const existing = lowerer.inspectHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, "%util.insp", key);
const name = `%util.insp.${lowerer.inspectHelpers.size}`;
lowerer.inspectHelpers.set(key, name);
const v = (): IrExpr => varRef("v.0", t, loc);
@@ -2075,7 +2074,7 @@ function formatUnionHelper(
const existing = lowerer.inspectHelpers.get(key);
if (existing) return existing;
const loc = helperLoc(site);
const name = keyedHelperName(lowerer, "%util.fmtv", key);
const name = `%util.fmtv.${lowerer.inspectHelpers.size}`;
lowerer.inspectHelpers.set(key, name);
const def = lowerer.unions.get(t.unionId);
if (!def) throw new InternalCompilerError(`format value over unknown union ${t.unionId}`);
@@ -34,7 +34,6 @@
* statics-bearing mixin classes outside a plain top-level statement
* (their declaration-time code must run exactly where the call
* evaluates). */
import { positionTag } from "./structural-positions.js";
import * as ts from "../ts7/adapter.js";
import type { Lowerer } from "./lowerer.js";
import { PoisonError } from "./lowerer.js";
@@ -453,7 +452,7 @@ function instantiateMixinCall(
const base = mixinBaseClassOf(lowerer, call.arguments.nodes[0]!);
const instName = lowerer.qualify(
ts.getSourceFileOfNode(call),
`%mx${positionTag(lowerer, call)}.${shape.classNode.name?.text ?? ""}`,
`%mx${ts.tokenStartOfNode(call)}.${shape.classNode.name?.text ?? ""}`,
);
const ordinal = lowerer.mixinOrdinals.get(shape.classNode) ?? 0;
lowerer.mixinOrdinals.set(shape.classNode, ordinal + 1);
@@ -17,7 +17,6 @@
* writes the original back on restore(). Each mock registers a closure
* that calls its restore() with its tracker (scr_test.c): the module's
* `mock`, or the test's t.mock, which is reset when the test ends. */
import { keyedHelperName } from "./stable-names.js";
import * as ts from "../ts7/adapter.js";
import {
BOOL,
@@ -137,7 +136,7 @@ function liftOnce(
const key = `mock.${role}:${owner}`;
const existing = lowerer.assertHelpers.get(key);
if (existing) return existing;
const name = keyedHelperName(lowerer, `%mock.${role}`, key);
const name = `%mock.${role}.${lowerer.assertHelpers.size}`;
lowerer.assertHelpers.set(key, name);
build(name);
return name;
@@ -34,7 +34,7 @@ test("retains dependencies from function storage, captures, generator channels a
const completed = record("completed");
const body = record("body");
const global = record("global", record("nested"));
const wanted = lowerer.shapes.shapes.map((shape) => shape.id).sort();
const wanted = lowerer.shapes.shapes.map((shape) => shape.id);
record("unused");
const unionId = lowerer.unions.intern([body, UNDEFINED_T]);
const unusedUnion = lowerer.unions.intern([F64, UNDEFINED_T]);
@@ -162,7 +162,7 @@ test("closes nested structural types and index-signature values", () => {
sent = record("sent"),
indexed = record("indexed");
const containerId = lowerer.shapes.intern([], false, indexed);
const wanted = lowerer.shapes.shapes.map((shape) => shape.id).sort();
const wanted = lowerer.shapes.shapes.map((shape) => shape.id);
record("unused");
const nested: IrType = {
kind: "func",
@@ -1,5 +1,4 @@
import { THREADS_MODULE } from "../threads-import.js";
import { positionTag } from "./structural-positions.js";
import { EXPERIMENTAL_MODULE } from "../experimental-import.js";
import { dynUndefinedExpr } from "../../ir/build.js";
import { everyStmtList, everyTypeChild } from "../../ir/traverse.js";
@@ -1075,14 +1074,8 @@ export function moduleArtifacts(
else delete rest.abstractMethods;
return rest;
}),
// Ids come from what each shape and union is (keyedHelperName), so
// ordering by id gives every lowering order the same module.
records: lowerer.shapes.shapes
.filter((r) => shapeIds.has(r.id))
.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)),
unions: lowerer.unions.unions
.filter((u) => unionIds.has(u.id))
.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)),
records: lowerer.shapes.shapes.filter((r) => shapeIds.has(r.id)),
unions: lowerer.unions.unions.filter((u) => unionIds.has(u.id)),
};
}
@@ -1684,7 +1677,7 @@ export function collectGlobals(
}
if (type.kind === "void") lowerer.badType(stmt.name, lowerer.typeOf(stmt.name));
const g: IrGlobal = {
id: `%g.${tag}${nsPathPrefix(lowerer, stmt)}%alias.${stmt.name.text}%${positionTag(lowerer, stmt)}`,
id: `%g.${tag}${nsPathPrefix(stmt)}%alias.${stmt.name.text}%${ts.tokenStartOfNode(stmt)}`,
name: stmt.name.text,
type,
mutable: false,
@@ -1761,7 +1754,7 @@ export function collectGlobals(
// (`%g.e.A%B.x`), so `namespace A { export const x }` and a
// top-level `const x` never collide; non-exported members add the
// declaring block's position (block-local bindings).
const nsPrefix = nsPathPrefix(lowerer, stmt, list.declarations.nodes[0]);
const nsPrefix = nsPathPrefix(stmt, list.declarations.nodes[0]);
for (const decl of list.declarations.nodes) {
// --provenance-sources: an elided pure-annotated dead const in a
// fetched source module registers no global (the statement
@@ -36,7 +36,6 @@
* of the compiler already takes (a body running before the declaring
* statement is not modeled — same as top-level const reads through early
* calls). */
import { positionTag, type PositionTagOwner } from "./structural-positions.js";
import * as ts from "../ts7/adapter.js";
import { packageShimFor } from "../package-shims.js";
import type { Lowerer } from "./lowerer.js";
@@ -165,11 +164,7 @@ function registerTypeOnlyBlocks(lowerer: Lowerer, decl: ts.ModuleDeclaration): v
* distinct (resolution is by symbol; names only need uniqueness and
* determinism). `modifierDecl` carries the declaration whose modifiers
* decide exportedness (the first declarator of a variable statement). */
export function nsPathPrefix(
owner: PositionTagOwner,
node: ts.Node,
modifierDecl?: ts.Node,
): string {
export function nsPathPrefix(node: ts.Node, modifierDecl?: ts.Node): string {
const parent = node.parent;
if (parent === undefined || !ts.isModuleBlock(parent)) return "";
const flagsHost = modifierDecl ?? node;
@@ -184,10 +179,10 @@ export function nsPathPrefix(
const nested = p.parent !== undefined && ts.isModuleBlock(p.parent);
const nonExported =
nested && (ts.getCombinedModifierFlags(p) & ts.ModifierFlags.Export) === 0;
segs.unshift(p.name.text + (nonExported ? `%${positionTag(owner, p)}` : ""));
segs.unshift(p.name.text + (nonExported ? `%${ts.tokenStartOfNode(p)}` : ""));
}
}
return `${segs.join("%")}${exported ? "" : `%${positionTag(owner, parent)}`}.`;
return `${segs.join("%")}${exported ? "" : `%${ts.tokenStartOfNode(parent)}`}.`;
}
/** The registered namespace-block kind a symbol's declaration sits in
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import {
dynUndefinedExpr,
nodeThrowExpr,
@@ -184,7 +183,7 @@ export function voidizedCallback(lowerer: Lowerer, cb: IrExpr, loc: SrcLoc): IrE
const toT = funcOf([...fromT.params], VOID);
const key = `server.voidcb:${typeKey(fromT)}`;
const existing = lowerer.arrHofHelpers.get(key);
const name = existing ?? keyedHelperName(lowerer, "%server.voidcb", key);
const name = existing ?? `%server.voidcb.${lowerer.arrHofHelpers.size}`;
if (!existing) {
lowerer.arrHofHelpers.set(key, name);
const impl = `${name}.impl`;
@@ -280,7 +279,7 @@ function receiverReturningCall(
): IrExpr {
const key = `server.recvret:${fn}:${opts?.prefix?.fn ?? ""}:${(opts?.mainArgIndices ?? []).join(".")}:${callArgs.map((a) => typeKey(a.type)).join(",")}`;
const existing = lowerer.arrHofHelpers.get(key);
const name = existing ?? keyedHelperName(lowerer, "%server.recvret", key);
const name = existing ?? `%server.recvret.${lowerer.arrHofHelpers.size}`;
if (!existing) {
lowerer.arrHofHelpers.set(key, name);
const params = callArgs.map((a, i) => ({ localId: `p${i}.0`, name: `p${i}`, type: a.type }));
@@ -591,7 +590,7 @@ function h2HeadersCallbackAdapter(
);
const key = `h2.hdrcb:${handleKind}:${withStatus}:${typeKey(fromT)}:${shapeId ?? ""}`;
const existing = lowerer.arrHofHelpers.get(key);
const name = existing ?? keyedHelperName(lowerer, "%h2.hdrcb", key);
const name = existing ?? `%h2.hdrcb.${lowerer.arrHofHelpers.size}`;
if (!existing) {
lowerer.arrHofHelpers.set(key, name);
const impl = `${name}.impl`;
@@ -3584,7 +3583,7 @@ export function lowerCompatReqStreamOptionalCall(
type: unionT,
loc,
};
const id = lowerer.closureName(loc, "", "chain");
const id = `chain.${lowerer.chainCounter++}`;
const body = lowerH2StreamMethodCall(lowerer, call, call.expression, {
kind: "chainRecv",
id,
@@ -4425,7 +4424,7 @@ function lowerHttpCreateServerForms(
const hasMax = opts.maxHeaderSize !== null;
const key = `server.opts:${opts.joinDup ? 1 : 0}:${opts.allowMissingHost ? 1 : 0}:${hasBuffer ? 1 : 0}:${hasMax ? 1 : 0}`;
const existing = lowerer.arrHofHelpers.get(key);
const name = existing ?? keyedHelperName(lowerer, "%server.opts", key);
const name = existing ?? `%server.opts.${lowerer.arrHofHelpers.size}`;
if (!existing) {
lowerer.arrHofHelpers.set(key, name);
const params = [
@@ -8010,7 +8009,7 @@ function lowerHttpResMethodCall(
const all = [...callArgs, cb];
const key = `server.endcb:${fn}:${all.map((a) => typeKey(a.type)).join(",")}`;
const existing = lowerer.arrHofHelpers.get(key);
const helper = existing ?? keyedHelperName(lowerer, "%server.endcb", key);
const helper = existing ?? `%server.endcb.${lowerer.arrHofHelpers.size}`;
if (!existing) {
lowerer.arrHofHelpers.set(key, helper);
const params = all.map((a, i) => ({ localId: `p${i}.0`, name: `p${i}`, type: a.type }));
@@ -2278,7 +2278,7 @@ function lowerStaticNamespaceImport(
): IrExpr {
const loc = locOf(call);
const resultT: IrType & { kind: "promise" } = { kind: "promise", inner: ns };
const fnName = lowerer.closureName(loc, "_staticImport");
const fnName = `%fn${lowerer.lambdaCounter++}_staticImport`;
const fnType: IrType & { kind: "func" } = { kind: "func", params: [], ret: resultT };
const fnCtx = newFnCtx(true, null, fnType, ns);
fnCtx.isAsync = true;
@@ -3965,9 +3965,13 @@ function stdlibGlobalTokenDestructure(
const canonical = ts.symbolName(member) === "global" ? "globalThis" : ts.symbolName(member);
token = `[builtin ${canonical}]`;
alias = canonical;
// Builtin constructors read as their interned function values, the
// same values the bare and `globalThis.X` spellings read.
value = builtinGlobalValue(canonical, locOf(el), true);
// Builtin constructors read as their interned function values, and
// crypto as the WebCrypto object: the same values the bare and
// `globalThis.X` spellings read.
value =
canonical === "crypto"
? { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc: locOf(el) }
: builtinGlobalValue(canonical, locOf(el), true);
} else if (globalName === "console" && !CONSOLE_CALL_MEMBERS.has(propName)) {
token = `[builtin console.${propName}]`;
} else if (TOKEN_OPAQUE_GLOBALS.has(globalName)) {
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import { nodeThrowExpr, boolLit, numLit, strLit } from "../../ir/build.js";
import { isValueStream, valueStreamOf } from "./builtins/hash-stream.js";
import { InternalCompilerError } from "../../errors.js";
@@ -316,7 +315,7 @@ function lowerStreamCallback(
);
}
}
const fnName = lowerer.closureName(loc, `_${which}cb`);
const fnName = `%fn${lowerer.lambdaCounter++}_${which}cb`;
const fnCtx = newFnCtx(true, null, funcType, funcType.ret);
lowerer.fnStack.push(fnCtx);
try {
@@ -1149,7 +1148,7 @@ function streamMethodWrapper(
paramTypes.push(got.type);
}
const ret = found.sig.ret;
const fnName = lowerer.closureName(loc, `_${which}vt`);
const fnName = `%fn${lowerer.lambdaCounter++}_${which}vt`;
const funcType = funcOf([thisType, ...paramTypes], ret);
lowerer.fnStack.push(newFnCtx(true, null, funcType, ret));
try {
@@ -1695,7 +1694,7 @@ function fromGenHelpers(
const key = `streamFromGen:${role}:${typeKey(gen)}`;
let name = lowerer.valueHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, `%stream.fromGen.${role}`, key);
name = `%stream.fromGen.${role}.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
lowerer.liftedFns.push({
name,
@@ -1784,7 +1783,7 @@ function classIteratorObject(
const key = `streamFromIter:${cit.iterT.className}`;
let name = lowerer.valueHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%stream.fromIter", key);
name = `%stream.fromIter.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const nextName = `${name}.next`;
const nextT = funcOf([], DYN);
@@ -1945,7 +1944,7 @@ function classAsyncIteratorObject(
const key = `streamFromAsyncIter:${cit.className}`;
let name = lowerer.valueHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%stream.fromAsyncIter", key);
name = `%stream.fromAsyncIter.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const it: IrLocal = { id: "it.0", name: "it", type: cit.iterT, mutable: false, boxed: true };
const src: IrLocal = {
@@ -3129,7 +3128,7 @@ function pipelineBoxHelper(
const key = `pipelineBox:${typeKey(promiseT)}`;
let name = lowerer.valueHelpers.get(key);
if (!name) {
name = keyedHelperName(lowerer, "%stream.pipelineBox", key);
name = `%stream.pipelineBox.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
lowerer.liftedFns.push({
name,
@@ -3156,7 +3155,7 @@ function pipelineAbortHelper(lowerer: Lowerer, ac: IrExpr, loc: SrcLoc): IrExpr
let name = lowerer.valueHelpers.get(key);
const abortT = funcOf([], VOID);
if (!name) {
name = keyedHelperName(lowerer, "%stream.pipelineAbort", key);
name = `%stream.pipelineAbort.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, name);
const acLocal: IrLocal = { id: "ac.0", name: "ac", type: DYN, mutable: false, boxed: true };
const acRef = varRef(acLocal.id, DYN, loc);
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import { dynUndefinedExpr, numLit, varRef } from "../../ir/build.js";
import * as ts from "../ts7/adapter.js";
import { isJsSourceFile } from "../program.js";
@@ -56,7 +55,7 @@ export function adaptZeroArgTimerCallback(
const toT: IrType = { kind: "func", params: [], ret: VOID };
const key = `timer.dropret:${typeKey(fromT)}`;
const existing = lowerer.arrHofHelpers.get(key);
const name = existing ?? keyedHelperName(lowerer, "%timer.dropret", key);
const name = existing ?? `%timer.dropret.${lowerer.arrHofHelpers.size}`;
if (!existing) {
lowerer.arrHofHelpers.set(key, name);
lowerer.liftedFns.push(
@@ -118,7 +117,7 @@ export function adaptZeroArgTimerCallback(
const toT: IrType = { kind: "func", params: [], ret: VOID };
const key = `timer.droparg:${typeKey(fromT)}`;
const existing = lowerer.arrHofHelpers.get(key);
const name = existing ?? keyedHelperName(lowerer, "%timer.droparg", key);
const name = existing ?? `%timer.droparg.${lowerer.arrHofHelpers.size}`;
if (!existing) {
lowerer.arrHofHelpers.set(key, name);
lowerer.liftedFns.push(
@@ -204,7 +203,7 @@ function makeTimerArgsThunk(
const toT: IrType = { kind: "func", params: [], ret: VOID };
const key = `timer.argsthunk:${n}`;
const existing = lowerer.arrHofHelpers.get(key);
const name = existing ?? keyedHelperName(lowerer, "%timer.argsthunk", key);
const name = existing ?? `%timer.argsthunk.${lowerer.arrHofHelpers.size}`;
if (!existing) {
lowerer.arrHofHelpers.set(key, name);
const impl = `${name}.impl`;
@@ -10,7 +10,6 @@ import {
} from "../../ir/ir.js";
import type { Node, Program, SourceFile, Symbol, Type } from "../ts7/adapter.js";
import { Lowerer } from "./lowerer.js";
import { UnionRegistry } from "../type-mapper.js";
import { classMethodValue } from "./class-method-values.js";
const loc = { file: "island.ts", start: 0, end: 1 };
@@ -33,7 +32,7 @@ test("reuses complete copy routes without repeating discriminator planning", ()
expect(plan).not.toHaveBeenCalled();
expect(lowerer.liftedFns.filter((fn) => fn.name === helper)).toHaveLength(1);
const recursive = {} as Type;
lowerer.shapes.recursiveRef(recursive, () => "Recursive");
lowerer.shapes.recursiveRef(recursive);
lowerer.shapes.finalizeRecursive(recursive, [{ name: "kind", type: STRING }]);
expect(lowerer.unionRetagHelper(from, to, loc)).toBe(helper);
expect(plan).toHaveBeenCalled();
@@ -55,7 +54,7 @@ test("replans after a recursive union gains its final arms", () => {
const lowerer = context();
const recursive = {} as Type;
const from = lowerer.unions.intern([UNDEFINED_T, NULL_T]);
const to = lowerer.unions.recursiveRef(recursive, () => "Recursive");
const to = lowerer.unions.recursiveRef(recursive);
const trapped = lowerer.unionRetagHelper(from, to, loc);
expect(trapped).not.toBeNull();
lowerer.unions.finalizeRecursive(recursive, [UNDEFINED_T]);
@@ -76,7 +75,7 @@ test("rechecks width conversions after recursive record definitions settle", ()
const lowerer = context();
const recursive = {} as Type;
const source = record(lowerer.shapes.intern([{ name: "value", type: F64 }]));
const target = record(lowerer.shapes.recursiveRef(recursive, () => "Recursive"));
const target = record(lowerer.shapes.recursiveRef(recursive));
const from = lowerer.unions.intern([source, UNDEFINED_T]);
const to = lowerer.unions.intern([target, NULL_T]);
expect(lowerer.unionRetagHelper(from, to, loc)).not.toBeNull();
@@ -130,7 +129,7 @@ test("literal ownership is reused across values and rechecked when recursive rec
expect(inspect).not.toHaveBeenCalled();
const recursive = {} as Type;
const pendingShape = record(lowerer.shapes.recursiveRef(recursive, () => "Recursive"));
const pendingShape = record(lowerer.shapes.recursiveRef(recursive));
const pending = lowerer.unions.intern([pendingShape], {
field: "kind",
cases: [{ tag: 0, values: ["a"] }],
@@ -144,7 +143,7 @@ test("literal ownership is reused across values and rechecked when recursive rec
test("literal ownership sees a recursive union's finalized discriminator", () => {
const lowerer = context();
const recursive = {} as Type;
const union = lowerer.unions.recursiveRef(recursive, () => "Recursive");
const union = lowerer.unions.recursiveRef(recursive);
const shape = record(lowerer.shapes.intern([{ name: "kind", type: STRING }]));
expect(lowerer.literalUnionArm(union, ["ready"])).toBeNull();
lowerer.unions.finalizeRecursive(recursive, [shape], {
@@ -183,10 +182,7 @@ test("removing undefined reuses the discriminator transformation without hiding
);
const recursive = {} as Type;
const pending: IrType = {
kind: "union",
unionId: lowerer.unions.recursiveRef(recursive, () => "Recursive"),
};
const pending: IrType = { kind: "union", unionId: lowerer.unions.recursiveRef(recursive) };
expect(lowerer.stripUndefinedArm(pending)).toEqual(pending);
lowerer.unions.finalizeRecursive(recursive, [STRING, UNDEFINED_T]);
expect(lowerer.stripUndefinedArm(pending)).toEqual(STRING);
@@ -219,10 +215,7 @@ test("adding undefined reuses semantic union transformations and sees recursive
expect(lowerer.unions.get(added.unionId)?.discriminant).toEqual(discriminant);
const recursive = {} as Type;
const pending: IrType = {
kind: "union",
unionId: lowerer.unions.recursiveRef(recursive, () => "Recursive"),
};
const pending: IrType = { kind: "union", unionId: lowerer.unions.recursiveRef(recursive) };
const before = lowerer.withUndefinedArmOf(pending);
lowerer.unions.finalizeRecursive(recursive, [a, b], discriminant);
expect(lowerer.withUndefinedArmOf(pending)).toEqual(added);
@@ -234,18 +227,13 @@ test("adding undefined reuses semantic union transformations and sees recursive
test("optional union widening keeps refused and missing contracts current", () => {
const lowerer = context();
const recursive = {} as Type;
const pending: IrType = {
kind: "union",
unionId: lowerer.unions.recursiveRef(recursive, () => "Recursive"),
};
const pending: IrType = { kind: "union", unionId: lowerer.unions.recursiveRef(recursive) };
expect(lowerer.withUndefinedArmOf(pending)).not.toBeNull();
lowerer.unions.finalizeRecursive(recursive, [{ kind: "date" }, STRING]);
expect(lowerer.withUndefinedArmOf(pending)).toBeNull();
expect(lowerer.withUndefinedArmOf(pending)).toBeNull();
// Union ids come from their arms, so a fresh registry names the union
// this one has not interned yet.
const missing: IrType = { kind: "union", unionId: new UnionRegistry().intern([F64, STRING]) };
const missing: IrType = { kind: "union", unionId: `u${lowerer.unions.unions.length}` };
expect(lowerer.withUndefinedArmOf(missing)).toBeNull();
expect(lowerer.unions.intern([F64, STRING])).toBe(missing.unionId);
expect(lowerer.withUndefinedArmOf(missing)?.kind).toBe("union");
@@ -1,6 +1,4 @@
import { relieveHeapPressure } from "../../heap-pressure.js";
import { keyedHelperName } from "./stable-names.js";
import { positionTag, StructuralPositions } from "./structural-positions.js";
import { onlyForwarded, stripTypeWrappers } from "./typed-arguments.js";
import {
needsGeneralNodeStreams,
@@ -1006,11 +1004,6 @@ function lowerShuffle(): ((wave: string[]) => void) | null {
* the bodies reachable emit did NOT mark (plus deferred collection
* diagnostics nothing flushed), reported separately: whole-program analysis without
* letting unreached code fail builds. */
/** The closure root of an emit-override specialization body. */
function emitSpecRoot(spec: EmitSpecRequest): string {
return `%emit.${spec.info.def.name}.${spec.event}.${spec.ordinal}`;
}
export function lowerToIr(
program: ts.Program,
entry: ts.SourceFile,
@@ -2178,6 +2171,12 @@ export class Lowerer {
/** Instance types (by checker type id) of the local classes generic
* factory calls return, and the per-instance class each names. */
readonly localClassInstanceTypes = new Map<number, string>();
/** A generic factory's instantiated result type → the class value of the
* local class it returns (TypeMapperCtx.localClassValue). */
readonly localClassValueTypes = new Map<number, IrType>();
/** While a generic factory instance lowers: its declared result type → the
* class value of the local class it returns. */
instanceResultClass: { declared: number; value: IrType } | null = null;
/** The ts-level twin of typeParamBindings, non-null while a CALL-keyed
* instance body lowers: type-parameter symbol → the bound CHECKER type,
* consulted where the mapped IrType has already widened away information
@@ -2808,8 +2807,6 @@ export class Lowerer {
/** Generated value helpers, keyed by operation and type. Callers own
* their key prefixes; this includes more than structural conversions. */
readonly valueHelpers = new Map<string, string>();
/** Each name keyedHelperName made, with a second hash of its key. */
readonly keyedNameKeys = new Map<string, number>();
/** structuralNarrowing's assignable classes, keyed by static and
* narrowed class name. */
readonly structuralClassPeers = new Map<string, readonly string[]>();
@@ -2946,6 +2943,7 @@ export class Lowerer {
ctx: FnCtx;
}
>();
chainCounter = 0;
/** Keyed by program-wide qualified class name (what IR object types carry). */
readonly classes = new Map<string, ClassInfo>();
readonly classBySymbol = new Map<ts.Symbol, ClassInfo>();
@@ -3175,47 +3173,7 @@ export class Lowerer {
* dispatch revision needs no weak-key identity or process-global state. */
errorMethodDispatchRevision: string | null = null;
errorPropertyDispatchRevision: string | null = null;
/** Structural source positions (structural-positions.ts) for names that
* stay the same when unrelated code moves. */
readonly positions = new StructuralPositions();
/** The outermost node whose token span is exactly `loc`, or null. */
nodeAtLoc(loc: SrcLoc): ts.Node | null {
const sf = this.program.getSourceFile(loc.file);
return sf === undefined ? null : (ts.nodeAtSpan(sf, loc.start, loc.end) ?? null);
}
/** The lowering unit whose name scopes the closures it lifts. */
private closureRoot = "%pre";
private readonly closureNameCounts = new Map<string, number>();
/** Runs `fn` with the closures it lifts named under `root`. */
withClosureRoot<T>(root: string, fn: () => T): T {
const previous = this.closureRoot;
this.closureRoot = root;
try {
return fn();
} finally {
this.closureRoot = previous;
}
}
/** The name of a closure (or wrapper) lifted for the source at `loc`:
* `<prefix>.<hash>` of the current lowering unit, the location and
* `suffix`, then `suffix`. A unit that lifts several functions for the same
* location and suffix numbers them in the order it lifts them, so a
* closure's name depends only on its unit, not on which units lowered
* before it. */
closureName(loc: SrcLoc, suffix = "", prefix = "%fn"): string {
const node = this.nodeAtLoc(loc);
const where = node === null ? `${loc.file}:${loc.start}:${loc.end}` : this.positions.of(node);
const key = `${prefix}|${this.closureRoot}|${where}|${suffix}`;
const first = keyedHelperName(this, prefix, key);
const seen = this.closureNameCounts.get(first) ?? 0;
this.closureNameCounts.set(first, seen + 1);
return (seen === 0 ? first : keyedHelperName(this, prefix, `${key}|${seen}`)) + suffix;
}
lambdaCounter = 0;
readonly preparedClassFactories = new Map<ts.ArrowFunction | ts.FunctionExpression, IrExpr>();
readonly preparingClassFactories = new Set<ts.ArrowFunction | ts.FunctionExpression>();
@@ -3568,7 +3526,6 @@ export class Lowerer {
this.checker = program.getTypeChecker();
this.typeCtx = {
checker: this.checker,
positionOf: (node) => this.positions.of(node),
shapes: this.shapes,
unions: this.unions,
classNamer: this.classNamer,
@@ -3578,6 +3535,10 @@ export class Lowerer {
genericClassInstance: (decl, ref, mapArgument) =>
this.genericClassInstanceType(decl, ref, mapArgument),
runtimeClassValue: (type) => runtimeClassValueOfType(this, type),
localClassValue: (type) =>
this.instanceResultClass?.declared === type.id
? this.instanceResultClass.value
: (this.localClassValueTypes.get(type.id) ?? null),
mixinClassInstance: (decl) =>
this.mixinTypeContext && this.mixinTypeContext.classNode === decl
? { kind: "object", className: this.mixinTypeContext.className }
@@ -3857,11 +3818,11 @@ export class Lowerer {
})()
? this.qualify(
ts.getSourceFileOfNode(decl),
`%cx${positionTag(this, decl)}.${decl.name?.text ?? ""}`,
`%cx${ts.tokenStartOfNode(decl)}.${decl.name?.text ?? ""}`,
)
: this.qualify(
ts.getSourceFileOfNode(decl),
nsPathPrefix(this, decl) + (decl.name ? decl.name.text : "%anon"),
nsPathPrefix(decl) + (decl.name ? decl.name.text : "%anon"),
);
for (let index = this.localClassInstantiations.length - 1; index >= 0; index--) {
const entry = this.localClassInstantiations[index]!;
@@ -7420,10 +7381,7 @@ export class Lowerer {
}
for (const decl of fp.classDecls) {
const info = this.classes.get(this.classNamer(decl));
if (info)
functions.push(
...this.withClosureRoot(info.def.name, () => this.lowerClassMembers(info)),
);
if (info) functions.push(...this.lowerClassMembers(info));
else if (this.countsSkips()) this.countSkippedBody(decl);
}
}
@@ -7436,10 +7394,7 @@ export class Lowerer {
// are reachable by definition, already counted by reachable emit.
if (!this.remainder) {
for (const fp of parts) {
const initName = this.initNameOf.get(fp.sf)!;
functions.push(
this.withClosureRoot(initName, () => this.lowerFileInit(fp.sf, fp.topStmts, initName)),
);
functions.push(this.lowerFileInit(fp.sf, fp.topStmts, this.initNameOf.get(fp.sf)!));
}
functions.push(this.buildMain());
}
@@ -7481,9 +7436,7 @@ export class Lowerer {
// default): the diagnostic is recorded — the instance skips
// like a signature-blocked function (lowerFunction's rule).
try {
functions.push(
this.withClosureRoot(inst.name, () => this.lowerGenericInstance(info, inst)),
);
functions.push(this.lowerGenericInstance(info, inst));
} catch (e) {
if (!(e instanceof PoisonError)) throw e;
}
@@ -7493,10 +7446,7 @@ export class Lowerer {
// instances of its own; the joint fixpoint covers both).
while (es < this.emitSpecQueue.length) {
try {
const spec = this.emitSpecQueue[es++]!;
const fn = this.withClosureRoot(emitSpecRoot(spec), () =>
lowerEmitOverrideSpec(this, spec),
);
const fn = lowerEmitOverrideSpec(this, this.emitSpecQueue[es++]!);
if (fn) functions.push(fn);
} catch (e) {
if (!(e instanceof PoisonError)) throw e;
@@ -8050,9 +8000,7 @@ export class Lowerer {
this.genericClassDemandPriority.set(info, ref);
instanceFunctions.push(
...this.withGenericDemandOwner(owner, () =>
this.shapes.withDeclaredOrderPriority(ref, () =>
this.withClosureRoot(info.def.name, () => this.lowerClassMembers(info)),
),
this.shapes.withDeclaredOrderPriority(ref, () => this.lowerClassMembers(info)),
),
);
relieveHeapPressure();
@@ -8079,7 +8027,7 @@ export class Lowerer {
instanceFunctions.push(
this.withGenericDemandOwner(owner, () =>
this.shapes.withDeclaredOrderPriority(ref, () =>
this.withClosureRoot(inst.name, () => this.lowerGenericInstance(info, inst)),
this.lowerGenericInstance(info, inst),
),
),
);
@@ -8112,12 +8060,9 @@ export class Lowerer {
);
while (specLowered < waveEnd) {
try {
const fn = this.shapes.withDeclaredOrderPriority([4, instanceMetadataOrder++], () => {
const spec = this.emitSpecQueue[specLowered++]!;
return this.withClosureRoot(emitSpecRoot(spec), () =>
lowerEmitOverrideSpec(this, spec),
);
});
const fn = this.shapes.withDeclaredOrderPriority([4, instanceMetadataOrder++], () =>
lowerEmitOverrideSpec(this, this.emitSpecQueue[specLowered++]!),
);
if (fn) instanceFunctions.push(fn);
} catch (e) {
if (!(e instanceof PoisonError)) throw e;
@@ -8145,9 +8090,7 @@ export class Lowerer {
initFunctions.push(
this.withGenericDemandOwner(owner, () =>
this.shapes.withDeclaredOrderPriority(priority, () =>
this.withClosureRoot(this.initNameOf.get(fp.sf)!, () =>
this.lowerFileInit(fp.sf, fp.topStmts, this.initNameOf.get(fp.sf)!),
),
this.lowerFileInit(fp.sf, fp.topStmts, this.initNameOf.get(fp.sf)!),
),
),
);
@@ -8173,9 +8116,7 @@ export class Lowerer {
const priority = metadataPriority.get(name) ?? [3, expressionMetadataOrder++];
const owner = demandOwner(priority);
const fn = this.withGenericDemandOwner(owner, () =>
this.shapes.withDeclaredOrderPriority(priority, () =>
this.withClosureRoot(name, unit.lower),
),
this.shapes.withDeclaredOrderPriority(priority, unit.lower),
);
if (fn) loweredUnits.set(name, fn);
relieveHeapPressure();
@@ -13480,6 +13421,10 @@ export class Lowerer {
const callable = this.lowerBuiltinCallableValue(bi, loc);
if (callable) return callable;
}
// node:crypto's webcrypto is the WebCrypto global's object.
if (bi.module === "crypto" && bi.member === "webcrypto") {
return { kind: "libCall", fn: "webcrypto.object", args: [], type: DYN, loc };
}
// node:stream/web's exports are the web stream globals themselves: in
// JavaScript sources the same identity token as the global spelling
// (`require("stream/web").ReadableStream === globalThis.ReadableStream`).
@@ -13559,7 +13504,7 @@ export class Lowerer {
const key = `${fn.valueFn ?? fn.fn}:${typeKey(funcType)}:${adapterShape}`;
let fnName = this.builtinCallableValueFns.get(key);
if (!fnName) {
fnName = keyedHelperName(this, `%builtin.value.${fn.valueFn ?? fn.fn}`, key);
fnName = `%builtin.value.${fn.valueFn ?? fn.fn}.${this.builtinCallableValueFns.size}`;
this.builtinCallableValueFns.set(key, fnName);
this.builtinCallableValueNames.set(fnName, display.slice(display.lastIndexOf(".") + 1));
const params = funcType.params.map((type, index) => ({
@@ -562,7 +562,7 @@ export function lowerFfiMemoryModule(lowerer: Lowerer, loc: SrcLoc): IrExpr {
const entries = lowerer.ffiImports
.filter((entry) => entry.library !== undefined)
.map((entry) => {
const name = lowerer.closureName(loc, "", "%ffi.module");
const name = `%ffi.module.${lowerer.lambdaCounter++}`;
const sourceTypes = ffiSourceParamTypes(entry.params);
const locals: IrLocal[] = sourceTypes.map((_, i) => ({
id: `%arg${i}`,
@@ -1,4 +1,3 @@
import { keyedHelperName } from "./stable-names.js";
import {
BOOL,
STRING,
@@ -28,7 +27,7 @@ export function objectEnumerationReceiver(
const key = `obj.receiver:${source.unionId}:${expected.shapeId}`;
let name = lowerer.arrHofHelpers.get(key);
if (name === undefined) {
name = keyedHelperName(lowerer, "%obj.receiver", key);
name = `%obj.receiver.${lowerer.arrHofHelpers.size}`;
lowerer.arrHofHelpers.set(key, name);
const value = varRef("value.0", source, loc);
lowerer.liftedFns.push({

Some files were not shown because too many files have changed in this diff Show More