mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-27 06:43:19 +08:00
Merge main
This commit is contained in:
+93
-29
@@ -4,21 +4,23 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
# Nightly full macOS sweep, including on days without a push to main.
|
||||
# Nightly full macOS sweep. Pushes to main run no macOS jobs, and pull requests run them only when they change
|
||||
# darwin code (scripts/ci-lanes.mjs).
|
||||
schedule:
|
||||
- cron: "17 7 * * *"
|
||||
# A manual run; macos_full runs the complete suite on macOS as the nightly sweep does (for example on a
|
||||
# release candidate commit).
|
||||
# A manual run, for example on a release candidate commit. It runs every lane; macos_suite selects the complete
|
||||
# suite on macOS, as the nightly sweep runs it, or the darwin suite that pull requests run.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
macos_full:
|
||||
description: Run the complete test suite on macOS
|
||||
type: boolean
|
||||
default: false
|
||||
macos_suite:
|
||||
description: The test suite to run on macOS
|
||||
type: choice
|
||||
options: [full, darwin]
|
||||
default: full
|
||||
|
||||
concurrency:
|
||||
# A new push to a pull request cancels its previous run. Every push to main runs to completion in a group of
|
||||
# its own commit, so each merged commit gets a full result (releases require a green run on their commit). The
|
||||
# its own commit, so each merged commit gets a result (releases require a green run on their commit). The
|
||||
# nightly sweep and manual runs neither cancel nor are cancelled by main pushes.
|
||||
group: ci-${{ github.event_name == 'schedule' && 'schedule' || github.event_name == 'workflow_dispatch' && format('dispatch-{0}', github.run_id) || github.event.pull_request.number || github.sha }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
@@ -59,7 +61,8 @@ jobs:
|
||||
NODE
|
||||
|
||||
# Which expensive lanes a pull request needs (scripts/ci-lanes.mjs, run from the base commit against the pull
|
||||
# request's diff, so a pull request cannot change its own selection). Pushes and the nightly sweep run every lane.
|
||||
# request's diff, so a pull request cannot change its own selection). Pushes to main run every lane except macos,
|
||||
# whose jobs the nightly sweep runs on main. The nightly sweep and manual runs run every lane.
|
||||
lanes:
|
||||
needs: validation_scope
|
||||
if: ${{ needs.validation_scope.outputs.full == 'true' }}
|
||||
@@ -86,7 +89,9 @@ jobs:
|
||||
HEAD: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
every() { printf 'linux=true\nmacos=true\neffects=true\nbootstrap=true\n' | tee -a "$GITHUB_OUTPUT"; }
|
||||
if [ "$EVENT" != pull_request ]; then
|
||||
if [ "$EVENT" = push ]; then
|
||||
printf 'linux=true\nmacos=false\neffects=true\nbootstrap=true\n' | tee -a "$GITHUB_OUTPUT"
|
||||
elif [ "$EVENT" != pull_request ]; then
|
||||
every
|
||||
elif git show "$BASE:scripts/ci-lanes.mjs" > "$RUNNER_TEMP/ci-lanes.mjs" 2> /dev/null; then
|
||||
node "$RUNNER_TEMP/ci-lanes.mjs" --base "$BASE" --head "$HEAD" --github-output
|
||||
@@ -219,25 +224,25 @@ jobs:
|
||||
tests_macos:
|
||||
needs: [validation_scope, lanes, native_macos]
|
||||
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.macos == 'true' }}
|
||||
name: test (macOS, ${{ matrix.flavor }}, ${{ (github.event_name != 'schedule' && !inputs.macos_full) && 'darwin ' || '' }}${{ matrix.shard }}/${{ (github.event_name != 'schedule' && !inputs.macos_full) && '3' || '10' }})
|
||||
name: test (macOS, ${{ matrix.flavor }}, ${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin ' || '' }}${{ matrix.shard }}/${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && '3' || '10' }})
|
||||
# arm64 macOS: same clang/libc the differential + number-formatting
|
||||
# oracles were verified against. Pull requests and pushes to main run the
|
||||
# darwin suite (scripts/ci-test-plan.mjs) in three slices; the nightly
|
||||
# schedule and manual runs with macos_full run the complete suite in ten.
|
||||
# The complete suite on every push needed more macOS runners than the
|
||||
# repository has.
|
||||
# oracles were verified against. Pull requests that change darwin code and
|
||||
# manual runs with macos_suite set to darwin run the darwin suite
|
||||
# (scripts/ci-test-plan.mjs) in three slices; the nightly schedule and
|
||||
# other manual runs run the complete suite in ten. The complete suite on
|
||||
# every push needed more macOS runners than the repository has.
|
||||
runs-on: macos-15
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
flavor: [plain, san]
|
||||
shard: ${{ fromJSON((github.event_name != 'schedule' && !inputs.macos_full) && '[1, 2, 3]' || '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]') }}
|
||||
shard: ${{ fromJSON((github.event_name != 'schedule' && inputs.macos_suite != 'full') && '[1, 2, 3]' || '[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]') }}
|
||||
env:
|
||||
# See the Linux tests job for the worker cap and case partitioning.
|
||||
SCRIPTC_TEST_WORKERS: "4"
|
||||
SCRIPTC_SAN: ${{ matrix.flavor == 'san' && '1' || '' }}
|
||||
SCRIPTC_TEST_SHARD: ${{ matrix.shard }}/${{ (github.event_name != 'schedule' && !inputs.macos_full) && '3' || '10' }}
|
||||
SCRIPTC_CI_SUITE: ${{ (github.event_name != 'schedule' && !inputs.macos_full) && 'darwin' || 'full' }}
|
||||
SCRIPTC_TEST_SHARD: ${{ matrix.shard }}/${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && '3' || '10' }}
|
||||
SCRIPTC_CI_SUITE: ${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin' || 'full' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
@@ -254,7 +259,7 @@ jobs:
|
||||
path: ${{ runner.temp }}
|
||||
- run: tar -xzf "$RUNNER_TEMP/native-darwin-arm64.tar.gz"
|
||||
- run: pnpm build
|
||||
- name: Tests (${{ (github.event_name != 'schedule' && !inputs.macos_full) && 'darwin suite' || 'corpus and integration suites' }})
|
||||
- name: Tests (${{ (github.event_name != 'schedule' && inputs.macos_suite != 'full') && 'darwin suite' || 'corpus and integration suites' }})
|
||||
run: node scripts/ci-test.mjs
|
||||
|
||||
native_linux:
|
||||
@@ -498,6 +503,30 @@ jobs:
|
||||
- name: TypeScript 7 full parity sweep
|
||||
run: pnpm test:ts7
|
||||
|
||||
# tsc-ts, the default checker, and tsgo (SCRIPTC_CHECKER=tsgo) serve the same TypeScript 7 API. Every corpus
|
||||
# program must compile to the same LLVM IR, with the same diagnostics, with either checker.
|
||||
checker_identity:
|
||||
needs: [validation_scope, lanes]
|
||||
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }}
|
||||
name: test (tsgo and tsc-ts give the same IR)
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 11.1.3
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: pnpm
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm build
|
||||
- name: Corpus LLVM IR with tsgo and with tsc-ts (release)
|
||||
run: pnpm test:checker-identity --optimization release
|
||||
- name: Corpus LLVM IR with tsgo and with tsc-ts (dev)
|
||||
run: pnpm test:checker-identity --optimization dev
|
||||
|
||||
llvm_artifacts_linux_x64:
|
||||
needs: [validation_scope, lanes]
|
||||
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.linux == 'true' }}
|
||||
@@ -844,14 +873,25 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- runtime: runtime-ios-arm64
|
||||
runner: macos-15
|
||||
- runtime: runtime-ios-simulator-arm64
|
||||
runner: macos-15
|
||||
- runtime: runtime-android-arm64
|
||||
runner: ubuntu-24.04
|
||||
runs-on: ${{ matrix.runner }}
|
||||
runtime: [runtime-android-arm64]
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/mobile-runtime-pack
|
||||
with:
|
||||
runtime: ${{ matrix.runtime }}
|
||||
|
||||
# The iOS runtime packs build on macOS runners, so they run in the macos lane.
|
||||
mobile_runtime_packs_ios:
|
||||
needs: [validation_scope, lanes]
|
||||
if: ${{ needs.validation_scope.outputs.full == 'true' && needs.lanes.outputs.macos == 'true' }}
|
||||
name: test (${{ matrix.runtime }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
runtime: [runtime-ios-arm64, runtime-ios-simulator-arm64]
|
||||
runs-on: macos-15
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -863,7 +903,29 @@ jobs:
|
||||
# Require this check on the stack base. Upper layers must not pass before
|
||||
# full validation, so untested groups cannot merge. Merge bottom-up.
|
||||
test:
|
||||
needs: [validation_scope, lanes, code_quality, native_macos, tests, tests_macos, native_linux, effects, bootstrap_seed, bootstrap, linux_host_clang, ts7_parity, llvm_artifacts_linux_x64, llvm_artifacts_macos_arm64, llvm_artifacts_macos_x64, windows_cli, llvm_artifacts_windows_x64, mobile_runtime_packs]
|
||||
needs:
|
||||
[
|
||||
validation_scope,
|
||||
lanes,
|
||||
code_quality,
|
||||
native_macos,
|
||||
tests,
|
||||
tests_macos,
|
||||
native_linux,
|
||||
effects,
|
||||
bootstrap_seed,
|
||||
bootstrap,
|
||||
linux_host_clang,
|
||||
ts7_parity,
|
||||
checker_identity,
|
||||
llvm_artifacts_linux_x64,
|
||||
llvm_artifacts_macos_arm64,
|
||||
llvm_artifacts_macos_x64,
|
||||
windows_cli,
|
||||
llvm_artifacts_windows_x64,
|
||||
mobile_runtime_packs,
|
||||
mobile_runtime_packs_ios,
|
||||
]
|
||||
# Run even after failed or cancelled lanes: skipped required jobs allow merging.
|
||||
if: ${{ always() }}
|
||||
runs-on: ubuntu-latest
|
||||
@@ -887,12 +949,14 @@ jobs:
|
||||
lane bootstrap "${{ needs.bootstrap.result }}" "${{ needs.lanes.outputs.bootstrap }}"
|
||||
lane linux_host_clang "${{ needs.linux_host_clang.result }}" "${{ needs.lanes.outputs.linux }}"
|
||||
lane ts7_parity "${{ needs.ts7_parity.result }}" "${{ needs.lanes.outputs.linux }}"
|
||||
lane checker_identity "${{ needs.checker_identity.result }}" "${{ needs.lanes.outputs.linux }}"
|
||||
lane llvm_artifacts_linux_x64 "${{ needs.llvm_artifacts_linux_x64.result }}" "${{ needs.lanes.outputs.linux }}"
|
||||
lane llvm_artifacts_macos_arm64 "${{ needs.llvm_artifacts_macos_arm64.result }}" "${{ needs.lanes.outputs.macos }}"
|
||||
lane llvm_artifacts_macos_x64 "${{ needs.llvm_artifacts_macos_x64.result }}" "${{ needs.lanes.outputs.macos }}"
|
||||
lane windows_cli "${{ needs.windows_cli.result }}" "${{ needs.lanes.outputs.linux }}"
|
||||
lane llvm_artifacts_windows_x64 "${{ needs.llvm_artifacts_windows_x64.result }}" "${{ needs.lanes.outputs.linux }}"
|
||||
lane mobile_runtime_packs "${{ needs.mobile_runtime_packs.result }}" "${{ needs.lanes.outputs.linux }}"
|
||||
lane mobile_runtime_packs_ios "${{ needs.mobile_runtime_packs_ios.result }}" "${{ needs.lanes.outputs.macos }}"
|
||||
;;
|
||||
false)
|
||||
echo "::notice::Full CI is deferred until this PR becomes the lowest unmerged layer. Merge the stack from the bottom up."
|
||||
|
||||
@@ -134,7 +134,7 @@ Verifies and loads the installed precompiled runtime objects and vendored librar
|
||||
<dd>Coverage only. After the report, list the functions that already operate on values typed only at runtime and the bindings, fields, functions, classes and modules that unsupported constructs would move to the dynamic tier, each with the diagnostic behind it. With a name such as <code>x</code>, <code>Point</code>, or <code>Point.x</code>, show only the matching units. See <a href="/docs/coverage#dynamic-tier">Dynamic tier</a>.</dd>
|
||||
|
||||
<dt><code>--wasm <file.wasm></code></dt>
|
||||
<dd>Coverage only. Report a WebAssembly module the program loads from a path its sources do not spell out. The report already lists modules whose paths the compiler resolves, modules inlined as base64 literals, and <code>.wasm</code> files shipped by packages that use WebAssembly; a <code>scriptc.wasm</code> array in <code>package.json</code> adds files the same way as this flag. Each module is listed with its size, whether it validates, and the WebAssembly proposals it uses. scriptc does not compile WebAssembly modules to native code yet. Repeatable.</dd>
|
||||
<dd>Coverage only. Report a WebAssembly module the program loads from a path its sources do not spell out. The report already lists modules whose paths the compiler resolves, modules inlined as base64 literals, and <code>.wasm</code> files shipped by packages that use WebAssembly; a <code>scriptc.wasm</code> array in <code>package.json</code> adds files the same way as this flag. Each module is listed with its size, whether it validates, and the WebAssembly proposals it uses. Builds translate the modules they find to native code; a build reads the <code>scriptc.wasm</code> array but not this flag. Repeatable.</dd>
|
||||
|
||||
<dt><code>--sanitize</code></dt>
|
||||
<dd>Build an executable with AddressSanitizer and a reference-count audit at exit, the same checks scriptc's own test suite uses. Requires a C compiler and is available only for host builds. Not supported with <code>--emit=asm</code> or <code>--emit=obj</code>.</dd>
|
||||
@@ -178,6 +178,9 @@ Verifies and loads the installed precompiled runtime objects and vendored librar
|
||||
|
||||
<dt><code>SCRIPTC_RUNTIME_PACK</code></dt>
|
||||
<dd>A runtime pack directory to use instead of an installed <code>@scriptc/runtime-<target></code> package.</dd>
|
||||
|
||||
<dt><code>SCRIPTC_CHECKER</code></dt>
|
||||
<dd>The TypeScript 7 API server that checks the program: <code>tsc-ts</code> (the default), which scriptc includes, or <code>tsgo</code>, from the <code>typescript</code> package scriptc also includes. Both give the same diagnostics and generated code. On Windows the default is <code>tsgo</code>, because tsc-ts's API server does not yet accept the named pipe scriptc connects over there.</dd>
|
||||
</dl>
|
||||
|
||||
```console
|
||||
|
||||
@@ -79,7 +79,6 @@ Some code compiles but can behave differently than under Node.js. Coverage repor
|
||||
- `SC6002`: values of one record type are built in different key orders, so the type keeps its declaration order, an object literal's key order differs from it, and the program observes the order through `JSON.stringify`, `Object.keys`, `Object.values`, `Object.entries`, `for...in`, or console output.
|
||||
- `SC6003`: `localeCompare` compares a string literal outside the built-in Latin-script collation.
|
||||
- `SC6004`: a call passes a class instance to a function that casts it to an unrelated class.
|
||||
- `SC6005`: a `Date` string literal uses a form the native parser does not accept.
|
||||
|
||||
## Gating builds and CI
|
||||
|
||||
|
||||
@@ -52,10 +52,14 @@ Type-only imports follow Node.js's type stripping. `import type` and `export typ
|
||||
|
||||
scriptc compiles a package automatically when it:
|
||||
|
||||
- ships its own type declarations, and
|
||||
- ships readable, unminified JavaScript.
|
||||
- ships its own type declarations, or is typed by its DefinitelyTyped `@types/` package, or is a dependency of another package that has no declarations at all, and
|
||||
- ships readable JavaScript: unminified, or minified by a bundler whose helpers keep their names.
|
||||
|
||||
A package that doesn't meet these requirements, or that uses something scriptc can't compile, gets a package-specific diagnostic that explains why. The usual fixes are to switch to a version or alternative package that compiles, or to replace the call with code you write yourself. A package whose declarations come from a separate `@types/` package doesn't ship its own; its diagnostic names the package and the `--npm-static` option that compiles it anyway.
|
||||
The declarations check your program; the package's JavaScript compiles with the types scriptc infers from it. A program that doesn't typecheck against the inferred types, for example because an `@types/` package describes another release, fails the build at that use.
|
||||
|
||||
A package that doesn't meet these requirements, or that uses something scriptc can't compile, gets a package-specific diagnostic that explains why. The usual fixes are to switch to a version or alternative package that compiles, or to replace the call with code you write yourself.
|
||||
|
||||
`graceful-fs` is the exception: it compiles as `node:fs` instead of from its JavaScript, with one [documented difference](/docs/limitations#graceful-fs).
|
||||
|
||||
<span id="value-conversion" />
|
||||
<span id="static-package-compilation-experimental" />
|
||||
@@ -63,17 +67,17 @@ A package that doesn't meet these requirements, or that uses something scriptc c
|
||||
|
||||
## Explicit package selection
|
||||
|
||||
Some packages rely on separately installed declarations, such as an `@types/` package, or on types inferred from their JavaScript. To try compiling one of them, name it with `--npm-static`:
|
||||
Some packages your program imports directly ship no declarations, so their types can only be inferred from their JavaScript. To try compiling one of them, name it with `--npm-static`:
|
||||
|
||||
```console
|
||||
$ scriptc build app.ts --npm-static=three -o app >/dev/null
|
||||
```
|
||||
|
||||
Naming a package skips the declaration requirement above, but the package still has to compile. The option selects only the packages it names: a dependency of a named package that doesn't ship its own declarations is still refused. Add a trailing `...` to select a package together with every package it depends on, for example `--npm-static=fs-extra...`. `--npm-static=auto` uses the default automatic selection. The option accepts a comma-separated list and can be repeated.
|
||||
Naming a package skips the declaration requirement above, but the package still has to compile. Add a trailing `...` to select a package together with every package it depends on, for example `--npm-static=fs-extra...`. `--npm-static=auto` uses the default automatic selection. The option accepts a comma-separated list and can be repeated.
|
||||
|
||||
## CommonJS packages
|
||||
|
||||
CommonJS packages compile with Node.js's module semantics. Each `require()` evaluates its module at the call, once, and later calls return the cached exports. A package can assign `module.exports` and run code before it requires its dependencies. If that code reads a `const` or `let` require binding before its `require` runs, the read throws the same `ReferenceError` as Node.js. A `var` binding that earlier code can reach is reported at build time. A `require()` of a JSON file, such as a package's own `package.json`, returns one object parsed at build time, shared by every module that requires it. A require cycle compiles when each of its modules assigns its exports before requiring anything, as semver's classes do, and uses the other modules only inside functions that its top-level code can't call. Other require cycles are reported at build time. The [limitations](/docs/limitations) page lists the supported `require` forms.
|
||||
CommonJS packages compile with Node.js's module semantics. Each `require()` evaluates its module at the call, once, and later calls return the cached exports. A package can assign `module.exports` and run code before it requires its dependencies. If that code reads a `const` or `let` require binding before its `require` runs, the read throws the same `ReferenceError` as Node.js. A `var` binding that earlier code can reach is reported at build time. A `require()` of a JSON file, such as a package's own `package.json`, returns one object parsed at build time, shared by every module that requires it. A require cycle compiles when each of its modules assigns its exports before requiring anything, as semver's classes and esbuild's per-file output do, and uses the other modules only inside functions that its top-level code can't call. Bundles built by esbuild compile with their bundled CommonJS modules and lazily initialized ES modules, each evaluated once, at its first use, as under Node.js. Other require cycles are reported at build time. The [limitations](/docs/limitations) page lists the supported `require` forms.
|
||||
|
||||
## Unsupported code inside a package
|
||||
|
||||
|
||||
@@ -102,6 +102,8 @@ title: "Limitations"
|
||||
|
||||
<include>../limitations/builtin-apis/process-memory.mdx</include>
|
||||
|
||||
<include>../limitations/builtin-apis/v8.mdx</include>
|
||||
|
||||
<include>../limitations/builtin-apis/performance-entries.mdx</include>
|
||||
|
||||
<include>../limitations/builtin-apis/builtin-module-lookup.mdx</include>
|
||||
@@ -212,6 +214,10 @@ title: "Limitations"
|
||||
|
||||
<include>../limitations/console-and-process-streams.mdx</include>
|
||||
|
||||
### graceful-fs
|
||||
|
||||
<include>../limitations/graceful-fs.mdx</include>
|
||||
|
||||
### DNS lookup errors
|
||||
|
||||
<include>../limitations/dns-lookup-errors.mdx</include>
|
||||
@@ -224,6 +230,10 @@ title: "Limitations"
|
||||
|
||||
<include>../limitations/udp-socket-options.mdx</include>
|
||||
|
||||
### Unix domain sockets and request options
|
||||
|
||||
<include>../limitations/unix-domain-sockets.mdx</include>
|
||||
|
||||
### Process arguments and errors
|
||||
|
||||
<include>../limitations/process-arguments-and-errors.mdx</include>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- **Date:** Date unions, year/month constructors, setters, statically typed TypeScript identity comparisons, thrown Date values, and locale formatters are unsupported.
|
||||
Zero-argument and single number/string constructors, storage and passing, `getTime`, `valueOf`, `toISOString`, `toUTCString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), `toString`, `toDateString`, `toTimeString`, `String(date)` and template literal conversion, calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile.
|
||||
- **Date:** Date unions, spread constructor arguments, setters, statically typed TypeScript identity comparisons, thrown Date values, and locale formatters are unsupported.
|
||||
Zero-argument, single number/string, and local-time year/month field constructors, storage and passing, `getTime`, `valueOf`, `toISOString`, `toUTCString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), `toString`, `toDateString`, `toTimeString`, `String(date)` and template literal conversion, calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile.
|
||||
JavaScript Dates retain identity through checked storage and support Date-copy construction and JSON serialization.
|
||||
A TypeScript Date converted to `unknown` becomes a Date with the same time value; each conversion creates a new Date, so two `unknown` values converted from one TypeScript Date are not `===`.
|
||||
See [Dates and timezones](#dates-and-timezones) for parsing restrictions.
|
||||
See [Dates and timezones](#dates-and-timezones) for timezone data.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
- **EventEmitter:** Literal event names use fixed argument tuples.
|
||||
Computed string names require a constant prefix or suffix that excludes `error`, meta events, and stream-internal events; their arguments must match the exact arity and support checked conversion.
|
||||
Unrestricted names, symbols, names that may reach internal events, and typed `listeners()`/`rawListeners()` results for names with possible computed registrations are unsupported.
|
||||
`events.once` accepts EventEmitter and stream instances with a literal event name and an optional object-literal `{ signal }`; a stream's `data` event, other options values, and EventTarget emitters are unsupported.
|
||||
Reading `defaultMaxListeners` through a CommonJS destructured or member binding (`const { defaultMaxListeners } = require("events")`) is unsupported.
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
- **Globals:** Stored `globalThis` references support identity and probes for absent capabilities.
|
||||
Most builtin members require direct global access; access through a stored global object can raise `SC2020`.
|
||||
`Temporal` is not defined, as in Node.js 24: `typeof Temporal` is `"undefined"`, `globalThis.Temporal` is `undefined`, and any other use throws `ReferenceError: Temporal is not defined`.
|
||||
Programs that name it type-check against TypeScript's `Temporal` declarations.
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
- **Performance entries:** `performance.mark()`, `measure()`, and the `getEntries` methods return entries with Node's fields, `toJSON()`, inspection, and identity.
|
||||
`Object.keys`, `for...in`, object spread, and `hasOwnProperty` on an entry are unsupported, and an entry stored in an `unknown` slot prints as a plain object.
|
||||
- **Event-loop metrics:** `performance.eventLoopUtilization()` counts the main thread's CPU time since the event loop started as active time, so blocking system calls count as idle where Node counts them as active.
|
||||
`monitorEventLoopDelay()` supports `enable()`, `disable()`, `reset()`, `percentile()`, and the `min`, `max`, `mean`, `stddev`, `count`, and `exceeds` readings; the BigInt readings, `percentiles`, and non-literal option objects are unsupported.
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
- **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.
|
||||
Plain objects, arrays, functions, class instances, and Date, RegExp, Map, Set, WeakMap, WeakSet, and Promise values inherit `constructor`, `valueOf`, `toString`, `hasOwnProperty`, and `propertyIsEnumerable` from `Object.prototype` as in Node, so `value.constructor === Object` and `value.valueOf === Object.prototype.valueOf` compare identities exactly.
|
||||
Named reads such as `value.valueOf` answer these inherited members; computed-key reads such as `value[key]` do not.
|
||||
`Object.prototype`'s own members read as values; its mutation and other reflection, such as `isPrototypeOf` and `__proto__`, are unsupported.
|
||||
The constructors `Object`, `Function`, `Date`, `RegExp`, `Map`, `Set`, `WeakMap`, `WeakSet`, and `Promise` read as function values; through a stored value only `Object` can be called, and `instanceof` and `new` against them remain unsupported.
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
- **Streams and readline:** Stream chunks are Buffers or strings.
|
||||
`NodeJS.ReadableStream` and `NodeJS.WritableStream`, and `stream.Readable` and `stream.Writable` under `@types/node`, accept every stream: when a program passes file, zlib, or `node:stream` streams through them, child-process pipes and `process.stdout` and `process.stderr` join those streams as `Readable` and `Writable` instances.
|
||||
`readable.pipe(process.stdout)` and `readable.pipe(process.stderr)` compile and, as in Node, do not end the process stream.
|
||||
When an untyped value reaches `NodeJS.ReadableStream` or `NodeJS.WritableStream`, those two interfaces hold any value, so JavaScript stream objects (constructor-function streams, plain objects with `on` and `pipe`) pass through TypeScript functions typed with them and come back to JavaScript as the same objects.
|
||||
Member calls on such a value use the native stream when it is one and call the object's own members otherwise; `readable.pipe()` into a JavaScript destination follows Node's pipe protocol.
|
||||
A `pipeline` stage and a `for await` source that arrive as untyped values must be native streams at run time; other values throw a not-supported error.
|
||||
Native streams expose no `emit` to JavaScript, so a JavaScript stream whose own `pipe()` emits on its destination (the legacy `Stream` pipe) cannot pipe into one.
|
||||
`Readable.from` accepts arrays, Sets, Map and Set iterators, generators, async generators, and class iterables and async iterables whose values are strings or Buffers, and in JavaScript any iterable or async iterable of them; a stream that mixes strings and Buffers is unsupported.
|
||||
In JavaScript, a sync iterator value with a `then` method that is not a promise, and an object whose `[Symbol.asyncIterator]` or `[Symbol.iterator]` method is a generator method, are unsupported.
|
||||
Object-mode streams of other values (including `Readable.from` over a `Map`, whose values are entry arrays) are unsupported.
|
||||
@@ -11,6 +17,7 @@
|
||||
`stream.compose` accepts two or more streams, or one `Duplex`; functions, iterables, and web streams as stages are unsupported, and the `ERR_INVALID_ARG_VALUE` message for a stream that is no longer readable or writable names the stream's class instead of printing the stream.
|
||||
`pipeline` from `node:stream/promises` accepts a trailing `{ signal, end }` object, and `finished` accepts `readable`, `writable`, `error`, `cleanup`, and `signal` options, with boolean literals for the flags.
|
||||
Stream constructors accept a `signal` option in an options object literal, and `stream.addAbortSignal` accepts any stream.
|
||||
A `transform` or `flush` option callback whose completion callback types `data` as `any`, as @types/node's `TransformCallback` does, may pass a Buffer, a string, or nothing; any other value throws a TypeError where the callback is called, instead of Node's `ERR_INVALID_ARG_TYPE` stream error.
|
||||
`node:readline` and `node:readline/promises` (also reached as `readline.promises`) interfaces, from `createInterface(options)` or `createInterface(input, output, completer, terminal)`, read `process.stdin` or a readable stream: `question`, `prompt`, `write` (with a key object literal of `name`, `ctrl`, `meta`, and `shift`), `pause`, `resume`, `'line'`/`'close'`/`'pause'`/`'resume'`/`'error'`/`'history'`/`'SIGINT'`/`'SIGTSTP'`/`'SIGCONT'` listeners, and `for await` compile.
|
||||
The `history`, `historySize`, `removeHistoryDuplicates`, `completer`, `tabSize`, and `escapeCodeTimeout` options are validated as in Node.
|
||||
With `terminal: true`, or with no `terminal` option and `process.stdout` as the output when it is a TTY, the interface runs Node's terminal mode: the editing keys, the kill ring, undo and redo, history recording and recall, Tab completion through the completer, Ctrl+C, Ctrl+D, and Ctrl+Z, `TERM=dumb`, and the same escape sequences written to the output.
|
||||
@@ -26,3 +33,4 @@
|
||||
The `readline/promises` `Readline` class writes to the same outputs.
|
||||
`readline.emitKeypressEvents` emits `'keypress'` events on `process.stdin` or a readable stream, shared with terminal interfaces on that stream as in Node; a listener's first parameter must admit `undefined`, which it receives for escape sequences.
|
||||
Chained listener registration is unsupported.
|
||||
`node:stream/web` exports the same `ReadableStream` class as the global; its other classes are unsupported.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
- **V8 heap statistics:** `node:v8` can be imported.
|
||||
`v8.getHeapStatistics()` returns Node's fields in Node's order, computed from the runtime allocator like `process.memoryUsage()`: `total_heap_size` and `total_physical_size` are the bytes the heap holds, `used_heap_size` the bytes allocated, `heap_size_limit` the machine's memory (the heap has no fixed ceiling), and `total_available_size` the limit minus the used bytes.
|
||||
Fields that describe V8 itself (`total_heap_size_executable`, `malloced_memory`, `peak_malloced_memory`, the global handle sizes, `external_memory`, and `does_zap_garbage`) are `0`, and `number_of_native_contexts` is `1`.
|
||||
The values are approximate and are not V8 heap figures.
|
||||
The other `node:v8` members observe the V8 engine, such as heap snapshots, profiles, flags, and serialization, and throw `SC2020` where they are used.
|
||||
@@ -5,6 +5,10 @@ Some builtins can be stored and passed as functions:
|
||||
- `util.isDeepStrictEqual`, `styleText`, `stripVTControlCharacters`, `toUSVString`, `inherits`, `deprecate`, `callbackify`, `debuglog`, and `debug`.
|
||||
- `fs.existsSync`, `unlinkSync`, `chmodSync`, `chownSync`, `renameSync`, and `closeSync`, and `fs/promises.unlink`, `chmod`, and `rename`.
|
||||
|
||||
In JavaScript, `setTimeout`, `setInterval`, `setImmediate`, `clearTimeout`, `clearInterval`, `clearImmediate`, `queueMicrotask`, and `process.nextTick` are function values wherever they are stored, including conditional and logical expressions and assignments: `typeof` answers `"function"`, identity holds across spellings, and calls schedule as the direct calls do, returning the same numeric timer handles.
|
||||
Calls through these values bypass `node:test` mock timers.
|
||||
Other builtin globals stored in JavaScript remain opaque identity values.
|
||||
|
||||
Other immutable aliases of table-backed Node builtins support direct calls, but cannot be passed elsewhere.
|
||||
`util.promisify` supports compile-time conversions of `child_process.execFile`, `child_process.exec`, `setTimeout` and `setImmediate` (to the `timers/promises` functions, as in Node), `stream.finished` and `stream.pipeline` (to the `stream/promises` functions, as in Node), the `zlib` callback one-shots `deflate`, `inflate`, `deflateRaw`, `inflateRaw`, `gzip`, `gunzip` and `unzip` (with or without options), the `crypto` callback functions `randomBytes`, `pbkdf2`, `scrypt`, `hkdf`, `randomFill`, `randomInt` and `generateKey`, `dns.lookup` (settling with `{ address, family }`, or the address array with `all: true`) and `dns.lookupService` (settling with `{ hostname, service }`), and the `fs` callback functions that have an `fs/promises` counterpart (`access`, `appendFile`, `chmod`, `chown`, `copyFile`, `lchown`, `link`, `lstat`, `lutimes`, `mkdir`, `mkdtemp`, `readFile`, `readdir`, `readlink`, `realpath`, `rename`, `rm`, `rmdir`, `stat`, `statfs`, `symlink`, `truncate`, `unlink`, `utimes`, and `writeFile`); calls through the converted function lower as the `fs/promises` call.
|
||||
|
||||
@@ -12,7 +16,15 @@ Other immutable aliases of table-backed Node builtins support direct calls, but
|
||||
Native calls accept standard process streams; custom native streams and mutations to `util.inspect.colors` remain unsupported.
|
||||
|
||||
`util.inherits` links the prototypes of constructor functions, and `util.deprecate`, `util.callbackify`, and `util.debuglog` return native function values that forward receivers and arguments.
|
||||
Class constructors passed to `util.inherits` or `util.deprecate` are refused.
|
||||
In JavaScript, a constructor function can inherit from `EventEmitter`, the legacy `Stream` constructor, or a stream class through `util.inherits` or `Object.setPrototypeOf`.
|
||||
`EventEmitter.call(this)` and `Stream.call(this)` initialize the object as Node does, and its listener methods keep their state in the object's own `_events`, `_eventsCount`, and `_maxListeners` properties.
|
||||
The `captureRejections` option and `events.errorMonitor` listeners are not supported on such objects.
|
||||
`Readable.call(this, options)` and the same call on `Writable`, `Duplex`, `Transform`, or `PassThrough` give the object a native stream.
|
||||
The stream calls the object's `_read`, `_write`, `_final`, `_transform`, `_flush`, and `_destroy` with the object as `this`, and the stream prototypes' methods and accessors work on it.
|
||||
Its listeners live in the native stream rather than in `_events`, and `_readableState` and `_writableState` are read-only views that answer the commonly read fields.
|
||||
Typed code that receives such an object as a stream class uses its native stream, and piping a native stream into it works.
|
||||
The options the native streams refuse (`objectMode`, `decodeStrings: false`, `signal`, and others) are refused here too, as are `_writev`, `_construct`, symbol event names, `listeners()`, `rawListeners()`, `wrap()`, strings written in encodings other than UTF-8, and pipes into a destination that overrides `write()` or `end()`.
|
||||
Other class constructors passed to `util.inherits` or `util.deprecate` are refused.
|
||||
Deprecation warnings are emitted on the next tick and follow assignments to `process.noDeprecation` and `process.throwDeprecation`; compiled binaries take no Node command-line flags such as `--no-deprecation` or `--trace-deprecation`, and `process.traceDeprecation` is unsupported.
|
||||
A stored `util.deprecate` reports a `length` of 4, where Node reports 3.
|
||||
In TypeScript, a `util.callbackify` result must reach a callback whose error parameter accepts `null`; the @types/node overloads that type it as non-null are refused.
|
||||
|
||||
@@ -12,3 +12,7 @@ Message listeners may return `void` or `Promise<void>`.
|
||||
Messages use newline-delimited JSON, so their static types must be JSON-stringifiable or `unknown` backed by a checked JSON tree.
|
||||
`send()` returns the channel's backpressure state and invokes its callback later with `null` or an `Error`.
|
||||
`disconnect()` changes `connected` synchronously, before the later disconnect events.
|
||||
|
||||
A `'data'` listener on a child's `stdout` or `stderr` whose chunk is `unknown` or `any` receives strings when a `setEncoding()` statement on the same stream precedes it in its block or an enclosing block, and Buffers when the function never calls `setEncoding()` on that stream.
|
||||
Any other placement of `setEncoding()` is refused at compile time, and a `setEncoding()` call that runs elsewhere afterwards makes the listener throw a TypeError instead of receiving strings.
|
||||
An inline `'exit'` or `'close'` listener may declare `(code: number, signal: string)`, which TypeScript accepts through the catch-all `on` overload; it receives the `null` values Node passes, and a typed use of a `null` value throws a TypeError.
|
||||
|
||||
@@ -9,4 +9,5 @@ JavaScript can store `process.stdin`, `stdout`, and `stderr`, replace output `wr
|
||||
Input supports data/end/error listeners, pause/resume, buffered reads, and TTY raw mode.
|
||||
Output writes are synchronous, with successful callbacks on the next-tick queue; queued backpressure is not modeled.
|
||||
Stored streams do not support piping, encoding changes, arbitrary properties, or other events.
|
||||
TypeScript output parameters support `write(string)`.
|
||||
A TypeScript `NodeJS.WritableStream` parameter that only receives `process.stdout` or `process.stderr` supports `write(string)`.
|
||||
When other streams flow into the same parameter, the process streams join them as `Writable` instances with the full writable surface.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
String constructors and `Date.parse` accept ECMAScript date-only forms, date-times with an explicit `Z` or numeric offset, and GMT certificate-validity strings from the supported X509 APIs.
|
||||
Other V8-specific forms and offset-less local date-times produce an Invalid Date or `NaN`; `SC6005` reports string literals in those forms.
|
||||
String constructors and `Date.parse` use V8's date-string grammar: ECMAScript date-time strings and the legacy forms Node.js accepts, such as RFC 1123 HTTP dates (`Mon, 01 Jan 2024 00:00:00 GMT`), `Date#toString` output, and `Jan 2, 2024`.
|
||||
Strings without a timezone, and `new Date(year, monthIndex, ...)` fields, are local time.
|
||||
|
||||
Local calendar getters use the operating system's timezone database.
|
||||
Local calendar getters and local-time conversions use the operating system's timezone database.
|
||||
Historical results can differ from Node.js when timezone data differs.
|
||||
For valid extreme years outside a platform calendar API's range, scriptc uses a calendar-equivalent year in the 400-year Gregorian cycle to obtain the timezone rule.
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
After a failed `dns.lookup` or `dns.lookupService`, Node calls the callback with the error alone, so the address and family parameters are `undefined`.
|
||||
A callback whose address parameter is typed `string` (as Node's own type declarations type it) receives `""` and `NaN` there, or an empty array for `all: true`, because a typed string or number cannot hold `undefined`.
|
||||
An untyped callback receives the error alone, as in Node.
|
||||
The promise forms in `node:dns/promises` (also reached as `dns.promises`) reject with the error and are unaffected.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
The `graceful-fs` package compiles as `node:fs`.
|
||||
Its imports and `require("graceful-fs")` calls resolve to the builtin module, so the package's JavaScript is not compiled, and `gracefulify(fs)` as a statement does nothing.
|
||||
Programs behave as with the package except under file-descriptor exhaustion: graceful-fs queues operations that fail with `EMFILE` or `ENFILE` and retries them when a descriptor closes, while a compiled program reports the error to its caller, as `fs` does.
|
||||
In a compiled program, `require("graceful-fs") === require("fs")` is `true`.
|
||||
@@ -3,3 +3,9 @@
|
||||
Independent concrete uses have no specialization-count limit; recursively expanding demands and type layouts produce diagnostics.
|
||||
Other generic calls compile when the target resolves statically.
|
||||
Generic function values require a concrete signature at use and a binding that is never reassigned.
|
||||
- **Variadic wrappers:** A generic higher-order wrapper is specialized to each wrapped function's exact signature.
|
||||
`Parameters<F>`, `ReturnType<F>`, and rest parameters typed as fixed tuples become that signature's parameter list.
|
||||
A function expression with no parameters that reads `arguments` compiles only when its value is asserted to, or contextually typed as, one fixed-arity signature; `arguments` is then that signature's tuple.
|
||||
Forwarding it with `fn.apply(this, arguments)` passes `this` through.
|
||||
Reading `fn.name` on a wrapped function requires its name to be known where the wrapper is called, such as a function declaration or a `const`-bound function.
|
||||
`Parameters<F>` over a signature with optional or array-typed rest parameters is unsupported, because that tuple has no fixed length.
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
`eval`, `new Function`, and other ways of running JavaScript source at runtime are unsupported, in application code and in packages.
|
||||
Rewrite code that depends on them, or replace the package.
|
||||
To compute a value from code at build time instead, use `comptime`.
|
||||
A compiled program embeds no JavaScript engine, so it cannot run JavaScript source at runtime.
|
||||
`eval`, indirect `eval`, `new Function`, and `Function` behave as they do under `node --disallow-code-generation-from-strings`, a deliberate difference from default Node.js: every call throws a catchable `EvalError` with the message `Code generation from strings disallowed for this context`.
|
||||
The arguments are evaluated first, and the Function constructor converts them to strings before it throws, as Node.js does.
|
||||
Packages that test for code generation before using it, such as zod 4, take their interpreted path.
|
||||
Code that needs to evaluate source at runtime must be rewritten; to compute a value from code at build time, use `comptime`.
|
||||
The `vm` module is refused for the same reason.
|
||||
See [npm Dependencies](/docs/dependencies#which-packages-compile) for which packages compile.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
`server.listen(path)` and the `socketPath` option of `http.request()` and `https.request()` use Unix domain sockets on Linux and macOS. On Windows, where Node maps these paths to named pipes, they fail with `ENOTSUP`. `server.address()` on a server listening on a Unix socket returns the socket path, as in Node, when the result is typed `string | AddressInfo | null` (the @types/node declaration). Where it is typed as the `{ address, family, port }` record alone, which cannot hold the path, the call throws an error for such a server. `listen(path, backlog, callback)` is refused at compile time; pass `listen(path, callback)`.
|
||||
When the options of `http.request()` come from a variable, or from an object literal that spreads one, they are read at run time with Node's validation errors. The `auth`, `createConnection`, `family`, `hints`, `insecureHTTPParser`, `joinDuplicateHeaders`, `localAddress`, `localPort`, `lookup` and `uniqueHeaders` options then throw a runtime error when they hold a value, and so do `setHost: false` and `setDefaultHeaders: false`.
|
||||
@@ -1,7 +1,31 @@
|
||||
Compiled programs can't load WebAssembly modules yet.
|
||||
Code that only mentions the `WebAssembly` types type-checks, including packages whose declarations name them, and `typeof WebAssembly` answers as it does in Node.js.
|
||||
Every use of the API itself is rejected: `WebAssembly.compile`, `instantiate` and `validate`, `new WebAssembly.Module` and the other constructors, `instanceof` checks, and reads of `globalThis.WebAssembly`.
|
||||
TypeScript sources fail to build with SC2020 at each use, and JavaScript sources throw that diagnostic when the statement runs.
|
||||
scriptc translates WebAssembly modules to native code when it builds the program; there is no interpreter or JIT in the executable.
|
||||
A build translates every module whose bytes it can see: files at paths the compiler resolves (`require.resolve`, `createRequire(...).resolve`, `import.meta.resolve`, `new URL("./x.wasm", import.meta.url)`, `path.join(__dirname, ...)`), including `.so` WebAssembly shared libraries such as quickjs-wasi's extensions, modules inlined as base64 or byte-array literals, `.wasm` files shipped by packages whose code uses WebAssembly, and files listed in a `scriptc.wasm` array in `package.json`.
|
||||
At run time `WebAssembly.compile`, `instantiate`, `validate` and `new WebAssembly.Module` find the translated module by the SHA-256 of the bytes they are given.
|
||||
Bytes the build did not see, including bytes produced at run time, reject with a `CompileError` that says the module was not translated, and `WebAssembly.validate` answers `false` for them.
|
||||
`scriptc coverage` lists the modules a build finds, with their size, whether they validate, and the WebAssembly proposals they use.
|
||||
|
||||
`scriptc coverage` lists the modules a program would load, with their size, whether they validate, and the WebAssembly proposals they use.
|
||||
It finds modules at paths the compiler resolves, modules inlined as base64 literals, `.wasm` files shipped by packages that use WebAssembly, and files named with [`--wasm`](/docs/cli) or a `scriptc.wasm` array in `package.json`.
|
||||
Translated modules can use the WebAssembly 2.0 feature set: the MVP plus sign-extension operators, non-trapping float-to-int conversions, bulk memory, multi-value, mutable globals, reference types, and extended constant expressions.
|
||||
Modules that use SIMD, threads, exception handling, tail calls, typed function references or GC, memory64, or multiple memories are not translated, so loading them rejects with the `CompileError` above; packages that fall back to a scalar build when that happens, such as undici and es-module-lexer, take their fallback.
|
||||
|
||||
Supported: `WebAssembly.compile`, `instantiate` (with bytes or a `Module`), `validate`, `new WebAssembly.Module`, `new WebAssembly.Instance`, `new WebAssembly.Memory`, `new WebAssembly.Table`, `new WebAssembly.Global`, `WebAssembly.Module.imports`, `exports` and `customSections`, and `instanceof` for every class; the error classes `CompileError`, `LinkError` and `RuntimeError`, constructed with or without `new`; `globalThis.WebAssembly` and its `compile`, `instantiate` and `validate` functions as values.
|
||||
Instances expose exported functions, memories (`buffer`, `grow`), tables (`get`, `set`, `grow`, `length`) and globals (`value`, `valueOf`).
|
||||
Imports can be functions, numbers or BigInts for immutable globals, and memories, tables and globals that the program constructs or other instances export; their limits, types and mutability are checked with Node.js's `LinkError` messages.
|
||||
Function references from a table or global reach JavaScript as callable functions, and exported functions can be stored in tables and globals, which is what dynamic linkers such as quickjs-wasi's extension loader do.
|
||||
Traps throw `WebAssembly.RuntimeError` with Node.js's messages, unbounded recursion throws `RangeError`, and an exception thrown by an imported function propagates through the WebAssembly frames unchanged.
|
||||
A `WebAssembly.Module` can be sent to a worker through `workerData` or `postMessage`.
|
||||
|
||||
Not supported yet: the `Tag` and `Exception` constructors, shared memories (`new WebAssembly.Memory({ shared: true, ... })` throws a `TypeError`), `compileStreaming` and `instantiateStreaming`, and the classes used as values other than with `new`, `instanceof` and `WebAssembly.Module`'s static functions.
|
||||
TypeScript sources fail to build with SC2020 at each such use, and JavaScript sources throw that diagnostic when the statement runs.
|
||||
Programs built for `wasm32-wasi` can't use the WebAssembly API.
|
||||
|
||||
Differences from Node.js:
|
||||
|
||||
- Growing a memory, from JavaScript or inside the module, does not detach the previous `ArrayBuffer`.
|
||||
The memory never moves, so views created before the growth stay valid but keep their old length; code that reads `memory.buffer` again after growing, as WebAssembly glue code does, behaves as in Node.js.
|
||||
- `valueOf()` answers a `WebAssembly.Global`'s value only when the receiver's static type is `WebAssembly.Global`; through an untyped value it answers the object, and converting a `Global` to a primitive uses the object rather than its value.
|
||||
- Instances are never freed, together with their memories, imports and exports.
|
||||
- A load whose result the module never uses may be removed instead of trapping when its address is out of bounds.
|
||||
Loads whose result is dropped explicitly still trap.
|
||||
- NaN payloads may differ, for example when a signaling NaN passes through arithmetic or a JavaScript number.
|
||||
- `WebAssembly.compile` and `instantiate` with bytes settle after the current microtasks, as in Node.js, but can settle after `setImmediate` callbacks that Node.js would run later.
|
||||
- The executable reads `.wasm` files at run time from the paths they had when it was built.
|
||||
|
||||
@@ -239,9 +239,9 @@
|
||||
"apiEntries": 51,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 0,
|
||||
"partial": 10,
|
||||
"refused": 0,
|
||||
"not-implemented": 51,
|
||||
"not-implemented": 41,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -329,8 +329,8 @@
|
||||
"apiEntries": 92,
|
||||
"static": {
|
||||
"supported": 1,
|
||||
"partial": 15,
|
||||
"refused": 1,
|
||||
"partial": 16,
|
||||
"refused": 0,
|
||||
"not-implemented": 75,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
@@ -539,9 +539,9 @@
|
||||
"apiEntries": 131,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 19,
|
||||
"partial": 25,
|
||||
"refused": 0,
|
||||
"not-implemented": 112,
|
||||
"not-implemented": 106,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -809,10 +809,10 @@
|
||||
"apiEntries": 68,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 3,
|
||||
"partial": 4,
|
||||
"refused": 0,
|
||||
"not-implemented": 0,
|
||||
"by-design": 65,
|
||||
"by-design": 64,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
}
|
||||
@@ -869,9 +869,9 @@
|
||||
"apiEntries": 89,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 7,
|
||||
"partial": 8,
|
||||
"refused": 1,
|
||||
"not-implemented": 81,
|
||||
"not-implemented": 80,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -16626,7 +16626,7 @@
|
||||
"inherited": false
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"status": "partial",
|
||||
"detail": "Derived from the exact API rows in this chapter.",
|
||||
"verification": "derived"
|
||||
}
|
||||
@@ -16718,9 +16718,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -16741,9 +16741,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -17132,9 +17132,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -17201,9 +17201,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -17316,9 +17316,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -17339,9 +17339,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -17362,9 +17362,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -17730,9 +17730,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -17753,9 +17753,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -36590,9 +36590,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "refused",
|
||||
"detail": "Explicitly rejected by a named compiler diagnostic.",
|
||||
"verification": "explicit-refusal"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -54688,9 +54688,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -54803,9 +54803,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -55286,9 +55286,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -55309,9 +55309,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -55677,9 +55677,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 call shapes accepted by the compiler lowering.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -55815,9 +55815,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"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -75973,9 +75973,9 @@
|
||||
"anchor": "v8getheapstatistics",
|
||||
"nodeStability": null,
|
||||
"static": {
|
||||
"status": "by-design",
|
||||
"detail": "Intentionally outside the static native execution model.",
|
||||
"verification": "architectural-policy"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -81011,9 +81011,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 static async-iteration subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
"nodeVersion": "24.22.0",
|
||||
"nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93",
|
||||
"summary": {
|
||||
"taskCount": 3453,
|
||||
"tierItemCount": 3453,
|
||||
"taskCount": 3454,
|
||||
"tierItemCount": 3454,
|
||||
"tiers": {
|
||||
"static": {
|
||||
"audit-partial": 1179,
|
||||
"verify-gap": 2146,
|
||||
"replace-refusal": 41,
|
||||
"audit-partial": 1197,
|
||||
"verify-gap": 2130,
|
||||
"replace-refusal": 40,
|
||||
"classify": 2,
|
||||
"implement": 85
|
||||
}
|
||||
},
|
||||
"priorities": {
|
||||
"high": 2287,
|
||||
"normal": 694,
|
||||
"normal": 695,
|
||||
"low": 472
|
||||
}
|
||||
},
|
||||
@@ -12300,12 +12300,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:dns",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:dns.dnsPromises.lookup",
|
||||
"tests": [
|
||||
"tests/corpus/dns-promises-lookup.ts",
|
||||
"tests/corpus/dns-promises-untyped.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -12325,12 +12328,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:dns",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:dns.dnsPromises.lookupService",
|
||||
"tests": [
|
||||
"tests/corpus/dns-promises-lookup.ts",
|
||||
"tests/corpus/dns-promises-untyped.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -12750,12 +12756,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:dns",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:dns.dnsPromises.getDefaultResultOrder",
|
||||
"tests": [
|
||||
"tests/corpus/dns-promises-lookup.ts",
|
||||
"tests/corpus/dns-promises-untyped.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -12825,12 +12834,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:dns",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:dns.dns.lookup",
|
||||
"tests": [
|
||||
"tests/corpus/5648-dns-lookup-forms.ts",
|
||||
"tests/corpus/5649-dns-service-servers.ts",
|
||||
"tests/corpus/5650-dns-lookup-errors-untyped.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -12950,12 +12963,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:dns",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:dns.dns.getServers",
|
||||
"tests": [
|
||||
"tests/corpus/5648-dns-lookup-forms.ts",
|
||||
"tests/corpus/5649-dns-service-servers.ts",
|
||||
"tests/corpus/5650-dns-lookup-errors-untyped.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -12975,12 +12992,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:dns",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:dns.dns.lookup",
|
||||
"tests": [
|
||||
"tests/corpus/5648-dns-lookup-forms.ts",
|
||||
"tests/corpus/5649-dns-service-servers.ts",
|
||||
"tests/corpus/5650-dns-lookup-errors-untyped.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -13000,12 +13021,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:dns",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:dns.dns.lookupService",
|
||||
"tests": [
|
||||
"tests/corpus/5648-dns-lookup-forms.ts",
|
||||
"tests/corpus/5649-dns-service-servers.ts",
|
||||
"tests/corpus/5650-dns-lookup-errors-untyped.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -13400,12 +13425,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:dns",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:dns.dns.setDefaultResultOrder",
|
||||
"tests": [
|
||||
"tests/corpus/5648-dns-lookup-forms.ts",
|
||||
"tests/corpus/5649-dns-service-servers.ts",
|
||||
"tests/corpus/5650-dns-lookup-errors-untyped.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -13425,12 +13454,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:dns",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:dns.dns.getDefaultResultOrder",
|
||||
"tests": [
|
||||
"tests/corpus/5648-dns-lookup-forms.ts",
|
||||
"tests/corpus/5649-dns-service-servers.ts",
|
||||
"tests/corpus/5650-dns-lookup-errors-untyped.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -34202,12 +34235,25 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "refused",
|
||||
"action": "replace-refusal",
|
||||
"verification": "explicit-refusal",
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "surface-manifest:unsupported:stdlib.webassembly",
|
||||
"tests": []
|
||||
"source": "surface-manifest:stdlib.webassembly",
|
||||
"tests": [
|
||||
"tests/corpus/webassembly-api.mjs",
|
||||
"tests/corpus/webassembly-typed.ts",
|
||||
"tests/corpus/webassembly-worker.ts",
|
||||
"tests/corpus/webassembly-spec-int/main.mjs",
|
||||
"tests/corpus/webassembly-spec-float/main.mjs",
|
||||
"tests/corpus/webassembly-spec-control/main.mjs",
|
||||
"tests/corpus/webassembly-spec-memory/main.mjs",
|
||||
"tests/corpus/webassembly-spec-table/main.mjs",
|
||||
"tests/corpus/webassembly-spec-imports/main.mjs",
|
||||
"tests/corpus/webassembly-jsapi.mjs",
|
||||
"tests/corpus/webassembly-wpt-jsapi.mjs",
|
||||
"tests/corpus/webassembly-dylink/main.mjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -54231,12 +54277,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:perf_hooks",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:perf_hooks.histogram.percentile",
|
||||
"tests": [
|
||||
"tests/corpus/6397-event-loop-metrics.ts",
|
||||
"tests/fixtures/node-types/event-loop-metrics.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -54356,12 +54405,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:perf_hooks",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:perf_hooks.histogram.reset",
|
||||
"tests": [
|
||||
"tests/corpus/6397-event-loop-metrics.ts",
|
||||
"tests/fixtures/node-types/event-loop-metrics.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -54881,12 +54933,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:perf_hooks",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:perf_hooks.histogram.disable",
|
||||
"tests": [
|
||||
"tests/corpus/6397-event-loop-metrics.ts",
|
||||
"tests/fixtures/node-types/event-loop-metrics.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -54906,12 +54961,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:perf_hooks",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:perf_hooks.histogram.enable",
|
||||
"tests": [
|
||||
"tests/corpus/6397-event-loop-metrics.ts",
|
||||
"tests/fixtures/node-types/event-loop-metrics.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -55306,12 +55364,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:perf_hooks",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "surface-manifest:node-builtin.perf_hooks.monitorEventLoopDelay",
|
||||
"tests": [
|
||||
"tests/corpus/6397-event-loop-metrics.ts",
|
||||
"tests/fixtures/node-types/event-loop-metrics.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -55466,12 +55527,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:perf_hooks",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:perf_hooks.performance.eventLoopUtilization",
|
||||
"tests": [
|
||||
"tests/corpus/6397-event-loop-metrics.ts",
|
||||
"tests/fixtures/node-types/event-loop-metrics.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -77813,6 +77877,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "v8:cefde4a8297c",
|
||||
"chapter": "v8",
|
||||
"label": "v8.getHeapStatistics()",
|
||||
"signature": "`v8.getHeapStatistics()`",
|
||||
"apiSymbol": "v8.getHeapStatistics",
|
||||
"docsUrl": "https://nodejs.org/docs/v24.22.0/api/v8.html#v8getheapstatistics",
|
||||
"nodeStability": null,
|
||||
"priority": "normal",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "surface-manifest:node-builtin.v8.getHeapStatistics",
|
||||
"tests": [
|
||||
"tests/corpus/v8-heap-statistics.ts",
|
||||
"tests/corpus/v8-require-optional.cjs"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "webcrypto:22408f834c48",
|
||||
"chapter": "webcrypto",
|
||||
@@ -81965,12 +82052,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:webstreams",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:webstreams.ReadableStream",
|
||||
"tests": [
|
||||
"tests/corpus/stream-web-readable.ts",
|
||||
"tests/corpus/stream-web-require.cjs",
|
||||
"tests/corpus/2862-web-readable-async-iteration.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -251,9 +251,9 @@
|
||||
"apiEntries": 51,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 0,
|
||||
"partial": 10,
|
||||
"refused": 0,
|
||||
"not-implemented": 51,
|
||||
"not-implemented": 41,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -341,8 +341,8 @@
|
||||
"apiEntries": 92,
|
||||
"static": {
|
||||
"supported": 1,
|
||||
"partial": 15,
|
||||
"refused": 1,
|
||||
"partial": 16,
|
||||
"refused": 0,
|
||||
"not-implemented": 75,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
@@ -566,9 +566,9 @@
|
||||
"apiEntries": 131,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 19,
|
||||
"partial": 25,
|
||||
"refused": 0,
|
||||
"not-implemented": 112,
|
||||
"not-implemented": 106,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -866,10 +866,10 @@
|
||||
"apiEntries": 68,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 3,
|
||||
"partial": 4,
|
||||
"refused": 0,
|
||||
"not-implemented": 0,
|
||||
"by-design": 65,
|
||||
"by-design": 64,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
}
|
||||
@@ -926,9 +926,9 @@
|
||||
"apiEntries": 89,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 7,
|
||||
"partial": 8,
|
||||
"refused": 1,
|
||||
"not-implemented": 81,
|
||||
"not-implemented": 80,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -30720,8 +30720,15 @@
|
||||
"inherited": false
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "derived:descendants"
|
||||
"status": "partial",
|
||||
"evidence": "derived:descendants",
|
||||
"tests": [
|
||||
"tests/corpus/5648-dns-lookup-forms.ts",
|
||||
"tests/corpus/5649-dns-service-servers.ts",
|
||||
"tests/corpus/5650-dns-lookup-errors-untyped.cjs",
|
||||
"tests/corpus/dns-promises-lookup.ts",
|
||||
"tests/corpus/dns-promises-untyped.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "chapter"
|
||||
},
|
||||
@@ -30835,8 +30842,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:dns"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:dns.dnsPromises.lookup",
|
||||
"tests": [
|
||||
"tests/corpus/dns-promises-lookup.ts",
|
||||
"tests/corpus/dns-promises-untyped.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -30858,8 +30869,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:dns"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:dns.dnsPromises.lookupService",
|
||||
"tests": [
|
||||
"tests/corpus/dns-promises-lookup.ts",
|
||||
"tests/corpus/dns-promises-untyped.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -31249,8 +31264,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:dns"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:dns.dnsPromises.getDefaultResultOrder",
|
||||
"tests": [
|
||||
"tests/corpus/dns-promises-lookup.ts",
|
||||
"tests/corpus/dns-promises-untyped.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -31364,8 +31383,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:dns"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:dns.dns.lookup",
|
||||
"tests": [
|
||||
"tests/corpus/5648-dns-lookup-forms.ts",
|
||||
"tests/corpus/5649-dns-service-servers.ts",
|
||||
"tests/corpus/5650-dns-lookup-errors-untyped.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -31479,8 +31503,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:dns"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:dns.dns.getServers",
|
||||
"tests": [
|
||||
"tests/corpus/5648-dns-lookup-forms.ts",
|
||||
"tests/corpus/5649-dns-service-servers.ts",
|
||||
"tests/corpus/5650-dns-lookup-errors-untyped.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -31502,8 +31531,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:dns"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:dns.dns.lookup",
|
||||
"tests": [
|
||||
"tests/corpus/5648-dns-lookup-forms.ts",
|
||||
"tests/corpus/5649-dns-service-servers.ts",
|
||||
"tests/corpus/5650-dns-lookup-errors-untyped.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -31548,8 +31582,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:dns"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:dns.dns.lookupService",
|
||||
"tests": [
|
||||
"tests/corpus/5648-dns-lookup-forms.ts",
|
||||
"tests/corpus/5649-dns-service-servers.ts",
|
||||
"tests/corpus/5650-dns-lookup-errors-untyped.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -31916,8 +31955,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:dns"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:dns.dns.setDefaultResultOrder",
|
||||
"tests": [
|
||||
"tests/corpus/5648-dns-lookup-forms.ts",
|
||||
"tests/corpus/5649-dns-service-servers.ts",
|
||||
"tests/corpus/5650-dns-lookup-errors-untyped.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -31939,8 +31983,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:dns"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:dns.dns.getDefaultResultOrder",
|
||||
"tests": [
|
||||
"tests/corpus/5648-dns-lookup-forms.ts",
|
||||
"tests/corpus/5649-dns-service-servers.ts",
|
||||
"tests/corpus/5650-dns-lookup-errors-untyped.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -52249,6 +52298,18 @@
|
||||
"tests/corpus/6295-jose-hs256/main.mjs",
|
||||
"tests/corpus/6331-jose-root-entry/main.ts",
|
||||
"tests/corpus/text-codec-values/main.ts",
|
||||
"tests/corpus/webassembly-api.mjs",
|
||||
"tests/corpus/webassembly-dylink/main.mjs",
|
||||
"tests/corpus/webassembly-jsapi.mjs",
|
||||
"tests/corpus/webassembly-spec-control/main.mjs",
|
||||
"tests/corpus/webassembly-spec-float/main.mjs",
|
||||
"tests/corpus/webassembly-spec-imports/main.mjs",
|
||||
"tests/corpus/webassembly-spec-int/main.mjs",
|
||||
"tests/corpus/webassembly-spec-memory/main.mjs",
|
||||
"tests/corpus/webassembly-spec-table/main.mjs",
|
||||
"tests/corpus/webassembly-typed.ts",
|
||||
"tests/corpus/webassembly-worker.ts",
|
||||
"tests/corpus/webassembly-wpt-jsapi.mjs",
|
||||
"tests/fixtures/node-types/navigator.ts",
|
||||
"tests/harness/fetch-conformance.test.ts",
|
||||
"tests/harness/project-config.test.ts"
|
||||
@@ -53600,8 +53661,22 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "refused",
|
||||
"evidence": "surface-manifest:unsupported:stdlib.webassembly"
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:stdlib.webassembly",
|
||||
"tests": [
|
||||
"tests/corpus/webassembly-api.mjs",
|
||||
"tests/corpus/webassembly-typed.ts",
|
||||
"tests/corpus/webassembly-worker.ts",
|
||||
"tests/corpus/webassembly-spec-int/main.mjs",
|
||||
"tests/corpus/webassembly-spec-float/main.mjs",
|
||||
"tests/corpus/webassembly-spec-control/main.mjs",
|
||||
"tests/corpus/webassembly-spec-memory/main.mjs",
|
||||
"tests/corpus/webassembly-spec-table/main.mjs",
|
||||
"tests/corpus/webassembly-spec-imports/main.mjs",
|
||||
"tests/corpus/webassembly-jsapi.mjs",
|
||||
"tests/corpus/webassembly-wpt-jsapi.mjs",
|
||||
"tests/corpus/webassembly-dylink/main.mjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -75571,7 +75646,9 @@
|
||||
"tests/corpus/5212-perf-marks-measures.ts",
|
||||
"tests/corpus/5213-perf-timing-errors.ts",
|
||||
"tests/corpus/5214-perf-entry-inspect.ts",
|
||||
"tests/corpus/5215-perf-timing-validation.js"
|
||||
"tests/corpus/5215-perf-timing-validation.js",
|
||||
"tests/corpus/6397-event-loop-metrics.ts",
|
||||
"tests/fixtures/node-types/event-loop-metrics.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "chapter"
|
||||
@@ -77612,8 +77689,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:perf_hooks"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:perf_hooks.histogram.percentile",
|
||||
"tests": [
|
||||
"tests/corpus/6397-event-loop-metrics.ts",
|
||||
"tests/fixtures/node-types/event-loop-metrics.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -77727,8 +77808,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:perf_hooks"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:perf_hooks.histogram.reset",
|
||||
"tests": [
|
||||
"tests/corpus/6397-event-loop-metrics.ts",
|
||||
"tests/fixtures/node-types/event-loop-metrics.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -78210,8 +78295,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:perf_hooks"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:perf_hooks.histogram.disable",
|
||||
"tests": [
|
||||
"tests/corpus/6397-event-loop-metrics.ts",
|
||||
"tests/fixtures/node-types/event-loop-metrics.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -78233,8 +78322,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:perf_hooks"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:perf_hooks.histogram.enable",
|
||||
"tests": [
|
||||
"tests/corpus/6397-event-loop-metrics.ts",
|
||||
"tests/fixtures/node-types/event-loop-metrics.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -78601,8 +78694,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:perf_hooks"
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:node-builtin.perf_hooks.monitorEventLoopDelay",
|
||||
"tests": [
|
||||
"tests/corpus/6397-event-loop-metrics.ts",
|
||||
"tests/fixtures/node-types/event-loop-metrics.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -78751,8 +78848,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:perf_hooks"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:perf_hooks.performance.eventLoopUtilization",
|
||||
"tests": [
|
||||
"tests/corpus/6397-event-loop-metrics.ts",
|
||||
"tests/fixtures/node-types/event-loop-metrics.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -104477,6 +104578,8 @@
|
||||
"status": "partial",
|
||||
"evidence": "derived:descendants",
|
||||
"tests": [
|
||||
"tests/corpus/v8-heap-statistics.ts",
|
||||
"tests/corpus/v8-require-optional.cjs",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
},
|
||||
@@ -105599,8 +105702,12 @@
|
||||
"anchor": "v8getheapstatistics",
|
||||
"nodeStability": null,
|
||||
"static": {
|
||||
"status": "by-design",
|
||||
"evidence": "compiler-chapter-policy:v8"
|
||||
"status": "partial",
|
||||
"evidence": "surface-manifest:node-builtin.v8.getHeapStatistics",
|
||||
"tests": [
|
||||
"tests/corpus/v8-heap-statistics.ts",
|
||||
"tests/corpus/v8-require-optional.cjs"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -111212,6 +111319,8 @@
|
||||
"tests/corpus/2630-stream-consumers-errors.ts",
|
||||
"tests/corpus/2862-web-readable-async-iteration.ts",
|
||||
"tests/corpus/4875-stream-consumers-array-buffer.ts",
|
||||
"tests/corpus/stream-web-readable.ts",
|
||||
"tests/corpus/stream-web-require.cjs",
|
||||
"tests/harness/fetch-conformance.test.ts"
|
||||
]
|
||||
},
|
||||
@@ -111480,8 +111589,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:webstreams"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:webstreams.ReadableStream",
|
||||
"tests": [
|
||||
"tests/corpus/stream-web-readable.ts",
|
||||
"tests/corpus/stream-web-require.cjs",
|
||||
"tests/corpus/2862-web-readable-async-iteration.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"webstreams": "unreviewed"
|
||||
},
|
||||
"evidenceByManifestPrefix": {
|
||||
"stdlib.webassembly": ["tests/corpus/webassembly-api.mjs", "tests/corpus/webassembly-typed.ts", "tests/corpus/webassembly-worker.ts", "tests/corpus/webassembly-spec-int/main.mjs", "tests/corpus/webassembly-spec-float/main.mjs", "tests/corpus/webassembly-spec-control/main.mjs", "tests/corpus/webassembly-spec-memory/main.mjs", "tests/corpus/webassembly-spec-table/main.mjs", "tests/corpus/webassembly-spec-imports/main.mjs", "tests/corpus/webassembly-jsapi.mjs", "tests/corpus/webassembly-wpt-jsapi.mjs", "tests/corpus/webassembly-dylink/main.mjs"],
|
||||
"node-builtin.fs.readFileSync": ["tests/corpus/1403-buffer-fs.ts", "tests/corpus/2595-fs-arg-ladders.cjs", "tests/corpus/2919-fs-function-values.ts", "tests/corpus/2920-fs-promises-function-values.ts", "tests/corpus/fs-read-encodings.ts"],
|
||||
"syntax.using-declarations": ["tests/corpus/2841-using-sync.ts"],
|
||||
"syntax.await-using-declarations": ["tests/corpus/2842-await-using.ts"],
|
||||
@@ -47,6 +48,10 @@
|
||||
"node-builtin.dgram": ["tests/corpus/2597-dgram-send-ladders.cjs"],
|
||||
"node-builtin.diagnostics_channel": ["tests/corpus/2214-dc-bind-store.cjs"],
|
||||
"node-builtin.dns": ["tests/corpus/2597-dgram-send-ladders.cjs"],
|
||||
"node-builtin.dns.promises": ["tests/corpus/dns-promises-lookup.ts", "tests/corpus/dns-promises-untyped.cjs"],
|
||||
"node-builtin.stream.web": ["tests/corpus/stream-web-readable.ts", "tests/corpus/stream-web-require.cjs"],
|
||||
"node-builtin.v8": ["tests/corpus/v8-require-optional.cjs"],
|
||||
"node-builtin.v8.getHeapStatistics": ["tests/corpus/v8-heap-statistics.ts", "tests/corpus/v8-require-optional.cjs"],
|
||||
"node-builtin.events": ["tests/corpus/1677-emitter-listeners.ts", "tests/corpus/2620-ee-override-once-order.ts"],
|
||||
"node-builtin.fs": ["tests/corpus/1403-buffer-fs.ts", "tests/corpus/2595-fs-arg-ladders.cjs", "tests/corpus/2919-fs-function-values.ts", "tests/corpus/2920-fs-promises-function-values.ts"],
|
||||
"node-builtin.fs.realpathSync.native": ["tests/corpus/fs-native-realpath.ts"],
|
||||
@@ -134,6 +139,8 @@
|
||||
"node-builtin.perf_hooks.performance.getEntries": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5214-perf-entry-inspect.ts", "tests/corpus/5215-perf-timing-validation.js"],
|
||||
"node-builtin.perf_hooks.performance.clearMarks": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5213-perf-timing-errors.ts", "tests/corpus/5215-perf-timing-validation.js"],
|
||||
"node-builtin.perf_hooks.performance.clearMeasures": ["tests/corpus/5212-perf-marks-measures.ts"],
|
||||
"node-builtin.perf_hooks.performance.eventLoopUtilization": ["tests/corpus/6397-event-loop-metrics.ts", "tests/fixtures/node-types/event-loop-metrics.ts"],
|
||||
"node-builtin.perf_hooks.monitorEventLoopDelay": ["tests/corpus/6397-event-loop-metrics.ts", "tests/fixtures/node-types/event-loop-metrics.ts"],
|
||||
"node-builtin.process": ["tests/corpus/990-process-basics.ts", "tests/corpus/2314-process-introspection.ts"],
|
||||
"node-builtin.process.hrtime": ["tests/corpus/process-hrtime.js"],
|
||||
"node-builtin.process.memoryUsage": ["tests/corpus/5210-process-memory-usage.ts"],
|
||||
@@ -242,6 +249,8 @@
|
||||
{ "chapter": "util", "symbols": ["util.parseEnv"], "status": "partial", "evidence": ["tests/corpus/node24-env-parse.ts", "tests/corpus/node24-env-parse-errors.ts", "tests/corpus/node24-env-imports.cjs", "tests/fixtures/node-types/env-files.ts", "tests/harness/project-config.test.ts"] },
|
||||
{ "chapter": "process", "symbols": ["process.memoryUsage.rss"], "status": "partial", "evidence": ["tests/corpus/5210-process-memory-usage.ts"] },
|
||||
{ "chapter": "perf_hooks", "symbols": ["performance.now"], "status": "partial", "evidence": ["tests/corpus/2427-perf-hooks-now.ts", "tests/corpus/2446-performance-global.ts"] },
|
||||
{ "chapter": "perf_hooks", "symbols": ["performance.eventLoopUtilization"], "status": "partial", "evidence": ["tests/corpus/6397-event-loop-metrics.ts", "tests/fixtures/node-types/event-loop-metrics.ts"] },
|
||||
{ "chapter": "perf_hooks", "symbols": ["histogram.enable", "histogram.disable", "histogram.percentile", "histogram.reset"], "status": "partial", "evidence": ["tests/corpus/6397-event-loop-metrics.ts", "tests/fixtures/node-types/event-loop-metrics.ts"] },
|
||||
{ "chapter": "perf_hooks", "symbols": ["performance.mark", "performance.measure", "performance.getEntries", "performance.getEntriesByName", "performance.getEntriesByType", "performance.clearMarks", "performance.clearMeasures"], "status": "partial", "evidence": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5213-perf-timing-errors.ts", "tests/corpus/5214-perf-entry-inspect.ts", "tests/corpus/5215-perf-timing-validation.js"] },
|
||||
{ "chapter": "perf_hooks", "symbols": ["PerformanceEntry", "PerformanceMark", "PerformanceMeasure"], "status": "partial", "evidence": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5214-perf-entry-inspect.ts"] },
|
||||
{ "chapter": "perf_hooks", "parent": "Class: `PerformanceEntry`", "symbols": ["duration", "entryType", "name", "startTime"], "status": "partial", "evidence": ["tests/corpus/5212-perf-marks-measures.ts", "tests/corpus/5214-perf-entry-inspect.ts"] },
|
||||
@@ -381,7 +390,10 @@
|
||||
{ "chapter": "util", "signatures": ["`util.promisify(original)`"], "status": "partial", "evidence": ["tests/corpus/1473-promisify-execfile.ts", "tests/corpus/1573-promisify-execfile-env-spread.ts", "tests/corpus/2912-promisify-fs-readfile.ts", "tests/corpus/5625-promisify-exec.ts", "tests/corpus/5626-promisify-forms.ts", "tests/fixtures/node-types/promisify-fs.ts", "tests/corpus/5628-promisify-timers.ts", "tests/fixtures/node-types/promisify-timers.ts", "tests/corpus/5629-promisify-stream.ts", "tests/corpus/5630-promisify-zlib.ts", "tests/fixtures/node-types/promisify-zlib.ts", "tests/corpus/5631-promisify-crypto.ts", "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", "tests/corpus/6428-promisify-callback-functions.ts", "tests/fixtures/node-types/promisify-callback-functions.ts", "tests/harness/project-config.test.ts"] },
|
||||
{ "chapter": "typescript", "symbols": ["type"], "status": "supported", "evidence": ["tests/corpus/2702-type-import-link-static/main.ts", "tests/corpus/2703-type-import-link-static-reexport/main.ts", "tests/corpus/2704-type-import-link-static-success/main.ts", "tests/corpus/2707-type-import-link-static-default-reexport/main.ts", "tests/harness/errors.test.ts"] },
|
||||
{ "chapter": "zlib", "signatures": ["Class: `zlib.Deflate`", "Class: `zlib.Inflate`", "Class: `zlib.Gzip`", "Class: `zlib.Gunzip`", "Class: `zlib.DeflateRaw`", "Class: `zlib.InflateRaw`", "Class: `zlib.Unzip`", "Class: `zlib.ZlibBase`", "`zlib.close([callback])`", "`zlib.flush([kind, ]callback)`", "`zlib.params(level, strategy, callback)`", "`zlib.reset()`", "`bytesWritten` Type: {number}", "Class: `Options`"], "status": "partial", "evidence": ["tests/corpus/5370-zlib-stream-roundtrip.ts", "tests/corpus/5371-zlib-stream-chunking.cjs", "tests/corpus/5372-zlib-stream-errors.cjs", "tests/corpus/5373-zlib-stream-options.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts", "tests/corpus/5375-zlib-stream-promises.ts", "tests/corpus/5377-zlib-stream-backpressure.cjs"] },
|
||||
{ "chapter": "zlib", "signatures": ["`zlib.constants`"], "status": "partial", "evidence": ["tests/corpus/5376-zlib-constants.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts"] }
|
||||
{ "chapter": "zlib", "signatures": ["`zlib.constants`"], "status": "partial", "evidence": ["tests/corpus/5376-zlib-constants.cjs", "tests/corpus/5374-zlib-stream-flush-params.ts"] },
|
||||
{ "chapter": "dns", "symbols": ["dns.lookup", "dns.lookupService", "dns.getServers", "dns.getDefaultResultOrder", "dns.setDefaultResultOrder"], "status": "partial", "evidence": ["tests/corpus/5648-dns-lookup-forms.ts", "tests/corpus/5649-dns-service-servers.ts", "tests/corpus/5650-dns-lookup-errors-untyped.cjs"] },
|
||||
{ "chapter": "dns", "symbols": ["dnsPromises.lookup", "dnsPromises.lookupService", "dnsPromises.getDefaultResultOrder"], "status": "partial", "evidence": ["tests/corpus/dns-promises-lookup.ts", "tests/corpus/dns-promises-untyped.cjs"] },
|
||||
{ "chapter": "webstreams", "symbols": ["ReadableStream"], "status": "partial", "evidence": ["tests/corpus/stream-web-readable.ts", "tests/corpus/stream-web-require.cjs", "tests/corpus/2862-web-readable-async-iteration.ts"] }
|
||||
],
|
||||
"dedicated": [
|
||||
{ "symbols": ["assert.doesNotThrow", "assert.doesNotReject"], "status": "partial", "evidence": ["tests/corpus/node24-no-error-assertions.ts", "tests/corpus/node24-no-error-assertions.cjs", "tests/corpus/node24-no-error-assertions-coercion.js", "tests/fixtures/node-types/no-error-assertions.ts", "tests/harness/project-config.test.ts"] },
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"bench:backend-analysis": "node scripts/bench-backend-analysis.mjs",
|
||||
"test": "vitest run",
|
||||
"test:ts7": "node scripts/test-ts7.mjs",
|
||||
"test:checker-identity": "node scripts/checker-identity.mjs",
|
||||
"generate:ts7-ast": "node scripts/generate-ts7-ast-schema.mjs",
|
||||
"test:fetch-conformance": "vitest run tests/harness/fetch-conformance.test.ts",
|
||||
"test:test262": "tsx tests/test262/run.ts",
|
||||
|
||||
@@ -42,6 +42,7 @@ export function relocateToolchain(manifest, sourceDirectory, destinationDirector
|
||||
const pathFrom = (value) => relative(destinationDirectory, resolve(sourceDirectory, value));
|
||||
for (const name of [
|
||||
"ts7",
|
||||
"tsc_ts",
|
||||
"llvm_package",
|
||||
"runtime_pack",
|
||||
"runtime_sources",
|
||||
@@ -96,6 +97,7 @@ export function installNativeCli(
|
||||
if (process.platform !== "win32") {
|
||||
for (const path of [
|
||||
manifest.ts7,
|
||||
...(manifest.tsc_ts === undefined ? [] : [manifest.tsc_ts]),
|
||||
manifest.comptime,
|
||||
join(manifest.llvm_package, "bin/scriptc-llvm-codegen"),
|
||||
]) {
|
||||
|
||||
@@ -116,6 +116,13 @@ async function tryFastPath(): Promise<number | null> {
|
||||
// the whole compiler graph to rediscover it.
|
||||
const helperRuntimePackTarget = !values.sanitize ? startup.precompiledRuntimePackTarget() : null;
|
||||
const helperObjectRoute = helperRuntimePackTarget !== null;
|
||||
// The full compiler reports a checker it cannot find.
|
||||
let checker: string | undefined;
|
||||
try {
|
||||
checker = startup.checkerCacheIdentity();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const hit = await startup.readRoutedExecutableCache(root, {
|
||||
entryPath: input,
|
||||
outDir,
|
||||
@@ -126,6 +133,7 @@ async function tryFastPath(): Promise<number | null> {
|
||||
...(optimization === "dev" || optimization === "speed" ? { optimization } : {}),
|
||||
...(values.strip ? { strip: true as const } : {}),
|
||||
...(values["detached-exit"] ? { detachedExit: true as const } : {}),
|
||||
...(checker === undefined ? {} : { checker }),
|
||||
npmStatic,
|
||||
ffiProfile: ffiPath === null ? null : { path: ffiPath, bytes: ffiBytes! },
|
||||
target: `${process.env["SCRIPTC_TARGET"] ?? "native"}:${buildPlatform}:${process.arch}:${
|
||||
|
||||
+190
-13
@@ -80,21 +80,54 @@ declare namespace NodeJS {
|
||||
path?: string | undefined;
|
||||
syscall?: string | undefined;
|
||||
}
|
||||
/* The piped child-output stream (child.stdout / child.stderr under
|
||||
* stdio ["ignore", "pipe", "pipe"]): 'data' fires one Buffer chunk per
|
||||
* read (≤64KB, consumer-driven — no listener, no read), 'end' fires
|
||||
* once at EOF and always BEFORE the child's 'exit'. A flowing stream
|
||||
* keeps the loop alive. */
|
||||
/* The readable-stream interface every Node stream implements. The
|
||||
* piped child-output stream (child.stdout / child.stderr under stdio
|
||||
* ["ignore", "pipe", "pipe"]) is declared with it: 'data' fires one
|
||||
* Buffer chunk per read (≤64KB, consumer-driven — no listener, no
|
||||
* read), 'end' fires once at EOF and always BEFORE the child's 'exit'. A
|
||||
* flowing stream keeps the loop alive. A program that also passes
|
||||
* other streams (fs, zlib, node:stream) where this interface is
|
||||
* expected compiles it as the node:stream Readable, with child output
|
||||
* bridged into one. */
|
||||
interface ReadableStream {
|
||||
on(event: "data", listener: (chunk: Buffer) => void): void;
|
||||
on(event: "data", listener: (chunk: string) => void): void;
|
||||
on(event: "end", listener: () => void): void;
|
||||
on(event: string, listener: (...args: any[]) => void): void;
|
||||
once(event: "data", listener: (chunk: Buffer) => void): void;
|
||||
once(event: "data", listener: (chunk: string) => void): void;
|
||||
once(event: "end", listener: () => void): void;
|
||||
once(event: string, listener: (...args: any[]) => void): void;
|
||||
/* setEncoding('utf8') makes subsequent data chunks strings, including
|
||||
* multibyte sequences split across native pipe reads. */
|
||||
setEncoding(encoding: string): this;
|
||||
readonly readable: boolean;
|
||||
pause(): this;
|
||||
resume(): this;
|
||||
isPaused(): boolean;
|
||||
pipe<T extends WritableStream>(destination: T, options?: { end?: boolean }): T;
|
||||
unpipe(destination?: WritableStream): this;
|
||||
[Symbol.asyncIterator](): AsyncIterableIterator<string | Buffer>;
|
||||
}
|
||||
/* The writable-stream interface every Node stream implements:
|
||||
* process.stdout/process.stderr, fs.WriteStream and the node:stream
|
||||
* Writable family. Written only to the process streams, it compiles as
|
||||
* their descriptor; with other streams flowing into it, as the
|
||||
* node:stream Writable (the process streams convert into one). */
|
||||
interface WritableStream {
|
||||
readonly writable: boolean;
|
||||
write(data: string | Uint8Array, callback?: (error?: Error | null) => void): boolean;
|
||||
write(
|
||||
data: string | Uint8Array,
|
||||
encoding: BufferEncoding,
|
||||
callback?: (error?: Error | null) => void,
|
||||
): boolean;
|
||||
end(callback?: () => void): void;
|
||||
end(data: string | Uint8Array, callback?: () => void): void;
|
||||
end(data: string | Uint8Array, encoding: BufferEncoding, callback?: () => void): void;
|
||||
/* Node's EventEmitter signature: listeners take untyped arguments. */
|
||||
on(event: string, listener: (...args: any[]) => void): void;
|
||||
once(event: string, listener: (...args: any[]) => void): void;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1271,8 +1304,37 @@ declare module "node:perf_hooks" {
|
||||
getEntriesByType(type: EntryType): PerformanceEntry[];
|
||||
clearMarks(name?: string): void;
|
||||
clearMeasures(name?: string): void;
|
||||
eventLoopUtilization(
|
||||
utilization1?: EventLoopUtilization,
|
||||
utilization2?: EventLoopUtilization,
|
||||
): EventLoopUtilization;
|
||||
}
|
||||
export const performance: Performance;
|
||||
/* Event-loop metrics: utilization (idle/active ms since the loop started)
|
||||
* and monitorEventLoopDelay's sampling histogram (nanoseconds). */
|
||||
export interface EventLoopUtilization {
|
||||
idle: number;
|
||||
active: number;
|
||||
utilization: number;
|
||||
}
|
||||
export interface EventLoopMonitorOptions {
|
||||
resolution?: number | undefined;
|
||||
}
|
||||
export interface Histogram {
|
||||
readonly count: number;
|
||||
readonly exceeds: number;
|
||||
readonly max: number;
|
||||
readonly mean: number;
|
||||
readonly min: number;
|
||||
readonly stddev: number;
|
||||
percentile(percentile: number): number;
|
||||
reset(): void;
|
||||
}
|
||||
export interface IntervalHistogram extends Histogram {
|
||||
enable(): boolean;
|
||||
disable(): boolean;
|
||||
}
|
||||
export function monitorEventLoopDelay(options?: EventLoopMonitorOptions): IntervalHistogram;
|
||||
}
|
||||
declare module "perf_hooks" {
|
||||
export * from "node:perf_hooks";
|
||||
@@ -1878,6 +1940,13 @@ declare module "node:fs" {
|
||||
declare module "fs" {
|
||||
export * from "node:fs";
|
||||
}
|
||||
/* graceful-fs — served as node:fs (it monkeypatches fs and retries on
|
||||
* EMFILE/ENFILE; compiled programs call fs directly, without the retry
|
||||
* queue). gracefulify is a no-op statement. The @types/graceful-fs shape. */
|
||||
declare module "graceful-fs" {
|
||||
export * from "node:fs";
|
||||
export function gracefulify<T>(fsModule: T): T;
|
||||
}
|
||||
|
||||
/* fs/promises — the SAME synchronous operations behind already-settled
|
||||
* promises: success fulfills, failure REJECTS with the would-be thrown
|
||||
@@ -3059,8 +3128,10 @@ declare module "child_process" {
|
||||
destroy(): void;
|
||||
on(event: "drain" | "finish", listener: () => void): void;
|
||||
on(event: "error", listener: (err: Error) => void): void;
|
||||
on(event: string, listener: (...args: any[]) => void): void;
|
||||
once(event: "drain" | "finish", listener: () => void): void;
|
||||
once(event: "error", listener: (err: Error) => void): void;
|
||||
once(event: string, listener: (...args: any[]) => void): void;
|
||||
}
|
||||
|
||||
export interface ChildProcess extends Disposable {
|
||||
@@ -4687,6 +4758,11 @@ declare module "net" {
|
||||
on(event: "close", listener: (hadError: boolean) => void): void;
|
||||
on(event: "error", listener: (err: Error) => void): void;
|
||||
on(event: "session", listener: (session: Buffer) => void): void;
|
||||
/* The host name's resolution, before the connect. */
|
||||
on(
|
||||
event: "lookup",
|
||||
listener: (err: Error | null, address: string, family: number, host: string) => void,
|
||||
): void;
|
||||
/* addListener IS on (Node aliases them) — the suite spells both. */
|
||||
addListener(event: "data", listener: (chunk: Buffer) => void): void;
|
||||
/* After setEncoding the chunk is a string. */
|
||||
@@ -4698,6 +4774,11 @@ declare module "net" {
|
||||
addListener(event: "close", listener: (hadError: boolean) => void): void;
|
||||
addListener(event: "error", listener: (err: Error) => void): void;
|
||||
addListener(event: "session", listener: (session: Buffer) => void): void;
|
||||
/* The host name's resolution, before the connect. */
|
||||
addListener(
|
||||
event: "lookup",
|
||||
listener: (err: Error | null, address: string, family: number, host: string) => void,
|
||||
): void;
|
||||
once(event: "data", listener: (chunk: Buffer) => void): void;
|
||||
/* After setEncoding the chunk is a string. */
|
||||
once(event: "data", listener: (chunk: string) => void): void;
|
||||
@@ -4708,6 +4789,11 @@ declare module "net" {
|
||||
once(event: "close", listener: (hadError: boolean) => void): void;
|
||||
once(event: "error", listener: (err: Error) => void): void;
|
||||
once(event: "session", listener: (session: Buffer) => void): void;
|
||||
/* The host name's resolution, before the connect. */
|
||||
once(
|
||||
event: "lookup",
|
||||
listener: (err: Error | null, address: string, family: number, host: string) => void,
|
||||
): void;
|
||||
}
|
||||
export interface Server {
|
||||
/* Whether the server keeps the event loop alive (ref'd by default). */
|
||||
@@ -4726,6 +4812,8 @@ declare module "net" {
|
||||
/* The positional bind address (the options form's host in argument
|
||||
* spelling). */
|
||||
listen(port: number, host: string, callback?: () => void): Server;
|
||||
/* A Unix-socket server; the socket file is removed when it closes. */
|
||||
listen(path: string, callback?: () => void): Server;
|
||||
/* The explicit-interface bind: host is an IP literal (absent = the
|
||||
* host-less dual-stack any); ipv6Only sets IPV6_V6ONLY and reusePort
|
||||
* requests kernel connection distribution where Node supports it. */
|
||||
@@ -5049,10 +5137,11 @@ declare module "http" {
|
||||
* callback receives the response as an IncomingMessage (statusCode,
|
||||
* headers, body via on("data"/"end")). */
|
||||
export interface RequestOptions {
|
||||
hostname?: string;
|
||||
host?: string;
|
||||
port?: number;
|
||||
path?: string;
|
||||
hostname?: string | null;
|
||||
host?: string | null;
|
||||
/* A string port (a URL's .port) is accepted, as by Node. */
|
||||
port?: number | string | null;
|
||||
path?: string | null;
|
||||
method?: string;
|
||||
timeout?: number;
|
||||
headers?: OutgoingHttpHeaders;
|
||||
@@ -5066,6 +5155,8 @@ declare module "http" {
|
||||
agent?: Agent | boolean | null;
|
||||
/* Aborting destroys the request with an AbortError. */
|
||||
signal?: AbortSignal;
|
||||
/* HTTP over the Unix socket at this path instead of host:port. */
|
||||
socketPath?: string;
|
||||
/* The options-record stance: Node ignores an options record's
|
||||
* unknown keys, so every key typechecks here — the option WALK in
|
||||
* the lowering decides per key (lower, fence by name, or drop
|
||||
@@ -5341,6 +5432,7 @@ declare module "https" {
|
||||
agent?: Agent | boolean | null;
|
||||
/* Aborting destroys the request with an AbortError. */
|
||||
signal?: AbortSignal;
|
||||
socketPath?: string;
|
||||
/* The options-record stance (see http.RequestOptions). */
|
||||
[option: string]: unknown;
|
||||
}
|
||||
@@ -6158,10 +6250,79 @@ declare module "dns" {
|
||||
export function getServers(): string[];
|
||||
export function getDefaultResultOrder(): "ipv4first" | "ipv6first" | "verbatim";
|
||||
export function setDefaultResultOrder(order: "ipv4first" | "ipv6first" | "verbatim"): void;
|
||||
/* The promise forms (node:dns/promises). */
|
||||
export const promises: typeof import("dns/promises");
|
||||
}
|
||||
declare module "node:dns" {
|
||||
export * from "dns";
|
||||
}
|
||||
/* node:dns/promises — lookup and lookupService as promises over the same
|
||||
* resolution as the callback forms: argument errors throw synchronously,
|
||||
* failures reject with the callbacks' errors. */
|
||||
declare module "dns/promises" {
|
||||
import type { LookupAddress, LookupAllOptions, LookupOneOptions, LookupOptions } from "dns";
|
||||
export function lookup(hostname: string, family: number): Promise<LookupAddress>;
|
||||
export function lookup(hostname: string, options: LookupOneOptions): Promise<LookupAddress>;
|
||||
export function lookup(hostname: string, options: LookupAllOptions): Promise<LookupAddress[]>;
|
||||
export function lookup(
|
||||
hostname: string,
|
||||
options: LookupOptions,
|
||||
): Promise<LookupAddress | LookupAddress[]>;
|
||||
export function lookup(hostname: string): Promise<LookupAddress>;
|
||||
export function lookupService(
|
||||
address: string,
|
||||
port: number,
|
||||
): Promise<{ hostname: string; service: string }>;
|
||||
export function getServers(): string[];
|
||||
export function getDefaultResultOrder(): "ipv4first" | "ipv6first" | "verbatim";
|
||||
export function setDefaultResultOrder(order: "ipv4first" | "ipv6first" | "verbatim"): void;
|
||||
}
|
||||
declare module "node:dns/promises" {
|
||||
export * from "dns/promises";
|
||||
}
|
||||
|
||||
/* node:v8 — importable for the packages that require it at top level.
|
||||
* getHeapStatistics answers allocator-derived figures in Node's shape
|
||||
* (approximate: the runtime heap's total and used bytes, the machine's
|
||||
* memory as the limit; V8-only figures are 0). The other members observe
|
||||
* V8 itself and fence at their use with the by-design reason; they are
|
||||
* declared so programs that type-check under their own tsc reach that
|
||||
* fence. */
|
||||
declare module "v8" {
|
||||
type DoesZapCodeSpaceFlag = 0 | 1;
|
||||
export interface HeapInfo {
|
||||
total_heap_size: number;
|
||||
total_heap_size_executable: number;
|
||||
total_physical_size: number;
|
||||
total_available_size: number;
|
||||
used_heap_size: number;
|
||||
heap_size_limit: number;
|
||||
malloced_memory: number;
|
||||
peak_malloced_memory: number;
|
||||
does_zap_garbage: DoesZapCodeSpaceFlag;
|
||||
number_of_native_contexts: number;
|
||||
number_of_detached_contexts: number;
|
||||
total_global_handles_size: number;
|
||||
used_global_handles_size: number;
|
||||
external_memory: number;
|
||||
}
|
||||
export function getHeapStatistics(): HeapInfo;
|
||||
export function cachedDataVersionTag(): number;
|
||||
export function getHeapSpaceStatistics(): unknown[];
|
||||
export function getHeapCodeStatistics(): unknown;
|
||||
export function getHeapSnapshot(options?: unknown): unknown;
|
||||
export function writeHeapSnapshot(fileName?: string, options?: unknown): string;
|
||||
export function setFlagsFromString(flags: string): void;
|
||||
export function setHeapSnapshotNearHeapLimit(limit: number): void;
|
||||
export function serialize(value: unknown): Buffer;
|
||||
export function deserialize(buffer: Uint8Array): unknown;
|
||||
export function takeCoverage(): void;
|
||||
export function stopCoverage(): void;
|
||||
export function isStringOneByteRepresentation(content: string): boolean;
|
||||
}
|
||||
declare module "node:v8" {
|
||||
export * from "v8";
|
||||
}
|
||||
|
||||
/* node:worker_threads — native workers use statically compiled entry points.
|
||||
* Lowering and runtime diagnostics define the supported options and methods. */
|
||||
@@ -6317,7 +6478,7 @@ declare module "node:cluster" {
|
||||
/* node:tty — the fd probe (the same isatty(3) behind process.*.isTTY)
|
||||
* and the supported structural view of a process output stream. */
|
||||
declare module "tty" {
|
||||
export interface WriteStream {
|
||||
export interface WriteStream extends NodeJS.WritableStream {
|
||||
write(data: string | Uint8Array, callback?: (error?: Error | null) => void): boolean;
|
||||
write(
|
||||
data: string | Uint8Array,
|
||||
@@ -6408,7 +6569,7 @@ declare module "events" {
|
||||
export { internal as EventEmitter };
|
||||
// The event's arguments, as checked-dynamic values.
|
||||
export function once(
|
||||
emitter: EventEmitter,
|
||||
emitter: EventEmitter | NodeJS.ReadableStream | NodeJS.WritableStream,
|
||||
eventName: string,
|
||||
options?: { signal?: AbortSignal },
|
||||
): Promise<unknown[]>;
|
||||
@@ -6567,8 +6728,8 @@ declare module "stream" {
|
||||
resume(): this;
|
||||
isPaused(): boolean;
|
||||
setEncoding(encoding: string): this;
|
||||
pipe<T extends Writable>(destination: T, options?: { end?: boolean }): T;
|
||||
unpipe(destination?: Writable): this;
|
||||
pipe<T extends NodeJS.WritableStream>(destination: T, options?: { end?: boolean }): T;
|
||||
unpipe(destination?: NodeJS.WritableStream): this;
|
||||
destroy(error?: Error): this;
|
||||
readonly readable: boolean;
|
||||
readonly readableEnded: boolean;
|
||||
@@ -6951,6 +7112,22 @@ declare module "node:stream/consumers" {
|
||||
import streamConsumers = require("stream/consumers");
|
||||
export = streamConsumers;
|
||||
}
|
||||
/* node:stream/web — the module exports the web stream globals themselves
|
||||
* (require("stream/web").ReadableStream === globalThis.ReadableStream).
|
||||
* ReadableStream is the implemented class; its reader and controller are
|
||||
* exported as types. */
|
||||
declare module "stream/web" {
|
||||
export const ReadableStream: typeof globalThis.ReadableStream;
|
||||
export type ReadableStream<T = unknown> = globalThis.ReadableStream<T>;
|
||||
export type ReadableStreamDefaultReader<T = unknown> = globalThis.ReadableStreamDefaultReader<T>;
|
||||
export type ReadableStreamDefaultController<T = unknown> =
|
||||
globalThis.ReadableStreamDefaultController<T>;
|
||||
export type UnderlyingSource<T = unknown> = globalThis.UnderlyingSource<T>;
|
||||
export type QueuingStrategy<T = unknown> = globalThis.QueuingStrategy<T>;
|
||||
}
|
||||
declare module "node:stream/web" {
|
||||
export * from "stream/web";
|
||||
}
|
||||
interface ErrorConstructor {
|
||||
stackTraceLimit: number;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@scriptc/runtime": "workspace:*",
|
||||
"tsc-ts": "0.0.5",
|
||||
"typescript": "7.0.2",
|
||||
"typescript5": "npm:typescript@5.9.3"
|
||||
},
|
||||
|
||||
@@ -16,6 +16,7 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures {
|
||||
fileHandle: features.fileHandle,
|
||||
fetch: features.fetch,
|
||||
...(features.eventTarget ? { eventTarget: true } : {}),
|
||||
...(features.wasm ? { wasm: true } : {}),
|
||||
...(features.warningReplace ? { warningReplace: true } : {}),
|
||||
...(features.stdioExtended ? { stdioExtended: true } : {}),
|
||||
zlib: features.zlib,
|
||||
@@ -25,18 +26,23 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures {
|
||||
dc: features.dc,
|
||||
dynAsync: features.dynAsync,
|
||||
...(features.dynTier ? { dynTier: true } : {}),
|
||||
...(features.streamDyn ? { streamDyn: true } : {}),
|
||||
...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}),
|
||||
events: features.processEvents,
|
||||
emitter: features.emitter,
|
||||
symbol: features.symbol,
|
||||
bigint: features.bigint,
|
||||
searchParams: features.searchParams,
|
||||
qs: features.qs,
|
||||
dynBuiltinClass: features.dynBuiltinClass,
|
||||
dynBuiltinStream: features.dynBuiltinStream,
|
||||
parseArgs: features.parseArgs,
|
||||
stream: features.stream,
|
||||
net: features.net,
|
||||
http: features.http,
|
||||
http2: features.http2,
|
||||
dgram: features.dgram,
|
||||
dnsPromises: features.dnsPromises,
|
||||
watch: features.fsWatch,
|
||||
foreignFfi: hasForeignFfiCallback(mod.ffiImports ?? []),
|
||||
nodeTest: features.nodeTest,
|
||||
@@ -44,5 +50,6 @@ export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures {
|
||||
tlsCa: features.tlsCa,
|
||||
cipher: features.cipher,
|
||||
webcrypto: features.webcrypto,
|
||||
perfLoop: features.perfLoop,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,18 +42,22 @@ export async function compilePackedLibrary(
|
||||
bigint: options.bigint ?? false,
|
||||
searchParams: options.searchParams ?? false,
|
||||
qs: false,
|
||||
dynBuiltinClass: false,
|
||||
dynBuiltinStream: false,
|
||||
parseArgs: false,
|
||||
stream: false,
|
||||
net: false,
|
||||
http: false,
|
||||
http2: false,
|
||||
dgram: false,
|
||||
dnsPromises: false,
|
||||
watch: false,
|
||||
foreignFfi: false,
|
||||
nodeTest: false,
|
||||
tls: false,
|
||||
cipher: false,
|
||||
webcrypto: false,
|
||||
perfLoop: false,
|
||||
tlsCa: false,
|
||||
};
|
||||
const selection = await loadRuntimePack({
|
||||
|
||||
@@ -34,6 +34,7 @@ import { InternalCompilerError } from "../../errors.js";
|
||||
* ScrDynPath { parent, key, index } — the %ScrDynPath type. */
|
||||
import type { IrRecordShape, IrType, IrUnionDef } from "../../ir/ir.js";
|
||||
import {
|
||||
accessorSlotProp,
|
||||
DYN_HANDLE_KINDS,
|
||||
isDirentShape,
|
||||
isDynTypedRefType,
|
||||
@@ -129,6 +130,8 @@ export interface DynHost extends WalkerHost {
|
||||
/** The shared adapter record: live array capsules read its `arrayOps`, so a
|
||||
* narrower return type would hand the native compiler a copy without it. */
|
||||
liveDynRefAdapter(t: IrType): LlStreamTypedRefAdapter;
|
||||
/** See moduleUsesDynBuiltinStream. */
|
||||
usesDynBuiltinStream?(): boolean;
|
||||
liveDynUnionRefAdapter(t: IrType & { kind: "union" }): string;
|
||||
dynPromiseAdapter(t: IrType): string;
|
||||
isErrorClass(className: string): boolean;
|
||||
@@ -139,6 +142,19 @@ export interface DynHost extends WalkerHost {
|
||||
|
||||
const FN_ATTRS = "#0";
|
||||
|
||||
/** scr_dyn_builtin_stream_check's kind for a runtime stream class (the
|
||||
* numbering of scr_dyn_builtin_class.c), or null. */
|
||||
function builtinStreamKind(className: string): number | null {
|
||||
const kinds: Record<string, number> = {
|
||||
"%Readable": 2,
|
||||
"%Writable": 3,
|
||||
"%Duplex": 4,
|
||||
"%Transform": 5,
|
||||
"%PassThrough": 6,
|
||||
};
|
||||
return kinds[className] ?? null;
|
||||
}
|
||||
|
||||
export class LlDyn {
|
||||
private readonly dynMatchers = new Map<string, string>();
|
||||
private readonly dynBuilders = new Map<string, string>();
|
||||
@@ -613,6 +629,18 @@ export class LlDyn {
|
||||
* tracking does not count the read's reference. Returns the value and
|
||||
* the matching release. */
|
||||
private memberRead(B: BlockBuilder, key: string, t: IrType): { value: string; release: string } {
|
||||
// An accessor slot reads the getter or setter of the accessor property
|
||||
// the checked-dynamic copy holds for it (see accessorSlotProp).
|
||||
const slot = accessorSlotProp(key);
|
||||
if (slot !== null) {
|
||||
this.host.declare(`declare ptr @scr_dyn_obj_accessor_fn(ptr, ptr, ${this.S}, i1 zeroext)`);
|
||||
this.host.declare(`declare void @scr_dyn_release(ptr)`);
|
||||
const fn = B.tmp();
|
||||
B.line(
|
||||
`${fn} = call ptr @scr_dyn_obj_accessor_fn(ptr %d, ptr ${this.host.cstr(slot.prop)}, ${this.S} ${Buffer.byteLength(slot.prop, "utf8")}, i1 ${slot.kind === "set" ? "true" : "false"})`,
|
||||
);
|
||||
return { value: fn, release: "scr_dyn_release" };
|
||||
}
|
||||
const held = this.isRecursiveShape(t) || this.holdsRecursiveShape(t);
|
||||
const release = held ? "scr_dyn_release_held" : "scr_dyn_release";
|
||||
this.host.declare(`declare void @${release}(ptr)`);
|
||||
@@ -1478,6 +1506,24 @@ export class LlDyn {
|
||||
B.line(`${ref} = call ptr @scr_dyn_typed_ref_unbox(ptr %d)`);
|
||||
B.terminate(`ret ptr ${ref}`);
|
||||
B.startBlock(lNext);
|
||||
// A constructor-function stream (scr_dyn_builtin_stream.c) carries
|
||||
// its runtime stream.
|
||||
const streamKind = t.kind === "object" ? builtinStreamKind(t.className) : null;
|
||||
if (streamKind !== null && this.host.usesDynBuiltinStream?.() === true) {
|
||||
host.declare(`declare ptr @scr_dyn_builtin_stream_check(ptr, double)`);
|
||||
const own = B.tmp(),
|
||||
hit = B.tmp();
|
||||
B.line(
|
||||
`${own} = call ptr @scr_dyn_builtin_stream_check(ptr %d, double ${f64Lit(streamKind)})`,
|
||||
);
|
||||
B.line(`${hit} = icmp ne ptr ${own}, null`);
|
||||
const lOwn = B.newLabel("dc.ctor"),
|
||||
lNotOwn = B.newLabel("dc.notctor");
|
||||
B.condBr(hit, lOwn, lNotOwn);
|
||||
B.startBlock(lOwn);
|
||||
B.terminate(`ret ptr ${own}`);
|
||||
B.startBlock(lNotOwn);
|
||||
}
|
||||
if (t.kind !== "union" && preserveRefs) {
|
||||
const kind = this.kindOf(B, "%d");
|
||||
const capsule = B.tmp();
|
||||
@@ -2687,13 +2733,53 @@ export class LlDyn {
|
||||
// Keys insert in DECLARED order (JS insertion order); internal
|
||||
// '%'-fields follow so a record→dyn→record round trip keeps them.
|
||||
const byName = new Map(shape.fields.map((f) => [f.name, f]));
|
||||
const order = shape.declaredOrder ?? shape.fields.map((f) => f.name);
|
||||
// An accessor property is one key whose getter and setter are the
|
||||
// slot closures (see accessorSlotProp), in accessorOrder's place.
|
||||
const order = shape.accessorOrder ?? shape.declaredOrder ?? shape.fields.map((f) => f.name);
|
||||
const inOrder = new Set(order);
|
||||
const dynFields = [
|
||||
...order.map((n) => byName.get(n)).filter((f) => f !== undefined),
|
||||
...shape.fields.filter((f) => !inOrder.has(f.name)),
|
||||
const accessors = new Set(
|
||||
shape.accessorOrder === undefined
|
||||
? []
|
||||
: shape.fields.flatMap((f) => {
|
||||
const slot = accessorSlotProp(f.name);
|
||||
return slot === null ? [] : [slot.prop];
|
||||
}),
|
||||
);
|
||||
type DynField = { name: string; type: IrType } | { accessor: string };
|
||||
const dynFields: DynField[] = [
|
||||
...order.flatMap((n) => {
|
||||
if (accessors.has(n)) return [{ accessor: n } as DynField];
|
||||
const f = byName.get(n);
|
||||
return f === undefined ? [] : [f as DynField];
|
||||
}),
|
||||
...shape.fields.filter(
|
||||
(f) =>
|
||||
!inOrder.has(f.name) && !(accessors.size > 0 && accessorSlotProp(f.name) !== null),
|
||||
),
|
||||
];
|
||||
for (const f of dynFields) {
|
||||
for (const entry of dynFields) {
|
||||
if ("accessor" in entry) {
|
||||
host.declare(
|
||||
`declare void @scr_dyn_obj_set_accessor(ptr, ptr, ${host.sizeType}, ptr, ptr)`,
|
||||
);
|
||||
const boxed = (kind: "get" | "set"): string => {
|
||||
const slot = byName.get(`%${kind}:${entry.accessor}`);
|
||||
if (slot === undefined) return "null";
|
||||
const fv = loadFieldOf(slot.name, slot.type);
|
||||
const conv = B.tmp();
|
||||
B.line(
|
||||
`${conv} = call ptr @${this.toDynHelper(slot.type)}(${this.valTy(slot.type)} ${fv})`,
|
||||
);
|
||||
return conv;
|
||||
};
|
||||
const getter = boxed("get");
|
||||
const setter = boxed("set");
|
||||
B.line(
|
||||
`call void @scr_dyn_obj_set_accessor(ptr ${d}, ptr ${host.cstr(entry.accessor)}, ${host.sizeType} ${Buffer.byteLength(entry.accessor, "utf8")}, ptr ${getter}, ptr ${setter}) ; ${llvmCommentText(entry.accessor)}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const f = entry;
|
||||
const klen = Buffer.byteLength(f.name, "utf8");
|
||||
const fv = loadFieldOf(f.name, f.type);
|
||||
// An ABSENT optional field contributes no key.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* LlEmitter's module-level output: module parts, helper definitions, library declarations and FFI callback definitions. */
|
||||
import { emitWasmModules } from "./wasm-modules.js";
|
||||
import { type LlEmitter, ffiCallbackDummyLl, llStrBytes } from "./emitter.js";
|
||||
import {
|
||||
mangleAsyncSpawn,
|
||||
@@ -177,6 +178,7 @@ export function emitModuleParts(emitter: LlEmitter): string[] {
|
||||
// programs additionally stamp the httpReq/httpRes ops — the C main's
|
||||
// install lines, gated on the same predicates native/executable.ts links by.
|
||||
const usesNet = runtimeFeatures.net;
|
||||
const usesNetLookup = runtimeFeatures.netLookup;
|
||||
const usesDgram = runtimeFeatures.dgram;
|
||||
const usesHttp2 = runtimeFeatures.http2;
|
||||
const usesHttp = runtimeFeatures.http;
|
||||
@@ -226,6 +228,7 @@ export function emitModuleParts(emitter: LlEmitter): string[] {
|
||||
if (usesFsWatch) emitter.declare(`declare void @scr_watch_install()`);
|
||||
if (emitter.ffiHasForeignCallback) emitter.declare(`declare void @scr_ffi_install()`);
|
||||
if (usesStream) emitter.declare(`declare void @scr_stream_install()`);
|
||||
if (usesNetLookup) emitter.declare(`declare void @scr_net_lookup_install()`);
|
||||
if (usesNet) {
|
||||
emitter.declare(`declare void @scr_net_install()`);
|
||||
emitter.declare(`declare void @scr_net_dyn_install()`);
|
||||
@@ -310,6 +313,7 @@ export function emitModuleParts(emitter: LlEmitter): string[] {
|
||||
emitter.declare(`declare zeroext i1 @scr_loop_run(ptr)`);
|
||||
if (emitter.mod.commonJsEntry === true)
|
||||
emitter.declare(`declare void @scr_loop_commonjs_entry()`);
|
||||
if (emitter.readsLoopMetrics()) emitter.declare(`declare void @scr_perf_loop_mark()`);
|
||||
emitter.declare(`declare zeroext i1 @scr_report_unhandled_rejections()`);
|
||||
emitter.declare(`declare void @scr_discard_unhandled_rejections()`);
|
||||
}
|
||||
@@ -686,11 +690,17 @@ export function emitModuleParts(emitter: LlEmitter): string[] {
|
||||
// runtime finds them before data + cap * 9), and the ScrArr header over
|
||||
// it (rc == SIZE_MAX, len == cap == filled, SCR_ELEM_STR = 2, no REF
|
||||
// entry points). Every dense slot is present; reads retain immortal
|
||||
// strings — a no-op.
|
||||
// strings — a no-op. Slots are 8 bytes on every target: on 32-bit
|
||||
// targets each holds the pointer in its low half and zero in its high
|
||||
// half, so the states start where the runtime looks for them.
|
||||
const n = inst.slots.length;
|
||||
const present = n === 0 ? "zeroinitializer" : `[ ${inst.slots.map(() => "i8 1").join(", ")} ]`;
|
||||
const wide = emitter.sizeType === "i64";
|
||||
const slot = wide ? "ptr" : "<{ ptr, i32 }>";
|
||||
const slotValue = (s: string): string =>
|
||||
wide ? `ptr ${s}` : `<{ ptr, i32 }> <{ ptr ${s}, i32 0 }>`;
|
||||
out.push(
|
||||
`@${inst.sym}_data = internal constant <{ [${n} x ptr], [${n} x i8] }> <{ [${n} x ptr] [ ${inst.slots.map((s) => `ptr ${s}`).join(", ")} ], [${n} x i8] ${present} }>, align 8`,
|
||||
`@${inst.sym}_data = internal constant <{ [${n} x ${slot}], [${n} x i8] }> <{ [${n} x ${slot}] [ ${inst.slots.map(slotValue).join(", ")} ], [${n} x i8] ${present} }>, align 8`,
|
||||
`@${inst.sym} = internal global %ScrArr { ${emitter.sizeType} -1, i32 ${n}, i32 ${n}, i8 2, i8 0, i16 0, i32 ${n}, ptr null, ptr @${inst.sym}_data, ptr null }`,
|
||||
);
|
||||
}
|
||||
@@ -729,6 +739,9 @@ export function emitModuleParts(emitter: LlEmitter): string[] {
|
||||
out.push(fnDefs[0] ?? "");
|
||||
for (let i = 1; i < fnDefs.length; i++) out.push("", fnDefs[i]!);
|
||||
out.push("");
|
||||
// WebAssembly modules translated into this program, and the registry.
|
||||
for (const line of emitWasmModules(emitter.mod, flushedDecls, emitter.wasmBoundsChecks))
|
||||
out.push(line);
|
||||
|
||||
// main(): scr_init, the program-dependent error-vt interval stamps,
|
||||
// scr_lib_init(argc, argv), then the entry function. An uncaught
|
||||
@@ -838,6 +851,7 @@ export function emitModuleParts(emitter: LlEmitter): string[] {
|
||||
...(snapshotsTlsCa ? [` call void @scr_tls_ca_install()`] : []),
|
||||
...(usesFetch ? [` call void @scr_fetch_install()`] : []),
|
||||
...(usesNet ? [` call void @scr_net_install()`, ` call void @scr_net_dyn_install()`] : []),
|
||||
...(usesNetLookup ? [` call void @scr_net_lookup_install()`] : []),
|
||||
...(usesHttp ? [` call void @scr_http_dyn_install()`] : []),
|
||||
...(usesDgram ? [` call void @scr_dgram_install()`] : []),
|
||||
...(usesHttp2 ? [` call void @scr_http2_dyn_install()`] : []),
|
||||
@@ -871,6 +885,8 @@ export function emitModuleParts(emitter: LlEmitter): string[] {
|
||||
...(runsLoop
|
||||
? [
|
||||
...(emitter.mod.commonJsEntry === true ? [` call void @scr_loop_commonjs_entry()`] : []),
|
||||
// performance.eventLoopUtilization measures from here on.
|
||||
...(emitter.readsLoopMetrics() ? [` call void @scr_perf_loop_mark()`] : []),
|
||||
` %loop_rejection = call zeroext i1 @scr_loop_run(ptr ${asyncEntry ? "%top" : "null"})`,
|
||||
` %lexc = call zeroext i1 @scr_exc_pending()`,
|
||||
` br i1 %lexc, label %luncaught, label %lok`,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { wasmBoundsChecks } from "./wasm-modules.js";
|
||||
import { jsTruthy } from "./jsval.js";
|
||||
import { type ArraySearchKind } from "./array-search.js";
|
||||
import { emitStringInputs } from "./string-lifetimes.js";
|
||||
@@ -52,7 +53,11 @@ import {
|
||||
VOID,
|
||||
classJsName,
|
||||
} from "../../ir/ir.js";
|
||||
import { moduleRuntimeFeatures, moduleUsesMockTimers } from "../../ir/runtime-features.js";
|
||||
import {
|
||||
moduleRuntimeFeatures,
|
||||
moduleUsesDynBuiltinStream,
|
||||
moduleUsesMockTimers,
|
||||
} from "../../ir/runtime-features.js";
|
||||
import { captureFreeFunctions, observesStacks } from "../stack-observations.js";
|
||||
import { type DenseArrayLoop } from "./dense-array-loops.js";
|
||||
import { findInitializedByteLoopBindings } from "./byte-windows.js";
|
||||
@@ -181,6 +186,7 @@ import {
|
||||
genResultThunkFor,
|
||||
childExitThunkFor,
|
||||
childExitSignalThunkFor,
|
||||
childDataDynThunkFor,
|
||||
childDataThunkFor,
|
||||
execFileThunkFor,
|
||||
ipcMessageThunkFor,
|
||||
@@ -413,6 +419,9 @@ export interface LlvmTargetOptions {
|
||||
* teardown after `process.exit` to a helper process (`--detached-exit`).
|
||||
* Executables only; the runtime's weak default keeps it off. */
|
||||
detachedExit?: boolean;
|
||||
/** Translated WebAssembly memories check bounds explicitly instead of
|
||||
* relying on guard pages (wasm-modules.ts wasmBoundsChecks). */
|
||||
wasmBoundsChecks?: boolean;
|
||||
}
|
||||
|
||||
export function emitLlvmModule(input: IrModule, options: LlvmTargetOptions = {}): string {
|
||||
@@ -675,6 +684,7 @@ export class LlEmitter {
|
||||
readonly emitLibraryIdentity: boolean;
|
||||
readonly runtimeAbiMarker: boolean;
|
||||
readonly detachedExit: boolean;
|
||||
readonly wasmBoundsChecks: boolean;
|
||||
/** Interned string literals: UTF-8 text → { symbol, byte length } —
|
||||
* first-use order, the runtime ABI’s determinism discipline. */
|
||||
readonly literals = new Map<string, { sym: string; len: number }>();
|
||||
@@ -889,6 +899,11 @@ export class LlEmitter {
|
||||
/** Identity-preserving static→dyn capsules for program classes and Web
|
||||
* APIs whose values remain directly observable. */
|
||||
readonly liveDynRefAdapters = new Map<string, LlStreamTypedRefAdapter>();
|
||||
private dynBuiltinStreamUse: boolean | undefined;
|
||||
/** See moduleUsesDynBuiltinStream. */
|
||||
get usesDynBuiltinStream(): boolean {
|
||||
return (this.dynBuiltinStreamUse ??= moduleUsesDynBuiltinStream(this.mod));
|
||||
}
|
||||
/** Runtime-arm dispatchers for live union values. */
|
||||
readonly liveDynUnionRefAdapters = new Map<string, string>();
|
||||
readonly dynPromiseAdapters = new Map<string, string>();
|
||||
@@ -1168,6 +1183,13 @@ export class LlEmitter {
|
||||
this.emitLibraryIdentity = options.emitLibraryIdentity !== false;
|
||||
this.runtimeAbiMarker = options.runtimeAbiMarker === true;
|
||||
this.detachedExit = options.detachedExit === true;
|
||||
this.wasmBoundsChecks =
|
||||
options.wasmBoundsChecks === true ||
|
||||
wasmBoundsChecks({
|
||||
sanitize: false,
|
||||
pointerBits: options.pointerBits ?? 64,
|
||||
targetTriple: options.targetTriple ?? "",
|
||||
});
|
||||
this.rcHelpers = options.inlineRc !== undefined ? new Set() : null;
|
||||
this.rcMode = options.inlineRc;
|
||||
this.rcHelpersNoinline = options.rcHelpersNoinline === true;
|
||||
@@ -1385,6 +1407,7 @@ export class LlEmitter {
|
||||
fieldAbsentTestIn: (B, value, unionId) => this.fieldAbsentTestIn(B, value, unionId),
|
||||
absentInstanceRef: (unionId) => this.absentInstanceRef(unionId),
|
||||
liveDynRefAdapter: (type) => this.liveDynRefAdapter(type),
|
||||
usesDynBuiltinStream: () => this.usesDynBuiltinStream,
|
||||
liveDynUnionRefAdapter: (type) => this.liveDynUnionRefAdapter(type),
|
||||
dynPromiseAdapter: (type) => this.dynPromiseAdapter(type),
|
||||
isErrorClass: (name) => this.classMeta.get(name)?.root.def.name === "%Error",
|
||||
@@ -4194,6 +4217,20 @@ export class LlEmitter {
|
||||
|
||||
/** Moves need plain slots that nothing else reads behind the binding's
|
||||
* back: no debugger view, no coroutine frame, no borrowed (+0) return. */
|
||||
/** Whether the program reads event-loop utilization (perf.loopStart /
|
||||
* perf.idleTime): main then marks the loop's start before running it. */
|
||||
private loopMetricsRead: boolean | undefined;
|
||||
readsLoopMetrics(): boolean {
|
||||
if (this.loopMetricsRead !== undefined) return this.loopMetricsRead;
|
||||
const visitor = {
|
||||
expr: (e: IrExpr): boolean =>
|
||||
!(e.kind === "libCall" && (e.fn === "perf.loopStart" || e.fn === "perf.idleTime")),
|
||||
stmt: (): boolean => true,
|
||||
};
|
||||
this.loopMetricsRead = !this.mod.functions.every((fn) => everyStmtList(fn.body, visitor));
|
||||
return this.loopMetricsRead;
|
||||
}
|
||||
|
||||
private lastUseMovesAllowed(): boolean {
|
||||
return (
|
||||
this.debug === null &&
|
||||
@@ -5557,12 +5594,19 @@ export class LlEmitter {
|
||||
return childDataThunkFor(this, param);
|
||||
}
|
||||
|
||||
childDataDynThunkFor(chunk: "bytes" | "str"): string {
|
||||
return childDataDynThunkFor(this, chunk);
|
||||
}
|
||||
|
||||
emitterFixedAdapter(cbT: IrType & { kind: "func" }): { fn: string; shim: string } {
|
||||
return emitterFixedAdapter(this, cbT);
|
||||
}
|
||||
|
||||
emitterOnceAdapter(boxerT: IrType & { kind: "func" }): { fn: string; shim: string } {
|
||||
return emitterOnceAdapter(this, boxerT);
|
||||
emitterOnceAdapter(
|
||||
boxerT: IrType & { kind: "func" },
|
||||
fire?: string,
|
||||
): { fn: string; shim: string } {
|
||||
return emitterOnceAdapter(this, boxerT, fire);
|
||||
}
|
||||
|
||||
wrapEmitterListener(target: string, adapterFn: string): string {
|
||||
|
||||
@@ -55,6 +55,23 @@ export function raceAdapterFor(host: LlvmEmitterContext, from: IrType, to: IrTyp
|
||||
if (sym) return sym;
|
||||
sym = `sc_race_${host.resolveThunks.size}`;
|
||||
host.resolveThunks.set(key, sym);
|
||||
const toArms = to.kind === "union" ? (host.unionsById.get(to.unionId)?.arms ?? []) : [];
|
||||
if (from.kind === "void" && !toArms.some((a) => a.kind === "undefinedT")) {
|
||||
// A Promise<never> entry (the frontend admits void entries without an
|
||||
// undefined result arm only for those): it can only reject, so its
|
||||
// fulfillment adapter never runs.
|
||||
host.declare(`declare void @scr_trap(ptr)`);
|
||||
const msg = host.cstr("scriptc: a Promise<never> race entry fulfilled\n");
|
||||
host.resolveThunkDefs.push(
|
||||
`define internal void @${sym}(ptr %dst, ptr %src) ${FN_ATTRS} { ; race ${key}`,
|
||||
`entry:`,
|
||||
` call void @scr_trap(ptr ${msg})`,
|
||||
` unreachable`,
|
||||
`}`,
|
||||
``,
|
||||
);
|
||||
return sym;
|
||||
}
|
||||
if (to.kind !== "union")
|
||||
throw new InternalCompilerError("llvm emitter bug: race adapter to a non-union");
|
||||
const toDef = host.unionsById.get(to.unionId);
|
||||
@@ -754,6 +771,31 @@ export function childDataThunkFor(host: LlvmEmitterContext, param: IrType): stri
|
||||
return sym;
|
||||
}
|
||||
|
||||
/** The child-stream 'data' thunk for an unknown/any chunk parameter: the
|
||||
* runtime's Buffer (or, after setEncoding, string) chunk boxes into a
|
||||
* checked-dynamic value the listener owns. */
|
||||
export function childDataDynThunkFor(host: LlvmEmitterContext, chunk: "bytes" | "str"): string {
|
||||
const key = `cdd:${chunk}`;
|
||||
let sym = host.resolveThunks.get(key);
|
||||
if (sym) return sym;
|
||||
sym = `sc_cdd_${host.resolveThunks.size}`;
|
||||
host.resolveThunks.set(key, sym);
|
||||
const box = chunk === "bytes" ? "scr_dyn_new_buffer" : "scr_dyn_new_str";
|
||||
host.declare(`declare ptr @${box}(ptr)`);
|
||||
host.resolveThunkDefs.push(
|
||||
`define internal void @${sym}(ptr %cb, ptr %chunk) ${FN_ATTRS} { ; child 'data' → dyn (${chunk})`,
|
||||
`entry:`,
|
||||
` %d = call ptr @${box}(ptr %chunk)`,
|
||||
` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`,
|
||||
` %fn = load ptr, ptr %fnp`,
|
||||
` call void %fn(ptr %cb, ptr %d)`,
|
||||
` ret void`,
|
||||
`}`,
|
||||
``,
|
||||
);
|
||||
return sym;
|
||||
}
|
||||
|
||||
export function emitterFixedAdapter(
|
||||
host: LlvmEmitterContext,
|
||||
cbT: IrType & { kind: "func" },
|
||||
@@ -822,23 +864,27 @@ export function emitterFixedAdapter(
|
||||
* (the event's tuple to any[]): the runtime-built listener closure holds
|
||||
* the boxer in caps[0], so the adapter calls it with the event's typed
|
||||
* arguments, as emitterFixedAdapter calls a listener, and hands the array
|
||||
* to scr_events_once_fire. */
|
||||
* to scr_events_once_fire (or `fire`, the signal form's entry). */
|
||||
export function emitterOnceAdapter(
|
||||
host: LlvmEmitterContext,
|
||||
boxerT: IrType & { kind: "func" },
|
||||
fire = "scr_events_once_fire",
|
||||
): { fn: string; shim: string } {
|
||||
const cursor = boxerT.params.length > 4;
|
||||
const shim = cursor ? "scr_ee_inv_args" : `scr_ee_inv_fixed${boxerT.params.length}`;
|
||||
if (cursor) host.declare("declare ptr @scr_ee_arg_next(ptr)");
|
||||
host.declare(`declare void @${shim}(ptr, ptr)`);
|
||||
const key = `eeOnce:${typeKey(boxerT)}`;
|
||||
const key =
|
||||
fire === "scr_events_once_fire"
|
||||
? `eeOnce:${typeKey(boxerT)}`
|
||||
: `eeOnce:${fire}:${typeKey(boxerT)}`;
|
||||
let sym = host.resolveThunks.get(key);
|
||||
if (sym) return { fn: sym, shim };
|
||||
sym = `sc_ee_once_${host.resolveThunks.size}`;
|
||||
host.resolveThunks.set(key, sym);
|
||||
host.declare(`declare ptr @scr_box_get_ref(ptr)`);
|
||||
host.declare(`declare void @scr_closure_release(ptr)`);
|
||||
host.declare(`declare void @scr_events_once_fire(ptr, ptr)`);
|
||||
host.declare(`declare void @${fire}(ptr, ptr)`);
|
||||
const params = cursor ? "ptr %args" : boxerT.params.map((_, i) => `ptr %a${i}`).join(", ");
|
||||
const d: string[] = [
|
||||
`define internal void @${sym}(ptr %cb${params ? ", " + params : ""}) ${FN_ATTRS} { ; events.once adapter ${typeKey(boxerT)}`,
|
||||
@@ -869,7 +915,7 @@ export function emitterOnceAdapter(
|
||||
` %fn = load ptr, ptr %fnp`,
|
||||
` %arr = call ptr %fn(${passed.join(", ")}) ; the boxed arguments, +1`,
|
||||
` call void @scr_closure_release(ptr %boxer)`,
|
||||
` call void @scr_events_once_fire(ptr %cb, ptr %arr) ; moves %arr`,
|
||||
` call void @${fire}(ptr %cb, ptr %arr) ; moves %arr`,
|
||||
` ret void`,
|
||||
`}`,
|
||||
``,
|
||||
|
||||
@@ -535,6 +535,27 @@ export function emitDynamicExpr(
|
||||
e.key.type.kind === "dyn" ? host.dyn.dynComputedKeyGetHelper() : host.dyn.dynKeyGetHelper();
|
||||
const t = B.tmp();
|
||||
const generic = `call ptr @${helper}(ptr ${d.name}, ptr ${k.name}, i1 ${e.optional ? "true" : "false"})`;
|
||||
const inherited = e.key.kind === "strLit" && INHERITED_OBJECT_MEMBERS.has(e.key.value);
|
||||
// Object.prototype itself answers its own members before the ordinary
|
||||
// read (which fences reflection on the prototype).
|
||||
let prototypeJoin: { member: string; from: string; join: string } | null = null;
|
||||
if (inherited) {
|
||||
host.declare(`declare ptr @scr_dyn_object_prototype_member(ptr, ptr)`);
|
||||
const member = B.tmp();
|
||||
B.line(
|
||||
`${member} = call ptr @scr_dyn_object_prototype_member(ptr ${d.name}, ptr ${k.name})`,
|
||||
);
|
||||
const isMember = B.tmp();
|
||||
B.line(`${isMember} = icmp ne ptr ${member}, null`);
|
||||
const from = B.newLabel("dkg.op");
|
||||
const read = B.newLabel("dkg.rd");
|
||||
const join = B.newLabel("dkg.opj");
|
||||
B.condBr(isMember, from, read);
|
||||
B.startBlock(from);
|
||||
B.br(join);
|
||||
B.startBlock(read);
|
||||
prototypeJoin = { member, from, join };
|
||||
}
|
||||
if (e.key.kind === "strLit") {
|
||||
// A constant key on an ordinary object reads through the site's
|
||||
// cache; every other receiver keeps the shared helper.
|
||||
@@ -560,7 +581,27 @@ export function emitDynamicExpr(
|
||||
} else {
|
||||
B.line(`${t} = ${generic}`);
|
||||
}
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
let result = t;
|
||||
if (prototypeJoin) {
|
||||
// An undefined read of an Object.prototype member name answers the
|
||||
// member the receiver inherits (scr_dyn_invoke.c; the frontend
|
||||
// links that unit for these reads).
|
||||
host.declare(`declare ptr @scr_dyn_inherited_read(ptr, ptr, ptr)`);
|
||||
const read = B.tmp();
|
||||
B.line(
|
||||
`${read} = call ptr @scr_dyn_inherited_read(ptr ${d.name}, ptr ${k.name}, ptr ${t})`,
|
||||
);
|
||||
const readEnd = B.newLabel("dkg.rde");
|
||||
B.br(readEnd);
|
||||
B.startBlock(readEnd);
|
||||
B.br(prototypeJoin.join);
|
||||
B.startBlock(prototypeJoin.join);
|
||||
result = B.tmp();
|
||||
B.line(
|
||||
`${result} = phi ptr [ ${prototypeJoin.member}, %${prototypeJoin.from} ], [ ${read}, %${readEnd} ]`,
|
||||
);
|
||||
}
|
||||
const out = host.own({ name: result, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
@@ -1204,3 +1245,13 @@ function emitUnionEqWrappedScalar(
|
||||
function wrappedStringLiteral(e: IrExpr): string | null {
|
||||
return e.kind === "unionWrap" && e.value.kind === "strLit" ? e.value.value : null;
|
||||
}
|
||||
|
||||
/** Object.prototype member names whose constant-key dynamic reads fall back
|
||||
* to the inherited member (runtime-features.ts links the unit). */
|
||||
export const INHERITED_OBJECT_MEMBERS: ReadonlySet<string> = new Set([
|
||||
"constructor",
|
||||
"valueOf",
|
||||
"toString",
|
||||
"hasOwnProperty",
|
||||
"propertyIsEnumerable",
|
||||
]);
|
||||
|
||||
@@ -306,3 +306,28 @@ test("sums that only feed a bitwise conversion of int32 operands add in 32 bits"
|
||||
}
|
||||
expect(body("number_add")).not.toMatch(/ = add i32 |fcmp ord/);
|
||||
});
|
||||
|
||||
test.each([32, 64] as const)(
|
||||
"tagged-template string arrays keep 8-byte slots before their states on %i-bit targets",
|
||||
(bits) => {
|
||||
const strings: IrExpr = {
|
||||
kind: "templateStrings",
|
||||
key: "site",
|
||||
cooked: ["a", "b", "c"],
|
||||
type: { kind: "array", elem: STRING },
|
||||
loc,
|
||||
};
|
||||
const mod = fixture();
|
||||
mod.functions[0]!.body = [{ kind: "exprStmt", expr: strings, loc }];
|
||||
const llvm = emitLlvmModule(mod, { pointerBits: bits });
|
||||
const data = /^@sc_tsa_0_data = internal constant .*$/m.exec(llvm)![0];
|
||||
// The runtime reads each slot as 8 bytes and finds the states after
|
||||
// cap * 8 bytes of slots (scr_arr_states_end).
|
||||
if (bits === 64) expect(data).toContain("<{ [3 x ptr], [3 x i8] }>");
|
||||
else {
|
||||
expect(data).toContain("<{ [3 x <{ ptr, i32 }>], [3 x i8] }>");
|
||||
expect(data.match(/<\{ ptr @\S+, i32 0 \}>/g)).toHaveLength(3);
|
||||
}
|
||||
expect(data).toContain("[3 x i8] [ i8 1, i8 1, i8 1 ]");
|
||||
},
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
isDirentShape,
|
||||
isDynTypedRefType,
|
||||
isRefCounted,
|
||||
shapeHasAccessorSlots,
|
||||
typeKey,
|
||||
} from "../../ir/ir.js";
|
||||
import { mangleFunction, mangleGlobal, mangleRecordStruct } from "../mangle.js";
|
||||
@@ -765,8 +766,13 @@ export function streamTypedRefMaterializeAdapter(
|
||||
!meta.def.instancePrototypeHelper &&
|
||||
!meta.def.prototypeDataHelper
|
||||
) {
|
||||
host.declare(`declare ptr @scr_stream_dyn_view(ptr)`);
|
||||
B.line(`${out} = call ptr @scr_stream_dyn_view(ptr %p)`);
|
||||
// Programs with constructor-function streams view them through
|
||||
// scr_dyn_builtin_stream.c, which answers their own objects.
|
||||
const view = host.usesDynBuiltinStream
|
||||
? "scr_dyn_builtin_stream_view"
|
||||
: "scr_stream_dyn_view";
|
||||
host.declare(`declare ptr @${view}(ptr)`);
|
||||
B.line(`${out} = call ptr @${view}(ptr %p)`);
|
||||
} else B.line(`${out} = call ptr @scr_dyn_new_obj()`);
|
||||
const withAttributes = meta.def.fields.some((field) => field.name === CLASS_ATTRIBUTES);
|
||||
if (meta.def.tracksOwnFields) {
|
||||
@@ -941,6 +947,18 @@ export function streamTypedRefMaterializeAdapter(
|
||||
B.line(`${copied} = call ptr @scr_dyn_copy_property_descriptors(ptr ${out}, ptr ${bag})`);
|
||||
B.line(`call void @scr_dyn_release_v(ptr ${copied})`);
|
||||
}
|
||||
// An Error subclass instance's own `name` sits where its class's
|
||||
// construction creates it: after a field, or after every other
|
||||
// property when no constructor sets it.
|
||||
const nameAfter = meta.def.errorNameAfter;
|
||||
if (meta.root.def.name === "%Error" && !meta.def.runtime && nameAfter !== null) {
|
||||
host.declare(`declare void @scr_error_dyn_place_name(ptr, ptr, ${host.sizeType})`);
|
||||
B.line(
|
||||
nameAfter === undefined
|
||||
? `call void @scr_error_dyn_place_name(ptr ${out}, ptr null, ${host.sizeType} 0)`
|
||||
: `call void @scr_error_dyn_place_name(ptr ${out}, ptr ${host.cstr(nameAfter)}, ${host.sizeType} ${Buffer.byteLength(nameAfter, "utf8")})`,
|
||||
);
|
||||
}
|
||||
// A frozen, sealed or non-extensible instance's view says so.
|
||||
if (withAttributes) {
|
||||
host.declare(`declare void @scr_dyn_class_view_attributes(ptr, double)`);
|
||||
@@ -965,6 +983,7 @@ export function streamTypedRefMaterializeAdapter(
|
||||
* Declared-field records and tuples cover the live stream values. */
|
||||
const ordinary =
|
||||
shape.indexValue ||
|
||||
shapeHasAccessorSlots(shape) ||
|
||||
shape.fields.some((field) => field.name === "handleEvent" && field.type.kind === "func");
|
||||
if (ordinary) {
|
||||
const out = B.tmp();
|
||||
|
||||
@@ -17,7 +17,7 @@ export function streamDataAdapter(
|
||||
const p = cbT.params[0];
|
||||
if (
|
||||
cbT.params.length > 1 ||
|
||||
(p && p.kind !== "bytes" && p.kind !== "string" && p.kind !== "dyn")
|
||||
(p && p.kind !== "bytes" && p.kind !== "string" && p.kind !== "dyn" && p.kind !== "union")
|
||||
) {
|
||||
throw new InternalCompilerError(
|
||||
"llvm emitter bug: stream data listener param shape (frontend must fence)",
|
||||
@@ -73,6 +73,46 @@ export function streamDataAdapter(
|
||||
);
|
||||
finish("%r0");
|
||||
d.push(`}`, ``);
|
||||
} else if (p.kind === "union") {
|
||||
// A `Buffer | string` chunk: wrap whichever slot the stream filled at
|
||||
// its arm of the (boxed) union.
|
||||
const def = host.unionsById.get(p.unionId);
|
||||
const bytesTag = def ? def.arms.findIndex((a) => a.kind === "bytes" && a.elem === "u8") : -1;
|
||||
const strTag = def ? def.arms.findIndex((a) => a.kind === "string") : -1;
|
||||
if (
|
||||
!def ||
|
||||
host.nullableUnions.has(p.unionId) ||
|
||||
bytesTag < 0 ||
|
||||
strTag < 0 ||
|
||||
def.arms.length !== 2
|
||||
)
|
||||
throw new InternalCompilerError(
|
||||
"llvm emitter bug: stream data listener union is not Buffer | string (frontend must fence)",
|
||||
);
|
||||
host.declare(`declare ptr @scr_union_new_ref(i32, ptr, ptr, ptr, ptr)`);
|
||||
host.declare(`declare ptr @scr_bytes_retain_v(ptr)`);
|
||||
host.declare(`declare void @scr_bytes_release_v(ptr)`);
|
||||
host.declare(`declare ptr @scr_str_retain_v(ptr)`);
|
||||
host.declare(`declare void @scr_str_release_v(ptr)`);
|
||||
d.push(
|
||||
` %uslot = alloca ptr`,
|
||||
` %isb = icmp ne ptr %a0, null`,
|
||||
` br i1 %isb, label %buf, label %str`,
|
||||
`buf:`,
|
||||
` %rb = call ptr @scr_bytes_retain_v(ptr %a0)`,
|
||||
` %ub = call ptr @scr_union_new_ref(i32 ${bytesTag}, ptr %rb, ptr @scr_bytes_retain_v, ptr @scr_bytes_release_v, ptr null)`,
|
||||
` store ptr %ub, ptr %uslot`,
|
||||
` br label %go`,
|
||||
`str:`,
|
||||
` %rs = call ptr @scr_str_retain_v(ptr %a1)`,
|
||||
` %us = call ptr @scr_union_new_ref(i32 ${strTag}, ptr %rs, ptr @scr_str_retain_v, ptr @scr_str_release_v, ptr null)`,
|
||||
` store ptr %us, ptr %uslot`,
|
||||
` br label %go`,
|
||||
`go:`,
|
||||
` %uv = load ptr, ptr %uslot`,
|
||||
);
|
||||
finish("%uv");
|
||||
d.push(`}`, ``);
|
||||
} else {
|
||||
// dyn: box by runtime tag — the JS lane's adapter parameter.
|
||||
host.declare(`declare ptr @scr_dyn_new_buffer(ptr)`);
|
||||
|
||||
@@ -236,6 +236,10 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L
|
||||
return finishRuntimeCall(host, e, "scr_dns_lookup_dyn", a);
|
||||
case "dns.lookupService":
|
||||
return finishRuntimeCall(host, e, "scr_dns_lookup_service", a);
|
||||
case "dns.promisesLookup":
|
||||
return finishRuntimeCall(host, e, "scr_dns_promises_lookup", a);
|
||||
case "dns.promisesLookupService":
|
||||
return finishRuntimeCall(host, e, "scr_dns_promises_lookup_service", a);
|
||||
case "dns.getServers":
|
||||
return finishRuntimeCall(host, e, "scr_dns_get_servers", a);
|
||||
case "dns.getDefaultResultOrder":
|
||||
|
||||
@@ -548,7 +548,12 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
mockTimerSym(host, e) ??
|
||||
(e.fn === "timers.queueMicrotaskDyn" && host.schedulesInContext
|
||||
? "scr_queue_microtask_dyn_als"
|
||||
: LIB_FN_SYMS[e.fn]);
|
||||
: // Programs with constructor-function streams unbox untyped pipe
|
||||
// destinations and pipeline stages through scr_dyn_builtin_stream.c,
|
||||
// which also takes those streams.
|
||||
e.fn === "stream.unboxDyn" && host.usesDynBuiltinStream
|
||||
? "scr_dyn_builtin_stream_unbox"
|
||||
: LIB_FN_SYMS[e.fn]);
|
||||
if (sym === undefined) throw new LlvmUnsupportedError(`libCall:${e.fn}`, e.loc);
|
||||
const args = READ_ONLY_STRING_LIB_FNS.has(e.fn)
|
||||
? emitBorrowedInputs(host, e.args)
|
||||
@@ -755,7 +760,8 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
|
||||
}
|
||||
const prefix = e.fn.slice(0, e.fn.indexOf(".")) as LibCallPrefix;
|
||||
// events.once builds typed listener adapters beside the emitter calls.
|
||||
if (e.fn === "events.once") return host.emitErrorsEventsLibCall(e);
|
||||
if (e.fn === "events.once" || e.fn === "events.onceSignal")
|
||||
return host.emitErrorsEventsLibCall(e);
|
||||
switch (prefix) {
|
||||
case "fetch":
|
||||
case "abort":
|
||||
@@ -857,6 +863,7 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
|
||||
case "event":
|
||||
case "events":
|
||||
case "blob":
|
||||
case "wasm":
|
||||
case "record":
|
||||
case "disposable":
|
||||
case "punycode":
|
||||
|
||||
@@ -60,7 +60,54 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
...handler,
|
||||
]);
|
||||
}
|
||||
if (
|
||||
e.fn === "http.requestDyn" ||
|
||||
e.fn === "http.requestDynCb" ||
|
||||
e.fn === "https.requestDyn" ||
|
||||
e.fn === "https.requestDynCb"
|
||||
) {
|
||||
// [options, autoEnd(, cb)]: the runtime walks the options record and
|
||||
// attaches its signal through events.addAbortListener (the event-target
|
||||
// unit, linked for these calls by runtime-features).
|
||||
const args = e.args.map((arg) => host.emitExpr(arg));
|
||||
host.usesTimers = true;
|
||||
const cb = args[2];
|
||||
const handler =
|
||||
cb === undefined
|
||||
? [ptr(), ptr()]
|
||||
: callbackAdapter(
|
||||
host,
|
||||
cb,
|
||||
callbackType(cb).params.length === 0
|
||||
? "scr_http_resp_thunk0"
|
||||
: "scr_http_resp_thunk_res",
|
||||
["ptr", "ptr"],
|
||||
);
|
||||
host.declare(`declare ptr @scr_events_add_abort_listener(ptr, ptr)`);
|
||||
return finishRuntimeCall(
|
||||
host,
|
||||
e,
|
||||
e.fn.startsWith("https.") ? "scr_https_request_dyn" : "scr_http_request_dyn",
|
||||
[
|
||||
abiValue(host, args[0]!),
|
||||
abiValue(host, args[1]!),
|
||||
...handler,
|
||||
ptr("@scr_events_add_abort_listener"),
|
||||
],
|
||||
);
|
||||
}
|
||||
const B = host.B;
|
||||
if (e.fn === "net.listenPath" || e.fn === "net.listenPathCb") {
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
let cb = "null";
|
||||
if (e.fn === "net.listenPathCb") {
|
||||
host.toHeap(args[2]!);
|
||||
cb = args[2]!.name;
|
||||
}
|
||||
host.declare(`declare void @scr_net_listen_path(ptr, ptr, ptr)`);
|
||||
B.line(`call void @scr_net_listen_path(ptr ${args[0]!.name}, ptr ${args[1]!.name}, ptr ${cb})`);
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
if (e.fn === "net.connectOptsChk") {
|
||||
return emitAlwaysThrowLibCall(host, e, "scr_net_connect_opts_chk");
|
||||
}
|
||||
|
||||
@@ -305,6 +305,28 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
B.line(`${raw} = call ptr @${get}(ptr ${recv.name}) ; +1 or NULL`);
|
||||
return host.wrapNullable(raw, raw, def!.arms[streamTag]!, streamTag, e.type, nullTag);
|
||||
}
|
||||
if (e.fn === "child.stdioStream") {
|
||||
// The general stream representation's stdio read: the child's bridged
|
||||
// runtime stream (+1) or NULL for an unpiped slot.
|
||||
if (e.type.kind !== "union")
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} result is not a union`);
|
||||
const def = host.unionsById.get(e.type.unionId);
|
||||
const streamTag = def ? def.arms.findIndex((a) => a.kind === "object") : -1;
|
||||
const nullTag = def ? def.arms.findIndex((a) => a.kind === "nullT") : -1;
|
||||
const fd = e.args[1];
|
||||
if (streamTag < 0 || nullTag < 0 || fd?.kind !== "numLit")
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} lacks its arms or slot`);
|
||||
const recv = host.emitExpr(e.args[0]!);
|
||||
const key = fd.value === 0 ? "stdin" : fd.value === 1 ? "stdout" : "stderr";
|
||||
host.declare(`declare ptr @scr_stream_child_stdio(ptr, ptr, ${host.sizeType})`);
|
||||
const raw = B.tmp();
|
||||
B.line(
|
||||
`${raw} = call ptr @scr_stream_child_stdio(ptr ${recv.name}, ptr ${host.cstr(key)}, ${host.sizeType} ${key.length}) ; +1 or NULL`,
|
||||
);
|
||||
const out = host.wrapNullable(raw, raw, def!.arms[streamTag]!, streamTag, e.type, nullTag);
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
if (e.fn === "writer.writeString" || e.fn === "writer.writeBytes") {
|
||||
const writer = host.emitExpr(e.args[0]!);
|
||||
const data = host.emitExpr(e.args[1]!);
|
||||
@@ -709,27 +731,34 @@ export function emitErrorsEventsLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
host.emitAlwaysPendingUnwind();
|
||||
return out;
|
||||
}
|
||||
if (e.fn === "events.once") {
|
||||
// (recv, name, boxer, errorBoxer, listenForError): scr_events_once
|
||||
// registers one-shot typed listeners behind adapters that box the
|
||||
// event's tuple (and the 'error' tuple) into the fulfilled array. The
|
||||
// runtime retains the boxers; the frame's temporaries release as usual.
|
||||
// May-throw ('newListener' meta listeners run inside).
|
||||
if (e.fn === "events.once" || e.fn === "events.onceSignal") {
|
||||
// (recv, name, boxer, errorBoxer, listenForError[, signal]):
|
||||
// scr_events_once registers one-shot typed listeners behind adapters
|
||||
// that box the event's tuple (and the 'error' tuple) into the fulfilled
|
||||
// array. The runtime retains the boxers; the frame's temporaries
|
||||
// release as usual. May-throw ('newListener' meta listeners run
|
||||
// inside). The signal form's adapters end in its own fire entry.
|
||||
const boxerT = e.args[2]!.type;
|
||||
const errorT = e.args[3]!.type;
|
||||
if (boxerT.kind !== "func" || errorT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: events.once boxer not a func");
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} boxer not a func`);
|
||||
const signal = e.fn === "events.onceSignal";
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
const event = host.emitterOnceAdapter(boxerT);
|
||||
const error = host.emitterOnceAdapter(errorT);
|
||||
host.declare(`declare ptr @scr_events_once(ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr)`);
|
||||
const fire = signal ? "scr_events_once_signal_fire" : undefined;
|
||||
const event = host.emitterOnceAdapter(boxerT, fire);
|
||||
const error = host.emitterOnceAdapter(errorT, fire);
|
||||
const entry = signal ? "scr_events_once_signal" : "scr_events_once";
|
||||
host.declare(
|
||||
`declare ptr @${entry}(ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr${signal ? ", ptr" : ""})`,
|
||||
);
|
||||
const errorBoxer = B.tmp();
|
||||
B.line(`${errorBoxer} = select i1 ${args[4]!.name}, ptr ${args[3]!.name}, ptr null`);
|
||||
const t = B.tmp();
|
||||
B.line(
|
||||
`${t} = call ptr @scr_events_once(ptr ${args[0]!.name}, ptr ${args[1]!.name}, ` +
|
||||
`${t} = call ptr @${entry}(ptr ${args[0]!.name}, ptr ${args[1]!.name}, ` +
|
||||
`ptr ${args[2]!.name}, ptr @${event.fn}, ptr @${event.shim}, ` +
|
||||
`ptr ${errorBoxer}, ptr @${error.fn}, ptr @${error.shim})`,
|
||||
`ptr ${errorBoxer}, ptr @${error.fn}, ptr @${error.shim}` +
|
||||
`${signal ? `, ptr ${args[5]!.name}` : ""})`,
|
||||
);
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
|
||||
@@ -127,6 +127,11 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"str.encodeUriComponent": "scr_str_encode_uri_component",
|
||||
"error.cause": "scr_error_cause",
|
||||
"error.setCause": "scr_error_set_cause",
|
||||
"error.setCode": "scr_error_set_code_value",
|
||||
"error.setCodeDyn": "scr_error_set_code_dyn",
|
||||
"error.assign": "scr_error_assign_dyn",
|
||||
"error.hasKey": "scr_error_has_key",
|
||||
"error.hasCode": "scr_error_has_code",
|
||||
"error.defineCause": "scr_error_define_cause",
|
||||
"error.deleteCause": "scr_error_delete_cause",
|
||||
"error.hasCause": "scr_error_has_cause",
|
||||
@@ -278,6 +283,14 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"perf.entryDetail": "scr_perf_entry_detail",
|
||||
"perf.entryKeep": "scr_perf_entry_keep",
|
||||
"perf.entryRef": "scr_perf_entry_ref",
|
||||
"perf.loopStart": "scr_perf_loop_start",
|
||||
"perf.idleTime": "scr_perf_idle_time",
|
||||
"perf.eldNew": "scr_eld_new",
|
||||
"perf.eldEnable": "scr_eld_enable",
|
||||
"perf.eldDisable": "scr_eld_disable",
|
||||
"perf.eldReset": "scr_eld_reset",
|
||||
"perf.eldPercentile": "scr_eld_percentile",
|
||||
"perf.eldStat": "scr_eld_stat",
|
||||
"process.memoryUsageTake": "scr_process_memory_usage_take",
|
||||
"process.memoryUsageField": "scr_process_memory_usage_field",
|
||||
"process.rss": "scr_process_rss",
|
||||
@@ -310,12 +323,13 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"date.now": "scr_date_now",
|
||||
"date.newNow": "scr_date_now",
|
||||
"date.newMs": "scr_date_new_ms",
|
||||
"date.newString": "scr_date_parse_get_time",
|
||||
"date.parse": "scr_date_parse_get_time",
|
||||
"date.newString": "scr_date_parse",
|
||||
"date.parse": "scr_date_parse",
|
||||
"date.getTime": "scr_date_get_time",
|
||||
"date.valueOf": "scr_date_get_time",
|
||||
"date.parseGetTime": "scr_date_parse_get_time",
|
||||
"date.parseGetTime": "scr_date_parse",
|
||||
"date.utc": "scr_date_utc",
|
||||
"date.newLocal": "scr_date_new_local",
|
||||
"date.getFullYear": "scr_date_get_full_year_local",
|
||||
"date.getUTCFullYear": "scr_date_get_full_year_utc",
|
||||
"date.getMonth": "scr_date_get_month_local",
|
||||
@@ -726,6 +740,10 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"child.unref": "scr_child_unref",
|
||||
"child.ref": "scr_child_ref",
|
||||
"procStream.write": "scr_proc_stream_write",
|
||||
"stream.stdioWritable": "scr_stream_stdio_writable",
|
||||
"stream.unboxDyn": "scr_stream_unbox_dyn",
|
||||
"stream.pipeDyn": "scr_stream_pipe_dyn",
|
||||
"stream.unpipeDyn": "scr_stream_unpipe_dyn",
|
||||
"process.kill": "scr_process_kill_named",
|
||||
"process.killNum": "scr_process_kill",
|
||||
// fs.promises (scr_bytes_io.c / scr_lib.c): the promise forms settle
|
||||
@@ -858,6 +876,24 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"event.get": "scr_event_obj_get",
|
||||
"event.set": "scr_event_obj_set",
|
||||
"blob.new": "scr_blob_new",
|
||||
"wasm.compile": "scr_wasm_compile",
|
||||
"wasm.validate": "scr_wasm_validate",
|
||||
"wasm.instantiate": "scr_wasm_instantiate",
|
||||
"wasm.moduleNew": "scr_wasm_module_new",
|
||||
"wasm.instanceNew": "scr_wasm_instance_new",
|
||||
"wasm.is": "scr_wasm_is",
|
||||
"wasm.errorNew": "scr_wasm_error_new",
|
||||
"wasm.memoryNew": "scr_wasm_memory_construct",
|
||||
"wasm.tableNew": "scr_wasm_table_construct",
|
||||
"wasm.globalNew": "scr_wasm_global_construct",
|
||||
"wasm.withoutNew": "scr_wasm_construct_without_new",
|
||||
"wasm.moduleImports": "scr_wasm_module_imports",
|
||||
"wasm.moduleExports": "scr_wasm_module_exports",
|
||||
"wasm.moduleCustomSections": "scr_wasm_module_custom_sections",
|
||||
"wasm.valueOf": "scr_wasm_value_of",
|
||||
"wasm.function": "scr_wasm_function",
|
||||
"wasm.namespace": "scr_wasm_namespace",
|
||||
"wasm.globalView": "scr_wasm_global_view",
|
||||
"blob.fileNew": "scr_file_new",
|
||||
"blob.slice": "scr_blob_slice",
|
||||
"blob.text": "scr_blob_text",
|
||||
@@ -926,8 +962,9 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"dyn.promiseAll": "scr_dyn_promise_all",
|
||||
"dyn.promiseAllSettled": "scr_dyn_promise_all_settled",
|
||||
"dyn.promiseAny": "scr_dyn_promise_any",
|
||||
"dyn.promiseResolve": "scr_dyn_promise_resolve",
|
||||
"dyn.typeof": "scr_dyn_typeof",
|
||||
"dyn.objectTag": "scr_dyn_object_tag",
|
||||
"dyn.objectTag": "scr_dyn_object_tag_of",
|
||||
"dyn.freeze": "scr_dyn_freeze",
|
||||
"dyn.isFrozen": "scr_dyn_is_frozen",
|
||||
"class.defineField": "scr_class_define_field",
|
||||
@@ -941,7 +978,11 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"dyn.nativeMapIs": "scr_dyn_native_map_is",
|
||||
"dyn.nativeUrlIs": "scr_dyn_native_url_is",
|
||||
"dyn.nativeDateIs": "scr_dyn_native_date_is",
|
||||
"dyn.globalConstructor": "scr_dyn_global_constructor",
|
||||
"dyn.builtinFunction": "scr_dyn_builtin_function",
|
||||
"date.nativeNew": "scr_dyn_native_date_new",
|
||||
"date.nativeNewFields": "scr_dyn_native_date_new_fields",
|
||||
"date.nativeNewValue": "scr_dyn_native_date_new_value",
|
||||
"date.checkedValue": "scr_dyn_native_date_value",
|
||||
"dyn.nativeRegexIs": "scr_dyn_native_regex_is",
|
||||
"dyn.toString": "scr_dyn_to_string_argument",
|
||||
@@ -1104,6 +1145,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"readable.opFind": "scr_stream_op_find",
|
||||
"readable.opReduce": "scr_stream_op_reduce",
|
||||
"readable.pipe": "scr_stream_pipe",
|
||||
"readable.pipeGeneral": "scr_stream_pipe_general",
|
||||
"writable.cork": "scr_stream_cork",
|
||||
"writable.uncork": "scr_stream_uncork",
|
||||
"stream.destroyErr": "scr_stream_destroy",
|
||||
@@ -1151,6 +1193,10 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"net.sockReadyState": "scr_net_sock_ready_state",
|
||||
"net.sockSetKeepAlive": "scr_net_sock_set_keepalive_ret",
|
||||
"net.sockOnCloseDyn": "scr_net_sock_on_close_dyn",
|
||||
"net.sockOnLookup": "scr_net_sock_on_lookup",
|
||||
"net.serverCloseDyn": "scr_net_server_close_dyn",
|
||||
"net.serverUnixPath": "scr_net_server_unix_path",
|
||||
"net.serverAddressCheck": "scr_net_server_address_check",
|
||||
"net.sockOnWriteFlushDyn": "scr_net_sock_on_write_flush_dyn",
|
||||
"net.sockOnFinishDyn": "scr_net_sock_on_finish_dyn",
|
||||
"net.isIP": "scr_net_is_ip",
|
||||
@@ -1414,6 +1460,10 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"dyn.classPrototype": "scr_dyn_class_prototype",
|
||||
"dyn.classBasePrototype": "scr_dyn_class_base_prototype",
|
||||
"dyn.classInherit": "scr_dyn_class_inherit",
|
||||
"dyn.builtinClass": "scr_dyn_builtin_class",
|
||||
"dyn.builtinStreamClass": "scr_dyn_builtin_stream_class",
|
||||
"dyn.builtinInstanceOf": "scr_dyn_builtin_instanceof",
|
||||
"dyn.functionInstanceOf": "scr_dyn_function_instanceof",
|
||||
"dyn.classSuper": "scr_dyn_class_super",
|
||||
"dyn.assignPrototype": "scr_dyn_assign_prototype",
|
||||
"dyn.definePrototypeProps": "scr_dyn_define_prototype_props",
|
||||
@@ -1490,6 +1540,8 @@ export function mockTimerSym(
|
||||
|
||||
export const USES_TIMERS_LIB_FNS = new Set<string>([
|
||||
"process.emitWarning",
|
||||
// Constructor-function streams defer their events on the stream ticks.
|
||||
"dyn.builtinStreamClass",
|
||||
// Decipheriv.setAuthTag reports DEP0182 on the next tick.
|
||||
"crypto.cipherNew",
|
||||
// WebCrypto results settle through the deferred crypto queue.
|
||||
@@ -1586,6 +1638,7 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
|
||||
"readable.opReduce",
|
||||
"readable.resume",
|
||||
"readable.pipe",
|
||||
"readable.pipeGeneral",
|
||||
"readable.unpipe",
|
||||
"writable.write",
|
||||
"writable.writeStr",
|
||||
@@ -1626,6 +1679,8 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
|
||||
"sc.arrayBuffer",
|
||||
"net.listen",
|
||||
"net.listenCb",
|
||||
"net.listenPath",
|
||||
"net.listenPathCb",
|
||||
"net.listenOpts",
|
||||
"net.listenOptsCb",
|
||||
"net.listenOptsReusePort",
|
||||
@@ -1644,6 +1699,10 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
|
||||
"http.requestCb",
|
||||
"http.requestUrl",
|
||||
"http.requestUrlCb",
|
||||
"http.requestDyn",
|
||||
"http.requestDynCb",
|
||||
"https.requestDyn",
|
||||
"https.requestDynCb",
|
||||
"https.request",
|
||||
"https.requestCb",
|
||||
"https.requestUrl",
|
||||
@@ -1665,4 +1724,5 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
|
||||
"timers.queueMicrotaskDyn",
|
||||
"timers.setImmediateFnValue",
|
||||
"timers.immediatePromise",
|
||||
"dyn.builtinFunction",
|
||||
]);
|
||||
|
||||
@@ -36,6 +36,24 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
if (e.fn === "stream.dynNative") {
|
||||
// Checked-dynamic stream slots: the boxed native stream (+1) or NULL.
|
||||
if (e.type.kind !== "union")
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} result is not a union`);
|
||||
const def = host.unionsById.get(e.type.unionId);
|
||||
const streamTag = def ? def.arms.findIndex((a) => a.kind === "object") : -1;
|
||||
const nullTag = def ? def.arms.findIndex((a) => a.kind === "nullT") : -1;
|
||||
if (streamTag < 0 || nullTag < 0)
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} union lacks its arms`);
|
||||
const value = host.emitExpr(e.args[0]!);
|
||||
const side = host.emitExpr(e.args[1]!);
|
||||
host.declare(`declare ptr @scr_stream_dyn_native(ptr, double)`);
|
||||
const raw = B.tmp();
|
||||
B.line(
|
||||
`${raw} = call ptr @scr_stream_dyn_native(ptr ${value.name}, double ${side.name}) ; +1 or NULL`,
|
||||
);
|
||||
return host.wrapNullable(raw, raw, def!.arms[streamTag]!, streamTag, e.type, nullTag);
|
||||
}
|
||||
if (e.fn === "stream.onData") {
|
||||
// The callback MOVES into the stream's listener registry; the
|
||||
// adapter is per callback shape — runtime-provided for the
|
||||
@@ -56,6 +74,8 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
host.declare(`declare void @scr_child_stream_thunk0(ptr, ptr)`);
|
||||
} else if (param.kind === "union") {
|
||||
adapter = host.childDataThunkFor(param);
|
||||
} else if (param.kind === "dyn") {
|
||||
adapter = host.childDataDynThunkFor("bytes");
|
||||
} else {
|
||||
adapter = "scr_child_stream_thunk_bytes";
|
||||
host.declare(`declare void @scr_child_stream_thunk_bytes(ptr, ptr)`);
|
||||
@@ -78,7 +98,8 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
if (e.fn === "stream.onDataStr") {
|
||||
host.usesTimers = true;
|
||||
const cbT = e.args[1]!.type;
|
||||
if (cbT.kind !== "func" || cbT.params[0]?.kind !== "string") {
|
||||
const strParam = cbT.kind === "func" ? cbT.params[0]?.kind : undefined;
|
||||
if (strParam !== "string" && strParam !== "dyn") {
|
||||
throw new InternalCompilerError(
|
||||
"llvm emitter bug: stream.onDataStr callback not a string func",
|
||||
);
|
||||
@@ -87,10 +108,12 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
const cb = host.emitExpr(e.args[1]!);
|
||||
const once = host.emitExpr(e.args[2]!);
|
||||
host.toHeap(cb);
|
||||
host.declare(`declare void @scr_child_stream_thunk_str(ptr, ptr)`);
|
||||
let adapter = "scr_child_stream_thunk_str";
|
||||
if (strParam === "dyn") adapter = host.childDataDynThunkFor("str");
|
||||
else host.declare(`declare void @scr_child_stream_thunk_str(ptr, ptr)`);
|
||||
host.declare(`declare void @scr_child_stream_on_data_str(ptr, ptr, ptr, i1 zeroext)`);
|
||||
B.line(
|
||||
`call void @scr_child_stream_on_data_str(ptr ${s0.name}, ptr ${cb.name}, ptr @scr_child_stream_thunk_str, i1 ${once.name})`,
|
||||
`call void @scr_child_stream_on_data_str(ptr ${s0.name}, ptr ${cb.name}, ptr @${adapter}, i1 ${once.name})`,
|
||||
);
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/* The WebAssembly modules translated into a program (IrModule.wasmModules):
|
||||
* each module's translation (wasm/translate.ts) and the registry the
|
||||
* runtime looks modules up in (scr_wasm_registry, scr_wasm_abi.h). The
|
||||
* translations join the program module, so the native helper optimizes,
|
||||
* partitions and caches them with it. Programs that do not use the
|
||||
* WebAssembly API carry no wasmModules and emit nothing here. */
|
||||
import type { IrModule } from "../../ir/ir.js";
|
||||
import { decodeBase64 } from "../../wasm/base64.js";
|
||||
import { translateWasmModule, WasmTranslateError } from "../../wasm/translate.js";
|
||||
|
||||
function hexBytes(hex: string): Uint8Array {
|
||||
const out = new Uint8Array(hex.length / 2);
|
||||
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(2 * i, 2 * i + 2), 16);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Whether translated memories check bounds explicitly instead of relying
|
||||
* on guard pages and the runtime's fault handler: sanitizer builds (whose
|
||||
* runtime owns SIGSEGV), 32-bit targets, and Windows, which has no
|
||||
* fault handler yet. */
|
||||
export function wasmBoundsChecks(options: {
|
||||
sanitize: boolean;
|
||||
pointerBits: 32 | 64;
|
||||
targetTriple: string;
|
||||
}): boolean {
|
||||
if (options.sanitize || options.pointerBits === 32) return true;
|
||||
const target = options.targetTriple || process.platform;
|
||||
return /windows|mingw|cygwin|win32|msvc/.test(target);
|
||||
}
|
||||
|
||||
/** The top-level lines of every translated module plus the registry.
|
||||
* `declared` holds the declarations the module already carries; runtime
|
||||
* entry points it lacks are declared here. A module the translator
|
||||
* cannot translate is left out of the registry, so loading it at run
|
||||
* time rejects with the CompileError for untranslated bytes. */
|
||||
export function emitWasmModules(
|
||||
mod: IrModule,
|
||||
declared: ReadonlySet<string>,
|
||||
boundsChecks: boolean,
|
||||
): string[] {
|
||||
const modules = mod.wasmModules;
|
||||
if (modules === undefined) return [];
|
||||
const out: string[] = [];
|
||||
const decls = new Set(declared);
|
||||
const descriptors: string[] = [];
|
||||
for (const module of modules) {
|
||||
const bytes = decodeBase64(module.bytes);
|
||||
if (bytes === null) continue;
|
||||
let translation;
|
||||
try {
|
||||
translation = translateWasmModule(bytes, hexBytes(module.sha256), {
|
||||
boundsChecks,
|
||||
customSections: mod.wasmCustomSections === true,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof WasmTranslateError) continue;
|
||||
throw error;
|
||||
}
|
||||
for (const decl of translation.decls) {
|
||||
if (decls.has(decl)) continue;
|
||||
decls.add(decl);
|
||||
out.push(decl);
|
||||
}
|
||||
for (const part of translation.text) out.push(part);
|
||||
descriptors.push(translation.descriptor);
|
||||
}
|
||||
if (descriptors.length === 0) {
|
||||
out.push(`@scr_wasm_registry = constant { i64, ptr } { i64 0, ptr null }`);
|
||||
} else {
|
||||
out.push(
|
||||
`@sc_wasm_registry_modules = private constant [${descriptors.length} x ptr] [${descriptors.map((d) => `ptr ${d}`).join(", ")}]`,
|
||||
`@scr_wasm_registry = constant { i64, ptr } { i64 ${descriptors.length}, ptr @sc_wasm_registry_modules }`,
|
||||
);
|
||||
}
|
||||
out.push("");
|
||||
return out;
|
||||
}
|
||||
@@ -25,6 +25,9 @@ export interface NativeLinkFeatures {
|
||||
fileHandle: boolean;
|
||||
fetch: boolean;
|
||||
eventTarget?: boolean;
|
||||
/** The WebAssembly API (scr_wasm.c). Absent when unused, so programs
|
||||
* without it keep their link identity. */
|
||||
wasm?: boolean;
|
||||
/** The program assigns process.emitWarning: links the
|
||||
* SCR_WARNING_REPLACE variant of the dyn-async unit. */
|
||||
warningReplace?: boolean;
|
||||
@@ -40,18 +43,28 @@ export interface NativeLinkFeatures {
|
||||
/** Dynamic-tier jsval operations: the scr_js_* units. Absent (not false)
|
||||
* for programs without them, so their link features stay unchanged. */
|
||||
dynTier?: boolean;
|
||||
/** Checked-dynamic stream slots: the scr_stream_dyn.c unit. Absent (not
|
||||
* false) for programs without them. */
|
||||
streamDyn?: boolean;
|
||||
/** events.once with options: scr_events_once_signal.c. Absent (not
|
||||
* false) for programs without it. */
|
||||
eventsOnceSignal?: boolean;
|
||||
events: boolean;
|
||||
emitter: boolean;
|
||||
symbol: boolean;
|
||||
bigint: boolean;
|
||||
searchParams: boolean;
|
||||
qs: boolean;
|
||||
dynBuiltinClass: boolean;
|
||||
dynBuiltinStream: boolean;
|
||||
parseArgs: boolean;
|
||||
stream: boolean;
|
||||
net: boolean;
|
||||
http: boolean;
|
||||
http2: boolean;
|
||||
dgram: boolean;
|
||||
/** node:dns/promises: scr_dns_promises.c. */
|
||||
dnsPromises: boolean;
|
||||
watch: boolean;
|
||||
foreignFfi: boolean;
|
||||
nodeTest: boolean;
|
||||
@@ -59,6 +72,8 @@ export interface NativeLinkFeatures {
|
||||
/** crypto.createCipheriv/createDecipheriv: scr_cipher.c plus the mbedTLS
|
||||
* archive. */
|
||||
cipher: boolean;
|
||||
/** perf_hooks event-loop metrics: scr_perf_loop.c. */
|
||||
perfLoop: boolean;
|
||||
tlsCa: boolean;
|
||||
/** crypto.subtle importKey/sign/verify/digest and CryptoKey values:
|
||||
* scr_webcrypto.c. */
|
||||
|
||||
@@ -113,11 +113,19 @@ export interface CcOptions {
|
||||
* the IR): compiles the scr_js_* units (two-tier design I3). Programs
|
||||
* without them link no scr_js_ symbol. */
|
||||
dynTier?: boolean;
|
||||
/** Checked-dynamic stream slots (stream.dynNative / stream.pipeDyn):
|
||||
* compiles scr_stream_dyn.c. */
|
||||
streamDyn?: boolean;
|
||||
/** events.once with options (events.onceSignal): compiles
|
||||
* scr_events_once_signal.c. */
|
||||
eventsOnceSignal?: boolean;
|
||||
/** The program constructs EventTarget/Event/CustomEvent values or
|
||||
* calls events.addAbortListener (the event.* libCalls), or uses the
|
||||
* native fetch AbortSignal built on the same engine: compiles
|
||||
* scr_event_target.c. */
|
||||
eventTarget?: boolean;
|
||||
/** The WebAssembly API: compiles scr_wasm.c. */
|
||||
wasm?: boolean;
|
||||
/** SCR_WARNING_REPLACE: process.emitWarning replacement support. */
|
||||
warningReplace?: boolean;
|
||||
/** SCR_STDIO_EXTENDED: stdin encoding, string data listeners, resize. */
|
||||
@@ -150,6 +158,15 @@ export interface CcOptions {
|
||||
* line (escape-only programs ride the always-linked component encoder
|
||||
* and never flip this). */
|
||||
qs?: boolean;
|
||||
/** The program uses EventEmitter or a stream class as an untyped
|
||||
* constructor value (a constructor function's base): compiles
|
||||
* scr_dyn_builtin_class.c, which also references the inspection,
|
||||
* dyn-invoke and symbol units. */
|
||||
dynBuiltinClass?: boolean;
|
||||
/** The program uses a stream class as an untyped constructor value:
|
||||
* compiles scr_dyn_builtin_stream.c, the constructors' stream half, which
|
||||
* also references the stream unit. */
|
||||
dynBuiltinStream?: boolean;
|
||||
/** The program uses native util parsing, comparison, or styling. Optional
|
||||
* utility units also require their symbol, inspection, and warning features. */
|
||||
parseArgs?: boolean;
|
||||
@@ -177,6 +194,11 @@ export interface CcOptions {
|
||||
* on the IR): compiles scr_dgram.c into the binary — the net gating
|
||||
* precedent, so dgram-free binaries keep their exact link line. */
|
||||
dgram?: boolean;
|
||||
/** The program uses node:dns/promises (the dns.promisesLookup libCalls):
|
||||
* compiles scr_dns_promises.c over the dgram unit, so callback-only
|
||||
* binaries keep their exact link line. */
|
||||
dnsPromises?: boolean;
|
||||
|
||||
/** The program uses fs.watch (moduleUsesFsWatch on the IR): compiles
|
||||
* scr_watch.c into the binary — the net gating precedent, so watch-free
|
||||
* binaries keep their exact link line. */
|
||||
@@ -209,6 +231,8 @@ export interface CcOptions {
|
||||
cipher?: boolean;
|
||||
/** crypto.subtle importKey/sign/verify/digest: compiles scr_webcrypto.c. */
|
||||
webcrypto?: boolean;
|
||||
/** perf_hooks event-loop metrics: compiles scr_perf_loop.c. */
|
||||
perfLoop?: boolean;
|
||||
/** Internal compiler hook: called only after a strict native artifact hit
|
||||
* or a successful stable build has installed `outPath`. The executable
|
||||
* frontend cache uses it to publish its stamp after native dependencies
|
||||
|
||||
@@ -182,6 +182,7 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
|
||||
unit((u) => u.opts.dc, "scr_dc.c"),
|
||||
unit((u) => u.opts.dynTier, "scr_js_value.c"),
|
||||
unit((u) => u.opts.eventTarget || u.nativeFetch, "scr_event_target.c"),
|
||||
unit((u) => u.opts.wasm, "scr_wasm.c"),
|
||||
unit(
|
||||
(u) =>
|
||||
u.opts.dynAsync ||
|
||||
@@ -195,6 +196,8 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
|
||||
// The performance timeline (every perf_hooks user-timing call selects
|
||||
// dynAsync, whose unit carries the timeline's buffer warning).
|
||||
unit((u) => u.opts.dynAsync, "scr_perf.c"),
|
||||
// perf_hooks event-loop metrics, only for programs that read them.
|
||||
unit((u) => u.opts.perfLoop, "scr_perf_loop.c"),
|
||||
// Blob and File (every blob.* call selects dynAsync, whose unit boxes
|
||||
// their reads as dyn promises).
|
||||
unit((u) => u.opts.dynAsync, "scr_blob.c"),
|
||||
@@ -217,6 +220,9 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
|
||||
// measures with.
|
||||
unit((u) => u.opts.events && u.regex, "scr_readline_tty.c"),
|
||||
unit((u) => u.opts.emitter || u.opts.workers, "scr_events_emitter.c"),
|
||||
// events.once with options registers through the emitter and
|
||||
// event-target units.
|
||||
unit((u) => u.opts.eventsOnceSignal === true, "scr_events_once_signal.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
|
||||
@@ -235,11 +241,33 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
|
||||
(u) => u.opts.parseArgs && u.opts.inspect && u.opts.dynAsync && u.opts.dynInvoke && u.regex,
|
||||
"scr_util_callable.c",
|
||||
),
|
||||
unit(
|
||||
(u) =>
|
||||
u.opts.dynBuiltinClass &&
|
||||
u.opts.inspect &&
|
||||
u.opts.dynInvoke &&
|
||||
u.opts.symbol &&
|
||||
(u.opts.emitter || u.opts.workers),
|
||||
"scr_dyn_builtin_class.c",
|
||||
),
|
||||
unit(
|
||||
(u) =>
|
||||
u.opts.dynBuiltinStream &&
|
||||
u.opts.dynBuiltinClass &&
|
||||
u.opts.stream &&
|
||||
u.opts.inspect &&
|
||||
u.opts.dynInvoke &&
|
||||
u.opts.symbol,
|
||||
"scr_dyn_builtin_stream.c",
|
||||
),
|
||||
unit((u) => u.opts.stream, "scr_stream.c", "scr_fs_stream.c"),
|
||||
// The zlib stream classes reference both the zlib engine and the
|
||||
// stream unit.
|
||||
unit((u) => u.opts.zlib && u.opts.stream, "scr_zlib_stream.c"),
|
||||
unit((u) => u.opts.stream && u.opts.dynAsync, "scr_stream_ops.c"),
|
||||
// Checked-dynamic stream slots test values and call JavaScript stream
|
||||
// members through the dyn-invoke unit.
|
||||
unit((u) => u.opts.streamDyn === true, "scr_stream_dyn.c"),
|
||||
// FileHandle streams reference both the stream unit and FileHandle.
|
||||
unit((u) => u.opts.stream && u.opts.fileHandle, "scr_fs_stream_fh.c"),
|
||||
// The readiness-poller backends (scr_platform.h): kqueue on macOS/BSD,
|
||||
@@ -252,6 +280,7 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
|
||||
unit((u) => u.http, "scr_http.c"),
|
||||
unit((u) => u.opts.http2, "scr_http2.c"),
|
||||
unit((u) => u.opts.dgram, "scr_dgram.c"),
|
||||
unit((u) => u.opts.dgram && u.opts.dnsPromises, "scr_dns_promises.c"),
|
||||
unit((u) => u.opts.watch, "scr_watch.c"),
|
||||
unit((u) => u.opts.foreignFfi, "scr_ffi_queue.c"),
|
||||
unit((u) => u.opts.nodeTest, "scr_test.c"),
|
||||
|
||||
@@ -82,7 +82,10 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures {
|
||||
workers: boolean;
|
||||
youngNursery: boolean;
|
||||
eventTarget: boolean;
|
||||
wasm: boolean;
|
||||
dynTier: boolean;
|
||||
streamDyn: boolean;
|
||||
eventsOnceSignal: boolean;
|
||||
warningReplace: boolean;
|
||||
stdioExtended: boolean;
|
||||
nativeFetch: boolean;
|
||||
@@ -272,7 +275,10 @@ export function effectiveRuntimeFeatures(
|
||||
workers: features.workers === true,
|
||||
youngNursery: features.youngNursery === true,
|
||||
eventTarget: features.eventTarget === true,
|
||||
wasm: features.wasm === true,
|
||||
dynTier: features.dynTier === true,
|
||||
streamDyn: features.streamDyn === true,
|
||||
eventsOnceSignal: features.eventsOnceSignal === true,
|
||||
warningReplace: features.warningReplace === true,
|
||||
stdioExtended: features.stdioExtended === true,
|
||||
nativeFetch,
|
||||
|
||||
@@ -107,12 +107,15 @@ const features = {
|
||||
bigint: false,
|
||||
searchParams: false,
|
||||
qs: false,
|
||||
dynBuiltinClass: false,
|
||||
dynBuiltinStream: false,
|
||||
parseArgs: false,
|
||||
stream: false,
|
||||
net: false,
|
||||
http: false,
|
||||
http2: false,
|
||||
dgram: false,
|
||||
dnsPromises: false,
|
||||
watch: false,
|
||||
foreignFfi: false,
|
||||
nodeTest: false,
|
||||
@@ -120,6 +123,7 @@ const features = {
|
||||
tlsCa: false,
|
||||
cipher: false,
|
||||
webcrypto: false,
|
||||
perfLoop: false,
|
||||
};
|
||||
|
||||
test.each([
|
||||
|
||||
@@ -44,12 +44,15 @@ const BASE: NativeLinkFeatures = {
|
||||
bigint: false,
|
||||
searchParams: false,
|
||||
qs: false,
|
||||
dynBuiltinClass: false,
|
||||
dynBuiltinStream: false,
|
||||
parseArgs: false,
|
||||
stream: false,
|
||||
net: false,
|
||||
http: false,
|
||||
http2: false,
|
||||
dgram: false,
|
||||
dnsPromises: false,
|
||||
watch: false,
|
||||
foreignFfi: false,
|
||||
nodeTest: false,
|
||||
@@ -57,6 +60,7 @@ const BASE: NativeLinkFeatures = {
|
||||
tlsCa: false,
|
||||
cipher: false,
|
||||
webcrypto: false,
|
||||
perfLoop: false,
|
||||
};
|
||||
|
||||
async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) {
|
||||
|
||||
@@ -84,6 +84,97 @@ test("a computed key reads the stack when the program spells it", () => {
|
||||
expect(observesStacks(mod([fn("%main", [computed])]))).toBe(false);
|
||||
});
|
||||
|
||||
test('a computed key that cannot be "stack" reads no stack', () => {
|
||||
const value: IrExpr = { loc, kind: "varRef", localId: "d", type: DYN };
|
||||
const spelled: IrStmt = {
|
||||
loc,
|
||||
kind: "exprStmt",
|
||||
expr: { loc, kind: "strLit", value: "stack", type: STRING },
|
||||
};
|
||||
const read = (key: IrExpr): IrStmt => ({
|
||||
loc,
|
||||
kind: "exprStmt",
|
||||
expr: { loc, kind: "dynKeyGet", value, key, type: DYN },
|
||||
});
|
||||
const index: IrExpr = { loc, kind: "varRef", localId: "i", type: F64 };
|
||||
const indexText: IrExpr = { loc, kind: "toString", operand: index, type: STRING };
|
||||
// A number key, and a key converted from one.
|
||||
expect(observesStacks(mod([fn("%main", [read(index), spelled])]))).toBe(false);
|
||||
expect(observesStacks(mod([fn("%main", [read(indexText), spelled])]))).toBe(false);
|
||||
// An immutable local answers with its initializer (the shape of the
|
||||
// dispatch helpers' formatted index keys).
|
||||
const key: IrExpr = { loc, kind: "varRef", localId: "key", type: STRING };
|
||||
const local = (init: IrExpr, mutable = false): IrFunction => ({
|
||||
...fn("%main", [{ loc, kind: "varDecl", localId: "key", init }, read(key), spelled]),
|
||||
locals: [{ id: "key", name: "key", type: STRING, mutable }],
|
||||
});
|
||||
expect(observesStacks(mod([local(indexText)]))).toBe(false);
|
||||
expect(observesStacks(mod([local(indexText, true)]))).toBe(true);
|
||||
// A parameter of a function called by name answers with its arguments.
|
||||
const helper: IrFunction = {
|
||||
...fn("readKey", [read(key)]),
|
||||
params: [{ localId: "key", name: "key", type: STRING }],
|
||||
locals: [{ id: "key", name: "key", type: STRING, mutable: false }],
|
||||
};
|
||||
const callWith = (arg: IrExpr): IrStmt => ({
|
||||
loc,
|
||||
kind: "exprStmt",
|
||||
expr: { loc, kind: "call", callee: "readKey", args: [arg], type: DYN },
|
||||
});
|
||||
const numbers = mod([fn("%main", [callWith(indexText), spelled]), helper]);
|
||||
expect(observesStacks(numbers)).toBe(false);
|
||||
const stack = mod([
|
||||
fn("%main", [
|
||||
callWith(indexText),
|
||||
callWith({ loc, kind: "strLit", value: "stack", type: STRING }),
|
||||
]),
|
||||
helper,
|
||||
]);
|
||||
expect(observesStacks(stack)).toBe(true);
|
||||
});
|
||||
|
||||
test("a key forwarded through functions with several call sites is searched once", () => {
|
||||
// f0 calls f1 twice, f1 calls f2 twice, and so on, each passing its
|
||||
// parameter on: 2^40 call paths reach the read in f40.
|
||||
const depth = 40;
|
||||
const value: IrExpr = { loc, kind: "varRef", localId: "d", type: DYN };
|
||||
const key: IrExpr = { loc, kind: "varRef", localId: "key", type: STRING };
|
||||
const forward = (callee: string): IrStmt => ({
|
||||
loc,
|
||||
kind: "exprStmt",
|
||||
expr: { loc, kind: "call", callee, args: [key], type: VOID },
|
||||
});
|
||||
const chain = (root: IrExpr): IrModule => {
|
||||
const functions: IrFunction[] = [];
|
||||
for (let i = 0; i <= depth; i++) {
|
||||
const body: IrStmt[] =
|
||||
i < depth
|
||||
? [forward(`f${i + 1}`), forward(`f${i + 1}`)]
|
||||
: [{ loc, kind: "exprStmt", expr: { loc, kind: "dynKeyGet", value, key, type: DYN } }];
|
||||
functions.push({
|
||||
...fn(`f${i}`, body),
|
||||
params: [{ localId: "key", name: "key", type: STRING }],
|
||||
locals: [{ id: "key", name: "key", type: STRING, mutable: false }],
|
||||
});
|
||||
}
|
||||
const spelled: IrStmt = {
|
||||
loc,
|
||||
kind: "exprStmt",
|
||||
expr: { loc, kind: "strLit", value: "stack", type: STRING },
|
||||
};
|
||||
const start: IrStmt = {
|
||||
loc,
|
||||
kind: "exprStmt",
|
||||
expr: { loc, kind: "call", callee: "f0", args: [root], type: VOID },
|
||||
};
|
||||
return mod([fn("%main", [start, spelled]), ...functions]);
|
||||
};
|
||||
expect(observesStacks(chain({ loc, kind: "strLit", value: "message", type: STRING }))).toBe(
|
||||
false,
|
||||
);
|
||||
expect(observesStacks(chain({ loc, kind: "strLit", value: "stack", type: STRING }))).toBe(true);
|
||||
});
|
||||
|
||||
test("programs without stack reads are not", () => {
|
||||
expect(observesStacks(mod([fn("%main", [call("helper")]), fn("helper", [ret])]))).toBe(false);
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* it answers true. */
|
||||
import { exprFunctionReference, moduleFunctionReferences } from "../ir/function-references.js";
|
||||
import { MAY_THROW_LIB_FNS } from "../ir/builtin-effects.js";
|
||||
import type { IrExpr, IrFunction, IrModule, IrStmt } from "../ir/ir.js";
|
||||
import type { IrExpr, IrFunction, IrModule, IrStmt, IrType } from "../ir/ir.js";
|
||||
import { MAY_THROW_ARR_METHODS } from "../ir/ir.js";
|
||||
import { everyExprChild, everyStmtChild, everyStmtList } from "../ir/traverse.js";
|
||||
import { unreachableStatements } from "./may-throw.js";
|
||||
@@ -13,16 +13,135 @@ import { writesPublished } from "./publication.js";
|
||||
/** Lib calls that read a captured stack trace. */
|
||||
const STACK_READS: ReadonlySet<string> = new Set(["error.stack"]);
|
||||
|
||||
/** Key types whose property names are never `stack`: a number, bigint or
|
||||
* boolean key names its decimal or `true`/`false` property. */
|
||||
const NON_STACK_KEY_TYPES: ReadonlySet<IrType["kind"]> = new Set(["f64", "bigint", "bool"]);
|
||||
|
||||
/** Whether a computed key can be the string "stack", the question a read
|
||||
* on a checked-dynamic value asks before it counts as a stack read.
|
||||
* - A string literal answers by its value; a key of a number, bigint or
|
||||
* boolean type, or one converted from such a value, cannot be "stack".
|
||||
* - An immutable local answers with its one initializer, and a parameter of
|
||||
* a function the program only calls by name answers with the arguments
|
||||
* its call sites pass.
|
||||
* - Any other key can be "stack" when the program spells the literal
|
||||
* "stack" somewhere, since the literal can reach the key through fields,
|
||||
* containers or calls; a key the program assembles from other strings or
|
||||
* from data it reads keeps the static answer. */
|
||||
class StackKeys {
|
||||
private readonly spelled: boolean;
|
||||
private readonly calls = new Map<string, { caller: IrFunction; args: IrExpr[] }[]>();
|
||||
private readonly named: Set<string>;
|
||||
private readonly inits = new Map<IrFunction, Map<string, IrExpr | null>>();
|
||||
/** Settled answers by function and local: true where a search reached a
|
||||
* key that can be "stack", false where a search that found none passed. */
|
||||
private readonly answers = new Map<string, boolean>();
|
||||
|
||||
constructor(mod: IrModule) {
|
||||
this.spelled = spellsStack(mod);
|
||||
this.named = new Set(moduleFunctionReferences(mod));
|
||||
// Class methods, accessors and constructors are called through dispatch.
|
||||
const classNames = new Set((mod.classes ?? []).map((cls) => cls.name));
|
||||
for (const fn of mod.functions) {
|
||||
if (isClassMember(fn.name, classNames)) this.named.add(fn.name);
|
||||
everyStmtList(fn.body, {
|
||||
stmt: () => true,
|
||||
expr: (expr) => {
|
||||
if (expr.kind === "call") {
|
||||
const sites = this.calls.get(expr.callee) ?? [];
|
||||
sites.push({ caller: fn, args: expr.args });
|
||||
this.calls.set(expr.callee, sites);
|
||||
}
|
||||
const referenced = exprFunctionReference(expr);
|
||||
if (referenced !== undefined) this.named.add(referenced);
|
||||
return true;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
mayBeStack(fn: IrFunction, key: IrExpr): boolean {
|
||||
const visited = new Set<string>();
|
||||
const answer = this.keyMayBeStack(fn, key, visited);
|
||||
// A search that found no way to "stack" explored everything it visited
|
||||
// to the end, so none of it can be "stack" either.
|
||||
if (!answer) for (const visit of visited) this.answers.set(visit, false);
|
||||
return answer;
|
||||
}
|
||||
|
||||
private keyMayBeStack(fn: IrFunction, key: IrExpr, visited: Set<string>): boolean {
|
||||
if (key.kind === "strLit") return key.value === "stack";
|
||||
if (!this.spelled) return false;
|
||||
if (NON_STACK_KEY_TYPES.has(key.type.kind)) return false;
|
||||
if (key.kind === "toString" && NON_STACK_KEY_TYPES.has(key.operand.type.kind)) return false;
|
||||
if (key.kind === "dynFrom" && NON_STACK_KEY_TYPES.has(key.value.type.kind)) return false;
|
||||
if (key.kind !== "varRef") return true;
|
||||
const visit = `${fn.name}\0${key.localId}`;
|
||||
const known = this.answers.get(visit);
|
||||
if (known !== undefined) return known;
|
||||
// Each local is searched once per question: a cycle, or a second path
|
||||
// to a local already searched, adds no value of its own.
|
||||
if (visited.has(visit)) return false;
|
||||
visited.add(visit);
|
||||
const answer = this.localMayBeStack(fn, key.localId, visited);
|
||||
if (answer) this.answers.set(visit, true);
|
||||
return answer;
|
||||
}
|
||||
|
||||
private localMayBeStack(fn: IrFunction, localId: string, visited: Set<string>): boolean {
|
||||
const local = fn.locals.find((candidate) => candidate.id === localId);
|
||||
if (local === undefined || local.mutable) return true;
|
||||
const index = fn.params.findIndex((param) => param.localId === localId);
|
||||
if (index !== -1) {
|
||||
const sites = this.calls.get(fn.name);
|
||||
if (this.named.has(fn.name) || sites === undefined) return true;
|
||||
return sites.some(
|
||||
(site) =>
|
||||
site.args.length !== fn.params.length ||
|
||||
this.keyMayBeStack(site.caller, site.args[index]!, visited),
|
||||
);
|
||||
}
|
||||
const init = this.initializerOf(fn, localId);
|
||||
return init === null || this.keyMayBeStack(fn, init, visited);
|
||||
}
|
||||
|
||||
/** The one initializer of an immutable local, or null when it has none
|
||||
* or more than one declaration. */
|
||||
private initializerOf(fn: IrFunction, localId: string): IrExpr | null {
|
||||
let inits = this.inits.get(fn);
|
||||
if (inits === undefined) {
|
||||
inits = new Map();
|
||||
everyStmtList(fn.body, {
|
||||
stmt: (stmt) => {
|
||||
if (stmt.kind === "varDecl")
|
||||
inits!.set(stmt.localId, inits!.has(stmt.localId) ? null : (stmt.init ?? null));
|
||||
return true;
|
||||
},
|
||||
expr: () => true,
|
||||
});
|
||||
this.inits.set(fn, inits);
|
||||
}
|
||||
return inits.get(localId) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether `name` follows the `%<class>.<member>` convention of class
|
||||
* methods, accessors and constructors for one of `classNames`. */
|
||||
function isClassMember(name: string, classNames: ReadonlySet<string>): boolean {
|
||||
if (!name.startsWith("%")) return false;
|
||||
for (let dot = name.indexOf("."); dot !== -1; dot = name.indexOf(".", dot + 1)) {
|
||||
if (classNames.has(name.slice(1, dot))) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** A typed `error.stack` read, or a read on a checked-dynamic value that
|
||||
* can reach an error's own `stack` accessor: the constant key `stack`, or
|
||||
* a computed key when `computedKeys` (the program spells "stack", so a
|
||||
* computed key can name it). A key the program assembles from other
|
||||
* strings or from data it reads keeps the static answer. */
|
||||
function readsStack(expr: IrExpr, computedKeys: boolean): boolean {
|
||||
* can reach an error's own `stack` accessor: one whose key can be "stack"
|
||||
* (StackKeys). */
|
||||
function readsStack(fn: IrFunction, expr: IrExpr, keys: StackKeys): boolean {
|
||||
if (expr.kind === "libCall") return STACK_READS.has(expr.fn);
|
||||
if (expr.kind !== "dynKeyGet") return false;
|
||||
if (expr.key.kind === "strLit") return expr.key.value === "stack";
|
||||
return computedKeys;
|
||||
return keys.mayBeStack(fn, expr.key);
|
||||
}
|
||||
|
||||
/** Whether the module contains the string literal "stack". */
|
||||
@@ -36,10 +155,10 @@ function spellsStack(mod: IrModule): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function containsStackRead(fn: IrFunction, computedKeys: boolean): boolean {
|
||||
function containsStackRead(fn: IrFunction, keys: StackKeys): boolean {
|
||||
return !everyStmtList(fn.body, {
|
||||
stmt: () => true,
|
||||
expr: (expr) => !readsStack(expr, computedKeys),
|
||||
expr: (expr) => !readsStack(fn, expr, keys),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -65,22 +184,14 @@ function visitLiveExprs(body: IrStmt[], visit: (expr: IrExpr) => void): void {
|
||||
* and those reads must not cost every call of the program a frame.
|
||||
* Programs without any stack read answer false without the walk. */
|
||||
export function observesStacks(mod: IrModule): boolean {
|
||||
const computedKeys = spellsStack(mod);
|
||||
if (!mod.functions.some((fn) => containsStackRead(fn, computedKeys))) return false;
|
||||
const keys = new StackKeys(mod);
|
||||
if (!mod.functions.some((fn) => containsStackRead(fn, keys))) return false;
|
||||
const byName = new Map(mod.functions.map((fn) => [fn.name, fn]));
|
||||
const classNames = new Set((mod.classes ?? []).map((cls) => cls.name));
|
||||
const pending: string[] = [...moduleFunctionReferences(mod)];
|
||||
// Class methods, accessors and constructors are reachable through
|
||||
// dispatch the IR does not name per site: all of them are roots.
|
||||
for (const fn of mod.functions) {
|
||||
if (!fn.name.startsWith("%")) continue;
|
||||
for (let dot = fn.name.indexOf("."); dot !== -1; dot = fn.name.indexOf(".", dot + 1)) {
|
||||
if (classNames.has(fn.name.slice(1, dot))) {
|
||||
pending.push(fn.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const fn of mod.functions) if (isClassMember(fn.name, classNames)) pending.push(fn.name);
|
||||
const reached = new Set<string>();
|
||||
let observed = false;
|
||||
while (pending.length > 0 && !observed) {
|
||||
@@ -90,7 +201,7 @@ export function observesStacks(mod: IrModule): boolean {
|
||||
const fn = byName.get(name);
|
||||
if (fn === undefined) continue;
|
||||
visitLiveExprs(fn.body, (expr) => {
|
||||
if (readsStack(expr, computedKeys)) observed = true;
|
||||
if (readsStack(fn, expr, keys)) observed = true;
|
||||
if (expr.kind === "call" && !reached.has(expr.callee)) pending.push(expr.callee);
|
||||
const referenced = exprFunctionReference(expr);
|
||||
if (referenced !== undefined && !reached.has(referenced)) pending.push(referenced);
|
||||
|
||||
@@ -192,6 +192,14 @@ export function moduleWasiUnavailableSurface(
|
||||
found = { surface: "OS signals (WASI Preview 1 has no signal API)", loc };
|
||||
return false;
|
||||
}
|
||||
if (node.fn.startsWith("wasm.")) {
|
||||
found = {
|
||||
surface:
|
||||
"the WebAssembly API (a wasm32-wasi program cannot load translated modules into its own linear memory yet)",
|
||||
loc,
|
||||
};
|
||||
return false;
|
||||
}
|
||||
if (node.fn === "crypto.cipherNew") {
|
||||
found = {
|
||||
surface:
|
||||
|
||||
@@ -50,18 +50,22 @@ export async function warmNativeCaches(
|
||||
bigint: false,
|
||||
searchParams: false,
|
||||
qs: false,
|
||||
dynBuiltinClass: false,
|
||||
dynBuiltinStream: false,
|
||||
parseArgs: false,
|
||||
stream: false,
|
||||
net: false,
|
||||
http: false,
|
||||
http2: false,
|
||||
dgram: false,
|
||||
dnsPromises: false,
|
||||
watch: false,
|
||||
foreignFfi: false,
|
||||
nodeTest: false,
|
||||
tls: false,
|
||||
cipher: false,
|
||||
webcrypto: false,
|
||||
perfLoop: false,
|
||||
tlsCa: false,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -221,13 +221,13 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions
|
||||
out.push("");
|
||||
}
|
||||
|
||||
// The WebAssembly modules the build can see: what translating them would
|
||||
// take (wasm/discover.ts). No module compiles to native code yet; the
|
||||
// sites that load one are blockers or deferred fences above.
|
||||
// The WebAssembly modules the build can see (wasm/discover.ts). A build
|
||||
// translates the valid ones to native code (wasm/translate.ts) when the
|
||||
// program uses the WebAssembly API; the others fail to load at run time.
|
||||
const wasm = input.wasmModules ?? [];
|
||||
if (wasm.length > 0) {
|
||||
out.push(
|
||||
` ${c(DIM, "WebAssembly modules:")} ${c(DIM, "(found at build time; none compile to native code yet)")}`,
|
||||
` ${c(DIM, "WebAssembly modules:")} ${c(DIM, "(found at build time; valid ones compile to native code)")}`,
|
||||
);
|
||||
for (const m of wasm) {
|
||||
const name = m.path !== undefined ? displayPath(m.path, opts.root) : "inline module";
|
||||
|
||||
@@ -444,16 +444,17 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes
|
||||
}
|
||||
}
|
||||
|
||||
// ── the WebAssembly global: every reached use refuses (webAssemblyDiag) ─
|
||||
// ── the WebAssembly global (builtins/webassembly.ts, wasm/translate.ts):
|
||||
// the core API lowers over modules translated at build time; the rest
|
||||
// refuses at each use (webAssemblyDiag) ─
|
||||
{
|
||||
const refusal = webAssemblyDiag("WebAssembly", { file: "", start: 0, end: 0 });
|
||||
add({
|
||||
id: "stdlib.webassembly",
|
||||
kind: "stdlib",
|
||||
name: "globalThis.WebAssembly",
|
||||
status: "unsupported",
|
||||
code: refusal.code,
|
||||
...(refusal.hint !== undefined ? { note: refusal.hint } : {}),
|
||||
status: "static",
|
||||
note: `compile, instantiate, validate, the Module, Instance, Memory, Table and Global constructors, Module.imports, exports and customSections, instanceof, the error classes, exports, imports and Module transfer to workers, over the modules translated at build time; the rest refuses with ${refusal.code} (${refusal.hint ?? ""})`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -59,8 +59,9 @@
|
||||
* order that differs from insertion order where the program
|
||||
* observes it (SC6002), localeCompare over text the built-in
|
||||
* collation does not cover (SC6003), a call feeding an instance
|
||||
* to a cast to an unrelated class (SC6004), and Date strings the native parser does not
|
||||
* accept (SC6005)
|
||||
* to a cast to an unrelated class (SC6004); SC6005 (Date strings
|
||||
* outside the native parser's grammar) is retired — the runtime
|
||||
* parses date strings with V8's grammar
|
||||
* SC9xxx internal compiler errors (still source-anchored)
|
||||
*/
|
||||
import type { SrcLoc } from "../ir/ir.js";
|
||||
@@ -200,18 +201,6 @@ export function unrelatedClassCastDivergenceDiag(
|
||||
};
|
||||
}
|
||||
|
||||
/** SC6005 — a Date string literal outside the grammar the native parser
|
||||
* accepts (ISO 8601 with an explicit offset, date-only forms, and the
|
||||
* certificate-time shape): Node parses many more forms. */
|
||||
export function dateLiteralDivergenceDiag(text: string, loc: SrcLoc): ScrDiagnostic {
|
||||
return {
|
||||
code: "SC6005",
|
||||
message: `the date string '${text}' parses to an invalid date (NaN) natively, while Node accepts it (the native parser takes ISO 8601 date-times with an explicit offset, such as '2024-01-02T03:04:05Z', and date-only forms)`,
|
||||
loc,
|
||||
hint: "write the date in ISO 8601 with an explicit offset ('Z' or '+HH:MM'), or build it from numbers with Date.UTC",
|
||||
};
|
||||
}
|
||||
|
||||
/* ── SC5xxx: native FFI ───────────────────────────────────────────────── */
|
||||
|
||||
/** SC5001 — the outbound native-FFI manifest is malformed or unreadable. */
|
||||
@@ -282,11 +271,11 @@ export const UNSUPPORTED: Record<string, UnsupportedEntry> = {
|
||||
"imports (#alias via the imports field, self-name references via " +
|
||||
"exports), statically compiled npm packages, " +
|
||||
"and the built-in fs, fs/promises, path, os, url, crypto, zlib, " +
|
||||
"child_process, net, http, tls, https, http2, dgram, dns, util, " +
|
||||
"child_process, net, http, tls, https, http2, dgram, dns, dns/promises, util, " +
|
||||
"util/types, string_decoder, querystring, readline, events, stream, " +
|
||||
"stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, " +
|
||||
"stream/promises, stream/consumers, stream/web, buffer, assert, assert/strict, worker_threads, " +
|
||||
"cluster, tty, async_hooks, timers, timers/promises, " +
|
||||
"diagnostics_channel, perf_hooks, module, and console modules (bare or " +
|
||||
"diagnostics_channel, perf_hooks, module, v8, and console modules (bare or " +
|
||||
"node:-prefixed) and node:test (node:-prefixed only, like in Node) " +
|
||||
"are supported",
|
||||
},
|
||||
@@ -751,17 +740,20 @@ export function noLoweringDiag(
|
||||
};
|
||||
}
|
||||
|
||||
/** A REACHED use of the WebAssembly API (SC2020): scriptc.d.ts declares
|
||||
* its types so packages that mention them typecheck, but no module
|
||||
* compiles to native code yet. `surface` names what was used
|
||||
* ('WebAssembly.compile', 'new WebAssembly.Module', 'globalThis.WebAssembly'). */
|
||||
/** A REACHED use of a part of the WebAssembly API scriptc does not
|
||||
* implement yet (SC2020): the streaming functions, Tag and Exception, the
|
||||
* classes as values. compile, instantiate, validate, the Module, Instance,
|
||||
* Memory, Table and Global constructors, Module.imports/exports/
|
||||
* customSections and the error classes lower (builtins/webassembly.ts).
|
||||
* `surface` names what was used ('WebAssembly.compileStreaming',
|
||||
* 'WebAssembly.Memory'). */
|
||||
export function webAssemblyDiag(surface: string, loc: SrcLoc): ScrDiagnostic {
|
||||
return {
|
||||
code: "SC2020",
|
||||
message: `'${surface}' is not supported yet: scriptc does not compile WebAssembly modules to native code yet`,
|
||||
message: `'${surface}' is not supported yet: scriptc implements WebAssembly.compile, instantiate, validate, the Module, Instance, Memory, Table and Global constructors, Module.imports, exports and customSections, and the error classes over modules translated at build time, but not this part of the API`,
|
||||
loc,
|
||||
milestone: "later",
|
||||
hint: "WebAssembly support is planned as translation of each module whose bytes the build can see (an inline literal, or a file read from a path the compiler resolves) into native code; 'scriptc coverage' lists the modules this program would load (https://scriptc.dev/limitations)",
|
||||
hint: "the streaming functions, Tag and Exception, and the classes used as values other than with new and instanceof are planned; 'scriptc coverage' lists the modules this program loads (https://scriptc.dev/limitations)",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -45,12 +45,15 @@ const native: EarlyExecutableNativeFeatures = {
|
||||
bigint: false,
|
||||
searchParams: false,
|
||||
qs: false,
|
||||
dynBuiltinClass: false,
|
||||
dynBuiltinStream: false,
|
||||
parseArgs: false,
|
||||
stream: false,
|
||||
net: false,
|
||||
http: false,
|
||||
http2: false,
|
||||
dgram: false,
|
||||
dnsPromises: false,
|
||||
watch: false,
|
||||
foreignFfi: false,
|
||||
nodeTest: false,
|
||||
@@ -58,6 +61,7 @@ const native: EarlyExecutableNativeFeatures = {
|
||||
tlsCa: false,
|
||||
cipher: false,
|
||||
webcrypto: false,
|
||||
perfLoop: false,
|
||||
};
|
||||
|
||||
async function fixture(): Promise<{
|
||||
@@ -199,7 +203,7 @@ test("early executable cache misses on source and resolution changes", async ()
|
||||
expect(await readEarlyExecutableCache(f.root, f.options)).toBeNull();
|
||||
});
|
||||
|
||||
test("early executable keys isolate compile modes, paths, implementation, and FFI manifests", async () => {
|
||||
test("early executable keys isolate compile modes, paths, implementation, checker, and FFI manifests", async () => {
|
||||
const f = await fixture();
|
||||
const tracker = new FrontendInputTracker();
|
||||
tracker.run(() => trackedReadFile(f.source));
|
||||
@@ -216,6 +220,7 @@ test("early executable keys isolate compile modes, paths, implementation, and FF
|
||||
{ ...f.options, optimization: "dev" },
|
||||
{ ...f.options, optimization: "speed" },
|
||||
{ ...f.options, detachedExit: true },
|
||||
{ ...f.options, checker: "tsc-ts-executable-identity" },
|
||||
{ ...f.options, npmStatic: "auto" },
|
||||
{ ...f.options, npmStatic: ["commander"] },
|
||||
{ ...f.options, outPath: join(f.options.outDir, "other") },
|
||||
|
||||
@@ -56,6 +56,7 @@ export interface EarlyExecutableNativeFeatures {
|
||||
fileHandle: boolean;
|
||||
fetch: boolean;
|
||||
eventTarget?: boolean;
|
||||
wasm?: boolean;
|
||||
warningReplace?: boolean;
|
||||
stdioExtended?: boolean;
|
||||
zlib: boolean;
|
||||
@@ -65,23 +66,29 @@ export interface EarlyExecutableNativeFeatures {
|
||||
dc: boolean;
|
||||
dynAsync: boolean;
|
||||
dynTier?: boolean;
|
||||
streamDyn?: boolean;
|
||||
eventsOnceSignal?: boolean;
|
||||
events: boolean;
|
||||
emitter: boolean;
|
||||
symbol: boolean;
|
||||
bigint: boolean;
|
||||
searchParams: boolean;
|
||||
qs: boolean;
|
||||
dynBuiltinClass: boolean;
|
||||
dynBuiltinStream: boolean;
|
||||
parseArgs: boolean;
|
||||
stream: boolean;
|
||||
net: boolean;
|
||||
http: boolean;
|
||||
http2: boolean;
|
||||
dgram: boolean;
|
||||
dnsPromises: boolean;
|
||||
watch: boolean;
|
||||
foreignFfi: boolean;
|
||||
nodeTest: boolean;
|
||||
tls: boolean;
|
||||
cipher: boolean;
|
||||
perfLoop: boolean;
|
||||
tlsCa: boolean;
|
||||
webcrypto: boolean;
|
||||
}
|
||||
@@ -116,6 +123,9 @@ export interface EarlyExecutableCacheOptions {
|
||||
windowsSubsystem?: "gui";
|
||||
/** Omitted unless --detached-exit is set. */
|
||||
detachedExit?: true;
|
||||
/** Omitted for the default tsgo checker; with SCRIPTC_CHECKER=tsc-ts, the
|
||||
* tsc-ts executable's file identity. */
|
||||
checker?: string;
|
||||
/** Omitted unless SCRIPTC_YOUNG_NURSERY=1 (backend/young-nursery.ts). */
|
||||
youngNursery?: true;
|
||||
npmStatic: readonly string[] | "auto" | null;
|
||||
@@ -185,12 +195,15 @@ const BOOLEAN_NATIVE_KEYS = [
|
||||
"bigint",
|
||||
"searchParams",
|
||||
"qs",
|
||||
"dynBuiltinClass",
|
||||
"dynBuiltinStream",
|
||||
"parseArgs",
|
||||
"stream",
|
||||
"net",
|
||||
"http",
|
||||
"http2",
|
||||
"dgram",
|
||||
"dnsPromises",
|
||||
"watch",
|
||||
"foreignFfi",
|
||||
"nodeTest",
|
||||
@@ -198,6 +211,7 @@ const BOOLEAN_NATIVE_KEYS = [
|
||||
"tlsCa",
|
||||
"cipher",
|
||||
"webcrypto",
|
||||
"perfLoop",
|
||||
] as const satisfies readonly (keyof EarlyExecutableNativeFeatures)[];
|
||||
|
||||
function validNativeFeatures(value: unknown): value is EarlyExecutableNativeFeatures {
|
||||
@@ -230,6 +244,7 @@ function cacheKey(options: EarlyExecutableCacheOptions): string {
|
||||
...(options.strip ? ["strip"] : []),
|
||||
...(options.windowsSubsystem === "gui" ? ["windows-subsystem-gui"] : []),
|
||||
...(options.detachedExit ? ["detached-exit"] : []),
|
||||
...(options.checker !== undefined ? [`checker:${options.checker}`] : []),
|
||||
...(options.youngNursery ? ["young-nursery"] : []),
|
||||
options.npmStatic === null
|
||||
? "<npm-static-off>"
|
||||
@@ -270,6 +285,7 @@ function routeKey(options: Omit<EarlyExecutableRouteOptions, "nativeEnvironment"
|
||||
if (options.strip) hash.update("strip\0");
|
||||
if (options.windowsSubsystem === "gui") hash.update("windows-subsystem-gui\0");
|
||||
if (options.detachedExit) hash.update("detached-exit\0");
|
||||
if (options.checker !== undefined) hash.update(`checker:${options.checker}\0`);
|
||||
if (options.youngNursery) hash.update("young-nursery\0");
|
||||
hash
|
||||
.update(
|
||||
|
||||
@@ -42,6 +42,9 @@ export const SUPPORTED_BUILTIN_MODULES = [
|
||||
"module",
|
||||
"console",
|
||||
"process",
|
||||
"v8",
|
||||
"stream/web",
|
||||
"dns/promises",
|
||||
"punycode",
|
||||
"constants",
|
||||
] as const;
|
||||
@@ -76,14 +79,33 @@ export function builtinDefaultImportModule(spec: string): string | null {
|
||||
export function canonicalBuiltinModule(spec: string): string | null {
|
||||
const bare = spec.startsWith("node:") ? spec.slice(5) : spec;
|
||||
if (bare === spec && PREFIX_ONLY_BUILTIN_MODULES.has(bare)) return null;
|
||||
return (SUPPORTED_BUILTIN_MODULES as readonly string[]).includes(bare) ? bare : null;
|
||||
if ((SUPPORTED_BUILTIN_MODULES as readonly string[]).includes(bare)) return bare;
|
||||
return bare === spec ? npmBuiltinAliasOf(spec) : null;
|
||||
}
|
||||
|
||||
/** npm packages served as a builtin module instead of compiling their
|
||||
* JavaScript (maintainer decision): graceful-fs monkeypatches the fs
|
||||
* module object and queues retries on EMFILE/ENFILE, which statically
|
||||
* lowered fs calls cannot honor, and it behaves as fs everywhere else. Its
|
||||
* import and require resolve to node:fs; gracefulify is a no-op statement.
|
||||
* The one documented difference: no retry queueing under file-descriptor
|
||||
* exhaustion. */
|
||||
const NPM_BUILTIN_ALIASES: Record<string, string | undefined> = { "graceful-fs": "fs" };
|
||||
|
||||
/** The builtin module an npm package specifier is served as, or null. */
|
||||
export function npmBuiltinAliasOf(spec: string): string | null {
|
||||
return Object.hasOwn(NPM_BUILTIN_ALIASES, spec) ? (NPM_BUILTIN_ALIASES[spec] ?? null) : null;
|
||||
}
|
||||
|
||||
/** Both specifier spellings of every supported builtin (prefix-only ones
|
||||
* keep just the node: form) — the ambient-module allowlist preflight uses. */
|
||||
export const SUPPORTED_NODE_MODULES: readonly string[] = SUPPORTED_BUILTIN_MODULES.flatMap((m) =>
|
||||
PREFIX_ONLY_BUILTIN_MODULES.has(m) ? [`node:${m}`] : [m, `node:${m}`],
|
||||
);
|
||||
* keep just the node: form) — the ambient-module allowlist preflight uses
|
||||
* — plus the npm packages served as builtins. */
|
||||
export const SUPPORTED_NODE_MODULES: readonly string[] = [
|
||||
...SUPPORTED_BUILTIN_MODULES.flatMap((m) =>
|
||||
PREFIX_ONLY_BUILTIN_MODULES.has(m) ? [`node:${m}`] : [m, `node:${m}`],
|
||||
),
|
||||
...Object.keys(NPM_BUILTIN_ALIASES),
|
||||
];
|
||||
|
||||
/* ── out-of-scope builtins ───────────────────────────────────────────────
|
||||
* Builtin modules a compiled binary is not going to serve — not "yet",
|
||||
@@ -92,8 +114,8 @@ export const SUPPORTED_NODE_MODULES: readonly string[] = SUPPORTED_BUILTIN_MODUL
|
||||
* so 'node:sqlite' keys as 'sqlite'. Exact-match only: an npm package
|
||||
* whose name collides with none of these keys keeps the generic wording.
|
||||
* Everything absent from this table stays the plain "not supported yet"
|
||||
* story — genuinely pending surface (console's Console class, stream/web)
|
||||
* must not read as permanently refused. */
|
||||
* story — genuinely pending surface (console's Console class) must not
|
||||
* read as permanently refused. */
|
||||
|
||||
/** Node's own HTTP/TLS/stream implementation internals, requireable by
|
||||
* legacy convention (underscore-prefixed lib/ modules). They expose
|
||||
@@ -102,11 +124,15 @@ export const SUPPORTED_NODE_MODULES: readonly string[] = SUPPORTED_BUILTIN_MODUL
|
||||
const NODE_INTERNAL_REASON =
|
||||
"a Node-internal module: it exposes Node's own implementation objects, which the scriptc runtime does not replicate";
|
||||
|
||||
const V8_REASON =
|
||||
"it observes V8 engine internals — heap statistics and snapshots, GC and CPU profiles, serialize/deserialize — and a compiled binary embeds no V8 engine to observe";
|
||||
/** node:v8 is importable (packages require it at top level and touch it
|
||||
* only on optional paths), and getHeapStatistics answers allocator-derived
|
||||
* figures; every other member observes the engine itself. Member fences
|
||||
* carry this reason instead of a module-level refusal. */
|
||||
export const V8_MEMBER_REASON =
|
||||
"by design: this member observes V8 engine internals — heap snapshots and spaces, GC and CPU profiles, flags, serialize/deserialize — and a compiled binary embeds no V8 engine to observe";
|
||||
|
||||
const OUT_OF_SCOPE_BUILTIN_REASONS: Record<string, string | undefined> = {
|
||||
v8: V8_REASON,
|
||||
vm: "it compiles and runs JavaScript source at runtime, and a compiled binary embeds no JavaScript engine to run it",
|
||||
inspector:
|
||||
"it drives the V8 inspector protocol — debugger, profiler, heap access — and a compiled binary embeds no V8 engine to inspect",
|
||||
"inspector/promises":
|
||||
@@ -142,3 +168,10 @@ export function unsupportedModuleFeatureOf(spec: string): string {
|
||||
: undefined;
|
||||
return reason === undefined ? `the '${spec}' module` : `the '${spec}' module (${reason})`;
|
||||
}
|
||||
|
||||
/** The ESNext temporal global's name. Node 24 does not define it: programs
|
||||
* that name it type-check against TS 7's lib slice (program.ts adds it only
|
||||
* for them) and lower to Node's answers. Spelled in two pieces so the
|
||||
* compiler's own sources never trip program.ts's scan when scriptc
|
||||
* compiles itself. */
|
||||
export const TEMPORAL_GLOBAL = "Tempo" + "ral";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as ts from "./ts7/syntax.js";
|
||||
import { tokenStartOfNode } from "./ts7/node-functions.js";
|
||||
|
||||
export interface CjsLexedExports {
|
||||
/** Names Node's lexer detects in THIS file (no reexport resolution). */
|
||||
@@ -30,7 +31,7 @@ function identRunEnd(text: string, pos: number): number {
|
||||
* a `a`-escaped name parses to the same AST as its plain spelling,
|
||||
* but merve reads bytes and stops at the backslash. */
|
||||
function sourceSpellsIdentifier(node: ts.Node, sf: ts.SourceFile): boolean {
|
||||
const ch = sf.text[node.getStart(sf)];
|
||||
const ch = sf.text[tokenStartOfNode(node)];
|
||||
return ch !== undefined && isIdentStartChar(ch);
|
||||
}
|
||||
|
||||
@@ -39,16 +40,16 @@ function sourceSpellsIdentifier(node: ts.Node, sf: ts.SourceFile): boolean {
|
||||
* defineProperty getter's `return IDENT.IDENT` because `this` lexes as an
|
||||
* identifier run — probed), escapes and private names do not. */
|
||||
function isIdentTokenNode(node: ts.Node, sf: ts.SourceFile): boolean {
|
||||
const start = node.getStart(sf);
|
||||
const start = tokenStartOfNode(node);
|
||||
const ch = sf.text[start];
|
||||
return ch !== undefined && isIdentStartChar(ch) && identRunEnd(sf.text, start) === node.getEnd();
|
||||
return ch !== undefined && isIdentStartChar(ch) && identRunEnd(sf.text, start) === node.end;
|
||||
}
|
||||
|
||||
/** The identifier run at the node's START — the whole name when its
|
||||
* spelling is plain, the byte prefix merve consumed when a unicode escape
|
||||
* interrupts the run mid-name. */
|
||||
function identPrefixOf(node: ts.Node, sf: ts.SourceFile): string {
|
||||
const start = node.getStart(sf);
|
||||
const start = tokenStartOfNode(node);
|
||||
return sf.text.slice(start, identRunEnd(sf.text, start));
|
||||
}
|
||||
|
||||
@@ -84,20 +85,17 @@ export function isModuleExports(e: ts.Expression): boolean {
|
||||
* follows — so the recognizer descends the leftmost-child chain while it
|
||||
* still starts where `e` starts. A ParenthesizedExpression breaks the
|
||||
* chain naturally (its child starts after the `(`), matching merve. */
|
||||
function leadingRequireOf(
|
||||
e: ts.Expression,
|
||||
sf: ts.SourceFile,
|
||||
): { spec: string; end: number } | null {
|
||||
const start = e.getStart(sf);
|
||||
function leadingRequireOf(e: ts.Expression): { spec: string; end: number } | null {
|
||||
const start = tokenStartOfNode(e);
|
||||
let cur: ts.Node = e;
|
||||
for (;;) {
|
||||
const spec = bareRequireSpecOf(cur);
|
||||
if (spec !== null && cur.getStart(sf) === start) return { spec, end: cur.getEnd() };
|
||||
if (spec !== null && tokenStartOfNode(cur) === start) return { spec, end: cur.end };
|
||||
let first: ts.Node | undefined;
|
||||
ts.forEachChild(cur, (c) => {
|
||||
first ??= c;
|
||||
});
|
||||
if (first === undefined || first.getStart(sf) !== start) return null;
|
||||
if (first === undefined || tokenStartOfNode(first) !== start) return null;
|
||||
cur = first;
|
||||
}
|
||||
}
|
||||
@@ -179,13 +177,13 @@ function scanTableLiteral(
|
||||
continue; // whitespace before `,` is skipped on the shorthand path
|
||||
}
|
||||
if (ts.isSpreadAssignment(prop)) {
|
||||
const req = leadingRequireOf(prop.expression, sf);
|
||||
const req = leadingRequireOf(prop.expression);
|
||||
let consumedEnd: number;
|
||||
if (req !== null) {
|
||||
reexports.push(req.spec);
|
||||
consumedEnd = req.end;
|
||||
} else if (sourceSpellsIdentifier(prop.expression, sf)) {
|
||||
consumedEnd = identRunEnd(text, prop.expression.getStart(sf));
|
||||
consumedEnd = identRunEnd(text, tokenStartOfNode(prop.expression));
|
||||
} else {
|
||||
return; // `...{}` and friends stop the scan
|
||||
}
|
||||
@@ -213,7 +211,7 @@ function scanTableLiteral(
|
||||
}
|
||||
if (key === null) return; // numeric / computed / template keys stop the scan
|
||||
const value = prop.initializer;
|
||||
const req = leadingRequireOf(value, sf);
|
||||
const req = leadingRequireOf(value);
|
||||
if (req !== null) {
|
||||
// A require VALUE adds the key, records the reexport, and stops
|
||||
// the scan UNCONDITIONALLY — probed: `{ a: require('x'), b: v }`
|
||||
@@ -223,7 +221,7 @@ function scanTableLiteral(
|
||||
reexports.push(req.spec);
|
||||
return;
|
||||
}
|
||||
const start = value.getStart(sf);
|
||||
const start = tokenStartOfNode(value);
|
||||
const ch0 = text[start];
|
||||
if (ch0 === undefined || !isIdentStartChar(ch0)) return; // literal values stop WITHOUT adding the key
|
||||
const consumedEnd = identRunEnd(text, start);
|
||||
@@ -351,8 +349,8 @@ function starExportSpecOf(call: ts.CallExpression, sf: ts.SourceFile): string |
|
||||
if (!sourceSpellsIdentifier(nameNode, sf)) return null;
|
||||
const arg0 = call.arguments.nodes[0];
|
||||
if (arg0 === undefined) return null;
|
||||
if (!byteAdjacentParen(sf, nameNode.getEnd(), arg0.getStart(sf))) return null;
|
||||
return leadingRequireOf(arg0, sf)?.spec ?? null;
|
||||
if (!byteAdjacentParen(sf, nameNode.end, tokenStartOfNode(arg0))) return null;
|
||||
return leadingRequireOf(arg0)?.spec ?? null;
|
||||
}
|
||||
|
||||
/** True when the bytes between a callee name ending at `nameEnd` and its
|
||||
@@ -384,7 +382,7 @@ function starAssignOf(stmt: ts.VariableStatement, sf: ts.SourceFile): [string, s
|
||||
init.arguments.nodes.length >= 1
|
||||
) {
|
||||
const arg0 = init.arguments.nodes[0]!;
|
||||
if (!byteAdjacentParen(sf, init.expression.getEnd(), arg0.getStart(sf))) return null;
|
||||
if (!byteAdjacentParen(sf, init.expression.end, tokenStartOfNode(arg0))) return null;
|
||||
const spec = bareRequireSpecOf(arg0);
|
||||
if (spec !== null) return [decl.name.text, spec];
|
||||
}
|
||||
@@ -717,15 +715,16 @@ export function cjsLexedExportsOfFile(sf: ts.SourceFile): CjsLexedExports {
|
||||
) {
|
||||
exports.add(lhs.argumentExpression.text);
|
||||
} else if (isModuleExports(lhs)) {
|
||||
events.push({ pos: n.getStart(sf), kind: "assign", rhs: n.right });
|
||||
events.push({ pos: tokenStartOfNode(n), kind: "assign", rhs: n.right });
|
||||
}
|
||||
} else if (ts.isCallExpression(n)) {
|
||||
scanDefineProperty(n, sf, exports);
|
||||
if (braceDepth === 0) {
|
||||
const starSpec = starExportSpecOf(n, sf);
|
||||
if (starSpec !== null) events.push({ pos: n.getStart(sf), kind: "spec", spec: starSpec });
|
||||
if (starSpec !== null)
|
||||
events.push({ pos: tokenStartOfNode(n), kind: "spec", spec: starSpec });
|
||||
const loopId = starLoopIdOf(n, sf);
|
||||
if (loopId !== null) events.push({ pos: n.getStart(sf), kind: "loop", id1: loopId });
|
||||
if (loopId !== null) events.push({ pos: tokenStartOfNode(n), kind: "loop", id1: loopId });
|
||||
}
|
||||
} else if (ts.isVariableStatement(n) && braceDepth === 0) {
|
||||
const assign = starAssignOf(n, sf);
|
||||
@@ -745,7 +744,7 @@ export function cjsLexedExportsOfFile(sf: ts.SourceFile): CjsLexedExports {
|
||||
for (const spec of starAssigns.get(ev.id1) ?? []) reexports.push(spec);
|
||||
} else {
|
||||
reexports = []; // every `module.exports =` clears — stars included (probed)
|
||||
const req = leadingRequireOf(ev.rhs, sf);
|
||||
const req = leadingRequireOf(ev.rhs);
|
||||
if (req !== null) {
|
||||
reexports.push(req.spec);
|
||||
} else if (ts.isObjectLiteralExpression(ev.rhs)) {
|
||||
|
||||
@@ -130,7 +130,7 @@ function markerNamed(
|
||||
function nameText(node: ts.Node | undefined): string {
|
||||
if (node === undefined) return "<anonymous>";
|
||||
if (ts.isIdentifier(node) || ts.isPrivateIdentifier(node)) return node.text;
|
||||
return node.getText();
|
||||
return ts.getTextOfNode(node);
|
||||
}
|
||||
|
||||
function contractDiag(
|
||||
|
||||
@@ -73,7 +73,7 @@ export function lifetimeRegionMemberOf(
|
||||
if (
|
||||
!ts.isClassDeclaration(owner) ||
|
||||
owner.name?.text !== "LifetimeRegion" ||
|
||||
!isExperimentalDeclarationFile(owner.getSourceFile(), known)
|
||||
!isExperimentalDeclarationFile(ts.getSourceFileOfNode(owner), known)
|
||||
)
|
||||
return null;
|
||||
const name = access.name.text;
|
||||
@@ -105,7 +105,7 @@ export function internTableMemberOf(
|
||||
if (
|
||||
!ts.isClassDeclaration(owner) ||
|
||||
owner.name?.text !== "InternTable" ||
|
||||
!isExperimentalDeclarationFile(owner.getSourceFile(), known)
|
||||
!isExperimentalDeclarationFile(ts.getSourceFileOfNode(owner), known)
|
||||
)
|
||||
return null;
|
||||
const name = access.name.text;
|
||||
@@ -135,7 +135,7 @@ export function threadSlotsMemberOf(
|
||||
if (
|
||||
!ts.isClassDeclaration(owner) ||
|
||||
owner.name?.text !== "ThreadSlots" ||
|
||||
!isExperimentalDeclarationFile(owner.getSourceFile(), known)
|
||||
!isExperimentalDeclarationFile(ts.getSourceFileOfNode(owner), known)
|
||||
)
|
||||
return null;
|
||||
const name = access.name.text;
|
||||
|
||||
@@ -7,6 +7,7 @@ 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";
|
||||
import { getTextOfNode } from "./ts7/node-functions.js";
|
||||
|
||||
test("fork discovery batches only candidate bindings and preserves aliases and shadowing", () => {
|
||||
const directory = mkdtempSync(join(tmpdir(), "scriptc-fork-discovery-"));
|
||||
@@ -49,7 +50,7 @@ test("fork discovery batches only candidate bindings and preserves aliases and s
|
||||
["first.ts", "second.ts", "third.ts"].map((name) => join(directory, name)),
|
||||
);
|
||||
expect(batch).toHaveBeenCalledTimes(1);
|
||||
expect(batch.mock.calls[0]![0].map((node) => node.getText())).toEqual([
|
||||
expect(batch.mock.calls[0]![0].map((node) => getTextOfNode(node))).toEqual([
|
||||
"start",
|
||||
"child",
|
||||
"launch",
|
||||
|
||||
@@ -12,7 +12,7 @@ export function isNodeGlobal(program: ts.Program, expression: ts.Identifier): bo
|
||||
return (
|
||||
declarations.length > 0 &&
|
||||
declarations.every((declaration) => {
|
||||
const source = declaration.getSourceFile();
|
||||
const source = ts.getSourceFileOfNode(declaration);
|
||||
return (
|
||||
source.isDeclarationFile &&
|
||||
(tsgoPath(source.fileName) === fallbackDtsPath() || isNodeTypesPath(source.fileName))
|
||||
@@ -61,7 +61,7 @@ export function isBuiltinMemberImport(
|
||||
if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
|
||||
const target = checker.getAliasedSymbol(symbol);
|
||||
const targetDecl = target.declarations[0];
|
||||
if (targetDecl !== undefined && targetDecl.getSourceFile().isDeclarationFile) {
|
||||
if (targetDecl !== undefined && ts.getSourceFileOfNode(targetDecl).isDeclarationFile) {
|
||||
for (
|
||||
let parent: ts.Node | undefined = targetDecl.parent;
|
||||
parent !== undefined && !ts.isSourceFile(parent);
|
||||
@@ -69,7 +69,8 @@ export function isBuiltinMemberImport(
|
||||
) {
|
||||
if (ts.isModuleDeclaration(parent) && ts.isStringLiteral(parent.name)) {
|
||||
return (
|
||||
canonicalBuiltinModule(parent.name.text) === moduleName && target.name === memberName
|
||||
canonicalBuiltinModule(parent.name.text) === moduleName &&
|
||||
ts.symbolName(target) === memberName
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -188,7 +189,7 @@ function staticString(
|
||||
): string | null {
|
||||
const current = strip(expr);
|
||||
if (ts.isStringLiteralLike(current)) return current.text;
|
||||
if (importMetaUrl(current)) return pathToFileURL(current.getSourceFile().fileName).href;
|
||||
if (importMetaUrl(current)) return pathToFileURL(ts.getSourceFileOfNode(current).fileName).href;
|
||||
if (ts.isTemplateExpression(current)) {
|
||||
let value = current.head.text;
|
||||
for (const span of current.templateSpans.nodes) {
|
||||
|
||||
@@ -16,7 +16,7 @@ export function inferredJsDiagnosticSuppressed(
|
||||
const checker = program.getTypeChecker();
|
||||
let suppressed = false;
|
||||
ts.walkPreorder(file, (node) => {
|
||||
if (node.getEnd() < diagnostic.pos || node.getStart(file) > diagnostic.end) return "skip";
|
||||
if (node.end < diagnostic.pos || ts.tokenStartOfNode(node) > diagnostic.end) return "skip";
|
||||
if (
|
||||
diagnostic.code === 2565 &&
|
||||
isJsSourceFileName(file.fileName) &&
|
||||
@@ -30,7 +30,7 @@ export function inferredJsDiagnosticSuppressed(
|
||||
ts.isPropertyDeclaration(decl) &&
|
||||
!decl.type &&
|
||||
!decl.initializer &&
|
||||
!/@type\b/.test(file.text.slice(decl.pos, decl.getStart(file))),
|
||||
!/@type\b/.test(file.text.slice(decl.pos, ts.tokenStartOfNode(decl))),
|
||||
)
|
||||
) {
|
||||
// Bare JS fields exist with value undefined before the first write;
|
||||
@@ -44,14 +44,14 @@ export function inferredJsDiagnosticSuppressed(
|
||||
const declaration = signature && signature.declaration;
|
||||
if (
|
||||
declaration &&
|
||||
isJsSourceFileName(declaration.getSourceFile().fileName) &&
|
||||
isJsSourceFileName(ts.getSourceFileOfNode(declaration).fileName) &&
|
||||
(ts.isFunctionDeclaration(declaration) ||
|
||||
ts.isFunctionExpression(declaration) ||
|
||||
ts.isMethodDeclaration(declaration)) &&
|
||||
node.arguments.nodes.length > declaration.parameters.nodes.length &&
|
||||
node.arguments.nodes[declaration.parameters.nodes.length]?.getStart(file) ===
|
||||
ts.tokenStartOfNode(node.arguments.nodes[declaration.parameters.nodes.length]!) ===
|
||||
diagnostic.pos &&
|
||||
node.arguments.nodes[node.arguments.nodes.length - 1]?.getEnd() === diagnostic.end &&
|
||||
node.arguments.nodes[node.arguments.nodes.length - 1]!.end === diagnostic.end &&
|
||||
bodyReadsArguments(declaration)
|
||||
) {
|
||||
suppressed = true;
|
||||
@@ -68,7 +68,7 @@ export function inferredJsDiagnosticSuppressed(
|
||||
const iterable = checker.getTypeAtLocation(node.expression);
|
||||
const iterator = checker
|
||||
.getPropertiesOfType(iterable)
|
||||
.find((property) => property.name.startsWith("__@iterator@"));
|
||||
.find((property) => ts.symbolName(property).startsWith("__@iterator@"));
|
||||
if (!iterator) return undefined;
|
||||
const signatures = checker.getCallSignatures(
|
||||
checker.getTypeOfSymbolAtLocation(iterator, node.expression),
|
||||
@@ -88,7 +88,7 @@ export function inferredJsDiagnosticSuppressed(
|
||||
declaration &&
|
||||
ts.isMethodDeclaration(declaration) &&
|
||||
declaration.type === undefined &&
|
||||
isJsSourceFileName(declaration.getSourceFile().fileName) &&
|
||||
isJsSourceFileName(ts.getSourceFileOfNode(declaration).fileName) &&
|
||||
declaration.body?.statements?.nodes.length === 1 &&
|
||||
ts.isThrowStatement(declaration.body.statements.nodes[0]!)
|
||||
) {
|
||||
|
||||
@@ -115,7 +115,7 @@ const CONTRACT_GLOBAL_TYPES = new Set(["Array", "ReadonlyArray", "Uint8Array"]);
|
||||
type TypeBindings = Map<ts.SourceFile, Set<string>>;
|
||||
|
||||
function locOf(file: ts.SourceFile, node: ts.Node): SrcLoc {
|
||||
return { file: file.fileName, start: node.getStart(), end: node.end };
|
||||
return { file: file.fileName, start: ts.tokenStartOfNode(node), end: node.end };
|
||||
}
|
||||
|
||||
function isExported(stmt: ts.Node): boolean {
|
||||
@@ -248,7 +248,7 @@ function typeShape(
|
||||
const lit = node.literal;
|
||||
if (ts.isStringLiteral(lit)) return { k: "stringLit", text: lit.text };
|
||||
if (lit.kind === ts.SyntaxKind.NullKeyword) return { k: "absent", unit: "null" };
|
||||
return { k: "unsupported", text: node.getText(file) };
|
||||
return { k: "unsupported", text: ts.getTextOfNode(node) };
|
||||
}
|
||||
if (ts.isArrayTypeNode(node))
|
||||
return { k: "array", elem: typeShape(file, node.elementType, bindings) };
|
||||
@@ -280,15 +280,19 @@ function typeShape(
|
||||
if (ts.isConditionalTypeNode(node)) {
|
||||
return {
|
||||
k: "unsupported",
|
||||
text: `a conditional type (${node.getText(file)})`,
|
||||
text: `a conditional type (${ts.getTextOfNode(node)})`,
|
||||
computed: "conditional",
|
||||
};
|
||||
}
|
||||
if (ts.isMappedTypeNode(node)) {
|
||||
return { k: "unsupported", text: `a mapped type (${node.getText(file)})`, computed: "mapped" };
|
||||
return {
|
||||
k: "unsupported",
|
||||
text: `a mapped type (${ts.getTextOfNode(node)})`,
|
||||
computed: "mapped",
|
||||
};
|
||||
}
|
||||
if (ts.isTypeReferenceNode(node)) {
|
||||
if (!ts.isIdentifier(node.typeName)) return { k: "unsupported", text: node.getText(file) };
|
||||
if (!ts.isIdentifier(node.typeName)) return { k: "unsupported", text: ts.getTextOfNode(node) };
|
||||
const name = node.typeName.text;
|
||||
const global =
|
||||
CONTRACT_GLOBAL_TYPES.has(name) && isUnshadowedGlobalType(file, node, name, bindings);
|
||||
@@ -302,10 +306,10 @@ function typeShape(
|
||||
return { k: "array", elem: typeShape(file, node.typeArguments.nodes[0]!, bindings) };
|
||||
}
|
||||
if ((node.typeArguments?.nodes.length ?? 0) > 0)
|
||||
return { k: "unsupported", text: node.getText(file) };
|
||||
return { k: "unsupported", text: ts.getTextOfNode(node) };
|
||||
return { k: "ref", name };
|
||||
}
|
||||
return { k: "unsupported", text: node.getText(file) };
|
||||
return { k: "unsupported", text: ts.getTextOfNode(node) };
|
||||
}
|
||||
|
||||
/** Unwrap `expr as const` / parenthesized initializers. */
|
||||
@@ -362,7 +366,7 @@ function envMsgArray(expr: ts.Expression): { env: string; msg: string }[] | null
|
||||
|
||||
/** One mergeable declaration site, `file:line` (1-based line). */
|
||||
function siteOf(file: ts.SourceFile, node: ts.Node): string {
|
||||
return `${file.fileName}:${ts.getLineAndCharacterOfPosition(file, node.getStart()).line + 1}`;
|
||||
return `${file.fileName}:${ts.getLineAndCharacterOfPosition(file, ts.tokenStartOfNode(node)).line + 1}`;
|
||||
}
|
||||
|
||||
/** Everything the sidecar projection needs from the program's syntax
|
||||
|
||||
@@ -35,7 +35,7 @@ export function entryFunctionExports(entry: ts.SourceFile): Map<string, EntryExp
|
||||
if (out.has(name) && stmt.body === undefined) continue; // overload signature
|
||||
out.set(name, {
|
||||
name,
|
||||
loc: { file: entry.fileName, start: stmt.getStart(), end: stmt.end },
|
||||
loc: { file: entry.fileName, start: ts.tokenStartOfNode(stmt), end: stmt.end },
|
||||
generic: (stmt.typeParameters?.nodes.length ?? 0) > 0,
|
||||
async: mods?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true,
|
||||
generator: stmt.asteriskToken !== undefined,
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
type IrType,
|
||||
isIntegerBytesElem,
|
||||
type SrcLoc,
|
||||
STRING,
|
||||
UNDEFINED_T,
|
||||
typeEquals,
|
||||
typeKey,
|
||||
@@ -84,29 +85,54 @@ export function arrayValueRead(
|
||||
};
|
||||
}
|
||||
|
||||
/** Read a stabilized union of native arrays without copying either the
|
||||
* receiver or its elements. Every arm contributes its missing-value case. */
|
||||
/** Read one position of a stabilized union of native arrays and tuples
|
||||
* (`const [a, b] = re.exec(s) || ["", ""]`) without copying the receiver
|
||||
* or its elements. Every array arm contributes its missing-value case; a
|
||||
* tuple arm answers its field, or undefined past its end. Tuple arms need
|
||||
* a constant position. With `stringChars` (the string iterator's code
|
||||
* points as a fresh string[]), a string arm (`re.exec(s) || ""`) answers
|
||||
* its code point at the position, or undefined past its end. */
|
||||
export function unionArrayValueRead(
|
||||
lowerer: Lowerer,
|
||||
value: IrExpr,
|
||||
index: IrExpr,
|
||||
loc: SrcLoc,
|
||||
stringChars?: (text: IrExpr) => IrExpr,
|
||||
): IrExpr | null {
|
||||
if (value.type.kind !== "union") return null;
|
||||
const unionId = value.type.unionId;
|
||||
const arms = lowerer.unions.get(unionId)?.arms;
|
||||
if (!arms?.length) return null;
|
||||
const position = index.kind === "numLit" ? String(index.value) : null;
|
||||
// A tuple arm's answer at the position: its field, or undefined past
|
||||
// its end. Null when the arm is neither an array nor a tuple.
|
||||
const tupleField = (arm: IrType): { name: string; type: IrType } | "past" | null => {
|
||||
if (arm.kind !== "record" || position === null) return null;
|
||||
const shape = lowerer.shapes.get(arm.shapeId);
|
||||
if (!shape?.tuple) return null;
|
||||
return shape.fields.find((f) => f.name === position) ?? "past";
|
||||
};
|
||||
// Keep the collection's IrType union layout and narrow each element at
|
||||
// its use; an inferred array predicate changes the collection's ABI.
|
||||
for (const arm of arms) if (arm.kind !== "array") return null;
|
||||
const stringArm = (arm: IrType): boolean => arm.kind === "string" && stringChars !== undefined;
|
||||
for (const arm of arms)
|
||||
if (arm.kind !== "array" && !stringArm(arm) && tupleField(arm) === null) return null;
|
||||
const answers = new Map<string, IrType>();
|
||||
for (const arm of arms) {
|
||||
if (arm.kind !== "array") return null;
|
||||
const answer = arrayValueType(lowerer, arm.elem);
|
||||
const field = arm.kind === "array" || stringArm(arm) ? null : tupleField(arm);
|
||||
const answer =
|
||||
arm.kind === "array"
|
||||
? arrayValueType(lowerer, arm.elem)
|
||||
: stringArm(arm)
|
||||
? arrayValueType(lowerer, STRING)
|
||||
: field === "past"
|
||||
? UNDEFINED_T
|
||||
: field!.type;
|
||||
const parts = answer.kind === "union" ? lowerer.unions.get(answer.unionId)!.arms : [answer];
|
||||
for (const part of parts) answers.set(typeKey(part), part);
|
||||
}
|
||||
const joined = [...answers.values()].sort((a, b) => (typeKey(a) < typeKey(b) ? -1 : 1));
|
||||
if (joined.length === 1 && joined[0]!.kind === "undefinedT") return null;
|
||||
if (
|
||||
joined.length > 1 &&
|
||||
(!unionContainerArmsOk(joined) ||
|
||||
@@ -118,12 +144,39 @@ export function unionArrayValueRead(
|
||||
let result: IrExpr | null = null;
|
||||
for (let tag = arms.length - 1; tag >= 0; tag--) {
|
||||
const arm = arms[tag]!;
|
||||
if (arm.kind !== "array") return null;
|
||||
const receiver: IrExpr = { kind: "unionNarrow", unionId, tag, value, type: arm, loc };
|
||||
const read = lowerer.coerceToExpected(
|
||||
arrayValueRead(lowerer, receiver, index, arm.elem, loc),
|
||||
type,
|
||||
);
|
||||
let read: IrExpr;
|
||||
if (arm.kind === "array") {
|
||||
read = lowerer.coerceToExpected(
|
||||
arrayValueRead(lowerer, receiver, index, arm.elem, loc),
|
||||
type,
|
||||
);
|
||||
} else if (stringArm(arm)) {
|
||||
read = lowerer.coerceToExpected(
|
||||
arrayValueRead(lowerer, stringChars!(receiver), index, STRING, loc),
|
||||
type,
|
||||
);
|
||||
} else {
|
||||
const field = tupleField(arm);
|
||||
if (field === null) return null;
|
||||
if (field === "past") {
|
||||
const missing = lowerer.wrappedUndefined(type, loc);
|
||||
if (!missing) return null;
|
||||
read = missing;
|
||||
} else {
|
||||
read = lowerer.coerceToExpected(
|
||||
{
|
||||
kind: "recordGet",
|
||||
obj: receiver,
|
||||
shapeId: (arm as IrType & { kind: "record" }).shapeId,
|
||||
field: field.name,
|
||||
type: field.type,
|
||||
loc,
|
||||
},
|
||||
type,
|
||||
);
|
||||
}
|
||||
}
|
||||
result =
|
||||
result === null
|
||||
? read
|
||||
|
||||
@@ -194,7 +194,7 @@ function onlyMethodCallReferences(lowerer: Lowerer, name: ts.Identifier): boolea
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
visit(name.getSourceFile());
|
||||
visit(ts.getSourceFileOfNode(name));
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ export function extendAssertedRecordMembers(
|
||||
symbol.declarations.length > 0 &&
|
||||
symbol.declarations.every(
|
||||
(d) =>
|
||||
!lowerer.isStdlibFile(d.getSourceFile()) &&
|
||||
!lowerer.isStdlibFile(ts.getSourceFileOfNode(d)) &&
|
||||
(ts.isTypeLiteralNode(d) ||
|
||||
ts.isInterfaceDeclaration(d) ||
|
||||
ts.isTypeAliasDeclaration(d)),
|
||||
|
||||
@@ -16,7 +16,7 @@ export function bindingNeverReassigned(lowerer: Lowerer, sym: ts.Symbol, decl: t
|
||||
let written = false;
|
||||
// Text pre-check keeps the file walk cheap: only same-named
|
||||
// identifiers pay a symbol resolution.
|
||||
const symText = sym.name;
|
||||
const symText = ts.symbolName(sym);
|
||||
const namesSym = (e: ts.Node): boolean =>
|
||||
ts.isIdentifier(e) && e.text === symText && lowerer.resolveValueSymbol(e) === sym;
|
||||
const scanTarget = (t: ts.Expression): void => {
|
||||
@@ -31,7 +31,7 @@ export function bindingNeverReassigned(lowerer: Lowerer, sym: ts.Symbol, decl: t
|
||||
if (ts.isArrayLiteralExpression(e) || ts.isObjectLiteralExpression(e)) {
|
||||
const walk = (n: ts.Node): void => {
|
||||
if (namesSym(n)) written = true;
|
||||
else n.forEachChild(walk);
|
||||
else ts.forEachChild(n, walk);
|
||||
};
|
||||
walk(e);
|
||||
}
|
||||
@@ -54,9 +54,9 @@ export function bindingNeverReassigned(lowerer: Lowerer, sym: ts.Symbol, decl: t
|
||||
) {
|
||||
scanTarget(n.initializer as ts.Expression);
|
||||
}
|
||||
n.forEachChild(visit);
|
||||
ts.forEachChild(n, visit);
|
||||
};
|
||||
decl.getSourceFile().forEachChild(visit);
|
||||
ts.forEachChild(ts.getSourceFileOfNode(decl), visit);
|
||||
lowerer.neverReassignedCache.set(sym, !written);
|
||||
return !written;
|
||||
}
|
||||
@@ -123,7 +123,7 @@ export function nullishValueUnitOf(
|
||||
!decl ||
|
||||
!ts.isVariableDeclaration(decl) ||
|
||||
!ts.isIdentifier(decl.name) ||
|
||||
decl.getSourceFile().isDeclarationFile ||
|
||||
ts.getSourceFileOfNode(decl).isDeclarationFile ||
|
||||
decl.initializer === undefined ||
|
||||
!ts.isVariableStatement(decl.parent?.parent)
|
||||
) {
|
||||
@@ -154,7 +154,7 @@ export function nullishValueUnitOf(
|
||||
/** True when any identifier resolving to `sym` sits inside a class
|
||||
* heritage clause anywhere in the program. */
|
||||
function usedInHeritageClause(lowerer: Lowerer, sym: ts.Symbol): boolean {
|
||||
const symText = sym.name;
|
||||
const symText = ts.symbolName(sym);
|
||||
let found = false;
|
||||
const visit = (n: ts.Node): void => {
|
||||
if (found) return;
|
||||
@@ -171,12 +171,12 @@ function usedInHeritageClause(lowerer: Lowerer, sym: ts.Symbol): boolean {
|
||||
}
|
||||
return;
|
||||
}
|
||||
n.forEachChild(visit);
|
||||
ts.forEachChild(n, visit);
|
||||
};
|
||||
for (const file of lowerer.program.getImplementationSourceFiles()) {
|
||||
if (found) break;
|
||||
if (file.isDeclarationFile) continue;
|
||||
file.forEachChild(visit);
|
||||
ts.forEachChild(file, visit);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
@@ -236,7 +236,7 @@ export function nullishGenericBindingUnitOf(
|
||||
* value knowable. Compound assignments, ++/--, for-in/of cursors, and
|
||||
* destructuring targets all disqualify. */
|
||||
function allWritesNullish(lowerer: Lowerer, sym: ts.Symbol, decl: ts.Node): boolean {
|
||||
const symText = sym.name;
|
||||
const symText = ts.symbolName(sym);
|
||||
const namesSym = (e: ts.Node): boolean =>
|
||||
ts.isIdentifier(e) && e.text === symText && lowerer.resolveValueSymbol(e) === sym;
|
||||
let ok = true;
|
||||
@@ -253,7 +253,7 @@ function allWritesNullish(lowerer: Lowerer, sym: ts.Symbol, decl: ts.Node): bool
|
||||
} else if (ts.isArrayLiteralExpression(lhs) || ts.isObjectLiteralExpression(lhs)) {
|
||||
const walk = (m: ts.Node): void => {
|
||||
if (namesSym(m)) ok = false;
|
||||
else m.forEachChild(walk);
|
||||
else ts.forEachChild(m, walk);
|
||||
};
|
||||
walk(lhs);
|
||||
}
|
||||
@@ -274,9 +274,9 @@ function allWritesNullish(lowerer: Lowerer, sym: ts.Symbol, decl: ts.Node): bool
|
||||
t = (t as ts.ParenthesizedExpression).expression;
|
||||
if (namesSym(t)) ok = false;
|
||||
}
|
||||
n.forEachChild(visit);
|
||||
ts.forEachChild(n, visit);
|
||||
};
|
||||
decl.getSourceFile().forEachChild(visit);
|
||||
ts.forEachChild(ts.getSourceFileOfNode(decl), visit);
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ export function deadUnmappableBinding(
|
||||
// the no-storage family never claims it (catch bindings sit outside a
|
||||
// variable statement too and stay out the same way).
|
||||
if (!ts.isVariableStatement(decl.parent?.parent)) return false;
|
||||
const sf = decl.getSourceFile();
|
||||
const sf = ts.getSourceFileOfNode(decl);
|
||||
if (sf.isDeclarationFile || isJsSourceFile(sf)) return false;
|
||||
if (decl.initializer !== undefined && !sideEffectFreeValueExpr(lowerer, decl.initializer))
|
||||
return false;
|
||||
@@ -344,7 +344,7 @@ export function deadUnmappableBinding(
|
||||
// bug), so only survivors of the syntactic filters pay it. Mappable
|
||||
// types keep their real storage.
|
||||
if (lowerer.mapTypeOf(lowerer.checker.getTypeOfSymbol(sym)) !== null) return false;
|
||||
const symText = sym.name;
|
||||
const symText = ts.symbolName(sym);
|
||||
const namesSym = (e: ts.Node): boolean =>
|
||||
ts.isIdentifier(e) && e.text === symText && lowerer.resolveValueSymbol(e) === sym;
|
||||
let dead = true;
|
||||
@@ -353,7 +353,7 @@ export function deadUnmappableBinding(
|
||||
if (ts.isIdentifier(n) && n.text === symText) {
|
||||
// Declaration-name occurrences are not reads.
|
||||
if (n.parent !== undefined && ts.isVariableDeclaration(n.parent) && n.parent.name === n) {
|
||||
n.forEachChild(visit);
|
||||
ts.forEachChild(n, visit);
|
||||
return;
|
||||
}
|
||||
// A plain-assignment LHS is a WRITE — dead only when the RHS
|
||||
@@ -375,12 +375,12 @@ export function deadUnmappableBinding(
|
||||
if (namesSym(n)) dead = false;
|
||||
return;
|
||||
}
|
||||
n.forEachChild(visit);
|
||||
ts.forEachChild(n, visit);
|
||||
};
|
||||
for (const file of lowerer.program.getImplementationSourceFiles()) {
|
||||
if (!dead) break;
|
||||
if (file.isDeclarationFile) continue;
|
||||
file.forEachChild(visit);
|
||||
ts.forEachChild(file, visit);
|
||||
}
|
||||
if (dead) lowerer.deadBindings.add(sym);
|
||||
return dead;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { lowerReadlineCursorCall } from "./builtins/readline.js";
|
||||
import { lowerMonitorEventLoopDelayCall } from "./builtins/performance.js";
|
||||
import {
|
||||
lowerEnvironmentData,
|
||||
lowerReceiveMessageOnPort,
|
||||
@@ -15,6 +16,8 @@ import { lowerStreamModuleCall } from "./lower-stream.js";
|
||||
import { lowerCallbackPromisify } from "./builtins/promisify-callback.js";
|
||||
import { lowerNodeModuleCall } from "./builtins/module-resolution.js";
|
||||
import { builtinModuleFnOf, builtinFenceHintOf } from "./surfaces.js";
|
||||
import { resultIsDiscarded } from "./call-position.js";
|
||||
import { locOf, requireSpecOf } from "../program.js";
|
||||
|
||||
/** `events.addAbortListener(signal, listener)`: the runtime registers the
|
||||
* once/resist-stop-propagation abort listener (or queues the call for an
|
||||
@@ -54,6 +57,38 @@ export function lowerAddAbortListener(
|
||||
return { kind: "dynObjLit", fields: [{ key, value: dispose }], type: DYN, loc };
|
||||
}
|
||||
|
||||
/** graceful-fs's `gracefulify(fsModule)` (graceful-fs is served as node:fs —
|
||||
* builtin-modules.ts): it patches a module object with the retry queue,
|
||||
* which statically lowered fs calls never consult, so the call statement
|
||||
* is a no-op. The argument still evaluates unless it is a plain reference
|
||||
* (the patched module, typically `require("fs")` or a namespace binding).
|
||||
* The returned module object has no representation; other positions
|
||||
* fence. */
|
||||
function lowerGracefulify(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr {
|
||||
const args = expr.arguments.nodes;
|
||||
if (!resultIsDiscarded(expr) || args.length !== 1 || ts.isSpreadElement(args[0]!)) {
|
||||
lowerer.noLowering(
|
||||
"gracefulify",
|
||||
expr,
|
||||
"graceful-fs compiles as node:fs; gracefulify(fsModule) lowers as a statement whose result is unused",
|
||||
);
|
||||
}
|
||||
let arg: ts.Expression = args[0]!;
|
||||
while (ts.isParenthesizedExpression(arg)) arg = arg.expression;
|
||||
const plain =
|
||||
ts.isIdentifier(arg) ||
|
||||
(ts.isCallExpression(arg) && requireSpecOf(arg) !== null) ||
|
||||
(ts.isPropertyAccessExpression(arg) && ts.isIdentifier(arg.expression));
|
||||
if (plain) return { kind: "seqExpr", stmts: [], result: dynUndefinedExpr(loc), type: DYN, loc };
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [{ kind: "exprStmt", expr: lowerer.lowerExpr(arg), loc: locOf(arg) }],
|
||||
result: dynUndefinedExpr(loc),
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** Named imports, namespace members and callable aliases share validation
|
||||
* and dispatch. Resolve diagnostic provenance only if no handler accepts. */
|
||||
export function lowerBuiltinCall(
|
||||
@@ -70,6 +105,9 @@ export function lowerBuiltinCall(
|
||||
return lowerAddAbortListener(lowerer, expr, loc);
|
||||
}
|
||||
if (bi.module === "events" && bi.member === "once") return lowerEventsOnce(lowerer, expr, loc);
|
||||
if (bi.module === "perf_hooks" && bi.member === "monitorEventLoopDelay") {
|
||||
return lowerMonitorEventLoopDelayCall(lowerer, expr);
|
||||
}
|
||||
if (bi.module === "events") {
|
||||
const helper = lowerEventsEmitterHelper(lowerer, expr, bi.member, loc);
|
||||
if (helper) return helper;
|
||||
@@ -126,6 +164,8 @@ export function lowerBuiltinCall(
|
||||
if (bi.module === "worker_threads" && bi.member === "markAsUncloneable") {
|
||||
return lowerUncloneable(lowerer, expr);
|
||||
}
|
||||
if (bi.module === "fs" && bi.member === "gracefulify")
|
||||
return lowerGracefulify(lowerer, expr, loc);
|
||||
const builtinFn = builtinModuleFnOf(lowerer, bi.module, bi.member);
|
||||
if (!builtinFn) {
|
||||
lowerer.noLowering(
|
||||
|
||||
@@ -25,7 +25,7 @@ export function builtinFunctionFenceValue(
|
||||
`library functions as values ('${display}' has no function-value lowering; call it directly)`,
|
||||
);
|
||||
lowerer.runtimeFences.push(diag);
|
||||
const line = ts.getLineAndCharacterOfPosition(node.getSourceFile(), loc.start).line + 1;
|
||||
const line = ts.getLineAndCharacterOfPosition(ts.getSourceFileOfNode(node), loc.start).line + 1;
|
||||
lowerer.liftedFns.push({
|
||||
name,
|
||||
params: [],
|
||||
|
||||
@@ -35,7 +35,7 @@ export function lowerDiagnosticsSubscriber(lowerer: Lowerer, node: ts.Expression
|
||||
if (ts.isIdentifier(node) && node.text === "setImmediate") {
|
||||
const sym = lowerer.checker.getSymbolAtLocation(node);
|
||||
const decls = sym ? sym.declarations : [];
|
||||
if (decls.length > 0 && decls.every((d) => lowerer.isStdlibFile(d.getSourceFile()))) {
|
||||
if (decls.length > 0 && decls.every((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d)))) {
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "timers.setImmediateFnValue",
|
||||
|
||||
@@ -52,10 +52,10 @@ export function blobClassOf(lowerer: Lowerer, node: ts.Expression): BlobClass |
|
||||
function blobReceiverClass(lowerer: Lowerer, node: ts.Expression): BlobClass | null {
|
||||
const type = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(node));
|
||||
const sym = type.getAliasSymbol() ?? type.getSymbol();
|
||||
if (!sym || (sym.name !== "Blob" && sym.name !== "File") || !lowerer.isStdlibSymbol(sym)) {
|
||||
return null;
|
||||
}
|
||||
return sym.name;
|
||||
if (!sym) return null;
|
||||
const name = ts.symbolName(sym);
|
||||
if ((name !== "Blob" && name !== "File") || !lowerer.isStdlibSymbol(sym)) return null;
|
||||
return name;
|
||||
}
|
||||
|
||||
function memberNameOf(access: ts.PropertyAccessExpression | ts.ElementAccessExpression) {
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import { numLit } from "../../../ir/build.js";
|
||||
import { BOOL, DYN, RUNTIME_EMITTER_CLASS, type IrExpr, type SrcLoc } from "../../../ir/ir.js";
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import { locOf } from "../../program.js";
|
||||
import type { Lowerer } from "../lowerer.js";
|
||||
|
||||
/* EventEmitter and the stream classes as untyped constructor values, the
|
||||
* bases of JavaScript constructor functions:
|
||||
*
|
||||
* function Bus() { EventEmitter.call(this); }
|
||||
* util.inherits(Bus, EventEmitter);
|
||||
*
|
||||
* A compiled `class Bus extends EventEmitter` gets a native emitter prefix,
|
||||
* but `new Bus()` on a constructor function builds an untyped object, so
|
||||
* the base is the runtime's dyn constructor (scr_dyn_builtin_class.c): a
|
||||
* function whose prototype carries Node's EventEmitter methods over the
|
||||
* object's own `_events` table. The stream classes have prototype chains
|
||||
* for util.inherits and instanceof; constructing over them refuses where
|
||||
* it runs. These values appear only where a constructor function uses its
|
||||
* base: util.inherits, `Base.call(this, …)` and `Base.apply(this, …)`,
|
||||
* `Base.prototype` and `instanceof Base` on an untyped value. */
|
||||
|
||||
/** scr_dyn_builtin_class's kind for a runtime class, or null. */
|
||||
export function builtinClassKind(className: string): number | null {
|
||||
switch (className) {
|
||||
case RUNTIME_EMITTER_CLASS:
|
||||
return 0;
|
||||
case "%Readable":
|
||||
return 2;
|
||||
case "%Writable":
|
||||
return 3;
|
||||
case "%Duplex":
|
||||
return 4;
|
||||
case "%Transform":
|
||||
return 5;
|
||||
case "%PassThrough":
|
||||
return 6;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** The classes by builtin module member. JavaScript sources bind
|
||||
* `require("events").EventEmitter` as a module member rather than a class
|
||||
* value, and `require("stream")` is the legacy Stream constructor. */
|
||||
const MODULE_CLASS_KINDS: Readonly<Record<string, Readonly<Record<string, number>>>> = {
|
||||
events: { EventEmitter: 0, default: 0 },
|
||||
stream: { Stream: 1, Readable: 2, Writable: 3, Duplex: 4, Transform: 5, PassThrough: 6 },
|
||||
};
|
||||
|
||||
/** The kind of a builtin module member that is one of those classes. */
|
||||
export function builtinMemberClassKind(module: string, member: string): number | null {
|
||||
return MODULE_CLASS_KINDS[module]?.[member] ?? null;
|
||||
}
|
||||
|
||||
function unparenthesized(node: ts.Expression): ts.Expression {
|
||||
let current = node;
|
||||
while (ts.isParenthesizedExpression(current)) current = current.expression;
|
||||
return current;
|
||||
}
|
||||
|
||||
/** The kind of a node that names one of those classes: a class value, a
|
||||
* builtin module member, or the events and stream modules themselves. */
|
||||
export function builtinClassKindOf(lowerer: Lowerer, node: ts.Expression): number | null {
|
||||
const expr = unparenthesized(node);
|
||||
const mapped = lowerer.mapTypeOf(lowerer.typeOf(expr));
|
||||
if (mapped?.kind === "classval") return builtinClassKind(mapped.className);
|
||||
const member = ts.isIdentifier(expr)
|
||||
? lowerer.builtinImportOf(expr)
|
||||
: ts.isPropertyAccessExpression(expr)
|
||||
? lowerer.builtinMemberOf(expr)
|
||||
: null;
|
||||
if (member) return MODULE_CLASS_KINDS[member.module]?.[member.member] ?? null;
|
||||
const module = lowerer.builtinNamespaceModuleOf(expr);
|
||||
return module === "events" ? 0 : module === "stream" ? 1 : null;
|
||||
}
|
||||
|
||||
/** Whether evaluating the node can be skipped: a binding or a member of
|
||||
* one, or of a builtin require. */
|
||||
function referenceOnly(node: ts.Expression): boolean {
|
||||
const expr = unparenthesized(node);
|
||||
if (ts.isIdentifier(expr)) return true;
|
||||
if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) {
|
||||
const base = unparenthesized(expr.expression);
|
||||
return (
|
||||
ts.isIdentifier(base) ||
|
||||
(ts.isCallExpression(base) &&
|
||||
ts.isIdentifier(base.expression) &&
|
||||
base.expression.text === "require" &&
|
||||
base.arguments.nodes.length === 1 &&
|
||||
ts.isStringLiteral(base.arguments.nodes[0]!))
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** The untyped constructor named by a node, or null when it names none of
|
||||
* those classes. */
|
||||
export function builtinClassDynValueOf(lowerer: Lowerer, node: ts.Expression): IrExpr | null {
|
||||
const kind = builtinClassKindOf(lowerer, node);
|
||||
if (kind === null) return null;
|
||||
const loc = locOf(node);
|
||||
const value = builtinClassValue(kind, loc);
|
||||
if (referenceOnly(node)) return value;
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [{ kind: "exprStmt", expr: lowerer.lowerExpr(node), loc }],
|
||||
result: value,
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** EventEmitter and the legacy Stream are dyn.builtinClass; the stream
|
||||
* classes, which carry native stream state for constructor functions, are
|
||||
* dyn.builtinStreamClass, so only programs that use them link the stream
|
||||
* half. */
|
||||
export function builtinClassValue(kind: number, loc: SrcLoc): IrExpr {
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: kind <= 1 ? "dyn.builtinClass" : "dyn.builtinStreamClass",
|
||||
args: [numLit(kind, loc)],
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** `Base.call(this, …)` / `Base.apply(this, args)` on one of those class
|
||||
* values: Function.prototype.call/apply over the untyped constructor. */
|
||||
export function lowerBuiltinClassCall(lowerer: Lowerer, call: ts.CallExpression): IrExpr | null {
|
||||
const callee = call.expression;
|
||||
if (!ts.isPropertyAccessExpression(callee) || callee.questionDotToken) return null;
|
||||
const method = callee.name.text;
|
||||
if (method !== "call" && method !== "apply") return null;
|
||||
if (call.arguments.nodes.some(ts.isSpreadElement)) return null;
|
||||
const receiver = builtinClassDynValueOf(lowerer, callee.expression);
|
||||
if (receiver === null) return null;
|
||||
const loc = locOf(call);
|
||||
return {
|
||||
kind: "dynInvoke",
|
||||
recv: receiver,
|
||||
method,
|
||||
calleeName: ts.getTextOfNode(callee),
|
||||
args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)),
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** `value instanceof Base` for an untyped value. */
|
||||
export function builtinClassInstanceOf(value: IrExpr, kind: number, loc: SrcLoc): IrExpr {
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "dyn.builtinInstanceOf",
|
||||
args: [value, numLit(kind, loc)],
|
||||
type: BOOL,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
@@ -50,7 +50,7 @@ export function registerBuiltinCallableAlias(
|
||||
// These checked native functions retain a runtime slot in JavaScript so
|
||||
// aliases can escape without narrowing their arguments to string.
|
||||
if (
|
||||
isJsSourceFile(nameNode.getSourceFile()) &&
|
||||
isJsSourceFile(ts.getSourceFileOfNode(nameNode)) &&
|
||||
target.module === "util" &&
|
||||
["stripVTControlCharacters", "toUSVString", "isDeepStrictEqual", "styleText"].includes(
|
||||
target.member,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { boolLit, strLit } from "../../../ir/build.js";
|
||||
import * as ts from "../../ts7/adapter.js";
|
||||
import { requestGeneralStreamSurface } from "../general-streams.js";
|
||||
import { type Lowerer } from "../lowerer.js";
|
||||
import { isJsSourceFile, locOf } from "../../program.js";
|
||||
import { isChildSurfaceMember } from "../surfaces.js";
|
||||
@@ -121,22 +122,32 @@ export function lowerChildMethodCall(
|
||||
}
|
||||
const listenerNode = stripTypeCasts(call.arguments.nodes[1]!);
|
||||
if (
|
||||
isJsSourceFile(listenerNode.getSourceFile()) &&
|
||||
isJsSourceFile(ts.getSourceFileOfNode(listenerNode)) &&
|
||||
(ts.isArrowFunction(listenerNode) || ts.isFunctionExpression(listenerNode)) &&
|
||||
listenerNode.parameters.nodes.some((param) => param.dotDotDotToken) &&
|
||||
listenerNode.parameters.nodes.every((param) => !param.type) &&
|
||||
!/@(?:param|type)\b/.test(
|
||||
listenerNode.getSourceFile().text.slice(listenerNode.pos, listenerNode.getStart()),
|
||||
ts
|
||||
.getSourceFileOfNode(listenerNode)
|
||||
.text.slice(listenerNode.pos, ts.tokenStartOfNode(listenerNode)),
|
||||
)
|
||||
) {
|
||||
for (const param of listenerNode.parameters.nodes) lowerer.checkedCallbackParams.add(param);
|
||||
}
|
||||
const cb = lowerer.lowerExpr(call.arguments.nodes[1]!);
|
||||
const narrowed =
|
||||
event === "exit" || event === "close"
|
||||
? narrowedExitListenerParams(lowerer, listenerNode)
|
||||
: [];
|
||||
const cb =
|
||||
narrowed.length > 0
|
||||
? lowerWithCheckedParams(lowerer, listenerNode, narrowed)
|
||||
: lowerer.lowerExpr(call.arguments.nodes[1]!);
|
||||
if (
|
||||
(isJsSourceFile(listenerNode.getSourceFile()) && cb.type.kind === "dyn") ||
|
||||
narrowed.length > 0 ||
|
||||
(isJsSourceFile(ts.getSourceFileOfNode(listenerNode)) && cb.type.kind === "dyn") ||
|
||||
(cb.type.kind === "func" &&
|
||||
(cb.type.rest ||
|
||||
(isJsSourceFile(listenerNode.getSourceFile()) &&
|
||||
(isJsSourceFile(ts.getSourceFileOfNode(listenerNode)) &&
|
||||
cb.type.params.some((param) => param.kind === "dyn"))))
|
||||
) {
|
||||
if (
|
||||
@@ -301,10 +312,12 @@ export function lowerChildMethodCall(
|
||||
|
||||
/** Method calls on piped child-output stream receivers (child.stdout /
|
||||
* child.stderr — the childStream kind): on/once("data" | "end").
|
||||
* 'data' listeners take zero parameters, `(chunk: Buffer)`, or a
|
||||
* `Buffer | string`-union chunk (the ngrok appendOutput shape — the
|
||||
* runtime only ever fires Buffers; the compiler-emitted adapter wraps
|
||||
* the chunk at the union's Buffer arm); 'end' listeners take none.
|
||||
* 'data' listeners take zero parameters, `(chunk: Buffer)`, `(chunk:
|
||||
* string)`, an unknown/any chunk (boxed as a checked-dynamic value; see
|
||||
* childStreamEncodedAt), or a `Buffer | string`-union chunk (the ngrok
|
||||
* appendOutput shape — the runtime only ever fires Buffers; the
|
||||
* compiler-emitted adapter wraps the chunk at the union's Buffer arm);
|
||||
* 'end' listeners take none.
|
||||
* Statement position only (Node returns the stream for chaining; here
|
||||
* the result is void). Chained receivers (`child.stdout?.on(...)`)
|
||||
* ride the optional-chain re-dispatch (chainBlocked). Everything else
|
||||
@@ -335,6 +348,7 @@ export function lowerChildStreamMethodCall(
|
||||
const evT = lowerer.typeOf(call.arguments.nodes[0]!);
|
||||
const event = evT.isStringLiteralType() ? evT.value : null;
|
||||
if (event !== "data" && event !== "end") {
|
||||
requestGeneralStreamSurface(lowerer, access.expression);
|
||||
lowerer.noLowering(
|
||||
`stream.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`,
|
||||
call.arguments.nodes[0]!,
|
||||
@@ -342,6 +356,7 @@ export function lowerChildStreamMethodCall(
|
||||
);
|
||||
}
|
||||
if (!ts.isExpressionStatement(call.parent)) {
|
||||
requestGeneralStreamSurface(lowerer, access.expression);
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call,
|
||||
@@ -376,23 +391,32 @@ export function lowerChildStreamMethodCall(
|
||||
if (
|
||||
param !== undefined &&
|
||||
param.kind !== "string" &&
|
||||
param.kind !== "dyn" &&
|
||||
!(param.kind === "bytes" && param.elem === "u8") &&
|
||||
!unionOk(param)
|
||||
) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call.arguments.nodes[1]!,
|
||||
`data listeners whose parameter is not 'Buffer', 'string', or a Buffer-armed union (got '${lowerer.fmt(param)}')`,
|
||||
`data listeners whose parameter is not 'Buffer', 'string', 'unknown', or a Buffer-armed union (got '${lowerer.fmt(param)}')`,
|
||||
);
|
||||
}
|
||||
// An unknown/any chunk is a Buffer, or a string once setEncoding ran.
|
||||
// The registration takes whichever the enclosing code establishes; a
|
||||
// later setEncoding that contradicts it throws the runtime's
|
||||
// listener-type TypeError instead of delivering the other kind.
|
||||
const strings =
|
||||
param?.kind === "string" ||
|
||||
(param?.kind === "dyn" && childStreamEncodedAt(lowerer, access.expression, call));
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: param?.kind === "string" ? "stream.onDataStr" : "stream.onData",
|
||||
fn: strings ? "stream.onDataStr" : "stream.onData",
|
||||
args: [receiver, cb, once],
|
||||
type: VOID,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
requestGeneralStreamSurface(lowerer, access.expression);
|
||||
lowerer.noLowering(
|
||||
`ReadableStream.${name}`,
|
||||
call,
|
||||
@@ -418,6 +442,7 @@ export function lowerChildWriterMethodCall(
|
||||
|
||||
if (name === "write") {
|
||||
if (call.arguments.nodes.length !== 1 || call.arguments.nodes.some(ts.isSpreadElement)) {
|
||||
requestGeneralStreamSurface(lowerer, access.expression);
|
||||
lowerer.noLowering(
|
||||
`child stdin write with ${call.arguments.nodes.length} arguments`,
|
||||
call,
|
||||
@@ -461,6 +486,7 @@ export function lowerChildWriterMethodCall(
|
||||
const eventType = lowerer.typeOf(call.arguments.nodes[0]!);
|
||||
const event = eventType.isStringLiteralType() ? eventType.value : null;
|
||||
if (event !== "drain" && event !== "finish" && event !== "error") {
|
||||
requestGeneralStreamSurface(lowerer, access.expression);
|
||||
lowerer.noLowering(
|
||||
`child stdin ${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`,
|
||||
call.arguments.nodes[0]!,
|
||||
@@ -507,6 +533,7 @@ export function lowerChildWriterMethodCall(
|
||||
return { kind: "libCall", fn, args: [receiver, cb, once], type: VOID, loc };
|
||||
}
|
||||
|
||||
requestGeneralStreamSurface(lowerer, access.expression);
|
||||
lowerer.noLowering(
|
||||
`Writable.${name}`,
|
||||
call,
|
||||
@@ -514,3 +541,255 @@ export function lowerChildWriterMethodCall(
|
||||
lowerer.checker.getSymbolAtLocation(access.name),
|
||||
);
|
||||
}
|
||||
|
||||
/** A receiver spelled as a plain access path (`child.stdout`, `out`): its
|
||||
* root symbol and member names, or null for other forms. A `const` alias
|
||||
* of an access path (`const out = child.stdout`) resolves to that path,
|
||||
* and so does a method call declared to return `this`
|
||||
* (`child.stdout.setEncoding("utf8")`), so every spelling of one stream
|
||||
* gets the same key. */
|
||||
function accessPathOf(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.Expression,
|
||||
depth = 0,
|
||||
): { root: ts.Symbol; path: string } | null {
|
||||
if (depth > 16) return null;
|
||||
if (ts.isParenthesizedExpression(expr) || ts.isNonNullExpression(expr)) {
|
||||
return accessPathOf(lowerer, expr.expression, depth + 1);
|
||||
}
|
||||
if (ts.isIdentifier(expr)) {
|
||||
const root = lowerer.checker.getSymbolAtLocation(expr);
|
||||
if (!root) return null;
|
||||
const init = constInitializerOf(root);
|
||||
const aliased = init === null ? null : accessPathOf(lowerer, init, depth + 1);
|
||||
return aliased ?? { root, path: "" };
|
||||
}
|
||||
if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) {
|
||||
const base = accessPathOf(lowerer, expr.expression, depth + 1);
|
||||
return base === null ? null : { root: base.root, path: `${base.path}.${expr.name.text}` };
|
||||
}
|
||||
if (returnsReceiver(lowerer, expr)) {
|
||||
return accessPathOf(
|
||||
lowerer,
|
||||
(expr.expression as ts.PropertyAccessExpression).expression,
|
||||
depth + 1,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** A method call (`recv.m(...)`, not optional) whose declaration returns
|
||||
* `this`, so its value is the receiver itself. */
|
||||
function returnsReceiver(lowerer: Lowerer, expr: ts.Expression): expr is ts.CallExpression {
|
||||
if (
|
||||
!ts.isCallExpression(expr) ||
|
||||
expr.questionDotToken ||
|
||||
!ts.isPropertyAccessExpression(expr.expression) ||
|
||||
expr.expression.questionDotToken
|
||||
)
|
||||
return false;
|
||||
const declaration = lowerer.checker.getResolvedSignature(expr)?.declaration;
|
||||
return declaration?.type?.kind === ts.SyntaxKind.ThisType;
|
||||
}
|
||||
|
||||
/** A call of a child_process factory (spawn, fork, exec, execFile), each
|
||||
* of which returns a new ChildProcess. */
|
||||
function createsChildProcess(lowerer: Lowerer, call: ts.CallExpression): boolean {
|
||||
const declaration = lowerer.checker.getResolvedSignature(call)?.declaration;
|
||||
if (
|
||||
declaration === undefined ||
|
||||
!ts.isFunctionDeclaration(declaration) ||
|
||||
declaration.name === undefined ||
|
||||
!["spawn", "fork", "exec", "execFile"].includes(declaration.name.text)
|
||||
)
|
||||
return false;
|
||||
for (let node: ts.Node | undefined = declaration.parent; node; node = node.parent) {
|
||||
if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) {
|
||||
return node.name.text === "child_process" || node.name.text === "node:child_process";
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** The statement that always evaluates `call` when it runs: the call is
|
||||
* the statement's expression, a variable's initializer, or the receiver of
|
||||
* a method chain that is one of those. Null for calls under a condition,
|
||||
* an operator, an argument list or any other construct. */
|
||||
function unconditionalStatementOf(call: ts.CallExpression): ts.Statement | null {
|
||||
let node: ts.Node = call;
|
||||
for (;;) {
|
||||
const parent: ts.Node | undefined = node.parent;
|
||||
if (parent === undefined) return null;
|
||||
if (
|
||||
ts.isParenthesizedExpression(parent) ||
|
||||
ts.isNonNullExpression(parent) ||
|
||||
(ts.isPropertyAccessExpression(parent) &&
|
||||
parent.expression === node &&
|
||||
!parent.questionDotToken) ||
|
||||
(ts.isCallExpression(parent) && parent.expression === node && !parent.questionDotToken)
|
||||
) {
|
||||
node = parent;
|
||||
continue;
|
||||
}
|
||||
if (ts.isExpressionStatement(parent)) return parent;
|
||||
if (ts.isVariableDeclaration(parent) && parent.initializer === node) {
|
||||
const list = parent.parent;
|
||||
const statement = list?.parent;
|
||||
return list !== undefined &&
|
||||
ts.isVariableDeclarationList(list) &&
|
||||
statement !== undefined &&
|
||||
ts.isVariableStatement(statement)
|
||||
? statement
|
||||
: null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** The initializer of a symbol declared once as a `const` variable, else null. */
|
||||
function constInitializerOf(symbol: ts.Symbol): ts.Expression | null {
|
||||
const decls = symbol.declarations;
|
||||
if (decls.length !== 1) return null;
|
||||
const decl = decls[0]!;
|
||||
if (
|
||||
!ts.isVariableDeclaration(decl) ||
|
||||
!ts.isIdentifier(decl.name) ||
|
||||
decl.initializer === undefined ||
|
||||
(ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const) === 0
|
||||
)
|
||||
return null;
|
||||
return decl.initializer;
|
||||
}
|
||||
|
||||
/** Whether two resolved access paths name different objects: the same root
|
||||
* with different members, or two `const` roots each initialized by `new`
|
||||
* or a child_process factory (such as two `spawn()` results). Any other
|
||||
* call may return an object another binding already holds. */
|
||||
function distinctPaths(
|
||||
lowerer: Lowerer,
|
||||
a: { root: ts.Symbol; path: string },
|
||||
b: { root: ts.Symbol; path: string },
|
||||
): boolean {
|
||||
if (a.root === b.root) return a.path !== b.path;
|
||||
const fresh = (root: ts.Symbol): boolean => {
|
||||
let init = constInitializerOf(root);
|
||||
while (init !== null && ts.isParenthesizedExpression(init)) init = init.expression;
|
||||
if (init === null) return false;
|
||||
return (
|
||||
ts.isNewExpression(init) || (ts.isCallExpression(init) && createsChildProcess(lowerer, init))
|
||||
);
|
||||
};
|
||||
return fresh(a.root) && fresh(b.root);
|
||||
}
|
||||
|
||||
/** Whether an unknown-typed 'data' listener registered by `call` receives
|
||||
* strings: a `setEncoding` statement on the same receiver path precedes
|
||||
* the registration in its own block or an enclosing block of the same
|
||||
* function. With no setEncoding on that path in the function, chunks are
|
||||
* Buffers. Any other placement leaves the encoding state unclear, which
|
||||
* is reported. */
|
||||
function childStreamEncodedAt(
|
||||
lowerer: Lowerer,
|
||||
receiver: ts.Expression,
|
||||
call: ts.CallExpression,
|
||||
): boolean {
|
||||
const key = accessPathOf(lowerer, receiver);
|
||||
const statement: ts.Node = call.parent!;
|
||||
let scope: ts.Node | undefined = statement;
|
||||
while (scope && !ts.isFunctionLike(scope) && !ts.isSourceFile(scope)) scope = scope.parent;
|
||||
const unclear = (): never =>
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call.arguments.nodes[1]!,
|
||||
"untyped child-stream 'data' listeners whose setEncoding state is unclear at their registration sites (declare the chunk as 'Buffer' or 'string')",
|
||||
);
|
||||
if (key === null || scope === undefined) return unclear();
|
||||
const preceding = new Set<ts.Node>();
|
||||
for (let node: ts.Node = statement; node !== scope;) {
|
||||
const parent: ts.Node | undefined = node.parent;
|
||||
if (parent === undefined) break;
|
||||
if (ts.isBlock(parent) || ts.isSourceFile(parent)) {
|
||||
for (const sibling of parent.statements.nodes) {
|
||||
if (sibling === node) break;
|
||||
preceding.add(sibling);
|
||||
}
|
||||
}
|
||||
node = parent;
|
||||
}
|
||||
const receiverType = lowerer.checker.getNonNullableType(lowerer.typeOf(receiver));
|
||||
let before = false;
|
||||
let elsewhere = false;
|
||||
const visit = (node: ts.Node): void => {
|
||||
if (node !== scope && ts.isFunctionLike(node)) return;
|
||||
if (
|
||||
ts.isCallExpression(node) &&
|
||||
ts.isPropertyAccessExpression(node.expression) &&
|
||||
node.expression.name.text === "setEncoding"
|
||||
) {
|
||||
const target = node.expression.expression;
|
||||
const path = accessPathOf(lowerer, target);
|
||||
if (path !== null && path.root === key.root && path.path === key.path) {
|
||||
const statement = unconditionalStatementOf(node);
|
||||
if (statement !== null && preceding.has(statement)) before = true;
|
||||
else elsewhere = true;
|
||||
} else if (
|
||||
(path === null || !distinctPaths(lowerer, path, key)) &&
|
||||
lowerer.checker.isTypeAssignableTo(receiverType, lowerer.typeOf(target))
|
||||
) {
|
||||
// Another spelling that can hold this stream (a parameter, a `let`
|
||||
// copy, a call result): its setEncoding may be this stream's.
|
||||
elsewhere = true;
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
visit(scope);
|
||||
if (elsewhere) return unclear();
|
||||
return before;
|
||||
}
|
||||
|
||||
/** An inline 'exit'/'close' listener may declare `(code: number, signal:
|
||||
* string)`: TypeScript accepts it through ChildProcess's catch-all `on`
|
||||
* overload, while Node passes `number | null` and `Signals | null`. Those
|
||||
* parameters bind as checked-dynamic values (the socket 'data' listener
|
||||
* precedent), so the body observes the null Node passes; a typed use of a
|
||||
* null value throws the checked conversion's TypeError. Returns the
|
||||
* narrowed parameter symbols, empty when the declared types already
|
||||
* admit null. */
|
||||
function narrowedExitListenerParams(lowerer: Lowerer, listener: ts.Expression): ts.Symbol[] {
|
||||
if (!ts.isArrowFunction(listener) && !ts.isFunctionExpression(listener)) return [];
|
||||
if (isJsSourceFile(ts.getSourceFileOfNode(listener))) return [];
|
||||
const symbols: ts.Symbol[] = [];
|
||||
const wanted: readonly string[] = ["f64", "string"];
|
||||
listener.parameters.nodes.slice(0, 2).forEach((param, i) => {
|
||||
if (!ts.isIdentifier(param.name) || param.dotDotDotToken || param.initializer) return;
|
||||
if (param.questionToken) return;
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(param.name);
|
||||
if (!symbol || lowerer.runtimeOptionalBindingTypes.has(symbol)) return;
|
||||
if (lowerer.paramShape(param).type.kind !== wanted[i]) return;
|
||||
symbols.push(symbol);
|
||||
});
|
||||
return symbols;
|
||||
}
|
||||
|
||||
/** Lowers an inline listener with the given parameters bound as
|
||||
* checked-dynamic values typed `unknown` for the body. */
|
||||
function lowerWithCheckedParams(
|
||||
lowerer: Lowerer,
|
||||
listener: ts.Expression,
|
||||
symbols: readonly ts.Symbol[],
|
||||
): IrExpr {
|
||||
const previousImplicit = lowerer.implicitParamTypes;
|
||||
const implicit = new Map(previousImplicit ?? []);
|
||||
for (const symbol of symbols) {
|
||||
implicit.set(symbol, lowerer.checker.getUnknownType());
|
||||
lowerer.runtimeOptionalBindingTypes.set(symbol, DYN);
|
||||
}
|
||||
lowerer.implicitParamTypes = implicit;
|
||||
try {
|
||||
return lowerer.lowerExpr(listener);
|
||||
} finally {
|
||||
for (const symbol of symbols) lowerer.runtimeOptionalBindingTypes.delete(symbol);
|
||||
lowerer.implicitParamTypes = previousImplicit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ export function builtinClassAliasOwnsNoSlot(lowerer: Lowerer, symbol: ts.Symbol)
|
||||
if (decl === undefined || !ts.isVariableDeclaration(decl)) return false;
|
||||
if (builtinMemberRequireDecl(decl.name, decl.initializer)) return false;
|
||||
if (streamClassAliasDecl(lowerer, decl.name, decl.initializer)) return false;
|
||||
if (!isJsSourceFile(decl.getSourceFile())) return true;
|
||||
if (!isJsSourceFile(ts.getSourceFileOfNode(decl))) return true;
|
||||
let init = decl.initializer;
|
||||
while (init !== undefined && ts.isParenthesizedExpression(init)) init = init.expression;
|
||||
if (init === undefined) return true;
|
||||
@@ -157,9 +157,10 @@ export function runtimeClassValueOfType(lowerer: Lowerer, type: ts.Type): string
|
||||
const symbol = type.getSymbol();
|
||||
if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null;
|
||||
let info = lowerer.builtinEmitterInfoOf(symbol) ?? lowerer.builtinStreamInfoOf(symbol);
|
||||
if (!info && symbol.name.endsWith("Constructor")) {
|
||||
if (!info && ts.symbolName(symbol).endsWith("Constructor")) {
|
||||
for (const [irName, rec] of RUNTIME_ERROR_CLASSES) {
|
||||
if (`${rec.lib}Constructor` === symbol.name) info = lowerer.classes.get(irName) ?? null;
|
||||
if (`${rec.lib}Constructor` === ts.symbolName(symbol))
|
||||
info = lowerer.classes.get(irName) ?? null;
|
||||
}
|
||||
}
|
||||
return info?.valueCtorParams !== undefined ? info.def.name : null;
|
||||
|
||||
@@ -136,7 +136,7 @@ export function zlibStreamModeOf(
|
||||
if (alias !== null) return alias.kind === "zlib" ? alias.mode : null;
|
||||
const target =
|
||||
symbol.flags & ts.SymbolFlags.Alias ? lowerer.checker.getAliasedSymbol(symbol) : symbol;
|
||||
const mode = ZLIB_STREAM_MODES.get(target.name);
|
||||
const mode = ZLIB_STREAM_MODES.get(ts.symbolName(target));
|
||||
if (mode === undefined) return null;
|
||||
return target.declarations.some((d) => ts.isClassDeclaration(d) && isZlibFallbackDecl(lowerer, d))
|
||||
? mode
|
||||
@@ -144,7 +144,7 @@ export function zlibStreamModeOf(
|
||||
}
|
||||
|
||||
function isZlibFallbackDecl(lowerer: Lowerer, d: ts.Node): boolean {
|
||||
const file = d.getSourceFile();
|
||||
const file = ts.getSourceFileOfNode(d);
|
||||
if (!lowerer.isStdlibFile(file) || isNodeTypesPath(file.fileName)) return false;
|
||||
for (let node: ts.Node | undefined = d.parent; node; node = node.parent) {
|
||||
if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) {
|
||||
|
||||
@@ -170,7 +170,7 @@ export function fsConstantMemberRead(expr: ts.PropertyAccessExpression): IrExpr
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
return isJsSourceFile(expr.getSourceFile())
|
||||
return isJsSourceFile(ts.getSourceFileOfNode(expr))
|
||||
? read
|
||||
: { kind: "dynCheck", value: read, type: F64, loc };
|
||||
}
|
||||
|
||||
@@ -240,7 +240,9 @@ function cryptoKdfInput(lowerer: Lowerer, node: ts.Expression): IrExpr {
|
||||
const symbol = lowerer.typeOf(node).getSymbol();
|
||||
if (
|
||||
type?.kind === "bytes" ||
|
||||
(symbol?.name === "ArrayBuffer" && lowerer.isStdlibSymbol(symbol))
|
||||
(symbol !== undefined &&
|
||||
ts.symbolName(symbol) === "ArrayBuffer" &&
|
||||
lowerer.isStdlibSymbol(symbol))
|
||||
) {
|
||||
const value = lowerer.coerceInto(node, lowerer.lowerExpr(node), DYN);
|
||||
return { kind: "libCall", fn: "bytes.bufferSource", args: [value], type: BYTES_U8, loc };
|
||||
@@ -878,8 +880,8 @@ export function isCipherType(lowerer: Lowerer, type: ts.Type): boolean {
|
||||
const symbol = type.getAliasSymbol() ?? type.getSymbol();
|
||||
return (
|
||||
!!symbol &&
|
||||
CIPHER_TYPE_NAMES.has(symbol.name) &&
|
||||
symbol.declarations.some((d) => lowerer.isStdlibFile(d.getSourceFile()))
|
||||
CIPHER_TYPE_NAMES.has(ts.symbolName(symbol)) &&
|
||||
symbol.declarations.some((d) => lowerer.isStdlibFile(ts.getSourceFileOfNode(d)))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -898,7 +900,7 @@ function v8CalleeText(node: ts.Expression): string {
|
||||
if (ts.isPropertyAccessExpression(node))
|
||||
return `${v8CalleeText(node.expression)}.${node.name.text}`;
|
||||
if (ts.isCallExpression(node)) return `${v8CalleeText(node.expression)}(...)`;
|
||||
return node.getText();
|
||||
return ts.getTextOfNode(node);
|
||||
}
|
||||
|
||||
/** Calls on native Cipheriv/Decipheriv handles dispatch through the
|
||||
@@ -959,7 +961,7 @@ export function lowerCryptoHashMethodCall(
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: evaluated,
|
||||
result: nodeThrowExpr(1, "", `${access.getText()} is not a function`, DYN, loc),
|
||||
result: nodeThrowExpr(1, "", `${ts.getTextOfNode(access)} is not a function`, DYN, loc),
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
|
||||
@@ -58,9 +58,8 @@ export function lowerDateCall(
|
||||
}
|
||||
return { kind: "libCall", fn: "date.now", args: [], type: F64, loc };
|
||||
}
|
||||
// Date.parse uses the same bounded date-string parser as
|
||||
// new Date(dateString).getTime(); keep their NaN and TimeClip behavior
|
||||
// identical for the ISO timestamps used by CLI applications.
|
||||
// Date.parse uses the same date-string parser as new Date(dateString)
|
||||
// (V8's grammar in the runtime), so their NaN and TimeClip behavior agree.
|
||||
if (lowerer.stdlibGlobalMember(access, "Date") === "parse") {
|
||||
if (call.arguments.nodes.length !== 1 || ts.isSpreadElement(call.arguments.nodes[0]!)) {
|
||||
lowerer.noLowering(`Date.parse with ${call.arguments.nodes.length} arguments`, call);
|
||||
|
||||
@@ -9,7 +9,12 @@ import { varRef } from "../../../ir/build.js";
|
||||
function isStdlibDisposableStack(lowerer: Lowerer, node: ts.Expression): boolean {
|
||||
const type = lowerer.checker.getBaseTypeOfLiteralType(lowerer.typeOf(node));
|
||||
for (const symbol of [type.getSymbol(), type.getAliasSymbol()]) {
|
||||
if (symbol?.name === "DisposableStack" && lowerer.isStdlibSymbol(symbol)) return true;
|
||||
if (
|
||||
symbol !== undefined &&
|
||||
ts.symbolName(symbol) === "DisposableStack" &&
|
||||
lowerer.isStdlibSymbol(symbol)
|
||||
)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -27,9 +27,10 @@ const ASSERTION_ERROR_PROPERTIES: ReadonlySet<string> = new Set([
|
||||
export function isExecExceptionType(lowerer: Lowerer, t: ts.Type): boolean {
|
||||
const sym = t.getSymbol();
|
||||
return (
|
||||
sym?.name === "ExecException" &&
|
||||
sym !== undefined &&
|
||||
ts.symbolName(sym) === "ExecException" &&
|
||||
sym.declarations.some(
|
||||
(d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(d.getSourceFile()),
|
||||
(d) => ts.isInterfaceDeclaration(d) && lowerer.isStdlibFile(ts.getSourceFileOfNode(d)),
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -52,6 +53,20 @@ export function refuseExecExceptionCode(lowerer: Lowerer, node: ts.Node): never
|
||||
* declaration is not stdlib). Reads only: writes keep their fence (no
|
||||
* compiled program constructs an errno error). Null for non-error
|
||||
* receivers and non-stdlib members, so the chain keeps trying. */
|
||||
/** Error members with slots of their own: never read through the dynamic
|
||||
* node, even when an intersection restates them. */
|
||||
const ERROR_SLOT_MEMBERS = new Set([
|
||||
"code",
|
||||
"cause",
|
||||
"stack",
|
||||
"message",
|
||||
"name",
|
||||
"errno",
|
||||
"syscall",
|
||||
"path",
|
||||
"dest",
|
||||
]);
|
||||
|
||||
export function lowerErrorCodeProperty(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.PropertyAccessExpression,
|
||||
@@ -135,6 +150,33 @@ export function lowerErrorCodeProperty(
|
||||
declared,
|
||||
);
|
||||
}
|
||||
// A member an `Error & { ... }` intersection adds beyond Error's own and
|
||||
// the system-error metadata (`Object.assign(new Error(m), { status })`):
|
||||
// an own property of the error's checked-dynamic node, read at the
|
||||
// declared type (the intersection mapping admits only checkable types).
|
||||
if (
|
||||
recvT.className === "%Error" &&
|
||||
!ERROR_SLOT_MEMBERS.has(expr.name.text) &&
|
||||
!expr.questionDotToken &&
|
||||
!lowerer.isStdlibMember(expr) &&
|
||||
lowerer.typeOf(expr.expression).isIntersectionType()
|
||||
) {
|
||||
const declared = lowerer.mapTypeOf(lowerer.typeOf(expr));
|
||||
if (declared && declared.kind !== "void") {
|
||||
const receiver = lowerer.lowerExpr(expr.expression);
|
||||
const loc = locOf(expr);
|
||||
return lowerer.coerceToExpected(
|
||||
{
|
||||
kind: "dynKeyGet",
|
||||
value: lowerer.coerceToExpected(receiver, DYN),
|
||||
key: { kind: "strLit", value: expr.name.text, type: STRING, loc },
|
||||
type: DYN,
|
||||
loc,
|
||||
},
|
||||
declared,
|
||||
);
|
||||
}
|
||||
}
|
||||
// A program class under the receiver's class can carry its own `code`
|
||||
// (a declared field or a property added through an untyped reference),
|
||||
// which Node reads instead of the errno code: read through the error's
|
||||
|
||||
@@ -250,7 +250,7 @@ export function lowerFsLadderCall(
|
||||
);
|
||||
}
|
||||
if (bi.module !== "fs" && bi.module !== "fs/promises") return null;
|
||||
if (!isJsSourceFile(expr.getSourceFile())) return null;
|
||||
if (!isJsSourceFile(ts.getSourceFileOfNode(expr))) return null;
|
||||
const args = expr.arguments.nodes;
|
||||
if (args.some(ts.isSpreadElement)) return null;
|
||||
// Every ladder argument crosses as a dyn value; an argument that
|
||||
@@ -1564,7 +1564,7 @@ export function lowerDirentMethodCall(
|
||||
if (call.questionDotToken || access.questionDotToken) return null;
|
||||
if (!lowerer.isStdlibMember(access)) return null;
|
||||
const recvSym = lowerer.typeOf(access.expression).getSymbol();
|
||||
if (recvSym?.name !== "Dirent") return null;
|
||||
if (recvSym === undefined || ts.symbolName(recvSym) !== "Dirent") return null;
|
||||
const receiver = lowerer.lowerExpr(access.expression);
|
||||
if (receiver.type.kind !== "record") return null;
|
||||
const shape = lowerer.shapes.get(receiver.type.shapeId);
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "../../../ir/ir.js";
|
||||
|
||||
function isFsFallbackDecl(lowerer: Lowerer, d: ts.Node): boolean {
|
||||
const file = d.getSourceFile();
|
||||
const file = ts.getSourceFileOfNode(d);
|
||||
if (!lowerer.isStdlibFile(file) || isNodeTypesPath(file.fileName)) return false;
|
||||
for (let node: ts.Node | undefined = d.parent; node; node = node.parent) {
|
||||
if (ts.isModuleDeclaration(node) && ts.isStringLiteral(node.name)) {
|
||||
@@ -153,11 +153,13 @@ export function fsStreamClassOf(
|
||||
lowerer: Lowerer,
|
||||
symbol: ts.Symbol | null | undefined,
|
||||
): "ReadStream" | "WriteStream" | null {
|
||||
if (!symbol || (symbol.name !== "ReadStream" && symbol.name !== "WriteStream")) return null;
|
||||
if (!symbol) return null;
|
||||
const name = ts.symbolName(symbol);
|
||||
if (name !== "ReadStream" && name !== "WriteStream") return null;
|
||||
const declared = symbol.declarations.some(
|
||||
(d) => ts.isClassDeclaration(d) && isFsFallbackDecl(lowerer, d),
|
||||
);
|
||||
return declared ? symbol.name : null;
|
||||
return declared ? name : null;
|
||||
}
|
||||
|
||||
/** `new fs.ReadStream(path, options)` / `new fs.WriteStream(...)`: the
|
||||
|
||||
@@ -504,7 +504,7 @@ function jsonNullishArgument(lowerer: Lowerer, node: ts.Expression): boolean {
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(node);
|
||||
return (
|
||||
symbol !== undefined &&
|
||||
symbol.declarations.every((decl) => lowerer.isStdlibFile(decl.getSourceFile()))
|
||||
symbol.declarations.every((decl) => lowerer.isStdlibFile(ts.getSourceFileOfNode(decl)))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ export function isKeyObjectClass(lowerer: Lowerer, node: ts.Expression): boolean
|
||||
export function isKeyObjectValue(lowerer: Lowerer, node: ts.Expression): boolean {
|
||||
const type = lowerer.typeOf(node);
|
||||
const sym = type.getAliasSymbol() ?? type.getSymbol();
|
||||
return !!sym && sym.name === "KeyObject" && lowerer.isStdlibSymbol(sym);
|
||||
return !!sym && ts.symbolName(sym) === "KeyObject" && lowerer.isStdlibSymbol(sym);
|
||||
}
|
||||
|
||||
/** `v instanceof KeyObject`. */
|
||||
@@ -51,7 +51,7 @@ export function lowerKeyObjectCall(lowerer: Lowerer, call: ts.CallExpression): I
|
||||
kind: "dynInvoke",
|
||||
recv: lowerer.lowerExprExpecting(access.expression, DYN),
|
||||
method: access.name.text,
|
||||
calleeName: access.getText(),
|
||||
calleeName: ts.getTextOfNode(access),
|
||||
args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)),
|
||||
type: DYN,
|
||||
loc: locOf(call),
|
||||
|
||||
@@ -23,6 +23,17 @@ import { stripTypeCasts } from "./arguments.js";
|
||||
export function builtinImportOf(
|
||||
lowerer: Lowerer,
|
||||
ident: ts.Identifier,
|
||||
): { module: string; member: string } | null {
|
||||
const binding = builtinModuleBindingOf(lowerer, ident);
|
||||
// node:stream/web's exports ARE the web stream globals: its bindings
|
||||
// lower through the globals' own paths (stdlibGlobalNameOf resolves the
|
||||
// alias to the same name), not through module-member tables.
|
||||
return binding?.module === "stream/web" ? null : binding;
|
||||
}
|
||||
|
||||
function builtinModuleBindingOf(
|
||||
lowerer: Lowerer,
|
||||
ident: ts.Identifier,
|
||||
): { module: string; member: string } | null {
|
||||
const symbol =
|
||||
ts.isShorthandPropertyAssignment(ident.parent) && ident.parent.name === ident
|
||||
@@ -124,7 +135,7 @@ export function builtinImportOf(
|
||||
if (symbol !== undefined && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
|
||||
const target = lowerer.checker.getAliasedSymbol(symbol);
|
||||
const tdecl = target.declarations[0];
|
||||
if (tdecl !== undefined && tdecl.getSourceFile().isDeclarationFile) {
|
||||
if (tdecl !== undefined && ts.getSourceFileOfNode(tdecl).isDeclarationFile) {
|
||||
for (
|
||||
let p: ts.Node | undefined = tdecl.parent;
|
||||
p !== undefined && !ts.isSourceFile(p);
|
||||
@@ -132,7 +143,7 @@ export function builtinImportOf(
|
||||
) {
|
||||
if (ts.isModuleDeclaration(p) && ts.isStringLiteral(p.name)) {
|
||||
const module = canonicalBuiltinModule(p.name.text);
|
||||
if (module !== null) return { module, member: target.name };
|
||||
if (module !== null) return { module, member: ts.symbolName(target) };
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -286,14 +297,14 @@ export function createRequireCalleeFileOf(
|
||||
): ts.SourceFile | null {
|
||||
const e = stripTypeCasts(callee);
|
||||
if (ts.isCallExpression(e)) {
|
||||
return createRequireBaseCallOf(lowerer, e) !== null ? e.getSourceFile() : null;
|
||||
return createRequireBaseCallOf(lowerer, e) !== null ? ts.getSourceFileOfNode(e) : null;
|
||||
}
|
||||
if (!ts.isIdentifier(e)) return null;
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(e);
|
||||
const decl = symbol ? symbol.declarations[0] : undefined;
|
||||
if (!decl || !ts.isVariableDeclaration(decl) || decl.initializer === undefined) return null;
|
||||
return createRequireBindingDecl(lowerer, decl.name, decl.initializer)
|
||||
? decl.getSourceFile()
|
||||
? ts.getSourceFileOfNode(decl)
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
} from "./operating-system.js";
|
||||
import { lowerQuerystringParseCall, lowerQuerystringStringifyCall } from "./querystring.js";
|
||||
import { lowerFsGlobCallbackCall, lowerFsGlobSyncCall, lowerFspGlobCall } from "./fs-glob.js";
|
||||
import { lowerV8HeapStatisticsCall } from "./v8.js";
|
||||
|
||||
export function lowerBuiltinModuleCall(
|
||||
lowerer: Lowerer,
|
||||
@@ -56,7 +57,7 @@ export function lowerBuiltinModuleCall(
|
||||
fn: BuiltinModuleFn,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
const name = expr.expression.getText();
|
||||
const name = ts.getTextOfNode(expr.expression);
|
||||
if (bi.module === "util/types") {
|
||||
return lowerUtilTypeCall(lowerer, expr, bi, loc, name);
|
||||
}
|
||||
@@ -134,6 +135,9 @@ export function lowerBuiltinModuleCall(
|
||||
if (bi.module === "child_process" && (bi.member === "execFileSync" || bi.member === "execSync")) {
|
||||
return lowerer.lowerExecSyncCall(expr, bi.member === "execSync", loc);
|
||||
}
|
||||
if (bi.module === "v8" && bi.member === "getHeapStatistics") {
|
||||
return lowerV8HeapStatisticsCall(lowerer, expr, loc);
|
||||
}
|
||||
if (bi.module === "os" && bi.member === "networkInterfaces") {
|
||||
return lowerOsNetworkInterfacesCall(lowerer, expr, loc);
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ export function lowerImportMetaResolveCall(
|
||||
);
|
||||
}
|
||||
const result = resolveImportMetaRuntime(
|
||||
call.getSourceFile().fileName,
|
||||
ts.getSourceFileOfNode(call).fileName,
|
||||
specifier,
|
||||
lowerer.targetPlatform,
|
||||
lowerer.frontendServices,
|
||||
@@ -314,8 +314,8 @@ function requireResolverBaseFile(lowerer: Lowerer, receiver: ts.Expression): ts.
|
||||
const created = createRequireCalleeFileOf(lowerer, receiver);
|
||||
if (created !== null) return created;
|
||||
return lowerer.isStdlibGlobal(receiver, "require") &&
|
||||
!isNodeEsmFile(receiver.getSourceFile(), lowerer.program)
|
||||
? receiver.getSourceFile()
|
||||
!isNodeEsmFile(ts.getSourceFileOfNode(receiver), lowerer.program)
|
||||
? ts.getSourceFileOfNode(receiver)
|
||||
: null;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ export function isNavigatorGlobal(lowerer: Lowerer, node: ts.Expression): boolea
|
||||
if (!ts.isIdentifier(expr) || expr.text !== "navigator") return false;
|
||||
if (lowerer.isStdlibGlobal(expr, "navigator")) return true;
|
||||
return (
|
||||
isJsSourceFile(expr.getSourceFile()) && lowerer.checker.getSymbolAtLocation(expr) === undefined
|
||||
isJsSourceFile(ts.getSourceFileOfNode(expr)) &&
|
||||
lowerer.checker.getSymbolAtLocation(expr) === undefined
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,10 @@ export function netAddressClassOf(lowerer: Lowerer, node: ts.Expression): NetAdd
|
||||
function netAddressValueClass(lowerer: Lowerer, node: ts.Expression): NetAddressClass | null {
|
||||
const type = lowerer.typeOf(node);
|
||||
const sym = type.getAliasSymbol() ?? type.getSymbol();
|
||||
if (!sym || (sym.name !== "SocketAddress" && sym.name !== "BlockList")) return null;
|
||||
return lowerer.isStdlibSymbol(sym) ? sym.name : null;
|
||||
if (!sym) return null;
|
||||
const name = ts.symbolName(sym);
|
||||
if (name !== "SocketAddress" && name !== "BlockList") return null;
|
||||
return lowerer.isStdlibSymbol(sym) ? name : null;
|
||||
}
|
||||
|
||||
/** `new SocketAddress(options?)` and `new BlockList()`: the argument
|
||||
@@ -127,7 +129,7 @@ export function lowerNetAddressCall(lowerer: Lowerer, call: ts.CallExpression):
|
||||
kind: "dynInvoke",
|
||||
recv: lowerer.lowerExprExpecting(access.expression, DYN),
|
||||
method: name,
|
||||
calleeName: access.getText(),
|
||||
calleeName: ts.getTextOfNode(access),
|
||||
args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)),
|
||||
type: DYN,
|
||||
loc,
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from "../../../ir/ir.js";
|
||||
import { PERF_ENTRY_FIELDS, PERF_ENTRY_MARKER, perfEntryJsonShape } from "../../type-mapper.js";
|
||||
import { builtinImportOf } from "./module-bindings.js";
|
||||
import { lowerOptionalArgument } from "../optional-arguments.js";
|
||||
import { checkedIterableSpread } from "../checked-iterable-spread.js";
|
||||
|
||||
/** True for any spelling of node:perf_hooks' `performance` object — the
|
||||
@@ -134,6 +135,12 @@ export function lowerPerfHooksCall(
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if (
|
||||
access.name.text === "eventLoopUtilization" &&
|
||||
isPerfHooksPerformanceExpr(lowerer, access.expression)
|
||||
) {
|
||||
return lowerEventLoopUtilization(lowerer, expr);
|
||||
}
|
||||
const timeline = PERF_TIMELINE_CALLS.get(access.name.text);
|
||||
if (timeline && isPerfHooksPerformanceExpr(lowerer, access.expression)) {
|
||||
return lowerPerfTimelineCall(lowerer, expr, access.name.text, timeline, loc);
|
||||
@@ -142,7 +149,7 @@ export function lowerPerfHooksCall(
|
||||
lowerer.noLowering(
|
||||
`perf_hooks performance.${access.name.text}`,
|
||||
expr,
|
||||
"performance.now() (and its .bind(performance) function value), mark, measure, the getEntries family, clearMarks and clearMeasures are the lowered surface",
|
||||
"performance.now() (and its .bind(performance) function value), mark, measure, the getEntries family, clearMarks, clearMeasures and eventLoopUtilization are the lowered surface",
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -278,7 +285,7 @@ function lowerPerfArguments(lowerer: Lowerer, call: ts.CallExpression, loc: SrcL
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const spelling = arg.expression.getText();
|
||||
const spelling = ts.getTextOfNode(arg.expression);
|
||||
const items = checkedIterableSpread(lowerer, lowerer.lowerExpr(arg.expression), spelling, loc);
|
||||
stmts.push({
|
||||
kind: "exprStmt",
|
||||
@@ -513,3 +520,309 @@ export function fencePerfEntryKeyWalk(
|
||||
`${what} over performance entries (Node keeps their fields as prototype getters, so own-key walks see none; read the fields or call toJSON())`,
|
||||
);
|
||||
}
|
||||
|
||||
/* ── event-loop metrics: eventLoopUtilization and monitorEventLoopDelay ── */
|
||||
|
||||
/** The stdlib IntervalHistogram (monitorEventLoopDelay's result), by the
|
||||
* receiver's checker type. Its value is the runtime histogram handle. */
|
||||
function isIntervalHistogram(lowerer: Lowerer, node: ts.Expression): boolean {
|
||||
const symbol = lowerer.typeOf(node).getSymbol();
|
||||
return (
|
||||
symbol !== undefined &&
|
||||
ts.symbolName(symbol) === "IntervalHistogram" &&
|
||||
lowerer.isStdlibSymbol(symbol)
|
||||
);
|
||||
}
|
||||
|
||||
/** IntervalHistogram's numeric members, as perf.eldStat selectors. */
|
||||
const HISTOGRAM_STATS: ReadonlyMap<string, number> = new Map([
|
||||
["min", 0],
|
||||
["max", 1],
|
||||
["mean", 2],
|
||||
["stddev", 3],
|
||||
["count", 4],
|
||||
["exceeds", 5],
|
||||
]);
|
||||
|
||||
/** `h.min`, `h.max`, … on an IntervalHistogram: the runtime's current
|
||||
* statistic (Node's hdr_histogram values; nanoseconds). Null for every
|
||||
* other read. */
|
||||
export function lowerHistogramProperty(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.PropertyAccessExpression,
|
||||
): IrExpr | null {
|
||||
const which = HISTOGRAM_STATS.get(expr.name.text);
|
||||
if (
|
||||
which === undefined ||
|
||||
expr.questionDotToken ||
|
||||
!isIntervalHistogram(lowerer, expr.expression)
|
||||
)
|
||||
return null;
|
||||
const loc = locOf(expr);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "perf.eldStat",
|
||||
args: [lowerer.lowerExprExpecting(expr.expression, F64), numLit(which, loc)],
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
/** enable(), disable(), reset() and percentile(p) on an IntervalHistogram.
|
||||
* Other members (the BigInt twins, the percentiles Map) fence by name. */
|
||||
export function lowerHistogramMethodCall(
|
||||
lowerer: Lowerer,
|
||||
expr: ts.CallExpression,
|
||||
access: ts.PropertyAccessExpression,
|
||||
): IrExpr | null {
|
||||
if (access.questionDotToken || !isIntervalHistogram(lowerer, access.expression)) return null;
|
||||
const loc = locOf(expr);
|
||||
const name = access.name.text;
|
||||
const handle = (): IrExpr => lowerer.lowerExprExpecting(access.expression, F64);
|
||||
const args = expr.arguments.nodes;
|
||||
if ((name === "enable" || name === "disable" || name === "reset") && args.length === 0) {
|
||||
const fn =
|
||||
name === "enable"
|
||||
? "perf.eldEnable"
|
||||
: name === "disable"
|
||||
? "perf.eldDisable"
|
||||
: "perf.eldReset";
|
||||
return { kind: "libCall", fn, args: [handle()], type: name === "reset" ? VOID : BOOL, loc };
|
||||
}
|
||||
if (name === "percentile" && args.length === 1 && !ts.isSpreadElement(args[0]!)) {
|
||||
// Node validates first: NaN, p <= 0 and p > 100 throw ERR_OUT_OF_RANGE.
|
||||
const h = lowerer.declareHiddenLocal("%histogram", F64);
|
||||
const p = lowerer.declareHiddenLocal("%percentile", F64);
|
||||
const pRef = (): IrExpr => varRef(p.id, F64, loc);
|
||||
const cmp = (op: "!==" | "<=" | ">", left: IrExpr, right: IrExpr): IrExpr => ({
|
||||
kind: "bin",
|
||||
op,
|
||||
left,
|
||||
right,
|
||||
type: BOOL,
|
||||
loc,
|
||||
});
|
||||
const or = (left: IrExpr, right: IrExpr): IrExpr => ({
|
||||
kind: "logical",
|
||||
op: "||",
|
||||
left,
|
||||
right,
|
||||
type: BOOL,
|
||||
loc,
|
||||
});
|
||||
const outOfRange = or(
|
||||
or(cmp("!==", pRef(), pRef()), cmp("<=", pRef(), numLit(0, loc))),
|
||||
cmp(">", pRef(), numLit(100, loc)),
|
||||
);
|
||||
const message: IrExpr = {
|
||||
kind: "strConcat",
|
||||
left: {
|
||||
kind: "strLit",
|
||||
value: 'The value of "percentile" is out of range. It must be > 0 && <= 100. Received ',
|
||||
type: STRING,
|
||||
loc,
|
||||
},
|
||||
right: { kind: "toString", operand: pRef(), type: STRING, loc },
|
||||
type: STRING,
|
||||
loc,
|
||||
};
|
||||
const thrown: IrExpr = {
|
||||
kind: "libCall",
|
||||
fn: "error.nodeThrow",
|
||||
args: [
|
||||
numLit(2, loc),
|
||||
{ kind: "strLit", value: "ERR_OUT_OF_RANGE", type: STRING, loc },
|
||||
message,
|
||||
],
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
return {
|
||||
kind: "seqExpr",
|
||||
stmts: [
|
||||
{ kind: "varDecl", localId: h.id, init: handle(), loc },
|
||||
{ kind: "varDecl", localId: p.id, init: lowerer.lowerExprExpecting(args[0]!, F64), loc },
|
||||
],
|
||||
result: {
|
||||
kind: "ternary",
|
||||
cond: outOfRange,
|
||||
then: thrown,
|
||||
else_: {
|
||||
kind: "libCall",
|
||||
fn: "perf.eldPercentile",
|
||||
args: [varRef(h.id, F64, loc), pRef()],
|
||||
type: F64,
|
||||
loc,
|
||||
},
|
||||
type: F64,
|
||||
loc,
|
||||
},
|
||||
type: F64,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
lowerer.noLowering(
|
||||
`IntervalHistogram.${name}`,
|
||||
expr,
|
||||
"enable(), disable(), reset(), percentile(p) and the min, max, mean, stddev, count and exceeds readings are the lowered surface",
|
||||
);
|
||||
}
|
||||
|
||||
/** `monitorEventLoopDelay(options?)` from node:perf_hooks, in any import
|
||||
* spelling (`lowerBuiltinCall`): a new sampling histogram handle. The
|
||||
* options are an inline object literal; the runtime validates the
|
||||
* resolution as Node does (an integer of at least 1). */
|
||||
export function lowerMonitorEventLoopDelayCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
const loc = locOf(expr);
|
||||
const args = expr.arguments.nodes;
|
||||
let resolution: IrExpr = numLit(10, loc);
|
||||
if (args.length > 1 || args.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(
|
||||
"this monitorEventLoopDelay call form",
|
||||
expr,
|
||||
"pass at most one options object",
|
||||
);
|
||||
}
|
||||
if (args.length === 1) {
|
||||
const options = args[0]!;
|
||||
if (!ts.isObjectLiteralExpression(options)) {
|
||||
lowerer.noLowering(
|
||||
"monitorEventLoopDelay with a non-literal options object",
|
||||
options,
|
||||
"spell the options inline: monitorEventLoopDelay({ resolution: 20 })",
|
||||
);
|
||||
}
|
||||
for (const property of options.properties.nodes) {
|
||||
const shorthand = ts.isShorthandPropertyAssignment(property);
|
||||
if (
|
||||
(!shorthand && !ts.isPropertyAssignment(property)) ||
|
||||
!ts.isIdentifier(property.name) ||
|
||||
property.name.text !== "resolution"
|
||||
) {
|
||||
lowerer.noLowering(
|
||||
"monitorEventLoopDelay options other than resolution",
|
||||
property,
|
||||
"resolution is the one option",
|
||||
);
|
||||
}
|
||||
// An undefined resolution takes the default, as Node's destructuring.
|
||||
resolution = lowerOptionalArgument(
|
||||
lowerer,
|
||||
shorthand ? property.name : property.initializer,
|
||||
F64,
|
||||
numLit(10, loc),
|
||||
);
|
||||
}
|
||||
}
|
||||
return { kind: "libCall", fn: "perf.eldNew", args: [resolution], type: F64, loc };
|
||||
}
|
||||
|
||||
/** performance.eventLoopUtilization(u1?, u2?) — Node's
|
||||
* lib/internal/perf/event_loop_utilization.js over the runtime's loop
|
||||
* start and idle time, one lifted helper per arity. */
|
||||
function lowerEventLoopUtilization(lowerer: Lowerer, expr: ts.CallExpression): IrExpr {
|
||||
const loc = locOf(expr);
|
||||
const args = expr.arguments.nodes;
|
||||
const resultT = lowerer.mapTypeOf(lowerer.typeOf(expr));
|
||||
if (resultT?.kind !== "record" || args.length > 2 || args.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering("this performance.eventLoopUtilization call form", expr);
|
||||
}
|
||||
const helper = eluHelperOf(lowerer, args.length, resultT);
|
||||
return {
|
||||
kind: "call",
|
||||
callee: helper,
|
||||
args: args.map((arg) => lowerer.lowerExprExpecting(arg, resultT)),
|
||||
type: resultT,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
|
||||
function eluHelperOf(lowerer: Lowerer, arity: number, recT: IrType & { kind: "record" }): string {
|
||||
const name = `%perf.elu.${arity}`;
|
||||
if (lowerer.liftedFns.some((f) => f.name === name)) return name;
|
||||
const loc: SrcLoc = { file: "<builtin>", start: 0, end: 0 };
|
||||
const f64Local = (id: string) => ({ id, name: id, type: F64, mutable: false });
|
||||
const params = Array.from({ length: arity }, (_, i) => ({
|
||||
localId: `u${i + 1}.0`,
|
||||
name: `u${i + 1}`,
|
||||
type: recT as IrType,
|
||||
}));
|
||||
const locals = [
|
||||
...params.map((p) => ({ id: p.localId, name: p.name, type: recT as IrType, mutable: false })),
|
||||
f64Local("ls.0"),
|
||||
f64Local("idle.0"),
|
||||
f64Local("active.0"),
|
||||
];
|
||||
const ref = (id: string): IrExpr => varRef(id, F64, loc);
|
||||
const field = (param: string, f: string): IrExpr => ({
|
||||
kind: "recordGet",
|
||||
obj: varRef(param, recT, loc),
|
||||
shapeId: recT.shapeId,
|
||||
field: f,
|
||||
type: F64,
|
||||
loc,
|
||||
});
|
||||
const bin = (op: "+" | "-" | "/" | "<=", left: IrExpr, right: IrExpr): IrExpr => ({
|
||||
kind: "bin",
|
||||
op,
|
||||
left,
|
||||
right,
|
||||
type: op === "<=" ? BOOL : F64,
|
||||
loc,
|
||||
});
|
||||
const record = (idle: IrExpr, active: IrExpr, utilization: IrExpr): IrExpr => ({
|
||||
kind: "recordLit",
|
||||
fields: [
|
||||
{ name: "idle", value: idle },
|
||||
{ name: "active", value: active },
|
||||
{ name: "utilization", value: utilization },
|
||||
],
|
||||
type: recT,
|
||||
loc,
|
||||
});
|
||||
const result = (idle: IrExpr, active: IrExpr): IrStmt => ({
|
||||
kind: "return",
|
||||
value: record(idle, active, bin("/", active, bin("+", idle, active))),
|
||||
loc,
|
||||
});
|
||||
const decl = (id: string, init: IrExpr): IrStmt => ({ kind: "varDecl", localId: id, init, loc });
|
||||
const body: IrStmt[] = [
|
||||
decl("ls.0", { kind: "libCall", fn: "perf.loopStart", args: [], type: F64, loc }),
|
||||
{
|
||||
kind: "if",
|
||||
cond: bin("<=", ref("ls.0"), numLit(0, loc)),
|
||||
then: [
|
||||
{ kind: "return", value: record(numLit(0, loc), numLit(0, loc), numLit(0, loc)), loc },
|
||||
],
|
||||
else_: null,
|
||||
loc,
|
||||
},
|
||||
];
|
||||
if (arity === 2) {
|
||||
body.push(
|
||||
decl("idle.0", bin("-", field("u1.0", "idle"), field("u2.0", "idle"))),
|
||||
decl("active.0", bin("-", field("u1.0", "active"), field("u2.0", "active"))),
|
||||
result(ref("idle.0"), ref("active.0")),
|
||||
);
|
||||
} else {
|
||||
body.push(
|
||||
decl("idle.0", { kind: "libCall", fn: "perf.idleTime", args: [], type: F64, loc }),
|
||||
decl(
|
||||
"active.0",
|
||||
bin(
|
||||
"-",
|
||||
bin("-", { kind: "libCall", fn: "perf.now", args: [], type: F64, loc }, ref("ls.0")),
|
||||
ref("idle.0"),
|
||||
),
|
||||
),
|
||||
arity === 0
|
||||
? result(ref("idle.0"), ref("active.0"))
|
||||
: result(
|
||||
bin("-", ref("idle.0"), field("u1.0", "idle")),
|
||||
bin("-", ref("active.0"), field("u1.0", "active")),
|
||||
),
|
||||
);
|
||||
}
|
||||
lowerer.liftedFns.push({ name, params, returnType: recT, locals, body, loc });
|
||||
return name;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user