mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-21 05:50:55 +08:00
Add native workers and shared memory
- Add native worker execution with isolated module state, structured messages, and lifecycle controls. - Support shared buffers and integer atomics with coordinated runtime ownership and cancellation. - Extend compiler, runtime-pack, differential, and compatibility coverage for native concurrency.
This commit is contained in:
@@ -46,6 +46,8 @@ SCRIPTC_TEST_WORKERS=4 SCRIPTC_SAN=1 pnpm test # sanitized lane
|
||||
|
||||
Corpus programs are differential tests against Node: every program runs under Node and as a compiled native binary, and stdout, stderr, and exit codes must match byte-for-byte. A new feature lands with corpus programs that pin its behavior both ways.
|
||||
|
||||
Before opening a PR that adds or changes corpus, diagnostic, or frontend fixtures, run `pnpm test:ts7 --baselines-only`. This fast completeness check uses the same fixture discovery as the parity suite; it does not type-check or replace behavioral parity. Generate missing preflight/order baselines, review their diagnostics and module order, and run the affected `order-parity.test.ts` batches before pushing. Keep existing baseline entries unchanged unless the corresponding behavior intentionally changes. The full `pnpm test:ts7` command also checks completeness before starting its sweep.
|
||||
|
||||
Test location follows scope:
|
||||
|
||||
- Co-locate white-box unit tests with implementation files under
|
||||
|
||||
@@ -54,7 +54,7 @@ fib.s
|
||||
|
||||
Different output kinds accumulate in `.scriptc/`; rebuilding a kind updates its file.
|
||||
|
||||
The IR and LLVM source outputs require only Node. On supported macOS, Linux, and Windows hosts, assembly and object outputs use the matching native helper installed with scriptc and do not invoke an external compiler, archiver, linker, or SDK. The object is a relocatable program object with undefined <code>scr_*</code> runtime symbols and a required <code>scr_runtime_abi_v6</code> marker, not a standalone library. External consumption is experimental and requires the exact runtime version reported by <code>--print=native-link-info</code>. That option still writes the object, performs no link, and prints a versioned JSON recipe with the target, <code>main</code> entry, installed precompiled runtime pack, FFI inputs, and system libraries. It never reports private scriptc cache paths. See <a href="/native-objects">Native Program Objects</a> for complete C-driver and direct-linker examples. <code>--emit=exe</code> is the default. LLVM builds emit the program object through the helper and link precompiled runtime objects; runtime development with AddressSanitizer compiles an instrumented C runtime.
|
||||
The IR and LLVM source outputs require only Node. On supported macOS, Linux, and Windows hosts, assembly and object outputs use the matching native helper installed with scriptc and do not invoke an external compiler, archiver, linker, or SDK. The object is a relocatable program object with undefined <code>scr_*</code> runtime symbols and a required <code>scr_runtime_abi_v7</code> marker, not a standalone library. External consumption is experimental and requires the exact runtime version reported by <code>--print=native-link-info</code>. That option still writes the object, performs no link, and prints a versioned JSON recipe with the target, <code>main</code> entry, installed precompiled runtime pack, FFI inputs, and system libraries. It never reports private scriptc cache paths. See <a href="/native-objects">Native Program Objects</a> for complete C-driver and direct-linker examples. <code>--emit=exe</code> is the default. LLVM builds emit the program object through the helper and link precompiled runtime objects; runtime development with AddressSanitizer compiles an instrumented C runtime.
|
||||
|
||||
## scriptc run
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ Without `-o`, artifacts are written to `.scriptc/` next to the input. Different
|
||||
|
||||
### External linking
|
||||
|
||||
Program objects define `main` and leave selected `scr_*` runtime functions undefined. A strong reference to `scr_runtime_abi_v6` checks runtime compatibility at link time.
|
||||
Program objects define `main` and leave selected `scr_*` runtime functions undefined. A strong reference to `scr_runtime_abi_v7` checks runtime compatibility at link time.
|
||||
|
||||
`--print=native-link-info` emits the program object and a JSON recipe containing the runtime pack and link order for external builds. This object ABI is experimental and requires an exact runtime version. See [Native Program Objects](/docs/native-objects) for examples.
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ A declaration in the standard library does not guarantee that an API compiles. U
|
||||
- **FileHandle and descriptor I/O:** Static reads and writes accept Buffer and Uint8Array windows, numeric positions, and UTF-8 strings. FileHandle reads also accept options records and allocate a buffer when omitted. Vector I/O, truncation, syncing, and permission changes compile. FileHandle streams, `readLines`, ownership changes, timestamps, and bigint Stats remain unsupported. Dynamic descriptor I/O accepts other ArrayBuffer views and Buffer encodings; read positions outside the safe integer range are refused. File operations complete synchronously before their callbacks or promise continuations. Descriptor permission changes on Windows throw `ENOSYS`.
|
||||
- **Crypto:** Hashing, HMAC, PBKDF2, and HKDF support MD5, SHA-1, SHA-224, SHA-256, SHA-384, and SHA-512. Scrypt supports its default parameters, numeric options, aliases, and memory limit. Both KDFs accept strings, Buffers, numeric typed arrays, DataViews, and fixed-length ArrayBuffers. KeyObject inputs, other digests, and cipher or public-key operations remain unsupported. Native derivations finish before their deferred callbacks, so computation blocks the caller.
|
||||
- **Exit status:** Numeric `process.exitCode = value` writes in statement position set the implicit exit status, which `process.exit()` uses when called without an argument. Reads, resets, and assignments used as values are unsupported.
|
||||
- **Builtin module lookup:** `process.getBuiltinModule()` exposes native subsets of `path`, `path/posix`, `path/win32`, `os`, and main-thread `worker_threads` metadata. Stored module references and functions retain identity. Other modules and exports, including `Worker`, throw `SC2020`. `process.versions` is a shared dictionary containing `node` and `openssl`; other component versions are absent unless defined by the program.
|
||||
- **Builtin module lookup:** `process.getBuiltinModule()` exposes native subsets of `path`, `path/posix`, `path/win32`, `os`, and current-thread `worker_threads` metadata. Stored module references and functions retain identity. Other modules and exports, including stored `Worker` constructors, throw `SC2020`. Direct `Worker` imports use the native worker support described below. `process.versions` is a shared dictionary containing `node` and `openssl`; other component versions are absent unless defined by the program.
|
||||
- **ArrayBuffers and typed arrays:** Fixed-length ArrayBuffers support shared typed-array and DataView storage. Resizing, transferring, Float16 arrays, and BigInt arrays are unsupported. Numeric typed arrays and Buffers preserve their kind and shared storage through `unknown`, including when nested in records and arrays. Numeric typed-array constructors can be stored and used with `new`, including constructors read from an array's `constructor` property. JavaScript numeric-array parameters and returns preserve typed-array storage.
|
||||
- **Prototypes:** Named properties and methods on program class prototypes support replacement, inheritance, and deletion. Bare prototype reflection, accessor replacement, and prototype storage on local, generic, mixin, or runtime-provided classes are unsupported.
|
||||
- **Intl.Segmenter:** Default Unicode grapheme segmentation, iteration, and `containing()` compile. Locale negotiation, word and sentence segmentation, `resolvedOptions()`, and detached methods are unsupported.
|
||||
@@ -94,6 +94,16 @@ Synchronous and promise filesystem APIs with trailing arguments that affect beha
|
||||
|
||||
Native error-first filesystem callbacks can be stored and passed to platform adapters. Reads support Buffer and UTF-8 results; writes preserve string flags and modes; copy supports regular files and recursive directories. Reads and writes reject already-aborted signals, but native file operations complete synchronously before the deferred callback, so cancellation cannot interrupt I/O in progress. Symbolic-link copy, copy filters and clone modes, recursive glob patterns, glob exclusions, recursive directory reads, and Dirent results report a native limitation.
|
||||
|
||||
### Native worker threads
|
||||
|
||||
`node:worker_threads` supports native threads in static executables. Worker entry points must resolve at build time: use a source-relative `new URL("./worker.ts", import.meta.url)`, the current module URL, an absolute path, or CommonJS `__filename`. Each worker initializes its own module state, event loop, environment snapshot, and reference-counted heap. Nested workers inherit their parent's environment snapshot. Supported constructor options are `workerData` and `argv`; runtime paths, `eval`, loaders, custom stdio, shared environments, resource limits, and transfer lists are unsupported. Worker argument strings containing null bytes are also unsupported.
|
||||
|
||||
Workers support `online`, `message`, `error`, and `exit` events, `postMessage`, `terminate`, `ref`, and `unref`. The parent port supports message listeners, `postMessage`, `close`, `start`, `ref`, `unref`, and `hasRef`. Messages clone primitives, plain object graphs, arrays, errors, ArrayBuffers, typed arrays, and DataViews, preserving cycles and aliases. Functions, proxies, other native handles, and nonempty transfer lists are refused. SharedArrayBuffers share their backing storage while retaining separate wrappers in each thread.
|
||||
|
||||
SharedArrayBuffer storage is fixed-length. Atomics supports integer `load`, `store`, `exchange`, `compareExchange`, `add`, `sub`, `and`, `or`, and `xor` over 8-, 16-, and 32-bit integer arrays. `wait` and `notify` use Int32Array views; BigInt atomics, `waitAsync`, and growable buffers are unsupported. `terminate` unwinds at runtime checkpoints, including loops and parked atomic waits, without running JavaScript catch or finally blocks. Blocking native calls finish before the next checkpoint.
|
||||
|
||||
Workers are unsupported in dynamic executables, WASI, compiled libraries, and executables that use foreign-thread native callbacks. Ordinary callbacks that run on their owning script thread remain supported. Shared byte storage cannot expose unmanaged FFI pointers; copy it into an ordinary Buffer before passing it to native code.
|
||||
|
||||
### Child-process workers
|
||||
|
||||
`child_process.fork()` requires a worker path resolved at build time from `new URL("./worker.ts", import.meta.url)` or `fileURLToPath(new URL("./worker.ts", import.meta.url))`. Never-reassigned `const` aliases and statically resolvable template parts are accepted. The worker and its imports are embedded in the executable, which re-executes itself to start that module. Worker arguments begin at `process.argv[2]`.
|
||||
|
||||
@@ -8,7 +8,7 @@ title: "Native Program Objects"
|
||||
|
||||
The external object ABI is **experimental**. Its `scr_*` function and data surface may change before 1.0, so consumers must use the exact runtime-pack version reported by the same compiler installation. This is stricter than semver compatibility.
|
||||
|
||||
The object intentionally leaves its selected runtime symbols undefined. It also holds a strong reference to `scr_runtime_abi_v6`, which the matching runtime defines. Linking an object against a runtime with another ABI marker fails at link time with the missing versioned symbol; it cannot become a latent runtime incompatibility.
|
||||
The object intentionally leaves its selected runtime symbols undefined. It also holds a strong reference to `scr_runtime_abi_v7`, which the matching runtime defines. Linking an object against a runtime with another ABI marker fails at link time with the missing versioned symbol; it cannot become a latent runtime incompatibility.
|
||||
|
||||
## Machine-readable link information
|
||||
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
"schemaVersion": 3,
|
||||
"nodeVersion": "24.15.0",
|
||||
"nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119",
|
||||
"artifactVersion": "3e2ea82c2a32fb0f484c",
|
||||
"artifactVersion": "32b04265083c89d00ea3",
|
||||
"rowCount": 3662
|
||||
}
|
||||
|
||||
@@ -1346,9 +1346,9 @@
|
||||
"apiEntries": 66,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 4,
|
||||
"partial": 24,
|
||||
"refused": 0,
|
||||
"not-implemented": 62,
|
||||
"not-implemented": 42,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -96738,9 +96738,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -96766,9 +96766,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -96794,9 +96794,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -96822,9 +96822,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -96850,9 +96850,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -96878,9 +96878,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -96906,9 +96906,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -96934,9 +96934,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -96962,9 +96962,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -97018,9 +97018,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "refused",
|
||||
@@ -97130,9 +97130,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -97158,9 +97158,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -97242,9 +97242,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -97270,9 +97270,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -97495,7 +97495,7 @@
|
||||
},
|
||||
"static": {
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
@@ -97550,9 +97550,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -97578,9 +97578,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -97606,9 +97606,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -97662,9 +97662,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -97943,7 +97943,7 @@
|
||||
},
|
||||
"static": {
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
@@ -97970,9 +97970,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -98055,7 +98055,7 @@
|
||||
},
|
||||
"static": {
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
@@ -98110,9 +98110,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"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"tiers": {
|
||||
"static": {
|
||||
"replace-refusal": 42,
|
||||
"verify-gap": 2447,
|
||||
"audit-partial": 717,
|
||||
"verify-gap": 2427,
|
||||
"audit-partial": 737,
|
||||
"classify": 2,
|
||||
"implement": 80
|
||||
},
|
||||
@@ -107505,12 +107505,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.MessagePort",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -107540,12 +107544,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.port.close",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-port-lifetime.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -107575,12 +107581,17 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.port.postMessage",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-message-graphs.ts",
|
||||
"tests/corpus/native-worker-shared-views.ts",
|
||||
"tests/corpus/native-worker-clone-errors.ts",
|
||||
"tests/corpus/native-worker-delayed-messages.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -107610,12 +107621,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.port.hasRef",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-port-lifetime.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -107645,12 +107658,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.port.ref",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-port-lifetime.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -107680,12 +107695,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.port.start",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-port-lifetime.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -107715,12 +107732,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.port.unref",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-port-lifetime.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -107750,12 +107769,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.'close'",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-messages.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -107783,12 +107805,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.'message'",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-messages.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -107849,12 +107874,17 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.Worker",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-messages.ts",
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-environment.ts",
|
||||
"tests/corpus/native-worker-roots/main.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "refused",
|
||||
@@ -107981,12 +108011,17 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.worker.postMessage",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-message-graphs.ts",
|
||||
"tests/corpus/native-worker-shared-views.ts",
|
||||
"tests/corpus/native-worker-clone-errors.ts",
|
||||
"tests/corpus/native-worker-delayed-messages.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -108014,12 +108049,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.worker.ref",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -108113,12 +108150,17 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.worker.terminate",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-terminate-loop.ts",
|
||||
"tests/corpus/native-worker-terminate-wait.ts",
|
||||
"tests/corpus/native-worker-terminate-suspended.ts",
|
||||
"tests/corpus/native-worker-loop-cancellation.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -108146,12 +108188,14 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.worker.unref",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -108416,9 +108460,11 @@
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "surface-manifest:node-builtin.worker_threads.threadId",
|
||||
"source": "compiler-feature:worker_threads.threadId",
|
||||
"tests": [
|
||||
"tests/corpus/957-builtins-namespace.ts"
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
@@ -108482,12 +108528,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.'error'",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-references.ts",
|
||||
"tests/corpus/native-worker-error.ts",
|
||||
"tests/corpus/native-worker-exit-listeners.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -108515,12 +108565,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.'exit'",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-references.ts",
|
||||
"tests/corpus/native-worker-error.ts",
|
||||
"tests/corpus/native-worker-exit-listeners.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -108548,12 +108602,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.'message'",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-references.ts",
|
||||
"tests/corpus/native-worker-error.ts",
|
||||
"tests/corpus/native-worker-exit-listeners.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -108614,12 +108672,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.'online'",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-references.ts",
|
||||
"tests/corpus/native-worker-error.ts",
|
||||
"tests/corpus/native-worker-exit-listeners.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -108956,9 +109018,11 @@
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "surface-manifest:node-builtin.worker_threads.isMainThread",
|
||||
"source": "compiler-feature:worker_threads.isMainThread",
|
||||
"tests": [
|
||||
"tests/corpus/957-builtins-namespace.ts"
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
@@ -108989,12 +109053,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.parentPort",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -109098,9 +109166,11 @@
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "surface-manifest:node-builtin.worker_threads.threadId",
|
||||
"source": "compiler-feature:worker_threads.threadId",
|
||||
"tests": [
|
||||
"tests/corpus/957-builtins-namespace.ts"
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
@@ -109164,12 +109234,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:worker_threads",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:worker_threads.worker_threads.workerData",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
|
||||
@@ -1467,9 +1467,9 @@
|
||||
"apiEntries": 66,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 4,
|
||||
"partial": 24,
|
||||
"refused": 0,
|
||||
"not-implemented": 62,
|
||||
"not-implemented": 42,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -125858,7 +125858,22 @@
|
||||
"status": "partial",
|
||||
"evidence": "derived:descendants",
|
||||
"tests": [
|
||||
"tests/corpus/957-builtins-namespace.ts"
|
||||
"tests/corpus/native-worker-clone-errors.ts",
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-delayed-messages.ts",
|
||||
"tests/corpus/native-worker-environment.ts",
|
||||
"tests/corpus/native-worker-error.ts",
|
||||
"tests/corpus/native-worker-exit-listeners.ts",
|
||||
"tests/corpus/native-worker-loop-cancellation.ts",
|
||||
"tests/corpus/native-worker-message-graphs.ts",
|
||||
"tests/corpus/native-worker-messages.ts",
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-references.ts",
|
||||
"tests/corpus/native-worker-roots/main.ts",
|
||||
"tests/corpus/native-worker-shared-views.ts",
|
||||
"tests/corpus/native-worker-terminate-loop.ts",
|
||||
"tests/corpus/native-worker-terminate-suspended.ts",
|
||||
"tests/corpus/native-worker-terminate-wait.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
@@ -126188,8 +126203,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.MessagePort",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -126218,8 +126238,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.port.close",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-port-lifetime.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -126248,8 +126271,14 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.port.postMessage",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-message-graphs.ts",
|
||||
"tests/corpus/native-worker-shared-views.ts",
|
||||
"tests/corpus/native-worker-clone-errors.ts",
|
||||
"tests/corpus/native-worker-delayed-messages.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -126332,8 +126361,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.port.hasRef",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-port-lifetime.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -126362,8 +126394,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.port.ref",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-port-lifetime.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -126392,8 +126427,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.port.start",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-port-lifetime.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -126422,8 +126460,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.port.unref",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-port-lifetime.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -126452,8 +126493,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.'close'",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-messages.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -126479,8 +126524,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.'message'",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-messages.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -126533,8 +126582,14 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.Worker",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-messages.ts",
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-environment.ts",
|
||||
"tests/corpus/native-worker-roots/main.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "refused",
|
||||
@@ -126641,8 +126696,14 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.worker.postMessage",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-message-graphs.ts",
|
||||
"tests/corpus/native-worker-shared-views.ts",
|
||||
"tests/corpus/native-worker-clone-errors.ts",
|
||||
"tests/corpus/native-worker-delayed-messages.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -126668,8 +126729,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.worker.ref",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -126749,8 +126813,14 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.worker.terminate",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-terminate-loop.ts",
|
||||
"tests/corpus/native-worker-terminate-wait.ts",
|
||||
"tests/corpus/native-worker-terminate-suspended.ts",
|
||||
"tests/corpus/native-worker-loop-cancellation.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -126776,8 +126846,11 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.worker.unref",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -126996,9 +127069,11 @@
|
||||
},
|
||||
"static": {
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:node-builtin.worker_threads.threadId",
|
||||
"evidence": "compiler-feature:worker_threads.threadId",
|
||||
"tests": [
|
||||
"tests/corpus/957-builtins-namespace.ts"
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
@@ -127055,8 +127130,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.'error'",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-references.ts",
|
||||
"tests/corpus/native-worker-error.ts",
|
||||
"tests/corpus/native-worker-exit-listeners.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -127082,8 +127162,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.'exit'",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-references.ts",
|
||||
"tests/corpus/native-worker-error.ts",
|
||||
"tests/corpus/native-worker-exit-listeners.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -127109,8 +127194,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.'message'",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-references.ts",
|
||||
"tests/corpus/native-worker-error.ts",
|
||||
"tests/corpus/native-worker-exit-listeners.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -127163,8 +127253,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.'online'",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-references.ts",
|
||||
"tests/corpus/native-worker-error.ts",
|
||||
"tests/corpus/native-worker-exit-listeners.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-implemented",
|
||||
@@ -127446,9 +127541,11 @@
|
||||
},
|
||||
"static": {
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:node-builtin.worker_threads.isMainThread",
|
||||
"evidence": "compiler-feature:worker_threads.isMainThread",
|
||||
"tests": [
|
||||
"tests/corpus/957-builtins-namespace.ts"
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
@@ -127478,8 +127575,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.parentPort",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
@@ -127569,9 +127671,11 @@
|
||||
},
|
||||
"static": {
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:node-builtin.worker_threads.threadId",
|
||||
"evidence": "compiler-feature:worker_threads.threadId",
|
||||
"tests": [
|
||||
"tests/corpus/957-builtins-namespace.ts"
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
@@ -127628,8 +127732,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:worker_threads"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:worker_threads.worker_threads.workerData",
|
||||
"tests": [
|
||||
"tests/corpus/native-worker-commonjs.cjs",
|
||||
"tests/corpus/native-worker-port-lifetime.ts",
|
||||
"tests/corpus/native-worker-references.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
"node-builtin.util.isDeepStrictEqual": ["tests/corpus/node24-deep-equality.ts", "tests/corpus/node24-deep-equality.cjs", "tests/corpus/node24-deep-equality-properties.js", "tests/fixtures/node-types/deep-equality.ts", "packages/compiler/test/deep-equality.test.ts"],
|
||||
"node-builtin.util.types": ["tests/corpus/node24-util-types.ts", "tests/corpus/node24-util-types.js", "tests/corpus/node24-util-types.cjs", "tests/fixtures/node-types/util-types.ts"],
|
||||
"node-builtin.util": ["tests/corpus/2440-console-inspect-args.ts", "tests/corpus/2912-promisify-fs-readfile.ts"],
|
||||
"node-builtin.worker_threads": ["tests/corpus/957-builtins-namespace.ts"],
|
||||
"node-builtin.worker_threads": ["tests/corpus/957-builtins-namespace.ts", "tests/corpus/native-worker-messages.ts", "tests/corpus/native-worker-references.ts"],
|
||||
"node-builtin.zlib": ["tests/corpus/1404-zlib-crypto-bytes.ts", "tests/corpus/2921-zlib-callbacks.ts", "tests/corpus/3115-zlib-static-compression-levels.ts"],
|
||||
"stdlib.fetch": ["tests/harness/fetch-conformance.test.ts"],
|
||||
"stdlib.map": ["tests/corpus/typed-collection-iterators.ts", "tests/corpus/typed-collection-iterator-references.ts", "tests/corpus/537-map-iter-drains.ts"],
|
||||
@@ -118,6 +118,14 @@
|
||||
"stdlib.abort": ["tests/harness/fetch-conformance.test.ts"]
|
||||
},
|
||||
"features": [
|
||||
{"chapter": "worker_threads", "symbols": ["Worker"], "status": "partial", "evidence": ["tests/corpus/native-worker-messages.ts", "tests/corpus/native-worker-commonjs.cjs", "tests/corpus/native-worker-environment.ts", "tests/corpus/native-worker-roots/main.ts"]},
|
||||
{"chapter": "worker_threads", "symbols": ["MessagePort", "parentPort", "worker_threads.workerData", "isMainThread", "threadId"], "status": "partial", "evidence": ["tests/corpus/native-worker-commonjs.cjs", "tests/corpus/native-worker-port-lifetime.ts", "tests/corpus/native-worker-references.ts"]},
|
||||
{"chapter": "worker_threads", "symbols": ["worker.postMessage", "port.postMessage"], "status": "partial", "evidence": ["tests/corpus/native-worker-message-graphs.ts", "tests/corpus/native-worker-shared-views.ts", "tests/corpus/native-worker-clone-errors.ts", "tests/corpus/native-worker-delayed-messages.ts"]},
|
||||
{"chapter": "worker_threads", "symbols": ["worker.terminate"], "status": "partial", "evidence": ["tests/corpus/native-worker-terminate-loop.ts", "tests/corpus/native-worker-terminate-wait.ts", "tests/corpus/native-worker-terminate-suspended.ts", "tests/corpus/native-worker-loop-cancellation.ts"]},
|
||||
{"chapter": "worker_threads", "symbols": ["worker.ref", "worker.unref"], "status": "partial", "evidence": ["tests/corpus/native-worker-references.ts"]},
|
||||
{"chapter": "worker_threads", "symbols": ["port.close", "port.start", "port.ref", "port.unref", "port.hasRef"], "status": "partial", "evidence": ["tests/corpus/native-worker-port-lifetime.ts"]},
|
||||
{"chapter": "worker_threads", "parent": "Class: `Worker`", "signatures": ["Event: `'online'`", "Event: `'message'`", "Event: `'error'`", "Event: `'exit'`"], "status": "partial", "evidence": ["tests/corpus/native-worker-references.ts", "tests/corpus/native-worker-error.ts", "tests/corpus/native-worker-exit-listeners.ts"]},
|
||||
{"chapter": "worker_threads", "parent": "Class: `MessagePort`", "signatures": ["Event: `'message'`", "Event: `'close'`"], "status": "partial", "evidence": ["tests/corpus/native-worker-port-lifetime.ts", "tests/corpus/native-worker-messages.ts"]},
|
||||
{"chapter": "util", "symbols": ["util.types.isAnyArrayBuffer", "util.types.isArrayBuffer", "util.types.isArrayBufferView", "util.types.isDataView", "util.types.isTypedArray", "util.types.isUint8Array", "util.types.isUint8ClampedArray", "util.types.isUint16Array", "util.types.isUint32Array", "util.types.isInt8Array", "util.types.isInt16Array", "util.types.isInt32Array", "util.types.isFloat32Array", "util.types.isFloat64Array", "util.types.isMap", "util.types.isSet", "util.types.isWeakMap", "util.types.isWeakSet", "util.types.isRegExp", "util.types.isNativeError", "util.types.isPromise", "util.types.isProxy", "util.types.isAsyncFunction", "util.types.isGeneratorFunction", "util.types.isGeneratorObject"], "status": "partial", "evidence": ["tests/corpus/node24-util-types.ts", "tests/corpus/node24-util-types.js", "tests/corpus/node24-util-types.cjs", "tests/fixtures/node-types/util-types.ts"]},
|
||||
{ "chapter": "util", "symbols": ["util.styleText"], "status": "partial", "evidence": ["tests/corpus/node24-style-text.ts", "tests/corpus/node24-style-text-validation.js", "tests/corpus/node24-style-text-environment.js", "tests/corpus/node24-style-text.cjs", "tests/fixtures/node-types/style-text.ts"] },
|
||||
{ "chapter": "util", "symbols": ["util.isDeepStrictEqual"], "status": "partial", "evidence": ["tests/corpus/node24-deep-equality.ts", "tests/corpus/node24-deep-equality.cjs", "tests/corpus/node24-deep-equality-properties.js", "tests/fixtures/node-types/deep-equality.ts", "packages/compiler/test/deep-equality.test.ts"] },
|
||||
|
||||
@@ -34,7 +34,7 @@ Builds use a bounded persistent cache. Unchanged source can reuse the validated
|
||||
- `scriptc run <file.ts>` — compile and run
|
||||
- `scriptc coverage <file.ts>` — what compiles statically, and why the rest doesn't
|
||||
|
||||
`scriptc build app.ts --emit=ir|llvm|asm|obj` selects serialized typed IR, textual LLVM IR, target assembly, or a relocatable program object as the one primary artifact. `--emit=exe` is the default. Assembly/object emission uses the matching helper on supported macOS, Linux, and Windows hosts (and produces WASI artifacts when that target is selected). Objects retain undefined `scr_*` runtime references plus the `scr_runtime_abi_v6` compatibility marker; they are not library archives. External consumption is experimental and requires the exact matching runtime. `scriptc build app.ts --print=native-link-info -o app.o` prints the versioned JSON target/runtime/link recipe without performing a link. `--emit=asm|obj --sanitize` is rejected until ASan pipeline parity is available.
|
||||
`scriptc build app.ts --emit=ir|llvm|asm|obj` selects serialized typed IR, textual LLVM IR, target assembly, or a relocatable program object as the one primary artifact. `--emit=exe` is the default. Assembly/object emission uses the matching helper on supported macOS, Linux, and Windows hosts (and produces WASI artifacts when that target is selected). Objects retain undefined `scr_*` runtime references plus the `scr_runtime_abi_v7` compatibility marker; they are not library archives. External consumption is experimental and requires the exact matching runtime. `scriptc build app.ts --print=native-link-info -o app.o` prints the versioned JSON target/runtime/link recipe without performing a link. `--emit=asm|obj --sanitize` is rejected until ASan pipeline parity is available.
|
||||
|
||||
For embedder-hosted modules that are not installed npm packages, coverage can map an exact bare specifier to a local declaration with repeatable `--external-types <specifier=file.d.ts>` options. This is analysis-only: the types unblock application measurement, while runtime module uses remain reported as blockers.
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ describe.runIf(supported)("macOS arm64 native link info", () => {
|
||||
minimum_os: "14.0",
|
||||
},
|
||||
program: { object, entry_symbol: "main" },
|
||||
runtime_abi: { version: 6, marker: "scr_runtime_abi_v6" },
|
||||
runtime_abi: { version: 7, marker: "scr_runtime_abi_v7" },
|
||||
runtime_pack: {
|
||||
kind: "precompiled",
|
||||
package: "@scriptc/runtime-darwin-arm64",
|
||||
@@ -165,6 +165,6 @@ describe.runIf(supported)("macOS arm64 native link info", () => {
|
||||
() => null,
|
||||
(failure: { stderr?: string }) => failure,
|
||||
);
|
||||
expect(error?.stderr).toContain("scr_runtime_abi_v6");
|
||||
expect(error?.stderr).toContain("scr_runtime_abi_v7");
|
||||
});
|
||||
});
|
||||
|
||||
+52
-14
@@ -601,7 +601,7 @@ interface BufferConstructor {
|
||||
from(data: string, encoding?: BufferEncoding): Buffer;
|
||||
/* The ArrayBuffer form is the VIEW construction: x.buffer with an
|
||||
* optional byte offset/length shares x's storage (the subarray rule). */
|
||||
from(data: ArrayBuffer, byteOffset?: number, length?: number): Buffer;
|
||||
from(data: ArrayBuffer | SharedArrayBuffer, byteOffset?: number, length?: number): Buffer;
|
||||
from(data: Uint8Array | readonly number[]): Buffer;
|
||||
alloc(size: number, fill?: string | number | Uint8Array, encoding?: BufferEncoding): Buffer;
|
||||
compare(buf1: Uint8Array, buf2: Uint8Array): number;
|
||||
@@ -4096,23 +4096,61 @@ declare module "node:dns" {
|
||||
export * from "dns";
|
||||
}
|
||||
|
||||
/* node:worker_threads — the MAIN-THREAD slice only. A compiled binary is
|
||||
* always the main thread (no JS-engine thread machinery exists), so
|
||||
* isMainThread lowers to `true` and threadId to 0 — Node's main-thread
|
||||
* answers exactly. Worker itself is declared surface without a lowering:
|
||||
* constructing one fences at the site; the CLASS as a value participates
|
||||
* in identity-only flows. */
|
||||
/* node:worker_threads — native workers use statically compiled entry points.
|
||||
* Lowering and runtime diagnostics define the supported options and methods. */
|
||||
declare module "worker_threads" {
|
||||
export const isMainThread: boolean;
|
||||
export const isInternalThread: boolean;
|
||||
export const threadId: number;
|
||||
export class Worker {
|
||||
constructor(filename: string | URL, options?: unknown);
|
||||
on(event: string, listener: (...args: unknown[]) => void): void;
|
||||
postMessage(value: unknown): void;
|
||||
terminate(): Promise<number>;
|
||||
export interface WorkerOptions {
|
||||
workerData?: unknown;
|
||||
transferList?: readonly unknown[];
|
||||
argv?: readonly unknown[];
|
||||
name?: string;
|
||||
[option: string]: unknown;
|
||||
}
|
||||
export const parentPort: unknown;
|
||||
export const workerData: unknown;
|
||||
export class Worker {
|
||||
constructor(filename: string | URL, options?: WorkerOptions);
|
||||
readonly threadId: number;
|
||||
on(event: "online", listener: () => void): this;
|
||||
on(event: "message", listener: (value: any) => void): this;
|
||||
on(event: "error", listener: (error: Error) => void): this;
|
||||
on(event: "exit", listener: (code: number) => void): this;
|
||||
on(event: string, listener: (...args: any[]) => void): this;
|
||||
once(event: string, listener: (...args: any[]) => void): this;
|
||||
off(event: string, listener: (...args: any[]) => void): this;
|
||||
addListener(event: string, listener: (...args: any[]) => void): this;
|
||||
removeListener(event: string, listener: (...args: any[]) => void): this;
|
||||
prependListener(event: string, listener: (...args: any[]) => void): this;
|
||||
prependOnceListener(event: string, listener: (...args: any[]) => void): this;
|
||||
postMessage(value: unknown, transferList?: readonly unknown[]): void;
|
||||
ref(): void;
|
||||
unref(): void;
|
||||
terminate(): Promise<number>;
|
||||
removeAllListeners(event?: string): this;
|
||||
listenerCount(event: string, listener?: (...args: any[]) => void): number;
|
||||
}
|
||||
export class MessagePort {
|
||||
on(event: "message", listener: (value: any) => void): this;
|
||||
on(event: "close", listener: () => void): this;
|
||||
on(event: string, listener: (...args: any[]) => void): this;
|
||||
once(event: string, listener: (...args: any[]) => void): this;
|
||||
off(event: string, listener: (...args: any[]) => void): this;
|
||||
addListener(event: string, listener: (...args: any[]) => void): this;
|
||||
removeListener(event: string, listener: (...args: any[]) => void): this;
|
||||
prependListener(event: string, listener: (...args: any[]) => void): this;
|
||||
prependOnceListener(event: string, listener: (...args: any[]) => void): this;
|
||||
postMessage(value: unknown, transferList?: readonly unknown[]): void;
|
||||
start(): void;
|
||||
close(callback?: () => void): void;
|
||||
ref(): void;
|
||||
unref(): void;
|
||||
hasRef(): boolean;
|
||||
removeAllListeners(event?: string): this;
|
||||
listenerCount(event: string, listener?: (...args: any[]) => void): number;
|
||||
}
|
||||
export const parentPort: MessagePort | null;
|
||||
export const workerData: any;
|
||||
}
|
||||
declare module "node:worker_threads" {
|
||||
export * from "worker_threads";
|
||||
|
||||
@@ -19,7 +19,7 @@ import { computeTraced as llvmTraced } from "./llvm/shapes.js";
|
||||
const loc = { file: "cycles.ts", start: 0, end: 1 };
|
||||
function module(): IrModule {
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [{ name: "main", locals: [], params: [], returnType: VOID, body: [], loc }],
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { NativeLinkFeatures } from "./native-link-info.js";
|
||||
export function executableLinkFeatures(mod: IrModule, dynamic: boolean): NativeLinkFeatures {
|
||||
const features = moduleRuntimeFeatures(mod);
|
||||
return {
|
||||
...(mod.workers ? { workers: true } : {}),
|
||||
dynamic,
|
||||
regex: features.regex,
|
||||
copying: features.copying,
|
||||
|
||||
@@ -46,6 +46,7 @@ export function emitByteNumber(
|
||||
little: string,
|
||||
fallback: () => LlValue,
|
||||
): LlValue {
|
||||
if (host.mod.workers) return fallback();
|
||||
const B = host.B,
|
||||
size = host.sizeType;
|
||||
const slow = B.newLabel("bytes.number.slow"),
|
||||
|
||||
@@ -9,7 +9,7 @@ const loc = { file: "class-membership.ts", start: 0, end: 1 };
|
||||
test("leaf layouts keep direct teardown while recursive layouts share the depth budget", () => {
|
||||
const linked = { kind: "object" as const, className: "Linked" };
|
||||
const mod: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [{ name: "main", params: [], returnType: VOID, locals: [], body: [], loc }],
|
||||
@@ -61,7 +61,7 @@ function graph() {
|
||||
{ name: "FlippedString", base: "PairString", genericOf: "Flipped" },
|
||||
];
|
||||
const mod: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [],
|
||||
@@ -95,7 +95,7 @@ test.each(["i32", "i64"])(
|
||||
expect(helper).toContain("label %ordinary");
|
||||
expect(helper).toContain("or i1");
|
||||
const ordinary: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [],
|
||||
|
||||
@@ -661,6 +661,7 @@ export function emitClassObjDefs(
|
||||
classObjs: Map<string, { nameSym: string }>,
|
||||
fnByName: Map<string, IrFunction>,
|
||||
llType: (t: IrType) => string,
|
||||
threadLocal = false,
|
||||
): string[] {
|
||||
const out: string[] = [];
|
||||
for (const [className, { nameSym }] of classObjs) {
|
||||
@@ -744,7 +745,7 @@ export function emitClassObjDefs(
|
||||
` call void @${mangleFunction(`%${className}.constructor`)}(${[`ptr %r`, ...ctorArgs].join(", ")})`,
|
||||
` ret ptr %o`,
|
||||
`}`,
|
||||
`@${mangleClassObj(className)} = internal global %ScrClassObj ` +
|
||||
`@${mangleClassObj(className)} = internal ${threadLocal ? "thread_local " : ""}global %ScrClassObj ` +
|
||||
`{ ${host.sizeType} -1, ${host.sizeType} ${intervalMeta.pre}, ${host.sizeType} ${intervalMeta.post}, ptr @${mangleCtorThunk(className)}, ptr ${nameSym}, ${host.sizeType} 0, ${host.sizeType} ${meta.def.jsLength ?? 0}, ptr null, ptr null } ; class ${className}`,
|
||||
``,
|
||||
);
|
||||
|
||||
@@ -66,7 +66,7 @@ function fixture(): IrModule {
|
||||
loc,
|
||||
};
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [predicate, consumer, entry],
|
||||
|
||||
@@ -59,7 +59,7 @@ function moduleFor(
|
||||
loc,
|
||||
};
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [
|
||||
@@ -184,7 +184,7 @@ const text = (obj: IrExpr): IrExpr => ({
|
||||
|
||||
function work(expr: IrExpr, parameter: IrType, boxed = false, tdz = false): string {
|
||||
const module: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: receiverLoc.file,
|
||||
entry: "main",
|
||||
records: [
|
||||
@@ -355,7 +355,7 @@ function sharedFieldModule(prefixes: IrType[][], fieldType: IrType): IrModule {
|
||||
],
|
||||
}));
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
records,
|
||||
|
||||
@@ -678,7 +678,8 @@ export class LlEmitter {
|
||||
}
|
||||
const mt = computeMayThrow(mod);
|
||||
this.mayThrow = mt.fns;
|
||||
this.indirectMayThrow = mt.indirect;
|
||||
if (mod.workers) for (const fn of mod.functions) this.mayThrow.add(fn.name);
|
||||
this.indirectMayThrow = mt.indirect || mod.workers === true;
|
||||
for (const cls of mod.classes ?? []) {
|
||||
for (const m of cls.methods ?? []) {
|
||||
if (this.mayThrow.has(`%${cls.name}.${m}`)) this.mayThrowMethods.add(m);
|
||||
@@ -835,7 +836,9 @@ export class LlEmitter {
|
||||
if (adapter.global !== null)
|
||||
globals.push(`@${adapter.global} = internal thread_local global ptr null`);
|
||||
if (adapter.table !== null) {
|
||||
globals.push(`@${adapter.table} = internal global %ScrFfiTable zeroinitializer`);
|
||||
globals.push(
|
||||
`@${adapter.table} = internal ${this.mod.workers ? "thread_local " : ""}global %ScrFfiTable zeroinitializer`,
|
||||
);
|
||||
}
|
||||
const params = cb.params.flatMap((param, i): string[] => {
|
||||
if (isFfiContextParam(param)) return [`ptr %ctx`];
|
||||
@@ -1382,7 +1385,8 @@ export class LlEmitter {
|
||||
// with neither keep the atexit path, so their listener timing is
|
||||
// unchanged.
|
||||
const hasRefGlobals = globals.some((g) => isRefCounted(g.type)) || fnValueProps.length > 0;
|
||||
const inlineExitListeners = usesEvents && (hasRefGlobals || this.ffiHasRetainedCallback);
|
||||
const inlineExitListeners =
|
||||
usesEvents && (this.mod.workers || hasRefGlobals || this.ffiHasRetainedCallback);
|
||||
this.declare(`declare i32 @scr_exit_code_hint_get()`);
|
||||
if (inlineExitListeners) {
|
||||
this.declare(`declare void @scr_run_exit_listeners(double)`);
|
||||
@@ -1564,11 +1568,11 @@ export class LlEmitter {
|
||||
// scr_runtime.h's natural alignment: kind at 8, f64 at 16, b at 24,
|
||||
// payload at 32).
|
||||
`%ScrCaught = type { ${this.sizeType}, i32, double, i8, ptr, ptr, ptr, ptr }`,
|
||||
// ScrBytes { rc, len, elem(i32+pad), data, backing }. Indexed
|
||||
// ScrBytes { rc, len, elem(i32+pad), data, backing, brands, shared }. Indexed
|
||||
// typed-array access GEPs through this directly: the IR type already
|
||||
// fixes elem, so the hot path needs neither a runtime kind load nor
|
||||
// the generic scr_bytes_get/set call.
|
||||
`%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8 }`,
|
||||
`%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, ptr }`,
|
||||
// The capture box { rc, kind, obj_retain, obj_release, obj_trace,
|
||||
// slot } — TDZ reads peek the payload slot (offset 40) directly.
|
||||
`%ScrBox = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
|
||||
@@ -1589,12 +1593,14 @@ export class LlEmitter {
|
||||
// objects compiled with -DSCR_THREAD_INSTANCES. Immutable interned
|
||||
// data (string literals, unit arms, template arrays, vtables) stays
|
||||
// shared.
|
||||
const tl = this.mod.lib?.threadInstances === true ? "thread_local " : "";
|
||||
const tl =
|
||||
this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : "";
|
||||
out.push(
|
||||
``,
|
||||
`@scr_error_vts = external ${tl}global [${RUNTIME_ERROR_CLASSES.size} x %ScrVt]`,
|
||||
`declare void @scr_init()`,
|
||||
`declare void @scr_lib_init(i32, ptr)`,
|
||||
...(this.mod.workers ? [`declare void @scr_runtime_workers_v7()`] : []),
|
||||
...(this.runtimeAbiMarker && this.mod.lib === undefined
|
||||
? [`declare void @${RUNTIME_ABI_MARKER}()`]
|
||||
: []),
|
||||
@@ -1792,7 +1798,7 @@ export class LlEmitter {
|
||||
// The runtime stream vtables' intervals (streamVtStampLines) — the
|
||||
// emitter story: instanceof and dynamic teardown dispatch through
|
||||
// them.
|
||||
out.push(`@${iv.vt} = external global %ScrVt`);
|
||||
out.push(`@${iv.vt} = external ${tl}global %ScrVt`);
|
||||
stamps.push(
|
||||
` store ${this.sizeType} ${iv.pre}, ptr getelementptr inbounds (%ScrVt, ptr @${iv.vt}, i64 0, i32 0)`,
|
||||
` store ${this.sizeType} ${iv.post}, ptr getelementptr inbounds (%ScrVt, ptr @${iv.vt}, i64 0, i32 1) ; ${iv.lib}`,
|
||||
@@ -1832,9 +1838,10 @@ export class LlEmitter {
|
||||
return out;
|
||||
}
|
||||
out.push(
|
||||
`define i32 @${this.wasi ? "__main_argc_argv" : "main"}(i32 %argc, ptr %argv) ${FN_ATTRS} {`,
|
||||
`define i32 @${this.mod.workers ? "sc_context_entry" : this.wasi ? "__main_argc_argv" : "main"}(i32 %argc, ptr %argv) ${FN_ATTRS} {`,
|
||||
`entry:`,
|
||||
...(this.runtimeAbiMarker ? [` call void @${RUNTIME_ABI_MARKER}()`] : []),
|
||||
...(this.mod.workers ? [` call void @scr_runtime_workers_v7()`] : []),
|
||||
` call void @scr_init()`,
|
||||
...stamps,
|
||||
// Event-surface programs (signal/exit listeners) fill the loop's
|
||||
@@ -1973,6 +1980,26 @@ export class LlEmitter {
|
||||
...(hasNoInlineRecordClone ? [`attributes #2 = { noinline sanitize_address }`] : []),
|
||||
``,
|
||||
);
|
||||
if (this.mod.workers) {
|
||||
out.push(
|
||||
`declare void @scr_context_cleanup()`,
|
||||
`declare i32 @scr_worker_argc()`,
|
||||
`declare ptr @scr_worker_argv()`,
|
||||
`define i32 @sc_worker_entry(i32 %root) ${FN_ATTRS} {`,
|
||||
`entry:`,
|
||||
` %argc = call i32 @scr_worker_argc()`,
|
||||
` %argv = call ptr @scr_worker_argv()`,
|
||||
` %result = call i32 @sc_context_entry(i32 %argc, ptr %argv)`,
|
||||
` ret i32 %result`,
|
||||
`}`,
|
||||
`define i32 @main(i32 %argc, ptr %argv) ${FN_ATTRS} {`,
|
||||
`entry:`,
|
||||
` %result = call i32 @sc_context_entry(i32 %argc, ptr %argv)`,
|
||||
` call void @scr_context_cleanup()`,
|
||||
` ret i32 %result`,
|
||||
`}`,
|
||||
);
|
||||
}
|
||||
out.push(...this.fieldAliasMetadata());
|
||||
if (this.debug !== null) out.push(this.debug.render());
|
||||
return out;
|
||||
@@ -2400,7 +2427,7 @@ export class LlEmitter {
|
||||
ret === "void" ? ` ${call}` : ` %r = ${call}`,
|
||||
ret === "void" ? ` ret void` : ` ret ${ret} %r`,
|
||||
`}`,
|
||||
`@${mangleFnClosure(name)} = internal global %ScrClosure { ${this.sizeType} -1, ptr @${mangleWrapper(name)}, ${this.sizeType} 0, ptr null, i32 ${(fn.generator ? 1 : 0) + (fn.async ? 2 : 0) + (fn.ownsPrototype ? 4 : 0)} }`,
|
||||
`@${mangleFnClosure(name)} = internal ${this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : ""}global %ScrClosure { ${this.sizeType} -1, ptr @${mangleWrapper(name)}, ${this.sizeType} 0, ptr null, i32 ${(fn.generator ? 1 : 0) + (fn.async ? 2 : 0) + (fn.ownsPrototype ? 4 : 0)} }`,
|
||||
``,
|
||||
);
|
||||
}
|
||||
@@ -2962,6 +2989,20 @@ export class LlEmitter {
|
||||
* using the result). Callers own the surrounding pending branch; a
|
||||
* `throw` unwinds unconditionally. */
|
||||
private emitUnwind(): void {
|
||||
if (this.mod.workers && this.tryStack.length > 0) {
|
||||
this.declare(`declare zeroext i1 @scr_context_stopping()`);
|
||||
const stopped = this.B.tmp();
|
||||
this.B.line(`${stopped} = call zeroext i1 @scr_context_stopping()`);
|
||||
const stop = this.B.newLabel("context.stop");
|
||||
const ordinary = this.B.newLabel("context.throw");
|
||||
this.B.condBr(stopped, stop, ordinary);
|
||||
this.B.startBlock(stop);
|
||||
const handlers = this.tryStack;
|
||||
this.tryStack = [];
|
||||
this.emitUnwind();
|
||||
this.tryStack = handlers;
|
||||
this.B.startBlock(ordinary);
|
||||
}
|
||||
const target = this.tryStack[this.tryStack.length - 1];
|
||||
const frameDepth = target?.frameDepth ?? 0;
|
||||
const scopeDepth = target?.scopeDepth ?? 0;
|
||||
@@ -3023,6 +3064,37 @@ export class LlEmitter {
|
||||
B.startBlock(lk);
|
||||
}
|
||||
|
||||
private workerLoopBudget(): string | null {
|
||||
if (!this.mod.workers) return null;
|
||||
const slot = this.B.slot();
|
||||
this.B.entryAllocas.push(`${slot} = alloca i32 ; worker cancellation budget`);
|
||||
this.B.line(`store i32 0, ptr ${slot}`);
|
||||
return slot;
|
||||
}
|
||||
|
||||
private emitWorkerLoopCheck(slot: string | null): void {
|
||||
if (slot === null) return;
|
||||
const B = this.B;
|
||||
const budget = B.tmp(),
|
||||
due = B.tmp(),
|
||||
remaining = B.tmp();
|
||||
const poll = B.newLabel("worker.poll"),
|
||||
next = B.newLabel("worker.next");
|
||||
B.line(`${budget} = load i32, ptr ${slot}`);
|
||||
B.line(`${remaining} = sub i32 ${budget}, 1`);
|
||||
B.line(`store i32 ${remaining}, ptr ${slot}`);
|
||||
B.line(`${due} = icmp eq i32 ${budget}, 0`);
|
||||
B.condBr(due, poll, next);
|
||||
B.startBlock(poll);
|
||||
B.line(`store i32 63, ptr ${slot}`);
|
||||
// Calls and throwing operations retain their own checks. Amortize the
|
||||
// extra cancellation poll across bounded loop batches so scalar work
|
||||
// does not cross the runtime ABI on every iteration.
|
||||
this.emitPendingCheck();
|
||||
B.br(next);
|
||||
B.startBlock(next);
|
||||
}
|
||||
|
||||
/** Moves an already-evaluated value into the runtime's exception cell —
|
||||
* the `throw` statement's kind dispatch (stmts.ts's), shared with
|
||||
* every synthetic thrower. Ownership of a refcounted payload must have
|
||||
@@ -3999,6 +4071,7 @@ export class LlEmitter {
|
||||
B.line(`call void @scr_stack_enter(ptr %source_frame, ptr ${this.cstr(frame)})`);
|
||||
B.returnEpilogue = `call void @scr_stack_leave(ptr %source_frame)`;
|
||||
}
|
||||
if (this.mod.workers) this.emitPendingCheck();
|
||||
this.emitStmts(fn.body);
|
||||
// Implicit exit of a void function: release the function scope unless
|
||||
// the body already terminated its final block (return, or a throw
|
||||
@@ -4612,11 +4685,13 @@ export class LlEmitter {
|
||||
break;
|
||||
}
|
||||
case "while": {
|
||||
const workerBudget = this.workerLoopBudget();
|
||||
const lc = B.newLabel("loop.c");
|
||||
const lb = B.newLabel("loop.b");
|
||||
const le = B.newLabel("loop.e");
|
||||
B.br(lc);
|
||||
B.startBlock(lc);
|
||||
this.emitWorkerLoopCheck(workerBudget);
|
||||
B.condBr(this.emitCondition(s.cond), lb, le);
|
||||
B.startBlock(lb);
|
||||
this.jumpTargets.push({
|
||||
@@ -4635,12 +4710,14 @@ export class LlEmitter {
|
||||
break;
|
||||
}
|
||||
case "doWhile": {
|
||||
const workerBudget = this.workerLoopBudget();
|
||||
// Body first (runs at least once); continue jumps to the CONDITION.
|
||||
const lb = B.newLabel("loop.b");
|
||||
const lc = B.newLabel("loop.c");
|
||||
const le = B.newLabel("loop.e");
|
||||
B.br(lb);
|
||||
B.startBlock(lb);
|
||||
this.emitWorkerLoopCheck(workerBudget);
|
||||
this.jumpTargets.push({
|
||||
kind: "loop",
|
||||
brkLabel: le,
|
||||
@@ -4802,8 +4879,10 @@ export class LlEmitter {
|
||||
};
|
||||
const lu = s.update || capturedLets.length > 0 ? B.newLabel("loop.u") : lc;
|
||||
freshenBindings();
|
||||
const workerBudget = this.workerLoopBudget();
|
||||
B.br(lc);
|
||||
B.startBlock(lc);
|
||||
this.emitWorkerLoopCheck(workerBudget);
|
||||
if (integerLoop && integerSlot) {
|
||||
const receiver = this.emitStableReceiver(integerLoop.limitReceiver, []);
|
||||
const lenPtr = B.tmp();
|
||||
@@ -4903,8 +4982,10 @@ export class LlEmitter {
|
||||
const lb = B.newLabel("fof.b");
|
||||
const lu = B.newLabel("fof.u");
|
||||
const le = B.newLabel("fof.e");
|
||||
const workerBudget = this.workerLoopBudget();
|
||||
B.br(lc);
|
||||
B.startBlock(lc);
|
||||
this.emitWorkerLoopCheck(workerBudget);
|
||||
const inBounds = B.tmp();
|
||||
let cur: string;
|
||||
if (snapshot && cursor && scratch) {
|
||||
|
||||
@@ -189,6 +189,12 @@ export function emitBytesGet(
|
||||
inBounds = false,
|
||||
): LlValue {
|
||||
const B = host.B;
|
||||
if (host.mod.workers) {
|
||||
host.declare(`declare double @scr_bytes_get(ptr, double)`);
|
||||
const result = B.tmp();
|
||||
B.line(`${result} = call double @scr_bytes_get(ptr ${receiver}, double ${index.name})`);
|
||||
return { name: result, type: F64 };
|
||||
}
|
||||
const idx = host.emitBytesIndex(receiver, index, expr, inBounds);
|
||||
const data = host.emitBytesData(receiver);
|
||||
const p = B.tmp();
|
||||
@@ -317,6 +323,11 @@ export function emitBytesSet(
|
||||
inBounds = false,
|
||||
): void {
|
||||
const B = host.B;
|
||||
if (host.mod.workers) {
|
||||
host.declare(`declare void @scr_bytes_set(ptr, double, double)`);
|
||||
B.line(`call void @scr_bytes_set(ptr ${receiver}, double ${index.name}, double ${value.name})`);
|
||||
return;
|
||||
}
|
||||
// Invalid integer-indexed writes are ignored after evaluating the RHS.
|
||||
const done = B.newLabel("bytes.store.done");
|
||||
const idx = emitBytesIndex(host, receiver, index, indexExpr, done, inBounds);
|
||||
|
||||
@@ -459,6 +459,11 @@ export function emitCallExpr(
|
||||
}
|
||||
case "bytes":
|
||||
case "mutable-bytes": {
|
||||
if (host.mod.workers) {
|
||||
host.declare(`declare void @scr_bytes_require_unshared(ptr)`);
|
||||
B.line(`call void @scr_bytes_require_unshared(ptr ${arg.name})`);
|
||||
host.emitPendingCheck();
|
||||
}
|
||||
const lenPtr = B.tmp();
|
||||
const len = B.tmp();
|
||||
const dataPtr = B.tmp();
|
||||
|
||||
@@ -17,7 +17,7 @@ function fixture(): IrModule {
|
||||
loc,
|
||||
});
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "__main",
|
||||
functions: [{ name: "__main", params: [], returnType: VOID, locals: [], body, loc }],
|
||||
|
||||
@@ -20,7 +20,7 @@ function sharedGraph(roots: number): IrModule {
|
||||
unionId: `input${index}`,
|
||||
}));
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
records: [
|
||||
|
||||
@@ -21,6 +21,13 @@ export function emitLlvmLayouts(
|
||||
): LlvmLayouts {
|
||||
const records = emitRecordShapes(host, mod);
|
||||
const classShapes = emitClassShapes(host, mod, classes);
|
||||
const objects = emitClassObjDefs(host, classes, classObjects, functions, llType);
|
||||
const objects = emitClassObjDefs(
|
||||
host,
|
||||
classes,
|
||||
classObjects,
|
||||
functions,
|
||||
llType,
|
||||
mod.workers === true || mod.lib?.threadInstances === true,
|
||||
);
|
||||
return { records, classes: classShapes, classObjects: objects };
|
||||
}
|
||||
|
||||
@@ -47,6 +47,13 @@ export function finishRuntimeCall(
|
||||
/** Strings inline their bytes; Buffer views carry a separate data pointer. */
|
||||
export function rawBytes(host: LlvmEmitterContext, value: LlValue): AbiArgument[] {
|
||||
const B = host.B;
|
||||
if (host.mod.workers && value.type.kind === "bytes") {
|
||||
// Native consumers can block or invoke callbacks. Keep a local copy of
|
||||
// shared storage alive in the ownership frame instead of escaping its
|
||||
// raw pointer or holding the shared-memory lock across the call.
|
||||
const name = callRuntime(host, "scr_bytes_local_copy", "ptr", [ptr(value.name)]);
|
||||
value = host.own({ name, type: value.type });
|
||||
}
|
||||
const lengthPtr = B.tmp(),
|
||||
length = B.tmp(),
|
||||
data = B.tmp();
|
||||
|
||||
@@ -12,6 +12,7 @@ import { LlvmUnsupportedError } from "./unsupported.js";
|
||||
import type { LlvmEmitterContext, LibCallExpr, LibCallPrefix, LlValue } from "./expr-context.js";
|
||||
import { LIB_FN_SYMS, USES_TIMERS_LIB_FNS } from "./lib-shared.js";
|
||||
import { DYN_KIND } from "./dyn.js";
|
||||
import { rawBytes } from "./lib-abi.js";
|
||||
|
||||
export function emitAssertInspectLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
|
||||
const B = host.B;
|
||||
@@ -234,32 +235,10 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
: "scr_http_request";
|
||||
let callArgs = head.map((a) => `${host.llType(a.type)} ${a.name}`);
|
||||
if (isTlsOptions) {
|
||||
const ca = args[8]!;
|
||||
const caLenPtr = B.tmp();
|
||||
const caLen = B.tmp();
|
||||
let caData: string;
|
||||
if (ca.type.kind === "string") {
|
||||
caData = B.tmp();
|
||||
B.line(`${caLenPtr} = getelementptr inbounds %ScrStr, ptr ${ca.name}, i64 0, i32 1`);
|
||||
B.line(`${caLen} = load ${host.sizeType}, ptr ${caLenPtr}`);
|
||||
B.line(
|
||||
`${caData} = getelementptr inbounds i8, ptr ${ca.name}, i64 ${host.abiOffset(24, 12)}`,
|
||||
);
|
||||
} else if (ca.type.kind === "bytes" && ca.type.elem === "u8") {
|
||||
const caDataPtr = B.tmp();
|
||||
caData = B.tmp();
|
||||
B.line(
|
||||
`${caLenPtr} = getelementptr inbounds i8, ptr ${ca.name}, i64 ${host.abiOffset(8, 4)}`,
|
||||
);
|
||||
B.line(`${caLen} = load ${host.sizeType}, ptr ${caLenPtr}`);
|
||||
B.line(
|
||||
`${caDataPtr} = getelementptr inbounds i8, ptr ${ca.name}, i64 ${host.abiOffset(24, 12)}`,
|
||||
);
|
||||
B.line(`${caData} = load ptr, ptr ${caDataPtr}`);
|
||||
} else {
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} CA is not a string or Buffer`);
|
||||
}
|
||||
callArgs = [...callArgs.slice(0, 8), `ptr ${caData}`, `${host.sizeType} ${caLen}`];
|
||||
callArgs = [
|
||||
...callArgs.slice(0, 8),
|
||||
...rawBytes(host, args[8]!).map((arg) => `${arg.type} ${arg.name}`),
|
||||
];
|
||||
host.declare(
|
||||
`declare ptr @scr_https_request(ptr, double, ptr, ptr, double, ptr, i1 zeroext, i1 zeroext, ptr, ${host.sizeType}, ptr, ptr)`,
|
||||
);
|
||||
@@ -364,6 +343,7 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
|
||||
case "stdin":
|
||||
return host.emitProcessLibCall(e);
|
||||
case "module":
|
||||
case "worker":
|
||||
return host.emitGenericLibCall(e);
|
||||
case "error":
|
||||
case "regex":
|
||||
@@ -394,6 +374,7 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
|
||||
case "weakMap":
|
||||
case "weakSet":
|
||||
case "ffi":
|
||||
case "sharedArrayBuffer":
|
||||
case "arrayBuffer":
|
||||
case "util":
|
||||
case "bigint":
|
||||
|
||||
@@ -37,6 +37,8 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"bytes.construct": "scr_bytes_construct",
|
||||
"bytes.instanceOf": "scr_bytes_instanceof",
|
||||
"arrayBuffer.constructor": "scr_array_buffer_constructor",
|
||||
"sharedArrayBuffer.new": "scr_shared_array_buffer_new",
|
||||
"sharedArrayBuffer.is": "scr_shared_array_buffer_is",
|
||||
"arrayBuffer.new": "scr_array_buffer_new",
|
||||
"ffi.argument": "scr_ffi_argument",
|
||||
"ffi.memoryModule": "scr_ffi_memory_module",
|
||||
@@ -716,7 +718,15 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"fs.chownSync": "scr_fs_chown",
|
||||
"fs.mkdirModeSync": "scr_fs_mkdir_mode",
|
||||
"fs.mkdirRecursiveModeSync": "scr_fs_mkdir_recursive_mode",
|
||||
"atomics.op": "scr_atomics_op",
|
||||
"atomics.notify": "scr_atomics_notify",
|
||||
"atomics.wait": "scr_atomics_wait",
|
||||
"worker.new": "scr_worker_new",
|
||||
"worker.isMainThread": "scr_context_is_main",
|
||||
"worker.threadId": "scr_context_thread_number",
|
||||
"worker.root": "scr_worker_root",
|
||||
"worker.data": "scr_worker_data",
|
||||
"worker.parentPort": "scr_worker_parent_port",
|
||||
"process.stdinDestroy": "scr_process_stdin_destroy",
|
||||
"process.stdinSetRawMode": "scr_process_stdin_set_raw_mode",
|
||||
// node:events EventEmitter (scr_events_emitter.c): receivers borrowed,
|
||||
@@ -1060,6 +1070,8 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
};
|
||||
|
||||
export const USES_TIMERS_LIB_FNS = new Set<string>([
|
||||
"worker.new",
|
||||
"worker.parentPort",
|
||||
"process.stdio",
|
||||
"fetch.start",
|
||||
"fetch.input",
|
||||
|
||||
@@ -124,7 +124,7 @@ function fixture(): IrModule {
|
||||
],
|
||||
};
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
records: [{ id: "cell", fields: [{ name: "x", type: F64 }] }],
|
||||
|
||||
@@ -81,7 +81,7 @@ function fixture(): IrModule {
|
||||
],
|
||||
};
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
unions: [{ id: "optional", arms: [STRING, UNDEFINED_T] }],
|
||||
|
||||
@@ -66,7 +66,7 @@ function fixture(value: IrType = record, key: IrType = F64): IrModule {
|
||||
],
|
||||
};
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
records: [{ id: "cell", fields: [{ name: "x", type: F64 }] }],
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#undef NDEBUG
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
_Thread_local int64_t foreign = 13;
|
||||
extern void set_owner(int64_t owner);
|
||||
extern int64_t get_owner(void);
|
||||
extern int64_t next_value(void);
|
||||
|
||||
enum { THREADS = 4, ITERATIONS = 10000 };
|
||||
static atomic_uint ready;
|
||||
|
||||
static void *run(void *argument) {
|
||||
int64_t id = (int64_t)(uintptr_t)argument;
|
||||
assert(get_owner() == 0);
|
||||
set_owner(id);
|
||||
atomic_fetch_add_explicit(&ready, 1, memory_order_release);
|
||||
while (atomic_load_explicit(&ready, memory_order_acquire) != THREADS) sched_yield();
|
||||
for (int64_t i = 0; i < ITERATIONS; i++) {
|
||||
assert(get_owner() == id);
|
||||
assert(next_value() == 20 + 2 * i);
|
||||
if (i % 64 == 0) sched_yield();
|
||||
}
|
||||
assert(foreign == 13 + ITERATIONS);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
pthread_t threads[THREADS];
|
||||
for (uintptr_t i = 0; i < THREADS; i++)
|
||||
assert(pthread_create(&threads[i], NULL, run, (void *)(i + 1)) == 0);
|
||||
for (unsigned i = 0; i < THREADS; i++) assert(pthread_join(threads[i], NULL) == 0);
|
||||
assert(get_owner() == 0 && next_value() == 20 && foreign == 14);
|
||||
puts("thread state stays isolated across compiled partitions");
|
||||
}
|
||||
@@ -76,14 +76,84 @@ test("splits generated LLVM functions and promotes only private cross-shard defi
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test("thread-local program state conservatively keeps the single-TU path", () => {
|
||||
test("thread-local definitions and declarations preserve their storage model across shards", () => {
|
||||
const tls = SAMPLE.replace(
|
||||
"@hidden_value = internal global i64 7",
|
||||
"@hidden_value = internal thread_local global i64 7",
|
||||
);
|
||||
expect(splitLlvmProgram(tls, { minimumBytes: 0, targetBytes: 64 * 1024 })).toBeNull();
|
||||
const split = splitLlvmProgram(tls, { minimumBytes: 0, targetBytes: 64 * 1024 })!;
|
||||
expect(split.shards[0]!.source).toContain("@hidden_value = hidden thread_local global i64 7");
|
||||
for (const shard of split.shards.slice(1)) {
|
||||
expect(shard.source).toContain("@hidden_value = external hidden thread_local global i64");
|
||||
}
|
||||
});
|
||||
|
||||
test.skipIf(process.platform === "win32")(
|
||||
"split and merged native modules keep globals independent on concurrent threads",
|
||||
async () => {
|
||||
const source = `%State = type { i64, i64 }
|
||||
@state = internal thread_local global %State { i64 7, i64 0 }
|
||||
@foreign = external thread_local global i64
|
||||
|
||||
define void @set_owner(i64 %owner) {
|
||||
entry:
|
||||
store i64 %owner, ptr getelementptr inbounds (%State, ptr @state, i64 0, i32 1)
|
||||
ret void
|
||||
}
|
||||
|
||||
define i64 @get_owner() {
|
||||
entry:
|
||||
%owner = load i64, ptr getelementptr inbounds (%State, ptr @state, i64 0, i32 1)
|
||||
ret i64 %owner
|
||||
}
|
||||
|
||||
define i64 @next_value() {
|
||||
entry:
|
||||
%current = load i64, ptr @state
|
||||
%next = add i64 %current, 1
|
||||
store i64 %next, ptr @state
|
||||
%other = load i64, ptr @foreign
|
||||
%after = add i64 %other, 1
|
||||
store i64 %after, ptr @foreign
|
||||
%result = add i64 %current, %other
|
||||
ret i64 %result
|
||||
}
|
||||
`;
|
||||
const split = splitLlvmProgram(source, { minimumBytes: 0, targetBytes: 64 * 1024 })!;
|
||||
expect(split.shards.length).toBeGreaterThan(2);
|
||||
const dir = await mkdtemp(join(tmpdir(), "scriptc-llvm-thread-state-"));
|
||||
scratch.push(dir);
|
||||
const objects: string[] = [];
|
||||
for (const shard of split.shards) {
|
||||
const path = join(dir, shard.name);
|
||||
const object = `${path}.o`;
|
||||
await writeFile(path, shard.source);
|
||||
execFileSync("clang", ["-Wno-override-module", "-O2", "-c", path, "-o", object]);
|
||||
objects.push(object);
|
||||
}
|
||||
const combined = join(dir, "combined.o");
|
||||
execFileSync("ld", ["-r", ...objects, "-o", combined]);
|
||||
if (process.platform === "linux") {
|
||||
const keep = join(dir, "keep.txt");
|
||||
await writeFile(keep, "set_owner\nget_owner\nnext_value\n");
|
||||
execFileSync("objcopy", [`--keep-global-symbols=${keep}`, combined]);
|
||||
}
|
||||
const binary = join(dir, "thread-state");
|
||||
execFileSync("clang", [
|
||||
"-std=c11",
|
||||
"-O2",
|
||||
"-pthread",
|
||||
join(import.meta.dirname, "split-thread-state.test.c"),
|
||||
combined,
|
||||
"-o",
|
||||
binary,
|
||||
]);
|
||||
expect(execFileSync(binary, { encoding: "utf8", timeout: 10000 })).toBe(
|
||||
"thread state stays isolated across compiled partitions\n",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("shared metadata bounds shard growth while preserving every definition", () => {
|
||||
const names = Array.from({ length: 120 }, (_, i) => `metadata_pad_${i}`);
|
||||
const body = names
|
||||
|
||||
@@ -202,12 +202,6 @@ export function splitLlvmProgram(
|
||||
if (/^@.+\s+=\s+external\s+/.test(line)) preamble.push(line);
|
||||
else return null;
|
||||
} else {
|
||||
// LLVM textual external TLS declarations compile on Darwin, but GNU
|
||||
// ELF clang emits the cross-shard reference as non-TLS and `ld -r`
|
||||
// correctly refuses the mismatch. Thread-instanced libraries keep
|
||||
// the canonical single-TU path until the producer can preserve TLS
|
||||
// model metadata portably across object formats.
|
||||
if (/\bthread_local(?:\([^)]*\))?\s+global\b/.test(line)) return null;
|
||||
globals.push(parsed);
|
||||
sawDefinition = true;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ function fixture(arm: IrType = record): IrModule {
|
||||
body: [ret(narrow(ref("value"), arm))],
|
||||
};
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
records: [{ id: "cell", fields: [{ name: "x", type: F64 }] }],
|
||||
|
||||
@@ -17,7 +17,7 @@ const concat = (left: IrExpr, right: IrExpr): IrExpr => ({
|
||||
|
||||
function module(value: IrExpr): IrModule {
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [
|
||||
|
||||
@@ -68,7 +68,7 @@ function fn(name: string, names: string[], value: IrExpr): IrFunction {
|
||||
}
|
||||
function mod(...functions: IrFunction[]): IrModule {
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [
|
||||
|
||||
@@ -8,7 +8,7 @@ const string = (value: string): IrExpr => ({ kind: "strLit", value, type: STRING
|
||||
|
||||
function emit(type: IrType, tests: (IrExpr | null)[], pointerBits: 32 | 64 = 64): string {
|
||||
const mod: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [
|
||||
|
||||
@@ -26,7 +26,7 @@ function fn(name: string, body: IrStmt[], locals: IrLocal[] = [local]): IrFuncti
|
||||
return { name, body, locals, params: [], returnType: VOID, loc };
|
||||
}
|
||||
function moduleWith(...functions: IrFunction[]): IrModule {
|
||||
return { irVersion: 13, sourceFile: loc.file, entry: "caller", functions };
|
||||
return { irVersion: 14, sourceFile: loc.file, entry: "caller", functions };
|
||||
}
|
||||
const assignment: IrStmt = { kind: "assign", localId: local.id, value, loc };
|
||||
const expression: IrExpr = { kind: "assignExpr", localId: local.id, value, type: F64, loc };
|
||||
|
||||
@@ -207,7 +207,8 @@ export function computeMayThrow(mod: IrModule): { fns: Set<string>; indirect: bo
|
||||
// A native callback may run arbitrary scriptc code. With retained
|
||||
// descriptors any manifest binding may pump a previously stored
|
||||
// callback, so every FFI call is conservatively a checkpoint.
|
||||
if (callbackFfiImports.has(rec.import) || manifestHasRetainedCallback) f.throws = true;
|
||||
if (mod.workers || callbackFfiImports.has(rec.import) || manifestHasRetainedCallback)
|
||||
f.throws = true;
|
||||
break;
|
||||
case "bytesNew": {
|
||||
// The size form (`new Uint8Array(n)`) throws Node's "Invalid
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
import type { NativeTargetSpec } from "./targets.js";
|
||||
|
||||
export interface NativeLinkFeatures {
|
||||
workers?: boolean;
|
||||
dynamic: boolean;
|
||||
regex: boolean;
|
||||
copying: boolean;
|
||||
|
||||
@@ -48,6 +48,8 @@ export interface CcOptions {
|
||||
* static runtime selection; executable section GC may still remove
|
||||
* unreachable static-runtime code. */
|
||||
dynamic?: boolean;
|
||||
/** Isolated native Worker contexts; every runtime object must use TLS. */
|
||||
workers?: boolean;
|
||||
/** The program contains a regex construct (index.ts detects it on the
|
||||
* IR): compiles scr_regex.c and links the vendored libregexp — as cached
|
||||
* standalone objects in static builds, from the engine archive under
|
||||
|
||||
@@ -127,6 +127,8 @@ export async function compileCInternal(
|
||||
const sanitize = opts.sanitize ?? false;
|
||||
const optimization = opts.optimization ?? "release";
|
||||
const dynamic = opts.dynamic ?? false;
|
||||
if (opts.workers && (dynamic || targetPlatform(resolveCc()) === "wasi"))
|
||||
throw new Error("worker threads require a native static executable");
|
||||
const regex = opts.regex ?? false;
|
||||
// fetch's implementation switch: the default is the NATIVE bridge
|
||||
// (scr_fetch.c over scr_net + scr_tls + scr_http's client parser +
|
||||
@@ -547,6 +549,7 @@ export async function compileCInternal(
|
||||
...debugFlags,
|
||||
...driver.targetArgs,
|
||||
...threadArgs,
|
||||
...(opts.workers ? ["-DSCR_WORKERS"] : []),
|
||||
...(sanitize
|
||||
? ["-O1", "-fsanitize=address", "-DSCR_RC_AUDIT"]
|
||||
: [optimization === "dev" ? "-O0" : "-O2"]),
|
||||
@@ -569,6 +572,11 @@ export async function compileCInternal(
|
||||
"-I",
|
||||
rtDir,
|
||||
...runtimeSources.map((f) => rt(join(rtDir, f))),
|
||||
...(opts.workers
|
||||
? ["scr_worker.c", "scr_worker_events.c", "scr_mailbox.c", "scr_message.c"].map((f) =>
|
||||
rt(join(rtDir, f)),
|
||||
)
|
||||
: []),
|
||||
...(opts.copying ? [rt(join(rtDir, "scr_copying.c"))] : []),
|
||||
...(opts.fileHandle ? [rt(join(rtDir, "scr_file_handle.c"))] : []),
|
||||
// win32 targets compile the libc-shim TU (stpcpy, arc4random_buf,
|
||||
@@ -589,9 +597,9 @@ export async function compileCInternal(
|
||||
...(opts.inspect
|
||||
? [rt(join(rtDir, "scr_inspect.c")), rt(join(rtDir, "scr_console_native.c"))]
|
||||
: []),
|
||||
...(opts.dynInvoke || nativeFetch ? [rt(join(rtDir, "scr_dyn_invoke.c"))] : []),
|
||||
...(opts.dynInvoke || opts.workers || nativeFetch ? [rt(join(rtDir, "scr_dyn_invoke.c"))] : []),
|
||||
...(opts.dc ? [rt(join(rtDir, "scr_dc.c"))] : []),
|
||||
...(opts.dynAsync || opts.dynInvoke || opts.dc || opts.fileHandle || nativeFetch
|
||||
...(opts.dynAsync || opts.dynInvoke || opts.workers || opts.dc || opts.fileHandle || nativeFetch
|
||||
? [rt(join(rtDir, "scr_async_dyn.c"))]
|
||||
: []),
|
||||
// The zlib UNIT (scr_zlib.c) gates on zlib.* IR use; the LINK (system
|
||||
@@ -611,12 +619,12 @@ export async function compileCInternal(
|
||||
// installer exactly then.
|
||||
...(opts.zlib && opts.dynamic ? [rt(join(rtDir, "scr_zlib_island.c"))] : []),
|
||||
...(opts.events ? [rt(join(rtDir, "scr_events.c")), rt(join(rtDir, "scr_readline.c"))] : []),
|
||||
...(opts.emitter ? [rt(join(rtDir, "scr_events_emitter.c"))] : []),
|
||||
...(opts.emitter || opts.workers ? [rt(join(rtDir, "scr_events_emitter.c"))] : []),
|
||||
// The checked-dynamic HANDLE support unit (listener gate + runtime
|
||||
// adapter closures): every referencing unit is one of the emitter or
|
||||
// net families (http implies net), so handle-free binaries keep
|
||||
// their exact size class.
|
||||
...(opts.emitter || net ? [rt(join(rtDir, "scr_dyn_handle.c"))] : []),
|
||||
...(opts.emitter || opts.workers || net ? [rt(join(rtDir, "scr_dyn_handle.c"))] : []),
|
||||
...(opts.symbol ? [rt(join(rtDir, "scr_symbol.c"))] : []),
|
||||
...(opts.assert && opts.bigint ? [rt(join(rtDir, "scr_bigint_assert.c"))] : []),
|
||||
...(opts.qs ? [rt(join(rtDir, "scr_qs.c"))] : []),
|
||||
@@ -753,6 +761,7 @@ export async function compileCInternal(
|
||||
...debugFlags,
|
||||
...driver.targetArgs,
|
||||
...threadArgs,
|
||||
...(opts.workers ? ["-DSCR_WORKERS"] : []),
|
||||
...(sanitize
|
||||
? ["-O1", "-fsanitize=address", "-DSCR_RC_AUDIT"]
|
||||
: [optimization === "dev" ? "-O0" : "-O2"]),
|
||||
@@ -974,6 +983,7 @@ export async function compileCInternal(
|
||||
const linkProbeArgs = [
|
||||
...(sanitize ? ["-fsanitize=address"] : []),
|
||||
...threadArgs,
|
||||
...(opts.workers ? ["-DSCR_WORKERS"] : []),
|
||||
...(targetPlatform(driver) === "win32" ? ["-ladvapi32", "-liphlpapi", "-lws2_32"] : []),
|
||||
...(tls && targetPlatform(driver) === "win32" ? ["-lbcrypt"] : []),
|
||||
...(tlsCa && targetPlatform(driver) === "win32" ? ["-lcrypt32"] : []),
|
||||
|
||||
@@ -37,6 +37,8 @@ export const NATIVE_RECIPE_IMPLEMENTATION_PATHS = [
|
||||
].map((stem) => join(backendDir, `${stem}${extname(implementationPath)}`));
|
||||
|
||||
export const EXECUTABLE_RUNTIME_SOURCES = [
|
||||
"scr_context.c",
|
||||
"scr_shared.c",
|
||||
"scr_number.c",
|
||||
"scr_bigint.c",
|
||||
"scr_string.c",
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* of the link contract: a program object keeps this symbol undefined and the
|
||||
* matching runtime defines it, so incompatible manual links fail before the
|
||||
* program can start. */
|
||||
export const RUNTIME_ABI_VERSION = 6 as const;
|
||||
export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v6" as const;
|
||||
export const RUNTIME_ABI_VERSION = 7 as const;
|
||||
export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v7" as const;
|
||||
|
||||
/** Object consumption is useful today, but the complete scr_* surface may
|
||||
* still change before 1.0. The versioned marker prevents accidental mixing;
|
||||
|
||||
@@ -75,6 +75,7 @@ export interface RuntimePackManifest {
|
||||
}
|
||||
|
||||
export interface RuntimeFeatureSet extends NativeLinkFeatures {
|
||||
workers: boolean;
|
||||
nativeFetch: boolean;
|
||||
netIslandEffective: boolean;
|
||||
netEffective: boolean;
|
||||
@@ -263,6 +264,7 @@ export function effectiveRuntimeFeatures(
|
||||
const tlsCaEffective = features.tlsCa || tlsEffective;
|
||||
return {
|
||||
...features,
|
||||
workers: features.workers === true,
|
||||
nativeFetch,
|
||||
netIslandEffective,
|
||||
netEffective,
|
||||
@@ -297,6 +299,12 @@ function selectVariant(unit: RuntimePackUnit, features: RuntimeFeatureSet): Runt
|
||||
if (selected === undefined) {
|
||||
throw new RuntimePackError(`runtime pack has no variant for ${unit.source}`, "invalid");
|
||||
}
|
||||
if ((features.workers === true) !== selected.defines.includes("SCR_WORKERS")) {
|
||||
throw new RuntimePackError(
|
||||
`runtime pack has an incompatible thread-state variant for ${unit.source}; reinstall the matching runtime package`,
|
||||
"invalid",
|
||||
);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
@@ -346,6 +354,11 @@ export function selectRuntimePackArtifacts(
|
||||
mode: RuntimePackMode = "executable",
|
||||
): RuntimePackArtifacts {
|
||||
const features = effectiveRuntimeFeatures(requested, env);
|
||||
if (
|
||||
features.workers &&
|
||||
(mode !== "executable" || features.dynamic || manifest.target.object_format === "wasm")
|
||||
)
|
||||
throw new RuntimePackError("worker threads require a native static executable", "unsupported");
|
||||
const key: RuntimePackFlavor = mode === "executable" ? flavor : `${mode}-${flavor}`;
|
||||
const selectedFlavor =
|
||||
mode === "library"
|
||||
|
||||
@@ -21,7 +21,7 @@ afterEach(async () =>
|
||||
Promise.all(dirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))),
|
||||
);
|
||||
|
||||
async function fixture(target: NativeTargetSpec) {
|
||||
async function fixture(target: NativeTargetSpec, workers = false) {
|
||||
const root = await mkdtemp(join(tmpdir(), "scriptc-runtime-pack-target-"));
|
||||
dirs.push(root);
|
||||
const packagePath = join(root, "package.json");
|
||||
@@ -52,7 +52,7 @@ async function fixture(target: NativeTargetSpec) {
|
||||
object_format: target.objectFormat,
|
||||
minimum_os: target.minimumOs,
|
||||
},
|
||||
runtime_abi: { version: 6, marker: "scr_runtime_abi_v6" },
|
||||
runtime_abi: { version: 7, marker: "scr_runtime_abi_v7" },
|
||||
compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple },
|
||||
macros: { executable: [], excluded: ["SCR_LIB"], sanitizer: "external-toolchain-required" },
|
||||
flavors: {
|
||||
@@ -62,7 +62,9 @@ async function fixture(target: NativeTargetSpec) {
|
||||
{
|
||||
source: "scr_runtime.c",
|
||||
predicate: true,
|
||||
variants: [{ id: "default", when: {}, defines: [], ...artifact }],
|
||||
variants: [
|
||||
{ id: "default", when: {}, defines: workers ? ["SCR_WORKERS"] : [], ...artifact },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -72,7 +74,9 @@ async function fixture(target: NativeTargetSpec) {
|
||||
{
|
||||
source: "scr_runtime.c",
|
||||
predicate: true,
|
||||
variants: [{ id: "default", when: {}, defines: [], ...artifact }],
|
||||
variants: [
|
||||
{ id: "default", when: {}, defines: workers ? ["SCR_WORKERS"] : [], ...artifact },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -141,3 +145,46 @@ test.each([
|
||||
object_format: target.objectFormat,
|
||||
});
|
||||
});
|
||||
|
||||
test.each([
|
||||
MACOS_X64_TARGET,
|
||||
LINUX_X64_GNU_TARGET,
|
||||
LINUX_ARM64_GNU_TARGET,
|
||||
WINDOWS_X64_MSVC_TARGET,
|
||||
LINUX_X64_MUSL_TARGET,
|
||||
LINUX_ARM64_MUSL_TARGET,
|
||||
])("worker objects require matching thread-state variants for $name", async (target) => {
|
||||
const ordinary = await fixture(target);
|
||||
const worker = await fixture(target, true);
|
||||
const request = {
|
||||
target,
|
||||
features: { ...features, workers: true },
|
||||
optimization: "release" as const,
|
||||
};
|
||||
await expect(
|
||||
loadRuntimePack({ ...request, resolver: () => ordinary.packagePath }),
|
||||
).rejects.toThrow("incompatible thread-state variant");
|
||||
await expect(
|
||||
loadRuntimePack({ ...request, resolver: () => worker.packagePath }),
|
||||
).resolves.toBeDefined();
|
||||
await expect(
|
||||
loadRuntimePack({ ...request, features, resolver: () => worker.packagePath }),
|
||||
).rejects.toThrow("incompatible thread-state variant");
|
||||
});
|
||||
|
||||
test("worker runtime packs refuse dynamic and WASI execution", async () => {
|
||||
for (const [target, dynamic] of [
|
||||
[LINUX_X64_GNU_TARGET, true],
|
||||
[WASM32_WASI_TARGET, false],
|
||||
] as const) {
|
||||
const pack = await fixture(target, true);
|
||||
await expect(
|
||||
loadRuntimePack({
|
||||
target,
|
||||
features: { ...features, workers: true, dynamic },
|
||||
optimization: "release",
|
||||
resolver: () => pack.packagePath,
|
||||
}),
|
||||
).rejects.toThrow("native static executable");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -112,7 +112,7 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) {
|
||||
object_format: target.objectFormat,
|
||||
minimum_os: target.minimumOs,
|
||||
},
|
||||
runtime_abi: { version: 6, marker: "scr_runtime_abi_v6" },
|
||||
runtime_abi: { version: 7, marker: "scr_runtime_abi_v7" },
|
||||
compiler: {
|
||||
command: "clang",
|
||||
identity: "fixture clang",
|
||||
|
||||
@@ -35,7 +35,7 @@ const fetch: IrExpr = {
|
||||
};
|
||||
function module(direct: IrExpr[] = []): IrModule {
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "%main",
|
||||
records: [],
|
||||
|
||||
@@ -161,6 +161,22 @@ test("early dev cache restores the matching dSYM and rejects a missing payload",
|
||||
expect(await readEarlyExecutableCache(f.root, f.options)).toBeNull();
|
||||
});
|
||||
|
||||
test("early executable cache preserves worker runtime selection when only frontend output restores", async () => {
|
||||
const f = await fixture();
|
||||
const tracker = new FrontendInputTracker();
|
||||
tracker.run(() => trackedReadFile(f.source));
|
||||
await publishEarlyExecutableCache(f.root, f.options, {
|
||||
llvmPath: f.llvmPath,
|
||||
irPath: f.irPath,
|
||||
native: { ...native, workers: true },
|
||||
executableRestored: false,
|
||||
frontend: tracker.snapshot(),
|
||||
});
|
||||
const restored = await readEarlyExecutableCache(f.root, f.options);
|
||||
expect(restored?.native.workers).toBe(true);
|
||||
expect(restored?.executableRestored).toBe(false);
|
||||
});
|
||||
|
||||
test("early executable cache misses on source and resolution changes", async () => {
|
||||
const f = await fixture();
|
||||
const tracker = new FrontendInputTracker();
|
||||
|
||||
@@ -48,6 +48,7 @@ export interface EarlyExecutableNativeFeatures {
|
||||
/** Omitted is the historical release posture. */
|
||||
optimization?: "dev";
|
||||
dynamic: boolean;
|
||||
workers?: boolean;
|
||||
regex: boolean;
|
||||
copying: boolean;
|
||||
textDecoderLegacy: boolean;
|
||||
@@ -194,7 +195,9 @@ function validNativeFeatures(value: unknown): value is EarlyExecutableNativeFeat
|
||||
return validSharedNativeFeatures<EarlyExecutableNativeFeatures>(
|
||||
value,
|
||||
BOOLEAN_NATIVE_KEYS,
|
||||
(native) => native.optimization === undefined || native.optimization === "dev",
|
||||
(native) =>
|
||||
(native.optimization === undefined || native.optimization === "dev") &&
|
||||
(native.workers === undefined || typeof native.workers === "boolean"),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { expect, test, vi } from "vitest";
|
||||
import { forkTargetPaths } from "./fork-target.js";
|
||||
import { fallbackDtsPath } from "./dts-paths.js";
|
||||
import { Ts7Host } from "./ts7/program-host.js";
|
||||
import { Ts7Api } from "./ts7/rpc-api.js";
|
||||
import { ModuleKind, ModuleResolutionKind } from "./ts7/enums.js";
|
||||
@@ -10,11 +11,7 @@ import { ModuleKind, ModuleResolutionKind } from "./ts7/enums.js";
|
||||
test("fork discovery batches only candidate bindings and preserves aliases and shadowing", () => {
|
||||
const directory = mkdtempSync(join(tmpdir(), "scriptc-fork-discovery-"));
|
||||
const entry = join(directory, "main.ts");
|
||||
const declarations = join(directory, "builtins.d.ts");
|
||||
writeFileSync(
|
||||
declarations,
|
||||
'declare module "node:child_process" { export function fork(path: unknown): void; }',
|
||||
);
|
||||
const declarations = fallbackDtsPath();
|
||||
writeFileSync(
|
||||
join(directory, "facade.ts"),
|
||||
'export { fork as launch } from "node:child_process";',
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
import { resolve } from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import * as ts from "./ts7/adapter.js";
|
||||
import { fallbackDtsPath, isNodeTypesPath, tsgoPath } from "./dts-paths.js";
|
||||
import { canonicalBuiltinModule } from "./builtin-modules.js";
|
||||
import { moduleSourceCandidates } from "./module-source-candidates.js";
|
||||
|
||||
export function isNodeGlobal(program: ts.Program, expression: ts.Identifier): boolean {
|
||||
const checker = program.getTypeChecker();
|
||||
const symbol = checker.getSymbolAtLocation(expression);
|
||||
const declarations = symbol ? checker.declarationsOf(symbol) : [];
|
||||
return (
|
||||
declarations.length > 0 &&
|
||||
declarations.every((declaration) => {
|
||||
const source = declaration.getSourceFile();
|
||||
return (
|
||||
source.isDeclarationFile &&
|
||||
(tsgoPath(source.fileName) === fallbackDtsPath() || isNodeTypesPath(source.fileName))
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function strip(expr: ts.Expression): ts.Expression {
|
||||
let current = expr;
|
||||
while (
|
||||
@@ -64,7 +81,7 @@ export function isBuiltinMemberImport(
|
||||
/** True when an identifier is the namespace/default binding for one Node
|
||||
* builtin module. JavaScript default imports of CommonJS builtins expose the
|
||||
* same module object as namespace imports, matching lowering's provenance. */
|
||||
function isBuiltinNamespaceImport(
|
||||
export function isBuiltinNamespaceImport(
|
||||
program: ts.Program,
|
||||
ident: ts.Identifier,
|
||||
moduleName: string,
|
||||
@@ -217,7 +234,8 @@ function staticString(
|
||||
if (
|
||||
ts.isNewExpression(current) &&
|
||||
ts.isIdentifier(current.expression) &&
|
||||
current.expression.text === "URL"
|
||||
(isBuiltinMemberImport(program, current.expression, "url", "URL") ||
|
||||
(current.expression.text === "URL" && isNodeGlobal(program, current.expression)))
|
||||
) {
|
||||
const args = current.arguments ?? [];
|
||||
if (args.length < 1 || args.length > 2) return null;
|
||||
|
||||
@@ -2,18 +2,22 @@ import * as ts from "../../ts7/adapter.js";
|
||||
import { type Lowerer } from "../lowerer.js";
|
||||
import { locOf } from "../../program.js";
|
||||
import { F64, type IrExpr, STRING } from "../../../ir/ir.js";
|
||||
import { lowerOptionalArgument } from "../optional-arguments.js";
|
||||
|
||||
/** `Atomics.wait(int32Array, idx, expected, timeoutMs)` — the
|
||||
* synchronous-sleep idiom (RouteStore's
|
||||
* `Atomics.wait(sleepBuffer, 0, 0, ms)`): scriptc has no threads, so
|
||||
* nothing can ever notify a waiter and the spec's behavior for every
|
||||
* compilable program is exactly "compare, then sleep out the timeout"
|
||||
* — "not-equal" when the element differs, a real nanosleep and
|
||||
* "timed-out" otherwise ("ok" is unreachable). The timeout argument is
|
||||
* REQUIRED: without it Node blocks until a notify that cannot exist
|
||||
* here — a certain deadlock, fenced with that explanation. Every other
|
||||
* Atomics member (notify has no one to wake; add/load/... — nothing
|
||||
* races) fences member-qualified. Null for non-Atomics receivers. */
|
||||
const OPERATIONS = [
|
||||
"load",
|
||||
"store",
|
||||
"exchange",
|
||||
"compareExchange",
|
||||
"add",
|
||||
"sub",
|
||||
"and",
|
||||
"or",
|
||||
"xor",
|
||||
];
|
||||
|
||||
/** Familiar integer Atomics over fixed typed storage. Wait queues are keyed
|
||||
* by backing allocation and byte offset, so distinct views address one queue. */
|
||||
export function lowerAtomicsCall(
|
||||
lowerer: Lowerer,
|
||||
call: ts.CallExpression,
|
||||
@@ -22,41 +26,80 @@ export function lowerAtomicsCall(
|
||||
const member = lowerer.stdlibGlobalMember(access, "Atomics");
|
||||
if (member === null) return null;
|
||||
const loc = locOf(call);
|
||||
if (member !== "wait") {
|
||||
const operation = OPERATIONS.indexOf(member);
|
||||
if (operation < 0 && member !== "wait" && member !== "notify") {
|
||||
lowerer.noLowering(
|
||||
`Atomics.${member}`,
|
||||
call,
|
||||
"Atomics.wait(int32Array, idx, expected, timeoutMs) is the supported Atomics surface " +
|
||||
"(scriptc has no threads — wait is the synchronous-sleep idiom, and nothing else has anyone to race)",
|
||||
"use integer load, store, exchange, compareExchange, add, sub, and, or, xor, wait or notify",
|
||||
lowerer.checker.getSymbolAtLocation(access.name),
|
||||
);
|
||||
}
|
||||
if (call.arguments.length !== 4 || call.arguments.some(ts.isSpreadElement)) {
|
||||
const minimum =
|
||||
member === "wait"
|
||||
? 3
|
||||
: member === "notify" || member === "load"
|
||||
? 2
|
||||
: member === "compareExchange"
|
||||
? 4
|
||||
: 3;
|
||||
const maximum = member === "wait" ? 4 : member === "notify" ? 3 : minimum;
|
||||
if (
|
||||
call.arguments.length < minimum ||
|
||||
call.arguments.length > maximum ||
|
||||
call.arguments.some(ts.isSpreadElement)
|
||||
) {
|
||||
lowerer.noLowering(
|
||||
`Atomics.wait with ${call.arguments.length} arguments`,
|
||||
`Atomics.${member} arguments`,
|
||||
call,
|
||||
"the timeout is required: without it the wait blocks forever — scriptc has no threads, " +
|
||||
"so no notify can ever arrive (Atomics.wait(arr, idx, expected, timeoutMs))",
|
||||
`use ${minimum}${maximum > minimum ? `–${maximum}` : ""} arguments`,
|
||||
);
|
||||
}
|
||||
const arrNode = call.arguments[0]!;
|
||||
const arrIr = lowerer.mapTypeOf(lowerer.typeOf(arrNode));
|
||||
if (!(arrIr?.kind === "bytes" && arrIr.elem === "i32")) {
|
||||
const source = call.arguments[0]!;
|
||||
// Generic constraints can remain unions in the checker while this body
|
||||
// has a concrete typed-array binding. Validate the actual lowered storage.
|
||||
const array = lowerer.lowerExpr(source);
|
||||
const type = array.type;
|
||||
if (
|
||||
type?.kind !== "bytes" ||
|
||||
!["i8", "u8", "i16", "u16", "i32", "u32"].includes(type.elem) ||
|
||||
((member === "wait" || member === "notify") && type.elem !== "i32")
|
||||
) {
|
||||
lowerer.noLowering(
|
||||
`Atomics.wait over '${lowerer.checker.typeToString(lowerer.typeOf(arrNode))}' values`,
|
||||
arrNode,
|
||||
"an Int32Array is the supported waitable array",
|
||||
`Atomics.${member} over this array`,
|
||||
source,
|
||||
member === "wait" || member === "notify"
|
||||
? "use an Int32Array"
|
||||
: "use an 8-, 16-, or 32-bit integer typed array",
|
||||
);
|
||||
}
|
||||
const arr = lowerer.lowerExpr(arrNode);
|
||||
const idx = lowerer.lowerExprExpecting(call.arguments[1]!, F64);
|
||||
const expected = lowerer.lowerExprExpecting(call.arguments[2]!, F64);
|
||||
const timeout = lowerer.lowerExprExpecting(call.arguments[3]!, F64);
|
||||
const number = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc });
|
||||
const arg = (index: number, fallback: number): IrExpr =>
|
||||
call.arguments[index]
|
||||
? lowerOptionalArgument(lowerer, call.arguments[index]!, F64, number(fallback))
|
||||
: number(fallback);
|
||||
const index = arg(1, 0);
|
||||
if (member === "wait")
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "atomics.wait",
|
||||
args: [array, index, arg(2, 0), arg(3, Infinity)],
|
||||
type: STRING,
|
||||
loc,
|
||||
};
|
||||
if (member === "notify")
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "atomics.notify",
|
||||
args: [array, index, arg(2, Infinity)],
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "atomics.wait",
|
||||
args: [arr, idx, expected, timeout],
|
||||
type: STRING,
|
||||
fn: "atomics.op",
|
||||
args: [array, index, arg(2, 0), arg(3, 0), number(operation)],
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import { locOf } from "../../program.js";
|
||||
import { resolve } from "node:path";
|
||||
import { isWorkerConstructor, workerModulePath } from "../../worker-target.js";
|
||||
import { tsgoPath } from "../../dts-paths.js";
|
||||
import {
|
||||
BOOL,
|
||||
DYN,
|
||||
F64,
|
||||
STRING,
|
||||
UNDEFINED_T,
|
||||
isFfiCallbackParam,
|
||||
type IrExpr,
|
||||
type SrcLoc,
|
||||
} from "../../../ir/ir.js";
|
||||
import type { Lowerer } from "../lowerer.js";
|
||||
|
||||
export function lowerWorkerMetadata(member: string, loc: SrcLoc): IrExpr | null {
|
||||
switch (member) {
|
||||
case "isMainThread":
|
||||
return { kind: "libCall", fn: "worker.isMainThread", args: [], type: BOOL, loc };
|
||||
case "threadId":
|
||||
return { kind: "libCall", fn: "worker.threadId", args: [], type: F64, loc };
|
||||
case "workerData":
|
||||
return { kind: "libCall", fn: "worker.data", args: [], type: DYN, loc };
|
||||
case "parentPort":
|
||||
return { kind: "libCall", fn: "worker.parentPort", args: [], type: DYN, loc };
|
||||
case "isInternalThread":
|
||||
return { kind: "boolLit", value: false, type: BOOL, loc };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function lowerWorkerNew(lowerer: Lowerer, expression: ts.NewExpression): IrExpr | null {
|
||||
const constructor = expression.expression;
|
||||
if (!isWorkerConstructor(lowerer.program, constructor)) return null;
|
||||
const args = expression.arguments ?? [];
|
||||
if (args.length < 1 || args.length > 2 || args.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(
|
||||
"Worker constructor arguments",
|
||||
expression,
|
||||
"use new Worker(filename, options?)",
|
||||
);
|
||||
}
|
||||
const filename = args[0]!;
|
||||
const path = workerModulePath(lowerer.program, filename);
|
||||
const root =
|
||||
path === null ? undefined : lowerer.workerTargetIdByPath.get(tsgoPath(resolve(path)));
|
||||
if (root === undefined) {
|
||||
lowerer.noLowering(
|
||||
"this Worker entry point",
|
||||
filename,
|
||||
"the worker entry must resolve to a statically compiled source file",
|
||||
);
|
||||
}
|
||||
if (lowerer.dynamic || lowerer.targetPlatform === "wasi") {
|
||||
lowerer.noLowering(
|
||||
"Worker execution in this target",
|
||||
expression,
|
||||
"use a native static executable",
|
||||
);
|
||||
}
|
||||
if (
|
||||
lowerer.ffiImports.some((entry) =>
|
||||
entry.params.some(
|
||||
(parameter) => isFfiCallbackParam(parameter) && parameter.callback.invoke === "foreign",
|
||||
),
|
||||
)
|
||||
) {
|
||||
lowerer.noLowering(
|
||||
"workers with foreign-thread native callbacks",
|
||||
expression,
|
||||
"native callbacks in worker executables must run on their owning script thread",
|
||||
);
|
||||
}
|
||||
lowerer.usesWorkers = true;
|
||||
const loc = locOf(expression);
|
||||
const options: IrExpr = args[1]
|
||||
? lowerer.lowerExprExpecting(args[1], DYN)
|
||||
: {
|
||||
kind: "dynFrom",
|
||||
value: { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc },
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "worker.new",
|
||||
args: [
|
||||
{ kind: "numLit", value: root, type: F64, loc },
|
||||
{ kind: "strLit", value: path!, type: STRING, loc },
|
||||
options,
|
||||
],
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { dynUndefinedExpr, varRef } from "../../../ir/build.js";
|
||||
import { BUF_NUM_METHODS } from "./buffer-numeric-methods.js";
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import {
|
||||
BYTES_ELEMENT_SIZE,
|
||||
BIGINT_T,
|
||||
BOOL,
|
||||
BYTES_U8,
|
||||
@@ -153,7 +152,11 @@ export function lowerBytesNew(
|
||||
expr: ts.NewExpression,
|
||||
symbol: ts.Symbol | null | undefined,
|
||||
): IrExpr | null {
|
||||
if (symbol != null && symbol.name === "ArrayBuffer" && lowerer.isStdlibSymbol(symbol)) {
|
||||
if (
|
||||
symbol != null &&
|
||||
(symbol.name === "ArrayBuffer" || symbol.name === "SharedArrayBuffer") &&
|
||||
lowerer.isStdlibSymbol(symbol)
|
||||
) {
|
||||
const args = expr.arguments ?? [];
|
||||
if (args.length > 1 || args.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(
|
||||
@@ -165,7 +168,13 @@ export function lowerBytesNew(
|
||||
const length = args[0]
|
||||
? lowerer.coerceInto(args[0], lowerer.lowerExpr(args[0]), DYN)
|
||||
: dynUndefinedExpr(locOf(expr));
|
||||
return { kind: "libCall", fn: "arrayBuffer.new", args: [length], type: DYN, loc: locOf(expr) };
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: symbol.name === "SharedArrayBuffer" ? "sharedArrayBuffer.new" : "arrayBuffer.new",
|
||||
args: [length],
|
||||
type: DYN,
|
||||
loc: locOf(expr),
|
||||
};
|
||||
}
|
||||
if (symbol && symbol.name === "DataView" && lowerer.isStdlibSymbol(symbol)) {
|
||||
return lowerDataViewNew(lowerer, expr);
|
||||
@@ -187,46 +196,6 @@ export function lowerBytesNew(
|
||||
const seed: IrExpr = { kind: "arrayLit", elems, type: arrayOf(F64), loc };
|
||||
return { kind: "bytesNew", source: seed, type, loc };
|
||||
}
|
||||
// The SYNTACTIC `new T(new SharedArrayBuffer(n))` form — fresh-buffer construction (the
|
||||
// shared spelling is the Atomics.wait sleep idiom's). The buffer
|
||||
// never exists as a value: n must be a byte-length LITERAL divisible
|
||||
// by the element size (tsc would admit any number; a bad one is
|
||||
// Node's RangeError — rejected at compile time instead of
|
||||
// half-lowering), and the whole expression is a zero-filled typed
|
||||
// array of n/elemSize elements. Erasing the buffer is exact: nothing
|
||||
// else can ever reference it, so neither sharing (scriptc has no
|
||||
// threads) nor aliasing is observable — SEMANTICS.md documents the
|
||||
// stance. The RESIZABLE form (a maxByteLength options bag) fences by
|
||||
// name: this erasure has no shared or growable buffer representation.
|
||||
if (
|
||||
ts.isNewExpression(argNode) &&
|
||||
ts.isIdentifier(argNode.expression) &&
|
||||
argNode.expression.text === "SharedArrayBuffer" &&
|
||||
lowerer.isStdlibSymbol(lowerer.resolveValueSymbol(argNode.expression) ?? undefined)
|
||||
) {
|
||||
const bufCtor = argNode.expression.text;
|
||||
if ((argNode.arguments?.length ?? 0) > 1) {
|
||||
lowerer.noLowering(
|
||||
`new ${name} over a resizable ${bufCtor}`,
|
||||
argNode,
|
||||
"a maxByteLength options bag needs growable shared storage; the fixed-length " +
|
||||
"SharedArrayBuffer here erases into the view: drop the options bag",
|
||||
);
|
||||
}
|
||||
const elemSize = BYTES_ELEMENT_SIZE[elem];
|
||||
const lenArg = argNode.arguments?.length === 1 ? argNode.arguments[0] : undefined;
|
||||
const lenT = lenArg ? lowerer.typeOf(lenArg) : null;
|
||||
const byteLen = lenT?.isNumberLiteralType() ? lenT.value : null;
|
||||
if (byteLen === null || byteLen % elemSize !== 0 || byteLen < 0) {
|
||||
lowerer.noLowering(
|
||||
`new ${name} over this ${bufCtor}`,
|
||||
argNode,
|
||||
`the byte length must be a number literal divisible by ${elemSize} — new ${name}(new ${bufCtor}(${elemSize}))`,
|
||||
);
|
||||
}
|
||||
const count: IrExpr = { kind: "numLit", value: byteLen / elemSize, type: F64, loc };
|
||||
return { kind: "bytesNew", source: count, type, loc };
|
||||
}
|
||||
const src = lowerer.lowerExpr(argNode);
|
||||
if (src.type.kind === "union") {
|
||||
const arms = lowerer.unions.get(src.type.unionId)?.arms;
|
||||
@@ -1303,7 +1272,8 @@ function lowerBufferStaticValue(
|
||||
args.length >= 1 &&
|
||||
args.length <= 3 &&
|
||||
!args.some(ts.isSpreadElement) &&
|
||||
((symbol) => symbol != null && symbol.name === "ArrayBuffer")(
|
||||
((symbol) =>
|
||||
symbol != null && (symbol.name === "ArrayBuffer" || symbol.name === "SharedArrayBuffer"))(
|
||||
lowerer.typeOf(args[0]!).getSymbol(),
|
||||
)
|
||||
) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { lowerWorkerMetadata } from "./builtins/workers.js";
|
||||
import * as ts from "../ts7/adapter.js";
|
||||
import {
|
||||
BOOL,
|
||||
DYN,
|
||||
F64,
|
||||
NULL_T,
|
||||
STRING,
|
||||
UNDEFINED_T,
|
||||
arrayOf,
|
||||
@@ -416,8 +416,9 @@ export function lowerNumberParserValue(
|
||||
};
|
||||
}
|
||||
|
||||
// These module values expose existing native callable lowerings and main-thread
|
||||
// metadata. Other exports keep an explicit runtime refusal, including Worker.
|
||||
// These module values expose native callable lowerings and current-thread
|
||||
// metadata. Stored Worker constructors remain refused: native worker roots
|
||||
// must be resolved from the source at compilation time.
|
||||
const MODULES = ["path/posix", "path/win32", "os", "worker_threads", "util/types"] as const;
|
||||
|
||||
const box = (value: IrExpr): IrExpr =>
|
||||
@@ -515,17 +516,18 @@ function moduleValue(lowerer: Lowerer, module: string, loc: SrcLoc): IrExpr {
|
||||
if (callable) add(member, callable);
|
||||
}
|
||||
for (const member of Object.keys(BUILTIN_MODULE_CONSTS[module] ?? {})) {
|
||||
const worker = module === "worker_threads" ? lowerWorkerMetadata(member, loc) : null;
|
||||
const value = builtinModuleConstOf(lowerer, module, member);
|
||||
if (value !== undefined) add(member, builtinConstLit(value, loc));
|
||||
if (worker) add(member, worker);
|
||||
else if (value !== undefined) add(member, builtinConstLit(value, loc));
|
||||
}
|
||||
if (module.startsWith("path/")) {
|
||||
add("posix", moduleValue(lowerer, "path/posix", loc));
|
||||
add("win32", moduleValue(lowerer, "path/win32", loc));
|
||||
}
|
||||
if (module === "worker_threads") {
|
||||
add("isInternalThread", { kind: "boolLit", value: false, type: BOOL, loc });
|
||||
add("parentPort", { kind: "unitLit", unit: "null", type: NULL_T, loc });
|
||||
add("workerData", { kind: "unitLit", unit: "null", type: NULL_T, loc });
|
||||
for (const member of ["isInternalThread", "parentPort", "workerData"])
|
||||
add(member, lowerWorkerMetadata(member, loc)!);
|
||||
}
|
||||
body.push({
|
||||
kind: "return",
|
||||
|
||||
@@ -3411,7 +3411,15 @@ function lowerDynReceiverMethodCall(
|
||||
access: ts.PropertyAccessExpression,
|
||||
): IrExpr | null {
|
||||
if (lowerer.chainBlocked(call, access)) return null;
|
||||
if (stdlibGlobalNameOf(lowerer, access.expression) !== null) return null;
|
||||
if (stdlibGlobalNameOf(lowerer, access.expression) !== null) {
|
||||
// A named builtin import can resolve to its ambient declaration, too.
|
||||
// parentPort is an instance value whose methods use the native handle;
|
||||
// it must work without a non-null assertion or a namespace spelling.
|
||||
const binding = ts.isIdentifier(access.expression)
|
||||
? lowerer.builtinImportOf(access.expression)
|
||||
: null;
|
||||
if (binding?.module !== "worker_threads" || binding.member !== "parentPort") return null;
|
||||
}
|
||||
// Only checker-untyped receivers: `any`/`unknown`, or the `any[]` an
|
||||
// Array.isArray guard narrows them to (the value is STILL the checked-dynamic tree
|
||||
// array — scalar narrowings bridge through maybeNarrow's dynCheck and
|
||||
@@ -4034,6 +4042,11 @@ const DYN_DISPATCH_METHODS = new Set([
|
||||
"additionalHeaders",
|
||||
"altsvc",
|
||||
"origin",
|
||||
// Worker and MessagePort methods use the owning context's handle adapter.
|
||||
"postMessage",
|
||||
"terminate",
|
||||
"start",
|
||||
"hasRef",
|
||||
]);
|
||||
|
||||
export function lowerDynDispatchMethodCall(
|
||||
|
||||
@@ -73,6 +73,7 @@ import { bufEncoding } from "./containers/bytes.js";
|
||||
import { isSafeToRepeat } from "./expressions/evaluation-safety.js";
|
||||
import { lowerSearchParamsNew } from "./builtins/url.js";
|
||||
import { lowerTextCodecNew } from "./builtins/text-codecs.js";
|
||||
import { lowerWorkerNew } from "./builtins/workers.js";
|
||||
import {
|
||||
requiresDynamicPackageDiag,
|
||||
unsupportedDiag,
|
||||
@@ -7905,6 +7906,8 @@ function assignedThisFieldType(lowerer: Lowerer, expr: ts.NewExpression): IrType
|
||||
|
||||
export function lowerNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr {
|
||||
const loc = locOf(expr);
|
||||
const worker = lowerWorkerNew(lowerer, expr);
|
||||
if (worker) return worker;
|
||||
let intlConstructor = expr.expression;
|
||||
if (ts.isIdentifier(intlConstructor)) {
|
||||
const symbol = lowerer.resolveValueSymbol(intlConstructor);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { lowerWorkerMetadata } from "./builtins/workers.js";
|
||||
import {
|
||||
dynUndefinedExpr,
|
||||
nodeThrowExpr,
|
||||
@@ -1398,6 +1399,10 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
{
|
||||
const bi = lowerer.builtinImportOf(expr);
|
||||
if (bi) {
|
||||
if (bi.module === "worker_threads") {
|
||||
const value = lowerWorkerMetadata(bi.member, loc);
|
||||
if (value) return value;
|
||||
}
|
||||
const c = builtinModuleConstOf(lowerer, bi.module, bi.member);
|
||||
if (c !== undefined) return builtinConstLit(c, loc);
|
||||
// module.builtinModules — the baked Node v24 list, a fresh
|
||||
@@ -1632,8 +1637,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
|
||||
Reflect:
|
||||
"reflective property access has no static lowering — read and call members directly",
|
||||
Intl: 'native Intl supports default Unicode grapheme segmentation and the composed new Intl.NumberFormat("en-US").format(x); locale negotiation and other ICU-backed operations remain unsupported',
|
||||
SharedArrayBuffer:
|
||||
"no shared-memory threads exist in a compiled program — Uint8Array is the byte storage",
|
||||
SharedArrayBuffer: "use direct new SharedArrayBuffer(length) for fixed shared storage",
|
||||
ArrayBuffer:
|
||||
"ArrayBuffer constructor values have no native lowering; use new ArrayBuffer(length) for fixed-length shared storage",
|
||||
WeakRef:
|
||||
@@ -11603,6 +11607,10 @@ export function lowerInstanceOf(lowerer: Lowerer, expr: ts.BinaryExpression, loc
|
||||
const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
|
||||
return { kind: "libCall", fn: "dyn.dataViewIs", args: [value], type: BOOL, loc };
|
||||
}
|
||||
if (lowerer.isStdlibGlobal(expr.right, "SharedArrayBuffer")) {
|
||||
const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
|
||||
return { kind: "libCall", fn: "sharedArrayBuffer.is", args: [value], type: BOOL, loc };
|
||||
}
|
||||
if (lowerer.isStdlibGlobal(expr.right, "ArrayBuffer")) {
|
||||
const value = lowerer.coerceInto(expr.left, lowerer.lowerExpr(expr.left), DYN);
|
||||
return { kind: "libCall", fn: "arrayBuffer.is", args: [value], type: BOOL, loc };
|
||||
|
||||
@@ -127,6 +127,7 @@ import {
|
||||
import { isMixinFnBinding, mixinResultBindingClassOf } from "./lower-mixins.js";
|
||||
import { cjsModuleRef, cjsModuleRegistryPrelude } from "./lower-node-module.js";
|
||||
import { forkTargetPaths } from "../fork-target.js";
|
||||
import { workerTargetPaths } from "../worker-target.js";
|
||||
import { isNativeProxyInitializer } from "./expressions/native-proxy.js";
|
||||
|
||||
/** One file's declarations, split for collection and init-body lowering. */
|
||||
@@ -147,6 +148,25 @@ export function appendForkModules(
|
||||
order: ts.SourceFile[],
|
||||
targets: ts.SourceFile[],
|
||||
onCycle: (cycle: string, reason: string) => void,
|
||||
): void {
|
||||
appendEntryModules(program, order, targets, forkTargetPaths(program, order), onCycle);
|
||||
}
|
||||
|
||||
export function appendWorkerModules(
|
||||
program: ts.Program,
|
||||
order: ts.SourceFile[],
|
||||
targets: ts.SourceFile[],
|
||||
onCycle: (cycle: string, reason: string) => void,
|
||||
): void {
|
||||
appendEntryModules(program, order, targets, workerTargetPaths(program, order), onCycle);
|
||||
}
|
||||
|
||||
function appendEntryModules(
|
||||
program: ts.Program,
|
||||
order: ts.SourceFile[],
|
||||
targets: ts.SourceFile[],
|
||||
paths: readonly string[],
|
||||
onCycle: (cycle: string, reason: string) => void,
|
||||
): void {
|
||||
if (order.length === 0) return;
|
||||
const byPath = new Map(
|
||||
@@ -187,7 +207,7 @@ export function appendForkModules(
|
||||
};
|
||||
|
||||
const knownTargets = new Set(targets);
|
||||
for (const path of forkTargetPaths(program, order)) {
|
||||
for (const path of paths) {
|
||||
const target = byPath.get(resolvePath(path));
|
||||
if (!target || target.isDeclarationFile || target.fileName.endsWith(".json")) continue;
|
||||
if (!knownTargets.has(target)) {
|
||||
@@ -3145,7 +3165,7 @@ export function lowerFileInit(
|
||||
export function buildMain(lowerer: Lowerer): IrFunction {
|
||||
const loc: SrcLoc = { file: lowerer.entry.fileName, start: 0, end: 0 };
|
||||
const body: IrStmt[] = [...cjsModuleRegistryPrelude(lowerer, loc)];
|
||||
const roots = [lowerer.entry, ...lowerer.forkTargets];
|
||||
const roots = [lowerer.entry, ...lowerer.forkTargets, ...lowerer.workerTargets];
|
||||
const isAsync = roots.some((root) => lowerer.asyncInitFiles.has(root));
|
||||
const callRoot = (root: ts.SourceFile): IrStmt[] => {
|
||||
const init = lowerer.initNameOf.get(root);
|
||||
@@ -3219,6 +3239,35 @@ export function buildMain(lowerer: Lowerer): IrFunction {
|
||||
loc,
|
||||
});
|
||||
}
|
||||
if (lowerer.workerTargets.length > 0) {
|
||||
const mainBody = body.splice(0);
|
||||
let workerBody: IrStmt[] | null = null;
|
||||
for (let id = lowerer.workerTargets.length - 1; id >= 0; id--) {
|
||||
workerBody = [
|
||||
{
|
||||
kind: "if",
|
||||
cond: {
|
||||
kind: "bin",
|
||||
op: "===",
|
||||
left: { kind: "libCall", fn: "worker.root", args: [], type: F64, loc },
|
||||
right: { kind: "numLit", value: id, type: F64, loc },
|
||||
type: BOOL,
|
||||
loc,
|
||||
},
|
||||
then: callRoot(lowerer.workerTargets[id]!),
|
||||
else_: workerBody,
|
||||
loc,
|
||||
},
|
||||
];
|
||||
}
|
||||
body.push({
|
||||
kind: "if",
|
||||
cond: { kind: "libCall", fn: "worker.isMainThread", args: [], type: BOOL, loc },
|
||||
then: mainBody,
|
||||
else_: [...cjsModuleRegistryPrelude(lowerer, loc), ...(workerBody ?? [])],
|
||||
loc,
|
||||
});
|
||||
}
|
||||
// Node's startup refusal (a resolution the graph carries that Node
|
||||
// rejects — preflight's Node-order resolution walk — or a module-LINK
|
||||
// SyntaxError from either named-import checker): the graph is refused
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { lowerWorkerMetadata } from "./builtins/workers.js";
|
||||
import { dynUndefinedExpr, nodeThrowExpr, numLit, varRef } from "../../ir/build.js";
|
||||
import type { FieldLift } from "./coercions/structural-plans.js";
|
||||
import { lowerBuiltinCall } from "./builtin-calls.js";
|
||||
@@ -233,6 +234,7 @@ import {
|
||||
buildMain,
|
||||
appendDynamicImportModules,
|
||||
appendForkModules,
|
||||
appendWorkerModules,
|
||||
} from "./lower-modules.js";
|
||||
import { prepareCjsModuleGraph } from "./lower-node-module.js";
|
||||
import {
|
||||
@@ -805,6 +807,7 @@ export interface LowererMode {
|
||||
libraryCallbacks?: boolean;
|
||||
/** Statically resolved child_process.fork program roots, in stable target-id order. */
|
||||
forkTargets?: readonly ts.SourceFile[];
|
||||
workerTargets?: readonly ts.SourceFile[];
|
||||
/** Program-validated ambient declaration symbols for each FFI name.
|
||||
* Undefined in discovery's legacy call-local validation path. */
|
||||
ffiBindingSymbols?: ReadonlyMap<string, ReadonlySet<ts.Symbol>>;
|
||||
@@ -878,6 +881,7 @@ export function lowerToIr(
|
||||
const extraModuleCycleDiags: ScrDiagnostic[] = [];
|
||||
const cycleKeys = new Set<string>();
|
||||
const forkTargets: ts.SourceFile[] = [];
|
||||
const workerTargets: ts.SourceFile[] = [];
|
||||
const onExtraCycle = (cycle: string, reason: string): void => {
|
||||
const key = `${cycle}\0${reason}`;
|
||||
if (cycleKeys.has(key)) return;
|
||||
@@ -894,6 +898,7 @@ export function lowerToIr(
|
||||
const before = moduleOrder.length;
|
||||
appendDynamicImportModules(program, moduleOrder, onExtraCycle);
|
||||
appendForkModules(program, moduleOrder, forkTargets, onExtraCycle);
|
||||
appendWorkerModules(program, moduleOrder, workerTargets, onExtraCycle);
|
||||
if (moduleOrder.length === before) break;
|
||||
if (pass === 31) throw new Error("dynamic import and fork module discovery did not converge");
|
||||
}
|
||||
@@ -909,6 +914,7 @@ export function lowerToIr(
|
||||
ffiImports,
|
||||
libraryCallbacks,
|
||||
forkTargets,
|
||||
workerTargets,
|
||||
externalTypes,
|
||||
externalTypeSpecifiersByFile,
|
||||
});
|
||||
@@ -929,6 +935,7 @@ export function lowerToIr(
|
||||
ffiImports,
|
||||
libraryCallbacks,
|
||||
forkTargets,
|
||||
workerTargets,
|
||||
externalTypes,
|
||||
externalTypeSpecifiersByFile,
|
||||
ffiBindingSymbols: ffiValidation.symbolsByName,
|
||||
@@ -956,6 +963,7 @@ export function lowerToIr(
|
||||
ffiImports,
|
||||
libraryCallbacks,
|
||||
forkTargets,
|
||||
workerTargets,
|
||||
ffiBindingSymbols: ffiValidation.symbolsByName,
|
||||
externalTypes,
|
||||
externalTypeSpecifiersByFile,
|
||||
@@ -976,6 +984,7 @@ export function lowerToIr(
|
||||
ffiImports,
|
||||
libraryCallbacks,
|
||||
forkTargets,
|
||||
workerTargets,
|
||||
ffiBindingSymbols: ffiValidation.symbolsByName,
|
||||
externalTypes,
|
||||
externalTypeSpecifiersByFile,
|
||||
@@ -2324,6 +2333,9 @@ export class Lowerer {
|
||||
readonly fileTag = new Map<ts.SourceFile, string>();
|
||||
/** Embedded fork roots and their private startup ids. */
|
||||
readonly forkTargets: readonly ts.SourceFile[];
|
||||
readonly workerTargets: readonly ts.SourceFile[];
|
||||
readonly workerTargetIdByPath = new Map<string, number>();
|
||||
usesWorkers = false;
|
||||
readonly forkTargetIdByPath = new Map<string, number>();
|
||||
/** Namespace ModuleBlocks this program lowers, filled by splitFiles:
|
||||
* "flattened" — an instantiated namespace whose body joined the file's
|
||||
@@ -2552,6 +2564,10 @@ export class Lowerer {
|
||||
this.startupCrash = mode.startupCrash ?? null;
|
||||
this.ffiImports = mode.ffiImports ?? [];
|
||||
this.libraryCallbacks = mode.libraryCallbacks ?? false;
|
||||
this.workerTargets = mode.workerTargets ?? [];
|
||||
this.workerTargets.forEach((sf, id) => {
|
||||
this.workerTargetIdByPath.set(tsgoPath(resolve(sf.fileName)), id);
|
||||
});
|
||||
this.forkTargets = mode.forkTargets ?? [];
|
||||
this.forkTargets.forEach((sf, id) => {
|
||||
this.forkTargetIdByPath.set(tsgoPath(resolve(sf.fileName)), id);
|
||||
@@ -4871,7 +4887,7 @@ export class Lowerer {
|
||||
this.diags.length > 0
|
||||
? null
|
||||
: {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: this.entry.fileName,
|
||||
functions,
|
||||
classes: artifacts.classes,
|
||||
@@ -4880,6 +4896,7 @@ export class Lowerer {
|
||||
globals: this.globalsList,
|
||||
...(this.npmEmbedded ? { embedded: this.npmEmbedded } : {}),
|
||||
entry: ENTRY_NAME,
|
||||
...(this.usesWorkers ? { workers: true } : {}),
|
||||
...(this.ffiImports.length > 0 ? { ffiImports: [...this.ffiImports] } : {}),
|
||||
};
|
||||
if (module) sanitizeUnregisteredClassTypes(module, (name) => this.classes.has(name));
|
||||
@@ -5806,10 +5823,9 @@ export class Lowerer {
|
||||
"the rest is ICU locale data the binary does not carry";
|
||||
const typeHints: Record<string, string | undefined> = {
|
||||
ArrayBuffer:
|
||||
"no free-standing ArrayBuffer value exists — typed arrays own their storage " +
|
||||
"(new Uint8Array(n) allocates; new Uint8Array(new ArrayBuffer(n)) erases the buffer into the view)",
|
||||
"fixed ArrayBuffer storage and typed-array views are supported; resizable and detached storage are not modeled",
|
||||
SharedArrayBuffer:
|
||||
"no shared-memory threads exist in a compiled program — Uint8Array is the byte storage",
|
||||
"fixed SharedArrayBuffer storage is supported; growable shared storage is not modeled",
|
||||
NumberFormat: intlHint,
|
||||
DateTimeFormat: intlHint,
|
||||
DurationFormat: intlHint,
|
||||
@@ -10161,6 +10177,10 @@ export class Lowerer {
|
||||
if (bi.module === "events" && bi.member === "defaultMaxListeners") {
|
||||
return { kind: "libCall", fn: "emitter.getDefaultMax", args: [], type: F64, loc };
|
||||
}
|
||||
if (bi.module === "worker_threads") {
|
||||
const value = lowerWorkerMetadata(bi.member, loc);
|
||||
if (value) return value;
|
||||
}
|
||||
const c = builtinModuleConstOf(this, bi.module, bi.member);
|
||||
if (c !== undefined) return builtinConstLit(c, loc);
|
||||
// module.builtinModules through a namespace binding — the baked
|
||||
|
||||
@@ -21,7 +21,7 @@ test("fenced instance slots are erased throughout signatures, storage and nested
|
||||
};
|
||||
const ref = (type: IrType): IrExpr => ({ kind: "varRef", localId: "x", type, loc });
|
||||
const module: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
globals: [{ id: "g", name: "g", type: composite, mutable: false }],
|
||||
@@ -110,7 +110,7 @@ test("modules without optional tables and registered class types retain their st
|
||||
const loc = { file: "registered.ts", start: 0, end: 1 };
|
||||
const type: IrType = { kind: "object", className: "Registered" };
|
||||
const module: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [
|
||||
|
||||
@@ -1640,10 +1640,9 @@ export const BUILTIN_MODULE_FNS: Record<
|
||||
* a literal of its value's kind. The bare-module values are the TARGET
|
||||
* platform's (builtinModuleConstOf swaps the win32 values in under a win32
|
||||
* triple — Node's own platform conditionals); the named namespaces answer
|
||||
* THEIR platform's constants everywhere. worker_threads/cluster carry the
|
||||
* main-thread truths: a compiled binary IS the main thread (no JS-engine
|
||||
* thread machinery) and never runs as a cluster worker (cluster forks the
|
||||
* node binary itself) — Node's own answers for a directly-run script. */
|
||||
* THEIR platform's constants everywhere. Worker metadata uses these names
|
||||
* for inventory but reads the current runtime context in its dedicated
|
||||
* lowering. Cluster metadata describes a directly-run native executable. */
|
||||
export const BUILTIN_MODULE_CONSTS: Record<
|
||||
string,
|
||||
Record<string, string | number | boolean | undefined> | undefined
|
||||
@@ -1754,6 +1753,34 @@ export interface AmbientSurfaceRow {
|
||||
}
|
||||
|
||||
export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [
|
||||
{
|
||||
id: "node-builtin.worker_threads.Worker",
|
||||
kind: "node-builtin",
|
||||
name: "worker_threads.Worker",
|
||||
fns: ["worker.new"],
|
||||
note: "native threads with statically compiled entry points, workerData, argv and message events",
|
||||
},
|
||||
{
|
||||
id: "node-builtin.worker_threads.metadata",
|
||||
kind: "node-builtin",
|
||||
name: "worker_threads metadata",
|
||||
fns: ["worker.isMainThread", "worker.threadId", "worker.data", "worker.parentPort"],
|
||||
note: "current-thread metadata and the worker's parent message port",
|
||||
},
|
||||
{
|
||||
id: "stdlib.sharedArrayBuffer",
|
||||
kind: "stdlib",
|
||||
name: "SharedArrayBuffer",
|
||||
fns: ["sharedArrayBuffer.new"],
|
||||
note: "fixed shared storage with context-local typed-array and DataView wrappers",
|
||||
},
|
||||
{
|
||||
id: "stdlib.atomics",
|
||||
kind: "stdlib",
|
||||
name: "Atomics",
|
||||
fns: ["atomics.op", "atomics.wait", "atomics.notify"],
|
||||
note: "8-, 16- and 32-bit integer operations, plus Int32Array wait and notify",
|
||||
},
|
||||
{
|
||||
id: "node-builtin.fs.callbacks",
|
||||
kind: "node-builtin",
|
||||
@@ -1945,7 +1972,7 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [
|
||||
kind: "node-builtin",
|
||||
name: "process.getBuiltinModule",
|
||||
fns: ["process.builtinId", "process.builtinModule", "process.builtinUnsupported"],
|
||||
note: "native path and os export subsets plus main-thread worker_threads metadata; other modules and exports throw SC2020",
|
||||
note: "native path and os export subsets plus current-thread worker_threads metadata; other modules and exports throw SC2020",
|
||||
},
|
||||
{
|
||||
id: "node-builtin.process.versions",
|
||||
@@ -2608,8 +2635,7 @@ export function stdlibMemberFence(lowerer: Lowerer, access: ts.PropertyAccessExp
|
||||
"allocate a new buffer and copy through typed-array views instead";
|
||||
} else if (container === "SharedArrayBuffer" || container.startsWith("SharedArrayBuffer<")) {
|
||||
hint =
|
||||
"no shared-memory threads exist in a compiled program — Uint8Array is the byte storage " +
|
||||
"(a fixed-length allocation: grow has nothing to share it with)";
|
||||
"native SharedArrayBuffer storage is fixed-length; growable shared buffers are unsupported";
|
||||
} else if (
|
||||
container === "Intl" ||
|
||||
container.startsWith("Intl.") ||
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as ts from "./ts7/adapter.js";
|
||||
|
||||
export interface ModuleSourceCandidates {
|
||||
calls: ts.CallExpression[];
|
||||
constructions: ts.NewExpression[];
|
||||
specifiers: string[];
|
||||
}
|
||||
|
||||
@@ -9,6 +10,7 @@ const MODULE_SOURCE_KINDS: ReadonlySet<ts.SyntaxKind> = new Set([
|
||||
ts.SyntaxKind.ImportDeclaration,
|
||||
ts.SyntaxKind.ExportDeclaration,
|
||||
ts.SyntaxKind.CallExpression,
|
||||
ts.SyntaxKind.NewExpression,
|
||||
]);
|
||||
|
||||
/** Root discovery and import admission inspect the same immutable syntax.
|
||||
@@ -20,7 +22,7 @@ export function moduleSourceCandidates(
|
||||
): ModuleSourceCandidates {
|
||||
const cached = program.analysis.moduleSourceCandidates.get(source);
|
||||
if (cached !== undefined) return cached;
|
||||
const result: ModuleSourceCandidates = { calls: [], specifiers: [] };
|
||||
const result: ModuleSourceCandidates = { calls: [], constructions: [], specifiers: [] };
|
||||
ts.walkPreorder(
|
||||
source,
|
||||
(node) => {
|
||||
@@ -44,6 +46,7 @@ export function moduleSourceCandidates(
|
||||
result.specifiers.push(arg.text);
|
||||
}
|
||||
}
|
||||
if (ts.isNewExpression(node)) result.constructions.push(node);
|
||||
return undefined;
|
||||
},
|
||||
MODULE_SOURCE_KINDS,
|
||||
|
||||
@@ -117,6 +117,7 @@ import {
|
||||
} from "./tsc-codes.js";
|
||||
import { trackedFileExists, trackedReadFile, trackedRealpath } from "./input-tracker.js";
|
||||
import { forkTargetPaths } from "./fork-target.js";
|
||||
import { workerTargetPaths } from "./worker-target.js";
|
||||
import { moduleSourceCandidates } from "./module-source-candidates.js";
|
||||
import { inferredJsDiagnosticSuppressed } from "./inferred-js-diagnostics.js";
|
||||
|
||||
@@ -669,6 +670,7 @@ function loadProgram7(
|
||||
...entryPackageProgramRoots7(program, entryPath),
|
||||
...createRequireProgramRoots7(program),
|
||||
...forkTargetPaths(program, program.getImplementationSourceFiles()),
|
||||
...workerTargetPaths(program, program.getImplementationSourceFiles()),
|
||||
];
|
||||
const extraRoots = candidates.filter(
|
||||
(root, index) => !programRoots.includes(root) && candidates.indexOf(root) === index,
|
||||
@@ -2693,6 +2695,7 @@ function preflight7(load: LoadResult): {
|
||||
[
|
||||
...createRequireProgramRoots7(program),
|
||||
...forkTargetPaths(program, program.getImplementationSourceFiles()),
|
||||
...workerTargetPaths(program, program.getImplementationSourceFiles()),
|
||||
],
|
||||
(sf, spec, resolutionKind) =>
|
||||
resolveImport7(program, sf, spec, resolutionKind) ??
|
||||
|
||||
@@ -2161,6 +2161,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
|
||||
if (isStdlibInterface("Immediate")) return F64;
|
||||
if (
|
||||
isStdlibInterface("ArrayBuffer") ||
|
||||
isStdlibInterface("SharedArrayBuffer") ||
|
||||
isStdlibInterface("PropertyDescriptor") ||
|
||||
isStdlibInterface("ProcessVersions")
|
||||
)
|
||||
@@ -2263,6 +2264,18 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
|
||||
)
|
||||
)
|
||||
return DYN;
|
||||
if (
|
||||
(psym?.name === "Worker" || psym?.name === "MessagePort") &&
|
||||
checker
|
||||
.declarationsOf(psym)
|
||||
.some(
|
||||
(d) =>
|
||||
(ts.isClassDeclaration(d) || ts.isInterfaceDeclaration(d)) &&
|
||||
ctx.isStdlibFile(d.getSourceFile()) &&
|
||||
isDeclaredInAmbientModule(d, "worker_threads"),
|
||||
)
|
||||
)
|
||||
return DYN;
|
||||
// fs/promises.FileHandle: an owned descriptor object. Module provenance
|
||||
// distinguishes it from user interfaces with the same name; both the
|
||||
// fallback and @types/node declare it in "fs/promises".
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { expect, test } from "vitest";
|
||||
import { workerTargetPaths } from "./worker-target.js";
|
||||
import { fallbackDtsPath } from "./dts-paths.js";
|
||||
import { Ts7Host } from "./ts7/program-host.js";
|
||||
import { Ts7Api } from "./ts7/rpc-api.js";
|
||||
import { ModuleKind, ModuleResolutionKind } from "./ts7/enums.js";
|
||||
|
||||
function discover(source: string): string[] {
|
||||
const directory = mkdtempSync(join(tmpdir(), "scriptc-worker-discovery-"));
|
||||
const entry = join(directory, "main.ts");
|
||||
writeFileSync(entry, source);
|
||||
writeFileSync(
|
||||
join(directory, "facade.ts"),
|
||||
'export { Worker as Task } from "node:worker_threads";',
|
||||
);
|
||||
const host = new Ts7Host((options) => new Ts7Api(options), { cwd: directory });
|
||||
const program = host.createProgram([entry, fallbackDtsPath()], {
|
||||
noLib: true,
|
||||
types: [],
|
||||
module: ModuleKind.ESNext,
|
||||
moduleResolution: ModuleResolutionKind.Bundler,
|
||||
});
|
||||
try {
|
||||
return workerTargetPaths(program, [program.getSourceFile(entry)!]).map((file) =>
|
||||
file.replace(directory + "/", ""),
|
||||
);
|
||||
} finally {
|
||||
program.dispose();
|
||||
host.close();
|
||||
rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
test("worker discovery preserves imports, facades, CommonJS bindings and distinct roots", () => {
|
||||
expect(
|
||||
discover(`
|
||||
import { Worker as Start } from "node:worker_threads";
|
||||
import * as threads from "worker_threads";
|
||||
import workers from "node:worker_threads";
|
||||
import { Task } from "./facade.js";
|
||||
const { Worker: Required } = require("node:worker_threads");
|
||||
const module = require("worker_threads");
|
||||
const Constructor = module.Worker;
|
||||
new Start(new URL("./one.ts", import.meta.url));
|
||||
new threads.Worker(new URL("./two.ts", import.meta.url));
|
||||
new workers.Worker(new URL("./three.ts", import.meta.url));
|
||||
new Task(new URL("./four.ts", import.meta.url));
|
||||
new Required(__filename);
|
||||
new Constructor(new URL(import.meta.url));
|
||||
new (require("worker_threads").Worker)("/absolute/worker.ts");
|
||||
new Start(new URL("./one.ts", import.meta.url));
|
||||
`),
|
||||
).toEqual(["one.ts", "two.ts", "three.ts", "four.ts", "main.ts", "/absolute/worker.ts"]);
|
||||
});
|
||||
|
||||
test("worker discovery rejects dynamic roots and shadowed globals or constructors", () => {
|
||||
expect(
|
||||
discover(`
|
||||
import { Worker } from "node:worker_threads";
|
||||
new Worker("./cwd-dependent.ts");
|
||||
let filename = "/mutable.ts";
|
||||
new Worker(filename);
|
||||
function local(Worker: any) { new Worker("/local.ts"); }
|
||||
function loader(require: any) {
|
||||
const { Worker: Local } = require("worker_threads");
|
||||
new Local("/shadowed.ts");
|
||||
}
|
||||
function path(__filename: string) { new Worker(__filename); }
|
||||
function shadowURL(URL: any) { new Worker(new URL("/wrong.ts")); }
|
||||
let { Worker: Mutable } = require("worker_threads");
|
||||
new Mutable("/mutable-constructor.ts");
|
||||
let namespace = require("worker_threads");
|
||||
new namespace.Worker("/mutable-namespace.ts");
|
||||
const unrelated = { Worker: class {} };
|
||||
new unrelated.Worker("/unrelated.ts");
|
||||
`),
|
||||
).toEqual([]);
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
import { isAbsolute, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import * as ts from "./ts7/adapter.js";
|
||||
import {
|
||||
isNodeGlobal,
|
||||
isBuiltinMemberImport,
|
||||
isBuiltinNamespaceImport,
|
||||
staticForkString,
|
||||
} from "./fork-target.js";
|
||||
import { moduleSourceCandidates } from "./module-source-candidates.js";
|
||||
import { canonicalBuiltinModule } from "./builtin-modules.js";
|
||||
|
||||
function workerNamespace(program: ts.Program, expression: ts.Expression): boolean {
|
||||
if (
|
||||
ts.isCallExpression(expression) &&
|
||||
ts.isIdentifier(expression.expression) &&
|
||||
expression.expression.text === "require" &&
|
||||
isNodeGlobal(program, expression.expression) &&
|
||||
expression.arguments.length === 1 &&
|
||||
ts.isStringLiteralLike(expression.arguments[0]!)
|
||||
)
|
||||
return canonicalBuiltinModule(expression.arguments[0]!.text) === "worker_threads";
|
||||
if (!ts.isIdentifier(expression)) return false;
|
||||
if (isBuiltinNamespaceImport(program, expression, "worker_threads")) return true;
|
||||
const checker = program.getTypeChecker(),
|
||||
symbol = checker.getSymbolAtLocation(expression);
|
||||
const declaration = symbol ? checker.declarationsOf(symbol)[0] : undefined;
|
||||
return (
|
||||
declaration !== undefined &&
|
||||
ts.isVariableDeclaration(declaration) &&
|
||||
ts.isVariableDeclarationList(declaration.parent) &&
|
||||
(declaration.parent.flags & ts.NodeFlags.Const) !== 0 &&
|
||||
declaration.initializer !== undefined &&
|
||||
ts.isCallExpression(declaration.initializer) &&
|
||||
workerNamespace(program, declaration.initializer)
|
||||
);
|
||||
}
|
||||
|
||||
/** Share constructor provenance between root discovery and lowering. Local
|
||||
* classes and shadowed require functions must never add native worker roots. */
|
||||
export function isWorkerConstructor(program: ts.Program, expression: ts.Expression): boolean {
|
||||
while (
|
||||
ts.isParenthesizedExpression(expression) ||
|
||||
ts.isAsExpression(expression) ||
|
||||
ts.isTypeAssertion(expression) ||
|
||||
ts.isNonNullExpression(expression)
|
||||
)
|
||||
expression = expression.expression;
|
||||
if (ts.isPropertyAccessExpression(expression))
|
||||
return expression.name.text === "Worker" && workerNamespace(program, expression.expression);
|
||||
if (!ts.isIdentifier(expression)) return false;
|
||||
if (isBuiltinMemberImport(program, expression, "worker_threads", "Worker")) return true;
|
||||
const checker = program.getTypeChecker(),
|
||||
symbol = checker.getSymbolAtLocation(expression);
|
||||
const declaration = symbol ? checker.declarationsOf(symbol)[0] : undefined;
|
||||
if (
|
||||
declaration &&
|
||||
ts.isBindingElement(declaration) &&
|
||||
ts.isObjectBindingPattern(declaration.parent)
|
||||
) {
|
||||
const variable = declaration.parent.parent;
|
||||
const member = declaration.propertyName ?? declaration.name;
|
||||
return (
|
||||
ts.isIdentifier(member) &&
|
||||
member.text === "Worker" &&
|
||||
ts.isVariableDeclaration(variable) &&
|
||||
ts.isVariableDeclarationList(variable.parent) &&
|
||||
(variable.parent.flags & ts.NodeFlags.Const) !== 0 &&
|
||||
variable.initializer !== undefined &&
|
||||
workerNamespace(program, variable.initializer)
|
||||
);
|
||||
}
|
||||
return (
|
||||
declaration !== undefined &&
|
||||
ts.isVariableDeclaration(declaration) &&
|
||||
ts.isVariableDeclarationList(declaration.parent) &&
|
||||
(declaration.parent.flags & ts.NodeFlags.Const) !== 0 &&
|
||||
declaration.initializer !== undefined &&
|
||||
ts.isPropertyAccessExpression(declaration.initializer) &&
|
||||
isWorkerConstructor(program, declaration.initializer)
|
||||
);
|
||||
}
|
||||
|
||||
export function workerModulePath(program: ts.Program, expression: ts.Expression): string | null {
|
||||
if (ts.isIdentifier(expression) && expression.text === "__filename") {
|
||||
if (isNodeGlobal(program, expression)) return resolve(expression.getSourceFile().fileName);
|
||||
}
|
||||
const value = staticForkString(program, expression);
|
||||
if (value === null) return null;
|
||||
try {
|
||||
if (value.startsWith("file:")) return fileURLToPath(value);
|
||||
// Relative string filenames are resolved against the invocation's cwd in
|
||||
// Node. Only absolute paths and source-relative URLs fix the build graph.
|
||||
return isAbsolute(value) ? resolve(value) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function workerTargetPaths(program: ts.Program, files: readonly ts.SourceFile[]): string[] {
|
||||
const targets = new Set<string>();
|
||||
const checker = program.getTypeChecker();
|
||||
for (const source of files) {
|
||||
if (source.isDeclarationFile || source.fileName.endsWith(".json")) continue;
|
||||
const constructions = moduleSourceCandidates(program, source).constructions;
|
||||
checker.prefetchSymbolNodesExact(
|
||||
constructions.flatMap(({ expression }) =>
|
||||
ts.isIdentifier(expression)
|
||||
? [expression]
|
||||
: ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.expression)
|
||||
? [expression.expression]
|
||||
: [],
|
||||
),
|
||||
);
|
||||
for (const construction of constructions) {
|
||||
const expression = construction.expression;
|
||||
if (!isWorkerConstructor(program, expression) || !construction.arguments?.[0]) continue;
|
||||
const path = workerModulePath(program, construction.arguments[0]);
|
||||
if (path !== null) targets.add(path);
|
||||
}
|
||||
}
|
||||
return [...targets];
|
||||
}
|
||||
@@ -451,6 +451,7 @@ async function compileExecutableNative(
|
||||
dgram: features.dgram,
|
||||
watch: features.watch,
|
||||
foreignFfi: features.foreignFfi,
|
||||
...(features.workers ? { workers: true } : {}),
|
||||
nodeTest: features.nodeTest,
|
||||
tls: features.tls,
|
||||
tlsCa: features.tlsCa,
|
||||
|
||||
@@ -8,6 +8,10 @@ import type { IrLibFn } from "./builtin-signatures.js";
|
||||
* seed on `dynCheck` and `awaitExpr` nodes, which throw on validation
|
||||
* failure / promise rejection). */
|
||||
export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"worker.new",
|
||||
"worker.data",
|
||||
"worker.parentPort",
|
||||
"process.exit",
|
||||
"text.encodeInto",
|
||||
"buffer.isAscii",
|
||||
"buffer.isUtf8",
|
||||
@@ -40,6 +44,10 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"buffer.byteLenDyn",
|
||||
"bytes.construct",
|
||||
"arrayBuffer.new",
|
||||
"sharedArrayBuffer.new",
|
||||
"atomics.op",
|
||||
"atomics.wait",
|
||||
"atomics.notify",
|
||||
"arrayBuffer.byteLengthGetter",
|
||||
"arrayBuffer.viewU8C",
|
||||
"arrayBuffer.viewI8",
|
||||
@@ -442,6 +450,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"dyn.getOwnPropertyDescriptor",
|
||||
"dyn.arrayProtoCall",
|
||||
"process.chdir",
|
||||
"process.umask",
|
||||
"process.loadEnvFile",
|
||||
"fs.realpathSync",
|
||||
"fs.realpathNativeSync",
|
||||
|
||||
@@ -923,19 +923,15 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
"fs.writeFileModeSync": { argTypes: [STRING, STRING, F64], result: VOID },
|
||||
"fs.mkdirModeSync": { argTypes: [STRING, F64], result: VOID },
|
||||
"fs.mkdirRecursiveModeSync": { argTypes: [STRING, F64], result: VOID },
|
||||
// Atomics.wait over an Int32Array — the synchronous-sleep idiom.
|
||||
/** Atomics.wait(int32Array, idx, expected, timeoutMs) → "not-equal"
|
||||
* when the element differs from `expected`, else a real nanosleep for
|
||||
* the timeout and "timed-out" (scr_lib.c). scriptc has no threads —
|
||||
* no other agent can ever notify, so for every compilable program this
|
||||
* IS the spec's behavior, and "ok" is unreachable; the frontend
|
||||
* REQUIRES the timeout argument (an infinite wait here is a certain
|
||||
* deadlock, fenced). The SharedArrayBuffer the lib types demand exists
|
||||
* only syntactically (new Int32Array(new SharedArrayBuffer(n)) lowers
|
||||
* to a plain i32 typed array — sharing is unobservable without
|
||||
* threads; SEMANTICS.md documents the stance). Never throws; +1 string
|
||||
* result. */
|
||||
"atomics.wait": { argTypes: [bytesOf("i32"), F64, F64, F64], result: STRING },
|
||||
"atomics.notify": { argTypes: [bytesOf("i32"), F64, F64], result: F64 },
|
||||
"atomics.op": { argTypes: [null, F64, F64, F64, F64], result: F64 },
|
||||
"worker.new": { argTypes: [F64, STRING, DYN], result: DYN },
|
||||
"worker.isMainThread": { argTypes: [], result: BOOL },
|
||||
"worker.threadId": { argTypes: [], result: F64 },
|
||||
"worker.root": { argTypes: [], result: F64 },
|
||||
"worker.data": { argTypes: [], result: DYN },
|
||||
"worker.parentPort": { argTypes: [], result: DYN },
|
||||
/** child_process.spawnSync (scr_child.c): posix_spawn + waitpid + piped
|
||||
* utf8 capture — cmd borrowed, args one borrowed string[] (the frontend
|
||||
* completes an omitted list to an empty literal), result an owned (+1)
|
||||
@@ -2611,6 +2607,8 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
"dyn.dataViewIs": { argTypes: [DYN], result: BOOL },
|
||||
"dyn.construct": { argTypes: [DYN, DYN, STRING], result: DYN },
|
||||
"arrayBuffer.constructor": { argTypes: [], result: DYN },
|
||||
"sharedArrayBuffer.new": { argTypes: [DYN], result: DYN },
|
||||
"sharedArrayBuffer.is": { argTypes: [DYN], result: BOOL },
|
||||
"arrayBuffer.new": { argTypes: [DYN], result: DYN },
|
||||
"ffi.argument": { argTypes: [DYN, STRING], result: DYN },
|
||||
"ffi.memoryModule": { argTypes: [DYN], result: DYN },
|
||||
|
||||
@@ -20,7 +20,7 @@ const expr = (value: IrExpr): IrStmt => ({ kind: "exprStmt", expr: value, loc })
|
||||
|
||||
function fixture(): IrModule {
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
globals: [{ id, name: "table", type, mutable: false }],
|
||||
|
||||
@@ -36,7 +36,7 @@ function fixture(): IrModule {
|
||||
],
|
||||
};
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: fn.name,
|
||||
functions: [fn],
|
||||
|
||||
@@ -903,7 +903,7 @@ export function isRefCounted(t: IrType): boolean {
|
||||
|
||||
export interface IrModule {
|
||||
/** Bumped on any breaking IR change; serialize.ts refuses mismatches. */
|
||||
irVersion: 13;
|
||||
irVersion: 14;
|
||||
sourceFile: string;
|
||||
functions: IrFunction[];
|
||||
/** Class shapes. Constructors and methods are ordinary module functions
|
||||
@@ -959,6 +959,8 @@ export interface IrModule {
|
||||
unions?: IrUnionDef[];
|
||||
/** Name of the synthetic function holding top-level statements. */
|
||||
entry: string;
|
||||
/** Executable roots run in isolated native worker contexts. */
|
||||
workers?: boolean;
|
||||
/** Outbound native FFI declarations used by `ffiCall` expressions.
|
||||
* These are link-time C ABI imports, not runtime dynamic-library handles:
|
||||
* executable builds resolve their symbols from the manifest's archive
|
||||
|
||||
@@ -76,15 +76,18 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run
|
||||
fileHandle: false,
|
||||
fetch: mod.embedded?.modules.some((m) => m.usesFetch === true) ?? false,
|
||||
processEvents: false,
|
||||
emitter: (mod.classes ?? []).some((c) => c.name === RUNTIME_EMITTER_CLASS),
|
||||
emitter:
|
||||
mod.workers === true || (mod.classes ?? []).some((c) => c.name === RUNTIME_EMITTER_CLASS),
|
||||
stream: (mod.classes ?? []).some((c) => RUNTIME_STREAM_CLASSES.has(c.name)),
|
||||
zlib: mod.embedded?.edges.some((e) => e.to === "node:zlib") ?? false,
|
||||
dc: false,
|
||||
assert: false,
|
||||
dynInvoke: false,
|
||||
dynAsync: mod.functions.some(
|
||||
(fn) => fn.async === true && fn.generator === undefined && fn.returnType.kind === "dyn",
|
||||
),
|
||||
dynInvoke: mod.workers === true,
|
||||
dynAsync:
|
||||
mod.workers === true ||
|
||||
mod.functions.some(
|
||||
(fn) => fn.async === true && fn.generator === undefined && fn.returnType.kind === "dyn",
|
||||
),
|
||||
inspect: false,
|
||||
childProcess: false,
|
||||
net: false,
|
||||
|
||||
@@ -40,7 +40,7 @@ const read = (localId: string, field = "a"): IrExpr => ({
|
||||
|
||||
function fixture(): IrModule {
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
records: [
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
* JSON's null/zero spellings must not erase NaN, infinities or negative zero. */
|
||||
import type { IrModule } from "./ir.js";
|
||||
|
||||
// Version 13 adds the NaN sentinel. Older readers interpret unknown tags
|
||||
// as negative infinity, so accepting a newer document would miscompile it.
|
||||
export const IR_VERSION = 13 as const;
|
||||
// Version 14 records worker execution, which requires thread-local program
|
||||
// state and matching runtime objects. Older readers must not erase that mode.
|
||||
export const IR_VERSION = 14 as const;
|
||||
|
||||
/** IR artifacts are plain data. Scan each function once before deciding
|
||||
* whether its JSON encoding needs the complete-number-domain replacer. */
|
||||
|
||||
@@ -315,7 +315,7 @@ function numericReadModule(overrides: Partial<IrExpr & { kind: "arrIntrinsic" }>
|
||||
...overrides,
|
||||
};
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [
|
||||
@@ -399,7 +399,7 @@ test.each(["sortPrimitive", "toSortedPrimitive"] as const)(
|
||||
|
||||
function expressionModule(expr: IrExpr, unions: IrUnionDef[]): IrModule {
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
unions,
|
||||
@@ -1286,7 +1286,7 @@ test.each([
|
||||
function tdzModule(mutable = true): IrModule {
|
||||
const value: IrExpr = { kind: "numLit", value: 0, type: F64, loc };
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [
|
||||
@@ -1454,7 +1454,7 @@ test("TDZ locals require a shared box", () => {
|
||||
|
||||
function discriminatedModule(): IrModule {
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [{ name: "main", params: [], locals: [], returnType: VOID, body: [], loc }],
|
||||
|
||||
@@ -134,7 +134,24 @@ export function validateModule(mod: IrModule): IrValidationError[] {
|
||||
const ffiByName = new Map<string, NonNullable<IrModule["ffiImports"]>[number]>();
|
||||
const ffiSymbols = new Set<string>();
|
||||
const moduleLoc: SrcLoc = { file: mod.sourceFile, start: 0, end: 0 };
|
||||
if (mod.workers !== undefined && typeof mod.workers !== "boolean")
|
||||
errors.push({ message: "worker execution mode must be a boolean", loc: moduleLoc });
|
||||
if (mod.workers && (mod.lib !== undefined || mod.embedded !== undefined))
|
||||
errors.push({
|
||||
message: "worker execution requires a native static executable",
|
||||
loc: moduleLoc,
|
||||
});
|
||||
for (const entry of mod.ffiImports ?? []) {
|
||||
if (
|
||||
mod.workers &&
|
||||
entry.params.some(
|
||||
(parameter) => isFfiCallbackParam(parameter) && parameter.callback.invoke === "foreign",
|
||||
)
|
||||
)
|
||||
errors.push({
|
||||
message: "worker execution cannot install foreign-thread callbacks",
|
||||
loc: moduleLoc,
|
||||
});
|
||||
if (ffiByName.has(entry.name)) {
|
||||
errors.push({ message: `duplicate FFI binding "${entry.name}"`, loc: moduleLoc });
|
||||
}
|
||||
@@ -968,6 +985,7 @@ export function validateModule(mod: IrModule): IrValidationError[] {
|
||||
globalsById,
|
||||
classValidation,
|
||||
errors,
|
||||
mod.workers === true,
|
||||
);
|
||||
}
|
||||
return errors;
|
||||
@@ -984,6 +1002,7 @@ function validateFunction(
|
||||
globals: Map<string, IrGlobal>,
|
||||
classValidation: ClassValidation,
|
||||
errors: IrValidationError[],
|
||||
workers: boolean,
|
||||
): void {
|
||||
const locals = new Map(fn.locals.map((l) => [l.id, l]));
|
||||
const err = (message: string, loc: SrcLoc) =>
|
||||
@@ -3711,6 +3730,15 @@ function validateFunction(
|
||||
}
|
||||
|
||||
function checkLibCall(e: IrExpr & { kind: "libCall" }): void {
|
||||
if (
|
||||
e.fn === "atomics.op" &&
|
||||
(e.args[0]?.type.kind !== "bytes" ||
|
||||
!["u8", "i8", "u16", "i16", "u32", "i32"].includes(e.args[0].type.elem))
|
||||
) {
|
||||
errors.push({ message: "integer Atomics require an integer typed array", loc: e.loc });
|
||||
}
|
||||
if (e.fn === "worker.new" && !workers)
|
||||
errors.push({ message: "Worker construction requires worker execution mode", loc: e.loc });
|
||||
const sig = LIB_FN_SIGS[e.fn];
|
||||
if (!sig) {
|
||||
err(`libCall of unknown library function "${e.fn as string}"`, e.loc);
|
||||
|
||||
@@ -15,7 +15,7 @@ function program(member: string): IrModule {
|
||||
loc,
|
||||
};
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "probe",
|
||||
functions: [
|
||||
|
||||
@@ -97,7 +97,7 @@ const sink = (name: string): IrFunction => ({
|
||||
/** A module holding the case function plus the two declared sinks. */
|
||||
function caseModule(params: string[], locals: string[], body: IrStmt[]): IrModule {
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: "corpus.ts",
|
||||
functions: [
|
||||
sink("send"),
|
||||
@@ -196,7 +196,7 @@ const RECORD_CFG: IntSlotConfig = {
|
||||
|
||||
function recordCase(body: IrStmt[], names = ["m"], extraFns: IrFunction[] = []): IrModule {
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: "fields.ts",
|
||||
functions: [
|
||||
...extraFns,
|
||||
@@ -237,7 +237,7 @@ const classCountRead = (): IrExpr => ({
|
||||
|
||||
function onlyOrdinaryClass(body: IrStmt[]): IntVerdict {
|
||||
const mod: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: "class-fields.ts",
|
||||
functions: [
|
||||
sink("send"),
|
||||
@@ -581,7 +581,7 @@ describe("the domain's edges beyond the corpus", () => {
|
||||
loc,
|
||||
};
|
||||
const mod: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: "optional.ts",
|
||||
functions: [
|
||||
{
|
||||
|
||||
@@ -59,6 +59,7 @@ const LIB_MODE_REFUSED_PREFIXES: readonly [string, string][] = [
|
||||
["als.", "AsyncLocalStorage"],
|
||||
["urj.", "unhandled-rejection tracking"],
|
||||
["dc.", "the diagnostics_channel surface"],
|
||||
["worker.new", "native worker threads"],
|
||||
];
|
||||
|
||||
/** Value/type kinds whose mere presence means an excluded unit's code (or
|
||||
@@ -131,6 +132,7 @@ export function moduleLibAsyncSurface(mod: IrModule): { surface: string; loc: Sr
|
||||
// fine-grained table missed must still refuse.
|
||||
const features = moduleRuntimeFeatures(mod);
|
||||
const coarse: [boolean, string][] = [
|
||||
[mod.workers === true, "native worker threads"],
|
||||
[features.processEvents, "process signal/exit listeners or the stdin event surface"],
|
||||
[features.net, "the node:net surface"],
|
||||
[features.http, "the node:http surface"],
|
||||
|
||||
@@ -261,7 +261,7 @@ test("semantic library cache restores and rebases IR after a comment-only edit",
|
||||
const sourceBefore = await readFile(f.source, "utf8");
|
||||
const returnStart = sourceBefore.indexOf("return");
|
||||
const semanticMod = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: f.source,
|
||||
functions: [
|
||||
{
|
||||
@@ -324,7 +324,7 @@ test("semantic library cache refuses token and directive edits", async () => {
|
||||
const f = await fixture();
|
||||
const sourceBefore = await readFile(f.source, "utf8");
|
||||
const semanticMod = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: f.source,
|
||||
functions: [
|
||||
{
|
||||
@@ -372,7 +372,7 @@ test("semantic LLVM cache accepts comment edits that shift source lines", async
|
||||
const f = await fixture();
|
||||
const sourceBefore = await readFile(f.source, "utf8");
|
||||
const semanticMod = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: f.source,
|
||||
functions: [
|
||||
{
|
||||
@@ -447,7 +447,7 @@ test("semantic LLVM cache accepts line separator normalization", async () => {
|
||||
frontend: tracker.snapshot(),
|
||||
semantic: {
|
||||
mod: {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: f.source,
|
||||
functions: [],
|
||||
entry: "__main",
|
||||
@@ -468,7 +468,7 @@ test("semantic LLVM cache rebases comment edits in multi-source graphs", async (
|
||||
const importedSource = "export function helper(): number { return 1; }\n";
|
||||
await writeFile(imported, importedSource);
|
||||
const semanticMod = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: f.source,
|
||||
functions: [
|
||||
{
|
||||
|
||||
@@ -60,7 +60,7 @@ test.each([
|
||||
writeFileSync(output, "previous executable");
|
||||
writeFileSync(llvmPath, "previous LLVM");
|
||||
const module: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: entry,
|
||||
entry: "%main",
|
||||
functions: [
|
||||
@@ -197,7 +197,7 @@ test.each([false, true])(
|
||||
writeFileSync(output, "previous object");
|
||||
writeFileSync(llvmPath, "previous LLVM");
|
||||
const module: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: entry,
|
||||
entry: "%main",
|
||||
functions: [
|
||||
|
||||
@@ -60,6 +60,7 @@ function implementationIdentity(toolchain: NativeToolchain): string {
|
||||
function validFeatures(value: unknown): value is NativeLinkFeatures {
|
||||
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
||||
const features = value as Record<string, unknown>;
|
||||
if (features["workers"] !== undefined && typeof features["workers"] !== "boolean") return false;
|
||||
return [
|
||||
"dynamic",
|
||||
"regex",
|
||||
|
||||
@@ -72,6 +72,7 @@ test.skipIf(process.platform !== "linux" && process.platform !== "darwin")(
|
||||
archives: [artifact],
|
||||
systemLibraries: [],
|
||||
features: {
|
||||
workers: false,
|
||||
dynamic: false,
|
||||
regex: true,
|
||||
copying: false,
|
||||
|
||||
@@ -1704,7 +1704,7 @@
|
||||
"kind": "node-builtin",
|
||||
"name": "process.getBuiltinModule",
|
||||
"status": "static",
|
||||
"note": "native path and os export subsets plus main-thread worker_threads metadata; other modules and exports throw SC2020"
|
||||
"note": "native path and os export subsets plus current-thread worker_threads metadata; other modules and exports throw SC2020"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.process.getgid",
|
||||
@@ -2233,6 +2233,13 @@
|
||||
"status": "static",
|
||||
"note": "recognized module (bare and node:-prefixed specifiers)"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.worker_threads.Worker",
|
||||
"kind": "node-builtin",
|
||||
"name": "worker_threads.Worker",
|
||||
"status": "static",
|
||||
"note": "native threads with statically compiled entry points, workerData, argv and message events"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.worker_threads.isMainThread",
|
||||
"kind": "node-builtin",
|
||||
@@ -2240,6 +2247,13 @@
|
||||
"status": "static",
|
||||
"note": "constant value read"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.worker_threads.metadata",
|
||||
"kind": "node-builtin",
|
||||
"name": "worker_threads metadata",
|
||||
"status": "static",
|
||||
"note": "current-thread metadata and the worker's parent message port"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.worker_threads.threadId",
|
||||
"kind": "node-builtin",
|
||||
@@ -2639,6 +2653,13 @@
|
||||
"name": "Array.prototype.with",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.atomics",
|
||||
"kind": "stdlib",
|
||||
"name": "Atomics",
|
||||
"status": "static",
|
||||
"note": "8-, 16- and 32-bit integer operations, plus Int32Array wait and notify"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.date.UTC",
|
||||
"kind": "stdlib",
|
||||
@@ -3994,6 +4015,13 @@
|
||||
"status": "static",
|
||||
"note": "live native iterators support next(), for...of, and drains; iterator helper methods are refused"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.sharedArrayBuffer",
|
||||
"kind": "stdlib",
|
||||
"name": "SharedArrayBuffer",
|
||||
"status": "static",
|
||||
"note": "fixed shared storage with context-local typed-array and DataView wrappers"
|
||||
},
|
||||
{
|
||||
"id": "stdlib.string.at",
|
||||
"kind": "stdlib",
|
||||
|
||||
@@ -75,7 +75,7 @@ function fixture(): IrModule {
|
||||
);
|
||||
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "__main",
|
||||
functions: [{ name: "__main", params: [], returnType: VOID, locals, body, loc }],
|
||||
@@ -145,7 +145,7 @@ function receiverReassignmentFixture(): IrModule {
|
||||
];
|
||||
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "__main",
|
||||
functions: [{ name: "__main", params: [], returnType: VOID, locals, body, loc }],
|
||||
@@ -212,7 +212,7 @@ function integerLoopFixture(mutatesIndex = false): IrModule {
|
||||
{ kind: "bytesSet", arr: bytesRef(), index: indexRef(), value: ref("sum"), loc },
|
||||
);
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "__main",
|
||||
functions: [
|
||||
@@ -271,7 +271,7 @@ test("LLVM emission performs typed-array element access directly on the valid pa
|
||||
const mod = fixture();
|
||||
expect(validateModule(mod)).toEqual([]);
|
||||
const ll = emitLlvmModule(mod);
|
||||
expect(ll).toContain("%ScrBytes = type { i64, i64, i32, ptr, ptr, i8 }");
|
||||
expect(ll).toContain("%ScrBytes = type { i64, i64, i32, ptr, ptr, i8, i8, i8, ptr }");
|
||||
expect(ll).toContain("getelementptr inbounds i8");
|
||||
expect(ll).toContain("getelementptr inbounds i32");
|
||||
expect(ll).toContain("getelementptr inbounds float");
|
||||
@@ -333,7 +333,7 @@ test("large record clones stay outlined while small clones remain inlineable", (
|
||||
const fields = Array.from({ length: count }, (_, i) => ({ name: `f${i}`, type: F64 }));
|
||||
const type = { kind: "record", shapeId: id } as const;
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: "record-clone.ts",
|
||||
entry: "__main",
|
||||
records: [{ id, fields }],
|
||||
|
||||
@@ -35,7 +35,7 @@ test("strings: literals, concat in a loop, toString, RC-clean under audit", asyn
|
||||
// while (i < 3) { acc = acc + ("-" + i); i = i + 1; }
|
||||
// console.log(acc, acc === "x-0-1-2", "α∂" < "β");
|
||||
const mod: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: "s.ts",
|
||||
entry: "__main",
|
||||
functions: [
|
||||
@@ -137,7 +137,7 @@ test("short-circuit: right operand of && only evaluates when left is true", asyn
|
||||
// if (false && sideEffect()) {} ; if (true || sideEffect()) {}
|
||||
// console.log("done")
|
||||
const mod: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: "l.ts",
|
||||
entry: "__main",
|
||||
functions: [
|
||||
@@ -227,7 +227,7 @@ test("string params: callee owns and releases; returns transfer ownership", asyn
|
||||
// function greet(who: string): string { return "hi " + who; }
|
||||
// console.log(greet("world"));
|
||||
const mod: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: "p.ts",
|
||||
entry: "__main",
|
||||
functions: [
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ const n = (localId: string): IrExpr => ({ kind: "varRef", localId, type: F64, lo
|
||||
const num = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc });
|
||||
|
||||
export const fibModule: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: "fib.ts",
|
||||
entry: "__main",
|
||||
functions: [
|
||||
|
||||
@@ -7,7 +7,7 @@ test("scalar fromCharCode avoids a heap argument pack in LLVM", () => {
|
||||
const loc = { file: "scalar-char.ts", start: 0, end: 0 };
|
||||
const value: IrExpr = { kind: "numLit", value: 65, type: F64, loc };
|
||||
const mod: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "__main",
|
||||
globals: [],
|
||||
|
||||
@@ -22,7 +22,7 @@ test("fib module JSON round-trips", () => {
|
||||
test("validator rejects type mismatches and bad references", () => {
|
||||
const loc = { file: "t.ts", start: 0, end: 0 };
|
||||
const bad: IrModule = {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: "t.ts",
|
||||
entry: "__main",
|
||||
functions: [
|
||||
@@ -108,7 +108,7 @@ test("serializer round-trips ±Infinity and NaN", () => {
|
||||
});
|
||||
|
||||
test("deserializer rejects the previous IR version", () => {
|
||||
const json = serializeModule(fibModule).replace('"irVersion": 13', '"irVersion": 12');
|
||||
const json = serializeModule(fibModule).replace('"irVersion": 14', '"irVersion": 13');
|
||||
expect(() => deserializeModule(json)).toThrow(/version mismatch/);
|
||||
});
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ const dyn = (): IrExpr => ({ kind: "dynFrom", value: str("value"), type: DYN, lo
|
||||
|
||||
function libCallModule(fn: IrLibFn, args: IrExpr[], type: IrType): IrModule {
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "__main",
|
||||
functions: [
|
||||
|
||||
@@ -61,6 +61,8 @@ const EMIT_FIXTURES = [
|
||||
"996-fs-rc-stress.ts",
|
||||
"997-fs-modules/main.ts",
|
||||
"1006-json-fs-config.ts",
|
||||
"native-worker-roots/main.ts",
|
||||
"native-worker-shared-views.ts",
|
||||
];
|
||||
|
||||
interface CProto {
|
||||
@@ -158,6 +160,7 @@ function splitParams(argsText: string): string[] {
|
||||
async function parseHeader(): Promise<{ protos: Map<string, CProto>; dataSyms: Set<string> }> {
|
||||
const raw = await readFile(headerPath, "utf8");
|
||||
const src = raw
|
||||
.replace(/\\\r?\n/g, "")
|
||||
.replace(/\/\*[\s\S]*?\*\//g, " ")
|
||||
.replace(/\/\/[^\n]*/g, " ")
|
||||
.replace(/^[ \t]*#[^\n]*$/gm, " ");
|
||||
@@ -317,19 +320,22 @@ describe("LLVM backend declares match scr_runtime.h prototypes", () => {
|
||||
#include <stdint.h>
|
||||
#include "scr_runtime.h"
|
||||
|
||||
/* LLVM emits: %ScrBytes = type { i64, i64, i32, ptr, ptr, i8 }.
|
||||
/* LLVM emits: %ScrBytes = type { i64, i64, i32, ptr, ptr, i8, i8, i8, ptr }.
|
||||
* Keep every ABI fact used by its field-index GEPs explicit here. */
|
||||
_Static_assert(sizeof(size_t) == 8, "LLVM ScrBytes expects 64-bit size_t");
|
||||
_Static_assert(sizeof(void *) == 8, "LLVM ScrBytes expects 64-bit pointers");
|
||||
_Static_assert(sizeof(ScrBytesElem) == 4, "LLVM ScrBytes elem field is i32");
|
||||
_Static_assert(_Alignof(ScrBytes) == 8, "LLVM ScrBytes alignment changed");
|
||||
_Static_assert(sizeof(ScrBytes) == 48, "LLVM ScrBytes size changed");
|
||||
_Static_assert(sizeof(ScrBytes) == 56, "LLVM ScrBytes size changed");
|
||||
_Static_assert(offsetof(ScrBytes, rc) == 0, "LLVM ScrBytes.rc offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, len) == 8, "LLVM ScrBytes.len offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, elem) == 16, "LLVM ScrBytes.elem offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, data) == 24, "LLVM ScrBytes.data offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, backing) == 32, "LLVM ScrBytes.backing offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, is_buffer) == 40, "LLVM ScrBytes.is_buffer offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, is_data_view) == 41, "LLVM ScrBytes.is_data_view offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, external) == 42, "LLVM ScrBytes.external offset changed");
|
||||
_Static_assert(offsetof(ScrBytes, shared) == 48, "LLVM ScrBytes.shared offset changed");
|
||||
`,
|
||||
);
|
||||
const driver = resolveCc();
|
||||
|
||||
@@ -312,7 +312,7 @@ ${[0, 1, 2, 3, 4, 5].map((index) => ` call void @add${index}(i64 ${index + 2})`
|
||||
expect(await symbols(helperObject, ["-u"])).toEqual(await symbols(clangObject, ["-u"]));
|
||||
expect(await symbols(helperObject, ["-gU"])).toEqual(await symbols(clangObject, ["-gU"]));
|
||||
expect(await symbols(helperObject, ["-gU"])).toEqual(["0000000000000000 T _main"]);
|
||||
expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v6");
|
||||
expect(await symbols(helperObject, ["-u"])).toContain("_scr_runtime_abi_v7");
|
||||
|
||||
const clangExe = join(dir, "clang-program");
|
||||
const linkDriver = join(dir, "clang-driver.c");
|
||||
@@ -646,7 +646,7 @@ ${[0, 1, 2, 3, 4, 5].map((index) => ` call void @add${index}(i64 ${index + 2})`
|
||||
.split("\n");
|
||||
expect(undefinedSymbols).toContain("_sf_scale");
|
||||
expect(undefinedSymbols).toContain("_sf_callback_mix");
|
||||
expect(undefinedSymbols).toContain("_scr_runtime_abi_v6");
|
||||
expect(undefinedSymbols).toContain("_scr_runtime_abi_v7");
|
||||
});
|
||||
|
||||
test("helper and clang object paths retain the same outbound FFI C ABI", async () => {
|
||||
|
||||
@@ -86,7 +86,7 @@ function fixture(): IrModule {
|
||||
.find((fn) => fn.name === "negative")!
|
||||
.body.splice(1, 0, { kind: "varDecl", localId: "other", init: str("other"), loc });
|
||||
return {
|
||||
irVersion: 13,
|
||||
irVersion: 14,
|
||||
sourceFile: loc.file,
|
||||
entry: "__main",
|
||||
globals: [{ id: "%g.e.acc", name: "globalAccumulator", type: STRING, mutable: true }],
|
||||
|
||||
@@ -14517,6 +14517,126 @@
|
||||
"<repo>/tests/corpus/scalar-string-slices.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/atomics-integer-operations.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/atomics-integer-operations.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-buffer-consumers.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-buffer-consumers.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-clone-errors.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-clone-errors.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-commonjs.cjs": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-commonjs.cjs"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-delayed-messages.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-delayed-messages.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-environment.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-environment.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-error.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-error.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-exit-listeners.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-exit-listeners.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-io.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-io.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-loop-cancellation.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-loop-cancellation.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-message-graphs.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-message-graphs.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-messages.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-messages.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-port-lifetime.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-port-lifetime.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-process-exit.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-process-exit.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-references.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-references.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-roots/main.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-roots/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-shared-views.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-shared-views.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-terminate-loop.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-terminate-loop.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-terminate-suspended.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-terminate-suspended.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/native-worker-terminate-wait.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/native-worker-terminate-wait.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
* sweep and the upgrade playbook's step 2. */
|
||||
|
||||
import { execFile } from "node:child_process";
|
||||
import { globSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
@@ -47,6 +47,7 @@ import { afterAll, describe, expect, test } from "vitest";
|
||||
import { checkPreflightTs7 } from "../../src/frontend/program-node.js";
|
||||
import { Ts7Host } from "../../src/frontend/ts7/program-adapter.js";
|
||||
import type { ScrDiagnostic } from "../../src/diagnostics/diagnostic.js";
|
||||
import { allOrderEntries, orderEntriesUnder } from "../../../../scripts/ts7-order-fixtures.mjs";
|
||||
|
||||
const repoRoot = join(import.meta.dirname, "../../../..");
|
||||
const baselinePath = join(import.meta.dirname, "baselines/order-parity.json");
|
||||
@@ -111,25 +112,13 @@ async function nativeAnswers(entries: string[]): Promise<BaselineEntry[]> {
|
||||
}
|
||||
|
||||
function entriesUnder(dir: string): string[] {
|
||||
const exts = ["ts", "js", "mjs", "cjs"];
|
||||
return exts
|
||||
.flatMap((ext) => [
|
||||
...globSync(join(repoRoot, dir, `*.${ext}`)),
|
||||
...globSync(join(repoRoot, dir, `*/main.${ext}`)),
|
||||
])
|
||||
.sort();
|
||||
return orderEntriesUnder(repoRoot, dir);
|
||||
}
|
||||
|
||||
const FULL = process.env["SCRIPTC_TS7_ALL"] === "1" || UPDATE;
|
||||
|
||||
function allEntries(): string[] {
|
||||
return [
|
||||
...entriesUnder("tests/corpus"),
|
||||
...entriesUnder("tests/diagnostics"),
|
||||
...entriesUnder("tests/fixtures/npm/cases"),
|
||||
...globSync(join(repoRoot, "tests/fixtures/strictness/*/main.ts")).sort(),
|
||||
...globSync(join(repoRoot, "tests/fixtures/node-types/*.ts")).sort(),
|
||||
];
|
||||
return allOrderEntries(repoRoot);
|
||||
}
|
||||
|
||||
function pickEntries(): string[] {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user