mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-29 07:04:09 +08:00
Merge pull request #895 from vercel-labs/ctate/make-scriptc-compilation-0c86a3
Make scriptc compilation native-only
This commit is contained in:
+2
-2
@@ -2,6 +2,7 @@ node_modules/
|
||||
.pnpm-store/
|
||||
# test-fixture packages are TEST DATA, committed on purpose
|
||||
!tests/diagnostics/npm-imports/node_modules/
|
||||
!tests/diagnostics/retired-engine-import-refusal/node_modules/
|
||||
!tests/fixtures/commander-calc/node_modules/
|
||||
!tests/fixtures/npm/node_modules/
|
||||
!tests/corpus/*/node_modules/
|
||||
@@ -25,7 +26,6 @@ output/
|
||||
/packages/runtime-*/artifacts/
|
||||
/packages/runtime-*/runtime-pack.json
|
||||
/packages/runtime-*/.runtime-pack-*
|
||||
!tests/fixtures/fetch/node_modules/eventsource-parser/dist/
|
||||
!tests/fixtures/npm/node_modules/*/dist/
|
||||
!tests/fixtures/npm/workspace/*/dist/
|
||||
!tests/fixtures/npm/cases/workspace-copied/members/*/dist/
|
||||
@@ -35,7 +35,7 @@ output/
|
||||
*.log
|
||||
.DS_Store
|
||||
packages/runtime/test/build/
|
||||
# lazily built engine archives for --dynamic (see packages/runtime/vendor/README.md)
|
||||
# Cached build-time evaluator archives (see packages/runtime/vendor/README.md)
|
||||
packages/runtime/vendor/.cache/
|
||||
# fs corpus scratch directories (created in the test cwd, removed on clean runs)
|
||||
tmp-*/
|
||||
|
||||
@@ -78,16 +78,15 @@ The Node.js compatibility system is an implementation inventory and planning too
|
||||
- Node's official documentation defines the public API hierarchy and stability metadata. Consult Node source when the docs do not fully specify behavior.
|
||||
- Running the pinned Node release is the behavior oracle for differential tests.
|
||||
- Compiler source and `packages/compiler/surface-manifest.json`, augmented by `internal/compatibility/static-support.json`, define static-native support.
|
||||
- `internal/compatibility/dynamic-support.json` defines dynamic-island modules, exports, members, explicit stubs, globals, and policies. It also generates the runtime builtin registry.
|
||||
- Test evidence determines positive behavioral claims. Source manifests alone do not prove Node-compatible behavior.
|
||||
|
||||
Treat static-native and dynamic-island compatibility independently. Support in one tier says nothing about the other, and `--dynamic` does not turn an unsupported typed call into a supported static call.
|
||||
Compatibility describes native compilation. There is no runtime JavaScript engine tier.
|
||||
|
||||
### Status contract
|
||||
|
||||
- `supported` — broadly implemented. This requires test evidence.
|
||||
- `partial` — a useful implemented subset. This requires test evidence, but evidence may cover an API family rather than every documented overload.
|
||||
- `refused` — a named compiler diagnostic or explicit dynamic throwing or rejecting stub exists. Never infer this status from absence.
|
||||
- `refused` — a named compiler diagnostic exists. Never infer this status from absence.
|
||||
- `not-implemented` — no implementation claim. Its verification basis decides whether it is ready to implement or must first be investigated.
|
||||
- `by-design` — intentionally outside a scriptc execution model. This requires an explicit implementation-owned architectural policy.
|
||||
- `unreviewed` — no compatibility subsystem owns a reliable classification. Do not treat this as unsupported or as an implementation ticket.
|
||||
@@ -107,7 +106,7 @@ Read a row's verification basis before acting:
|
||||
- `architectural-policy` — explicit intentional exclusion.
|
||||
- `unreviewed`, `not-applicable`, and `derived` retain the meanings above.
|
||||
|
||||
`pnpm node-compat:backlog` emits tier-specific work. Its actions are:
|
||||
`pnpm node-compat:backlog` emits native compilation work. Its actions are:
|
||||
|
||||
- `verify-gap` — investigate a registry gap first; this is not yet an implementation ticket.
|
||||
- `implement` — implement a declared or verified gap.
|
||||
@@ -124,7 +123,7 @@ Useful queue filters compose:
|
||||
```bash
|
||||
pnpm node-compat:backlog --action=implement --priority=high
|
||||
pnpm node-compat:backlog --action=verify-gap --chapter=assert
|
||||
pnpm node-compat:backlog --tier=dynamic --format=tsv
|
||||
pnpm node-compat:backlog --tier=static --format=tsv
|
||||
```
|
||||
|
||||
### Generated files
|
||||
@@ -136,11 +135,10 @@ Never hand-edit these files:
|
||||
- `internal/compatibility/generated/node-v24-backlog.json`
|
||||
- `docs/src/generated/node-v24-compatibility.json`
|
||||
- `docs/src/generated/node-v24-compatibility-meta.json`
|
||||
- `packages/runtime/src/scr_island_manifest.h`
|
||||
|
||||
Change compiler decision sources or the implementation-owned compatibility manifests, then regenerate. Use `pnpm manifest` when compiler decision tables change and `pnpm node-compat` whenever the Node pin, compatibility manifests, compiler surface manifest, or dynamic registry changes.
|
||||
Change compiler decision sources or the implementation-owned compatibility manifests, then regenerate. Use `pnpm manifest` when compiler decision tables change and `pnpm node-compat` whenever the Node pin, compatibility manifests, compiler surface manifest, changes.
|
||||
|
||||
The public docs artifact must not expose repository test paths, internal source identifiers, engineering evidence bookkeeping, documentation/metadata headings, command-line or configuration-only entries, or rows that are N/A in both tiers. Those remain in the complete internal census under `internal/compatibility/generated/`.
|
||||
The public docs artifact must not expose repository test paths, internal source identifiers, engineering evidence bookkeeping, documentation/metadata headings, command-line or configuration-only entries, or rows that are N/A in the native tier. Those remain in the complete internal census under `internal/compatibility/generated/`.
|
||||
|
||||
### Landing compatibility work
|
||||
|
||||
@@ -148,9 +146,9 @@ For each API family:
|
||||
|
||||
1. Start from the backlog action and verification basis. Verify every `registry-gap` before assuming code is missing.
|
||||
2. Read the pinned Node documentation, stability metadata, relevant Node source when needed, and existing scriptc implementation paths.
|
||||
3. Decide static-native and dynamic-island scope separately.
|
||||
3. Define the native compilation scope.
|
||||
4. Implement the smallest coherent behavior family. Preserve explicit refusals for unimplemented forms rather than silently diverging.
|
||||
5. Add differential evidence that executes under Node and as a compiled binary. Cover stdout, stderr, exit status, success behavior, and important error shapes. Dynamic-island work needs island-executed differential fixtures; a load-only export check does not prove behavior.
|
||||
5. Add differential evidence that executes under Node and as a compiled binary. Cover stdout, stderr, exit status, success behavior, and important error shapes. A load-only export check does not prove behavior.
|
||||
6. Update the implementation-owned source or manifest. Positive `supported` and `partial` mappings must name existing test evidence.
|
||||
7. Run `pnpm manifest` if compiler surface tables changed, then run `pnpm node-compat`. Never patch generated JSON or headers.
|
||||
8. Inspect the internal ledger, public artifact, and backlog diff. Confirm the intended API family changed and unrelated rows did not.
|
||||
|
||||
@@ -4,6 +4,8 @@ All notable changes to scriptc will be documented in this file.
|
||||
|
||||
## Unreleased
|
||||
|
||||
- **Compilation is native-only.** The `--dynamic` option and embedded JavaScript execution runtime have been removed. Eligible dependencies compile natively by default; unsupported packages receive build diagnostics. QuickJS remains only in the native compiler's isolated `comptime` helper and as standalone regex/Unicode libraries.
|
||||
|
||||
<!-- release:start -->
|
||||
|
||||
## 0.2.10
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
<a href="https://www.npmjs.com/package/scriptc"><img alt="npm downloads per month: scriptc" src="https://img.shields.io/npm/dm/scriptc.svg?style=for-the-badge&labelColor=000000&label=npm%20downloads" height="28"></a>
|
||||
</p>
|
||||
|
||||
scriptc compiles TypeScript and JavaScript to native executables and WebAssembly. It uses TypeScript's type information to compile supported code to native instructions. Static builds run without Node.js or a JavaScript engine.
|
||||
scriptc compiles TypeScript and JavaScript to native executables and WebAssembly. It uses TypeScript's type information to compile supported code to native instructions. Executables run without Node.js or a JavaScript engine.
|
||||
|
||||
For npm dependencies and `any`-typed code, enable `--dynamic` to include the embedded [quickjs-ng](https://github.com/quickjs-ng/quickjs) engine. Dependency JavaScript is included at build time.
|
||||
Application code and eligible npm dependencies compile to native instructions. Unsupported operations produce diagnostics; there is no runtime JavaScript engine fallback.
|
||||
|
||||
scriptc is experimental and supports a subset of JavaScript, TypeScript, and Node.js APIs. See the [limitations](https://scriptc.dev/docs/limitations) and [Node.js compatibility reference](https://scriptc.dev/compatibility) before using it for an existing project.
|
||||
|
||||
@@ -51,7 +51,7 @@ hello, scriptc
|
||||
|
||||
These examples use POSIX shell syntax. On Windows, build `hello.exe` and run it with `.\hello.exe`.
|
||||
|
||||
Use `scriptc coverage hello.ts` to check which operations compile statically, require dynamic execution, or are unsupported. See [coverage reports](https://scriptc.dev/docs/coverage) for details.
|
||||
Use `scriptc coverage hello.ts` to check which operations compile statically and which operations are unsupported. See [coverage reports](https://scriptc.dev/docs/coverage) for details.
|
||||
|
||||
## Use Node APIs
|
||||
|
||||
@@ -78,7 +78,7 @@ listening on http://localhost:8080
|
||||
|
||||
## Use npm packages
|
||||
|
||||
Pass `--dynamic` to embed an npm package's JavaScript in the executable. The result does not read `node_modules` at runtime.
|
||||
Eligible npm packages compile to native code by default. The executable does not read `node_modules` at runtime.
|
||||
|
||||
Create `cli.ts`:
|
||||
|
||||
@@ -90,12 +90,12 @@ console.log(pc.green("hello from scriptc"));
|
||||
|
||||
```console
|
||||
$ npm install picocolors
|
||||
$ scriptc build cli.ts --dynamic -o cli
|
||||
$ scriptc build cli.ts -o cli
|
||||
$ ./cli
|
||||
hello from scriptc
|
||||
```
|
||||
|
||||
Enabling `--dynamic` does not add support for unsupported statically typed calls. See [npm dependencies](https://scriptc.dev/docs/dependencies) for package resolution and the boundary between static and dynamic code.
|
||||
Package support is partial. See [npm dependencies](https://scriptc.dev/docs/dependencies) for eligibility, package resolution, and compilation diagnostics.
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@@ -12,10 +12,9 @@ Usage:
|
||||
scriptc build <file.ts|.js> [options] compile to an executable or source artifact
|
||||
scriptc run <file.ts|.js> [options] compile and run
|
||||
scriptc coverage <file.ts|.js> how much compiles statically, and why not
|
||||
scriptc coverage <file.ts|.js> --dynamic what a --dynamic build compiles, and what still blocks it
|
||||
scriptc coverage <file.ts|.js> --external-types <specifier=file.d.ts>
|
||||
type-resolve an embedder-provided module for analysis
|
||||
scriptc cache warm [runtime|tls|dynamic…] verify and load precompiled runtime families
|
||||
scriptc cache warm [runtime|tls…] verify and load precompiled runtime families
|
||||
for the current compiler/SDK/target
|
||||
```
|
||||
|
||||
@@ -54,7 +53,7 @@ fib.s
|
||||
|
||||
Different output kinds accumulate in `.scriptc/`; rebuilding a kind updates its file.
|
||||
|
||||
The IR and LLVM source outputs require only Node. On supported macOS, Linux, and Windows hosts, assembly and object outputs use the matching native helper installed with scriptc and do not invoke an external compiler, archiver, linker, or SDK. The object is a relocatable program object with undefined <code>scr_*</code> runtime symbols and a required <code>scr_runtime_abi_v13</code> marker, not a standalone library. External consumption is experimental and requires the exact runtime version reported by <code>--print=native-link-info</code>. That option still writes the object, performs no link, and prints a versioned JSON recipe with the target, <code>main</code> entry, installed precompiled runtime pack, FFI inputs, and system libraries. It never reports private scriptc cache paths. See <a href="/native-objects">Native Program Objects</a> for complete C-driver and direct-linker examples. <code>--emit=exe</code> is the default. LLVM builds emit the program object through the helper and link precompiled runtime objects; runtime development with AddressSanitizer compiles an instrumented C runtime.
|
||||
The IR and LLVM source outputs require only Node. On supported macOS, Linux, and Windows hosts, assembly and object outputs use the matching native helper installed with scriptc and do not invoke an external compiler, archiver, linker, or SDK. The object is a relocatable program object with undefined <code>scr_*</code> runtime symbols and a required <code>scr_runtime_abi_v14</code> marker, not a standalone library. External consumption is experimental and requires the exact runtime version reported by <code>--print=native-link-info</code>. That option still writes the object, performs no link, and prints a versioned JSON recipe with the target, <code>main</code> entry, installed precompiled runtime pack, FFI inputs, and system libraries. It never reports private scriptc cache paths. See <a href="/native-objects">Native Program Objects</a> for complete C-driver and direct-linker examples. <code>--emit=exe</code> is the default. LLVM builds emit the program object through the helper and link precompiled runtime objects; runtime development with AddressSanitizer compiles an instrumented C runtime.
|
||||
|
||||
## scriptc run
|
||||
|
||||
@@ -69,11 +68,11 @@ Note: `run` does not forward extra command-line arguments to the program. To pas
|
||||
|
||||
## scriptc coverage
|
||||
|
||||
Analyzes the program without producing a binary and reports, statement by statement, what compiles statically, what needs the embedded dynamic engine, and what blocks the rest. With `--dynamic` it answers a different question: what would a `--dynamic` build compile, and what still blocks it. Both forms are covered in depth in [Coverage Reports](/docs/coverage).
|
||||
Analyzes the program without producing a binary and reports, statement by statement, what compiles to native code and what blocks the rest. Reports are covered in depth in [Coverage Reports](/docs/coverage).
|
||||
|
||||
## scriptc cache warm
|
||||
|
||||
Verifies and reads the installed precompiled runtime objects and vendor archives. Pass one or more of `runtime`, `tls`, and `dynamic` to select those families. Ordinary builds need no runtime compilation or installation-time warming. With `--sanitize`, this command builds instrumented runtime objects using the development C toolchain.
|
||||
Verifies and reads the installed precompiled runtime objects and vendor archives. Pass one or more of `runtime` and `tls` to select those families. Ordinary builds need no runtime compilation or installation-time warming. With `--sanitize`, this command builds instrumented runtime objects using the development C toolchain.
|
||||
|
||||
## Options
|
||||
|
||||
@@ -90,9 +89,6 @@ Verifies and reads the installed precompiled runtime objects and vendor archives
|
||||
<dt><code>--fail-on <blockers|divergences></code></dt>
|
||||
<dd>Coverage only. Exit with status 1 when the entry has blockers that would fail a build, or with <code>divergences</code>, also when it has Node divergence warnings. See <a href="/docs/coverage#gating-builds-and-ci">Gating builds and CI</a>.</dd>
|
||||
|
||||
<dt><code>--dynamic</code></dt>
|
||||
<dd>Embed the JavaScript engine to run npm dependencies and supported <code>any</code>-typed code. Static compilation is the default; without this flag, dynamic-tier sites produce compilation errors. See <a href="/dependencies">npm Dependencies</a>.</dd>
|
||||
|
||||
<dt><code>--ffi <file></code></dt>
|
||||
<dd>Bind signature-only TypeScript declarations to native C ABI symbols and link the manifest's archive, object, and system-library inputs. See <a href="/ffi">Native FFI</a>.</dd>
|
||||
|
||||
@@ -112,10 +108,10 @@ Verifies and reads the installed precompiled runtime objects and vendor archives
|
||||
<dd>On Linux, when the program calls <code>process.exit()</code>, hand the release of its memory to a helper process so the parent sees the exit sooner. The kernel otherwise frees the whole address space before the exit is reported, which takes tens of milliseconds per gigabyte. Opt in only where the trade-offs are acceptable: for up to about 100 ms after the exit, the helper keeps the executable mapped (overwriting the binary in place fails with "text file busy"), its memory stays charged to the cgroup, and the teardown CPU time no longer appears in the parent's resource usage. Valid only for executable builds; it has no effect on other platforms.</dd>
|
||||
|
||||
<dt><code>--npm-static <pkg[,pkg…]|auto></code></dt>
|
||||
<dd>EXPERIMENTAL. Compile the named npm packages' shipped JS statically as program modules instead of embedding them for the engine (repeatable; <code>auto</code> opts in every eligible direct import). A package the preflight refuses falls back to the island with a coverage-report note. See <a href="/dependencies">npm Dependencies</a> for maturity notes.</dd>
|
||||
<dd>Explicitly select npm packages for native compilation (repeatable). Naming a package overrides the automatic declaration-surface eligibility check; <code>auto</code> uses the default automatic selection. Refused packages produce diagnostics. See <a href="/dependencies">npm Dependencies</a> for maturity notes.</dd>
|
||||
|
||||
<dt><code>--provenance-sources</code></dt>
|
||||
<dd>EXPERIMENTAL. Compile npm dependencies from their provenance-attested source, fetched at the attested commit, as static program modules; packages without a usable attestation keep the engine path (a note, never a failure).</dd>
|
||||
<dd>EXPERIMENTAL. Compile npm dependencies from their provenance-attested source, fetched at the attested commit, as static program modules; packages without a usable attestation keep the installed-source compilation path.</dd>
|
||||
|
||||
<dt><code>--external-types <specifier=file.d.ts></code></dt>
|
||||
<dd>Coverage only. Map an exact bare module specifier to a local declaration file supplied by an embedder. Repeat the option for multiple modules. The declaration supplies checker types so application coverage can continue; runtime imports and values remain explicit <code>SC1010</code> blockers. Relative paths resolve from the current working directory. Accepted files end in <code>.d.ts</code>, <code>.d.mts</code>, or <code>.d.cts</code>.</dd>
|
||||
|
||||
+25
-132
@@ -2,13 +2,11 @@
|
||||
title: "Coverage Reports"
|
||||
---
|
||||
|
||||
`scriptc coverage` analyzes a program without building an executable. It reports how many executable statements compile statically, which operations require the embedded JavaScript engine, and which operations are unsupported.
|
||||
`scriptc coverage` analyzes a program without building an executable. It reports how many executable statements compile to native code and groups unsupported operations by diagnostic code. Counts describe the program being analyzed, not JavaScript or Node.js support in general.
|
||||
|
||||
The counts apply to the program being analyzed, not to JavaScript or Node.js support in general. Dependency implementation statements are excluded from these totals.
|
||||
<span id="static-programs" />
|
||||
|
||||
## Static programs
|
||||
|
||||
The following program compiles entirely to native code:
|
||||
## A native program
|
||||
|
||||
```console
|
||||
$ cat hello.ts
|
||||
@@ -20,148 +18,43 @@ $ scriptc coverage hello.ts
|
||||
statements analyzed 2
|
||||
compile statically 2 (100%)
|
||||
|
||||
fully static — this program has no dynamic remainder.
|
||||
fully static — all analyzed statements compile to native code.
|
||||
```
|
||||
|
||||
A static executable does not include a JavaScript engine.
|
||||
<span id="report-fields" />
|
||||
|
||||
## Programs that require dynamic execution
|
||||
## Reading the report
|
||||
|
||||
This program imports an npm package whose JavaScript requires the embedded engine:
|
||||
|
||||
```console
|
||||
$ cat cli.ts
|
||||
import pc from "picocolors";
|
||||
|
||||
console.log(pc.green("hello"));
|
||||
|
||||
$ scriptc coverage cli.ts
|
||||
|
||||
statements analyzed 1
|
||||
compile statically 0 (0%)
|
||||
|
||||
runs with --dynamic 2 sites (embeds a JS engine, ~620KB — static stays the default)
|
||||
×1 SC2013 importing 'picocolors' requires the embedded dynamic engine, which this build does not include — the package's implementation runs there
|
||||
at cli.ts:1:1
|
||||
hint: build with --dynamic to run npm package code in the embedded engine (adds ~620KB to the binary), or try --npm-static auto to compile eligible packages' JavaScript statically; static builds never include the engine
|
||||
×1 SC2013 values from the 'picocolors' package run in the embedded dynamic engine, which this build does not include
|
||||
at cli.ts:3:13
|
||||
hint: build with --dynamic to run npm package code in the embedded engine (adds ~620KB to the binary), or try --npm-static auto to compile eligible packages' JavaScript statically; static builds never include the engine
|
||||
```
|
||||
|
||||
Each group collects the sites that share one root cause: the same code and message, with type arguments of generic instantiations collapsed, so one uncompilable declaration reported through several instantiations appears once. The `at` line lists the first sites in source order, and every group carries a hint.
|
||||
|
||||
The package must be installed before analysis; the quickstart shows [how to install and use it](/docs/quickstart#use-an-npm-dependency).
|
||||
|
||||
### Report fields
|
||||
Each blocker group collects the sites that share one root cause: the same code and message, with type arguments of generic instantiations collapsed, so one uncompilable declaration reported through several instantiations appears once. The `at` line lists the first sites in source order, and every group carries a hint.
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Meaning</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<thead><tr><th>Field</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>statements analyzed</code></td>
|
||||
<td>Executable statements in the program, excluding dependency implementation code.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>compile statically</code></td>
|
||||
<td>Statements that compile to native code.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>runs with --dynamic</code></td>
|
||||
<td>Sites that require the embedded engine. Without <code>--dynamic</code>, these sites are compilation errors. Site counts can differ from statement counts.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>blockers</code></td>
|
||||
<td>Unsupported operations, listed with a count, diagnostic code, reason, locations, and hint. Enabling <code>--dynamic</code> does not resolve these blockers.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>in unreached code</code></td>
|
||||
<td>Blockers in code the entry never reaches. A build never compiles that code, so these cannot fail it.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>differs from Node</code></td>
|
||||
<td>Sites that compile but can behave differently from Node.js. See <a href="#node-divergences">Node divergences</a>.</td>
|
||||
</tr>
|
||||
<tr><td><code>statements analyzed</code></td><td>Executable statements in application code, excluding dependency implementation code.</td></tr>
|
||||
<tr><td><code>compile statically</code></td><td>Statements that compile to native instructions.</td></tr>
|
||||
<tr><td><code>blockers</code></td><td>Unsupported operations with counts, diagnostic codes, reasons, locations, and hints.</td></tr>
|
||||
<tr><td><code>in unreached code</code></td><td>Blockers in code the entry never reaches. A build never compiles that code, so these cannot fail it.</td></tr>
|
||||
<tr><td><code>differs from Node</code></td><td>Sites that compile but can behave differently from Node.js. See <a href="#node-divergences">Node divergences</a>.</td></tr>
|
||||
<tr><td><code>npm package compilation</code></td><td>Native package compilation outcomes and refusal reasons.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Unreached code and deferred JavaScript operations are reported separately. A deferred operation can remain in a compiled package, but executing it raises a runtime error. A successful build therefore does not establish compatibility for every package API.
|
||||
|
||||
The statement totals include the bodies of functions whose signatures cannot compile. Their statements count as not static, so the percentage reflects how much of the program compiles.
|
||||
|
||||
## Analyze with --dynamic
|
||||
|
||||
Add `--dynamic` to include dynamic execution in the analysis:
|
||||
|
||||
```console
|
||||
$ cat cli.ts
|
||||
import pc from "picocolors";
|
||||
|
||||
console.log(pc.green("hello"));
|
||||
|
||||
$ scriptc coverage cli.ts --dynamic
|
||||
|
||||
statements analyzed 1
|
||||
compile statically 0 (0%)
|
||||
compile dynamically 1 (100%) (island sites — the embedded engine runs them)
|
||||
|
||||
builds with --dynamic — no remaining blockers (the island sites above run in the embedded engine).
|
||||
```
|
||||
|
||||
The final line indicates whether the program can build with `--dynamic`. Any remaining blockers are listed in the report.
|
||||
|
||||
## Embedded Node.js builtins
|
||||
|
||||
When embedded dependencies import Node.js builtin modules, the dynamic report lists each module, its shim status, and the package that imports it.
|
||||
|
||||
The [commander example](/docs/dependencies#use-a-package) uses the following source:
|
||||
|
||||
```console
|
||||
$ cat tool.ts
|
||||
import { Command } from "commander";
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name("greet")
|
||||
.argument("<name>", "who to greet")
|
||||
.option("-u, --upper", "use uppercase")
|
||||
.action((name: string, opts: { upper?: boolean }) => {
|
||||
const text = `hello, ${name}`;
|
||||
console.log(opts.upper ? text.toUpperCase() : text);
|
||||
});
|
||||
|
||||
program.parse();
|
||||
|
||||
$ scriptc coverage tool.ts --dynamic
|
||||
|
||||
statements analyzed 5
|
||||
compile statically 2 (40%)
|
||||
compile dynamically 3 (60%) (island sites — the embedded engine runs them)
|
||||
|
||||
embedded npm code imports Node builtins:
|
||||
node:child_process shimmed (commander)
|
||||
node:events shimmed (commander)
|
||||
node:fs shimmed (commander)
|
||||
node:path shimmed (commander)
|
||||
node:process shimmed (commander)
|
||||
node:util shimmed (commander)
|
||||
|
||||
builds with --dynamic — no remaining blockers (the island sites above run in the embedded engine).
|
||||
```
|
||||
|
||||
Shims provide supported Node.js APIs inside the embedded engine. They are separate implementations from Node.js's modules. See [npm Dependencies](/docs/dependencies#runtime-behavior) and the [Node.js compatibility reference](/compatibility) for their restrictions.
|
||||
<span id="programs-that-require-dynamic-execution" />
|
||||
<span id="analyze-with-dynamic" />
|
||||
<span id="embedded-nodejs-builtins" />
|
||||
|
||||
## Diagnostic codes
|
||||
|
||||
Coverage uses the same diagnostic codes as `scriptc build`. Common examples include:
|
||||
|
||||
- `SC2020`: a declared standard-library or Node.js API has no supported compiler implementation. Use the diagnostic's suggested alternative where available.
|
||||
- `SC2013`: a package import or package value requires the embedded engine. Enable `--dynamic`, remove the dependency, or evaluate the experimental [static package options](/docs/dependencies#static-package-compilation-experimental).
|
||||
- `SC1100`: a value crosses an unsupported `unknown`/`any` boundary. Narrow or cast the value as directed by the diagnostic.
|
||||
- `SC2020`: a declared standard-library or Node.js API has no supported compiler implementation.
|
||||
- `SC2012`: this API form cannot compile natively.
|
||||
- `SC2013`: a package cannot compile natively; inspect the package-specific reason and the [dependency guide](/docs/dependencies).
|
||||
- `SC1100`: a value crosses an unsupported checked-value boundary. Narrow or cast the value as directed by the diagnostic.
|
||||
|
||||
## Node divergences
|
||||
|
||||
@@ -257,11 +150,11 @@ An unchanged program reuses the previous verdict: coverage records the files and
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>stats</code></td>
|
||||
<td>Coverage only, absent after a preflight failure: the report's statement counts.</td>
|
||||
<td>Coverage only, absent after a preflight failure: the report's statement counts. <code>statementsDynamic</code> is always 0 and remains for schema compatibility.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>category</code></td>
|
||||
<td><code>invalid-source</code> (the program has a TypeScript or JSON error), <code>unsupported</code> (valid code scriptc cannot compile yet), <code>dynamic-only</code> (compiles with <code>--dynamic</code>), <code>divergence</code> (compiles, but can behave differently from Node.js), <code>environment</code> (project configuration, scriptc's type world, toolchain, or target), or <code>internal</code> (a compiler bug).</td>
|
||||
<td><code>invalid-source</code> (the program has a TypeScript or JSON error), <code>unsupported</code> (valid code scriptc cannot compile yet), <code>divergence</code> (compiles, but can behave differently from Node.js), <code>environment</code> (project configuration, scriptc's type world, toolchain, or target), or <code>internal</code> (a compiler bug).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>severity</code></td>
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
title: "npm Dependencies"
|
||||
---
|
||||
|
||||
scriptc can embed an npm package's JavaScript and execute it inside the compiled binary. Enable this behavior with `--dynamic`. Dependency code runs in [quickjs-ng](https://github.com/quickjs-ng/quickjs), while supported statically typed application code compiles to native code.
|
||||
|
||||
The embedded engine is also called the dynamic island. It has its own heap and job queue. Values crossing back into static types are validated at runtime.
|
||||
scriptc compiles eligible npm dependencies to native code alongside your application. Package support is partial: unsupported packages and operations receive diagnostics, and there is no runtime JavaScript engine fallback.
|
||||
|
||||
## Use a package
|
||||
|
||||
@@ -31,53 +29,33 @@ Install the dependency, then build and run the executable:
|
||||
|
||||
```console
|
||||
$ npm install commander
|
||||
$ scriptc build tool.ts --dynamic -o greet >/dev/null
|
||||
$ scriptc build tool.ts -o greet >/dev/null
|
||||
$ ./greet ada --upper
|
||||
HELLO, ADA
|
||||
```
|
||||
|
||||
The callback body compiles statically. The `commander` implementation runs in the embedded engine. Use [`scriptc coverage --dynamic`](/docs/coverage#analyze-with-dynamic) to inspect a program's static and dynamic statement counts and the builtin modules its dependencies import.
|
||||
Both the callback and the package implementation compile to native instructions. Use [coverage reports](/docs/coverage) to inspect package compilation and deferred operations.
|
||||
|
||||
## Package resolution and embedding
|
||||
<span id="package-resolution-and-embedding" />
|
||||
<span id="runtime-behavior" />
|
||||
<span id="engine-and-builtin-modules" />
|
||||
|
||||
The compiler resolves packages from `node_modules` using Node.js package resolution, including `package.json` export conditions. Shipped or installed type declarations provide the types used to check application code.
|
||||
## Package resolution
|
||||
|
||||
The package's JavaScript and its resolved imports are embedded at build time. The executable does not load `node_modules` at runtime. Deploy it to a machine compatible with the build's target platform.
|
||||
The compiler resolves dependencies from `node_modules` using Node.js package resolution, including `package.json` export conditions. It recursively compiles eligible dependencies. The executable does not load `node_modules` at runtime.
|
||||
|
||||
Packages can also expose TypeScript runtime entries (`.ts`, `.mts`, or `.cts`). The compiler emits JavaScript before collecting and embedding runtime imports, removing type annotations and type-only bindings. `import type` and `export type` declarations do not add runtime dependencies. See [Limitations](/docs/limitations#embedded-javascript-engine) for package source restrictions.
|
||||
Automatic selection requires readable runtime source and a package-owned declaration surface. Packages that fail eligibility or preflight receive a package-specific diagnostic. TypeScript workspace entries compile as program modules, including `.js` specifiers that resolve to `.ts` sources.
|
||||
|
||||
Workspace packages with a TypeScript executable entry compile as program modules, including `.js` import specifiers that resolve to `.ts` source. Workspace packages with JavaScript executable entries use the same policy as other JavaScript dependencies: execute them with `--dynamic`, or attempt static compilation with `--npm-static`.
|
||||
<span id="value-conversion" />
|
||||
<span id="static-package-compilation-experimental" />
|
||||
<span id="type-declarations-and-boundary-restrictions" />
|
||||
|
||||
## Runtime behavior
|
||||
## Explicit package selection
|
||||
|
||||
### Engine and builtin modules
|
||||
`--npm-static <pkg[,pkg…]|auto>` remains available for explicit selection. Naming a package overrides the automatic declaration-surface eligibility check, allowing an attempt to compile packages that rely on separately installed declarations or inferred JavaScript types. `auto` uses the default automatic selection. Neither form bypasses compilation checks.
|
||||
|
||||
Node.js builtin modules are provided by shims, some of which bridge to the native runtime. Each shim has a supported subset; the [coverage report](/docs/coverage#embedded-nodejs-builtins) lists the modules reached by the dependency graph, and the [compatibility reference](/compatibility) lists API support.
|
||||
|
||||
The engine is created lazily on first use, with one engine per process. A `--dynamic` build that does not use the engine emits the same code as a static build. Supported `any`-typed expressions also execute in the engine when `--dynamic` is enabled.
|
||||
|
||||
### Value conversion
|
||||
|
||||
Values passed between static and dynamic code are copied. Mutations to a copied value in one execution tier do not change the original in the other tier. This differs from ordinary JavaScript reference sharing.
|
||||
|
||||
Typed callback parameters and values returned to static code are validated. If the package supplies a value that does not match the expected type, the conversion throws a catchable `TypeError`. See [Limitations](/docs/limitations) for runtime differences and restrictions on values that can cross the boundary.
|
||||
|
||||
## Static package compilation (experimental)
|
||||
|
||||
`--npm-static <pkg[,pkg…]|auto>` attempts to compile packages' shipped JavaScript as static program modules, using their type declarations. Named packages are selected explicitly; `auto` selects eligible direct imports.
|
||||
|
||||
Support is partial. An accepted package may contain deferred operations: the build succeeds and coverage lists those sites, but executing an unsupported deferred operation raises a runtime error. A package rejected during preflight falls back to the embedded engine with a coverage note; that path requires `--dynamic`. Other unsupported forms can still prevent a static build.
|
||||
|
||||
Check the coverage report for each package before depending on static execution.
|
||||
An accepted package may contain deferred operations: the build succeeds and coverage lists those sites, but executing an unsupported deferred operation raises a runtime error. Review the coverage report and test the paths your application uses. See [Limitations](/docs/limitations) for native runtime differences.
|
||||
|
||||
## Provenance sources (experimental)
|
||||
|
||||
`--provenance-sources` attempts to compile a package from source at its npm provenance-attested commit. This allows TypeScript source to compile as TypeScript instead of compiling the published JavaScript.
|
||||
|
||||
Packages without usable attestations retain the embedded-engine path, with a note. This option has the same partial-support restrictions as `--npm-static`.
|
||||
|
||||
## Type declarations and boundary restrictions
|
||||
|
||||
Packages require shipped or installed type declarations. If TypeScript reports that it cannot find a declaration file, install the corresponding `@types` package or add a local declaration.
|
||||
|
||||
Class instances and promises cannot pass into `any` slots in the engine. Closures can cross as host functions only in supported forms. Unsupported conversions produce compilation diagnostics with guidance where available.
|
||||
`--provenance-sources` attempts to compile a package from source at its npm provenance-attested commit. This allows TypeScript source to compile as TypeScript instead of compiling the published JavaScript. Packages without usable attestations keep their installed-source compilation path, subject to the same native eligibility and compilation checks.
|
||||
|
||||
@@ -68,14 +68,14 @@ Without `-o`, artifacts are written to `.scriptc/` next to the input. Different
|
||||
|
||||
### External linking
|
||||
|
||||
Program objects define `main` and leave selected `scr_*` runtime functions undefined. A strong reference to `scr_runtime_abi_v13` checks runtime compatibility at link time.
|
||||
Program objects define `main` and leave selected `scr_*` runtime functions undefined. A strong reference to `scr_runtime_abi_v14` checks runtime compatibility at link time.
|
||||
|
||||
`--print=native-link-info` emits the program object and a JSON recipe containing the runtime pack and link order for external builds. This object ABI is experimental and requires an exact runtime version. See [Native Program Objects](/docs/native-objects) for examples.
|
||||
|
||||
## Native runtime
|
||||
|
||||
- **Memory:** Values use reference counting. Acyclic values are freed when their last reference is released, and a cycle collector runs at defined collection points. Some cycles involving opaque native values or the embedded engine can remain allocated.
|
||||
- **Concurrency:** `async`/`await` uses stackful fibers. The native event loop schedules microtasks, timers, and I/O, using kqueue on macOS and epoll on Linux. Static tasks and embedded-engine jobs use separate queues; their interleaving can differ from Node.js.
|
||||
- **Memory:** Values use reference counting. Acyclic values are freed when their last reference is released, and a cycle collector runs at defined collection points. Some cycles involving opaque native values can remain allocated.
|
||||
- **Concurrency:** `async`/`await` uses stackful fibers. The native event loop schedules microtasks, timers, and I/O, using kqueue on macOS and epoll on Linux.
|
||||
- **Networking:** Supported `net`, `http`, `https`, `tls`, `dgram`, and `dns` APIs use native implementations. TLS uses vendored mbedTLS.
|
||||
- **Numbers:** Values use JavaScript's double-precision representation. Number-to-string formatting uses shortest-roundtrip output and is tested against Node.js.
|
||||
- **Regular expressions:** The runtime uses QuickJS's regular-expression bytecode interpreter, linked into programs that use regex operations.
|
||||
@@ -90,11 +90,11 @@ The sanitizer lane runs the corpus with AddressSanitizer and a reference-count a
|
||||
|
||||
Test coverage provides evidence for the cases exercised. It does not establish complete compatibility with Node.js.
|
||||
|
||||
## Embedded JavaScript engine
|
||||
<span id="embedded-javascript-engine" />
|
||||
|
||||
`--dynamic` includes quickjs-ng for npm dependencies and supported `any`-typed code. The engine has its own heap and microtask queue. Conversions between static and dynamic values copy data, and conversions to static types validate it at runtime. A mismatched value raises a catchable `TypeError`.
|
||||
## JavaScript at build time
|
||||
|
||||
See [npm Dependencies](/docs/dependencies) for package execution, value conversion, and experimental static package options.
|
||||
Application and dependency code compile to native instructions. The compiler retains an isolated JavaScript evaluator for `comptime`; its results become native literals. The native compiler uses QuickJS for that build-time evaluator. The runtime retains QuickJS-derived regular-expression and Unicode libraries without linking the JavaScript execution engine into application binaries.
|
||||
|
||||
## Repository layout
|
||||
|
||||
@@ -116,7 +116,7 @@ See [npm Dependencies](/docs/dependencies) for package execution, value conversi
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>packages/runtime</code></td>
|
||||
<td>C runtime, reference counting and cycle collection, fibers, event loop, networking, and engine integration.</td>
|
||||
<td>C runtime, reference counting and cycle collection, fibers, event loop, networking,.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>packages/runtime-*</code></td>
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
title: "Introduction"
|
||||
---
|
||||
|
||||
scriptc compiles TypeScript and JavaScript to native executables and WebAssembly. It uses TypeScript's type information to compile supported code directly to native instructions. Static builds run without Node.js or a JavaScript engine.
|
||||
scriptc compiles TypeScript and JavaScript to native executables and WebAssembly. It uses TypeScript's type information to compile supported code directly to native instructions. Executables run without Node.js or a JavaScript engine.
|
||||
|
||||
For npm dependencies and `any`-typed code, enable `--dynamic` to include an embedded JavaScript engine. Use [coverage reports](/docs/coverage) to check which parts of a program compile statically, require the engine, or are unsupported.
|
||||
Eligible npm dependencies compile to native code alongside application code. Use [coverage reports](/docs/coverage) to check which operations compile and which are unsupported.
|
||||
|
||||
## Compile a program
|
||||
|
||||
@@ -34,34 +34,11 @@ $ ./fib
|
||||
|
||||
See [Quickstart](/docs/quickstart) for installation and platform requirements.
|
||||
|
||||
## Compilation modes
|
||||
<span id="compilation-modes" />
|
||||
|
||||
scriptc classifies operations into three tiers:
|
||||
## Native compilation
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tier</th>
|
||||
<th>Behavior</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Static</td>
|
||||
<td>Compiles to native code. This is the default mode.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dynamic</td>
|
||||
<td>Runs in the embedded quickjs-ng engine when <code>--dynamic</code> is enabled. This includes npm packages' shipped JavaScript and supported uses of <code>any</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Rejected</td>
|
||||
<td>Produces a compilation diagnostic with an <code>SC</code> code, source location, and rewrite hint where available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Enabling `--dynamic` does not add support for unsupported statically typed calls. The [npm dependencies guide](/docs/dependencies) explains package execution and the boundary between static and dynamic code.
|
||||
scriptc compiles supported TypeScript and JavaScript to native instructions. It has no runtime JavaScript engine fallback. The [npm dependencies guide](/docs/dependencies) explains package eligibility and diagnostics.
|
||||
|
||||
## Language and API support
|
||||
|
||||
@@ -72,7 +49,7 @@ Static compilation includes supported forms of:
|
||||
- Strings, BigInts, arrays, Maps, Sets, JSON, Math, typed arrays, and Buffers.
|
||||
- Node.js filesystem, path, process, child-process, crypto, timer, and networking APIs.
|
||||
|
||||
These features have API-specific restrictions. The [Node.js compatibility reference](/compatibility) lists static and dynamic API support, and [Limitations](/docs/limitations) describes compilation restrictions and runtime differences from Node.js.
|
||||
These features have API-specific restrictions. The [Node.js compatibility reference](/compatibility) lists native API support, and [Limitations](/docs/limitations) describes compilation restrictions and runtime differences from Node.js.
|
||||
|
||||
Programs are checked against the `es2025` standard library and scriptc's ambient declarations. Project declarations such as `@types/node` participate when available, and `tsconfig.json` controls checker strictness.
|
||||
|
||||
@@ -116,6 +93,6 @@ HELLO
|
||||
|
||||
- [Quickstart](/docs/quickstart): install the CLI and build an executable.
|
||||
- [CLI Reference](/docs/cli): commands and compiler options.
|
||||
- [Coverage Reports](/docs/coverage): identify dynamic operations and compilation blockers.
|
||||
- [Coverage Reports](/docs/coverage): identify compilation blockers.
|
||||
- [npm Dependencies](/docs/dependencies): use packages in compiled programs.
|
||||
- [How It Works](/docs/how-it-works): compiler stages, runtime, and testing.
|
||||
|
||||
@@ -4,7 +4,7 @@ title: "Limitations"
|
||||
|
||||
scriptc supports a subset of JavaScript, TypeScript, and Node.js APIs. This page describes current compilation limits and runtime behavior that differs from Node.js.
|
||||
|
||||
Use [`scriptc coverage`](/docs/coverage) to identify unsupported operations in your program. `scriptc build` reports compilation errors with `SC` codes, source locations, and rewrite hints where available. The [Node.js compatibility reference](/compatibility) lists API support separately for static code and code running in the embedded JavaScript engine. Enabling `--dynamic` does not add support for an unsupported statically typed call.
|
||||
Use [`scriptc coverage`](/docs/coverage) to identify unsupported operations in your program. `scriptc build` reports compilation errors with `SC` codes, source locations, and rewrite hints where available. The [Node.js compatibility reference](/compatibility) lists native API support.
|
||||
|
||||
## Native debugging
|
||||
|
||||
@@ -12,7 +12,7 @@ Executable builds with `--optimization=dev` support source breakpoints and nativ
|
||||
|
||||
Debugger values use native representations. Numbers and booleans display directly. Strings expose their `ScrStr` layout, including UTF-8 `data` and byte `len`; tagged unions expose their `tag` and `slot` fields. Other heap values and scalar bindings captured before initialization can appear as opaque pointers.
|
||||
|
||||
TypeScript expression evaluation, JavaScript object formatting, and stepping through code in the embedded JavaScript engine are not supported.
|
||||
TypeScript expression evaluation, JavaScript object formatting are not supported.
|
||||
|
||||
## Static compilation
|
||||
|
||||
@@ -24,16 +24,16 @@ The following sections describe supported forms and their remaining restrictions
|
||||
- **Class reflection:** `JSON.stringify` and `Object.keys` require every declared field to have a representation that can pass through runtime-checked storage. They raise an error for classes with opaque fields such as native Maps. Class fields, inherited accessors, and added properties retain identity through untyped values.
|
||||
- **Method values:** Borrowing a class method onto an unrelated object is unsupported. Detached methods that use `this`, builtin method values, abstract declarations, unspecialized generic methods, and incompatible override signatures produce runtime errors or compilation diagnostics. Supported class methods can be stored and passed as unbound functions. A field call supplies its containing instance as `this`; `call`, `apply`, and `bind` work when the signature supports runtime-checked conversion. Extracted overrides retain the selected method, and receiver validation accepts the declaring class and its subclasses.
|
||||
- **Loose equality:** Object-to-primitive `==` and `!=` comparisons are unsupported because `valueOf` and `toString` can execute user code. Convert the object explicitly before comparing. Comparisons between represented primitives and primitive unions support number, string, boolean, and BigInt coercions, including `x == null` and `x != null`.
|
||||
- **Resource disposal:** Top-level and switch-clause `using` declarations, disposal through an engine-held package value, and `DisposableStack` constructors are unsupported. Block- and function-scoped `using` and `await using` support compiled classes with zero-parameter disposal methods and supported FileHandle, timer, immediate, child-process, and readline handles. Array `for (using ... of ...)` loops also compile. Caught `SuppressedError` values expose `name` and `message`, but not `error` or `suppressed` payloads.
|
||||
- **Resource disposal:** Top-level and switch-clause `using` declarations, and `DisposableStack` constructors are unsupported. Block- and function-scoped `using` and `await using` support compiled classes with zero-parameter disposal methods and supported FileHandle, timer, immediate, child-process, and readline handles. Array `for (using ... of ...)` loops also compile. Caught `SuppressedError` values expose `name` and `message`, but not `error` or `suppressed` payloads.
|
||||
- **Async iteration:** Async `yield*` delegation, stored Web or Node iterator handles, and general structural async-iterator objects are unsupported. Typed async generators support lazy execution, `await`, direct `yield`, queued `.next()`/`.return()`/`.throw()` requests, and `for await`. `for await` also accepts Node `Readable` streams, Web `ReadableStream` values, and compiled class iterators whose zero-parameter `next()` returns a promise of a `{ value, done? }` record. Abrupt completion runs cleanup, including literal `destroyOnReturn` and `preventCancel` options. The `node:timers/promises` `setInterval(delay, value)` iterator compiles without AbortSignal options.
|
||||
- **Generics:** Generic functions declared inside other functions, generic class expressions, and generic methods requiring dynamic dispatch are unsupported. Generic classes can extend generic bases when each specialization has a concrete layout. 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.
|
||||
- **Function values:** Immutable aliases of overloaded program functions retain per-call overload resolution and identity. A function stored in a slot with a different signature remains the same function for equality, array searches, `length`, and own properties. Typed rest-parameter functions can be stored and called indirectly. Builtin function values have API-specific restrictions; see [Builtin function values](#builtin-function-values).
|
||||
- **Argument spreads:** Non-empty fixed tuples can spread into fixed signatures; arrays, Sets, and compiled class iterables can spread into typed rest parameters. Runtime-length spreads into fixed or rest parameters require types that support checked `unknown` conversion. Other JavaScript calls use the checked-value or embedded-engine path; unsupported static forms produce a diagnostic. Calls with no native parameter slot are unsupported for runtime-length spreads. Arguments evaluate once, in source order, before defaults run, including surplus arguments.
|
||||
- **Dynamic imports:** Computed specifiers, import attributes, CommonJS namespace synthesis, and imports of shipped JavaScript packages are unsupported in static code or require `--dynamic`. Literal `import()` of compiled ESM/TypeScript modules and supported Node builtins compiles without the engine. Evaluation is lazy and microtask-delayed, top-level `await` is honored, repeated imports share namespace identity, exported mutable bindings stay live, and namespace keys use Node's sorted order.
|
||||
- **Argument spreads:** Non-empty fixed tuples can spread into fixed signatures; arrays, Sets, and compiled class iterables can spread into typed rest parameters. Runtime-length spreads into fixed or rest parameters require types that support checked `unknown` conversion. Other JavaScript calls use the checked-value path; unsupported static forms produce a diagnostic. Calls with no native parameter slot are unsupported for runtime-length spreads. Arguments evaluate once, in source order, before defaults run, including surplus arguments.
|
||||
- **Dynamic imports:** Computed specifiers, import attributes, CommonJS namespace synthesis, and imports of shipped JavaScript packages are unsupported unless the target is a compiled ESM module. Literal `import()` of compiled ESM/TypeScript modules and supported Node builtins compiles without the engine. Evaluation is lazy and microtask-delayed, top-level `await` is honored, repeated imports share namespace identity, exported mutable bindings stay live, and namespace keys use Node's sorted order.
|
||||
|
||||
### Types and class definitions
|
||||
|
||||
- **BigInt:** `BigInt64Array`, `BigUint64Array`, BigInt filesystem stats, and native BigInts crossing into the embedded engine are unsupported. Arbitrary-precision BigInts otherwise support literals, operators, conversions, arrays, records, classes, closures, unions, promises, typed Map keys and Set elements, Buffer 64-bit reads/writes, and DataView 64-bit access. Native `unknown` and JavaScript `any` storage preserve BigInts, including checked Map keys and Set elements. JSON serialization throws a `TypeError` unless a replacer converts the value.
|
||||
- **BigInt:** `BigInt64Array`, `BigUint64Array`, BigInt filesystem stats are unsupported. Arbitrary-precision BigInts otherwise support literals, operators, conversions, arrays, records, classes, closures, unions, promises, typed Map keys and Set elements, Buffer 64-bit reads/writes, and DataView 64-bit access. Native `unknown` and JavaScript `any` storage preserve BigInts, including checked Map keys and Set elements. JSON serialization throws a `TypeError` unless a replacer converts the value.
|
||||
- **Interfaces:** Interfaces explicitly implemented by compiled classes retain live object identity through checked native values. Method-signature interfaces and callable-property interfaces structurally implemented by class methods use the same representation. Other data interfaces retain native record layouts and their copy-coercion rules. Generic methods require a statically resolved body; a `const` interface binding initialized directly with `new` can keep the concrete class for specialization. Unsupported member representations produce diagnostics.
|
||||
- **Record shapes:** Records use exact native layouts. Passing `{ a, b }` where `{ a }` is expected can produce `SC2002`. Accepted flows into a strict field-subset shape copy the record; see [Record identity and property order](#record-identity-and-property-order).
|
||||
- **Unions:** Narrow a union before an operation whose members require incompatible native representations, such as reading `u.length` on `string | string[]`. Some union-to-union widening and unions combining function and data members are unsupported. Same-representation methods on class unions, `map` and `forEach` on array unions, and shared or joinable field reads compile. `console.log(u)` supports printing a whole union.
|
||||
@@ -54,12 +54,12 @@ const results = await Promise.all(jobs); // number[]
|
||||
|
||||
A declaration in the standard library does not guarantee that an API compiles. Unsupported declared APIs produce `SC2020`, with alternatives where available. Check the [Node.js compatibility reference](/compatibility) for API-level support.
|
||||
|
||||
- **Text encoding:** `TextEncoder` and `TextDecoder` can be stored in fields, arguments, return values, arrays, and closures. Encoding supports UTF-8, including `encodeInto` into a Uint8Array or Buffer view. Codec properties expose the canonical encoding and decoder flags. Decoding accepts recognized WHATWG labels, including runtime labels and omitted or undefined encodings. Literal constructor options support `fatal` and `ignoreBOM`; literal decode options support streaming UTF-8, UTF-16, and single-byte encodings. Other legacy streaming encodings, runtime options objects, inspection, JSON/engine conversion, and module-scope declarations using `var` or lacking an initializer are unsupported. Use initialized `let` or `const` bindings for stored codecs. Byte arrays passed from native code into embedded packages are copied; writes inside the package do not update the original native array.
|
||||
- **Text encoding:** `TextEncoder` and `TextDecoder` can be stored in fields, arguments, return values, arrays, and closures. Encoding supports UTF-8, including `encodeInto` into a Uint8Array or Buffer view. Codec properties expose the canonical encoding and decoder flags. Decoding accepts recognized WHATWG labels, including runtime labels and omitted or undefined encodings. Literal constructor options support `fatal` and `ignoreBOM`; literal decode options support streaming UTF-8, UTF-16, and single-byte encodings. Other legacy streaming encodings, runtime options objects, inspection, JSON conversion, and module-scope declarations using `var` or lacking an initializer are unsupported. Use initialized `let` or `const` bindings for stored codecs.
|
||||
- **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.
|
||||
- **CommonJS metadata:** The module graph is fixed at build time. Cache deletion or reloading, metadata writes, `module.paths` mutation, and `require.extensions` are unsupported. Read-only access to `module.id`, `filename`, `path`, `paths`, `loaded`, `isPreloading`, `parent`, `children`, `require.main`, and `require.cache` lookup/enumeration compiles.
|
||||
- **ESM metadata:** Native `import.meta.url`, `filename`, `dirname`, and `main` support dot access, statically known keys, and destructuring selected fields. Storing or mutating the metadata object, rest bindings, runtime keys, and stored `resolve` callbacks are unsupported. Native `import.meta.resolve()` requires a literal specifier. Embedded file modules provide mutable metadata with escaped file URLs and module-relative resolution. Embedded module instances distinguished by URL query strings or fragments are unsupported.
|
||||
- **ESM metadata:** Native `import.meta.url`, `filename`, `dirname`, and `main` support dot access, statically known keys, and destructuring selected fields. Storing or mutating the metadata object, rest bindings, runtime keys, and stored `resolve` callbacks are unsupported. Native `import.meta.resolve()` requires a literal specifier. Module instances distinguished by URL query strings or fragments are unsupported.
|
||||
- **Date:** Date unions, year/month constructors, setters, statically typed TypeScript identity comparisons, thrown Date values, and locale/string formatters are unsupported. Zero-argument and single number/string constructors, storage and passing, `getTime`, `valueOf`, `toISOString`, calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile. JavaScript Dates retain identity through checked storage and support Date-copy construction and JSON serialization. See [Dates and timezones](#dates-and-timezones) for parsing restrictions.
|
||||
- **URL:** Non-ASCII or percent-escaped hosts are unsupported. Absolute inputs and relative inputs with runtime string or URL bases, including optional bases, compile. `URL.canParse`, `URL.parse`, `toString()`, and `toJSON()` compile; the factories use the same parser as construction, returning `false` or `null` for rejected inputs while preserving conversion errors. `href`, `protocol`, `username`, `password`, `host`, `hostname`, `port`, `pathname`, `search`, and `hash` support assignment in native and embedded code. Invalid component writes are ignored except for invalid `href` values, which throw; credentials are percent-encoded and ports accept valid leading digits. Native URL assignments require statically known property keys; `+=` accepts primitive operands, while other assignment operators and object operands for `+=` are refused. `origin` is read-only. `searchParams` is a live view whose identity survives query and URL replacement. Opaque paths retain their input bytes without WHATWG C0 encoding.
|
||||
- **URL:** Non-ASCII or percent-escaped hosts are unsupported. Absolute inputs and relative inputs with runtime string or URL bases, including optional bases, compile. `URL.canParse`, `URL.parse`, `toString()`, and `toJSON()` compile; the factories use the same parser as construction, returning `false` or `null` for rejected inputs while preserving conversion errors. `href`, `protocol`, `username`, `password`, `host`, `hostname`, `port`, `pathname`, `search`, and `hash` support assignment in native code. Invalid component writes are ignored except for invalid `href` values, which throw; credentials are percent-encoded and ports accept valid leading digits. Native URL assignments require statically known property keys; `+=` accepts primitive operands, while other assignment operators and object operands for `+=` are refused. `origin` is read-only. `searchParams` is a live view whose identity survives query and URL replacement. Opaque paths retain their input bytes without WHATWG C0 encoding.
|
||||
- **Filesystem flags:** `fs.openSync` supports inline bitwise OR expressions of `O_RDONLY`, `O_WRONLY`, `O_RDWR`, `O_CREAT`, `O_EXCL`, `O_NOFOLLOW`, `O_NONBLOCK`, `O_TRUNC`, and `O_APPEND`, with an optional creation mode. Computed numeric flags are unsupported because bit values vary by target. `fstatSync`, `fchmodSync`, `fsyncSync`, and `linkSync` support direct calls. On Windows, numeric opens with `O_NOFOLLOW` and calls to `fchmodSync` throw `ENOSYS`; use a POSIX target if you require these operations.
|
||||
- **FileHandle and descriptor I/O:** Static reads and writes accept Buffer and Uint8Array windows, numeric positions, and UTF-8 strings. FileHandle reads also accept options records and allocate a buffer when omitted. Vector I/O, truncation, syncing, and permission changes compile. FileHandle streams, `readLines`, ownership changes, timestamps, and bigint Stats remain unsupported. Dynamic descriptor I/O accepts other ArrayBuffer views and Buffer encodings; read positions outside the safe integer range are refused. File operations complete synchronously before their callbacks or promise continuations. Descriptor permission changes on Windows throw `ENOSYS`.
|
||||
- **Crypto:** Hashing, HMAC, PBKDF2, and HKDF support MD5, SHA-1, SHA-224, SHA-256, SHA-384, and SHA-512. Scrypt supports its default parameters, numeric options, aliases, and memory limit. Both KDFs accept strings, Buffers, numeric typed arrays, DataViews, and fixed-length ArrayBuffers. KeyObject inputs, other digests, and cipher or public-key operations remain unsupported. Native derivations finish before their deferred callbacks, so computation blocks the caller.
|
||||
@@ -86,9 +86,9 @@ Some builtins can be stored and passed as functions:
|
||||
|
||||
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` and UTF-8 `fs.readFile`.
|
||||
|
||||
`util.styleText` supports standard formats and aliases, arrays of formats, nested ANSI resets, and explicit color enabling with `{ validateStream: false }`. Native calls accept standard process streams; custom native streams and mutations to `util.inspect.colors` remain unsupported. Embedded calls use the stream and terminal metadata supplied by the embedded runtime.
|
||||
`util.styleText` supports standard formats and aliases, arrays of formats, nested ANSI resets, and explicit color enabling with `{ validateStream: false }`. Native calls accept standard process streams; custom native streams and mutations to `util.inspect.colors` remain unsupported.
|
||||
|
||||
`util.isDeepStrictEqual` supports primitives, ordinary records and arrays, cycles, enumerable string and symbol properties, and byte views in static code. Its optional third argument skips prototype and constructor comparisons. Structural comparisons of native Maps, Sets, class instances, Errors, proxies, accessor properties, and function-valued own `constructor` properties remain refused. Comparisons running inside the embedded JavaScript engine support a wider range of object and collection types.
|
||||
`util.isDeepStrictEqual` supports primitives, ordinary records and arrays, cycles, enumerable string and symbol properties, and byte views in static code. Its optional third argument skips prototype and constructor comparisons. Structural comparisons of native Maps, Sets, class instances, Errors, proxies, accessor properties, and function-valued own `constructor` properties remain refused.
|
||||
|
||||
Synchronous and promise filesystem APIs with trailing arguments that affect behavior remain call-only. This includes writers, open, directory creation/removal, metadata readers, and directory readers. Passing them as values could discard modes, flags, encodings, or options during function-signature adaptation.
|
||||
|
||||
@@ -102,7 +102,7 @@ Workers support `online`, `message`, `error`, and `exit` events, `postMessage`,
|
||||
|
||||
SharedArrayBuffer storage is fixed-length. Atomics supports integer `load`, `store`, `exchange`, `compareExchange`, `add`, `sub`, `and`, `or`, and `xor` over 8-, 16-, and 32-bit integer arrays. `wait` and `notify` use Int32Array views; BigInt atomics, `waitAsync`, and growable buffers are unsupported. `terminate` unwinds at runtime checkpoints, including loops and parked atomic waits, without running JavaScript catch or finally blocks. Blocking native calls finish before the next checkpoint.
|
||||
|
||||
Workers are unsupported in dynamic executables, WASI, compiled libraries, and executables that use foreign-thread native callbacks. Ordinary callbacks that run on their owning script thread remain supported. Shared byte storage cannot expose unmanaged FFI pointers; copy it into an ordinary Buffer before passing it to native code.
|
||||
Workers are unsupported in WASI, compiled libraries, and executables that use foreign-thread native callbacks. Ordinary callbacks that run on their owning script thread remain supported. Shared byte storage cannot expose unmanaged FFI pointers; copy it into an ordinary Buffer before passing it to native code.
|
||||
|
||||
### Child-process workers
|
||||
|
||||
@@ -114,7 +114,7 @@ Parent and child channels support `send(message, callback?)`, `connected`, `disc
|
||||
|
||||
### Untyped values
|
||||
|
||||
Most uses of `any` require `--dynamic`; otherwise they produce `SC2011`. Use `unknown` with a checked cast for native storage. Native Map and Set type arguments may use `any`, with the same checked storage as `unknown`, without an engine.
|
||||
Supported uses of `any` use native checked values. Operations without native lowering produce diagnostics such as `SC2011`. Use `unknown` with a checked cast where an operation needs a concrete type. Native Map and Set type arguments may use `any`, with the same checked storage as `unknown`, without an engine.
|
||||
|
||||
Locals, parameters, and returns can carry untyped values; record and tuple fields can hold `unknown`. JavaScript instance fields can also hold native checked values. Bare fields start as `undefined`, and redeclaring a field with the same checked-value type resets it in initialization order. Explicit TypeScript `any`/`unknown` class fields, static checked-value fields, statically typed array elements, and union members are unsupported in static builds.
|
||||
|
||||
@@ -210,7 +210,7 @@ Local calendar getters use the operating system's timezone database. Historical
|
||||
|
||||
`WeakMap` and `WeakSet` accept checked native objects and arrays, functions, numeric typed arrays, ArrayBuffers, native weak collections, and typed records, arrays, tuples, and classes that preserve identity through checked storage. Copied values, other native handles, and symbol keys are unsupported. Constructor seeds support arrays; custom iterators and detached methods are unsupported. Weak collections do not retain their keys, but values that refer back to a key can keep it alive until deletion or collection release.
|
||||
|
||||
Memory uses reference counting. Acyclic values are freed deterministically, and cycles are collected at defined collection points. Native checked objects, arrays, Sets, and captured closures participate in cycle collection. Cycles through opaque native capsules or across the static/engine boundary can remain allocated.
|
||||
Memory uses reference counting. Acyclic values are freed deterministically, and cycles are collected at defined collection points. Native checked objects, arrays, Sets, and captured closures participate in cycle collection. Cycles through opaque native capsules can remain allocated.
|
||||
|
||||
### Foreign-thread callbacks
|
||||
|
||||
@@ -232,37 +232,27 @@ Uncaught exceptions print `Uncaught <value>` to stderr instead of Node.js's sour
|
||||
|
||||
Errors carry no stack frames. `console.log` and `util.inspect` print an Error in Node's stackless form, `[TypeError: message]`, where Node prints `TypeError: message` followed by stack frames; the two agree when `Error.stackTraceLimit` is `0`.
|
||||
|
||||
## Embedded JavaScript engine
|
||||
<span id="embedded-javascript-engine" />
|
||||
|
||||
`--dynamic` uses [quickjs-ng](https://github.com/quickjs-ng/quickjs) to run embedded dependency code. Node builtin modules are provided by shims with their own supported APIs; the [coverage report](/docs/coverage) lists the builtins used by the embedded dependency graph.
|
||||
## npm package compilation
|
||||
|
||||
In a TypeScript `--dynamic` build, `const host: any = globalThis` or `(globalThis as any).TextDecoder` accesses the engine's global object. Its Node globals use those shims even without an embedded npm package. For example, the engine's `TextDecoder` supports UTF-8 labels only. Optional capabilities such as `fetch` are installed only when the build links their bridge.
|
||||
|
||||
Embedded Web Streams support default readable and writable streams, their reader, writer, and controller APIs, count and byte-length queuing strategies, transform streams, and UTF-8 encoder/decoder streams. Writable sinks serialize queued writes and wait for asynchronous start/close/abort callbacks. Transforms honor readable-side backpressure and asynchronous start/transform/flush/cancel callbacks. Default-stream `pipeTo()` and `pipeThrough()` honor `preventClose`, `preventAbort`, `preventCancel`, and AbortSignal, propagate cleanup errors, and release their locks when piping settles. Default-stream `tee()` shares chunks between two branches and cancels the source only when both branches cancel. Byte streams, BYOB readers, and stream transfer remain unsupported. These embedded APIs do not broaden the separate static Web Streams surface.
|
||||
|
||||
Static tasks drain before engine jobs run at event-loop quiescence. A static `await` racing a package promise can therefore resolve in a different order from Node.js.
|
||||
|
||||
Top-level `await` is unsupported in embedded ESM packages. It is supported in the program's own ESM graph and packages compiled through `--npm-static`.
|
||||
|
||||
TypeScript runtime sources in embedded packages are emitted in isolation using the package's module format. Package compiler configuration is not applied, and type-only dependencies must use explicit `import type`, `export type`, or inline `type` specifiers. Declaration files and TSX runtime entries are rejected during the build.
|
||||
|
||||
`--npm-static` and `--provenance-sources` are experimental. See [npm Dependencies](/docs/dependencies) for their current restrictions.
|
||||
Packages compile to native code. Runtime JavaScript evaluation, `eval`, and `new Function` are unsupported. A package that depends on unsupported execution features must be rewritten or replaced. Review [npm Dependencies](/docs/dependencies) for automatic eligibility and deferred-operation restrictions.
|
||||
|
||||
## Process events
|
||||
|
||||
Static signal listeners resolve names on the target platform, including `SIGWINCH` on POSIX. `on`, `once`, `off`, and `removeListener` accept computed signal names and deliver the signal name and number. Computed names for other event families and chained registrations are unsupported. Windows registration is limited to CRT signals; WASI has no OS signals.
|
||||
|
||||
Literal `uncaughtException` and `uncaughtExceptionMonitor` registrations compile without the JavaScript engine. Handled exceptions resume queued native work; monitors alone do not suppress failure. Error objects and primitive exceptions can reach listeners, while arbitrary native objects remain subject to exception-value restrictions. Exception capture callbacks and embedded-engine exception handling are unsupported.
|
||||
Literal `uncaughtException` and `uncaughtExceptionMonitor` registrations compile without the JavaScript engine. Handled exceptions resume queued native work; monitors alone do not suppress failure. Error objects and primitive exceptions can reach listeners, while arbitrary native objects remain subject to exception-value restrictions. Exception capture callbacks are unsupported.
|
||||
|
||||
## Native addons
|
||||
|
||||
Node-API (N-API) and V8 `.node` addons require Node's addon runtime, which scriptc does not embed. Direct `createRequire` calls to local addons are rejected at compile time. Embedded npm packages receive a catchable `ERR_DLOPEN_FAILED` when loading an addon, allowing packages with JavaScript fallbacks to use them.
|
||||
Node-API (N-API) and V8 `.node` addons require Node's addon runtime, which scriptc does not embed. Direct `createRequire` calls to local addons are rejected at compile time. Packages that require an addon must use a compilable JavaScript implementation or a native FFI replacement.
|
||||
|
||||
Use [Native FFI](/docs/ffi#replacing-a-node-api-addon) to call a plain C ABI function linked from an object or static archive. The guide includes a replacement for a small Node-API helper.
|
||||
|
||||
## WASI target
|
||||
|
||||
The `wasm32-wasi` executable target supports async/await, promises, generators, timers, portable event-loop work, stdin/readline, filesystem callbacks and promises, and the embedded JavaScript engine.
|
||||
The `wasm32-wasi` executable target supports async/await, promises, generators, timers, portable event-loop work, stdin/readline, filesystem callbacks and promises.
|
||||
|
||||
WASI Preview 1 does not provide sockets, process spawning, OS signals, network interfaces, or filesystem notifications. Networking/fetch, child processes, signal APIs, `os.networkInterfaces()`, and `fs.watch` are rejected before linking with `SC3002`. `--sanitize` and native FFI are unavailable.
|
||||
|
||||
@@ -280,4 +270,4 @@ Numbers use JavaScript's double-precision representation. Integer inference and
|
||||
|
||||
Wasm reactors require a host-provided WASI Preview 1 adapter. A reactor can run supported three.js CPU operations and send animated geometry, material state, and resource lifecycle notifications to an external web application through declared imports. The application supplies DOM, graphics, media, audio, and decoder bindings, including the renderer.
|
||||
|
||||
Library mode excludes promises, async functions, timers, and the embedded JavaScript engine. See [WebAssembly Modules](/docs/wasm) for the embedding contract.
|
||||
Library mode excludes promises, async functions, timers. See [WebAssembly Modules](/docs/wasm) for the embedding contract.
|
||||
|
||||
@@ -8,7 +8,7 @@ title: "Native Program Objects"
|
||||
|
||||
The external object ABI is **experimental**. Its `scr_*` function and data surface may change before 1.0, so consumers must use the exact runtime-pack version reported by the same compiler installation. This is stricter than semver compatibility.
|
||||
|
||||
The object intentionally leaves its selected runtime symbols undefined. It also holds a strong reference to `scr_runtime_abi_v13`, which the matching runtime defines. Linking an object against a runtime with another ABI marker fails at link time with the missing versioned symbol; it cannot become a latent runtime incompatibility.
|
||||
The object intentionally leaves its selected runtime symbols undefined. It also holds a strong reference to `scr_runtime_abi_v14`, which the matching runtime defines. Linking an object against a runtime with another ABI marker fails at link time with the missing versioned symbol; it cannot become a latent runtime incompatibility.
|
||||
|
||||
## Machine-readable link information
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ $ file fib.exe
|
||||
fib.exe: PE32+ executable (console) x86-64, for MS Windows
|
||||
```
|
||||
|
||||
The full corpus runs in a differential lane against Windows Node, including `--dynamic` programs and `child_process`. The Windows fixture lanes also exercise real loopback traffic through `net`, `http`, `https`, `tls`, `http2`, `dgram`, and `dns`, plus the native `fetch` implementation (including redirects, streaming bodies, compression, cancellation, and proxy handling).
|
||||
The full corpus runs in a differential lane against Windows Node, including `child_process`. The Windows fixture lanes also exercise real loopback traffic through `net`, `http`, `https`, `tls`, `http2`, `dgram`, and `dns`, plus the native `fetch` implementation (including redirects, streaming bodies, compression, cancellation, and proxy handling).
|
||||
|
||||
Windows executable builds use the console subsystem by default. Set <code>--windows-subsystem=gui</code> for an application that owns its own window so Windows does not open an extra console window. This selects the PE subsystem at link time.
|
||||
|
||||
@@ -65,7 +65,7 @@ The full library-mode feature set applies: profile-declared exports and ABI entr
|
||||
|
||||
Set <code>SCRIPTC_TARGET=wasm32-wasi</code> to produce a standalone <code>.wasm</code> module. Without <code>-o</code>, <code>scriptc build hello.ts</code> writes <code>.scriptc/hello.wasm</code>. Compilation does not require Node. <code>scriptc run</code> requires Node.js 24 or newer on <code>PATH</code> to host the module with Node's WASI implementation, inherits stdio and environment, preopens the current working directory as <code>/</code>, and maps the host platform's temporary directory to the guest's <code>/tmp</code>. A built module can run in another WASI Preview 1 host instead.
|
||||
|
||||
WASI is a production LLVM target with the same language tiers as the native targets. Its 32-bit LLVM ABI supports collections, closures, exceptions, classes, checked dynamic values, async/await, promises, synchronous and asynchronous generators, timers, stdin/readline events, process-exit listeners, filesystem callbacks and promises, and the <code>--dynamic</code> QuickJS island.
|
||||
WASI is a production LLVM target with the same native compilation model as the native targets. Its 32-bit LLVM ABI supports collections, closures, exceptions, classes, checked dynamic values, async/await, promises, synchronous and asynchronous generators, timers, stdin/readline events, process-exit listeners, filesystem callbacks and promises.
|
||||
|
||||
The remaining executable boundary is host capability, not language coverage. WASI Preview 1 has no portable socket, process-spawn, OS-signal, network-interface, or filesystem-notification APIs. Networking/fetch, child processes, signal APIs, <code>os.networkInterfaces()</code>, and <code>fs.watch</code> therefore fail before linking with diagnostic <code>SC3002</code>. <code>--sanitize</code> and native FFI are unavailable too. Filesystem behavior is bounded by the host's preopens, and process/OS introspection follows WASI's reduced model.
|
||||
|
||||
@@ -91,17 +91,17 @@ The remaining executable boundary is host capability, not language coverage. WAS
|
||||
<tr>
|
||||
<td>macOS arm64 / x64</td>
|
||||
<td>native host helper + runtime pack</td>
|
||||
<td>Full surface, <code>--dynamic</code>, sanitizer lane (external C route)</td>
|
||||
<td>Native surface, sanitizer lane (external C route)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Linux arm64 / x86_64</td>
|
||||
<td>native host helper + runtime pack; <code>SCRIPTC_TARGET=<arch>-linux-musl</code> selects musl</td>
|
||||
<td>Static and dynamic surfaces incl. servers, TLS, fetch, fs.watch, and child_process; executable linking needs the matching libc/sysroot</td>
|
||||
<td>Native surface incl. servers, TLS, fetch, fs.watch, and child_process; executable linking needs the matching libc/sysroot</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Windows x86_64</td>
|
||||
<td>native x64 MSVC helper + runtime pack</td>
|
||||
<td>Static and dynamic surfaces incl. servers, TLS, fetch, and child_process; executable linking uses Zig’s Windows sysroot</td>
|
||||
<td>Native surface incl. servers, TLS, fetch, and child_process; executable linking uses Zig’s Windows sysroot</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>iOS arm64 (device and simulator)</td>
|
||||
@@ -116,7 +116,7 @@ The remaining executable boundary is host capability, not language coverage. WAS
|
||||
<tr>
|
||||
<td>WebAssembly / WASI Preview 1</td>
|
||||
<td>matching host helper + WASI runtime pack</td>
|
||||
<td>Production LLVM target; full language and dynamic tiers, bounded by WASI P1 host capabilities</td>
|
||||
<td>Production LLVM target; native language surface, bounded by WASI P1 host capabilities</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -46,7 +46,7 @@ $ ./hello scriptc
|
||||
hello, scriptc
|
||||
```
|
||||
|
||||
This program compiles statically, so the executable does not include a JavaScript engine. The shell examples use POSIX syntax; on Windows, build `hello.exe` and invoke it with `.\hello.exe`.
|
||||
The executable does not include a JavaScript engine. The shell examples use POSIX syntax; on Windows, build `hello.exe` and invoke it with `.\hello.exe`.
|
||||
|
||||
## Check compilation support
|
||||
|
||||
@@ -62,10 +62,10 @@ $ scriptc coverage hello.ts
|
||||
statements analyzed 2
|
||||
compile statically 2 (100%)
|
||||
|
||||
fully static — this program has no dynamic remainder.
|
||||
fully static — all analyzed statements compile to native code.
|
||||
```
|
||||
|
||||
For programs that need dynamic execution or contain unsupported operations, the report lists the affected sites and diagnostic codes. See [Coverage Reports](/docs/coverage).
|
||||
For programs that contain unsupported operations, the report lists the affected sites and diagnostic codes. See [Coverage Reports](/docs/coverage).
|
||||
|
||||
## Use an npm dependency
|
||||
|
||||
@@ -77,16 +77,16 @@ import pc from "picocolors";
|
||||
console.log(pc.green("hello"));
|
||||
```
|
||||
|
||||
Install the package and enable the embedded engine with `--dynamic`:
|
||||
Install the package, then compile its JavaScript along with your application:
|
||||
|
||||
```console
|
||||
$ npm install picocolors
|
||||
$ scriptc build cli.ts --dynamic -o demo >/dev/null
|
||||
$ scriptc build cli.ts -o demo >/dev/null
|
||||
$ ./demo
|
||||
hello
|
||||
```
|
||||
|
||||
The package's JavaScript is embedded at build time. The executable does not read `node_modules` at runtime. See [npm Dependencies](/docs/dependencies) for package resolution, type declarations, and runtime behavior.
|
||||
The package's JavaScript compiles to native code at build time. The executable does not read `node_modules` at runtime. See [npm Dependencies](/docs/dependencies) for package resolution, type declarations, and runtime behavior.
|
||||
|
||||
## Next steps
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ $ scriptc coverage hello.ts
|
||||
statements analyzed 2
|
||||
compile statically 2 (100%)
|
||||
|
||||
fully static — this program has no dynamic remainder.`;
|
||||
fully static — all analyzed statements compile to native code.`;
|
||||
|
||||
const tiers = [
|
||||
{
|
||||
@@ -32,8 +32,8 @@ const tiers = [
|
||||
body: "Supported TypeScript and JavaScript operations compile to native code. Static executables run without Node.js or a JavaScript engine.",
|
||||
},
|
||||
{
|
||||
title: "Runs dynamically",
|
||||
body: "Enable --dynamic to run npm packages and supported any-typed code in an embedded JavaScript engine. Values converted to static types are checked at runtime.",
|
||||
title: "Native npm dependencies",
|
||||
body: "Eligible npm dependencies compile alongside your application. Checked values preserve runtime type checks without an embedded JavaScript engine.",
|
||||
},
|
||||
{
|
||||
title: "Rejected at compile time",
|
||||
@@ -48,11 +48,11 @@ const points = [
|
||||
},
|
||||
{
|
||||
title: "Native executables",
|
||||
body: "Build a program into an executable for a supported target. JavaScript dependencies used with --dynamic are embedded at build time.",
|
||||
body: "Build a program into an executable for a supported target. Eligible JavaScript dependencies compile to native code at build time.",
|
||||
},
|
||||
{
|
||||
title: "Compilation coverage",
|
||||
body: "Coverage reports show static and dynamic statement counts and list compilation blockers for the program being analyzed.",
|
||||
body: "Coverage reports show native statement counts and list compilation blockers for the program being analyzed.",
|
||||
},
|
||||
{
|
||||
title: "Differentially tested",
|
||||
@@ -145,8 +145,7 @@ export default function Home() {
|
||||
<h2 className="text-heading-24 text-gray-1000">Check compilation support</h2>
|
||||
<p className="mt-3 text-copy-14 text-gray-900">
|
||||
Use {siteName} coverage to analyze a program before building it. The report
|
||||
identifies operations that compile statically, require the embedded engine,
|
||||
or remain unsupported.
|
||||
identifies operations that compile natively and explains unsupported forms.
|
||||
</p>
|
||||
<Link
|
||||
href="/docs/coverage"
|
||||
|
||||
@@ -41,7 +41,6 @@ type Row = {
|
||||
anchor: string;
|
||||
nodeStability: NodeStability;
|
||||
static: Tier;
|
||||
dynamic: Tier;
|
||||
};
|
||||
type Chapter = { slug: string; title: string; entries: number; apiEntries: number };
|
||||
type Snapshot = { nodeVersion: string; verificationBases: Record<VerificationBasis, Verification>; chapters: Chapter[]; rows: Row[] };
|
||||
@@ -111,7 +110,7 @@ function StabilityBadge({ stability }: { stability: NodeStability }) {
|
||||
);
|
||||
}
|
||||
|
||||
function statusCounts(rows: Row[], tier: "static" | "dynamic") {
|
||||
function statusCounts(rows: Row[], tier: "static") {
|
||||
return rows.reduce<Record<Status, number>>(
|
||||
(counts, row) => {
|
||||
if (row.scope !== "api" || row.depth === 0) return counts;
|
||||
@@ -198,7 +197,6 @@ export function NodeCompatibilityMatrix() {
|
||||
const [loadError, setLoadError] = useState(false);
|
||||
const [query, setQuery] = useState("");
|
||||
const [staticFilter, setStaticFilter] = useState<"all" | Status>("all");
|
||||
const [dynamicFilter, setDynamicFilter] = useState<"all" | Status>("all");
|
||||
const [expanded, setExpanded] = useState<Set<string>>(() => new Set());
|
||||
|
||||
const rows = snapshot?.rows ?? [];
|
||||
@@ -208,8 +206,7 @@ export function NodeCompatibilityMatrix() {
|
||||
const chapterTitles = useMemo(() => new Map(chapters.map((item) => [item.slug, item.title])), [chapters]);
|
||||
const base = `https://nodejs.org/docs/v${snapshot?.nodeVersion ?? "24.15.0"}/api/`;
|
||||
const staticCounts = useMemo(() => statusCounts(rows, "static"), [rows]);
|
||||
const dynamicCounts = useMemo(() => statusCounts(rows, "dynamic"), [rows]);
|
||||
const visibleStatuses = useMemo(() => statuses.filter((status) => staticCounts[status] > 0 || dynamicCounts[status] > 0), [dynamicCounts, staticCounts]);
|
||||
const visibleStatuses = useMemo(() => statuses.filter((status) => staticCounts[status] > 0), [staticCounts]);
|
||||
const tree = useMemo(() => buildTree(rows), [rows]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -232,12 +229,11 @@ export function NodeCompatibilityMatrix() {
|
||||
}, []);
|
||||
|
||||
const needle = query.trim().toLocaleLowerCase();
|
||||
const hasResultFilter = needle !== "" || staticFilter !== "all" || dynamicFilter !== "all";
|
||||
const hasResultFilter = needle !== "" || staticFilter !== "all";
|
||||
const rowMatches = (row: Row) => {
|
||||
if (staticFilter !== "all" && row.static.status !== staticFilter) return false;
|
||||
if (dynamicFilter !== "all" && row.dynamic.status !== dynamicFilter) return false;
|
||||
if (!needle) return true;
|
||||
return [chapterTitles.get(row.chapter), row.kind, row.name, row.signature, row.apiSymbol, row.static.detail, row.dynamic.detail]
|
||||
return [chapterTitles.get(row.chapter), row.kind, row.name, row.signature, row.apiSymbol, row.static.detail]
|
||||
.join(" ")
|
||||
.toLocaleLowerCase()
|
||||
.includes(needle);
|
||||
@@ -247,7 +243,7 @@ export function NodeCompatibilityMatrix() {
|
||||
() => hasResultFilter ? filterTree(tree, rowMatches) : tree,
|
||||
// rowMatches closes over these filter values.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[dynamicFilter, hasResultFilter, needle, staticFilter, tree],
|
||||
[hasResultFilter, needle, staticFilter, tree],
|
||||
);
|
||||
const visible = useMemo(
|
||||
() => flattenTree(resultTree, expanded, hasResultFilter),
|
||||
@@ -260,14 +256,13 @@ export function NodeCompatibilityMatrix() {
|
||||
}, 0) : nodeCount(tree),
|
||||
// rowMatches closes over these filter values.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[dynamicFilter, hasResultFilter, needle, staticFilter, tree],
|
||||
[hasResultFilter, needle, staticFilter, tree],
|
||||
);
|
||||
|
||||
const changeQuery = (value: string) => setQuery(value);
|
||||
const resetFilters = () => {
|
||||
setQuery("");
|
||||
setStaticFilter("all");
|
||||
setDynamicFilter("all");
|
||||
setExpanded(new Set());
|
||||
};
|
||||
const toggle = (id: string) => setExpanded((current) => {
|
||||
@@ -280,8 +275,7 @@ export function NodeCompatibilityMatrix() {
|
||||
useEffect(() => {
|
||||
if (!snapshot) return;
|
||||
if (staticFilter !== "all" && staticCounts[staticFilter] === 0) setStaticFilter("all");
|
||||
if (dynamicFilter !== "all" && dynamicCounts[dynamicFilter] === 0) setDynamicFilter("all");
|
||||
}, [dynamicCounts, dynamicFilter, snapshot, staticCounts, staticFilter]);
|
||||
}, [snapshot, staticCounts, staticFilter]);
|
||||
|
||||
if (loadError) {
|
||||
return <div className="flex h-full items-center justify-center p-6"><div className="rounded-xl border border-red-500/40 bg-red-100 p-4 text-sm text-red-900">The compatibility dataset could not be loaded.</div></div>;
|
||||
@@ -296,9 +290,9 @@ export function NodeCompatibilityMatrix() {
|
||||
<div className="flex flex-col gap-3 xl:flex-row xl:items-center">
|
||||
<div className="min-w-fit">
|
||||
<h1 id="compatibility-title" className="text-lg font-semibold leading-6 text-gray-1000">Node.js 24 Compatibility</h1>
|
||||
<p className="text-xs text-gray-800">Static native and dynamic island · Node {snapshot.nodeVersion}</p>
|
||||
<p className="text-xs text-gray-800">Native compilation · Node {snapshot.nodeVersion}</p>
|
||||
</div>
|
||||
<div className="grid min-w-0 flex-1 gap-2 md:grid-cols-[minmax(240px,1fr)_auto_auto]">
|
||||
<div className="grid min-w-0 flex-1 gap-2 md:grid-cols-[minmax(240px,1fr)_auto]">
|
||||
<label className="relative block">
|
||||
<span className="sr-only">Search the Node.js API inventory</span>
|
||||
<svg aria-hidden="true" viewBox="0 0 16 16" className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-gray-800" fill="none" stroke="currentColor" strokeWidth="1.5">
|
||||
@@ -313,13 +307,7 @@ export function NodeCompatibilityMatrix() {
|
||||
{statuses.filter((status) => staticCounts[status] > 0).map((status) => <option key={status} value={status}>{labels[status]} ({staticCounts[status]})</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-xs text-gray-900">
|
||||
<span>Dynamic</span>
|
||||
<select value={dynamicFilter} onChange={(event) => setDynamicFilter(event.target.value as "all" | Status)} className="h-9 rounded-md border border-gray-alpha-500 bg-background-100 px-2 text-xs text-gray-1000">
|
||||
<option value="all">All</option>
|
||||
{statuses.filter((status) => dynamicCounts[status] > 0).map((status) => <option key={status} value={status}>{labels[status]} ({dynamicCounts[status]})</option>)}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-3 text-xs text-gray-900">
|
||||
<span aria-live="polite">{visible.length.toLocaleString()} visible · {matchedCount.toLocaleString()} {hasResultFilter ? "matching" : "API"} rows</span>
|
||||
@@ -339,7 +327,7 @@ export function NodeCompatibilityMatrix() {
|
||||
<div className="min-h-0 flex-1 overflow-auto overscroll-contain bg-background-100">
|
||||
<table role="treegrid" aria-label="Node.js 24 compatibility hierarchy" className="compatibility-table min-w-[900px]">
|
||||
<thead className="sticky top-0 z-10 bg-background-200/95 backdrop-blur-sm">
|
||||
<tr><th className="w-[40%]">Node.js API hierarchy</th><th className="w-[10%] text-center">Node stability</th><th className="w-[25%]">Static native</th><th className="w-[25%]">Dynamic island</th></tr>
|
||||
<tr><th className="w-[55%]">Node.js API hierarchy</th><th className="w-[15%] text-center">Node stability</th><th className="w-[30%]">Native support</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{visible.map(({ node, level }) => {
|
||||
@@ -370,7 +358,6 @@ export function NodeCompatibilityMatrix() {
|
||||
</td>
|
||||
<td className="text-center"><StabilityBadge stability={row.nodeStability} /></td>
|
||||
<td><StatusBadge status={row.static.status} detail={row.static.detail} verification={verificationBases?.[row.static.verification]} /></td>
|
||||
<td><StatusBadge status={row.dynamic.status} detail={row.dynamic.detail} verification={verificationBases?.[row.dynamic.verification]} /></td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
"schemaVersion": 3,
|
||||
"nodeVersion": "24.15.0",
|
||||
"nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119",
|
||||
"artifactVersion": "cb524f2eb043e40eb9f4",
|
||||
"artifactVersion": "32dbd982601194038ff9",
|
||||
"rowCount": 3662
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -63,13 +63,13 @@ test("Markdown twins preserve examples and declare the HTML canonical URL", asyn
|
||||
});
|
||||
|
||||
test("literal HTML tables and definition lists remain readable", async () => {
|
||||
const intro = await (await get("/docs")).text();
|
||||
assert.match(intro, /<table[ >]/);
|
||||
const coverage = await (await get("/docs/coverage")).text();
|
||||
assert.match(coverage, /<table[ >]/);
|
||||
const cli = await (await get("/docs/cli")).text();
|
||||
assert.match(cli, /<dl[ >]/);
|
||||
assert.match(cli, /<dt[ >]/);
|
||||
const markdown = await (await get("/docs/cli.md")).text();
|
||||
assert.ok(markdown.includes("--dynamic"));
|
||||
assert.ok(!markdown.includes("--dynamic"));
|
||||
assert.ok(markdown.includes("--external-types"));
|
||||
});
|
||||
|
||||
|
||||
@@ -15,4 +15,4 @@ $ ./app-ld
|
||||
|
||||
`cc` uses the C compiler as a linker driver. `ld` links the same precompiled runtime objects directly with the selected Apple SDK. The object defines `main`; it is a complete program object, not a library to load into another process. Use `scriptc build --lib --profile ...` for a host-callable static library.
|
||||
|
||||
The external object ABI is experimental. Always consume the runtime pack at the exact `runtime_pack.version` reported by the same scriptc installation. The object requires `scr_runtime_abi_v13`, so a mismatched runtime fails during the link instead of starting with an incompatible ABI.
|
||||
The external object ABI is experimental. Always consume the runtime pack at the exact `runtime_pack.version` reported by the same scriptc installation. The object requires `scr_runtime_abi_v14`, so a mismatched runtime fails during the link instead of starting with an incompatible ABI.
|
||||
|
||||
@@ -4,13 +4,12 @@ This workspace package owns scriptc's Node.js parity system.
|
||||
|
||||
- `node-v24.json` pins the Node release tag, commit, and canonical docs/source inputs.
|
||||
- `static-support.json` overlays dedicated compiler paths and their internal test evidence on the compiler's generated surface manifest.
|
||||
- `dynamic-support.json` is the implementation-owned dynamic-island module/global registry. Its `globals.supported` entries are installed by the always-on web prelude, while `globals.npmSupported` entries are installed only by the embedded npm module bootstrap. It also generates `packages/runtime/src/scr_island_manifest.h`, so runtime exports and compatibility claims share one source.
|
||||
- `generated/node-v24-internal.json` is the engineering ledger, including implementation evidence and repository test paths.
|
||||
- `generated/node-v24-backlog.json` is the internal tier-specific work queue. It separates verification, implementation, explicit-refusal replacement, and partial-surface audit work, and prioritizes stable Node APIs above experimental, deprecated, and legacy APIs.
|
||||
- `docs/src/generated/node-v24-compatibility.json` is the stripped public artifact consumed by the docs website. It contains compiler-relevant runtime APIs, statuses, verification bases, and user-facing details; documentation, metadata, command-line/configuration entries, and rows that are N/A in both tiers remain internal.
|
||||
- `docs/src/generated/node-v24-compatibility.json` is the stripped public artifact consumed by the docs website. It contains compiler-relevant runtime APIs, statuses, verification bases, and user-facing details; documentation, metadata, command-line/configuration entries, and rows that are N/A for native execution remain internal.
|
||||
|
||||
Statuses are evidence-aware: `supported` and `partial` require test evidence; `refused` requires an explicit compiler diagnostic or dynamic throwing stub; `not-implemented` is the actionable backlog for an owned API family with no implementation match; `by-design` records an architectural exclusion; `unreviewed` still needs classification; and `not-applicable` is not a runtime API for that execution tier.
|
||||
Statuses are evidence-aware: `supported` and `partial` require test evidence; `refused` requires an explicit compiler diagnostic or native throwing stub; `not-implemented` is the actionable backlog for an owned API family with no implementation match; `by-design` records an architectural exclusion; `unreviewed` still needs classification; and `not-applicable` is not a runtime API for that execution tier.
|
||||
|
||||
Run `pnpm node-compat:backlog` from the repository root for a summary. The filters `--tier`, `--action`, `--priority`, `--chapter`, and `--status` compose; use `--format=json` or `--format=tsv` for tooling.
|
||||
|
||||
Run `pnpm node-compat` from the repository root after changing the Node pin or either support manifest. `pnpm node-compat:check` is the offline drift/evidence gate; `pnpm --filter @internal/compatibility check:upstream` additionally verifies the pinned upstream inputs over the network.
|
||||
Run `pnpm node-compat` from the repository root after changing the Node pin or the support manifest. `pnpm node-compat:check` is the offline drift/evidence gate; `pnpm --filter @internal/compatibility check:upstream` additionally verifies the pinned upstream inputs over the network.
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"chapterPolicies": {
|
||||
"addons": "by-design",
|
||||
"n-api": "by-design",
|
||||
"embedding": "not-applicable",
|
||||
"cli": "not-applicable",
|
||||
"debugger": "not-applicable",
|
||||
"deprecations": "not-applicable",
|
||||
"intl": "not-applicable",
|
||||
"permissions": "not-implemented",
|
||||
"report": "not-implemented",
|
||||
"single-executable-applications": "not-applicable",
|
||||
"repl": "not-implemented",
|
||||
"sqlite": "not-implemented",
|
||||
"test": "not-implemented",
|
||||
"tracing": "not-implemented",
|
||||
"vm": "by-design",
|
||||
"wasi": "by-design"
|
||||
},
|
||||
"features": [
|
||||
{"chapter": "webstreams", "symbols": ["readableStream.cancel", "readableStream.getReader", "readableStream.tee"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-readers/main.ts", "tests/fixtures/npm/cases/node24-web-stream-readable-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-tee/main.ts"]},
|
||||
{"chapter": "webstreams", "parent": "Class: `ReadableStream`", "symbols": ["locked"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-readers/main.ts", "tests/fixtures/npm/cases/node24-web-stream-readable-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-tee/main.ts"]},
|
||||
{"chapter": "webstreams", "symbols": ["readableStreamDefaultReader.cancel", "readableStreamDefaultReader.read", "readableStreamDefaultReader.releaseLock"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-readers/main.ts", "tests/fixtures/npm/cases/node24-web-stream-readable-lifecycle/main.ts"]},
|
||||
{"chapter": "webstreams", "parent": "Class: `ReadableStreamDefaultReader`", "symbols": ["closed"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-readers/main.ts", "tests/fixtures/npm/cases/node24-web-stream-readable-lifecycle/main.ts"]},
|
||||
{"chapter": "webstreams", "symbols": ["readableStreamDefaultController.close", "readableStreamDefaultController.enqueue", "readableStreamDefaultController.error"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-readers/main.ts", "tests/fixtures/npm/cases/node24-web-stream-readable-lifecycle/main.ts"]},
|
||||
{"chapter": "webstreams", "symbols": ["readableStream.pipeTo", "readableStream.pipeThrough"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts"]},
|
||||
{"chapter": "webstreams", "parent": "Class: `TransformStream`", "symbols": ["readable", "writable"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts"]},
|
||||
{"chapter": "webstreams", "symbols": ["transformStreamDefaultController.enqueue", "transformStreamDefaultController.error", "transformStreamDefaultController.terminate"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts"]},
|
||||
{"chapter": "webstreams", "parent": "Class: `TransformStreamDefaultController`", "symbols": ["desiredSize"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts"]},
|
||||
{"chapter": "webstreams", "parent": "Class: `TextEncoderStream`", "symbols": ["encoding", "readable", "writable"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts"]},
|
||||
{"chapter": "webstreams", "symbols": ["writableStream.abort", "writableStream.close", "writableStream.getWriter", "writableStreamDefaultWriter.abort", "writableStreamDefaultWriter.close", "writableStreamDefaultWriter.releaseLock", "writableStreamDefaultWriter.write", "writableStreamDefaultController.error"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts"]},
|
||||
{"chapter": "webstreams", "parent": "Class: `WritableStream`", "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts"], "symbols": ["locked"]},
|
||||
{"chapter": "webstreams", "parent": "Class: `WritableStreamDefaultWriter`", "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts"], "symbols": ["closed", "desiredSize", "ready"]},
|
||||
{"chapter": "webstreams", "parent": "Class: `WritableStreamDefaultController`", "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts"], "symbols": ["signal"]},
|
||||
{"chapter": "webstreams", "parent": "Class: `CountQueuingStrategy`", "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts"], "symbols": ["highWaterMark", "size"]},
|
||||
{"chapter": "webstreams", "parent": "Class: `ByteLengthQueuingStrategy`", "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts"], "symbols": ["highWaterMark", "size"]},
|
||||
{"chapter": "webstreams", "parent": "Class: `ReadableStreamDefaultController`", "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", "tests/fixtures/npm/cases/node24-web-stream-readers/main.ts", "tests/fixtures/npm/cases/node24-web-stream-readable-lifecycle/main.ts"], "symbols": ["desiredSize"]},
|
||||
{"chapter": "util", "symbols": ["util.types.isAnyArrayBuffer", "util.types.isArrayBufferView", "util.types.isArgumentsObject", "util.types.isArrayBuffer", "util.types.isAsyncFunction", "util.types.isBigInt64Array", "util.types.isBigUint64Array", "util.types.isBooleanObject", "util.types.isBoxedPrimitive", "util.types.isBigIntObject", "util.types.isDataView", "util.types.isDate", "util.types.isFloat16Array", "util.types.isFloat32Array", "util.types.isFloat64Array", "util.types.isGeneratorFunction", "util.types.isGeneratorObject", "util.types.isInt8Array", "util.types.isInt16Array", "util.types.isInt32Array", "util.types.isMap", "util.types.isMapIterator", "util.types.isModuleNamespaceObject", "util.types.isNativeError", "util.types.isNumberObject", "util.types.isPromise", "util.types.isProxy", "util.types.isRegExp", "util.types.isSet", "util.types.isSetIterator", "util.types.isSharedArrayBuffer", "util.types.isStringObject", "util.types.isSymbolObject", "util.types.isTypedArray", "util.types.isUint8Array", "util.types.isUint8ClampedArray", "util.types.isUint16Array", "util.types.isUint32Array", "util.types.isWeakMap", "util.types.isWeakSet"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-util-types/main.ts"]},
|
||||
{"chapter": "util", "symbols": ["util.types.isCryptoKey", "util.types.isKeyObject", "util.types.isExternal"], "status": "not-implemented"},
|
||||
{ "chapter": "util", "symbols": ["util.styleText"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-style-text/main.ts"] },
|
||||
{ "chapter": "util", "symbols": ["util.isDeepStrictEqual"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-deep-equality/main.ts"] },
|
||||
{ "chapter": "assert", "symbols": ["assert.doesNotThrow", "assert.doesNotReject"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-no-error-assertions/main.ts"] },
|
||||
{ "chapter": "util", "symbols": ["util.stripVTControlCharacters", "util.toUSVString"], "status": "supported", "evidence": ["tests/fixtures/npm/cases/node24-text-utilities/main.ts"] },
|
||||
{ "chapter": "util", "symbols": ["util.getSystemErrorName", "util.getSystemErrorMessage", "util.getSystemErrorMap"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-system-errors/main.ts"] },
|
||||
{ "chapter": "url", "symbols": ["url.fileURLToPath", "url.fileURLToPathBuffer", "url.pathToFileURL"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-file-url-conversion/main.ts"] },
|
||||
{ "chapter": "url", "parent": "Class: `URL`", "signatures": ["`protocol` Type: {string}", "`username` Type: {string}", "`password` Type: {string}", "`host` Type: {string}", "`hostname` Type: {string}", "`port` Type: {string}"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-url-authority/main.ts"] },
|
||||
{ "chapter": "url", "parent": "Class: `URL`", "signatures": ["`href` Type: {string}", "`pathname` Type: {string}", "`search` Type: {string}", "`hash` Type: {string}"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-url-mutation/main.ts"] },
|
||||
{ "chapter": "diagnostics_channel", "symbols": ["start", "end", "asyncStart", "asyncEnd", "error", "TracingChannel", "channel.bindStore", "channel.unbindStore", "channel.runStores", "tracingChannel.subscribe", "tracingChannel.unsubscribe", "tracingChannel.traceSync", "tracingChannel.tracePromise", "tracingChannel.traceCallback"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-diagnostics-tracing/main.ts", "tests/fixtures/npm/cases/node24-diagnostics-async/main.ts", "tests/fixtures/npm/cases/node24-diagnostics-subscriber-throw/main.ts", "tests/fixtures/npm/cases/node24-diagnostics-transform-throw/main.ts"] },
|
||||
{ "chapter": "util", "symbols": ["util.MIMEType", "util.MIMEParams", "mime.toString", "mime.toJSON", "mimeParams.delete", "mimeParams.entries", "mimeParams.get", "mimeParams.has", "mimeParams.keys", "mimeParams.set", "mimeParams.values"], "signatures": ["`mimeParams[Symbol.iterator]()`"], "status": "supported", "evidence": ["tests/fixtures/npm/cases/node24-mime/main.ts", "tests/fixtures/npm/cases/node24-mime-errors/main.ts"] },
|
||||
{ "chapter": "util", "parent": "Class: `util.MIMEType`", "signatures": ["`type` Type: {string}", "`subtype` Type: {string}", "`essence` Type: {string}", "`params` Type: {MIMEParams}"], "status": "supported", "evidence": ["tests/fixtures/npm/cases/node24-mime/main.ts", "tests/fixtures/npm/cases/node24-mime-errors/main.ts"] },
|
||||
{ "chapter": "timers", "symbols": ["timersPromises.setTimeout", "timersPromises.setImmediate", "timersPromises.setInterval", "timersPromises.scheduler.wait", "timersPromises.scheduler.yield"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-promise-timers/main.ts", "tests/fixtures/npm/cases/node24-timers-unref/main.ts"] },
|
||||
{ "chapter": "timers", "symbols": ["immediate.ref", "immediate.unref", "immediate.hasRef", "timeout.ref", "timeout.unref", "timeout.hasRef", "timeout.refresh", "timeout.close"], "signatures": ["`immediate[Symbol.dispose]()`", "`timeout[Symbol.dispose]()`"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/island-timers/main.ts", "tests/fixtures/npm/cases/node24-promise-timers/main.ts", "tests/fixtures/npm/cases/node24-timers-unref/main.ts"] },
|
||||
{ "chapter": "events", "symbols": ["events.once", "events.on", "events.addAbortListener"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-async-events/main.ts"] },
|
||||
{ "chapter": "events", "symbols": ["emitter.on", "emitter.addListener", "emitter.once", "emitter.prependListener", "emitter.prependOnceListener", "emitter.removeListener", "emitter.off", "emitter.removeAllListeners", "emitter.emit", "emitter.listenerCount", "emitter.eventNames", "events.errorMonitor"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/stream-shims/main.ts", "tests/fixtures/npm/cases/node24-event-emitter/main.ts"] },
|
||||
{ "chapter": "util", "symbols": ["util.promisify", "util.callbackify"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/util-shims/main.ts", "tests/fixtures/npm/cases/node24-util-adapters/main.ts"] },
|
||||
{ "chapter": "util", "symbols": ["util.parseArgs", "parseArgs"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/util-shims/main.ts", "tests/fixtures/npm/cases/node24-parseargs/main.ts"] },
|
||||
{ "chapter": "buffer", "symbols": ["buffer.isAscii", "buffer.isUtf8", "buffer.transcode"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-text-encoding/main.ts"] },
|
||||
{ "chapter": "util", "parent": "Class: `util.TextEncoder`", "signatures": ["`textEncoder.encodeInto(src, dest)`", "`encoding` Type: {string}"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-text-encoding/main.ts"] },
|
||||
{ "chapter": "util", "parent": "Class: `util.TextDecoder`", "signatures": ["`encoding` Type: {string}", "`fatal` Type: {boolean}", "`ignoreBOM` Type: {boolean}"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-text-encoding/main.ts"] },
|
||||
{ "chapter": "process", "symbols": ["process.chdir"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-env/main.ts"] },
|
||||
{ "chapter": "util", "symbols": ["util.parseEnv"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-env/main.ts"] },
|
||||
{ "chapter": "process", "symbols": ["process.loadEnvFile"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-env/main.ts"] },
|
||||
{ "chapter": "fs", "signatures": ["`fsync` {boolean} Whether the stream is performing a `fs.fsyncSync()` after every write operation."], "status": "not-implemented" },
|
||||
{ "chapter": "fs", "parent": "Class: `FileHandle`", "symbols": ["fd"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-fs-descriptors/main.ts"] },
|
||||
{ "chapter": "environment_variables", "symbols": ["process.env"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts", "tests/fixtures/npm/cases/node24-env/main.ts"] },
|
||||
{ "chapter": "environment_variables", "symbols": ["export"], "status": "not-applicable" },
|
||||
{ "chapter": "modules", "symbols": ["__dirname", "__filename", "exports", "module", "require", "cache"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/create-require/main.ts", "tests/fixtures/npm/cases/dynamic-import/main.ts", "tests/fixtures/npm/cases/node24-commonjs-metadata/main.ts"] },
|
||||
{ "chapter": "modules", "symbols": ["filename", "id", "loaded", "parent", "path", "module.require", "children", "isPreloading", "paths"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-commonjs-metadata/main.ts"] },
|
||||
{ "chapter": "modules", "symbols": ["require.resolve", "require.resolve.paths"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/create-require/main.ts"] },
|
||||
{ "chapter": "modules", "symbols": ["extensions", "main", "Module"], "status": "not-implemented" },
|
||||
{ "chapter": "modules", "symbols": ["node:", "node_modules"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/builtin-shims/main.ts", "tests/fixtures/npm/cases/create-require/main.ts"] },
|
||||
{ "chapter": "esm", "symbols": ["import", "file:", "node:", "await"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/dynamic-import/main.ts", "tests/corpus/2646-top-level-await.ts"] },
|
||||
{ "chapter": "esm", "signatures": ["`import()` expressions"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/dynamic-import/main.ts", "tests/corpus/2050-dynamic-import-own-module/main.ts", "tests/corpus/2790-dynamic-import-self-name/main.ts"] },
|
||||
{ "chapter": "esm", "symbols": ["import.meta.resolve"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/dual-entry/main.ts", "tests/fixtures/npm/cases/node24-esm-metadata/main.ts"] },
|
||||
{ "chapter": "esm", "symbols": ["meta", "dirname", "filename", "main", "url"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-esm-metadata/main.ts"] },
|
||||
{ "chapter": "esm", "symbols": ["data:", "require", "__filename", "require.main", "require.resolve", "NODE_PATH", "require.extensions", "require.cache"], "status": "not-implemented" },
|
||||
{ "chapter": "packages", "symbols": ["package.json", "\"name\"", "\"main\"", "\"type\"", "\"exports\"", "\"imports\""], "status": "partial", "evidence": ["tests/fixtures/npm/cases/dual-entry/main.ts", "tests/fixtures/npm/cases/self-name/main.ts", "tests/fixtures/npm/cases/scoped-nested/main.ts", "tests/corpus/2790-dynamic-import-self-name/main.ts"] },
|
||||
{ "chapter": "packages", "symbols": ["--input-type"], "status": "not-applicable" },
|
||||
{ "chapter": "typescript", "symbols": ["type"], "status": "supported", "evidence": ["tests/corpus/2705-type-import-link-dynamic/main.ts", "tests/corpus/2706-type-import-link-dynamic-success/main.ts", "tests/corpus/2708-type-import-link-dynamic/main.ts", "tests/harness/errors.test.ts"] }
|
||||
],
|
||||
"modules": {
|
||||
"events": { "exports": ["EventEmitter", "once", "on", "addAbortListener", "listenerCount", "getEventListeners", "setMaxListeners", "defaultMaxListeners", "errorMonitor", "captureRejectionSymbol"], "members": ["emitter.addListener", "emitter.emit", "emitter.eventNames", "emitter.getMaxListeners", "emitter.listenerCount", "emitter.listeners", "emitter.off", "emitter.on", "emitter.once", "emitter.prependListener", "emitter.prependOnceListener", "emitter.removeAllListeners", "emitter.removeListener", "emitter.setMaxListeners", "emitter.rawListeners", "events.getEventListeners", "events.listenerCount", "events.once", "events.on", "events.setMaxListeners"], "evidence": ["tests/fixtures/npm/cases/stream-shims/main.ts"] },
|
||||
"path": { "exports": ["sep", "delimiter", "basename", "dirname", "extname", "join", "resolve", "normalize", "relative", "isAbsolute", "toNamespacedPath", "parse", "format", "posix", "win32"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"path/posix": { "exports": ["sep", "delimiter", "basename", "dirname", "extname", "join", "resolve", "normalize", "relative", "isAbsolute", "toNamespacedPath", "parse", "format", "posix", "win32"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"path/win32": { "exports": ["sep", "delimiter", "basename", "dirname", "extname", "join", "resolve", "normalize", "relative", "isAbsolute", "toNamespacedPath", "parse", "format", "posix", "win32"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"process": { "exports": ["chdir", "loadEnvFile", "argv", "env", "platform", "execPath", "execArgv", "version", "versions", "stdout", "stderr", "stdin", "cwd", "exit", "nextTick", "hrtime", "pid", "ppid", "title", "argv0", "release", "config", "allowedNodeEnvironmentFlags", "emitWarning", "uptime", "memoryUsage", "umask", "exitCode", "on", "once", "off", "removeListener", "emit"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"fs": { "exports": ["readFileSync", "writeFileSync", "appendFileSync", "existsSync", "realpathSync", "mkdirSync", "rmSync", "rmdirSync", "unlinkSync", "readdirSync", "statSync", "lstatSync", "accessSync", "mkdtempSync", "chmodSync", "copyFileSync", "renameSync", "constants", "Stats", "Dirent", "promises", "readFile", "writeFile", "appendFile", "exists", "realpath", "mkdir", "rm", "rmdir", "unlink", "readdir", "stat", "lstat", "access", "mkdtemp", "chmod", "copyFile", "rename", "readlink", "readlinkSync", "createReadStream", "createWriteStream", "watch", "watchFile", "unwatchFile", "openSync", "closeSync", "readSync", "read", "open", "writeSync", "write", "close", "readvSync", "writevSync", "readv", "writev", "fstatSync", "fstat", "fchmodSync", "fchmod", "fsyncSync", "fsync", "fdatasyncSync", "fdatasync", "ftruncateSync", "ftruncate"], "refused": ["watch", "watchFile"], "members": ["dirent.isBlockDevice", "dirent.isCharacterDevice", "dirent.isDirectory", "dirent.isFIFO", "dirent.isFile", "dirent.isSocket", "dirent.isSymbolicLink", "stats.isBlockDevice", "stats.isCharacterDevice", "stats.isDirectory", "stats.isFIFO", "stats.isFile", "stats.isSocket", "stats.isSymbolicLink"], "evidence": ["tests/fixtures/npm/cases/fs-shims/main.ts", "tests/fixtures/npm/cases/node24-fs-descriptors/main.ts"] },
|
||||
"fs/promises": { "exports": ["readFile", "writeFile", "appendFile", "realpath", "mkdir", "rm", "rmdir", "unlink", "readdir", "stat", "lstat", "access", "mkdtemp", "chmod", "copyFile", "rename", "readlink", "constants", "open"], "evidence": ["tests/fixtures/npm/cases/fs-shims/main.ts", "tests/fixtures/npm/cases/node24-fs-descriptors/main.ts"], "members": ["FileHandle", "filehandle.fd", "filehandle.close", "filehandle.read", "filehandle.write", "filehandle.readv", "filehandle.writev", "filehandle.readFile", "filehandle.writeFile", "filehandle.appendFile", "filehandle.stat", "filehandle.sync", "filehandle.datasync", "filehandle.truncate", "filehandle.chmod"] },
|
||||
"child_process": { "exports": ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync", "fork"], "refused": ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync", "fork"], "evidence": ["tests/harness/npm.test.ts"] },
|
||||
"os": { "exports": ["EOL", "platform", "arch", "hostname", "homedir", "tmpdir", "type", "endianness", "userInfo", "release", "version", "machine", "cpus", "availableParallelism", "totalmem", "freemem", "loadavg", "uptime", "networkInterfaces", "constants"], "evidence": ["tests/fixtures/npm/cases/builtin-shims/main.ts"] },
|
||||
"tty": { "exports": ["isatty", "ReadStream", "WriteStream"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"http": { "exports": ["request", "get", "Agent", "globalAgent", "ClientRequest", "IncomingMessage", "STATUS_CODES", "METHODS", "createServer", "Server"], "refused": ["createServer", "Server"], "members": ["agent.destroy", "request.abort", "request.end", "request.destroy", "request.flushHeaders", "request.getHeader", "request.removeHeader", "request.setHeader", "request.setTimeout", "request.write", "message.destroy", "message.setEncoding", "message.pause", "message.resume"], "evidence": ["tests/fixtures/fetch/cases/island-http/main.ts"] },
|
||||
"https": { "exports": ["request", "get", "Agent", "globalAgent", "ClientRequest", "IncomingMessage", "STATUS_CODES", "METHODS", "createServer", "Server"], "refused": ["createServer", "Server"], "members": ["agent.destroy", "request.abort", "request.end", "request.destroy", "request.flushHeaders", "request.getHeader", "request.removeHeader", "request.setHeader", "request.setTimeout", "request.write", "message.destroy", "message.setEncoding", "message.pause", "message.resume"], "evidence": ["tests/fixtures/fetch/cases/island-http/main.ts"] },
|
||||
"net": { "exports": ["isIP", "isIPv4", "isIPv6", "connect", "createConnection", "createServer", "Socket", "Server"], "refused": ["connect", "createConnection", "createServer", "Socket", "Server"], "evidence": ["tests/fixtures/fetch/cases/island-http/main.ts"] },
|
||||
"tls": { "exports": ["connect", "createServer", "createSecureContext", "TLSSocket", "rootCertificates"], "refused": ["connect", "createServer", "createSecureContext", "TLSSocket"], "evidence": ["tests/fixtures/fetch/cases/island-http/main.ts"] },
|
||||
"diagnostics_channel": { "exports": ["channel", "subscribe", "unsubscribe", "hasSubscribers", "tracingChannel", "Channel"], "members": ["channel.publish", "channel.subscribe", "channel.unsubscribe", "channel.bindStore", "channel.unbindStore", "channel.runStores", "tracingChannel.subscribe", "tracingChannel.unsubscribe", "tracingChannel.traceSync", "tracingChannel.tracePromise", "tracingChannel.traceCallback"], "evidence": ["tests/fixtures/npm/cases/builtin-shims/main.ts", "tests/fixtures/npm/cases/node24-diagnostics-tracing/main.ts", "tests/fixtures/npm/cases/node24-diagnostics-async/main.ts", "tests/fixtures/npm/cases/node24-diagnostics-subscriber-throw/main.ts", "tests/fixtures/npm/cases/node24-diagnostics-transform-throw/main.ts"] },
|
||||
"module": { "exports": ["createRequire", "builtinModules", "isBuiltin", "syncBuiltinESMExports", "register", "findSourceMap"], "refused": ["register"], "evidence": ["tests/fixtures/npm/cases/create-require/main.ts", "tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"url": { "exports": ["URL", "URLSearchParams", "fileURLToPath", "fileURLToPathBuffer", "pathToFileURL", "parse", "format", "resolve", "domainToASCII", "domainToUnicode", "urlToHttpOptions"], "members": ["url.toString", "url.toJSON", "URL.canParse", "URL.parse", "urlSearchParams.append", "urlSearchParams.delete", "urlSearchParams.entries", "urlSearchParams.forEach", "urlSearchParams.get", "urlSearchParams.getAll", "urlSearchParams.has", "urlSearchParams.keys", "urlSearchParams.set", "urlSearchParams.sort", "urlSearchParams.toString", "urlSearchParams.values", "urlSearchParams.size"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts", "tests/fixtures/npm/cases/island-web-plumbing/main.ts", "tests/fixtures/npm/cases/node24-url-factories/main.ts"] },
|
||||
"buffer": { "exports": ["Buffer", "SlowBuffer", "INSPECT_MAX_BYTES", "kMaxLength", "kStringMaxLength", "constants", "isAscii", "isUtf8", "atob", "btoa", "Blob", "File", "transcode", "resolveObjectURL"], "members": ["Buffer.alloc", "Buffer.allocUnsafe", "Buffer.allocUnsafeSlow", "Buffer.from", "Buffer.isBuffer", "Buffer.isEncoding", "Buffer.byteLength", "Buffer.concat", "Buffer.compare", "buf.compare", "buf.copy", "buf.equals", "buf.fill", "buf.includes", "buf.indexOf", "buf.lastIndexOf", "buf.subarray", "buf.slice", "buf.swap16", "buf.swap32", "buf.swap64", "buf.toJSON", "buf.toString", "buf.write", "buf.readBigInt64BE", "buf.readBigInt64LE", "buf.readBigUInt64BE", "buf.readBigUInt64LE", "buf.readDoubleBE", "buf.readDoubleLE", "buf.readFloatBE", "buf.readFloatLE", "buf.readInt8", "buf.readInt16BE", "buf.readInt16LE", "buf.readInt32BE", "buf.readInt32LE", "buf.readIntBE", "buf.readIntLE", "buf.readUInt8", "buf.readUInt16BE", "buf.readUInt16LE", "buf.readUInt32BE", "buf.readUInt32LE", "buf.readUIntBE", "buf.readUIntLE", "buf.writeBigInt64BE", "buf.writeBigInt64LE", "buf.writeBigUInt64BE", "buf.writeBigUInt64LE", "buf.writeDoubleBE", "buf.writeDoubleLE", "buf.writeFloatBE", "buf.writeFloatLE", "buf.writeInt8", "buf.writeInt16BE", "buf.writeInt16LE", "buf.writeInt32BE", "buf.writeInt32LE", "buf.writeIntBE", "buf.writeIntLE", "buf.writeUInt8", "buf.writeUInt16BE", "buf.writeUInt16LE", "buf.writeUInt32BE", "buf.writeUInt32LE", "buf.writeUIntBE", "buf.writeUIntLE"], "evidence": ["tests/fixtures/npm/cases/buffer-shims/main.ts"] },
|
||||
"string_decoder": { "exports": ["StringDecoder"], "members": ["stringDecoder.write", "stringDecoder.end"], "evidence": ["tests/fixtures/npm/cases/buffer-shims/main.ts", "tests/fixtures/npm/cases/node24-string-decoder/main.ts"] },
|
||||
"assert": { "exports": ["AssertionError", "ok", "fail", "equal", "notEqual", "strictEqual", "notStrictEqual", "deepEqual", "notDeepEqual", "deepStrictEqual", "notDeepStrictEqual", "throws", "doesNotThrow", "rejects", "doesNotReject", "match", "doesNotMatch", "ifError", "strict"], "evidence": ["tests/fixtures/npm/cases/assert-shims/main.ts"] },
|
||||
"assert/strict": { "exports": ["AssertionError", "ok", "fail", "equal", "notEqual", "strictEqual", "notStrictEqual", "deepEqual", "notDeepEqual", "deepStrictEqual", "notDeepStrictEqual", "throws", "doesNotThrow", "rejects", "doesNotReject", "match", "doesNotMatch", "ifError", "strict"], "evidence": ["tests/fixtures/npm/cases/assert-shims/main.ts"] },
|
||||
"domain": { "exports": ["create", "createDomain", "Domain", "active"], "members": ["domain.add", "domain.bind", "domain.enter", "domain.exit", "domain.intercept", "domain.remove", "domain.run"], "evidence": ["tests/fixtures/npm/cases/island-web-plumbing/main.ts"] },
|
||||
"worker_threads": { "exports": ["isMainThread", "parentPort", "threadId", "workerData", "resourceLimits", "MessageChannel", "MessagePort", "Worker", "receiveMessageOnPort", "SHARE_ENV", "markAsUntransferable", "getEnvironmentData", "setEnvironmentData"], "refused": ["Worker"], "members": ["port.close", "port.postMessage", "port.hasRef", "port.ref", "port.start", "port.unref"], "evidence": ["tests/fixtures/npm/cases/island-web-plumbing/main.ts"] },
|
||||
"perf_hooks": { "exports": ["performance", "PerformanceObserver", "monitorEventLoopDelay", "constants"], "members": ["performanceObserver.disconnect", "performanceObserver.observe", "performanceObserver.takeRecords", "performance.clearMarks", "performance.clearMeasures", "performance.getEntries", "performance.getEntriesByName", "performance.getEntriesByType", "performance.mark", "performance.measure", "performance.now", "performance.toJSON"], "evidence": ["tests/fixtures/npm/cases/island-web-plumbing/main.ts"] },
|
||||
"v8": { "exports": ["startupSnapshot", "cachedDataVersionTag", "getHeapStatistics", "getHeapSpaceStatistics", "getHeapCodeStatistics", "getCppHeapStatistics", "setFlagsFromString", "takeCoverage", "stopCoverage", "setHeapSnapshotNearHeapLimit", "serialize", "deserialize", "writeHeapSnapshot", "getHeapSnapshot", "queryObjects", "startCpuProfile", "isStringOneByteRepresentation", "promiseHooks", "Serializer", "Deserializer", "DefaultSerializer", "DefaultDeserializer", "GCProfiler"], "refused": ["serialize", "deserialize", "writeHeapSnapshot", "getHeapSnapshot", "queryObjects", "startCpuProfile", "isStringOneByteRepresentation", "promiseHooks", "Serializer", "Deserializer", "DefaultSerializer", "DefaultDeserializer", "GCProfiler"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"dns": { "exports": ["lookup", "lookupService", "resolve", "resolve4", "resolve6", "resolveAny", "resolveCaa", "resolveCname", "resolveMx", "resolveNaptr", "resolveNs", "resolvePtr", "resolveSoa", "resolveSrv", "resolveTlsa", "resolveTxt", "reverse", "getServers", "setServers", "getDefaultResultOrder", "setDefaultResultOrder", "Resolver", "promises", "ADDRCONFIG", "V4MAPPED", "ALL", "NODATA", "FORMERR", "SERVFAIL", "NOTFOUND", "NOTIMP", "REFUSED", "BADQUERY", "BADNAME", "BADFAMILY", "BADRESP", "CONNREFUSED", "TIMEOUT", "EOF", "FILE", "NOMEM", "DESTRUCTION", "BADSTR", "BADFLAGS", "NONAME", "BADHINTS", "NOTINITIALIZED", "LOADIPHLPAPI", "ADDRGETNETWORKPARAMS", "CANCELLED"], "refused": ["lookup", "lookupService", "resolve", "resolve4", "resolve6", "resolveAny", "resolveCaa", "resolveCname", "resolveMx", "resolveNaptr", "resolveNs", "resolvePtr", "resolveSoa", "resolveSrv", "resolveTlsa", "resolveTxt", "reverse"], "evidence": ["tests/fixtures/npm/cases/island-web-plumbing/main.ts"] },
|
||||
"dns/promises": { "exports": ["lookup", "lookupService", "resolve", "resolve4", "resolve6", "resolveAny", "resolveCaa", "resolveCname", "resolveMx", "resolveNaptr", "resolveNs", "resolvePtr", "resolveSoa", "resolveSrv", "resolveTlsa", "resolveTxt", "reverse", "getServers", "setServers", "getDefaultResultOrder", "setDefaultResultOrder", "Resolver", "NODATA", "FORMERR", "SERVFAIL", "NOTFOUND", "NOTIMP", "REFUSED", "BADQUERY", "BADNAME", "BADFAMILY", "BADRESP", "CONNREFUSED", "TIMEOUT", "EOF", "FILE", "NOMEM", "DESTRUCTION", "BADSTR", "BADFLAGS", "NONAME", "BADHINTS", "NOTINITIALIZED", "LOADIPHLPAPI", "ADDRGETNETWORKPARAMS", "CANCELLED"], "refused": ["lookup", "lookupService", "resolve", "resolve4", "resolve6", "resolveAny", "resolveCaa", "resolveCname", "resolveMx", "resolveNaptr", "resolveNs", "resolvePtr", "resolveSoa", "resolveSrv", "resolveTlsa", "resolveTxt", "reverse"], "evidence": ["tests/fixtures/npm/cases/island-web-plumbing/main.ts"] },
|
||||
"async_hooks": { "exports": ["AsyncLocalStorage", "AsyncResource", "executionAsyncId", "triggerAsyncId", "executionAsyncResource", "createHook"], "members": ["asyncLocalStorage.run", "asyncLocalStorage.exit", "asyncLocalStorage.getStore", "asyncLocalStorage.enterWith", "asyncLocalStorage.disable", "asyncResource.runInAsyncScope", "asyncResource.bind", "asyncResource.emitDestroy", "asyncResource.asyncId", "asyncResource.triggerAsyncId"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"punycode": { "exports": ["version", "ucs2", "decode", "encode", "toASCII", "toUnicode"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"querystring": { "exports": ["parse", "stringify", "decode", "encode", "escape", "unescape", "unescapeBuffer"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"constants": { "exports": ["F_OK", "R_OK", "W_OK", "X_OK"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"console": { "exports": ["Console", "log", "info", "debug", "warn", "error", "trace", "dir", "assert", "count", "countReset", "time", "timeEnd", "group", "groupEnd", "table", "clear"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"timers": { "exports": ["setTimeout", "clearTimeout", "setInterval", "clearInterval", "setImmediate", "clearImmediate"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"timers/promises": { "exports": ["setTimeout", "setImmediate", "setInterval", "scheduler"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"zlib": { "exports": ["deflateSync", "inflateSync", "deflateRawSync", "inflateRawSync", "gzipSync", "gunzipSync", "unzipSync", "deflate", "inflate", "deflateRaw", "inflateRaw", "gzip", "gunzip", "unzip", "Deflate", "Inflate", "DeflateRaw", "InflateRaw", "Gzip", "Gunzip", "Unzip", "BrotliCompress", "BrotliDecompress", "createDeflate", "createInflate", "createDeflateRaw", "createInflateRaw", "createGzip", "createGunzip", "createUnzip", "createBrotliCompress", "createBrotliDecompress", "brotliCompressSync", "brotliDecompressSync", "constants"], "members": ["zlib.close", "zlib.flush", "zlib.reset"], "evidence": ["tests/fixtures/npm/cases/zlib-shims/main.ts"] },
|
||||
"readline": { "exports": ["Interface", "createInterface", "clearLine", "clearScreenDown", "cursorTo", "moveCursor", "emitKeypressEvents"], "members": ["rl.question", "rl.close", "rl.pause", "rl.prompt", "rl.resume", "rl.setPrompt", "rl.getPrompt", "rl.write"], "evidence": ["tests/fixtures/npm/cases/misc-shims/main.ts"] },
|
||||
"stream": { "exports": ["Stream", "Readable", "Writable", "Duplex", "Transform", "PassThrough", "pipeline", "finished", "addAbortSignal", "promises", "isErrored", "isDestroyed", "isReadable", "isWritable"], "members": ["stream.Readable.from", "readable.read", "readable.push", "readable.unshift", "readable.pause", "readable.resume", "readable.isPaused", "readable.setEncoding", "readable.pipe", "readable.unpipe", "readable.destroy", "writable.write", "writable.end", "writable.cork", "writable.uncork", "writable.setDefaultEncoding", "writable.destroy", "transform.destroy"], "refusedMembers": ["readable.wrap"], "evidence": ["tests/fixtures/npm/cases/stream-shims/main.ts"] },
|
||||
"stream/promises": { "exports": ["pipeline", "finished"], "evidence": ["tests/fixtures/npm/cases/stream-shims/main.ts"] },
|
||||
"stream/consumers": { "exports": ["text", "buffer", "arrayBuffer", "json", "blob"], "evidence": ["tests/fixtures/npm/cases/stream-shims/main.ts"] },
|
||||
"stream/web": {"exports": ["ReadableStream", "TransformStream", "TextDecoderStream", "WritableStream", "WritableStreamDefaultWriter", "WritableStreamDefaultController", "CountQueuingStrategy", "ByteLengthQueuingStrategy", "TransformStreamDefaultController", "TextEncoderStream", "ReadableStreamDefaultReader", "ReadableStreamDefaultController"], "evidence": ["tests/fixtures/npm/cases/web-streams/main.ts", "tests/fixtures/npm/cases/node24-web-stream-queues/main.ts", "tests/fixtures/npm/cases/node24-web-stream-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-errors/main.ts", "tests/fixtures/npm/cases/node24-web-stream-pipes/main.ts", "tests/fixtures/npm/cases/node24-web-stream-pipe-errors/main.ts", "tests/fixtures/npm/cases/node24-web-stream-pipe-abort/main.ts", "tests/fixtures/npm/cases/node24-web-stream-transforms/main.ts", "tests/fixtures/npm/cases/node24-web-stream-encoding/main.ts", "tests/fixtures/npm/cases/node24-web-stream-readers/main.ts", "tests/fixtures/npm/cases/node24-web-stream-readable-lifecycle/main.ts", "tests/fixtures/npm/cases/node24-web-stream-tee/main.ts"]},
|
||||
"crypto": { "exports": ["createHash", "createHmac", "hash", "Hash", "Hmac", "randomBytes", "randomFillSync", "randomFill", "randomInt", "randomUUID", "getRandomValues", "timingSafeEqual", "pbkdf2", "pbkdf2Sync", "getHashes", "getCiphers", "getCurves", "webcrypto", "subtle", "constants", "KeyObject", "createCipheriv", "createDecipheriv", "createSign", "createVerify", "createDiffieHellman", "createECDH", "createPublicKey", "createPrivateKey", "createSecretKey", "diffieHellman", "generateKeyPair", "generateKeyPairSync", "generateKey", "generateKeySync", "sign", "verify", "publicEncrypt", "publicDecrypt", "privateEncrypt", "privateDecrypt", "scrypt", "scryptSync", "hkdf", "hkdfSync", "X509Certificate", "Certificate", "checkPrime", "checkPrimeSync", "generatePrime", "generatePrimeSync", "secureHeapUsed", "setEngine", "setFips", "getFips"], "refused": ["KeyObject", "createCipheriv", "createDecipheriv", "createSign", "createVerify", "createDiffieHellman", "createECDH", "createPublicKey", "createPrivateKey", "createSecretKey", "diffieHellman", "generateKeyPair", "generateKeyPairSync", "generateKey", "generateKeySync", "sign", "verify", "publicEncrypt", "publicDecrypt", "privateEncrypt", "privateDecrypt", "X509Certificate", "Certificate", "checkPrime", "checkPrimeSync", "generatePrime", "generatePrimeSync", "secureHeapUsed", "setEngine"], "members": ["hash.copy", "hash.digest", "hash.update", "hmac.digest", "hmac.update"], "evidence": ["tests/fixtures/npm/cases/crypto-shims/main.ts", "tests/fixtures/npm/cases/node24-crypto-kdf/main.ts"] },
|
||||
"util": { "exports": ["getSystemErrorName", "getSystemErrorMessage", "getSystemErrorMap", "parseEnv", "format", "formatWithOptions", "inspect", "inherits", "promisify", "callbackify", "deprecate", "debuglog", "debug", "types", "isDeepStrictEqual", "stripVTControlCharacters", "styleText", "parseArgs", "toUSVString", "_extend", "TextEncoder", "TextDecoder", "MIMEType", "MIMEParams", "isArray"], "evidence": ["tests/fixtures/npm/cases/util-shims/main.ts"] },
|
||||
"util/types": { "exports": ["isAnyArrayBuffer", "isArrayBufferView", "isArgumentsObject", "isArrayBuffer", "isAsyncFunction", "isBigInt64Array", "isBigUint64Array", "isBooleanObject", "isBoxedPrimitive", "isBigIntObject", "isCryptoKey", "isDataView", "isDate", "isExternal", "isFloat16Array", "isFloat32Array", "isFloat64Array", "isGeneratorFunction", "isGeneratorObject", "isInt8Array", "isInt16Array", "isInt32Array", "isKeyObject", "isMap", "isMapIterator", "isModuleNamespaceObject", "isNativeError", "isNumberObject", "isPromise", "isProxy", "isRegExp", "isSet", "isSetIterator", "isSharedArrayBuffer", "isStringObject", "isSymbolObject", "isTypedArray", "isUint8Array", "isUint8ClampedArray", "isUint16Array", "isUint32Array", "isWeakMap", "isWeakSet"], "evidence": ["tests/fixtures/npm/cases/util-shims/main.ts", "tests/fixtures/npm/cases/node24-util-types/main.ts"] }
|
||||
},
|
||||
"globals": {
|
||||
"supported": ["atob", "btoa", "clearImmediate", "clearInterval", "clearTimeout", "queueMicrotask", "setImmediate", "setInterval", "setTimeout", "structuredClone", "AbortController", "AbortSignal", "Blob", "CustomEvent", "DOMException", "Event", "EventTarget", "File", "Headers", "MessageChannel", "MessageEvent", "MessagePort", "Request", "Response", "TextDecoder", "TextDecoderStream", "TextEncoder", "TextEncoderStream", "TransformStream", "TransformStreamDefaultController", "URLSearchParams", "ReadableStream", "ReadableStreamDefaultReader", "ReadableStreamDefaultController", "WritableStream", "WritableStreamDefaultWriter", "WritableStreamDefaultController", "CountQueuingStrategy", "ByteLengthQueuingStrategy"],
|
||||
"npmSupported": ["Buffer", "URL"],
|
||||
"absent": ["BroadcastChannel", "CloseEvent", "CompressionStream", "CryptoKey", "DecompressionStream", "EventSource", "FormData", "Navigator", "ReadableByteStreamController", "ReadableStreamBYOBReader", "ReadableStreamBYOBRequest", "Storage", "SubtleCrypto", "URLPattern", "WebAssembly", "WebSocket"],
|
||||
"evidence": ["tests/harness/web-globals.test.ts"]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
||||
"build": "pnpm check",
|
||||
"update": "node src/update.mjs",
|
||||
"backlog": "node src/backlog.mjs",
|
||||
"check": "node src/generate-island-header.mjs --check && node src/generate.mjs --check-local",
|
||||
"check": "node src/generate.mjs --check-local",
|
||||
"check:upstream": "node src/generate.mjs --check"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ const format = option("format") ?? "summary";
|
||||
const limit = integerOption("limit", format === "summary" ? 20 : Number.MAX_SAFE_INTEGER);
|
||||
|
||||
for (const [name, value, allowed] of [
|
||||
["tier", tierFilter, ["static", "dynamic"]],
|
||||
["tier", tierFilter, ["static"]],
|
||||
[
|
||||
"action",
|
||||
actionFilter,
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const packageRoot = fileURLToPath(new URL("../", import.meta.url));
|
||||
const root = fileURLToPath(new URL("../../../", import.meta.url));
|
||||
const manifestPath = `${packageRoot}dynamic-support.json`;
|
||||
const headerPath = `${root}packages/runtime/src/scr_island_manifest.h`;
|
||||
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
||||
|
||||
function wrap(text, width = 88) {
|
||||
const out = [];
|
||||
for (let i = 0; i < text.length; i += width) out.push(text.slice(i, i + width));
|
||||
return out;
|
||||
}
|
||||
|
||||
function render() {
|
||||
const lines = [
|
||||
"/* Generated by @internal/compatibility from dynamic-support.json.",
|
||||
" * Do not edit: the manifest is the implementation-owned builtin registry. */",
|
||||
"static const struct {",
|
||||
" const char *name;",
|
||||
" const char *exports;",
|
||||
"} isl_builtins[] = {",
|
||||
];
|
||||
for (const [module, entry] of Object.entries(manifest.modules)) {
|
||||
const chunks = wrap(entry.exports.join(","));
|
||||
lines.push(` {"node:${module}",`);
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
lines.push(` "${chunks[i]}"${i === chunks.length - 1 ? "}," : ""}`);
|
||||
}
|
||||
}
|
||||
lines.push("};", "");
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
const rendered = render();
|
||||
if (process.argv.includes("--check")) {
|
||||
if (readFileSync(headerPath, "utf8") !== rendered) {
|
||||
throw new Error("scr_island_manifest.h is stale; run 'pnpm node-compat'");
|
||||
}
|
||||
console.log(`island manifest is current (${Object.keys(manifest.modules).length} modules)`);
|
||||
} else {
|
||||
writeFileSync(headerPath, rendered);
|
||||
console.log(
|
||||
`wrote packages/runtime/src/scr_island_manifest.h (${Object.keys(manifest.modules).length} modules)`,
|
||||
);
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
* Node owns the denominator: the pinned index.md names every API chapter and all.json
|
||||
* supplies the structured API tree (classes, methods, properties, events,
|
||||
* globals, overloads, and conceptual sections). scriptc owns the projection:
|
||||
* the compiler surface/dedicated manifests and the runtime island manifest.
|
||||
* the compiler surface and dedicated native compatibility manifests.
|
||||
* Anything those sources do not prove is either "not-implemented" (when a
|
||||
* known implementation surface has no matching implementation) or
|
||||
* "unreviewed" (when no compatibility subsystem owns the classification).
|
||||
@@ -27,7 +27,6 @@ const publicOutputPath = `${root}docs/src/generated/node-v24-compatibility.json`
|
||||
const publicMetaOutputPath = `${root}docs/src/generated/node-v24-compatibility-meta.json`;
|
||||
const compilerManifestPath = `${root}packages/compiler/surface-manifest.json`;
|
||||
const compilerCompatPath = `${packageRoot}static-support.json`;
|
||||
const islandManifestPath = `${packageRoot}dynamic-support.json`;
|
||||
const pinPath = `${packageRoot}node-v24.json`;
|
||||
const pin = JSON.parse(readFileSync(pinPath, "utf8"));
|
||||
const NODE_BASE = pin.markdownBaseUrl;
|
||||
@@ -297,46 +296,14 @@ function scopeOf(row) {
|
||||
|
||||
function classificationContext() {
|
||||
const compilerCompat = JSON.parse(readFileSync(compilerCompatPath, "utf8"));
|
||||
const island = JSON.parse(readFileSync(islandManifestPath, "utf8"));
|
||||
for (const [chapter, status] of Object.entries(compilerCompat.chapterPolicies)) {
|
||||
validateStatus(status, `static chapter policy '${chapter}'`);
|
||||
}
|
||||
for (const [chapter, status] of Object.entries(island.chapterPolicies)) {
|
||||
validateStatus(status, `dynamic chapter policy '${chapter}'`);
|
||||
}
|
||||
for (const [module, entry] of Object.entries(island.modules)) {
|
||||
const exports = new Set(entry.exports);
|
||||
for (const name of entry.refused ?? []) {
|
||||
if (!exports.has(name))
|
||||
throw new Error(`island module '${module}' marks non-export '${name}' refused`);
|
||||
}
|
||||
const members = new Set(entry.members ?? []);
|
||||
for (const name of entry.refusedMembers ?? []) {
|
||||
if (members.has(name))
|
||||
throw new Error(
|
||||
`island module '${module}' marks member '${name}' both supported and refused`,
|
||||
);
|
||||
}
|
||||
validateEvidence(entry.evidence, `island module '${module}'`);
|
||||
}
|
||||
const supportedGlobals = new Set(island.globals.supported);
|
||||
const npmSupportedGlobals = new Set(island.globals.npmSupported ?? []);
|
||||
for (const name of npmSupportedGlobals) {
|
||||
if (supportedGlobals.has(name))
|
||||
throw new Error(`island global '${name}' is both always-supported and npm-supported`);
|
||||
}
|
||||
for (const name of island.globals.absent) {
|
||||
if (supportedGlobals.has(name) || npmSupportedGlobals.has(name))
|
||||
throw new Error(`island global '${name}' is both supported and absent`);
|
||||
}
|
||||
validateEvidence(island.globals.evidence, "island globals");
|
||||
return {
|
||||
manifest: manifestIndex(),
|
||||
compilerCompat,
|
||||
dedicated: dedicatedIndex(compilerCompat),
|
||||
staticFeatures: featureEntries(compilerCompat),
|
||||
dynamicFeatures: featureEntries(island),
|
||||
island,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -387,78 +354,6 @@ function classifyStatic(row, chapter, ctx, parentSignature) {
|
||||
return tier("unreviewed", "");
|
||||
}
|
||||
|
||||
function classifyDynamic(row, chapter, ctx, parentSignature) {
|
||||
if (row.depth === 0) return tier("unreviewed", "", { source: "chapter-summary" });
|
||||
if (row.scope === "documentation" || row.scope === "configuration")
|
||||
return tier("not-applicable", "", { source: row.scope });
|
||||
const feature = featureOf(row, ctx.dynamicFeatures, parentSignature);
|
||||
if (feature)
|
||||
return tier(feature.status, "", {
|
||||
source: `island-feature:${row.chapter}.${row.apiSymbol}`,
|
||||
tests: feature.evidence,
|
||||
});
|
||||
|
||||
if (row.chapter === "globals") {
|
||||
if (ctx.island.globals.supported.includes(row.name)) {
|
||||
return tier("partial", "", {
|
||||
source: `island-global:${row.name}`,
|
||||
tests: ctx.island.globals.evidence,
|
||||
});
|
||||
}
|
||||
if (ctx.island.globals.npmSupported?.includes(row.name)) {
|
||||
return tier("partial", "", {
|
||||
source: `island-npm-global:${row.name}`,
|
||||
tests: ctx.island.globals.evidence,
|
||||
});
|
||||
}
|
||||
if (ctx.island.globals.absent.includes(row.name)) {
|
||||
return tier("not-implemented", "", {
|
||||
source: `island-global-absent:${row.name}`,
|
||||
tests: ctx.island.globals.evidence,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const modules = pin.chapterModules[row.chapter] ?? [];
|
||||
for (const module of modules) {
|
||||
const moduleEntry = ctx.island.modules[module];
|
||||
const direct = directExportName(row, module);
|
||||
if (!moduleEntry) continue;
|
||||
if (moduleEntry.refusedMembers?.includes(row.apiSymbol)) {
|
||||
return tier("refused", "", { source: `island-member-stub:node:${module}.${row.apiSymbol}` });
|
||||
}
|
||||
if (moduleEntry.members?.includes(row.apiSymbol)) {
|
||||
return tier("partial", "", {
|
||||
source: `island-member:node:${module}.${row.apiSymbol}`,
|
||||
tests: moduleEntry.evidence,
|
||||
});
|
||||
}
|
||||
if (direct && moduleEntry.refused?.includes(direct)) {
|
||||
return tier("refused", "", { source: `island-stub:node:${module}.${direct}` });
|
||||
}
|
||||
if (direct && moduleEntry.exports.includes(direct)) {
|
||||
return tier("partial", "", {
|
||||
source: `island-export:node:${module}.${direct}`,
|
||||
tests: moduleEntry.evidence,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const policy = ctx.island.chapterPolicies[row.chapter];
|
||||
if (policy && policy !== "unreviewed") {
|
||||
return tier(policy, "", { source: `island-chapter-policy:${row.chapter}` });
|
||||
}
|
||||
if (modules.length > 0 && modules.every((module) => ctx.island.modules[module] === undefined)) {
|
||||
return tier("not-implemented", "", {
|
||||
source: `unshimmed:${modules.map((module) => `node:${module}`).join(",")}`,
|
||||
});
|
||||
}
|
||||
if (modules.length > 0 || row.chapter === "globals" || row.chapter === "errors") {
|
||||
return tier("not-implemented", "", { source: `island-unmatched:${row.chapter}` });
|
||||
}
|
||||
return tier("unreviewed", "");
|
||||
}
|
||||
|
||||
function treeSize(node) {
|
||||
let size = 1;
|
||||
for (const field of CHILD_FIELDS) for (const child of node[field] ?? []) size += treeSize(child);
|
||||
@@ -502,7 +397,6 @@ function flattenChapter(rootNode, chapter, ctx) {
|
||||
row.scope = scopeOf(row);
|
||||
const parentSignature = parents.at(-1)?.textRaw ?? parents.at(-1)?.name;
|
||||
row.static = classifyStatic(row, chapter, ctx, parentSignature);
|
||||
row.dynamic = classifyDynamic(row, chapter, ctx, parentSignature);
|
||||
rows.push(row);
|
||||
for (const field of CHILD_FIELDS) {
|
||||
for (const child of node[field] ?? []) walk(child, [...parents, node], depth + 1, stability);
|
||||
@@ -530,7 +424,7 @@ function summarizeTier(rows, key, chapterPolicy) {
|
||||
const tests = [...new Set(descendants.flatMap((tier) => tier.tests ?? []))].sort();
|
||||
if (statuses.size === 0 && chapterPolicy) {
|
||||
return tier(chapterPolicy, "", {
|
||||
source: `${key === "static" ? "compiler" : "island"}-chapter-policy:${rows[0].chapter}`,
|
||||
source: `compiler-chapter-policy:${rows[0].chapter}`,
|
||||
});
|
||||
}
|
||||
if (statuses.size === 1)
|
||||
@@ -618,18 +512,12 @@ function buildSnapshot({ allJson, indexMarkdown, allSha256, indexSha256 }) {
|
||||
"static",
|
||||
ctx.compilerCompat.chapterPolicies[link.slug],
|
||||
);
|
||||
chapterRows[0].dynamic = summarizeTier(
|
||||
chapterRows,
|
||||
"dynamic",
|
||||
ctx.island.chapterPolicies[link.slug],
|
||||
);
|
||||
chapters.push({
|
||||
slug: link.slug,
|
||||
title: link.title,
|
||||
entries: chapterRows.length,
|
||||
apiEntries: chapterRows.filter((row) => row.scope === "api").length,
|
||||
static: statusCounts(chapterRows, "static"),
|
||||
dynamic: statusCounts(chapterRows, "dynamic"),
|
||||
});
|
||||
rows.push(...chapterRows);
|
||||
}
|
||||
@@ -640,7 +528,7 @@ function buildSnapshot({ allJson, indexMarkdown, allSha256, indexSha256 }) {
|
||||
ids.add(row.id);
|
||||
}
|
||||
return {
|
||||
schemaVersion: 4,
|
||||
schemaVersion: 5,
|
||||
nodeVersion: pin.version,
|
||||
nodeTag: pin.tag,
|
||||
nodeCommit: pin.commit,
|
||||
@@ -652,7 +540,6 @@ function buildSnapshot({ allJson, indexMarkdown, allSha256, indexSha256 }) {
|
||||
supportManifests: {
|
||||
compilerSurface: "packages/compiler/surface-manifest.json",
|
||||
compilerDedicated: "internal/compatibility/static-support.json",
|
||||
dynamicIsland: "internal/compatibility/dynamic-support.json",
|
||||
},
|
||||
chapters,
|
||||
rows,
|
||||
@@ -669,8 +556,6 @@ function publicDetail(tier) {
|
||||
if (tier.status === "unreviewed") return "Not yet classified; tracked as open parity work.";
|
||||
if (source.startsWith("surface-manifest:unsupported:"))
|
||||
return "Explicitly rejected by a named compiler diagnostic.";
|
||||
if (source.startsWith("surface-manifest:dynamic-only:"))
|
||||
return "Explicitly refused in static code; this form requires the dynamic island.";
|
||||
if (
|
||||
/^surface-manifest:node-builtin\.fs\.(?:statfsSync|promises\.statfs)$/.test(source) ||
|
||||
source === "compiler-feature:fs.fs.statfs"
|
||||
@@ -818,97 +703,6 @@ function publicDetail(tier) {
|
||||
}
|
||||
if (source.startsWith("compiler-unmatched:"))
|
||||
return "No static compiler lowering is registered for this API yet.";
|
||||
if (source.startsWith("island-export:"))
|
||||
return "Available through the embedded Node compatibility shim; exact member and overload coverage may be narrower.";
|
||||
if (source.startsWith("island-member:"))
|
||||
return "Implemented by the embedded Node compatibility shim.";
|
||||
if (source.startsWith("island-member-stub:"))
|
||||
return "Exposed only as an explicit throwing or rejecting compatibility stub.";
|
||||
if (source.startsWith("island-stub:"))
|
||||
return "Exposed only as an explicit throwing or rejecting compatibility stub.";
|
||||
if (source.startsWith("island-global:"))
|
||||
return "Provided by the embedded engine's web/global compatibility layer.";
|
||||
if (source.startsWith("island-npm-global:"))
|
||||
return "Provided when the embedded npm module bootstrap runs; absent from standalone dynamic islands.";
|
||||
if (source.startsWith("island-global-absent:"))
|
||||
return "Verified absent from the embedded engine's web/global compatibility layer.";
|
||||
if (source.startsWith("unshimmed:"))
|
||||
return "No dynamic-island shim exists for this Node module yet.";
|
||||
if (source.startsWith("island-chapter-policy:")) {
|
||||
if (tier.status === "by-design")
|
||||
return "Intentionally outside the dynamic-island execution model.";
|
||||
if (tier.status === "not-applicable")
|
||||
return "This Node executable or embedding feature does not map to dynamic-island code.";
|
||||
return "No dynamic-island implementation exists for this Node API family yet.";
|
||||
}
|
||||
if (source.startsWith("island-feature:")) {
|
||||
if (/^island-feature:assert\.assert\.doesNot(?:Throw|Reject)$/.test(source)) {
|
||||
return "Supports no-error assertions over callbacks, promises, and promise-like objects, with Error classes, regular expressions, validation functions, and Node-compatible failure metadata for the tested forms.";
|
||||
}
|
||||
if (source.startsWith("island-feature:util.util.types."))
|
||||
return "Checks embedded engine brands without invoking getters or proxy traps, including spoofed tags and prototypes, subclasses, generators, iterators, and revoked proxies.";
|
||||
if (source === "island-feature:util.util.styleText")
|
||||
return "Applies standard styles and aliases with nested ANSI resets, stream validation, color environment settings, and tested option and conversion hooks; terminal metadata and warnings follow the embedded runtime.";
|
||||
if (source === "island-feature:util.util.stripVTControlCharacters")
|
||||
return "Removes ANSI control sequences with the Node matcher and validates non-string arguments.";
|
||||
if (source === "island-feature:util.util.toUSVString")
|
||||
return "Replaces unpaired surrogate code units and preserves Node string coercion, custom conversion hooks, and conversion errors.";
|
||||
if (source.startsWith("island-feature:diagnostics_channel."))
|
||||
return "Publishes synchronous, promise, and callback tracing lifecycle events with result/error identity, subscriber management, and store bindings. Bound stores follow the embedded engine's synchronous AsyncLocalStorage model and do not propagate across awaits.";
|
||||
if (source.startsWith("island-feature:timers.timersPromises."))
|
||||
return "Provides cancellable promise timers, buffered interval iteration, scheduler waits and yields, and ref options over the native event loop; delay warnings remain outside this subset.";
|
||||
if (source.startsWith("island-feature:timers."))
|
||||
return "Provides timer and immediate handles with reference control, refresh, cancellation, and disposal over the native event loop.";
|
||||
if (source === "island-feature:events.events.once")
|
||||
return "Waits for EventEmitter or EventTarget events with AbortSignal cancellation, Node abort errors, and listener cleanup.";
|
||||
if (source === "island-feature:events.events.on")
|
||||
return "Iterates EventEmitter or EventTarget events with buffered delivery, close events, cancellation, watermark backpressure, and listener cleanup.";
|
||||
if (source === "island-feature:events.events.addAbortListener")
|
||||
return "Registers disposable abort listeners that run even when ordinary propagation is stopped; already-aborted signals schedule the callback in a microtask.";
|
||||
if (source.startsWith("island-feature:events.")) {
|
||||
return "Implements EventEmitter listener lifecycle, symbol event names, filtered listener counts, and error monitoring; rejection capture and listener warnings remain outside this subset.";
|
||||
}
|
||||
if (/^island-feature:util\.util\.(?:promisify|callbackify)$/.test(source)) {
|
||||
return "Adapts callbacks and promises with receiver preservation, custom promisify hooks, function metadata, and Node-compatible argument and rejection handling; scheduling follows the island microtask queue.";
|
||||
}
|
||||
if (/^island-feature:util\.(?:util\.)?parseArgs$/.test(source)) {
|
||||
return "Parses command-line options, positionals, defaults, and tokens with Node-compatible configuration validation.";
|
||||
}
|
||||
if (/^island-feature:buffer\.buffer\.(?:isAscii|isUtf8)$/.test(source)) {
|
||||
return "Validates bytes in ArrayBuffer and typed-array views with Node-compatible argument errors.";
|
||||
}
|
||||
if (source === "island-feature:buffer.buffer.transcode") {
|
||||
return "Converts Buffer and Uint8Array inputs among ASCII, Latin-1, UTF-8, and UTF-16LE, including encoding aliases and conversion errors.";
|
||||
}
|
||||
if (source === "island-feature:util.util.getSystemErrorMap") {
|
||||
return "Returns a fresh mutable Map of platform-specific error codes, names, and messages.";
|
||||
}
|
||||
if (source === "island-feature:util.util.isDeepStrictEqual") {
|
||||
return "Compares tested object, collection, cycle, byte-view, and builtin-value families with Node 24 prototype skipping; some exotic object brands remain unsupported.";
|
||||
}
|
||||
if (/^island-feature:util\.util\.getSystemError(?:Name|Message)$/.test(source)) {
|
||||
return "Converts platform-specific negative error codes to names or messages, with Node-compatible validation for common argument forms.";
|
||||
}
|
||||
if (source === "island-feature:url.url.fileURLToPathBuffer") {
|
||||
return "Converts file URLs to raw path bytes with POSIX or Windows options, preserving invalid UTF-8 and malformed escapes; internationalized hostnames remain unsupported.";
|
||||
}
|
||||
if (/^island-feature:url\.url\.(?:fileURLToPath|pathToFileURL)$/.test(source)) {
|
||||
return "Supports file-path conversion with POSIX or Windows options for the tested string and URL forms; internationalized hostnames remain unsupported.";
|
||||
}
|
||||
if (source === "island-feature:util.textEncoder.encodeInto") {
|
||||
return "Writes complete UTF-8 characters into island-owned Uint8Array or Buffer views; byte arrays received from native code are copied.";
|
||||
}
|
||||
if (/^island-feature:util\.(?:encoding|fatal|ignoreBOM)$/.test(source)) {
|
||||
return "Codec properties expose UTF-8 and decoder flags; other decoder encodings remain unsupported in the island.";
|
||||
}
|
||||
if (tier.status === "not-applicable")
|
||||
return "Node configuration or documentation that does not map to an island runtime API.";
|
||||
if (tier.status === "not-implemented")
|
||||
return "Not implemented in the embedded module-loader subset yet.";
|
||||
return "Implemented for the documented embedded module-loader subset.";
|
||||
}
|
||||
if (source.startsWith("island-unmatched:"))
|
||||
return "This API is not implemented by the dynamic-island shim.";
|
||||
if (source === "documentation") return "Documentation section; not a runtime API.";
|
||||
if (source === "configuration")
|
||||
return "Node command-line or configuration entry; not a runtime API.";
|
||||
@@ -930,7 +724,7 @@ const PUBLIC_VERIFICATION = {
|
||||
},
|
||||
"explicit-refusal": {
|
||||
label: "Explicit refusal",
|
||||
detail: "Backed by a named compiler refusal or an explicit dynamic throwing or rejecting stub.",
|
||||
detail: "Backed by a named compiler refusal.",
|
||||
},
|
||||
"verified-absence": {
|
||||
label: "Verified absence",
|
||||
@@ -972,16 +766,12 @@ function verificationBasis(tier) {
|
||||
if (source === "derived:descendants") basis = "derived";
|
||||
else if (tier.status === "supported" || tier.status === "partial") basis = "test-backed";
|
||||
else if (tier.status === "refused") basis = "explicit-refusal";
|
||||
else if (source.startsWith("island-global-absent:")) basis = "verified-absence";
|
||||
else if (tier.status === "by-design") basis = "architectural-policy";
|
||||
else if (tier.status === "not-applicable") basis = "not-applicable";
|
||||
else if (tier.status === "unreviewed") basis = "unreviewed";
|
||||
else if (
|
||||
tier.status === "not-implemented" &&
|
||||
(source.startsWith("compiler-chapter-policy:") ||
|
||||
source.startsWith("compiler-feature:") ||
|
||||
source.startsWith("island-chapter-policy:") ||
|
||||
source.startsWith("island-feature:"))
|
||||
(source.startsWith("compiler-chapter-policy:") || source.startsWith("compiler-feature:"))
|
||||
)
|
||||
basis = "declared-gap";
|
||||
else if (tier.status === "not-implemented") basis = "registry-gap";
|
||||
@@ -1026,12 +816,12 @@ function backlogAction(tier) {
|
||||
|
||||
function buildBacklog(snapshot) {
|
||||
const tasks = [];
|
||||
const tierItems = { static: {}, dynamic: {} };
|
||||
const tierItems = { static: {} };
|
||||
const priorities = { high: 0, normal: 0, low: 0 };
|
||||
for (const row of snapshot.rows) {
|
||||
if (row.depth === 0 || row.scope !== "api") continue;
|
||||
const tiers = {};
|
||||
for (const key of ["static", "dynamic"]) {
|
||||
for (const key of ["static"]) {
|
||||
const action = backlogAction(row[key]);
|
||||
if (action === null) continue;
|
||||
tierItems[key][action] = (tierItems[key][action] ?? 0) + 1;
|
||||
@@ -1065,9 +855,7 @@ function buildBacklog(snapshot) {
|
||||
nodeCommit: snapshot.nodeCommit,
|
||||
summary: {
|
||||
taskCount: tasks.length,
|
||||
tierItemCount:
|
||||
Object.values(tierItems.static).reduce((sum, count) => sum + count, 0) +
|
||||
Object.values(tierItems.dynamic).reduce((sum, count) => sum + count, 0),
|
||||
tierItemCount: Object.values(tierItems.static).reduce((sum, count) => sum + count, 0),
|
||||
tiers: tierItems,
|
||||
priorities,
|
||||
},
|
||||
@@ -1080,8 +868,7 @@ function publicExclusion(row) {
|
||||
if (row.scope === "metadata") return "metadata";
|
||||
if (row.scope === "configuration") return "configuration";
|
||||
if (row.scope !== "api") return "non-api";
|
||||
if (row.static.status === "not-applicable" && row.dynamic.status === "not-applicable")
|
||||
return "not-applicable";
|
||||
if (row.static.status === "not-applicable") return "not-applicable";
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1118,14 +905,12 @@ function projectPublicMatrix(snapshot) {
|
||||
continue;
|
||||
}
|
||||
root.static = summarizeTier(chapterRows, "static");
|
||||
root.dynamic = summarizeTier(chapterRows, "dynamic");
|
||||
chapters.push({
|
||||
slug: chapter.slug,
|
||||
title: chapter.title,
|
||||
entries: chapterRows.length,
|
||||
apiEntries: chapterRows.length,
|
||||
static: statusCounts(chapterRows, "static"),
|
||||
dynamic: statusCounts(chapterRows, "dynamic"),
|
||||
});
|
||||
rows.push(...chapterRows);
|
||||
}
|
||||
@@ -1170,11 +955,6 @@ function publicSnapshot(snapshot) {
|
||||
detail: publicDetail(row.static),
|
||||
verification: verificationBasis(row.static),
|
||||
},
|
||||
dynamic: {
|
||||
status: row.dynamic.status,
|
||||
detail: publicDetail(row.dynamic),
|
||||
verification: verificationBasis(row.dynamic),
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -1210,12 +990,8 @@ function checkLocal() {
|
||||
const chapter = snapshot.chapters.find((item) => item.slug === row.chapter);
|
||||
const parentSignature = ancestors[row.depth - 1]?.signature;
|
||||
const expectedStatic = classifyStatic(row, chapter, ctx, parentSignature);
|
||||
const expectedDynamic = classifyDynamic(row, chapter, ctx, parentSignature);
|
||||
ancestors[row.depth] = row;
|
||||
if (
|
||||
JSON.stringify(row.static) !== JSON.stringify(expectedStatic) ||
|
||||
JSON.stringify(row.dynamic) !== JSON.stringify(expectedDynamic)
|
||||
) {
|
||||
if (JSON.stringify(row.static) !== JSON.stringify(expectedStatic)) {
|
||||
throw new Error(
|
||||
`generated classification is stale at ${row.id} (${row.signature}); run 'pnpm node-compat'`,
|
||||
);
|
||||
@@ -1238,12 +1014,8 @@ function checkLocal() {
|
||||
for (const row of publicRows) {
|
||||
if (row.depth > 0 && row.scope !== "api")
|
||||
throw new Error(`${row.id} exposes a non-API row in the public matrix`);
|
||||
if (
|
||||
row.depth > 0 &&
|
||||
row.static.status === "not-applicable" &&
|
||||
row.dynamic.status === "not-applicable"
|
||||
) {
|
||||
throw new Error(`${row.id} exposes a row that is not applicable to either compiler tier`);
|
||||
if (row.depth > 0 && row.static.status === "not-applicable") {
|
||||
throw new Error(`${row.id} exposes a row that is not applicable to native compilation`);
|
||||
}
|
||||
}
|
||||
if (readFileSync(backlogOutputPath, "utf8") !== render(buildBacklog(snapshot))) {
|
||||
@@ -1256,7 +1028,7 @@ function checkLocal() {
|
||||
throw new Error("the docs compatibility metadata is stale; run 'pnpm node-compat'");
|
||||
}
|
||||
for (const row of snapshot.rows) {
|
||||
for (const key of ["static", "dynamic"]) {
|
||||
for (const key of ["static"]) {
|
||||
if (
|
||||
(row[key].status === "supported" || row[key].status === "partial") &&
|
||||
(row[key].tests?.length ?? 0) === 0
|
||||
@@ -1265,11 +1037,7 @@ function checkLocal() {
|
||||
}
|
||||
if (row[key].status === "refused") {
|
||||
const source = row[key].evidence ?? "";
|
||||
const explicit =
|
||||
key === "static"
|
||||
? source.startsWith("surface-manifest:unsupported:") ||
|
||||
source.startsWith("surface-manifest:dynamic-only:")
|
||||
: source.startsWith("island-stub:") || source.startsWith("island-member-stub:");
|
||||
const explicit = source.startsWith("surface-manifest:unsupported:");
|
||||
if (!explicit && source !== "derived:descendants") {
|
||||
throw new Error(`${row.id} claims ${key} refused without an explicit refusal source`);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { spawnSync } from "node:child_process";
|
||||
const check = process.argv.slice(2).includes("--check");
|
||||
const args = check ? ["--check"] : [];
|
||||
|
||||
for (const script of ["generate-island-header.mjs", "generate.mjs"]) {
|
||||
for (const script of ["generate.mjs"]) {
|
||||
const result = spawnSync(process.execPath, [`src/${script}`, ...args], { stdio: "inherit" });
|
||||
if (result.error) throw result.error;
|
||||
if (result.status !== 0) process.exit(result.status ?? 1);
|
||||
|
||||
@@ -34,13 +34,13 @@ Builds use a bounded persistent cache. Unchanged source can reuse the validated
|
||||
- `scriptc run <file.ts>` — compile and run
|
||||
- `scriptc coverage <file.ts>` — what compiles statically, and why the rest doesn't
|
||||
|
||||
`scriptc build app.ts --emit=ir|llvm|asm|obj` selects serialized typed IR, textual LLVM IR, target assembly, or a relocatable program object as the one primary artifact. `--emit=exe` is the default. Assembly/object emission uses the matching helper on supported macOS, Linux, and Windows hosts (and produces WASI artifacts when that target is selected). Objects retain undefined `scr_*` runtime references plus the `scr_runtime_abi_v13` compatibility marker; they are not library archives. External consumption is experimental and requires the exact matching runtime. `scriptc build app.ts --print=native-link-info -o app.o` prints the versioned JSON target/runtime/link recipe without performing a link. `--emit=asm|obj --sanitize` is rejected until ASan pipeline parity is available.
|
||||
`scriptc build app.ts --emit=ir|llvm|asm|obj` selects serialized typed IR, textual LLVM IR, target assembly, or a relocatable program object as the one primary artifact. `--emit=exe` is the default. Assembly/object emission uses the matching helper on supported macOS, Linux, and Windows hosts (and produces WASI artifacts when that target is selected). Objects retain undefined `scr_*` runtime references plus the `scr_runtime_abi_v14` compatibility marker; they are not library archives. External consumption is experimental and requires the exact matching runtime. `scriptc build app.ts --print=native-link-info -o app.o` prints the versioned JSON target/runtime/link recipe without performing a link. `--emit=asm|obj --sanitize` is rejected until ASan pipeline parity is available.
|
||||
|
||||
For embedder-hosted modules that are not installed npm packages, coverage can map an exact bare specifier to a local declaration with repeatable `--external-types <specifier=file.d.ts>` options. This is analysis-only: the types unblock application measurement, while runtime module uses remain reported as blockers.
|
||||
|
||||
No annotations, no dialect, no special stdlib: the same TypeScript you run on Node, type-checked by the real TypeScript compiler. Programs outside the static tier can opt into `--dynamic`, which embeds a small JavaScript engine (~620KB) for the parts that can't be static; everything else fails the build with a specific error code and usually a rewrite hint.
|
||||
No annotations, no dialect, no special stdlib: the same TypeScript you run on Node, type-checked by the real TypeScript compiler. Programs compile to native code; unsupported constructs fail the build with a specific error code and usually a rewrite hint.
|
||||
|
||||
WebAssembly is available as a production LLVM target: `SCRIPTC_TARGET=wasm32-wasi scriptc build app.ts`. It emits a WASI Preview 1 `.wasm` module, and `scriptc run` supplies a WASI host. The complete executable language tier—including async, generators, timers, and `--dynamic`—is supported. APIs needing capabilities WASI P1 does not expose (network sockets/fetch, child processes, OS signals, and filesystem watching), sanitizer builds, and native FFI are rejected with `SC3002`. With `--lib --profile`, the target produces a callable Wasm reactor with named host imports and per-instance memory. See [WebAssembly Modules](https://scriptc.dev/wasm) for the embedding contract.
|
||||
WebAssembly is available as a production LLVM target: `SCRIPTC_TARGET=wasm32-wasi scriptc build app.ts`. It emits a WASI Preview 1 `.wasm` module, and `scriptc run` supplies a WASI host. The complete executable language tier—including async, generators, and timers—is supported. APIs needing capabilities WASI P1 does not expose (network sockets/fetch, child processes, OS signals, and filesystem watching), sanitizer builds, and native FFI are rejected with `SC3002`. With `--lib --profile`, the target produces a callable Wasm reactor with named host imports and per-instance memory. See [WebAssembly Modules](https://scriptc.dev/wasm) for the embedding contract.
|
||||
|
||||
Native code can be called through an explicit, link-time C ABI manifest: declare the function signature in TypeScript, bind it to a C symbol, and build with `--ffi <manifest.json>`. See the [Native FFI guide](https://scriptc.dev/ffi).
|
||||
|
||||
|
||||
@@ -122,7 +122,6 @@ async function tryFastPath(): Promise<number | null> {
|
||||
outPath,
|
||||
emitIr: values["emit-ir"],
|
||||
sanitize: values.sanitize,
|
||||
dynamic: values.dynamic,
|
||||
backend: "llvm",
|
||||
...(optimization === "dev" || optimization === "speed" ? { optimization } : {}),
|
||||
...(values.strip ? { strip: true as const } : {}),
|
||||
@@ -203,9 +202,7 @@ async function coverageFastPath(
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const hit = await startup
|
||||
.readCoverageVerdict(resolve(inputArg), { dynamic: values.dynamic })
|
||||
.catch(() => null);
|
||||
const hit = await startup.readCoverageVerdict(resolve(inputArg), {}).catch(() => null);
|
||||
if (hit === null) return null;
|
||||
const { coverage, sourceTexts } = hit;
|
||||
if (print === "diagnostics") {
|
||||
|
||||
@@ -72,7 +72,7 @@ describe.runIf(supported)("macOS arm64 native link info", () => {
|
||||
minimum_os: "14.0",
|
||||
},
|
||||
program: { object, entry_symbol: "main" },
|
||||
runtime_abi: { version: 13, marker: "scr_runtime_abi_v13" },
|
||||
runtime_abi: { version: 14, marker: "scr_runtime_abi_v14" },
|
||||
runtime_pack: {
|
||||
kind: "precompiled",
|
||||
package: "@scriptc/runtime-darwin-arm64",
|
||||
@@ -165,6 +165,6 @@ describe.runIf(supported)("macOS arm64 native link info", () => {
|
||||
() => null,
|
||||
(failure: { stderr?: string }) => failure,
|
||||
);
|
||||
expect(error?.stderr).toContain("scr_runtime_abi_v13");
|
||||
expect(error?.stderr).toContain("scr_runtime_abi_v14");
|
||||
});
|
||||
});
|
||||
|
||||
+2
-36
@@ -15,7 +15,7 @@
|
||||
* ambient OR @types/node — see isStdlibFile/isNodeTypesPath), and
|
||||
* everything else @types/node declares hits the SC2020-family fence at
|
||||
* its use site. scriptc.d.ts holds the always-shipped core (divergence
|
||||
* overrides, comptime, __island_eval, setTimeout — which MERGES with
|
||||
* overrides, comptime, setTimeout — which MERGES with
|
||||
* @types/node's as an overload instead of colliding). */
|
||||
|
||||
/* NodeJS.ErrnoException — the Error shape @types/node attaches to fs and
|
||||
@@ -608,12 +608,6 @@ declare var TextDecoder: {
|
||||
new (label?: string, options?: { fatal?: boolean; ignoreBOM?: boolean }): TextDecoder;
|
||||
};
|
||||
|
||||
/* The WHATWG event surface (Node globals since v15): declared so the
|
||||
* suite's event-plumbing tests typecheck and fence per SITE with the
|
||||
* member's own name. The dynamic island's web prelude implements
|
||||
* Event/CustomEvent/EventTarget/DOMException/AbortController for real;
|
||||
* the static tier has no lowering yet — construction and members fence
|
||||
* honestly (SC2020). */
|
||||
interface Event {
|
||||
readonly type: string;
|
||||
readonly bubbles: boolean;
|
||||
@@ -691,11 +685,6 @@ declare var EventTarget: {
|
||||
readonly prototype: EventTarget;
|
||||
};
|
||||
|
||||
/* MessagePort/MessageChannel — the worker_threads pair Node exposes as
|
||||
* globals (v15+). The island's worker_threads shim implements the
|
||||
* same-thread subset (postMessage delivers structured-clone copies on a
|
||||
* microtask); the web prelude exposes the classes as engine globals.
|
||||
* Statically declared-not-lowered: per-site fences. */
|
||||
interface MessagePort extends EventTarget {
|
||||
postMessage(value: unknown, transferOrOptions?: unknown): void;
|
||||
start(): void;
|
||||
@@ -718,9 +707,6 @@ declare var MessageChannel: {
|
||||
readonly prototype: MessageChannel;
|
||||
};
|
||||
|
||||
/* AbortSignal, the native fetch-cancellation slice. The three statics,
|
||||
* state/reason reads, throwIfAborted, and abort listeners all lower in
|
||||
* static builds; dynamic builds use the island's Web implementation. */
|
||||
interface AbortSignal extends EventTarget {
|
||||
readonly aborted: boolean;
|
||||
readonly reason: unknown;
|
||||
@@ -820,7 +806,7 @@ interface Response {
|
||||
json(): Promise<unknown>;
|
||||
text(): Promise<string>;
|
||||
/* The whole body as bytes: bytes() answers a Uint8Array in both tiers.
|
||||
* arrayBuffer() remains dynamic-only because static programs have no
|
||||
* arrayBuffer() remains unsupported because static programs have no
|
||||
* free-standing ArrayBuffer representation; the compiler diagnoses its
|
||||
* direct use and points at bytes(). */
|
||||
arrayBuffer(): Promise<ArrayBuffer>;
|
||||
@@ -1053,26 +1039,6 @@ declare module "perf_hooks" {
|
||||
* performance.now() either way. */
|
||||
declare var performance: import("node:perf_hooks").Performance;
|
||||
|
||||
/* node:module. createRequire's lowered shape is the version/config-
|
||||
* reading pattern real CLIs ship: a const binding over
|
||||
* createRequire(import.meta.url) (or __filename) whose require calls
|
||||
* take STATIC string literals — the indirection erases at compile time.
|
||||
* A builtin spec makes the binding a namespace import in const clothing;
|
||||
* relative and package-import program modules use the compiled module
|
||||
* graph; a relative .json document bakes and parses (JSON.parse's
|
||||
* `unknown` stance — validate with a checked cast); an installed npm
|
||||
* package loads through the island's require-condition entry under
|
||||
* --dynamic, or through the compiled graph under --npm-static; a bare name
|
||||
* nothing installed resolves compiles to Node's catchable MODULE_NOT_FOUND
|
||||
* throw (the optional-dependency try/require pattern).
|
||||
* Dynamic specifiers fence: a compiled binary's module graph is fixed at
|
||||
* build time. builtinModules is the baked Node v24 list (a fresh
|
||||
* mutable array per read where Node ships one frozen singleton);
|
||||
* isBuiltin checks runtime strings against that pinned list, and
|
||||
* syncBuiltinESMExports is a no-op because the static builtin surface is
|
||||
* immutable. Both spellings
|
||||
* name the builtin, like in Node (the builtin wins over the npm package
|
||||
* named "module" for the bare specifier there too). */
|
||||
declare module "node:module" {
|
||||
export function createRequire(filename: string | URL): ScriptcRequire;
|
||||
export const builtinModules: string[];
|
||||
|
||||
+1
-25
@@ -10,42 +10,18 @@
|
||||
* rejected over an override-manufactured error; the affected sites meet the
|
||||
* lowerer's honest fences instead (SC1100's checked-cast family for
|
||||
* JSON.parse results, the executor fences for Promise shapes). The
|
||||
* primitives every world needs (comptime, setTimeout, __island_eval) live
|
||||
* primitives every world needs (comptime, setTimeout) live
|
||||
* in scriptc.d.ts, which ships to BOTH programs. */
|
||||
|
||||
/* `at(): string`, not the lib's `string | undefined`: undefined is
|
||||
* unrepresentable, so an out-of-range at() THROWS a catchable TypeError at
|
||||
* the validated island exit instead of returning undefined (documented
|
||||
* divergence, SEMANTICS.md). */
|
||||
interface String {
|
||||
at(index: number): string;
|
||||
}
|
||||
|
||||
/* Object.values/entries with the actual field-value union, not the lib's
|
||||
* `any` (the lib's precise overloads only cover index-signature and
|
||||
* ArrayLike sources). A PRECISION override, not a divergence: the lowering
|
||||
* emits the field list statically and the values are exactly T[keyof T] —
|
||||
* without this the result would be `any`-typed and drag every downstream
|
||||
* use into the island. An opaque object has no statically known keys;
|
||||
* its actual values remain unknown instead of becoming an impossible
|
||||
* never payload. */
|
||||
interface ObjectConstructor {
|
||||
values<T extends object>(o: T): Array<keyof T extends never ? unknown : T[keyof T]>;
|
||||
entries<T extends object>(o: T): Array<[string, keyof T extends never ? unknown : T[keyof T]]>;
|
||||
}
|
||||
|
||||
/* `parse` returns `unknown`, not the lib's `any` — the dynamic boundary. A
|
||||
* checked cast (`JSON.parse(s) as Config`) validates the value against the
|
||||
* target type at runtime and THROWS on mismatch — the mechanism that makes
|
||||
* trusting TS types sound at data boundaries. Reviver results use the same
|
||||
* checked boundary. A function replacer can omit the root, so that form
|
||||
* returns string | undefined even though the stock lib promises string.
|
||||
*
|
||||
* `stringify` takes `unknown`, not the lib's `any`: the same honest
|
||||
* surface, but the parameter must not CONTEXTUALLY TYPE literal arguments
|
||||
* as `any` (an any-typed slot is the island boundary; `JSON.stringify([1,
|
||||
* 2])` must keep building a static array and serializing it
|
||||
* type-directedly). */
|
||||
interface JSON {
|
||||
parse(text: string): unknown;
|
||||
parse(text: string, reviver: (this: unknown, key: string, value: unknown) => unknown): unknown;
|
||||
|
||||
+1
-9
@@ -5,7 +5,7 @@
|
||||
* without a lowering is a SC2020 diagnostic at its use site. This file
|
||||
* declares only what that ES-only library does not:
|
||||
*
|
||||
* 1. scriptc's own primitives (comptime, __island_eval).
|
||||
* 1. scriptc's own primitives (comptime).
|
||||
* 2. The RequestInfo type alias, supplied by the default DOM lib in ordinary
|
||||
* TypeScript projects but absent from @types/node's globals.
|
||||
* 3. setTimeout/clearTimeout and the Timeout handle (Node/dyn territory the
|
||||
@@ -108,11 +108,3 @@ declare function clearTimeout(handle?: Timeout | number | null | undefined): voi
|
||||
* baked result is exactly what Node would compute at runtime (the
|
||||
* differential harness verifies this with a `comptime = (f) => f()` shim). */
|
||||
declare function comptime<T>(compute: () => T): T;
|
||||
|
||||
/* INTERNAL TESTING HOOK — not part of the supported surface, deliberately
|
||||
* undocumented. Evaluates `code` in the embedded dynamic-island engine and
|
||||
* returns String(result); island exceptions arrive as catchable errors.
|
||||
* Requires --dynamic (a clean diagnostic otherwise). This exists to prove
|
||||
* the engine embed end-to-end in tests; user-facing constructs will lower
|
||||
* to the island through their own paths, not through this. */
|
||||
declare function __island_eval(code: string): string;
|
||||
|
||||
@@ -21,7 +21,7 @@ import { computeTraced as llvmTraced } from "./llvm/shapes.js";
|
||||
const loc = { file: "cycles.ts", start: 0, end: 1 };
|
||||
function module(): IrModule {
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [{ name: "main", locals: [], params: [], returnType: VOID, body: [], loc }],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { IrExpr, IrFunction, IrModule, IrStmt, IrType } from "../ir/ir.js";
|
||||
import { RUNTIME_EMITTER_CLASS, RUNTIME_STREAM_CLASSES } from "../ir/ir.js";
|
||||
import { dynamicFieldStores } from "../ir/int32-slots.js";
|
||||
import { everyExprChild, everyModuleNode, everyStmtChild, everyTypeChild } from "../ir/traverse.js";
|
||||
import { everyExprChild, everyStmtChild, everyTypeChild } from "../ir/traverse.js";
|
||||
|
||||
/** Which shape fields can be written after their object escapes.
|
||||
*
|
||||
@@ -33,7 +33,7 @@ import { everyExprChild, everyModuleNode, everyStmtChild, everyTypeChild } from
|
||||
* in which case no commit can happen; FFI operands and intrinsics other
|
||||
* than publish always count;
|
||||
* - runtime-rooted hierarchies (errors, emitters, streams) are mutable;
|
||||
* - library builds and dynamic-island programs disable the refinement.
|
||||
* - library builds disable the refinement.
|
||||
*
|
||||
* Keys: `object:<hierarchy root>` / `record:<id>` units, fields as
|
||||
* `<unit>\0<field>`. */
|
||||
@@ -50,14 +50,6 @@ export interface CycleMutability {
|
||||
fields: Set<string>;
|
||||
}
|
||||
|
||||
const DYNAMIC_ISLAND_EXPRS = new Set<string>([
|
||||
"jsMarshal",
|
||||
"jsOp",
|
||||
"jsExit",
|
||||
"jsBridgePromise",
|
||||
"dynFromJsval",
|
||||
]);
|
||||
|
||||
/** Expressions whose operands may be converted to checked-dynamic values
|
||||
* (typed-ref capsules) by the backend or runtime. */
|
||||
const DYNAMIC_BOUNDARY_EXPRS = new Set<string>([
|
||||
@@ -90,16 +82,6 @@ export function computeCycleMutability(
|
||||
classes.map((c) => [`%${c.name}.constructor`, c]),
|
||||
);
|
||||
|
||||
// The dynamic island stores engine values in native slots and can reach
|
||||
// any object through them: no refinement.
|
||||
let island = false;
|
||||
everyModuleNode(mod, {
|
||||
expr: (e) => !(island = island || DYNAMIC_ISLAND_EXPRS.has(e.kind)),
|
||||
stmt: () => true,
|
||||
type: (t) => !(island = island || t.kind === "jsval"),
|
||||
});
|
||||
if (island) return { ...result, all: true };
|
||||
|
||||
for (const c of classes) {
|
||||
if (c.runtime || c.name === RUNTIME_EMITTER_CLASS || RUNTIME_STREAM_CLASSES.has(c.name))
|
||||
result.units.add(unitOfClass(c.name));
|
||||
|
||||
@@ -1,29 +1,19 @@
|
||||
/** Runtime requirements derived from the lowered program, shared by compiler hosts. */
|
||||
import { type IrModule } from "../ir/ir.js";
|
||||
import {
|
||||
moduleEmbedsBuiltin,
|
||||
moduleEmbedsCompressedNpm,
|
||||
moduleRuntimeFeatures,
|
||||
} from "../ir/runtime-features.js";
|
||||
import { moduleRuntimeFeatures } from "../ir/runtime-features.js";
|
||||
import { hasForeignFfiCallback } from "./ffi-callbacks.js";
|
||||
import type { NativeLinkFeatures } from "./native-link-info.js";
|
||||
|
||||
export function executableLinkFeatures(mod: IrModule, dynamic: boolean): NativeLinkFeatures {
|
||||
export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures {
|
||||
const features = moduleRuntimeFeatures(mod);
|
||||
return {
|
||||
...(mod.workers ? { workers: true } : {}),
|
||||
dynamic,
|
||||
regex: features.regex,
|
||||
copying: features.copying,
|
||||
textDecoderLegacy: features.legacyTextDecoder,
|
||||
fileHandle: features.fileHandle,
|
||||
fetch: features.fetch,
|
||||
netIsland:
|
||||
moduleEmbedsBuiltin(mod, "node:http") ||
|
||||
moduleEmbedsBuiltin(mod, "node:https") ||
|
||||
moduleEmbedsBuiltin(mod, "node:net") ||
|
||||
moduleEmbedsBuiltin(mod, "node:tls"),
|
||||
zlib: features.zlib || moduleEmbedsCompressedNpm(mod),
|
||||
zlib: features.zlib,
|
||||
assert: features.assert,
|
||||
inspect: features.inspect,
|
||||
dynInvoke: features.dynInvoke,
|
||||
|
||||
@@ -48,7 +48,7 @@ function module(
|
||||
functions: IrModule["functions"],
|
||||
): IrModule {
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
classes,
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
/** Fields that are never written once their object can be observed outside
|
||||
* its own construction (cycle-mutability.ts derives the facts from every
|
||||
* store in the IR, including typed-ref commits from checked-dynamic code,
|
||||
* and disables them for library builds and dynamic islands).
|
||||
* and disables them for library builds).
|
||||
*
|
||||
* Outside constructors, a reference read from such a field stays valid for
|
||||
* as long as its owner object is alive: nothing can replace it and release
|
||||
|
||||
@@ -24,13 +24,11 @@ export async function compilePackedLibrary(
|
||||
): Promise<void> {
|
||||
const optimization = options.optimization ?? "release";
|
||||
const features: NativeLinkFeatures = {
|
||||
dynamic: false,
|
||||
regex: options.regex ?? false,
|
||||
copying: options.copying ?? false,
|
||||
textDecoderLegacy: options.textDecoderLegacy ?? false,
|
||||
fileHandle: false,
|
||||
fetch: false,
|
||||
netIsland: false,
|
||||
zlib: options.zlib ?? false,
|
||||
assert: options.assert ?? false,
|
||||
inspect: options.inspect ?? false,
|
||||
|
||||
@@ -113,7 +113,7 @@ export async function executableLinkerEnvironmentFingerprint(
|
||||
.update("\0")
|
||||
.update(effectiveDriverIdentity)
|
||||
.update("\0");
|
||||
for (const name of ["PATH", "SCRIPTC_FETCH_CURL", "SCRIPTC_TEST_RUNTIME_SRC_DIR"] as const) {
|
||||
for (const name of ["PATH", "SCRIPTC_TEST_RUNTIME_SRC_DIR"] as const) {
|
||||
const value = env[name];
|
||||
hash
|
||||
.update(name)
|
||||
|
||||
@@ -150,7 +150,7 @@ function fixture(helper: IrFunction): IrModule {
|
||||
],
|
||||
};
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
records: [{ id: "cell", fields: [{ name: "x", type: F64 }] }],
|
||||
|
||||
@@ -16,7 +16,7 @@ const loc = { file: "class-membership.ts", start: 0, end: 1 };
|
||||
test("leaf layouts keep direct teardown while recursive layouts share the depth budget", () => {
|
||||
const linked = { kind: "object" as const, className: "Linked" };
|
||||
const mod: IrModule = {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [{ name: "main", params: [], returnType: VOID, locals: [], body: [], loc }],
|
||||
@@ -61,7 +61,7 @@ test("releases split into an inlinable fast path and an out-of-line remainder",
|
||||
// An array of the class itself makes the hierarchy cycle-capable.
|
||||
const kids = { kind: "array" as const, elem: { kind: "object" as const, className: "Node" } };
|
||||
const mod: IrModule = {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [{ name: "main", params: [], returnType: VOID, locals: [], body: [], loc }],
|
||||
@@ -90,7 +90,7 @@ test("ProgramLifetime hierarchies are never freed and take no reference counts",
|
||||
const kids = { kind: "array" as const, elem: { kind: "object" as const, className: "Node" } };
|
||||
const owners = { kind: "array" as const, elem: { kind: "object" as const, className: "Owner" } };
|
||||
const mod: IrModule = {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [{ name: "main", params: [], returnType: VOID, locals: [], body: [], loc }],
|
||||
@@ -142,7 +142,7 @@ test("published ProgramLifetime instances use the published mark every other obj
|
||||
const leaf = { kind: "object" as const, className: "Leaf" };
|
||||
const root = { localId: "root", name: "root", type: node };
|
||||
const mod: IrModule = {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [
|
||||
@@ -234,7 +234,7 @@ function graph() {
|
||||
{ name: "FlippedString", base: "PairString", genericOf: "Flipped" },
|
||||
];
|
||||
const mod: IrModule = {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [],
|
||||
@@ -268,7 +268,7 @@ test.each(["i32", "i64"])(
|
||||
expect(helper).toContain("label %ordinary");
|
||||
expect(helper).toContain("or i1");
|
||||
const ordinary: IrModule = {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [],
|
||||
@@ -283,7 +283,7 @@ test.each(["i32", "i64"])(
|
||||
|
||||
test("unconstructed narrowing layouts may omit abstract slots while live classes require implementations", () => {
|
||||
const mod: IrModule = {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [{ name: "main", params: [], returnType: VOID, locals: [], body: [], loc }],
|
||||
|
||||
@@ -341,11 +341,6 @@ export function programLifetimeRc(sizeType: "i32" | "i64"): string {
|
||||
return sizeType === "i32" ? "-2147483648" : "-9223372036854775808";
|
||||
}
|
||||
|
||||
/** The newFn initialization stores for fields whose type ADMITS undefined
|
||||
* (undefFieldInitLineC's LLVM twin): undefined-armed union fields start
|
||||
* at the interned unit instance; dyn fields start at native undefined;
|
||||
* jsval fields (an `any` class field under --dynamic) start at the engine's
|
||||
* undefined cell. */
|
||||
function undefFieldInits(
|
||||
host: ClassHost,
|
||||
meta: LlClassMeta,
|
||||
@@ -359,11 +354,10 @@ function undefFieldInits(
|
||||
// A nullable-pointer field's zeroed slot already is its unit arm.
|
||||
if (nullable?.get(meta.def.name, f.name)) return;
|
||||
const { index } = classFieldIndex(meta, f.name);
|
||||
if (f.type.kind === "jsval" || f.type.kind === "dyn") {
|
||||
const undefinedFn = f.type.kind === "dyn" ? "scr_dyn_undefined" : "scr_jsval_undefined";
|
||||
host.declare(`declare ptr @${undefinedFn}()`);
|
||||
if (f.type.kind === "dyn") {
|
||||
host.declare("declare ptr @scr_dyn_undefined()");
|
||||
out.push(
|
||||
` %ufv${i} = call ptr @${undefinedFn}()`,
|
||||
` %ufv${i} = call ptr @scr_dyn_undefined()`,
|
||||
` %uf${i} = getelementptr inbounds %${mangleClassStruct(meta.def.name)}, ptr %o, i64 0, i32 ${index}`,
|
||||
` store ptr %ufv${i}, ptr %uf${i} ; ${llvmCommentText(f.name)} starts undefined`,
|
||||
);
|
||||
|
||||
@@ -71,7 +71,7 @@ function fixture(): IrModule {
|
||||
loc,
|
||||
};
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [predicate, consumer, entry],
|
||||
|
||||
@@ -329,7 +329,7 @@ const RECEIVER_FREE_LIB_FNS = new Set<IrLibFn>([
|
||||
|
||||
/** Typed container and string operations. Their runtime paths compare and
|
||||
* copy values but never call back into compiled code, as long as no
|
||||
* operand is a function or a checked/engine value. */
|
||||
* operand is a function or a checked value. */
|
||||
/** Operands of a data intrinsic (receiver first), or null when `e` is not
|
||||
* one. A switch over the kinds keeps this inside the self-hosted subset,
|
||||
* which has no `in` on IR expression unions. */
|
||||
@@ -350,12 +350,12 @@ function dataIntrinsicOperands(e: IrExpr): IrExpr[] | null {
|
||||
|
||||
function opaqueOperand(e: IrExpr): boolean {
|
||||
const kind = e.type.kind;
|
||||
return kind === "func" || kind === "dyn" || kind === "jsval" || kind === "caught";
|
||||
return kind === "func" || kind === "dyn" || kind === "caught";
|
||||
}
|
||||
|
||||
/** The functions that may observe the ambient receiver stack when entered
|
||||
* without a fresh binding: the JS `this` read (libCall dyn.this), async and
|
||||
* generator creation (which snapshot it), dynamic and engine operations,
|
||||
* generator creation (which snapshot it), checked-value operations,
|
||||
* diagnostics-channel stores, and anything outside the admitted kinds; then
|
||||
* every function that can reach one through a direct call, constructor or
|
||||
* virtual dispatch (a backward reachability fixpoint, so recursion such as
|
||||
|
||||
@@ -25,7 +25,7 @@ const index = ref("i", F64);
|
||||
|
||||
function module(name: string, returnType: IrExpr["type"], body: IrStmt[]): IrModule {
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "__main",
|
||||
functions: [
|
||||
|
||||
@@ -70,7 +70,6 @@ export const DYN_KIND = {
|
||||
FUNC: 8,
|
||||
HANDLE: 9,
|
||||
PROMISE: 10,
|
||||
JSVAL: 11 /* SCR_DYN_JSVAL — island values held by reference */,
|
||||
TYPED_REF: 12 /* SCR_DYN_TYPED_REF — static Web-stream transit capsule */,
|
||||
PROXY: 13,
|
||||
BIGINT: 14,
|
||||
@@ -2621,7 +2620,6 @@ export class LlDyn {
|
||||
DYN_KIND.FUNC,
|
||||
DYN_KIND.HANDLE,
|
||||
DYN_KIND.PROMISE,
|
||||
DYN_KIND.JSVAL,
|
||||
DYN_KIND.TYPED_REF,
|
||||
DYN_KIND.PROXY,
|
||||
DYN_KIND.BIGINT,
|
||||
@@ -2636,14 +2634,6 @@ export class LlDyn {
|
||||
host.declare(`declare void @scr_dyn_proxy_unsupported(ptr)`);
|
||||
B.line(`call void @scr_dyn_proxy_unsupported(ptr ${host.cstr("string conversion")})`);
|
||||
B.br(done);
|
||||
B.startBlock(labels.get(DYN_KIND.JSVAL)!);
|
||||
{
|
||||
// Island-held: the engine's own ToString (a bridged failure
|
||||
// leaves the exception pending and appends nothing).
|
||||
host.declare(`declare void @scr_dyn_isl_tostr_buf(ptr, ptr)`);
|
||||
B.line(`call void @scr_dyn_isl_tostr_buf(ptr %b, ptr %d)`);
|
||||
B.br(done);
|
||||
}
|
||||
B.startBlock(labels.get(DYN_KIND.TYPED_REF)!);
|
||||
{
|
||||
host.declare(`declare ptr @scr_dyn_typed_ref_materialize(ptr)`);
|
||||
@@ -3209,9 +3199,7 @@ export class LlDyn {
|
||||
B.terminate(`ret ptr ${r}`);
|
||||
B.startBlock(lNext);
|
||||
}
|
||||
// ISLAND-held receivers: o[k] reads the REAL engine property (getters
|
||||
// included, throws bridged catchably) and the result wraps back
|
||||
// scalar-normalized — the routed keyed read that retired the fence.
|
||||
|
||||
{
|
||||
const isProxy = B.tmp();
|
||||
B.line(`${isProxy} = icmp eq i32 ${kd}, ${DYN_KIND.PROXY}`);
|
||||
@@ -3225,19 +3213,7 @@ export class LlDyn {
|
||||
B.terminate(`ret ptr ${r}`);
|
||||
B.startBlock(lNext);
|
||||
}
|
||||
{
|
||||
const isJv = B.tmp();
|
||||
B.line(`${isJv} = icmp eq i32 ${kd}, ${DYN_KIND.JSVAL}`);
|
||||
const lJv = B.newLabel("kg.jv");
|
||||
const lNext = B.newLabel("kg.n");
|
||||
B.condBr(isJv, lJv, lNext);
|
||||
B.startBlock(lJv);
|
||||
host.declare(`declare ptr @scr_dyn_isl_key_get(ptr, ptr)`);
|
||||
const r = B.tmp();
|
||||
B.line(`${r} = call ptr @scr_dyn_isl_key_get(ptr %d, ptr %k)`);
|
||||
B.terminate(`ret ptr ${r}`);
|
||||
B.startBlock(lNext);
|
||||
}
|
||||
|
||||
// OBJ: the own member or the inherited builtin Error constructor.
|
||||
{
|
||||
const isObj = B.tmp();
|
||||
@@ -3607,23 +3583,7 @@ export class LlDyn {
|
||||
B.terminate(`ret ptr ${out}`);
|
||||
B.startBlock(lNext);
|
||||
}
|
||||
// Island-held sources keep the engine's iterator and IteratorClose
|
||||
// behavior while returning checked values for the destructuring slots.
|
||||
{
|
||||
const isJv = B.tmp();
|
||||
B.line(`${isJv} = icmp eq i32 ${kd}, ${DYN_KIND.JSVAL}`);
|
||||
const lJv = B.newLabel("din.jv");
|
||||
const lNotJv = B.newLabel("din.njv");
|
||||
B.condBr(isJv, lJv, lNotJv);
|
||||
B.startBlock(lJv);
|
||||
host.declare(`declare ptr @scr_dyn_jsval_iter_n(ptr, double)`);
|
||||
const count = B.tmp();
|
||||
const out = B.tmp();
|
||||
B.line(`${count} = sitofp ${host.sizeType} %n to double`);
|
||||
B.line(`${out} = call ptr @scr_dyn_jsval_iter_n(ptr %d, double ${count})`);
|
||||
B.terminate(`ret ptr ${out}`);
|
||||
B.startBlock(lNotJv);
|
||||
}
|
||||
|
||||
const okA = B.tmp();
|
||||
const okS = B.tmp();
|
||||
const okB = B.tmp();
|
||||
@@ -3861,14 +3821,7 @@ export class LlDyn {
|
||||
B.line(`${r} = call ptr @${box}(ptr ${expr}, ptr null)`);
|
||||
return r;
|
||||
}
|
||||
if (t.kind === "jsval") {
|
||||
// An island value wraps by reference (scalar-normalizing) — the
|
||||
// jsval-returning callback shape of the routed-dispatch lane.
|
||||
this.host.declare(`declare ptr @scr_dyn_from_jsval(ptr)`);
|
||||
const r = B.tmp();
|
||||
B.line(`${r} = call ptr @scr_dyn_from_jsval(ptr ${expr})`);
|
||||
return r;
|
||||
}
|
||||
|
||||
const r = B.tmp();
|
||||
B.line(`${r} = call ptr @${this.toDynHelper(t)}(${this.valTy(t)} ${expr})`);
|
||||
return r;
|
||||
@@ -3964,26 +3917,6 @@ export class LlDyn {
|
||||
B.line(`${ad} = load ptr, ptr ${adSlot}`);
|
||||
if (p.kind === "dyn") {
|
||||
argNames.push(this.retainDyn(B, ad));
|
||||
} else if (p.kind === "jsval") {
|
||||
// A checker-'any' param: the dyn argument enters the island —
|
||||
// wrapped cells unwrap by reference, data deep-copies, boxed
|
||||
// functions cross through the host shim; a kind with no crossing
|
||||
// throws the catchable TypeError (null + pending).
|
||||
host.declare(`declare ptr @scr_jsval_from_dyn(ptr)`);
|
||||
const a = B.tmp();
|
||||
B.line(`${a} = call ptr @scr_jsval_from_dyn(ptr ${ad})`);
|
||||
const isNull = B.tmp();
|
||||
B.line(`${isNull} = icmp eq ptr ${a}, null`);
|
||||
const lFail = B.newLabel("dfk.jf");
|
||||
const lOk = B.newLabel("dfk.jo");
|
||||
B.condBr(isNull, lFail, lOk);
|
||||
B.startBlock(lFail);
|
||||
t.params.slice(0, i).forEach((q, j) => {
|
||||
if (isRefCounted(q)) B.line(`call void ${releaseSym(host, q)}(ptr ${argNames[j]})`);
|
||||
});
|
||||
B.terminate(`ret ptr null`);
|
||||
B.startBlock(lOk);
|
||||
argNames.push(a);
|
||||
} else {
|
||||
const pathSlot = B.slot();
|
||||
B.entryAllocas.push(`${pathSlot} = alloca %ScrDynPath`);
|
||||
|
||||
@@ -64,7 +64,7 @@ function moduleFor(
|
||||
loc,
|
||||
};
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [
|
||||
@@ -270,7 +270,7 @@ const text = (obj: IrExpr): IrExpr => ({
|
||||
|
||||
function work(expr: IrExpr, parameter: IrType, boxed = false, tdz = false): string {
|
||||
const module: IrModule = {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: receiverLoc.file,
|
||||
entry: "main",
|
||||
records: [
|
||||
@@ -461,7 +461,7 @@ function sharedFieldModule(prefixes: IrType[][], fieldType: IrType): IrModule {
|
||||
],
|
||||
}));
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
records,
|
||||
|
||||
@@ -2,25 +2,7 @@ import { emitLlvmLayouts } from "./layouts.js";
|
||||
import { FIRST_EMITTED_CYC_KIND, inlineAllocSupported } from "./alloc.js";
|
||||
import { llvmBytes as llBytes } from "../literals.js";
|
||||
import { InternalCompilerError } from "../../errors.js";
|
||||
/** Lower typed IR to LLVM, using the runtime's C ABI.
|
||||
*
|
||||
* Reference-counted temporaries own one reference. Declarations, assignments,
|
||||
* returns and arguments move that ownership; frame and scope exits release
|
||||
* everything remaining. Slot-backed entries release the slot's current value.
|
||||
*
|
||||
* Exceptions use a pending flag and explicit cleanup edges. Throwing calls
|
||||
* test scr_exc_pending before consuming results, release the scopes crossed,
|
||||
* and branch to the innermost handler or return to the caller. Catch bindings
|
||||
* hold ScrCaught snapshots; finally runs on normal, throwing and returning
|
||||
* paths. This keeps ownership cleanup intact without nonlocal jumps.
|
||||
*
|
||||
* Program objects are emitted by the packaged LLVM helper and linked with
|
||||
* runtime packs. Sanitizer development builds use an external LLVM driver.
|
||||
* Dynamic values and islands share the runtime ABI; embedded npm modules use
|
||||
* compressed source tables that the runtime inflates on demand.
|
||||
*/
|
||||
import { createHash } from "node:crypto";
|
||||
import { deflateRawSync } from "node:zlib";
|
||||
import {
|
||||
endsWithJump,
|
||||
matchStringSelfConcat,
|
||||
@@ -60,12 +42,7 @@ import {
|
||||
STRING,
|
||||
VOID,
|
||||
} from "../../ir/ir.js";
|
||||
import {
|
||||
moduleRuntimeFeatures,
|
||||
moduleEmbedsBuiltin,
|
||||
moduleEmbedsCompressedNpm,
|
||||
NPM_COMPRESS_MIN,
|
||||
} from "../../ir/runtime-features.js";
|
||||
import { moduleRuntimeFeatures } from "../../ir/runtime-features.js";
|
||||
import {
|
||||
matchIntegerArrayForLoop,
|
||||
matchIntegerBytesForLoop,
|
||||
@@ -201,14 +178,8 @@ import { emitIntrinsicExpr, emitSerializationExpr, emitAsyncExpr } from "./expr-
|
||||
import { PublishWalkers } from "./publish.js";
|
||||
import { computeImmutableFields, stableGlobals, type ImmutableFields } from "../immutability.js";
|
||||
import { computePublishedTypes, type PublishedTypes } from "../publication.js";
|
||||
import { emitJsInteropExpr, emitExpr } from "./expr-dispatch.js";
|
||||
import {
|
||||
emitJsMarshal,
|
||||
emitJsOp,
|
||||
emitJsExit,
|
||||
islandAdapter,
|
||||
islandTypedAdapter,
|
||||
} from "./expr-island.js";
|
||||
import { emitExpr } from "./expr-dispatch.js";
|
||||
|
||||
import {
|
||||
dynKind,
|
||||
raceAdapterFor,
|
||||
@@ -1599,22 +1570,6 @@ export class LlEmitter {
|
||||
const hasNoInlineRecordClone = [...this.recordCloneShapes].some(
|
||||
(shapeId) => (this.recordsById.get(shapeId)?.fields.length ?? 0) >= 16,
|
||||
);
|
||||
const embedded = this.mod.embedded;
|
||||
const usesIsland = embedded !== undefined && embedded.modules.length > 0;
|
||||
const storeNpmText = (text: string): { bytes: Buffer; raw: number } => {
|
||||
const plain = Buffer.from(text, "utf8");
|
||||
if (text.length < NPM_COMPRESS_MIN) return { bytes: plain, raw: 0 };
|
||||
const deflated = deflateRawSync(plain, { level: 9 });
|
||||
return deflated.length < plain.length
|
||||
? { bytes: deflated, raw: plain.length }
|
||||
: { bytes: plain, raw: 0 };
|
||||
};
|
||||
const npmStored = usesIsland
|
||||
? embedded.modules.map((m) => ({
|
||||
src: storeNpmText(m.source),
|
||||
esm: m.esm === undefined ? null : storeNpmText(m.esm),
|
||||
}))
|
||||
: [];
|
||||
|
||||
// Module globals, FIRST OCCURRENCE per id: a class-expression static
|
||||
// instantiated through several mixin applications registers one global
|
||||
@@ -1682,26 +1637,14 @@ export class LlEmitter {
|
||||
const usesDgram = runtimeFeatures.dgram;
|
||||
const usesHttp2 = runtimeFeatures.http2;
|
||||
const usesHttp = runtimeFeatures.http;
|
||||
// Fetch-referencing programs register the native fetch bridge before
|
||||
// any island entry (the engine's lazy boot consults it) — native/executable.ts
|
||||
// compiles scr_fetch.c on the same predicate.
|
||||
// Fetch-referencing programs register native fetch before main.
|
||||
// Runtime selection compiles scr_fetch.c on the same predicate.
|
||||
const usesFetch = runtimeFeatures.fetch;
|
||||
const embedsZlib = moduleEmbedsBuiltin(this.mod, "node:zlib");
|
||||
const embedsNet =
|
||||
moduleEmbedsBuiltin(this.mod, "node:http") ||
|
||||
moduleEmbedsBuiltin(this.mod, "node:https") ||
|
||||
moduleEmbedsBuiltin(this.mod, "node:net") ||
|
||||
moduleEmbedsBuiltin(this.mod, "node:tls");
|
||||
const snapshotsTlsCa =
|
||||
runtimeFeatures.tls ||
|
||||
runtimeFeatures.tlsCa ||
|
||||
moduleEmbedsBuiltin(this.mod, "node:https") ||
|
||||
moduleEmbedsBuiltin(this.mod, "node:tls");
|
||||
const snapshotsTlsCa = runtimeFeatures.tls || runtimeFeatures.tlsCa;
|
||||
// The process verdict has the same precedence as the C reference
|
||||
// emitter: node:test owns the final status when present; otherwise an
|
||||
// embedded process.exitCode owns it; ordinary programs return zero.
|
||||
const usesNodeTest = runtimeFeatures.nodeTest;
|
||||
const programExitUsesIsland = !usesNodeTest && usesIsland;
|
||||
// Declared NOW — the extern block flushes before main assembles.
|
||||
if (usesEvents) this.declare(`declare void @scr_events_install()`);
|
||||
if (usesChildProcess) this.declare(`declare void @scr_child_dyn_install()`);
|
||||
@@ -1732,21 +1675,7 @@ export class LlEmitter {
|
||||
if (usesHttp2) this.declare(`declare void @scr_http2_dyn_install()`);
|
||||
if (usesHttp) this.declare(`declare void @scr_http_dyn_install()`);
|
||||
if (usesFetch) this.declare(`declare void @scr_fetch_install()`);
|
||||
if (embedsZlib) this.declare(`declare void @scr_zlib_island_install()`);
|
||||
if (embedsNet) this.declare(`declare void @scr_net_island_install()`);
|
||||
if (usesIsland) {
|
||||
this.declare(
|
||||
`declare void @scr_island_modules(ptr, ${this.sizeType}, ptr, ${this.sizeType})`,
|
||||
);
|
||||
this.declare(`declare void @scr_island_entry_module(ptr)`);
|
||||
this.declare(`declare i32 @scr_island_exit_code()`);
|
||||
if (moduleEmbedsCompressedNpm(this.mod)) {
|
||||
this.declare(`declare void @scr_island_set_inflate(ptr)`);
|
||||
this.declare(
|
||||
`declare zeroext i1 @scr_zlib_inflate_exact(ptr, ${this.sizeType}, ptr, ${this.sizeType})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (usesNodeTest) this.declare(`declare i32 @scr_test_exit_code()`);
|
||||
if (snapshotsTlsCa) {
|
||||
this.declare(`declare void @scr_tls_ca_install()`);
|
||||
@@ -1785,7 +1714,6 @@ export class LlEmitter {
|
||||
// async loop-exhaustion story.
|
||||
const runsLoop =
|
||||
this.usesTimers ||
|
||||
usesIsland ||
|
||||
this.ffiHasForeignCallback ||
|
||||
this.mod.functions.some((f) => f.async === true || f.generator !== undefined);
|
||||
const uncaughtReleases = entryMayThrow && !asyncEntry ? globalReleaseLines("gu") : [];
|
||||
@@ -1810,50 +1738,26 @@ export class LlEmitter {
|
||||
this.declare(`declare void @scr_promise_rethrow_top_level(ptr)`);
|
||||
this.declare(`declare void @scr_promise_release(ptr)`);
|
||||
this.declare(`declare void @scr_exit_code_note(i32)`);
|
||||
if (programExitUsesIsland && inlineExitListeners) {
|
||||
this.declare(`declare ${this.sizeType} @scr_island_exit_code_version()`);
|
||||
}
|
||||
}
|
||||
const topPendingExitLines = (): string[] => {
|
||||
if (!asyncEntry) return [];
|
||||
const lines: string[] = [];
|
||||
if (usesNodeTest) {
|
||||
lines.push(` %tla_program_exit = call i32 @scr_test_exit_code()`);
|
||||
} else if (usesIsland) {
|
||||
lines.push(` %tla_program_exit = call i32 @scr_island_exit_code()`);
|
||||
}
|
||||
if (usesNodeTest || usesIsland) {
|
||||
if (usesNodeTest) {
|
||||
lines.push(
|
||||
` %tla_program_exit_zero = icmp eq i32 %tla_program_exit, 0`,
|
||||
` %tla_exit_status = select i1 %tla_program_exit_zero, i32 %tla_status, i32 %tla_program_exit`,
|
||||
);
|
||||
}
|
||||
const exitStatus = usesNodeTest || usesIsland ? "%tla_exit_status" : "%tla_status";
|
||||
const tracksIslandExit = programExitUsesIsland && inlineExitListeners;
|
||||
if (tracksIslandExit) {
|
||||
lines.push(` %tla_exit_version = call ${this.sizeType} @scr_island_exit_code_version()`);
|
||||
}
|
||||
const exitStatus = usesNodeTest ? "%tla_exit_status" : "%tla_status";
|
||||
// finish_top_level initially notes 13. Replace that hint before exit
|
||||
// listeners run when a higher-priority verdict was already selected.
|
||||
lines.push(` call void @scr_exit_code_note(i32 ${exitStatus})`);
|
||||
for (const line of exitListenerLines("xp")) lines.push(line);
|
||||
if (tracksIslandExit) {
|
||||
lines.push(
|
||||
` %tla_exit_version_after = call ${this.sizeType} @scr_island_exit_code_version()`,
|
||||
` %tla_exit_changed = icmp ne ${this.sizeType} %tla_exit_version_after, %tla_exit_version`,
|
||||
` br i1 %tla_exit_changed, label %tla_exit_updated, label %tla_exit_unchanged`,
|
||||
`tla_exit_updated:`,
|
||||
` %tla_listener_exit = call i32 @scr_island_exit_code()`,
|
||||
` call void @scr_exit_code_note(i32 %tla_listener_exit)`,
|
||||
` br label %tla_exit_done`,
|
||||
`tla_exit_unchanged:`,
|
||||
` br label %tla_exit_done`,
|
||||
`tla_exit_done:`,
|
||||
` %tla_final_exit = phi i32 [ %tla_listener_exit, %tla_exit_updated ], [ ${exitStatus}, %tla_exit_unchanged ]`,
|
||||
);
|
||||
}
|
||||
for (const line of topPendingReleases) lines.push(line);
|
||||
lines.push(` ret i32 ${tracksIslandExit ? "%tla_final_exit" : exitStatus}`);
|
||||
lines.push(` ret i32 ${exitStatus}`);
|
||||
return lines;
|
||||
};
|
||||
// LIBRARY mode: the runtime entry points the generated library
|
||||
@@ -2122,68 +2026,12 @@ export class LlEmitter {
|
||||
);
|
||||
}
|
||||
if (this.templateStringsInstances.size > 0) out.push(``);
|
||||
const islandEntryPath = usesIsland ? this.cstr(this.mod.sourceFile) : null;
|
||||
for (const [text, c] of this.cstrs) {
|
||||
// NUL-terminated byte-array constants for scr_jb_puts and indentation.
|
||||
out.push(`@${c.sym} = internal constant [${c.len + 1} x i8] c"${llStrBytes(text)}"`);
|
||||
}
|
||||
if (this.cstrs.size > 0) out.push(``);
|
||||
if (usesIsland) {
|
||||
const fmt = { esm: 0, cjs: 1, json: 2 } as const;
|
||||
const edgeKind = { any: 0, import: 1, require: 2 } as const;
|
||||
embedded.modules.forEach((m, i) => {
|
||||
const stored = npmStored[i]!;
|
||||
const key = Buffer.from(m.key, "utf8");
|
||||
out.push(
|
||||
`@sc_npm_key_${i} = internal constant [${key.length + 1} x i8] c"${llBytes(key)}"`,
|
||||
`@sc_npm_src_${i} = internal constant [${stored.src.bytes.length + 1} x i8] c"${llBytes(stored.src.bytes)}"`,
|
||||
);
|
||||
if (stored.esm !== null) {
|
||||
out.push(
|
||||
`@sc_npm_esm_${i} = internal constant [${stored.esm.bytes.length + 1} x i8] c"${llBytes(stored.esm.bytes)}"`,
|
||||
);
|
||||
}
|
||||
});
|
||||
const moduleRows = embedded.modules.map((m, i) => {
|
||||
const stored = npmStored[i]!;
|
||||
const esm =
|
||||
stored.esm === null
|
||||
? `ptr null, ${this.sizeType} 0, ${this.sizeType} 0`
|
||||
: `ptr @sc_npm_esm_${i}, ${this.sizeType} ${stored.esm.bytes.length}, ${this.sizeType} ${stored.esm.raw}`;
|
||||
return (
|
||||
`%ScrIslandModule { ptr @sc_npm_key_${i}, ptr @sc_npm_src_${i}, ` +
|
||||
`${this.sizeType} ${stored.src.bytes.length}, ${this.sizeType} ${stored.src.raw}, ` +
|
||||
`i32 ${fmt[m.format]}, ${esm} }`
|
||||
);
|
||||
});
|
||||
out.push(
|
||||
`@sc_npm_modules = internal constant [${moduleRows.length} x %ScrIslandModule] [ ${moduleRows.join(", ")} ]`,
|
||||
);
|
||||
embedded.edges.forEach((edge, i) => {
|
||||
const parts: [string, string][] = [
|
||||
["from", edge.from],
|
||||
["spec", edge.specifier],
|
||||
["to", edge.to],
|
||||
];
|
||||
for (const [part, text] of parts) {
|
||||
const bytes = Buffer.from(text, "utf8");
|
||||
out.push(
|
||||
`@sc_npm_edge_${i}_${part} = internal constant [${bytes.length + 1} x i8] c"${llBytes(bytes)}"`,
|
||||
);
|
||||
}
|
||||
});
|
||||
if (embedded.edges.length > 0) {
|
||||
const edgeRows = embedded.edges.map(
|
||||
(edge, i) =>
|
||||
`%ScrIslandEdge { ptr @sc_npm_edge_${i}_from, ptr @sc_npm_edge_${i}_spec, ` +
|
||||
`ptr @sc_npm_edge_${i}_to, i32 ${edgeKind[edge.kind]} }`,
|
||||
);
|
||||
out.push(
|
||||
`@sc_npm_edges = internal constant [${edgeRows.length} x %ScrIslandEdge] [ ${edgeRows.join(", ")} ]`,
|
||||
);
|
||||
}
|
||||
out.push(``);
|
||||
}
|
||||
|
||||
for (const line of ffiCallbacks.globals) out.push(line);
|
||||
if (ffiCallbacks.globals.length > 0) out.push(``);
|
||||
for (const g of globals) {
|
||||
@@ -2305,24 +2153,12 @@ export class LlEmitter {
|
||||
...(this.ffiHasForeignCallback ? [` call void @scr_ffi_install()`] : []),
|
||||
...(snapshotsTlsCa ? [` call void @scr_tls_ca_install()`] : []),
|
||||
...(usesFetch ? [` call void @scr_fetch_install()`] : []),
|
||||
...(embedsZlib ? [` call void @scr_zlib_island_install()`] : []),
|
||||
...(embedsNet ? [` call void @scr_net_island_install()`] : []),
|
||||
...(usesNet ? [` call void @scr_net_install()`, ` call void @scr_net_dyn_install()`] : []),
|
||||
...(usesHttp ? [` call void @scr_http_dyn_install()`] : []),
|
||||
...(usesDgram ? [` call void @scr_dgram_install()`] : []),
|
||||
...(usesHttp2 ? [` call void @scr_http2_dyn_install()`] : []),
|
||||
...(usesStream ? [` call void @scr_stream_install()`] : []),
|
||||
` call void @scr_lib_init(i32 %argc, ptr %argv)`,
|
||||
...(usesIsland
|
||||
? [
|
||||
` call void @scr_island_entry_module(ptr ${islandEntryPath})`,
|
||||
...(moduleEmbedsCompressedNpm(this.mod)
|
||||
? [` call void @scr_island_set_inflate(ptr @scr_zlib_inflate_exact)`]
|
||||
: []),
|
||||
` call void @scr_island_modules(ptr @sc_npm_modules, ${this.sizeType} ${embedded.modules.length}, ` +
|
||||
`ptr ${embedded.edges.length > 0 ? "@sc_npm_edges" : "null"}, ${this.sizeType} ${embedded.edges.length})`,
|
||||
]
|
||||
: []),
|
||||
...(asyncEntry
|
||||
? [` %top = call ptr @${mangleAsyncSpawn(this.mod.entry)}()`]
|
||||
: [` call void @${mangleFunction(this.mod.entry)}()`]),
|
||||
@@ -2413,9 +2249,7 @@ export class LlEmitter {
|
||||
...globalReleases,
|
||||
...(usesNodeTest
|
||||
? [` %test_exit = call i32 @scr_test_exit_code()`, ` ret i32 %test_exit`]
|
||||
: usesIsland
|
||||
? [` %island_exit = call i32 @scr_island_exit_code()`, ` ret i32 %island_exit`]
|
||||
: [` %native_exit = call i32 @scr_exit_code_hint_get()`, ` ret i32 %native_exit`]),
|
||||
: [` %native_exit = call i32 @scr_exit_code_hint_get()`, ` ret i32 %native_exit`]),
|
||||
`}`,
|
||||
``,
|
||||
...this.inlineRcTail(flushedDecls),
|
||||
@@ -3183,8 +3017,8 @@ export class LlEmitter {
|
||||
* mentions (transitively), contains no suspension entry point. Such a
|
||||
* body always completes before the call returns, which is exactly the
|
||||
* behavior of a fiber that never suspends. Excluded conservatively:
|
||||
* wasm32 (its coroutine lowering owns async bodies), programs embedding
|
||||
* the dynamic engine, module-evaluation bodies (their promise caches),
|
||||
* wasm32 (its coroutine lowering owns async bodies), module-evaluation
|
||||
* bodies (their promise caches),
|
||||
* `dyn` results (resolution may adopt a thenable), and every program
|
||||
* that uses node:test subtests — `t.test()` runs a subtest inline and
|
||||
* awaits it on the CURRENT fiber even from synchronous code. Worker
|
||||
@@ -3196,8 +3030,6 @@ export class LlEmitter {
|
||||
private fiberlessAsyncFunctions(): Set<string> {
|
||||
const eligible = new Set<string>();
|
||||
if (this.wasi || this.mod.workers === true) return eligible;
|
||||
const embedded = this.mod.embedded;
|
||||
if (embedded !== undefined && embedded.modules.length > 0) return eligible;
|
||||
const suspends =
|
||||
/@scr_(?:await_|module_await\b|promise_await_settled\b|test_sub\b|gen_yield_|async_gen_|fiber_)/;
|
||||
for (const decl of this.decls) if (/@scr_test_sub\b/.test(decl)) return eligible;
|
||||
@@ -4264,16 +4096,6 @@ export class LlEmitter {
|
||||
} else if (t.kind === "dyn") {
|
||||
this.declare(`declare void @scr_dyn_throw(ptr)`);
|
||||
B.line(`call void @scr_dyn_throw(ptr ${v.name})`);
|
||||
} else if (t.kind === "jsval") {
|
||||
const rc = vAdapters(this.shapeHost, t);
|
||||
const test = "scr_jsval_is_object";
|
||||
this.declare(`declare zeroext i1 @${test}(ptr)`);
|
||||
this.declare(`declare void @scr_throw_ref_classified(ptr, ptr, ptr, ptr, i1 zeroext)`);
|
||||
const object = B.tmp();
|
||||
B.line(`${object} = call zeroext i1 @${test}(ptr ${v.name})`);
|
||||
B.line(
|
||||
`call void @scr_throw_ref_classified(ptr ${v.name}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(this.shapeHost, t)}, i1 zeroext ${object})`,
|
||||
);
|
||||
} else {
|
||||
const rc = vAdapters(this.shapeHost, t);
|
||||
this.declare(`declare void @scr_throw_ref(ptr, ptr, ptr, ptr)`);
|
||||
@@ -4312,7 +4134,7 @@ export class LlEmitter {
|
||||
valueName = this.unionGetBool(v.name);
|
||||
} else if (arm.kind === "string") {
|
||||
valueName = this.unionPeek(v.name, unionId);
|
||||
} else if (arm.kind === "bigint" || arm.kind === "dyn" || arm.kind === "jsval") {
|
||||
} else if (arm.kind === "bigint" || arm.kind === "dyn") {
|
||||
valueName = this.unionPeek(v.name, unionId);
|
||||
}
|
||||
const truthy = this.truthyOf(arm.kind, valueName, true);
|
||||
@@ -4406,14 +4228,6 @@ export class LlEmitter {
|
||||
B.line(`${truthy} = call zeroext i1 @scr_dyn_truthy(ptr ${valueName})`);
|
||||
return truthy;
|
||||
}
|
||||
case "jsval": {
|
||||
this.declare(`declare i32 @scr_jsval_truthy(ptr)`);
|
||||
const raw = B.tmp();
|
||||
const truthy = B.tmp();
|
||||
B.line(`${raw} = call i32 @scr_jsval_truthy(ptr ${valueName})`);
|
||||
B.line(`${truthy} = icmp ne i32 ${raw}, 0`);
|
||||
return truthy;
|
||||
}
|
||||
default:
|
||||
throw new LlvmUnsupportedError(`truthy:${unionArm ? "union:" : ""}${kind}`);
|
||||
}
|
||||
@@ -7546,7 +7360,6 @@ export class LlEmitter {
|
||||
emitDynamicExpr(
|
||||
e: ExprOf<
|
||||
| "dynFrom"
|
||||
| "dynFromJsval"
|
||||
| "dynCall"
|
||||
| "dynInvoke"
|
||||
| "dynArrLit"
|
||||
@@ -7757,10 +7570,6 @@ export class LlEmitter {
|
||||
return emitAsyncExpr(this, e);
|
||||
}
|
||||
|
||||
emitJsInteropExpr(e: ExprOf<"jsMarshal" | "jsOp" | "jsExit" | "jsBridgePromise">): LlValue {
|
||||
return emitJsInteropExpr(this, e);
|
||||
}
|
||||
|
||||
/** Read a receiver whose value is consumed immediately by a field or tag
|
||||
* load. No user code may run between this read and its consumer. Locals
|
||||
* and their projections already have an owner; other expressions keep
|
||||
@@ -8266,26 +8075,6 @@ export class LlEmitter {
|
||||
return emitExpr(this, e);
|
||||
}
|
||||
|
||||
emitJsMarshal(e: IrExpr & { kind: "jsMarshal" }): LlValue {
|
||||
return emitJsMarshal(this, e);
|
||||
}
|
||||
|
||||
emitJsOp(e: IrExpr & { kind: "jsOp" }): LlValue {
|
||||
return emitJsOp(this, e);
|
||||
}
|
||||
|
||||
emitJsExit(e: IrExpr & { kind: "jsExit" }): LlValue {
|
||||
return emitJsExit(this, e);
|
||||
}
|
||||
|
||||
islandAdapter(arity: number, retKind: "void" | "jsval" | "f64" | "bool" | "string"): string {
|
||||
return islandAdapter(this, arity, retKind);
|
||||
}
|
||||
|
||||
islandTypedAdapter(fn: IrType & { kind: "func" }): string {
|
||||
return islandTypedAdapter(this, fn);
|
||||
}
|
||||
|
||||
dynKind(d: string): string {
|
||||
return dynKind(this, d);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ const concat = (left: IrExpr, right: IrExpr): IrExpr => ({
|
||||
|
||||
function logBody(name: "console.log" | "console.error", args: IrExpr[]): string {
|
||||
const mod: IrModule = {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [
|
||||
|
||||
@@ -628,14 +628,14 @@ export function emitArrIntrinsic(
|
||||
case "pop":
|
||||
case "shift": {
|
||||
host.emitPublishedArrayGuard(r.name, e.receiver.type, method === "pop" ? 2 : 6);
|
||||
const dynamic = elem.kind === "dyn" && e.type.kind === "dyn";
|
||||
if (!dynamic && e.type.kind !== "union")
|
||||
const checkedValue = elem.kind === "dyn" && e.type.kind === "dyn";
|
||||
if (!checkedValue && e.type.kind !== "union")
|
||||
throw new InternalCompilerError("llvm emitter bug: array removal result is not a union");
|
||||
const def = e.type.kind === "union" ? host.unionsById.get(e.type.unionId) : undefined;
|
||||
const tag = def ? def.arms.findIndex((arm) => typeEquals(arm, elem)) : -1;
|
||||
const undefTag = undefinedArmTag(e.type, host.unionsById);
|
||||
const sameUnion = elem.kind === "union" && typeEquals(elem, e.type);
|
||||
if (!dynamic && ((!sameUnion && tag < 0) || undefTag < 0))
|
||||
if (!checkedValue && ((!sameUnion && tag < 0) || undefTag < 0))
|
||||
throw new InternalCompilerError("llvm emitter bug: array removal union lacks its arms");
|
||||
host.declare(`declare zeroext i8 @scr_arr_${method}_state(ptr, ptr)`);
|
||||
const rawSlot = B.slot();
|
||||
@@ -658,11 +658,11 @@ export function emitArrIntrinsic(
|
||||
else if (elem.kind === "bool") B.line(`${value} = icmp ne i64 ${raw}, 0`);
|
||||
else B.line(`${value} = inttoptr i64 ${raw} to ptr`);
|
||||
B.line(
|
||||
`store ptr ${dynamic || sameUnion || e.type.kind !== "union" ? value : host.unionNewOwned(e.type.unionId, tag, { name: value, type: elem })}, ptr ${resultSlot}`,
|
||||
`store ptr ${checkedValue || sameUnion || e.type.kind !== "union" ? value : host.unionNewOwned(e.type.unionId, tag, { name: value, type: elem })}, ptr ${resultSlot}`,
|
||||
);
|
||||
B.br(lj);
|
||||
B.startBlock(la);
|
||||
if (dynamic) {
|
||||
if (checkedValue) {
|
||||
host.declare(`declare ptr @scr_dyn_undefined()`);
|
||||
const absent = B.tmp();
|
||||
B.line(`${absent} = call ptr @scr_dyn_undefined()`);
|
||||
|
||||
@@ -26,21 +26,10 @@ export function emitControlExpr(
|
||||
switch (e.kind) {
|
||||
case "dynDestrCheck": {
|
||||
// RequireObjectCoercible with V8's destructuring TypeError. dyn
|
||||
// values check in the runtime helper and pass through unchanged
|
||||
// (same temp, same ownership); island values check in the engine
|
||||
// (a fresh +1 cell for the same value comes back).
|
||||
// values check in the runtime helper and pass through unchanged.
|
||||
const v = host.emitExpr(e.value);
|
||||
const first = e.firstProp !== undefined ? host.cstr(e.firstProp) : "null";
|
||||
if (e.value.type.kind === "jsval") {
|
||||
host.declare(`declare ptr @scr_jsval_destr_check(ptr, ptr, ptr)`);
|
||||
const t = B.tmp();
|
||||
B.line(
|
||||
`${t} = call ptr @scr_jsval_destr_check(ptr ${v.name}, ptr ${host.cstr(e.spelling)}, ptr ${first})`,
|
||||
);
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
|
||||
const helper = host.dyn.dynDestrCheckHelper();
|
||||
B.line(`call void @${helper}(ptr ${v.name}, ptr ${host.cstr(e.spelling)}, ptr ${first})`);
|
||||
host.emitPendingCheck();
|
||||
@@ -48,18 +37,9 @@ export function emitControlExpr(
|
||||
}
|
||||
case "dynIterN": {
|
||||
// GetIterator + first-N steps as a fresh array (V8's exact
|
||||
// not-iterable TypeError on non-iterables): the dyn helper for
|
||||
// dyn operands, the engine's real iterator protocol for island
|
||||
// ones.
|
||||
// not-iterable TypeError on non-iterables) through the native dyn helper.
|
||||
const v = emitBorrowedInput(host, e.value);
|
||||
if (e.value.type.kind === "jsval") {
|
||||
host.declare(`declare ptr @scr_jsval_iter_n(ptr, double)`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_iter_n(ptr ${v.name}, double ${f64Lit(e.count)})`);
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
|
||||
const helper = host.dyn.dynIterNHelper();
|
||||
const t = B.tmp();
|
||||
B.line(
|
||||
@@ -212,63 +192,7 @@ export function emitControlExpr(
|
||||
B.line(`${t} = load ptr, ptr ${slot}`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
if (e.receiver.type.kind === "jsval") {
|
||||
// Island-handle chain: the nullish test asks the engine value;
|
||||
// the unit path result is the engine's undefined (+1 cell), the
|
||||
// body runs lazily over the bound handle otherwise.
|
||||
const r = host.emitExpr(e.receiver);
|
||||
host.declare(`declare zeroext i1 @scr_jsval_is_nullish(ptr)`);
|
||||
host.declare(`declare ptr @scr_jsval_retain_v(ptr)`);
|
||||
const isN = B.tmp();
|
||||
B.line(`${isN} = call zeroext i1 @scr_jsval_is_nullish(ptr ${r.name})`);
|
||||
const bind = B.slot();
|
||||
B.entryAllocas.push(`${bind} = alloca ptr`);
|
||||
B.line(`store ptr null, ptr ${bind}`);
|
||||
host.ownSlot(bind, e.receiver.type);
|
||||
if (e.type.kind === "void") {
|
||||
const lb = B.newLabel("ocj.b");
|
||||
const lj = B.newLabel("ocj.j");
|
||||
B.condBr(isN, lj, lb);
|
||||
B.startBlock(lb);
|
||||
const rr = B.tmp();
|
||||
B.line(`${rr} = call ptr @scr_jsval_retain_v(ptr ${r.name})`);
|
||||
B.line(`store ptr ${rr}, ptr ${bind}`);
|
||||
host.chainSlots.set(e.id, { name: bind, type: e.receiver.type, slot: true });
|
||||
host.frames.push([]);
|
||||
host.emitExpr(e.body);
|
||||
host.releaseFrame(host.frames.pop()!);
|
||||
host.chainSlots.delete(e.id);
|
||||
B.br(lj);
|
||||
B.startBlock(lj);
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
if (e.type.kind !== "jsval")
|
||||
throw new InternalCompilerError("llvm emitter bug: jsval optChain result kind");
|
||||
const slot = B.slot();
|
||||
B.entryAllocas.push(`${slot} = alloca ptr`);
|
||||
const lu = B.newLabel("ocj.u");
|
||||
const lb = B.newLabel("ocj.b");
|
||||
const lj = B.newLabel("ocj.j");
|
||||
B.condBr(isN, lu, lb);
|
||||
B.startBlock(lu);
|
||||
host.declare(`declare ptr @scr_jsval_undefined()`);
|
||||
const un = B.tmp();
|
||||
B.line(`${un} = call ptr @scr_jsval_undefined()`);
|
||||
B.line(`store ptr ${un}, ptr ${slot}`);
|
||||
B.br(lj);
|
||||
B.startBlock(lb);
|
||||
const rr = B.tmp();
|
||||
B.line(`${rr} = call ptr @scr_jsval_retain_v(ptr ${r.name})`);
|
||||
B.line(`store ptr ${rr}, ptr ${bind}`);
|
||||
host.chainSlots.set(e.id, { name: bind, type: e.receiver.type, slot: true });
|
||||
host.emitBranchInto(slot, e.body);
|
||||
host.chainSlots.delete(e.id);
|
||||
B.br(lj);
|
||||
B.startBlock(lj);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = load ptr, ptr ${slot}`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
|
||||
if (e.receiver.type.kind !== "union")
|
||||
throw new LlvmUnsupportedError(`optChain:${e.receiver.type.kind}`, e.loc);
|
||||
const def = host.unionsById.get(e.receiver.type.unionId);
|
||||
@@ -471,35 +395,10 @@ export function emitControlExpr(
|
||||
// TAG against its unit arms as the test. Pass-through shape: the
|
||||
// result IS the left box. Narrowed shape: the single non-unit
|
||||
// arm's payload extracts (+1 for ref kinds) and the box releases.
|
||||
if (e.left.type.kind === "jsval") {
|
||||
// The island form: engine nullish test, lazy right (jsval).
|
||||
const l = host.emitExpr(e.left);
|
||||
host.moveTemp(l);
|
||||
host.declare(`declare zeroext i1 @scr_jsval_is_nullish(ptr)`);
|
||||
const slot = B.slot();
|
||||
B.entryAllocas.push(`${slot} = alloca ptr`);
|
||||
const isN = B.tmp();
|
||||
B.line(`${isN} = call zeroext i1 @scr_jsval_is_nullish(ptr ${l.name})`);
|
||||
const lu = B.newLabel("nulj.u");
|
||||
const lv = B.newLabel("nulj.v");
|
||||
const lj = B.newLabel("nulj.j");
|
||||
B.condBr(isN, lu, lv);
|
||||
B.startBlock(lu);
|
||||
host.releaseValue(l.name, e.left.type);
|
||||
host.emitBranchInto(slot, e.right);
|
||||
B.br(lj);
|
||||
B.startBlock(lv);
|
||||
B.line(`store ptr ${l.name}, ptr ${slot}`);
|
||||
B.br(lj);
|
||||
B.startBlock(lj);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = load ptr, ptr ${slot}`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
|
||||
if (e.left.type.kind === "dyn") {
|
||||
// The checked-dynamic form: the runtime kind decides (UNDEF/
|
||||
// NULL take the default; a wrapped island value asks the
|
||||
// engine); the right runs lazily in its branch (already dyn).
|
||||
// The native checked-value kind decides whether the left is
|
||||
// nullish; the right runs lazily in its branch.
|
||||
const l = host.emitExpr(e.left);
|
||||
host.moveTemp(l);
|
||||
host.declare(`declare zeroext i1 @scr_dyn_is_nullish(ptr)`);
|
||||
|
||||
@@ -1,47 +1,7 @@
|
||||
/* Focused LLVM expression emission extracted from emitter.ts. */
|
||||
import { InternalCompilerError } from "../../errors.js";
|
||||
import { type IrExpr } from "../../ir/ir.js";
|
||||
import type { LlvmEmitterContext, ExprOf, LlValue } from "./expr-context.js";
|
||||
|
||||
export function emitJsInteropExpr(
|
||||
host: LlvmEmitterContext,
|
||||
e: ExprOf<"jsMarshal" | "jsOp" | "jsExit" | "jsBridgePromise">,
|
||||
): LlValue {
|
||||
const B = host.B;
|
||||
switch (e.kind) {
|
||||
case "jsMarshal":
|
||||
return host.emitJsMarshal(e);
|
||||
case "jsOp":
|
||||
return host.emitJsOp(e);
|
||||
case "jsExit":
|
||||
return host.emitJsExit(e);
|
||||
case "jsBridgePromise": {
|
||||
// Island → static promise bridge: a fresh pending ScrPromise the
|
||||
// engine promise settles. Operand borrowed; the +1 promise joins
|
||||
// the frame. Pending check like other island ops.
|
||||
const v = host.emitExpr(e.value);
|
||||
const payload =
|
||||
e.type.kind === "promise" && e.type.inner.kind === "void"
|
||||
? 0 // SCR_ISLP_VOID
|
||||
: e.type.kind === "promise" &&
|
||||
e.type.inner.kind === "array" &&
|
||||
e.type.inner.elem.kind === "jsval"
|
||||
? 5 // SCR_ISLP_JSVAL_ARR: the Array.isArray-gated by-reference exit at settle
|
||||
: 4; // SCR_ISLP_JSVAL
|
||||
host.declare(`declare ptr @scr_jsval_bridge_promise(ptr, i32)`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_bridge_promise(ptr ${v.name}, i32 ${payload})`);
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
default: {
|
||||
const _exhaustive: never = e;
|
||||
void _exhaustive;
|
||||
throw new InternalCompilerError("unreachable");
|
||||
}
|
||||
}
|
||||
}
|
||||
import type { LlvmEmitterContext, LlValue } from "./expr-context.js";
|
||||
|
||||
export function emitExpr(host: LlvmEmitterContext, e: IrExpr): LlValue {
|
||||
switch (e.kind) {
|
||||
@@ -117,7 +77,6 @@ export function emitExpr(host: LlvmEmitterContext, e: IrExpr): LlValue {
|
||||
case "fieldAbsent":
|
||||
return host.emitRecordExpr(e);
|
||||
case "dynFrom":
|
||||
case "dynFromJsval":
|
||||
case "dynCall":
|
||||
case "dynInvoke":
|
||||
case "dynArrLit":
|
||||
@@ -150,11 +109,6 @@ export function emitExpr(host: LlvmEmitterContext, e: IrExpr): LlValue {
|
||||
case "newPromise":
|
||||
case "promiseWithResolvers":
|
||||
return host.emitAsyncExpr(e);
|
||||
case "jsMarshal":
|
||||
case "jsOp":
|
||||
case "jsExit":
|
||||
case "jsBridgePromise":
|
||||
return host.emitJsInteropExpr(e);
|
||||
case "libCall":
|
||||
return host.emitLibCall(e);
|
||||
default: {
|
||||
|
||||
@@ -28,7 +28,6 @@ export function emitDynamicExpr(
|
||||
host: LlvmEmitterContext,
|
||||
e: ExprOf<
|
||||
| "dynFrom"
|
||||
| "dynFromJsval"
|
||||
| "dynCall"
|
||||
| "dynInvoke"
|
||||
| "dynArrLit"
|
||||
@@ -116,16 +115,6 @@ export function emitDynamicExpr(
|
||||
B.line(`${t} = call ptr @${conv}(${valTy} ${v.name})`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
case "dynFromJsval": {
|
||||
// Island value → dyn: the by-reference wrap (scr_dyn_from_jsval
|
||||
// retains the cell in; engine scalars normalize to native dyn
|
||||
// kinds at wrap time). Operand borrowed, result +1, never throws.
|
||||
const v = emitBorrowedInput(host, e.value);
|
||||
host.declare(`declare ptr @scr_dyn_from_jsval(ptr)`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_dyn_from_jsval(ptr ${v.name})`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
case "dynCall": {
|
||||
// Calling a dyn value: args are already dyn (the lowering boxed or
|
||||
// converted them); everything is BORROWED by scr_dyn_call — the
|
||||
@@ -627,16 +616,16 @@ export function emitDynamicExpr(
|
||||
} else if (e.test === "error") {
|
||||
// `u instanceof Error`: the checked-dynamic tree's error encoding — an object
|
||||
// carrying the reserved "%error" marker key, a registered native
|
||||
// Error view, or a real engine Error held by reference.
|
||||
// Error view.
|
||||
const kd = host.dynKind(d.name);
|
||||
const isObj = B.tmp();
|
||||
B.line(`${isObj} = icmp eq i32 ${kd}, ${DYN_KIND.OBJ}`);
|
||||
const slot = B.slot();
|
||||
B.entryAllocas.push(`${slot} = alloca i1`);
|
||||
host.declare(`declare zeroext i1 @scr_dyn_isl_is_error(ptr)`);
|
||||
const isl = B.tmp();
|
||||
B.line(`${isl} = call zeroext i1 @scr_dyn_isl_is_error(ptr ${d.name})`);
|
||||
B.line(`store i1 ${isl}, ptr ${slot}`);
|
||||
host.declare(`declare zeroext i1 @scr_dyn_ref_is_error(ptr)`);
|
||||
const nativeError = B.tmp();
|
||||
B.line(`${nativeError} = call zeroext i1 @scr_dyn_ref_is_error(ptr ${d.name})`);
|
||||
B.line(`store i1 ${nativeError}, ptr ${slot}`);
|
||||
const lObj = B.newLabel("dts.o");
|
||||
const lj = B.newLabel("dts.j");
|
||||
B.condBr(isObj, lObj, lj);
|
||||
@@ -649,7 +638,7 @@ export function emitDynamicExpr(
|
||||
);
|
||||
B.line(`${has} = icmp ne ptr ${m}, null`);
|
||||
const branded = B.tmp();
|
||||
B.line(`${branded} = or i1 ${has}, ${isl}`);
|
||||
B.line(`${branded} = or i1 ${has}, ${nativeError}`);
|
||||
B.line(`store i1 ${branded}, ptr ${slot}`);
|
||||
B.br(lj);
|
||||
B.startBlock(lj);
|
||||
@@ -672,10 +661,9 @@ export function emitDynamicExpr(
|
||||
}
|
||||
return acc;
|
||||
};
|
||||
// ISLAND-held nodes route the tests that depend on the engine's
|
||||
// answer through the scr_dyn_isl_* helpers (false on every
|
||||
// other kind — the calls stay unconditional and branch-free).
|
||||
const orIsl = (acc: string, helper: string, arg?: string): string => {
|
||||
|
||||
// Native reference capsules answer through materialization helpers.
|
||||
const orNativeRef = (acc: string, helper: string, arg?: string): string => {
|
||||
host.declare(`declare zeroext i1 @${helper}(ptr${arg !== undefined ? ", ptr" : ""})`);
|
||||
const c = B.tmp();
|
||||
B.line(
|
||||
@@ -703,9 +691,8 @@ export function emitDynamicExpr(
|
||||
B.line(`${test} = and i1 ${bytes}, ${buffer}`);
|
||||
} else if (e.test === "object") {
|
||||
// `typeof v === "object"`: objects, arrays, bytes, native
|
||||
// handles, promises, AND null — engine-held objects by the
|
||||
// engine's own typeof.
|
||||
const object = orIsl(
|
||||
// handles, promises, null, and native reference capsules.
|
||||
const object = orNativeRef(
|
||||
oneOf([
|
||||
DYN_KIND.OBJ,
|
||||
DYN_KIND.ARR,
|
||||
@@ -715,7 +702,7 @@ export function emitDynamicExpr(
|
||||
DYN_KIND.PROXY,
|
||||
DYN_KIND.NULL,
|
||||
]),
|
||||
"scr_dyn_isl_typeof_is",
|
||||
"scr_dyn_ref_typeof_is",
|
||||
host.cstr("object"),
|
||||
);
|
||||
host.declare("declare zeroext i1 @scr_dyn_is_callable(ptr)");
|
||||
@@ -726,9 +713,8 @@ export function emitDynamicExpr(
|
||||
B.line(`${nonCallable} = xor i1 ${callable}, true`);
|
||||
B.line(`${test} = and i1 ${object}, ${nonCallable}`);
|
||||
} else if (e.test === "array") {
|
||||
// Array.isArray: the checked-dynamic tree's array kind, or the engine's own
|
||||
// answer for an engine-held value.
|
||||
test = orIsl(oneOf([DYN_KIND.ARR]), "scr_dyn_isl_is_array");
|
||||
// Array.isArray accepts native arrays and typed array capsules.
|
||||
test = orNativeRef(oneOf([DYN_KIND.ARR]), "scr_dyn_ref_is_array");
|
||||
} else if (e.test === "function") {
|
||||
host.declare("declare zeroext i1 @scr_dyn_is_callable(ptr)");
|
||||
test = B.tmp();
|
||||
|
||||
@@ -1,889 +0,0 @@
|
||||
/* Focused LLVM expression emission extracted from emitter.ts. */
|
||||
import { InternalCompilerError } from "../../errors.js";
|
||||
import { undefinedArmTag } from "../../ir/analysis.js";
|
||||
import {
|
||||
canMarshalFuncIntoIsland,
|
||||
type IrExpr,
|
||||
type IrType,
|
||||
islandCallbackRet,
|
||||
islandPromisePayloadTag,
|
||||
isRefCounted,
|
||||
isUnitType,
|
||||
STRING,
|
||||
typeKey,
|
||||
} from "../../ir/ir.js";
|
||||
import { FN_ATTRS, releaseSym } from "./shapes.js";
|
||||
import type { LlvmEmitterContext, LlValue } from "./expr-context.js";
|
||||
import { f64Lit } from "./common.js";
|
||||
|
||||
export function emitJsMarshal(
|
||||
host: LlvmEmitterContext,
|
||||
e: IrExpr & { kind: "jsMarshal" },
|
||||
): LlValue {
|
||||
const B = host.B;
|
||||
const v = host.emitExpr(e.value);
|
||||
const simple = (sym: string, argTy: string, fallible: boolean): LlValue => {
|
||||
host.declare(`declare ptr @${sym}(${argTy === "i1" ? "i1 zeroext" : argTy})`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @${sym}(${argTy} ${v.name})`);
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
if (fallible) host.emitPendingCheck();
|
||||
return out;
|
||||
};
|
||||
switch (e.value.type.kind) {
|
||||
case "f64":
|
||||
return simple("scr_jsval_from_f64", "double", false);
|
||||
case "bool":
|
||||
return simple("scr_jsval_from_bool", "i1", false);
|
||||
case "string":
|
||||
return simple("scr_jsval_from_str", "ptr", false);
|
||||
case "dyn":
|
||||
// A CHECKED-DYNAMIC (dyn) value entering the island: deep copy,
|
||||
// data kinds only — boxed functions/handles/promises throw the
|
||||
// catchable TypeError in the runtime, and a wrapped island value
|
||||
// unwraps to the SAME engine value (the identity round trip).
|
||||
// Transfer ownership to the island runtime.
|
||||
return simple("scr_jsval_from_dyn", "ptr", true);
|
||||
case "bytes":
|
||||
// A typed array crossing IN: a COPY (the boundary's copy stance).
|
||||
return simple("scr_jsval_from_bytes", "ptr", true);
|
||||
case "url":
|
||||
// A URL crossing IN: an engine URL instance built from href.
|
||||
return simple("scr_jsval_from_url", "ptr", true);
|
||||
case "promise": {
|
||||
// A STATIC promise crossing IN: a real engine thenable settled
|
||||
// when the scriptc promise settles (the async-callback return
|
||||
// bridge). from_promise takes ownership of a +1 — retain past
|
||||
// the borrowed frame temp. Transfer ownership to the island runtime.
|
||||
const tag = islandPromisePayloadTag(e.value.type.inner);
|
||||
if (!tag)
|
||||
throw new InternalCompilerError(
|
||||
"llvm emitter bug: jsMarshal of a promise outside the bridge payload domain",
|
||||
);
|
||||
const tagN = { void: 0, f64: 1, bool: 2, string: 3, jsval: 4, jsvalArr: 5 }[tag];
|
||||
host.declare(`declare ptr @scr_promise_retain(ptr)`);
|
||||
host.declare(`declare ptr @scr_jsval_from_promise(ptr, i32)`);
|
||||
const pr = B.tmp();
|
||||
B.line(`${pr} = call ptr @scr_promise_retain(ptr ${v.name})`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_from_promise(ptr ${pr}, i32 ${tagN})`);
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
case "func": {
|
||||
// A closure entering the island as a host function: from_closure
|
||||
// retains it; the engine's finalizer releases it at teardown. The
|
||||
// per-signature adapter gives the runtime one uniform call shape.
|
||||
const fn = e.value.type;
|
||||
const adapter = canMarshalFuncIntoIsland(fn)
|
||||
? host.islandAdapter(
|
||||
fn.params.length,
|
||||
fn.ret.kind as "void" | "jsval" | "f64" | "bool" | "string",
|
||||
)
|
||||
: host.islandTypedAdapter(fn);
|
||||
host.declare(`declare ptr @scr_jsval_from_closure(ptr, i32, ptr)`);
|
||||
// ISLAND-REST closures encode a NEGATIVE arity (the runtime ABI’s
|
||||
// rule): the wrapper hands the trailing slot the engine array of
|
||||
// the surplus arguments.
|
||||
const arity =
|
||||
fn.rest === true && fn.restAbi === "jsval" ? -fn.params.length : fn.params.length;
|
||||
const t = B.tmp();
|
||||
B.line(
|
||||
`${t} = call ptr @scr_jsval_from_closure(ptr ${v.name}, i32 ${arity}, ptr @${adapter})`,
|
||||
);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
default: {
|
||||
// JSON-safe composite: deep copy through the emitted serializer
|
||||
// and the engine's JSON parser (documented aliasing divergence).
|
||||
const helper = host.walkers.jsonWriteHelper(e.value.type);
|
||||
host.declare(`declare void @scr_jb_init(ptr)`);
|
||||
host.declare(`declare ptr @scr_jb_finish(ptr)`);
|
||||
host.declare(`declare ptr @scr_jsval_from_json(ptr)`);
|
||||
const buf = B.slot();
|
||||
B.entryAllocas.push(`${buf} = alloca %ScrJsonBuf`);
|
||||
B.line(`call void @scr_jb_init(ptr ${buf})`);
|
||||
B.line(`call void @${helper}(ptr ${buf}, ${host.llType(e.value.type)} ${v.name})`);
|
||||
const js = B.tmp();
|
||||
B.line(`${js} = call ptr @scr_jb_finish(ptr ${buf})`);
|
||||
host.own({ name: js, type: STRING });
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_from_json(ptr ${js})`);
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function emitJsOp(host: LlvmEmitterContext, e: IrExpr & { kind: "jsOp" }): LlValue {
|
||||
const B = host.B;
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
const a = (i: number): string => args[i]!.name;
|
||||
const nameSym = (): string => host.internLiteral(e.name!);
|
||||
const fallible = (call: () => string): LlValue => {
|
||||
const t = call();
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
};
|
||||
const argPack = (list: string[]): string => {
|
||||
if (list.length === 0) return "null";
|
||||
const arr = B.slot();
|
||||
B.entryAllocas.push(`${arr} = alloca [${list.length} x ptr]`);
|
||||
list.forEach((x, i) => {
|
||||
const p = B.tmp();
|
||||
B.line(`${p} = getelementptr inbounds [${list.length} x ptr], ptr ${arr}, i64 0, i64 ${i}`);
|
||||
B.line(`store ptr ${x}, ptr ${p}`);
|
||||
});
|
||||
return arr;
|
||||
};
|
||||
const JSOP: Record<string, number> = {
|
||||
add: 0,
|
||||
sub: 1,
|
||||
mul: 2,
|
||||
div: 3,
|
||||
mod: 4,
|
||||
pow: 5,
|
||||
lt: 6,
|
||||
le: 7,
|
||||
gt: 8,
|
||||
ge: 9,
|
||||
eq: 10,
|
||||
neq: 11,
|
||||
};
|
||||
switch (e.op) {
|
||||
case "add":
|
||||
case "sub":
|
||||
case "mul":
|
||||
case "div":
|
||||
case "mod":
|
||||
case "pow":
|
||||
host.declare(`declare ptr @scr_jsval_binop(i32, ptr, ptr)`);
|
||||
return fallible(() => {
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_binop(i32 ${JSOP[e.op]}, ptr ${a(0)}, ptr ${a(1)})`);
|
||||
return t;
|
||||
});
|
||||
case "lt":
|
||||
case "le":
|
||||
case "gt":
|
||||
case "ge":
|
||||
case "eq":
|
||||
case "neq": {
|
||||
host.declare(`declare i32 @scr_jsval_cmp(i32, ptr, ptr)`);
|
||||
const r = B.tmp();
|
||||
B.line(`${r} = call i32 @scr_jsval_cmp(i32 ${JSOP[e.op]}, ptr ${a(0)}, ptr ${a(1)})`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = icmp eq i32 ${r}, 1`);
|
||||
const out = { name: t, type: e.type };
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
case "neg":
|
||||
case "plus": {
|
||||
const sym = e.op === "neg" ? "scr_jsval_neg" : "scr_jsval_plus";
|
||||
host.declare(`declare ptr @${sym}(ptr)`);
|
||||
return fallible(() => {
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @${sym}(ptr ${a(0)})`);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
case "truthy":
|
||||
case "not": {
|
||||
host.declare(`declare i32 @scr_jsval_truthy(ptr)`);
|
||||
const r = B.tmp();
|
||||
B.line(`${r} = call i32 @scr_jsval_truthy(ptr ${a(0)})`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = icmp ${e.op === "truthy" ? "ne" : "eq"} i32 ${r}, 0`);
|
||||
return { name: t, type: e.type };
|
||||
}
|
||||
case "typeof": {
|
||||
host.declare(`declare ptr @scr_jsval_typeof(ptr)`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_typeof(ptr ${a(0)})`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
case "toStr":
|
||||
host.declare(`declare ptr @scr_jsval_to_str(ptr)`);
|
||||
return fallible(() => {
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_to_str(ptr ${a(0)})`);
|
||||
return t;
|
||||
});
|
||||
case "getProp":
|
||||
host.declare(`declare ptr @scr_jsval_get_prop(ptr, ptr)`);
|
||||
return fallible(() => {
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_get_prop(ptr ${a(0)}, ptr ${nameSym()})`);
|
||||
return t;
|
||||
});
|
||||
case "globalGet":
|
||||
host.declare(`declare ptr @scr_jsval_global_get(ptr)`);
|
||||
return fallible(() => {
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_global_get(ptr ${nameSym()})`);
|
||||
return t;
|
||||
});
|
||||
case "setProp": {
|
||||
host.declare(`declare i32 @scr_jsval_set_prop(ptr, ptr, ptr)`);
|
||||
B.line(
|
||||
`${B.tmp()} = call i32 @scr_jsval_set_prop(ptr ${a(0)}, ptr ${nameSym()}, ptr ${a(1)})`,
|
||||
);
|
||||
host.emitPendingCheck();
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
case "getIdx":
|
||||
host.declare(`declare ptr @scr_jsval_get_idx(ptr, ptr)`);
|
||||
return fallible(() => {
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_get_idx(ptr ${a(0)}, ptr ${a(1)})`);
|
||||
return t;
|
||||
});
|
||||
case "iterNew":
|
||||
host.declare(`declare ptr @scr_jsval_iter_new(ptr)`);
|
||||
return fallible(() => {
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_iter_new(ptr ${a(0)})`);
|
||||
return t;
|
||||
});
|
||||
case "setIdx": {
|
||||
host.declare(`declare i32 @scr_jsval_set_idx(ptr, ptr, ptr)`);
|
||||
B.line(`${B.tmp()} = call i32 @scr_jsval_set_idx(ptr ${a(0)}, ptr ${a(1)}, ptr ${a(2)})`);
|
||||
host.emitPendingCheck();
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
case "callMethod": {
|
||||
const pack = argPack(args.slice(1).map((x) => x.name));
|
||||
host.declare(`declare ptr @scr_jsval_call_method(ptr, ptr, i32, ptr)`);
|
||||
return fallible(() => {
|
||||
const t = B.tmp();
|
||||
B.line(
|
||||
`${t} = call ptr @scr_jsval_call_method(ptr ${a(0)}, ptr ${nameSym()}, i32 ${args.length - 1}, ptr ${pack})`,
|
||||
);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
case "optCallMethod": {
|
||||
const pack = argPack(args.slice(1).map((x) => x.name));
|
||||
host.declare(`declare ptr @scr_jsval_opt_call_method(ptr, ptr, i32, ptr)`);
|
||||
return fallible(() => {
|
||||
const t = B.tmp();
|
||||
B.line(
|
||||
`${t} = call ptr @scr_jsval_opt_call_method(ptr ${a(0)}, ptr ${nameSym()}, i32 ${args.length - 1}, ptr ${pack})`,
|
||||
);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
case "callFn": {
|
||||
const pack = argPack(args.slice(1).map((x) => x.name));
|
||||
host.declare(`declare ptr @scr_jsval_call(ptr, i32, ptr)`);
|
||||
return fallible(() => {
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_call(ptr ${a(0)}, i32 ${args.length - 1}, ptr ${pack})`);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
case "callFnThis": {
|
||||
const pack = argPack(args.slice(2).map((x) => x.name));
|
||||
host.declare(`declare ptr @scr_jsval_call_this(ptr, ptr, i32, ptr)`);
|
||||
return fallible(() => {
|
||||
const t = B.tmp();
|
||||
B.line(
|
||||
`${t} = call ptr @scr_jsval_call_this(ptr ${a(0)}, ptr ${a(1)}, i32 ${args.length - 2}, ptr ${pack})`,
|
||||
);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
case "callSpread": {
|
||||
// Spread application (`f(...pre, ...s)`): the prelude helper's
|
||||
// real spread syntax — iterator protocols are the engine's own,
|
||||
// the guards front-run V8's spread-call TypeError texts (the name
|
||||
// literal is the spread expression's spelling).
|
||||
host.declare(`declare ptr @scr_jsval_call_spread(ptr, ptr, ptr, ptr)`);
|
||||
return fallible(() => {
|
||||
const t = B.tmp();
|
||||
B.line(
|
||||
`${t} = call ptr @scr_jsval_call_spread(ptr ${a(0)}, ptr ${a(1)}, ptr ${a(2)}, ptr ${nameSym()})`,
|
||||
);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
case "construct": {
|
||||
const pack = argPack(args.slice(1).map((x) => x.name));
|
||||
host.declare(`declare ptr @scr_jsval_construct(ptr, i32, ptr)`);
|
||||
return fallible(() => {
|
||||
const t = B.tmp();
|
||||
B.line(
|
||||
`${t} = call ptr @scr_jsval_construct(ptr ${a(0)}, i32 ${args.length - 1}, ptr ${pack})`,
|
||||
);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
case "objLit": {
|
||||
const pack = argPack(args.map((x) => x.name));
|
||||
host.declare(`declare ptr @scr_jsval_obj_lit(i32, ptr)`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_obj_lit(i32 ${args.length / 2}, ptr ${pack})`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
case "tplStrings": {
|
||||
const pack = argPack(args.map((x) => x.name));
|
||||
host.declare(`declare ptr @scr_jsval_tpl_strings(i32, ptr)`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_tpl_strings(i32 ${args.length / 2}, ptr ${pack})`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
case "objSpread": {
|
||||
host.declare(`declare ptr @scr_jsval_obj_spread(ptr, ptr)`);
|
||||
return fallible(() => {
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_obj_spread(ptr ${a(0)}, ptr ${a(1)})`);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
case "defineGetter": {
|
||||
// Getter completion for an island literal (the runtime ABI’s
|
||||
// scr_jsval_define_getter shape): defines key a(1) on obj a(0)
|
||||
// as an engine getter invoking a(2); answers the object (+1).
|
||||
host.declare(`declare ptr @scr_jsval_define_getter(ptr, ptr, ptr)`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_define_getter(ptr ${a(0)}, ptr ${a(1)}, ptr ${a(2)})`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
case "arrLit": {
|
||||
const pack = argPack(args.map((x) => x.name));
|
||||
host.declare(`declare ptr @scr_jsval_arr_lit(i32, ptr)`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_arr_lit(i32 ${args.length}, ptr ${pack})`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
case "instanceOf": {
|
||||
// JS_IsInstanceOf through the engine: 1 true, 0 false, -1 threw
|
||||
// (Symbol.hasInstance can raise) — the fallible pattern, result
|
||||
// narrowed to bool by comparing against 1.
|
||||
host.declare(`declare i32 @scr_jsval_instance_of(ptr, ptr)`);
|
||||
return fallible(() => {
|
||||
const r = B.tmp();
|
||||
B.line(`${r} = call i32 @scr_jsval_instance_of(ptr ${a(0)}, ptr ${a(1)})`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = icmp eq i32 ${r}, 1`);
|
||||
return t;
|
||||
});
|
||||
}
|
||||
case "undefLit": {
|
||||
host.declare(`declare ptr @scr_jsval_undefined()`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_undefined()`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
case "nullLit": {
|
||||
host.declare(`declare ptr @scr_jsval_null()`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_null()`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
default:
|
||||
throw new InternalCompilerError(`llvm emitter bug: jsOp ${e.op satisfies never}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function emitJsExit(host: LlvmEmitterContext, e: IrExpr & { kind: "jsExit" }): LlValue {
|
||||
const B = host.B;
|
||||
const v = host.emitExpr(e.value);
|
||||
switch (e.type.kind) {
|
||||
case "f64":
|
||||
case "bool": {
|
||||
const isF64 = e.type.kind === "f64";
|
||||
const sym = isF64 ? "scr_jsval_exit_f64" : "scr_jsval_exit_bool";
|
||||
host.declare(`declare i32 @${sym}(ptr, ptr)`);
|
||||
const slot = B.slot();
|
||||
B.entryAllocas.push(`${slot} = alloca ${isF64 ? "double" : "i8"}`);
|
||||
B.line(`store ${isF64 ? `double ${f64Lit(0)}` : "i8 0"}, ptr ${slot}`);
|
||||
B.line(`${B.tmp()} = call i32 @${sym}(ptr ${v.name}, ptr ${slot})`);
|
||||
host.emitPendingCheck();
|
||||
const t = B.tmp();
|
||||
if (isF64) {
|
||||
B.line(`${t} = load double, ptr ${slot}`);
|
||||
return { name: t, type: e.type };
|
||||
}
|
||||
B.line(`${t} = load i8, ptr ${slot}`);
|
||||
const b = B.tmp();
|
||||
B.line(`${b} = trunc i8 ${t} to i1`);
|
||||
return { name: b, type: e.type };
|
||||
}
|
||||
case "string": {
|
||||
host.declare(`declare ptr @scr_jsval_exit_str(ptr)`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_exit_str(ptr ${v.name})`);
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
case "bytes": {
|
||||
// Uint8Array exit: kind-checked, copied out (+1).
|
||||
host.declare(`declare ptr @scr_jsval_exit_bytes(ptr)`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_exit_bytes(ptr ${v.name})`);
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
default: {
|
||||
// `any[]`-declared slot: Array.isArray-gated, elements BY
|
||||
// REFERENCE (identity crosses; the spine is a snapshot copy).
|
||||
// JSON-safe element types keep the round trip below.
|
||||
if (e.type.kind === "array" && e.type.elem.kind === "jsval") {
|
||||
host.declare(`declare ptr @scr_jsval_exit_jsval_arr(ptr)`);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr @scr_jsval_exit_jsval_arr(ptr ${v.name})`);
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
// An undefined-armed union target: the engine's undefined takes
|
||||
// the undefined arm FIRST (JSON cannot spell it); then null and
|
||||
// data ride the round trip into the union's dynCheck like any
|
||||
// composite.
|
||||
const roundTrip = (): string => {
|
||||
host.declare(`declare ptr @scr_jsval_to_json(ptr)`);
|
||||
host.declare(`declare ptr @scr_json_parse(ptr)`);
|
||||
const js = B.tmp();
|
||||
B.line(`${js} = call ptr @scr_jsval_to_json(ptr ${v.name})`);
|
||||
host.own({ name: js, type: STRING });
|
||||
host.emitPendingCheck();
|
||||
const dom = B.tmp();
|
||||
B.line(`${dom} = call ptr @scr_json_parse(ptr ${js})`);
|
||||
host.own({ name: dom, type: { kind: "dyn" } });
|
||||
host.emitPendingCheck();
|
||||
const helper = host.dyn.dynCheckHelper(e.type);
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ${host.llType(e.type)} @${helper}(ptr ${dom}, ptr null)`);
|
||||
return t;
|
||||
};
|
||||
const undefTag = e.type.kind === "union" ? undefinedArmTag(e.type, host.unionsById) : -1;
|
||||
if (e.type.kind === "union" && undefTag >= 0) {
|
||||
host.declare(`declare zeroext i1 @scr_jsval_is_undefined(ptr)`);
|
||||
const isU = B.tmp();
|
||||
B.line(`${isU} = call zeroext i1 @scr_jsval_is_undefined(ptr ${v.name})`);
|
||||
const slot = B.slot();
|
||||
B.entryAllocas.push(`${slot} = alloca ptr`);
|
||||
const lu = B.newLabel("jx.u");
|
||||
const ld = B.newLabel("jx.d");
|
||||
const lj = B.newLabel("jx.j");
|
||||
B.condBr(isU, lu, ld);
|
||||
B.startBlock(lu);
|
||||
B.line(`store ptr ${host.unitInstanceRef(e.type.unionId, undefTag)}, ptr ${slot}`);
|
||||
B.br(lj);
|
||||
B.startBlock(ld);
|
||||
host.frames.push([]);
|
||||
const unionDef = host.unionsById.get(e.type.unionId);
|
||||
const dataArms = unionDef
|
||||
? unionDef.arms.flatMap((a, i) => (isUnitType(a) ? [] : [{ a, i }]))
|
||||
: [];
|
||||
const jsvalArr =
|
||||
dataArms.length === 1 &&
|
||||
dataArms[0]!.a.kind === "array" &&
|
||||
dataArms[0]!.a.elem.kind === "jsval"
|
||||
? dataArms[0]!
|
||||
: null;
|
||||
let t: string;
|
||||
if (jsvalArr) {
|
||||
// The `any[] | undefined` defaulted-parameter spelling: the
|
||||
// engine array exits BY REFERENCE into the data arm.
|
||||
host.declare(`declare ptr @scr_jsval_exit_jsval_arr(ptr)`);
|
||||
const arr = B.tmp();
|
||||
B.line(`${arr} = call ptr @scr_jsval_exit_jsval_arr(ptr ${v.name})`);
|
||||
host.emitPendingCheck();
|
||||
t = host.unionNewOwned(e.type.unionId, jsvalArr.i, { name: arr, type: jsvalArr.a });
|
||||
B.line(`store ptr ${t}, ptr ${slot}`);
|
||||
} else {
|
||||
t = roundTrip();
|
||||
host.own({ name: t, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
host.moveTemp({ name: t, type: e.type });
|
||||
B.line(`store ptr ${t}, ptr ${slot}`);
|
||||
}
|
||||
host.releaseFrame(host.frames.pop()!);
|
||||
B.br(lj);
|
||||
B.startBlock(lj);
|
||||
const out = B.tmp();
|
||||
B.line(`${out} = load ptr, ptr ${slot}`);
|
||||
return host.own({ name: out, type: e.type });
|
||||
}
|
||||
const t = roundTrip();
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function islandAdapter(
|
||||
host: LlvmEmitterContext,
|
||||
arity: number,
|
||||
retKind: "void" | "jsval" | "f64" | "bool" | "string",
|
||||
): string {
|
||||
const tag = { void: "v", jsval: "j", f64: "f", bool: "b", string: "s" }[retKind];
|
||||
const key = `ia:${arity}:${tag}`;
|
||||
let sym = host.resolveThunks.get(key);
|
||||
if (sym) return sym;
|
||||
sym = `sc_ia_${arity}${tag}`;
|
||||
host.resolveThunks.set(key, sym);
|
||||
host.declare(`declare ptr @scr_jsval_retain_v(ptr)`);
|
||||
const d: string[] = [
|
||||
`define internal ptr @${sym}(ptr %c, ptr %argv) ${FN_ATTRS} { ; island host-call adapter (${arity} arg${arity === 1 ? "" : "s"}, ${retKind})`,
|
||||
`entry:`,
|
||||
];
|
||||
const passed: string[] = ["ptr %c"];
|
||||
for (let i = 0; i < arity; i++) {
|
||||
d.push(
|
||||
` %ap${i} = getelementptr inbounds ptr, ptr %argv, i64 ${i}`,
|
||||
` %av${i} = load ptr, ptr %ap${i}`,
|
||||
` %ar${i} = call ptr @scr_jsval_retain_v(ptr %av${i})`,
|
||||
);
|
||||
passed.push(`ptr %ar${i}`);
|
||||
}
|
||||
d.push(
|
||||
` %fnp = getelementptr inbounds %ScrClosure, ptr %c, i64 0, i32 1`,
|
||||
` %fn = load ptr, ptr %fnp`,
|
||||
);
|
||||
switch (retKind) {
|
||||
case "void":
|
||||
d.push(` call void %fn(${passed.join(", ")})`, ` ret ptr null`);
|
||||
break;
|
||||
case "jsval":
|
||||
d.push(` %r = call ptr %fn(${passed.join(", ")})`, ` ret ptr %r`);
|
||||
break;
|
||||
case "f64":
|
||||
host.declare(`declare ptr @scr_jsval_from_f64(double)`);
|
||||
d.push(
|
||||
` %r = call double %fn(${passed.join(", ")})`,
|
||||
` %j = call ptr @scr_jsval_from_f64(double %r)`,
|
||||
` ret ptr %j`,
|
||||
);
|
||||
break;
|
||||
case "bool":
|
||||
host.declare(`declare ptr @scr_jsval_from_bool(i1 zeroext)`);
|
||||
d.push(
|
||||
` %r = call i1 %fn(${passed.join(", ")})`,
|
||||
` %j = call ptr @scr_jsval_from_bool(i1 %r)`,
|
||||
` ret ptr %j`,
|
||||
);
|
||||
break;
|
||||
case "string":
|
||||
// The closure's +1 string marshals in, then releases. NULL is the
|
||||
// throw-path dummy — the wrapper reverse-bridges the pending
|
||||
// exception.
|
||||
host.declare(`declare ptr @scr_jsval_from_str(ptr)`);
|
||||
host.declare(`declare void @scr_str_release(ptr)`);
|
||||
d.push(
|
||||
` %r = call ptr %fn(${passed.join(", ")})`,
|
||||
` %isnull = icmp eq ptr %r, null`,
|
||||
` br i1 %isnull, label %bad, label %ok`,
|
||||
`bad:`,
|
||||
` ret ptr null`,
|
||||
`ok:`,
|
||||
` %j = call ptr @scr_jsval_from_str(ptr %r)`,
|
||||
` call void @scr_str_release(ptr %r)`,
|
||||
` ret ptr %j`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
d.push(`}`, ``);
|
||||
host.resolveThunkDefs.push(...d);
|
||||
return sym;
|
||||
}
|
||||
|
||||
export function islandTypedAdapter(
|
||||
host: LlvmEmitterContext,
|
||||
fn: IrType & { kind: "func" },
|
||||
): string {
|
||||
const ret = islandCallbackRet(
|
||||
fn.ret,
|
||||
(id) => host.recordsById.get(id),
|
||||
(id) => host.unionsById.get(id),
|
||||
);
|
||||
if (!ret)
|
||||
throw new InternalCompilerError(
|
||||
"llvm emitter bug: typed island adapter with unsupported return",
|
||||
);
|
||||
const key = `ita:${fn.params.map((p) => typeKey(p)).join(",")}=>${ret.async ? "P:" : ""}${ret.tag}`;
|
||||
let sym = host.resolveThunks.get(key);
|
||||
if (sym) return sym;
|
||||
sym = `sc_ita_${host.resolveThunks.size}`;
|
||||
host.resolveThunks.set(key, sym);
|
||||
const d: string[] = [
|
||||
`define internal ptr @${sym}(ptr %c, ptr %argv) ${FN_ATTRS} { ; typed island host-call adapter: ${key}`,
|
||||
`entry:`,
|
||||
];
|
||||
// One slot per param: scalars hold the converted value; refs start
|
||||
// NULL so the convfail path can release NULL-tolerantly. Unit-arm
|
||||
// instances are immortal — their release is a no-op.
|
||||
const slotTy: string[] = [];
|
||||
fn.params.forEach((p, i) => {
|
||||
const ty = host.llType(p);
|
||||
const st = ty === "double" ? "double" : ty === "i1" ? "i8" : "ptr";
|
||||
slotTy.push(st);
|
||||
d.push(` %sl${i} = alloca ${st}`);
|
||||
d.push(
|
||||
` store ${st} ${st === "double" ? f64Lit(0) : st === "i8" ? "0" : "null"}, ptr %sl${i}`,
|
||||
);
|
||||
});
|
||||
let canFail = false;
|
||||
const cleanup: string[] = [];
|
||||
fn.params.forEach((p, i) => {
|
||||
if (isRefCounted(p)) {
|
||||
cleanup.push(
|
||||
` %cf${i} = load ptr, ptr %sl${i}`,
|
||||
` call void ${releaseSym(host.shapeHost, p)}(ptr %cf${i})`,
|
||||
);
|
||||
}
|
||||
});
|
||||
let blk = 0;
|
||||
const failCheckPtr = (val: string): void => {
|
||||
// NULL result → convfail.
|
||||
canFail = true;
|
||||
const b = blk++;
|
||||
d.push(
|
||||
` %cn${b} = icmp eq ptr ${val}, null`,
|
||||
` br i1 %cn${b}, label %convfail, label %cont${b}`,
|
||||
`cont${b}:`,
|
||||
);
|
||||
};
|
||||
fn.params.forEach((p, i) => {
|
||||
d.push(
|
||||
` %ap${i} = getelementptr inbounds ptr, ptr %argv, i64 ${i}`,
|
||||
` %av${i} = load ptr, ptr %ap${i}`,
|
||||
);
|
||||
switch (p.kind) {
|
||||
case "dyn":
|
||||
host.declare(`declare ptr @scr_dyn_from_jsval(ptr)`);
|
||||
d.push(
|
||||
` %dr${i} = call ptr @scr_dyn_from_jsval(ptr %av${i})`,
|
||||
` store ptr %dr${i}, ptr %sl${i}`,
|
||||
);
|
||||
break;
|
||||
case "jsval":
|
||||
host.declare(`declare ptr @scr_jsval_retain_v(ptr)`);
|
||||
d.push(
|
||||
` %jr${i} = call ptr @scr_jsval_retain_v(ptr %av${i})`,
|
||||
` store ptr %jr${i}, ptr %sl${i}`,
|
||||
);
|
||||
break;
|
||||
case "f64": {
|
||||
canFail = true;
|
||||
host.declare(`declare i32 @scr_jsval_exit_f64(ptr, ptr)`);
|
||||
const b = blk++;
|
||||
d.push(
|
||||
` %fx${i} = call i32 @scr_jsval_exit_f64(ptr %av${i}, ptr %sl${i})`,
|
||||
` %fk${i} = icmp eq i32 %fx${i}, 0`,
|
||||
` br i1 %fk${i}, label %convfail, label %cont${b}`,
|
||||
`cont${b}:`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "bool": {
|
||||
canFail = true;
|
||||
host.declare(`declare i32 @scr_jsval_exit_bool(ptr, ptr)`);
|
||||
const b = blk++;
|
||||
d.push(
|
||||
` %bx${i} = call i32 @scr_jsval_exit_bool(ptr %av${i}, ptr %sl${i})`,
|
||||
` %bk${i} = icmp eq i32 %bx${i}, 0`,
|
||||
` br i1 %bk${i}, label %convfail, label %cont${b}`,
|
||||
`cont${b}:`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "string": {
|
||||
host.declare(`declare ptr @scr_jsval_exit_str(ptr)`);
|
||||
d.push(` %sx${i} = call ptr @scr_jsval_exit_str(ptr %av${i})`);
|
||||
failCheckPtr(`%sx${i}`);
|
||||
d.push(` store ptr %sx${i}, ptr %sl${i}`);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Composite (record/array/union): the jsExit pipeline — engine
|
||||
// JSON.stringify, json.parse, the interned dynCheck builder.
|
||||
canFail = true;
|
||||
host.declare(`declare ptr @scr_jsval_to_json(ptr)`);
|
||||
host.declare(`declare ptr @scr_json_parse(ptr)`);
|
||||
host.declare(`declare void @scr_str_release(ptr)`);
|
||||
host.declare(`declare void @scr_dyn_release(ptr)`);
|
||||
const utag = p.kind === "union" ? undefinedArmTag(p, host.unionsById) : -1;
|
||||
const b = blk++;
|
||||
if (p.kind === "union" && utag >= 0) {
|
||||
host.declare(`declare zeroext i1 @scr_jsval_is_undefined(ptr)`);
|
||||
d.push(
|
||||
` %iu${i} = call zeroext i1 @scr_jsval_is_undefined(ptr %av${i})`,
|
||||
` br i1 %iu${i}, label %undef${b}, label %conv${b}`,
|
||||
`undef${b}:`,
|
||||
` store ptr ${host.unitInstanceRef(p.unionId, utag)}, ptr %sl${i} ; absent/undefined argument -> the undefined arm`,
|
||||
` br label %cont${b}`,
|
||||
`conv${b}:`,
|
||||
);
|
||||
}
|
||||
d.push(` %tj${i} = call ptr @scr_jsval_to_json(ptr %av${i})`);
|
||||
const b2 = blk++;
|
||||
d.push(
|
||||
` %tjn${i} = icmp eq ptr %tj${i}, null`,
|
||||
` br i1 %tjn${i}, label %convfail, label %cont${b2}`,
|
||||
`cont${b2}:`,
|
||||
` %dp${i} = call ptr @scr_json_parse(ptr %tj${i})`,
|
||||
` call void @scr_str_release(ptr %tj${i})`,
|
||||
);
|
||||
const b3 = blk++;
|
||||
d.push(
|
||||
` %dpn${i} = icmp eq ptr %dp${i}, null`,
|
||||
` br i1 %dpn${i}, label %convfail, label %cont${b3}`,
|
||||
`cont${b3}:`,
|
||||
` %cv${i} = call ${host.llType(p)} @${host.dyn.dynCheckHelper(p)}(ptr %dp${i}, ptr null)`,
|
||||
` call void @scr_dyn_release(ptr %dp${i})`,
|
||||
...host.pendingTestLines(`%pe${i}`),
|
||||
);
|
||||
const b4 = blk++;
|
||||
d.push(
|
||||
` br i1 %pe${i}, label %convfail, label %cont${b4}`,
|
||||
`cont${b4}:`,
|
||||
` store ${host.llType(p)} %cv${i}, ptr %sl${i}`,
|
||||
);
|
||||
if (p.kind === "union" && utag >= 0) d.push(` br label %cont${b}`, `cont${b}:`);
|
||||
}
|
||||
}
|
||||
});
|
||||
// The call over the converted slots (each moves into the callee).
|
||||
const passed = ["ptr %c"];
|
||||
fn.params.forEach((p, i) => {
|
||||
const ty = host.llType(p);
|
||||
if (ty === "i1") {
|
||||
d.push(` %ld${i} = load i8, ptr %sl${i}`, ` %lb${i} = trunc i8 %ld${i} to i1`);
|
||||
passed.push(`i1 %lb${i}`);
|
||||
} else {
|
||||
d.push(` %ld${i} = load ${ty}, ptr %sl${i}`);
|
||||
passed.push(`${ty} %ld${i}`);
|
||||
}
|
||||
});
|
||||
d.push(
|
||||
` %fnp = getelementptr inbounds %ScrClosure, ptr %c, i64 0, i32 1`,
|
||||
` %fn = load ptr, ptr %fnp`,
|
||||
);
|
||||
if (ret.async) {
|
||||
// The closure returns a +1 ScrPromise; from_promise takes ownership.
|
||||
const tagN = { void: 0, f64: 1, bool: 2, string: 3, jsval: 4, json: 0, dyn: 0 }[ret.tag];
|
||||
host.declare(`declare ptr @scr_jsval_from_promise(ptr, i32)`);
|
||||
d.push(
|
||||
` %pr = call ptr %fn(${passed.join(", ")})`,
|
||||
` %prn = icmp eq ptr %pr, null`,
|
||||
` br i1 %prn, label %pnull, label %pok`,
|
||||
`pnull:`,
|
||||
` ret ptr null`,
|
||||
`pok:`,
|
||||
` %pj = call ptr @scr_jsval_from_promise(ptr %pr, i32 ${tagN})`,
|
||||
` ret ptr %pj`,
|
||||
);
|
||||
} else {
|
||||
switch (ret.tag) {
|
||||
case "void":
|
||||
d.push(` call void %fn(${passed.join(", ")})`, ` ret ptr null`);
|
||||
break;
|
||||
case "jsval":
|
||||
d.push(` %r = call ptr %fn(${passed.join(", ")})`, ` ret ptr %r`);
|
||||
break;
|
||||
case "f64":
|
||||
host.declare(`declare ptr @scr_jsval_from_f64(double)`);
|
||||
d.push(
|
||||
` %r = call double %fn(${passed.join(", ")})`,
|
||||
` %j = call ptr @scr_jsval_from_f64(double %r)`,
|
||||
` ret ptr %j`,
|
||||
);
|
||||
break;
|
||||
case "bool":
|
||||
host.declare(`declare ptr @scr_jsval_from_bool(i1 zeroext)`);
|
||||
d.push(
|
||||
` %r = call i1 %fn(${passed.join(", ")})`,
|
||||
` %j = call ptr @scr_jsval_from_bool(i1 %r)`,
|
||||
` ret ptr %j`,
|
||||
);
|
||||
break;
|
||||
case "string":
|
||||
host.declare(`declare ptr @scr_jsval_from_str(ptr)`);
|
||||
host.declare(`declare void @scr_str_release(ptr)`);
|
||||
d.push(
|
||||
` %r = call ptr %fn(${passed.join(", ")})`,
|
||||
` %rn = icmp eq ptr %r, null`,
|
||||
` br i1 %rn, label %snull, label %sok`,
|
||||
`snull:`,
|
||||
` ret ptr null`,
|
||||
`sok:`,
|
||||
` %j = call ptr @scr_jsval_from_str(ptr %r)`,
|
||||
` call void @scr_str_release(ptr %r)`,
|
||||
` ret ptr %j`,
|
||||
);
|
||||
break;
|
||||
case "dyn":
|
||||
// A checked-dynamic (+1 dyn) result: deep copy into the engine
|
||||
// (the jsMarshal dyn rule); NULL is the throw-path dummy.
|
||||
host.declare(`declare ptr @scr_jsval_from_dyn(ptr)`);
|
||||
host.declare(`declare void @scr_dyn_release(ptr)`);
|
||||
d.push(
|
||||
` %r = call ptr %fn(${passed.join(", ")})`,
|
||||
` %rn = icmp eq ptr %r, null`,
|
||||
` br i1 %rn, label %dnull, label %dok`,
|
||||
`dnull:`,
|
||||
` ret ptr null`,
|
||||
`dok:`,
|
||||
` %j = call ptr @scr_jsval_from_dyn(ptr %r)`,
|
||||
` call void @scr_dyn_release(ptr %r)`,
|
||||
` ret ptr %j`,
|
||||
);
|
||||
break;
|
||||
case "json": {
|
||||
// A JSON-safe composite return: the jsMarshal path — the type-
|
||||
// directed serializer, then the engine's JSON parser (deep
|
||||
// copy). NULL result is the throw-path dummy.
|
||||
const helper = host.walkers.jsonWriteHelper(fn.ret);
|
||||
host.declare(`declare void @scr_jb_init(ptr)`);
|
||||
host.declare(`declare ptr @scr_jb_finish(ptr)`);
|
||||
host.declare(`declare ptr @scr_jsval_from_json(ptr)`);
|
||||
host.declare(`declare void @scr_str_release(ptr)`);
|
||||
d.push(
|
||||
` %jbuf = alloca %ScrJsonBuf`,
|
||||
` %rv = call ${host.llType(fn.ret)} %fn(${passed.join(", ")})`,
|
||||
...host.pendingTestLines("%rpend"),
|
||||
` br i1 %rpend, label %jfail, label %jok`,
|
||||
`jfail:`,
|
||||
...(isRefCounted(fn.ret)
|
||||
? [` call void ${releaseSym(host.shapeHost, fn.ret)}(${host.llType(fn.ret)} %rv)`]
|
||||
: []),
|
||||
` ret ptr null`,
|
||||
`jok:`,
|
||||
` call void @scr_jb_init(ptr %jbuf)`,
|
||||
` call void @${helper}(ptr %jbuf, ${host.llType(fn.ret)} %rv)`,
|
||||
...(isRefCounted(fn.ret)
|
||||
? [` call void ${releaseSym(host.shapeHost, fn.ret)}(${host.llType(fn.ret)} %rv)`]
|
||||
: []),
|
||||
` %rj = call ptr @scr_jb_finish(ptr %jbuf)`,
|
||||
` %j = call ptr @scr_jsval_from_json(ptr %rj)`,
|
||||
` call void @scr_str_release(ptr %rj)`,
|
||||
` ret ptr %j`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canFail) {
|
||||
d.push(
|
||||
`convfail:`,
|
||||
// Params already converted release here (NULL-tolerant; unit-arm
|
||||
// instances are immortal — their release is a no-op). The pending
|
||||
// TypeError reverse-bridges in the wrapper.
|
||||
...cleanup,
|
||||
` ret ptr null`,
|
||||
);
|
||||
}
|
||||
d.push(`}`, ``);
|
||||
host.resolveThunkDefs.push(...d);
|
||||
return sym;
|
||||
}
|
||||
@@ -17,7 +17,7 @@ function fixture(): IrModule {
|
||||
loc,
|
||||
});
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "__main",
|
||||
functions: [{ name: "__main", params: [], returnType: VOID, locals: [], body, loc }],
|
||||
@@ -112,7 +112,7 @@ test("reference identity and instanceof borrow their operands", () => {
|
||||
body: [{ kind: "return" as const, value, loc }],
|
||||
});
|
||||
const mod: IrModule = {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "__main",
|
||||
classes: [
|
||||
|
||||
@@ -20,7 +20,7 @@ function sharedGraph(roots: number): IrModule {
|
||||
unionId: `input${index}`,
|
||||
}));
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
records: [
|
||||
|
||||
@@ -294,7 +294,6 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
|
||||
switch (prefix) {
|
||||
case "fetch":
|
||||
case "abort":
|
||||
case "island":
|
||||
case "json":
|
||||
return host.emitWebLibCall(e);
|
||||
case "dyn":
|
||||
|
||||
@@ -86,19 +86,6 @@ export function emitWebLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValu
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
// The handful with non-generic shapes first.
|
||||
if (e.fn === "island.castFail") {
|
||||
// The deferred boundary failure: the island value was evaluated
|
||||
// (its side effects are real), the throw is unconditional
|
||||
// (catchable TypeError naming the target type), and the typed
|
||||
// dummy is NULL — the pending check abandons it.
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.declare(`declare void @scr_jsval_cast_fail(ptr, ptr)`);
|
||||
B.line(`call void @scr_jsval_cast_fail(ptr ${args[0]!.name}, ptr ${args[1]!.name})`);
|
||||
const out = host.own({ name: "null", type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
return host.emitGenericLibCall(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -924,14 +924,6 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"http.clientDestroyed": "scr_http_client_destroyed",
|
||||
"http2.streamUndefCall": "scr_http2_stream_undef_call",
|
||||
"http.agentNew": "scr_http_agent_new",
|
||||
// The island surface (--dynamic): eval/import bridge catchably (the
|
||||
// may-throw seed's pending check); importDyn answers an engine promise
|
||||
// and never throws itself. insp.jsval throws on composite island
|
||||
// values (engine JSON.stringify inside).
|
||||
"island.eval": "scr_island_eval",
|
||||
"island.import": "scr_jsval_import",
|
||||
"island.importDyn": "scr_jsval_import_dyn",
|
||||
"insp.jsval": "scr_insp_jsval",
|
||||
// timers/promises (+1 promises; the await parks a fiber, so the async
|
||||
// loop gate already runs) and diagnostics_channel (scr_dc.c): all
|
||||
// generic shapes; the throwing members ride the pending check.
|
||||
|
||||
@@ -129,7 +129,7 @@ function fixture(): IrModule {
|
||||
],
|
||||
};
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
records: [{ id: "cell", fields: [{ name: "x", type: F64 }] }],
|
||||
|
||||
@@ -82,7 +82,7 @@ function fixture(): IrModule {
|
||||
],
|
||||
};
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
unions: [{ id: "optional", arms: [STRING, UNDEFINED_T] }],
|
||||
|
||||
@@ -67,7 +67,7 @@ function fixture(value: IrType = record, key: IrType = F64): IrModule {
|
||||
],
|
||||
};
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
records: [{ id: "cell", fields: [{ name: "x", type: F64 }] }],
|
||||
|
||||
@@ -27,7 +27,7 @@ function declarationHost(
|
||||
tracedShapes: new Set(),
|
||||
tracedUnions: new Set(),
|
||||
nullableUnions: new NullableUnions({
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: "test.ts",
|
||||
functions: [],
|
||||
entry: "main",
|
||||
@@ -170,7 +170,7 @@ describe("LLVM runtime RC symbols", () => {
|
||||
describe("emitted object releases", () => {
|
||||
test("a surviving headered record enqueues only when not already buffered", () => {
|
||||
const mod: IrModule = {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: "cycle.ts",
|
||||
entry: "main",
|
||||
functions: [],
|
||||
@@ -192,7 +192,7 @@ describe("emitted object releases", () => {
|
||||
|
||||
describe("live-object audit notes", () => {
|
||||
const mod: IrModule = {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: "audit.ts",
|
||||
entry: "main",
|
||||
functions: [],
|
||||
|
||||
@@ -581,8 +581,7 @@ export function arrNewCall(host: ShapeHost, elem: IrType, capText: string): stri
|
||||
elem.kind === "classval" || // local class objects own traced capture boxes
|
||||
elem.kind === "promise" || // promise entries (Promise.all inputs): full REF story
|
||||
elem.kind === "child" || // spawned child handles: scr_child_* adapters, no trace
|
||||
elem.kind === "netServer" || // server handles ([...set] drains): REF, no trace
|
||||
elem.kind === "jsval" || // island handles (`any[]` under --dynamic): REF, no trace
|
||||
elem.kind === "netServer" ||
|
||||
elem.kind === "dyn" || // native collection seeds/drains: traced REF
|
||||
elem.kind === "regex" || // RegExp values: scr_regex_* adapters, no trace (no refs inside)
|
||||
(elem.kind === "array" && traceAdapter(host, elem) !== null);
|
||||
|
||||
@@ -73,7 +73,7 @@ function fixture(arm: IrType = record): IrModule {
|
||||
body: [ret(narrow(ref("value"), arm))],
|
||||
};
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
records: [{ id: "cell", fields: [{ name: "x", type: F64 }] }],
|
||||
|
||||
@@ -17,7 +17,7 @@ const concat = (left: IrExpr, right: IrExpr): IrExpr => ({
|
||||
|
||||
function module(value: IrExpr): IrModule {
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [
|
||||
|
||||
@@ -72,7 +72,7 @@ function fn(name: string, names: string[], value: IrExpr): IrFunction {
|
||||
}
|
||||
function mod(...functions: IrFunction[]): IrModule {
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [
|
||||
|
||||
@@ -13,7 +13,7 @@ function emit(type: IrType, tests: (IrExpr | null)[], pointerBits: 32 | 64 = 64)
|
||||
function emitWith(disc: IrExpr, tests: (IrExpr | null)[], pointerBits: 32 | 64 = 64): string {
|
||||
const type = disc.kind === "varRef" ? disc.type : F64;
|
||||
const mod: IrModule = {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [
|
||||
|
||||
@@ -28,7 +28,7 @@ function fn(name: string, body: IrStmt[], locals: IrLocal[] = [local]): IrFuncti
|
||||
return { name, body, locals, params: [], returnType: VOID, loc };
|
||||
}
|
||||
function moduleWith(...functions: IrFunction[]): IrModule {
|
||||
return { irVersion: 15, sourceFile: loc.file, entry: "caller", functions };
|
||||
return { irVersion: 16, sourceFile: loc.file, entry: "caller", functions };
|
||||
}
|
||||
const assignment: IrStmt = { kind: "assign", localId: local.id, value, loc };
|
||||
const expression: IrExpr = { kind: "assignExpr", localId: local.id, value, type: F64, loc };
|
||||
|
||||
@@ -243,15 +243,6 @@ export function computeMayThrow(
|
||||
)
|
||||
f.throws = true;
|
||||
break;
|
||||
case "jsOp":
|
||||
case "jsExit":
|
||||
case "jsMarshal":
|
||||
case "jsBridgePromise":
|
||||
// Island operations bridge engine exceptions into the cell
|
||||
// (jsMarshal only on engine-level surprise, but the emitted
|
||||
// pending check exists — seed conservatively).
|
||||
f.throws = true;
|
||||
break;
|
||||
case "libCall":
|
||||
// The may-throw seed hook: throwing library calls (fs.*,
|
||||
// json.parse) count exactly like a `throw` statement.
|
||||
|
||||
@@ -16,13 +16,11 @@ import type { NativeTargetSpec } from "./targets.js";
|
||||
|
||||
export interface NativeLinkFeatures {
|
||||
workers?: boolean;
|
||||
dynamic: boolean;
|
||||
regex: boolean;
|
||||
copying: boolean;
|
||||
textDecoderLegacy: boolean;
|
||||
fileHandle: boolean;
|
||||
fetch: boolean;
|
||||
netIsland: boolean;
|
||||
zlib: boolean;
|
||||
assert: boolean;
|
||||
inspect: boolean;
|
||||
|
||||
@@ -299,7 +299,6 @@ test.skipIf(
|
||||
cPath,
|
||||
outPath: join(dir, "host"),
|
||||
cacheIdentity: TEST_CACHE_IDENTITY,
|
||||
dynamic: true,
|
||||
net: true,
|
||||
http: true,
|
||||
tls: true,
|
||||
@@ -309,18 +308,13 @@ test.skipIf(
|
||||
// reusable independently of the output path.
|
||||
systemLibraries: ["m"],
|
||||
});
|
||||
const hostEngine = (await readdir(vendorRoot)).find((name) =>
|
||||
name.startsWith("3c8f3d689539-plain-"),
|
||||
);
|
||||
const hostTls = (await readdir(vendorRoot)).find((name) => name.startsWith("mbedtls-"));
|
||||
expect(hostEngine).toBeDefined();
|
||||
expect(hostTls).toBeDefined();
|
||||
|
||||
process.env["SCRIPTC_CC"] = "zigcc";
|
||||
const zigOptions = {
|
||||
cPath,
|
||||
cacheIdentity: TEST_CACHE_IDENTITY,
|
||||
dynamic: true,
|
||||
net: true,
|
||||
http: true,
|
||||
tls: true,
|
||||
@@ -330,9 +324,6 @@ test.skipIf(
|
||||
await compileC({ ...zigOptions, outPath: join(dir, "zig-first") });
|
||||
|
||||
let vendorEntries = await readdir(vendorRoot);
|
||||
expect(vendorEntries.filter((name) => name.startsWith("3c8f3d689539-plain-"))).toHaveLength(
|
||||
2,
|
||||
);
|
||||
expect(vendorEntries.filter((name) => name.startsWith("mbedtls-")).length).toBe(2);
|
||||
// Host clang uses system zlib, so only the Zig build materializes a zlib
|
||||
// object family in the shared vendor root.
|
||||
@@ -340,22 +331,12 @@ test.skipIf(
|
||||
// Invalidate the host archive. A targetless Zig rebuild must continue to
|
||||
// use the separately keyed Zig archive instead of repairing or consuming
|
||||
// the host-clang entry.
|
||||
await writeFile(
|
||||
join(vendorRoot, hostEngine!, "libqjs.a"),
|
||||
"host archive intentionally invalid\n",
|
||||
);
|
||||
await writeFile(
|
||||
join(vendorRoot, hostTls!, "libmbedtls.a"),
|
||||
"host archive intentionally invalid\n",
|
||||
);
|
||||
await compileC({ ...zigOptions, outPath: join(dir, "zig-second") });
|
||||
vendorEntries = await readdir(vendorRoot);
|
||||
expect(vendorEntries.filter((name) => name.startsWith("3c8f3d689539-plain-"))).toHaveLength(
|
||||
2,
|
||||
);
|
||||
expect(await readFile(join(vendorRoot, hostEngine!, "libqjs.a"), "utf8")).toBe(
|
||||
"host archive intentionally invalid\n",
|
||||
);
|
||||
expect(await readFile(join(vendorRoot, hostTls!, "libmbedtls.a"), "utf8")).toBe(
|
||||
"host archive intentionally invalid\n",
|
||||
);
|
||||
@@ -694,7 +675,7 @@ exec "$@"
|
||||
cPath,
|
||||
outPath: join(dir, "second"),
|
||||
cacheIdentity: TEST_CACHE_IDENTITY,
|
||||
dynamic: true,
|
||||
tls: true,
|
||||
});
|
||||
expect((await readFile(ccacheLog, "utf8")).trim()).not.toBe("");
|
||||
} finally {
|
||||
@@ -2249,27 +2230,22 @@ test("native cache warming seeds exact runtime and vendor families without compl
|
||||
process.env["SCRIPTC_CACHE_DIR"] = cacheRoot;
|
||||
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
||||
delete process.env["SCRIPTC_NO_CACHE"];
|
||||
const warmed = await warmNativeCaches({ profiles: ["runtime", "tls", "dynamic"] });
|
||||
const warmed = await warmNativeCaches({ profiles: ["runtime", "tls"] });
|
||||
expect(warmed.cacheRoot).toBe(cacheRoot);
|
||||
expect(warmed.profiles.map(({ profile }) => profile)).toEqual(["runtime", "tls", "dynamic"]);
|
||||
expect(warmed.profiles.map(({ profile }) => profile)).toEqual(["runtime", "tls"]);
|
||||
expect(warmed.profiles.every(({ elapsedMs }) => elapsedMs >= 0)).toBe(true);
|
||||
expect(
|
||||
(await readdir(join(cacheRoot, "obj"), { withFileTypes: true })).filter((e) =>
|
||||
e.isDirectory(),
|
||||
),
|
||||
).toHaveLength(3);
|
||||
).toHaveLength(2);
|
||||
expect(await readdir(join(cacheRoot, "bin")).catch(() => [])).toEqual([]);
|
||||
const vendorEntries = await readdir(vendorCacheRoot);
|
||||
const tlsEntry = vendorEntries.find((name) => name.startsWith("mbedtls-"));
|
||||
const engineEntry = vendorEntries.find((name) => name.startsWith("3c8f3d689539-plain-"));
|
||||
expect(tlsEntry).toBeDefined();
|
||||
expect(engineEntry).toBeDefined();
|
||||
expect((await stat(join(vendorCacheRoot, tlsEntry!, "libmbedtls.a.sha256"))).isFile()).toBe(
|
||||
true,
|
||||
);
|
||||
expect((await stat(join(vendorCacheRoot, engineEntry!, "libqjs.a.sha256"))).isFile()).toBe(
|
||||
true,
|
||||
);
|
||||
} finally {
|
||||
if (oldCacheDir === undefined) delete process.env["SCRIPTC_CACHE_DIR"];
|
||||
else process.env["SCRIPTC_CACHE_DIR"] = oldCacheDir;
|
||||
@@ -2295,28 +2271,26 @@ test("damaged shared vendor archives are rejected and rebuilt before linking", a
|
||||
delete process.env["SCRIPTC_NO_CACHE"];
|
||||
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
||||
await writeFile(cPath, "int main(void) { return 0; }\n");
|
||||
await warmNativeCaches({ profiles: ["dynamic"] });
|
||||
await warmNativeCaches({ profiles: ["tls"] });
|
||||
|
||||
const vendorRoot = join(cacheRoot, "vendor");
|
||||
const engineDir = (await readdir(vendorRoot)).find((name) =>
|
||||
name.startsWith("3c8f3d689539-plain-"),
|
||||
);
|
||||
expect(engineDir).toBeDefined();
|
||||
const engineArchive = join(vendorRoot, engineDir!, "libqjs.a");
|
||||
expect((await stat(`${engineArchive}.sha256`)).isFile()).toBe(true);
|
||||
await writeFile(engineArchive, corruption);
|
||||
const tlsDir = (await readdir(vendorRoot)).find((name) => name.startsWith("mbedtls-"));
|
||||
expect(tlsDir).toBeDefined();
|
||||
const tlsArchive = join(vendorRoot, tlsDir!, "libmbedtls.a");
|
||||
expect((await stat(`${tlsArchive}.sha256`)).isFile()).toBe(true);
|
||||
await writeFile(tlsArchive, corruption);
|
||||
|
||||
await compileC({
|
||||
cPath,
|
||||
outPath,
|
||||
cacheIdentity: TEST_CACHE_IDENTITY,
|
||||
dynamic: true,
|
||||
tls: true,
|
||||
});
|
||||
|
||||
expect((await stat(outPath)).isFile()).toBe(true);
|
||||
const repaired = await readFile(engineArchive);
|
||||
const repaired = await readFile(tlsArchive);
|
||||
expect(repaired).not.toEqual(corruption);
|
||||
expect((await readFile(`${engineArchive}.sha256`, "utf8")).trim()).toBe(
|
||||
expect((await readFile(`${tlsArchive}.sha256`, "utf8")).trim()).toBe(
|
||||
createHash("sha256").update(repaired).digest("hex"),
|
||||
);
|
||||
} finally {
|
||||
@@ -2329,7 +2303,7 @@ test("damaged shared vendor archives are rejected and rebuilt before linking", a
|
||||
}
|
||||
}, 120_000);
|
||||
|
||||
test("dynamic builds promote staged vendor archives before bounded LRU eviction", async () => {
|
||||
test("TLS builds promote staged vendor archives before bounded LRU eviction", async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), "scriptc-vendor-lru-"));
|
||||
scratch.push(dir);
|
||||
const cacheRoot = join(dir, "cache");
|
||||
@@ -2345,21 +2319,19 @@ test("dynamic builds promote staged vendor archives before bounded LRU eviction"
|
||||
delete process.env["SCRIPTC_CACHE_MAX_MB"];
|
||||
delete process.env["SCRIPTC_TEST_VENDOR_CACHE_DIR"];
|
||||
await writeFile(cPath, "int main(void) { return 0; }\n");
|
||||
await warmNativeCaches({ profiles: ["dynamic"] });
|
||||
await warmNativeCaches({ profiles: ["tls"] });
|
||||
|
||||
const vendorRoot = join(cacheRoot, "vendor");
|
||||
const engineDir = (await readdir(vendorRoot)).find((name) =>
|
||||
name.startsWith("3c8f3d689539-plain-"),
|
||||
);
|
||||
expect(engineDir).toBeDefined();
|
||||
const engineArchive = join(vendorRoot, engineDir!, "libqjs.a");
|
||||
const tlsDir = (await readdir(vendorRoot)).find((name) => name.startsWith("mbedtls-"));
|
||||
expect(tlsDir).toBeDefined();
|
||||
const tlsArchive = join(vendorRoot, tlsDir!, "libmbedtls.a");
|
||||
const initialBytes = await cacheTreeBytes(cacheRoot);
|
||||
const capMb = Math.max(4, Math.ceil((initialBytes * 2) / (1024 * 1024)));
|
||||
const filler = join(cacheRoot, "filler.bin");
|
||||
const staleArchiveTime = new Date("2000-01-01T00:00:00.000Z");
|
||||
const fillerTime = new Date("2001-01-01T00:00:00.000Z");
|
||||
await writeFile(filler, Buffer.alloc(capMb * 1024 * 1024));
|
||||
await utimes(engineArchive, staleArchiveTime, staleArchiveTime);
|
||||
await utimes(tlsArchive, staleArchiveTime, staleArchiveTime);
|
||||
await utimes(filler, fillerTime, fillerTime);
|
||||
process.env["SCRIPTC_CACHE_MAX_MB"] = String(capMb);
|
||||
|
||||
@@ -2367,11 +2339,11 @@ test("dynamic builds promote staged vendor archives before bounded LRU eviction"
|
||||
cPath,
|
||||
outPath,
|
||||
cacheIdentity: TEST_CACHE_IDENTITY,
|
||||
dynamic: true,
|
||||
tls: true,
|
||||
});
|
||||
|
||||
expect((await stat(outPath)).isFile()).toBe(true);
|
||||
expect((await stat(engineArchive)).mtimeMs).toBeGreaterThan(fillerTime.getTime());
|
||||
expect((await stat(tlsArchive)).mtimeMs).toBeGreaterThan(fillerTime.getTime());
|
||||
expect(await cacheTreeBytes(cacheRoot)).toBeLessThanOrEqual(capMb * 1024 * 1024);
|
||||
} finally {
|
||||
if (oldCacheDir === undefined) delete process.env["SCRIPTC_CACHE_DIR"];
|
||||
@@ -2823,7 +2795,8 @@ test.skipIf(process.platform === "win32")(
|
||||
expect(realClang).toBeDefined();
|
||||
|
||||
const waitForSignal = async (): Promise<void> => {
|
||||
const deadline = Date.now() + 10_000;
|
||||
// A cold runtime compile shares CPUs with the full Sandbox gate.
|
||||
const deadline = Date.now() + 60_000;
|
||||
while (!existsSync(signal)) {
|
||||
if (Date.now() > deadline) throw new Error("timed out waiting for final compilation");
|
||||
await new Promise((resolveWait) => setTimeout(resolveWait, 10));
|
||||
|
||||
@@ -370,8 +370,6 @@ export function localArtifactIdentity(
|
||||
);
|
||||
}
|
||||
return hash
|
||||
.update(process.env["SCRIPTC_FETCH_CURL"] === "1" ? "fetch-curl" : "fetch-native")
|
||||
.update("\0")
|
||||
.update(JSON.stringify(normalizedOptions))
|
||||
.update("\0")
|
||||
.update(resolve(opts.cPath))
|
||||
|
||||
@@ -9,7 +9,7 @@ import { type CcDriver, targetPlatform, resolveCc } from "./driver.js";
|
||||
import { clearCcCaches } from "./runtime-objects.js";
|
||||
import { compileCInternal } from "./executable.js";
|
||||
|
||||
export type NativeCacheWarmProfile = "runtime" | "tls" | "dynamic";
|
||||
export type NativeCacheWarmProfile = "runtime" | "tls";
|
||||
|
||||
export interface WarmNativeCachesOptions {
|
||||
/** Native object posture to seed. Defaults to the shipped release/-O2 lane. */
|
||||
@@ -29,7 +29,7 @@ export function supportedNativeCacheWarmProfiles(
|
||||
driver: CcDriver,
|
||||
): readonly NativeCacheWarmProfile[] {
|
||||
if (isMobileTarget(driver.target) || targetPlatform(driver) === "wasi") return [];
|
||||
return ["runtime", "tls", "dynamic"];
|
||||
return ["runtime", "tls"];
|
||||
}
|
||||
|
||||
/** Populate expensive native prerequisites against the current compiler,
|
||||
@@ -48,7 +48,7 @@ export async function warmNativeCaches(
|
||||
);
|
||||
}
|
||||
await ensurePrivateCacheRoot(cacheRoot, process.env["SCRIPTC_CACHE_DIR"] === undefined);
|
||||
const known = new Set<NativeCacheWarmProfile>(["runtime", "tls", "dynamic"]);
|
||||
const known = new Set<NativeCacheWarmProfile>(["runtime", "tls"]);
|
||||
for (const profile of options.profiles ?? []) {
|
||||
if (!known.has(profile)) throw new Error(`unknown native cache warm profile '${profile}'`);
|
||||
}
|
||||
@@ -91,7 +91,6 @@ export async function warmNativeCaches(
|
||||
optimization: options.optimization ?? "release",
|
||||
sanitize: options.sanitize ?? false,
|
||||
...(profile === "tls" ? { fetch: true } : {}),
|
||||
...(profile === "dynamic" ? { dynamic: true } : {}),
|
||||
},
|
||||
true,
|
||||
protectedPaths,
|
||||
|
||||
@@ -13,13 +13,11 @@ import {
|
||||
import { existingDriverTracePaths, normalizedProbeInvocation } from "./trace-paths.js";
|
||||
import { stableTestMemo } from "./session.js";
|
||||
import {
|
||||
QJS_ENGINE_SOURCES,
|
||||
LRE_SOURCES,
|
||||
ZLIB_SOURCES,
|
||||
vendorEngineDir,
|
||||
vendorTlsDir,
|
||||
vendorZlibDir,
|
||||
vendorCurlDir,
|
||||
} from "./vendor.js";
|
||||
import { nativeSourceFiles, runtimeSrcDir } from "./runtime-inputs.js";
|
||||
|
||||
@@ -53,7 +51,7 @@ interface EffectiveCompilerInvocationProbe {
|
||||
* intentionally uses a target-wide synthetic TU so it can discover every
|
||||
* owned system header, but that generic command is not sufficient identity for
|
||||
* a wrapper that injects flags or preincluded files only for a particular build
|
||||
* flavor (for example, only when it sees -O2 or -DSCR_DYNAMIC). */
|
||||
* flavor (for example, only when it sees -O2). */
|
||||
async function effectiveCompilerInvocationFingerprintFresh(
|
||||
driver: Pick<CcDriver, "argv">,
|
||||
environmentFingerprint: string,
|
||||
@@ -166,10 +164,10 @@ export function effectiveCompilerInvocationFingerprint(
|
||||
* hashes the resolved files. Runtime-local and vendored source/header bytes
|
||||
* have their own content/version identities; scanning every owned file for
|
||||
* angle includes closes the system-header gap in separately built QuickJS,
|
||||
* libregexp, mbedTLS, zlib, curl, and Ryū translation units. */
|
||||
* libregexp, mbedTLS, zlib, and Ryū translation units. */
|
||||
export async function implicitDependencyProbeIncludes(rtDir: string): Promise<string[]> {
|
||||
const vendor = join(rtDir, "..", "vendor");
|
||||
const quickjsSources = new Set<string>([...QJS_ENGINE_SOURCES, ...LRE_SOURCES]);
|
||||
const quickjsSources = new Set<string>(LRE_SOURCES);
|
||||
const zlibSources = new Set<string>(ZLIB_SOURCES);
|
||||
const fileGroups = await Promise.all([
|
||||
nativeSourceFiles(rtDir, false),
|
||||
@@ -186,7 +184,6 @@ export async function implicitDependencyProbeIncludes(rtDir: string): Promise<st
|
||||
false,
|
||||
(name) => name.endsWith(".h") || zlibSources.has(name),
|
||||
),
|
||||
nativeSourceFiles(join(vendor, "curl", "include"), true, (name) => name.endsWith(".h")),
|
||||
]);
|
||||
const includes = new Set<string>();
|
||||
for (const path of fileGroups.flat()) {
|
||||
@@ -383,8 +380,6 @@ async function implicitToolchainFingerprintsFresh(
|
||||
join(vendorTlsDir(), "library"),
|
||||
"-I",
|
||||
vendorZlibDir(),
|
||||
"-I",
|
||||
join(vendorCurlDir(), "include"),
|
||||
"-M",
|
||||
];
|
||||
const [dependencyResults, linker, assembler, compilerInvocation] = await Promise.all([
|
||||
|
||||
@@ -44,20 +44,9 @@ export interface CcOptions {
|
||||
systemLibraries?: readonly string[];
|
||||
/** Darwin framework names, emitted as distinct driver arguments. */
|
||||
frameworks?: readonly string[];
|
||||
/** Embed the dynamic-island engine (--dynamic): compiles scr_island.c,
|
||||
* defines SCR_DYNAMIC, and links the cached libqjs.a. Off retains the
|
||||
* static runtime selection; executable section GC may still remove
|
||||
* unreachable static-runtime code. */
|
||||
dynamic?: boolean;
|
||||
/** Isolated native Worker contexts; every runtime object must use TLS. */
|
||||
workers?: boolean;
|
||||
/** The program contains a regex construct (index.ts detects it on the
|
||||
* IR): compiles scr_regex.c and links the vendored libregexp — as cached
|
||||
* standalone objects in static builds, from the engine archive under
|
||||
* --dynamic (one libregexp per binary; its host hooks want the island's
|
||||
* JSContext there). Off = regex-free: the command line is exactly the
|
||||
* historical runtime selection; executable section GC may remove unrelated
|
||||
* unreachable code. */
|
||||
|
||||
regex?: boolean;
|
||||
/** The program uses one of the copying/typed-array bridge intrinsics
|
||||
* implemented in scr_copying.c (index.ts detects them on the IR).
|
||||
@@ -77,23 +66,10 @@ export interface CcOptions {
|
||||
* scr_tls + scr_http's client parser + zlib — the socket units join
|
||||
* the link implicitly, no libcurl anywhere), which builds for every
|
||||
* target the socket units reach: hosts, linux cross, win32 cross.
|
||||
* Static user-code fetch compiles the same TU without the engine; the
|
||||
* broader web surface still uses its dynamic half. Fetch-free builds
|
||||
* keep their exact link line. SCRIPTC_FETCH_CURL=1 selects the retired
|
||||
* curl reference instead
|
||||
* (scr_fetch_curl.c + system libcurl on hosts / the generated soname
|
||||
* stub on linux cross targets — ensureCurlStub), kept compilable for
|
||||
* one release as the flip's reference. */
|
||||
* Fetch-free builds keep their exact link line. */
|
||||
fetch?: boolean;
|
||||
/** The embedded npm graph imports node:http or node:https (index.ts
|
||||
* detects it on the IR): compiles the island's http/https client
|
||||
* bridge (scr_net_island.c) and implies the socket units into the
|
||||
* link, exactly like fetch. The emitted main calls
|
||||
* scr_net_island_install on the same predicate (native-fetch builds
|
||||
* also register it from scr_fetch_install). */
|
||||
netIsland?: boolean;
|
||||
/** The program uses zlib (index.ts detects zlib.* libCalls on the IR):
|
||||
* compiles scr_zlib.c — the regex/curl gating precedent, so zlib-free
|
||||
* compiles scr_zlib.c — the regex gating precedent, so zlib-free
|
||||
* binaries keep their exact link line. The default host-clang build links
|
||||
* the SYSTEM libz (macOS ships it), byte-identical to the historical line;
|
||||
* every Zig build compiles the vendored zlib with the selected driver instead
|
||||
@@ -199,7 +175,7 @@ export interface CcOptions {
|
||||
nodeTest?: boolean;
|
||||
/** The program uses the node:tls or node:https surface (moduleUsesTls on
|
||||
* the IR): compiles scr_tls.c and links the vendored mbedTLS archive
|
||||
* (built lazily like the engine archive, cached per flavor). Always
|
||||
* (built lazily and cached per flavor). Always
|
||||
* alongside scr_net.c and scr_http.c, which moduleUsesNet /
|
||||
* moduleUsesHttpServer answer true for whenever this does. TLS-free
|
||||
* binaries keep their exact link line and never compile mbedTLS. */
|
||||
|
||||
@@ -27,9 +27,7 @@ export function executableSectionEliminationFlags(platform: string): {
|
||||
} {
|
||||
switch (platform) {
|
||||
case "darwin":
|
||||
// ld64's symbol subsections make this sufficient for ordinary C/LLVM
|
||||
// objects. Do not attach it only to --dynamic: static programs have the
|
||||
// same unreachable runtime sections.
|
||||
// ld64's symbol subsections let dead_strip remove unreachable runtime code.
|
||||
return { compile: [], link: ["-Wl,-dead_strip"] };
|
||||
case "linux":
|
||||
return {
|
||||
@@ -51,51 +49,6 @@ export function executableSectionEliminationFlags(platform: string): {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── alternate C compiler (SCRIPTC_CC) and cross target (SCRIPTC_TARGET) ──────────
|
||||
* SCRIPTC_CC=zigcc swaps the compiler driver to `zig cc` (clang underneath, with
|
||||
* zig's bundled sysroots — the door to cross-compiling). Unset or
|
||||
* SCRIPTC_CC=clang is the default. Host Linux adds the two glibc requirements
|
||||
* that macOS does not need: -D_GNU_SOURCE while compiling, and -lm after all
|
||||
* link inputs. Other hosts keep the historical bare-clang command line.
|
||||
*
|
||||
* SCRIPTC_TARGET=<triple> (zigcc only — plain clang has no cross sysroots here)
|
||||
* adds `-target <triple>` to every compile. Linux triples also add
|
||||
* -D_GNU_SOURCE: glibc hides POSIX/GNU declarations (kill, realpath, stpcpy,
|
||||
* arc4random_buf, posix_spawn_file_actions_addchdir_np, ...) under plain
|
||||
* -std=c11, where macOS exposes everything by default. Musl triples additionally
|
||||
* carry SCR_MUSL because musl deliberately exposes no libc-identification macro;
|
||||
* the runtime uses it only for the narrow libc shim in scr_musl.c. Pin the glibc
|
||||
* minor in GNU triples (e.g. aarch64-linux-gnu.2.36) so the binary runs on the
|
||||
* differential container's distro (see tests/harness/linux-differential.test.ts).
|
||||
*
|
||||
* Fetch is NATIVE everywhere (scr_fetch.c over the socket units — no
|
||||
* libcurl), so it cross-compiles wherever net/http/tls do, win32
|
||||
* included. The retired curl reference (SCRIPTC_FETCH_CURL=1) keeps the
|
||||
* historical host -lcurl link and the linux-gnu import-STUB arm (soname
|
||||
* libcurl.so.4 — see ensureCurlStub). The event-loop units
|
||||
* (net/http/dgram/watch) cross-compile to Linux: the readiness poller is
|
||||
* the scr_platform.h contract with kqueue and epoll backends; libregexp,
|
||||
* zlib, mbedTLS, and the engine archive (--dynamic) build per target
|
||||
* (ensureLreObjects / ensureZlibObjects / ensureTlsArchive /
|
||||
* buildEngineArchiveDirect — default host-clang zlib builds still link the
|
||||
* system libz, byte-identically; Zig builds use vendored zlib objects).
|
||||
* Windows triples (x86_64-windows-gnu, mingw-w64 headers and CRT via zig)
|
||||
* have no gates left: events, net/http, fetch, watch, zlib, dgram/dns,
|
||||
* tls, and the engine archive (--dynamic) all build per target through
|
||||
* their win32 arms (mbedTLS compiles unchanged for the triple — its own _WIN32
|
||||
* port covers entropy and timing; the TLS link adds bcrypt for
|
||||
* BCryptGenRandom, while the CA-store unit adds crypt32 for the Windows
|
||||
* system certificate stores).
|
||||
* They additionally compile
|
||||
* scr_win.c, the win32 libc shim TU (stpcpy, arc4random_buf — see the
|
||||
* _WIN32 block in scr_runtime.h), linking -ladvapi32 for its CSPRNG
|
||||
* (RtlGenRandom) and GetUserNameA. Native zigcc builds (no SCRIPTC_TARGET)
|
||||
* support everything: same platform, same archives, just a different
|
||||
* driver binary.
|
||||
* Mobile triples (aarch64-apple-ios, aarch64-apple-ios-simulator,
|
||||
* aarch64-linux-android — see the mobile-targets block below) are
|
||||
* LIBRARY-MODE targets: compileLibArchive accepts them, compile()/compileC
|
||||
* refuse the executable lane with the pointer to --lib. */
|
||||
export interface CcDriver {
|
||||
/** The compiler argv prefix: ["clang"] (default) or ["zig", "cc"]. */
|
||||
argv: string[];
|
||||
|
||||
@@ -81,21 +81,16 @@ import {
|
||||
import {
|
||||
currentVendorCacheBuildIdentity,
|
||||
vendorBuildCacheRoot,
|
||||
engineArchivePath,
|
||||
tlsArchivePath,
|
||||
lreObjectPaths,
|
||||
zlibObjectPaths,
|
||||
curlStubDirPath,
|
||||
ensureEngineArchive,
|
||||
stageVendorInputs,
|
||||
ensureTlsArchive,
|
||||
ensureLreObjects,
|
||||
ensureZlibObjects,
|
||||
ensureCurlStub,
|
||||
vendorEngineDir,
|
||||
vendorZlibDir,
|
||||
vendorTlsDir,
|
||||
vendorCurlDir,
|
||||
} from "./vendor.js";
|
||||
import { execFileAsync, subprocessFailureDetail, CcCompileError } from "./process.js";
|
||||
import { implicitLinkerFingerprint } from "./linker-fingerprint.js";
|
||||
@@ -103,21 +98,6 @@ import { CacheInputsChangedError } from "./session.js";
|
||||
import { ensureRuntimeObjects, stageRuntimeObjects, clearCcCaches } from "./runtime-objects.js";
|
||||
import { localizeLibraryObjects } from "./object-merge.js";
|
||||
|
||||
/** Compiles one C program together with the runtime sources.
|
||||
* With caching disabled, the runtime (a dozen small files) is recompiled on
|
||||
* every build — no cached-archive staleness bugs. Sanitized dev builds compile
|
||||
* the program separately so its O0 setting does not slow the O1 runtime.
|
||||
* Other builds use one clang invocation. --dynamic additionally compiles
|
||||
* scr_island.c under SCR_DYNAMIC and links the cached engine archive (built
|
||||
* lazily, see above); regex-using programs additionally compile scr_regex.c
|
||||
* and link libregexp (the cached objects, or the archive's own copy under
|
||||
* --dynamic). Executable links use the target's section-elimination recipe
|
||||
* independently of those feature gates.
|
||||
*
|
||||
* With a caller-supplied dependency identity and an enabled cache root,
|
||||
* unchanged programs skip payload code generation/linking via the binary
|
||||
* cache after lightweight metadata probes, and misses link the program's own
|
||||
* TU against cached per-flavor runtime objects. */
|
||||
export async function compileCInternal(
|
||||
opts: CcOptions,
|
||||
cacheWarmOnly: boolean,
|
||||
@@ -126,29 +106,14 @@ export async function compileCInternal(
|
||||
const rtDir = runtimeSrcDir();
|
||||
const sanitize = opts.sanitize ?? false;
|
||||
const optimization = opts.optimization ?? "release";
|
||||
const dynamic = opts.dynamic ?? false;
|
||||
if (opts.workers && (dynamic || targetPlatform(resolveCc()) === "wasi"))
|
||||
if (opts.workers && targetPlatform(resolveCc()) === "wasi")
|
||||
throw new Error("worker threads require a native static executable");
|
||||
const regex = opts.regex ?? false;
|
||||
// fetch's implementation switch: the default is the NATIVE bridge
|
||||
// (scr_fetch.c over scr_net + scr_tls + scr_http's client parser +
|
||||
// zlib — no libcurl anywhere), which implies the socket units into the
|
||||
// link. SCRIPTC_FETCH_CURL=1 keeps the retired curl reference
|
||||
// (scr_fetch_curl.c + system libcurl / the linux soname stub)
|
||||
// compilable for one release as the flip's reference.
|
||||
const fetchOn = opts.fetch ?? false;
|
||||
// The retired curl bridge has only a SCR_DYNAMIC implementation.
|
||||
// Static fetch always keeps the native runtime even when a developer
|
||||
// has the comparison switch exported in their shell.
|
||||
const curlFetch = dynamic && fetchOn && process.env["SCRIPTC_FETCH_CURL"] === "1";
|
||||
const nativeFetch = fetchOn && !curlFetch;
|
||||
// The island's node:http/https client bridge: embedded graphs that
|
||||
// import those builtins get working clients over the same socket units
|
||||
// (native-fetch builds always carry it — scr_fetch_install registers it).
|
||||
const netIsland = dynamic && ((opts.netIsland ?? false) || nativeFetch);
|
||||
const net = (opts.net ?? false) || nativeFetch || netIsland;
|
||||
const http = (opts.http ?? false) || nativeFetch || netIsland;
|
||||
const tls = (opts.tls ?? false) || nativeFetch || netIsland;
|
||||
// Fetch shares the native network, TLS, HTTP, and zlib runtime.
|
||||
const nativeFetch = opts.fetch ?? false;
|
||||
const net = (opts.net ?? false) || nativeFetch;
|
||||
const http = (opts.http ?? false) || nativeFetch;
|
||||
const tls = (opts.tls ?? false) || nativeFetch;
|
||||
const tlsCa = (opts.tlsCa ?? false) || tls;
|
||||
const driver = resolveCc();
|
||||
const darwinDebugSymbols = needsDarwinDebugSymbols(
|
||||
@@ -195,42 +160,6 @@ export async function compileCInternal(
|
||||
// POSIX drivers need the thread compile/link mode; win32 uses CreateThread.
|
||||
const threadArgs =
|
||||
targetPlatform(driver) === "win32" || targetPlatform(driver) === "wasi" ? [] : ["-pthread"];
|
||||
if (driver.target !== null) {
|
||||
// See the resolveCc block: these inputs are built on and for the HOST
|
||||
// (vendored archives, system libs). Regex, zlib, and the engine archive
|
||||
// are NOT here: their vendored sources are plain C that ensureLreObjects
|
||||
// / ensureZlibObjects / buildEngineArchiveDirect compile per target with
|
||||
// the driver itself — win32 included (the Windows lane runs the
|
||||
// @dynamic corpus and the zlib program against the box's Node). The
|
||||
// NATIVE fetch rides the socket units and cross-compiles with them —
|
||||
// no gate; only the retired curl REFERENCE (SCRIPTC_FETCH_CURL=1)
|
||||
// keeps a linux-only arm (the vendored curl headers + the generated
|
||||
// soname stub, ensureCurlStub — win32 has no system libcurl contract
|
||||
// to bind at load time). The event-loop units, tls, and dgram cross-compile to Linux
|
||||
// AND Windows (scr_platform.h poller + per-target mbedTLS; the
|
||||
// loop's win32 arm is WSAPoll, scr_loop_wsapoll.c, and the socket
|
||||
// units' win32 arms respell winsock behind POSIX-errno wrappers —
|
||||
// scr_net.c/scr_dgram.c/scr_tls.c). The events unit cross-compiles
|
||||
// everywhere: its win32 arm is CRT signal() +
|
||||
// PeekNamedPipe/WaitForSingleObject probes (scr_events.c), served by
|
||||
// the loop's capped win32 idle sleep (scr_async.c).
|
||||
const unsupported = (
|
||||
[
|
||||
// The NATIVE fetch cross-compiles wherever the socket units do
|
||||
// (linux and win32 both); only the retired curl reference keeps
|
||||
// its linux-only soname-stub arm.
|
||||
["fetch (SCRIPTC_FETCH_CURL)", curlFetch && targetPlatform(driver) !== "linux"],
|
||||
] as const
|
||||
)
|
||||
.filter(([, on]) => on)
|
||||
.map(([name]) => name);
|
||||
if (unsupported.length > 0) {
|
||||
throw new Error(
|
||||
`SCRIPTC_TARGET=${driver.target}: ${unsupported.join(", ")} not supported under a cross target yet ` +
|
||||
`(host-built vendor archives / system libs — see docs/linux-port.md).`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const cachePolicy = toolchainEnvironmentCachePolicy();
|
||||
const configuredCacheRoot = cacheRootDir();
|
||||
const toolchainEnv = toolchainEnvironmentFingerprint();
|
||||
@@ -422,20 +351,15 @@ export async function compileCInternal(
|
||||
? null
|
||||
: join(tmpdir(), `scriptc-vendor-${process.pid}-${Math.random().toString(36).slice(2)}`);
|
||||
const vendorCacheRoot = transientVendorRoot ?? vendorBuildCacheRoot(persistentCache?.root);
|
||||
// Every vendor output path is deterministic from pins, flags, driver, and
|
||||
// target. Build the command/key from those paths now, but do not materialize
|
||||
// them until a complete-binary lookup has missed.
|
||||
let engineArchive = dynamic
|
||||
? engineArchivePath(sanitize, driver, vendorBuildIdentity, vendorCacheRoot)
|
||||
: null;
|
||||
let tlsArchive = tls
|
||||
? tlsArchivePath(sanitize, driver, vendorBuildIdentity, vendorCacheRoot)
|
||||
: null;
|
||||
// --dynamic + regex shares the archive's libregexp (its host hooks and
|
||||
|
||||
// ours would collide; see scr_regex.c) — the standalone objects are for
|
||||
// static builds only.
|
||||
let lreObjects =
|
||||
regex && !dynamic ? lreObjectPaths(sanitize, driver, vendorBuildIdentity, vendorCacheRoot) : [];
|
||||
let lreObjects = regex
|
||||
? lreObjectPaths(sanitize, driver, vendorBuildIdentity, vendorCacheRoot)
|
||||
: [];
|
||||
// Vendored zlib is the Zig story — default host-clang builds keep the exact
|
||||
// historical `-lz` system link (see CcOptions.zlib). The native fetch's
|
||||
// gzip decoder rides the same objects/link.
|
||||
@@ -443,36 +367,13 @@ export async function compileCInternal(
|
||||
((opts.zlib ?? false) || nativeFetch) && isZigDriver(driver)
|
||||
? zlibObjectPaths(sanitize, driver, vendorBuildIdentity, vendorCacheRoot)
|
||||
: [];
|
||||
// The libcurl import stub is likewise CROSS-only — host builds keep the
|
||||
// exact historical system `-lcurl` link (see CcOptions.fetch). Curl
|
||||
// reference builds only.
|
||||
let curlStubDir =
|
||||
curlFetch && driver.target !== null
|
||||
? curlStubDirPath(driver, vendorBuildIdentity, vendorCacheRoot)
|
||||
: null;
|
||||
const materializeVendorPrerequisites = async (
|
||||
stageRoot?: string,
|
||||
materializeCacheRoot: string = vendorCacheRoot,
|
||||
): Promise<void> => {
|
||||
// Preserve source order to avoid multiplying first-build resource
|
||||
// pressure when several large vendor sets are cold simultaneously.
|
||||
if (dynamic) {
|
||||
const cachedArchive = engineArchivePath(
|
||||
sanitize,
|
||||
driver,
|
||||
vendorBuildIdentity,
|
||||
materializeCacheRoot,
|
||||
);
|
||||
protectCachedArtifact(cacheWarmPaths, cachedArchive);
|
||||
const materialize = async (): Promise<string[]> => [
|
||||
await ensureEngineArchive(sanitize, driver, vendorBuildIdentity, materializeCacheRoot),
|
||||
];
|
||||
const paths =
|
||||
stageRoot === undefined
|
||||
? await materialize()
|
||||
: await stageVendorInputs(materialize, join(stageRoot, "engine"));
|
||||
engineArchive = paths[0]!;
|
||||
}
|
||||
|
||||
if (tls) {
|
||||
const cachedArchive = tlsArchivePath(
|
||||
sanitize,
|
||||
@@ -490,7 +391,7 @@ export async function compileCInternal(
|
||||
: await stageVendorInputs(materialize, join(stageRoot, "tls"));
|
||||
tlsArchive = paths[0]!;
|
||||
}
|
||||
if (regex && !dynamic) {
|
||||
if (regex) {
|
||||
for (const object of lreObjectPaths(
|
||||
sanitize,
|
||||
driver,
|
||||
@@ -520,20 +421,6 @@ export async function compileCInternal(
|
||||
? await materialize()
|
||||
: await stageVendorInputs(materialize, join(stageRoot, "zlib"));
|
||||
}
|
||||
if (curlStubDir !== null) {
|
||||
protectCachedArtifact(
|
||||
cacheWarmPaths,
|
||||
join(curlStubDirPath(driver, vendorBuildIdentity, materializeCacheRoot), "libcurl.so"),
|
||||
);
|
||||
const materialize = async (): Promise<string[]> => [
|
||||
join(await ensureCurlStub(driver, vendorBuildIdentity, materializeCacheRoot), "libcurl.so"),
|
||||
];
|
||||
const paths =
|
||||
stageRoot === undefined
|
||||
? await materialize()
|
||||
: await stageVendorInputs(materialize, join(stageRoot, "curl"));
|
||||
curlStubDir = dirname(paths[0]!);
|
||||
}
|
||||
};
|
||||
// rt() maps each runtime source's path on the command line: identity for
|
||||
// the historical single invocation, cached-.o substitution on cache misses.
|
||||
@@ -614,10 +501,9 @@ export async function compileCInternal(
|
||||
? ["-I", vendorZlibDir(), ...zlibObjects]
|
||||
: []
|
||||
: []),
|
||||
// The zlib ↔ island bridge: only when BOTH halves are in the build
|
||||
// (the scr_inspect_island.c pattern) — the emitted main calls its
|
||||
|
||||
// through the native installation hooks — the emitted main calls its
|
||||
// installer exactly then.
|
||||
...(opts.zlib && opts.dynamic ? [rt(join(rtDir, "scr_zlib_island.c"))] : []),
|
||||
...(opts.events ? [rt(join(rtDir, "scr_events.c")), rt(join(rtDir, "scr_readline.c"))] : []),
|
||||
...(opts.emitter || opts.workers ? [rt(join(rtDir, "scr_events_emitter.c"))] : []),
|
||||
// The checked-dynamic HANDLE support unit (listener gate + runtime
|
||||
@@ -673,57 +559,8 @@ export async function compileCInternal(
|
||||
// This is independent of the mbedTLS archive: getCACertificates-only
|
||||
// programs need crypt32 too, while TLS programs imply the CA unit.
|
||||
...(tlsCa && targetPlatform(driver) === "win32" ? ["-lcrypt32"] : []),
|
||||
// Static fetch is the engine-free half of scr_fetch.c. Dynamic builds
|
||||
// compile the same source beside scr_island.c below, where its
|
||||
// SCR_DYNAMIC half installs the full web surface.
|
||||
...(nativeFetch && !dynamic ? [rt(join(rtDir, "scr_fetch.c"))] : []),
|
||||
...(engineArchive
|
||||
? [
|
||||
"-DSCR_DYNAMIC",
|
||||
"-I",
|
||||
vendorEngineDir(),
|
||||
rt(join(rtDir, "scr_island.c")),
|
||||
rt(join(rtDir, "scr_web.c")),
|
||||
// The one unit referencing BOTH the island and the inspect
|
||||
// engine (insp.jsval): linked exactly when both halves are.
|
||||
...(opts.inspect ? [rt(join(rtDir, "scr_inspect_island.c"))] : []),
|
||||
// fetch: the NATIVE bridge by default (its socket/tls/zlib
|
||||
// dependencies joined the link above); the curl REFERENCE
|
||||
// (SCRIPTC_FETCH_CURL=1) keeps the historical system -lcurl /
|
||||
// linux soname-stub arms for one release.
|
||||
...(nativeFetch ? [rt(join(rtDir, "scr_fetch.c"))] : []),
|
||||
// The island's node:http/https CLIENT bridge (scr_net_island.c):
|
||||
// the one TU referencing both the socket units and the engine —
|
||||
// compiled beside the native fetch (scr_fetch_install registers
|
||||
// it) and whenever the embedded graph imports node:http/https
|
||||
// (the emitted main calls scr_net_island_install exactly then).
|
||||
...(netIsland ? [rt(join(rtDir, "scr_net_island.c"))] : []),
|
||||
...(curlFetch
|
||||
? curlStubDir !== null
|
||||
? [
|
||||
"-I",
|
||||
join(vendorCurlDir(), "include"),
|
||||
rt(join(rtDir, "scr_fetch_curl.c")),
|
||||
`-L${curlStubDir}`,
|
||||
"-lcurl",
|
||||
]
|
||||
: [rt(join(rtDir, "scr_fetch_curl.c")), "-lcurl"]
|
||||
: []),
|
||||
engineArchive,
|
||||
// Linux's libm is appended after every input below for GNU ld's
|
||||
// left-to-right archive resolution. Other dynamic targets keep
|
||||
// the historical engine-adjacent spelling.
|
||||
...(driver.linkArgs.includes("-lm") ? [] : ["-lm"]),
|
||||
// The PE stack reserve, pinned to the 8MB POSIX main-stack
|
||||
// geometry ISL_MAIN_STACK_BUDGET is sized against (4MB engine
|
||||
// budget + 4MB excursion margin) — quickjs-ng's own CMake makes
|
||||
// the same 8MB choice on Windows. Not left to the driver:
|
||||
// classic mingw ld defaults to 2MB (which the budget would blow
|
||||
// straight past); zig's lld happens to default to 16MB today,
|
||||
// but that is nobody's contract.
|
||||
...(targetPlatform(driver) === "win32" ? ["-Wl,--stack,8388608"] : []),
|
||||
]
|
||||
: []),
|
||||
// Fetch uses the native network, TLS, and HTTP runtime.
|
||||
...(nativeFetch ? [rt(join(rtDir, "scr_fetch.c"))] : []),
|
||||
// A .ll program TU (the LLVM backend) deliberately carries no target
|
||||
// triple (byte-stable output; clang supplies the host/SCRIPTC_TARGET
|
||||
// triple exactly as it does for .c) — silence the -Woverride-module
|
||||
@@ -772,11 +609,9 @@ export async function compileCInternal(
|
||||
"-Wno-deprecated-declarations",
|
||||
"-I",
|
||||
rtDir,
|
||||
...(regex || dynamic ? ["-I", vendorEngineDir()] : []),
|
||||
...(regex ? ["-I", vendorEngineDir()] : []),
|
||||
...(zlibObjects.length > 0 ? ["-I", vendorZlibDir()] : []),
|
||||
...(curlStubDir !== null ? ["-I", join(vendorCurlDir(), "include")] : []),
|
||||
...(tlsArchive !== null ? ["-I", join(vendorTlsDir(), "include")] : []),
|
||||
...(dynamic ? ["-DSCR_DYNAMIC"] : []),
|
||||
];
|
||||
// Keep sanitizer runtime objects optimized in both postures. Large dev
|
||||
// programs still compile at O0, avoiding an expensive LLVM optimization
|
||||
@@ -987,24 +822,18 @@ export async function compileCInternal(
|
||||
...(targetPlatform(driver) === "win32" ? ["-ladvapi32", "-liphlpapi", "-lws2_32"] : []),
|
||||
...(tls && targetPlatform(driver) === "win32" ? ["-lbcrypt"] : []),
|
||||
...(tlsCa && targetPlatform(driver) === "win32" ? ["-lcrypt32"] : []),
|
||||
...(curlFetch && driver.target === null ? ["-lcurl"] : []),
|
||||
...(dynamic && !driver.linkArgs.includes("-lm") ? ["-lm"] : []),
|
||||
...(((opts.zlib ?? false) || nativeFetch) && !isZigDriver(driver) ? ["-lz"] : []),
|
||||
...driver.linkArgs,
|
||||
...executableLinkFlags,
|
||||
];
|
||||
// Both the wrapper dry run and dependency trace need the real build's
|
||||
// compile/link flag shape: wrappers commonly inject flags or native inputs
|
||||
// conditionally on optimization, sanitizer, dynamic, or platform switches.
|
||||
// conditionally on optimization, sanitizer, or platform switches.
|
||||
const effectiveLinkInvocationArgs = [
|
||||
...(separateSanitizedProgram ? cflags : programCompilerArgs),
|
||||
...(targetPlatform(driver) === "win32" ? ["-ladvapi32", "-liphlpapi", "-lws2_32"] : []),
|
||||
...(tls && targetPlatform(driver) === "win32" ? ["-lbcrypt"] : []),
|
||||
...(tlsCa && targetPlatform(driver) === "win32" ? ["-lcrypt32"] : []),
|
||||
...(curlStubDir !== null ? [`-L${curlStubDir}`] : []),
|
||||
...(curlFetch && driver.target === null ? ["-lcurl"] : []),
|
||||
...(dynamic && !driver.linkArgs.includes("-lm") ? ["-lm"] : []),
|
||||
...(dynamic && targetPlatform(driver) === "win32" ? ["-Wl,--stack,8388608"] : []),
|
||||
...(((opts.zlib ?? false) || nativeFetch) && !isZigDriver(driver) ? ["-lz"] : []),
|
||||
...driver.linkArgs,
|
||||
...executableLinkFlags,
|
||||
@@ -1012,10 +841,7 @@ export async function compileCInternal(
|
||||
// A complete hit is checked before cross-target curl's generated import stub
|
||||
// is materialized. Its -L spelling still joins the dry-run identity, while
|
||||
// the real trace omits only that not-yet-existing scriptc-owned directory.
|
||||
const linkTraceInvocationArgs =
|
||||
curlStubDir === null
|
||||
? effectiveLinkInvocationArgs
|
||||
: effectiveLinkInvocationArgs.filter((arg) => arg !== `-L${curlStubDir}`);
|
||||
const linkTraceInvocationArgs = effectiveLinkInvocationArgs;
|
||||
let implicitLinker: string | null = null;
|
||||
let preBuildDependencies: NativeArtifactDependency[] | null = null;
|
||||
let localArtifactDependencyPaths: string[] | null = null;
|
||||
|
||||
@@ -37,7 +37,6 @@ export async function executableNativeEnvironmentFingerprint(
|
||||
.update("\0");
|
||||
for (const name of [
|
||||
"PATH",
|
||||
"SCRIPTC_FETCH_CURL",
|
||||
"SCRIPTC_TEST_RUNTIME_SRC_DIR",
|
||||
"SCRIPTC_TEST_VENDOR_CACHE_DIR",
|
||||
"SCRIPTC_TEST_TRUST_COMPILER_WRAPPER",
|
||||
|
||||
@@ -6,24 +6,18 @@ import { resolvedToolIdentity } from "./tool-identity.js";
|
||||
export const {
|
||||
vendorEngineDir,
|
||||
vendorTlsDir,
|
||||
vendorCurlDir,
|
||||
vendorBuildCacheRoot,
|
||||
vendorCacheTargetFlavor,
|
||||
vendorCacheBuildIdentity,
|
||||
currentVendorCacheBuildIdentity,
|
||||
engineArchivePath,
|
||||
stageVendorInputs,
|
||||
ensureEngineArchive,
|
||||
lreObjectPaths,
|
||||
ensureLreObjects,
|
||||
vendorZlibDir,
|
||||
zlibObjectPaths,
|
||||
ensureZlibObjects,
|
||||
curlStubDirPath,
|
||||
ensureCurlStub,
|
||||
tlsArchivePath,
|
||||
ensureTlsArchive,
|
||||
QJS_ENGINE_SOURCES,
|
||||
LRE_SOURCES,
|
||||
ZLIB_SOURCES,
|
||||
} = createVendorArchives({
|
||||
|
||||
@@ -54,7 +54,7 @@ test("published types close over fields, containers and whole hierarchies", () =
|
||||
cls("Other", [["n", F64]]),
|
||||
];
|
||||
const mod: IrModule = {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
classes,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* of the link contract: a program object keeps this symbol undefined and the
|
||||
* matching runtime defines it, so incompatible manual links fail before the
|
||||
* program can start. */
|
||||
export const RUNTIME_ABI_VERSION = 13 as const;
|
||||
export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v13" as const;
|
||||
export const RUNTIME_ABI_VERSION = 14 as const;
|
||||
export const RUNTIME_ABI_MARKER = "scr_runtime_abi_v14" as const;
|
||||
|
||||
/** Object consumption is useful today, but the complete scr_* surface may
|
||||
* still change before 1.0. The versioned marker prevents accidental mixing;
|
||||
|
||||
@@ -81,7 +81,6 @@ export interface RuntimePackManifest {
|
||||
export interface RuntimeFeatureSet extends NativeLinkFeatures {
|
||||
workers: boolean;
|
||||
nativeFetch: boolean;
|
||||
netIslandEffective: boolean;
|
||||
netEffective: boolean;
|
||||
httpEffective: boolean;
|
||||
tlsEffective: boolean;
|
||||
@@ -255,26 +254,17 @@ function isRuntimePackTarget(target: Record<string, unknown> | null): boolean {
|
||||
|
||||
export function effectiveRuntimeFeatures(
|
||||
features: NativeLinkFeatures,
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
_env: NodeJS.ProcessEnv = process.env,
|
||||
): RuntimeFeatureSet {
|
||||
const curlFetch = features.dynamic && features.fetch && env["SCRIPTC_FETCH_CURL"] === "1";
|
||||
if (curlFetch) {
|
||||
throw new RuntimePackError(
|
||||
"SCRIPTC_FETCH_CURL=1 is an external developer-toolchain comparison mode and is not available with precompiled runtime packs",
|
||||
"unsupported",
|
||||
);
|
||||
}
|
||||
const nativeFetch = features.fetch;
|
||||
const netIslandEffective = features.dynamic && (features.netIsland || nativeFetch);
|
||||
const netEffective = features.net || nativeFetch || netIslandEffective;
|
||||
const httpEffective = features.http || nativeFetch || netIslandEffective;
|
||||
const tlsEffective = features.tls || nativeFetch || netIslandEffective;
|
||||
const netEffective = features.net || nativeFetch;
|
||||
const httpEffective = features.http || nativeFetch;
|
||||
const tlsEffective = features.tls || nativeFetch;
|
||||
const tlsCaEffective = features.tlsCa || tlsEffective;
|
||||
return {
|
||||
...features,
|
||||
workers: features.workers === true,
|
||||
nativeFetch,
|
||||
netIslandEffective,
|
||||
netEffective,
|
||||
httpEffective,
|
||||
tlsEffective,
|
||||
@@ -380,10 +370,7 @@ export function selectRuntimePackArtifacts(
|
||||
mode: RuntimePackMode = "executable",
|
||||
): RuntimePackArtifacts {
|
||||
const features = effectiveRuntimeFeatures(requested, env);
|
||||
if (
|
||||
features.workers &&
|
||||
(mode !== "executable" || features.dynamic || manifest.target.object_format === "wasm")
|
||||
)
|
||||
if (features.workers && (mode !== "executable" || manifest.target.object_format === "wasm"))
|
||||
throw new RuntimePackError("worker threads require a native static executable", "unsupported");
|
||||
const key = runtimePackFlavorKey(manifest, flavor, mode);
|
||||
const selectedFlavor = manifest.flavors[key];
|
||||
@@ -392,11 +379,6 @@ export function selectRuntimePackArtifacts(
|
||||
`runtime pack has no ${key} flavor; reinstall the matching runtime package`,
|
||||
"invalid",
|
||||
);
|
||||
if (mode !== "executable" && requested.dynamic)
|
||||
throw new RuntimePackError(
|
||||
"library runtime packs do not support dynamic execution",
|
||||
"unsupported",
|
||||
);
|
||||
return {
|
||||
features,
|
||||
runtime: selectedFlavor.runtime_units
|
||||
|
||||
@@ -52,7 +52,7 @@ async function fixture(target: NativeTargetSpec, workers = false) {
|
||||
object_format: target.objectFormat,
|
||||
minimum_os: target.minimumOs,
|
||||
},
|
||||
runtime_abi: { version: 13, marker: "scr_runtime_abi_v13" },
|
||||
runtime_abi: { version: 14, marker: "scr_runtime_abi_v14" },
|
||||
compiler: { command: "fixture", identity: "fixture", target: target.llvmTriple },
|
||||
macros: { executable: [], excluded: ["SCR_LIB"], sanitizer: "external-toolchain-required" },
|
||||
flavors: {
|
||||
@@ -90,13 +90,11 @@ async function fixture(target: NativeTargetSpec, workers = false) {
|
||||
}
|
||||
|
||||
const features = {
|
||||
dynamic: false,
|
||||
regex: false,
|
||||
copying: false,
|
||||
textDecoderLegacy: false,
|
||||
fileHandle: false,
|
||||
fetch: false,
|
||||
netIsland: false,
|
||||
zlib: false,
|
||||
assert: false,
|
||||
inspect: false,
|
||||
@@ -173,15 +171,12 @@ test.each([
|
||||
});
|
||||
|
||||
test("worker runtime packs refuse dynamic and WASI execution", async () => {
|
||||
for (const [target, dynamic] of [
|
||||
[LINUX_X64_GNU_TARGET, true],
|
||||
[WASM32_WASI_TARGET, false],
|
||||
] as const) {
|
||||
for (const target of [WASM32_WASI_TARGET]) {
|
||||
const pack = await fixture(target, true);
|
||||
await expect(
|
||||
loadRuntimePack({
|
||||
target,
|
||||
features: { ...features, workers: true, dynamic },
|
||||
features: { ...features, workers: true },
|
||||
optimization: "release",
|
||||
resolver: () => pack.packagePath,
|
||||
}),
|
||||
|
||||
@@ -27,13 +27,11 @@ import { MACOS_ARM64_TARGET, WINDOWS_X64_MSVC_TARGET, type NativeTargetSpec } fr
|
||||
const VERSION = compilerReleaseVersion();
|
||||
|
||||
const BASE: NativeLinkFeatures = {
|
||||
dynamic: false,
|
||||
regex: false,
|
||||
copying: false,
|
||||
textDecoderLegacy: false,
|
||||
fileHandle: false,
|
||||
fetch: false,
|
||||
netIsland: false,
|
||||
zlib: false,
|
||||
assert: false,
|
||||
inspect: false,
|
||||
@@ -81,9 +79,9 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) {
|
||||
};
|
||||
const base = await artifact("artifacts/base.o", "base");
|
||||
const legacy = await artifact("artifacts/legacy.o", "legacy");
|
||||
const dynamic = await artifact("artifacts/dynamic.o", "dynamic");
|
||||
const fileHandle = await artifact("artifacts/fileHandle.o", "fileHandle");
|
||||
const regex = await artifact("artifacts/regex.a", "regex");
|
||||
const quickjs = await artifact("artifacts/qjs.a", "qjs");
|
||||
const zlib = await artifact("artifacts/zlib.a", "zlib");
|
||||
await writeFile(join(root, "license.txt"), "license");
|
||||
const units = [
|
||||
{
|
||||
@@ -97,7 +95,12 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) {
|
||||
defines: ["SCR_TEXT_DECODER_LEGACY"],
|
||||
...legacy,
|
||||
},
|
||||
{ id: "dynamic", when: { dynamic: true }, defines: ["SCR_DYNAMIC"], ...dynamic },
|
||||
{
|
||||
id: "fileHandle",
|
||||
when: { fileHandle: true },
|
||||
defines: ["SCR_TEST_FILE_HANDLE"],
|
||||
...fileHandle,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -113,14 +116,14 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) {
|
||||
object_format: target.objectFormat,
|
||||
minimum_os: target.minimumOs,
|
||||
},
|
||||
runtime_abi: { version: 13, marker: "scr_runtime_abi_v13" },
|
||||
runtime_abi: { version: 14, marker: "scr_runtime_abi_v14" },
|
||||
compiler: {
|
||||
command: "clang",
|
||||
identity: "fixture clang",
|
||||
target: target.llvmTriple,
|
||||
},
|
||||
macros: {
|
||||
executable: ["SCR_DYNAMIC", "SCR_TEXT_DECODER_LEGACY"],
|
||||
executable: ["SCR_TEST_FILE_HANDLE", "SCR_TEXT_DECODER_LEGACY"],
|
||||
excluded: ["SCR_LIB", "SCR_THREAD_INSTANCES", "SCR_RC_AUDIT"],
|
||||
sanitizer: "external-toolchain-required",
|
||||
},
|
||||
@@ -129,8 +132,8 @@ async function fixture(target: NativeTargetSpec = MACOS_ARM64_TARGET) {
|
||||
dev: { optimization: "-O0", runtime_units: units },
|
||||
},
|
||||
archives: [
|
||||
{ id: "libregexp", predicate: { all: ["regex"], not: ["dynamic"] }, ...regex },
|
||||
{ id: "quickjs", predicate: "dynamic", ...quickjs },
|
||||
{ id: "libregexp", predicate: { all: ["regex"], not: ["fileHandle"] }, ...regex },
|
||||
{ id: "zlib", predicate: "fileHandle", ...zlib },
|
||||
],
|
||||
system_libraries: [{ name: "System", predicate: true }],
|
||||
licenses: [{ path: "license.txt", license: "fixture" }],
|
||||
@@ -180,10 +183,9 @@ describe("runtime pack manifests", () => {
|
||||
});
|
||||
|
||||
test("feature implications and predicates are deterministic", () => {
|
||||
const features = effectiveRuntimeFeatures({ ...BASE, dynamic: true, fetch: true });
|
||||
const features = effectiveRuntimeFeatures({ ...BASE, fileHandle: true, fetch: true });
|
||||
expect(features).toMatchObject({
|
||||
nativeFetch: true,
|
||||
netIslandEffective: true,
|
||||
netEffective: true,
|
||||
httpEffective: true,
|
||||
tlsEffective: true,
|
||||
@@ -191,7 +193,7 @@ describe("runtime pack manifests", () => {
|
||||
zlibEffective: true,
|
||||
});
|
||||
expect(evaluateRuntimePredicate({ all: ["fetch"], not: ["regex"] }, features)).toBe(true);
|
||||
expect(evaluateRuntimePredicate({ any: ["regex", "dynamic"] }, features)).toBe(true);
|
||||
expect(evaluateRuntimePredicate({ any: ["regex", "fileHandle"] }, features)).toBe(true);
|
||||
});
|
||||
|
||||
test("static runtime-pack executable links dead-strip too", async () => {
|
||||
@@ -238,15 +240,17 @@ describe("runtime pack manifests", () => {
|
||||
});
|
||||
expect(legacy.runtimeObjects.map((path) => path.split("/").at(-1))).toEqual(["legacy.o"]);
|
||||
expect(legacy.archives.map((path) => path.split("/").at(-1))).toEqual(["regex.a"]);
|
||||
const dynamic = await loadRuntimePack({
|
||||
const fileHandle = await loadRuntimePack({
|
||||
target: MACOS_ARM64_TARGET,
|
||||
features: { ...BASE, dynamic: true, regex: true },
|
||||
features: { ...BASE, fileHandle: true, regex: true },
|
||||
optimization: "dev",
|
||||
resolver,
|
||||
});
|
||||
expect(dynamic.flavor).toBe("dev");
|
||||
expect(dynamic.runtimeObjects.map((path) => path.split("/").at(-1))).toEqual(["dynamic.o"]);
|
||||
expect(dynamic.archives.map((path) => path.split("/").at(-1))).toEqual(["qjs.a"]);
|
||||
expect(fileHandle.flavor).toBe("dev");
|
||||
expect(fileHandle.runtimeObjects.map((path) => path.split("/").at(-1))).toEqual([
|
||||
"fileHandle.o",
|
||||
]);
|
||||
expect(fileHandle.archives.map((path) => path.split("/").at(-1))).toEqual(["zlib.a"]);
|
||||
});
|
||||
|
||||
test("runtime bitcode follows the selected speed variant and is verified", async () => {
|
||||
@@ -306,7 +310,7 @@ describe("runtime pack manifests", () => {
|
||||
expect(
|
||||
await loadRuntimeBitcode({
|
||||
target: MACOS_ARM64_TARGET,
|
||||
features: { ...BASE, dynamic: true },
|
||||
features: { ...BASE, fileHandle: true },
|
||||
resolver,
|
||||
}),
|
||||
).toBeNull();
|
||||
@@ -691,9 +695,7 @@ test("library runtime selection requires dedicated packs and never substitutes e
|
||||
await writeFile(join(f.root, "runtime-pack.json"), JSON.stringify(f.manifest));
|
||||
const threaded = await loadRuntimePack({ ...options, mode: "library-thread" });
|
||||
expect(threaded.runtimeObjects.map((path) => basename(path))).toEqual(["base.o"]);
|
||||
await expect(
|
||||
loadRuntimePack({ ...options, features: { ...BASE, dynamic: true }, mode: "library" }),
|
||||
).rejects.toThrow("do not support dynamic");
|
||||
|
||||
delete f.manifest.flavors.release;
|
||||
delete f.manifest.flavors.dev;
|
||||
await writeFile(join(f.root, "runtime-pack.json"), JSON.stringify(f.manifest));
|
||||
|
||||
@@ -35,7 +35,7 @@ const fetch: IrExpr = {
|
||||
};
|
||||
function module(direct: IrExpr[] = []): IrModule {
|
||||
return {
|
||||
irVersion: 15,
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "%main",
|
||||
records: [],
|
||||
|
||||
@@ -2,7 +2,7 @@ import { everyModuleNode, everyStmtList, transformStmtList } from "../ir/travers
|
||||
import type { LlvmUnsupportedError } from "./llvm/emitter.js";
|
||||
import type { ScrDiagnostic } from "../diagnostics/diagnostic.js";
|
||||
import { type IrModule, type SrcLoc } from "../ir/ir.js";
|
||||
import { moduleUsesFetch, moduleEmbedsBuiltin } from "../ir/runtime-features.js";
|
||||
import { moduleUsesFetch } from "../ir/runtime-features.js";
|
||||
|
||||
/** Reflection tables expose methods that the program never calls. On WASI,
|
||||
* keep their host-only implementations as explicit runtime refusals. Typed
|
||||
@@ -121,11 +121,6 @@ export function targetRefusalDiag(target: string, surface: string, loc: SrcLoc):
|
||||
};
|
||||
}
|
||||
|
||||
/** APIs that require host capabilities absent from portable WASI Preview 1.
|
||||
* These are target diagnostics, not backend-tier gaps: the same language IR
|
||||
* (including async, generators, and the dynamic island) is otherwise valid.
|
||||
* Keep the fine-grained walk first so diagnostics point at the API use; the
|
||||
* embedded-module checks are the entry-anchored safety net for island code. */
|
||||
export function moduleWasiUnavailableSurface(
|
||||
mod: IrModule,
|
||||
): { surface: string; loc: SrcLoc } | null {
|
||||
@@ -204,10 +199,5 @@ export function moduleWasiUnavailableSurface(
|
||||
if (moduleUsesFetch(mod)) {
|
||||
return { surface: "network-backed fetch (WASI Preview 1 has no socket API)", loc: entryLoc };
|
||||
}
|
||||
for (const builtin of ["node:http", "node:https", "node:net", "node:tls"]) {
|
||||
if (moduleEmbedsBuiltin(mod, builtin)) {
|
||||
return { surface: `${builtin} networking (WASI Preview 1 has no socket API)`, loc: entryLoc };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
|
||||
import { mkdir, mkdtemp, readdir, rm } from "node:fs/promises";
|
||||
import { availableParallelism, tmpdir } from "node:os";
|
||||
import { basename, dirname, join, resolve } from "node:path";
|
||||
import { basename, join, resolve } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import {
|
||||
stageCachedFile,
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
QJS_COMMIT,
|
||||
MBEDTLS_VERSION,
|
||||
ZLIB_VERSION,
|
||||
QJS_ENGINE_SOURCES,
|
||||
LRE_SOURCES,
|
||||
ZLIB_SOURCES,
|
||||
} from "./vendor-inputs.js";
|
||||
@@ -26,7 +25,6 @@ export {
|
||||
QJS_COMMIT,
|
||||
MBEDTLS_VERSION,
|
||||
ZLIB_VERSION,
|
||||
QJS_ENGINE_SOURCES,
|
||||
LRE_SOURCES,
|
||||
ZLIB_SOURCES,
|
||||
} from "./vendor-inputs.js";
|
||||
@@ -40,8 +38,7 @@ export interface VendorArchiveContext {
|
||||
}
|
||||
|
||||
export function createVendorArchives(context: VendorArchiveContext) {
|
||||
const { runtimeSrcDir, targetPlatform, isZigDriver, resolvedToolIdentity, runtimeFingerprint } =
|
||||
context;
|
||||
const { runtimeSrcDir, isZigDriver, resolvedToolIdentity, runtimeFingerprint } = context;
|
||||
const validVendorArtifact = validCachedFile;
|
||||
|
||||
function vendorEngineDir(): string {
|
||||
@@ -124,16 +121,6 @@ export function createVendorArchives(context: VendorArchiveContext) {
|
||||
);
|
||||
}
|
||||
|
||||
function engineArchivePath(
|
||||
sanitize: boolean,
|
||||
driver: CcDriver,
|
||||
buildIdentity: string,
|
||||
cacheRoot: string = vendorBuildCacheRoot(),
|
||||
): string {
|
||||
const flavor = `${sanitize ? "asan" : "plain"}-${vendorCacheTargetFlavor(driver)}-${buildIdentity}`;
|
||||
return join(cacheRoot, `${QJS_COMMIT.slice(0, 12)}-${flavor}`, "libqjs.a");
|
||||
}
|
||||
|
||||
/** Publish one finished vendor prerequisite from disposable build scratch.
|
||||
* The shared data and digest use the ordinary atomic cache publisher.
|
||||
* Concurrent builders produce equivalent bytes; on platforms where rename
|
||||
@@ -192,126 +179,6 @@ export function createVendorArchives(context: VendorArchiveContext) {
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
/** The engine archive for one flavor, built lazily on the first --dynamic
|
||||
* compile and cached under <build-cache>/vendor/<commit>-<flavor>-<target>-<toolchain>/ — unlike
|
||||
* the runtime's own sources (recompiled every build, ~100ms), the engine is
|
||||
* far too big to rebuild per compile. The plain flavor is ALWAYS MinSizeRel
|
||||
* regardless of the program's optimization level: measured binary delta is
|
||||
* ~620KB vs ~900KB at -O2, and eval-in-an-island workloads don't earn the
|
||||
* difference. The asan flavor (Debug + QJS_ENABLE_ASAN) exists so the
|
||||
* sanitized test lane checks engine interop under instrumentation.
|
||||
*
|
||||
* Concurrency: parallel first builds (e.g. test workers) each build in a
|
||||
* private temp dir and publish with an atomic rename — first one wins,
|
||||
* losers discard their work and use the winner's archive.
|
||||
*
|
||||
* The qjs library target is just four TUs (QJS_ENGINE_SOURCES), so host-clang,
|
||||
* targetless Zig, and cross builds use the same direct per-TU recipe. This removes CMake from
|
||||
* the runtime dependency set and lets cache warming compile exactly the
|
||||
* archive a later program consumes. The default host-clang driver uses bare
|
||||
* clang + ar; every Zig driver uses the selected zig cc + zig ar. */
|
||||
async function ensureEngineArchive(
|
||||
sanitize: boolean,
|
||||
driver: CcDriver,
|
||||
buildIdentity: string,
|
||||
cacheRoot: string = vendorBuildCacheRoot(),
|
||||
): Promise<string> {
|
||||
const archive = engineArchivePath(sanitize, driver, buildIdentity, cacheRoot);
|
||||
const cacheDir = dirname(archive);
|
||||
if (await validVendorArtifact(archive)) return archive;
|
||||
return buildEngineArchiveDirect(sanitize, driver, cacheRoot, cacheDir);
|
||||
}
|
||||
|
||||
/** The qjs library target's source list (CMakeLists.txt `qjs_sources` with
|
||||
* QJS_BUILD_LIBC off — cutils is header-only): the cross-build recipe
|
||||
* compiles exactly these. */
|
||||
|
||||
/** The engine archive for one target, per-TU compiler invocations plus ar.
|
||||
* The flags mirror what the former CMake
|
||||
* configurations apply to the qjs library target: gnu11 (CMAKE_C_EXTENSIONS
|
||||
* ON), hidden visibility, -funsigned-char, -DQUICKJS_NG_BUILD and
|
||||
* -D_GNU_SOURCE (qjs_defines; linux targetArgs already carry the latter),
|
||||
* then MinSizeRel (-Os -DNDEBUG) for plain or Debug+QJS_ENABLE_ASAN
|
||||
* (-O0 -ggdb -fno-omit-frame-pointer -fsanitize=address
|
||||
* -fno-sanitize-recover=all) for asan. Same atomic-rename publish as every
|
||||
* vendor cache. */
|
||||
async function buildEngineArchiveDirect(
|
||||
sanitize: boolean,
|
||||
driver: CcDriver,
|
||||
cacheRoot: string,
|
||||
cacheDir: string,
|
||||
): Promise<string> {
|
||||
const vendor = vendorEngineDir();
|
||||
const archive = join(cacheDir, "libqjs.a");
|
||||
const compileArgv = isZigDriver(driver) ? driver.argv : ["clang"];
|
||||
const arArgv = isZigDriver(driver) ? [...driver.argv.slice(0, 1), "ar"] : ["ar"];
|
||||
const cflags = [
|
||||
"-std=gnu11",
|
||||
...driver.targetArgs,
|
||||
"-fvisibility=hidden",
|
||||
"-funsigned-char",
|
||||
"-DQUICKJS_NG_BUILD",
|
||||
"-D_GNU_SOURCE",
|
||||
// CMake's qjs_defines on WIN32 (quickjs.c's own _WIN32 arms cover the
|
||||
// rest — timezoneapi, intrin, cutils.h's _msize usable-size probe).
|
||||
...(targetPlatform(driver) === "win32"
|
||||
? ["-DWIN32_LEAN_AND_MEAN", "-D_WIN32_WINNT=0x0601"]
|
||||
: []),
|
||||
...(sanitize
|
||||
? [
|
||||
"-O0",
|
||||
"-ggdb",
|
||||
"-fno-omit-frame-pointer",
|
||||
"-fsanitize=address",
|
||||
"-fno-sanitize-recover=all",
|
||||
"-DQJS_ENABLE_ASAN",
|
||||
]
|
||||
: ["-Os", "-DNDEBUG"]),
|
||||
"-I",
|
||||
vendor,
|
||||
];
|
||||
await mkdir(cacheRoot, { recursive: true });
|
||||
const buildDir = await mkdtemp(
|
||||
join(tmpdir(), `scriptc-vendor-qjs-${driver.target ?? "host"}-`),
|
||||
);
|
||||
try {
|
||||
const width = Math.min(QJS_ENGINE_SOURCES.length, availableParallelism());
|
||||
for (let i = 0; i < QJS_ENGINE_SOURCES.length; i += width) {
|
||||
await Promise.all(
|
||||
QJS_ENGINE_SOURCES.slice(i, i + width).map((src) =>
|
||||
execFileAsync(
|
||||
compileArgv[0] ?? "clang",
|
||||
[
|
||||
...compileArgv.slice(1),
|
||||
...cflags,
|
||||
"-c",
|
||||
join(vendor, src),
|
||||
"-o",
|
||||
join(buildDir, `${basename(src, ".c")}.o`),
|
||||
],
|
||||
{ cwd: buildDir },
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
await execFileAsync(arArgv[0] ?? "ar", [
|
||||
...arArgv.slice(1),
|
||||
"rcs",
|
||||
join(buildDir, "libqjs.a"),
|
||||
...QJS_ENGINE_SOURCES.map((s) => join(buildDir, `${basename(s, ".c")}.o`)),
|
||||
]);
|
||||
await publishVendorArtifact(join(buildDir, "libqjs.a"), archive);
|
||||
} finally {
|
||||
await rm(buildDir, { recursive: true, force: true });
|
||||
}
|
||||
return archive;
|
||||
}
|
||||
|
||||
/** The vendor sources behind static-build regex support: quickjs-ng's
|
||||
* libregexp and its unicode tables (cutils is header-only). Deliberately
|
||||
* NOT the engine — a regex-using static binary links ~110KB of matcher,
|
||||
* never the ~620KB island. */
|
||||
|
||||
function lreObjectPaths(
|
||||
sanitize: boolean,
|
||||
driver: CcDriver,
|
||||
@@ -327,11 +194,10 @@ export function createVendorArchives(context: VendorArchiveContext) {
|
||||
}
|
||||
|
||||
/** The libregexp objects for one flavor, compiled lazily on the first
|
||||
* regex-using static build (~1s) and cached like the engine archive —
|
||||
* regex-using build (~1s) and cached under
|
||||
* <build-cache>/vendor/<commit>-lre-<flavor>-<target>-<toolchain>/*.o — with the same atomic-rename
|
||||
* publish (parallel first builds race safely; losers discard their work).
|
||||
* Plain is -Os (size class matters: the regex fence pins regex-using
|
||||
* binaries well under the engine class); asan matches the final link so
|
||||
* Plain is -Os to keep regex-using binaries small; asan matches the final link so
|
||||
* the sanitized lane instruments the matcher too. Cross targets get their
|
||||
* own flavor directory, compiled with the SCRIPTC_CC driver and its target
|
||||
* args — libregexp/libunicode are plain C11, so the cross story is exactly
|
||||
@@ -465,88 +331,6 @@ export function createVendorArchives(context: VendorArchiveContext) {
|
||||
return objects;
|
||||
}
|
||||
|
||||
function vendorCurlDir(): string {
|
||||
return join(runtimeSrcDir(), "..", "vendor", "curl");
|
||||
}
|
||||
|
||||
/** Every libcurl function scr_fetch_curl.c references — the generated import
|
||||
* stub defines exactly these. A new curl call in scr_fetch_curl.c that is
|
||||
* missing here fails the CROSS link immediately with the symbol's name
|
||||
* (host builds resolve it from the real system libcurl and never look). */
|
||||
const CURL_STUB_SYMBOLS = [
|
||||
"curl_easy_cleanup",
|
||||
"curl_easy_getinfo",
|
||||
"curl_easy_init",
|
||||
"curl_easy_setopt",
|
||||
"curl_easy_strerror",
|
||||
"curl_free",
|
||||
"curl_global_cleanup",
|
||||
"curl_global_init",
|
||||
"curl_multi_add_handle",
|
||||
"curl_multi_cleanup",
|
||||
"curl_multi_info_read",
|
||||
"curl_multi_init",
|
||||
"curl_multi_perform",
|
||||
"curl_multi_poll",
|
||||
"curl_multi_remove_handle",
|
||||
"curl_slist_append",
|
||||
"curl_slist_free_all",
|
||||
"curl_url",
|
||||
"curl_url_cleanup",
|
||||
"curl_url_get",
|
||||
"curl_url_set",
|
||||
];
|
||||
|
||||
function curlStubDirPath(
|
||||
driver: CcDriver,
|
||||
buildIdentity: string,
|
||||
cacheRoot: string = vendorBuildCacheRoot(),
|
||||
): string {
|
||||
return join(cacheRoot, `curl-stub-${driver.target}-${buildIdentity}`);
|
||||
}
|
||||
|
||||
/** The libcurl import stub for one CROSS target, generated lazily on the
|
||||
* first fetch-using cross build and cached like the zlib objects —
|
||||
* <build-cache>/vendor/curl-stub-<target>-<toolchain>/libcurl.so, atomic-rename publish. The
|
||||
* stub is empty definitions of CURL_STUB_SYMBOLS compiled `-shared` with
|
||||
* `-soname libcurl.so.4`: linking against it satisfies scr_fetch_curl.c's
|
||||
* references and records a plain `DT_NEEDED libcurl.so.4`, which the
|
||||
* TARGET system's real libcurl satisfies at load time (curl's unversioned
|
||||
* exports make the classic import-stub technique sound here — nothing
|
||||
* from the stub's bodies ever ships in the binary). One stub per target,
|
||||
* no sanitize flavor: the stub's code is never executed or instrumented,
|
||||
* and asan links against plain shared libraries freely. */
|
||||
async function ensureCurlStub(
|
||||
driver: CcDriver,
|
||||
buildIdentity: string,
|
||||
cacheRoot: string = vendorBuildCacheRoot(),
|
||||
): Promise<string> {
|
||||
const cacheDir = curlStubDirPath(driver, buildIdentity, cacheRoot);
|
||||
const lib = join(cacheDir, "libcurl.so");
|
||||
if (await validVendorArtifact(lib)) return cacheDir;
|
||||
|
||||
await mkdir(cacheRoot, { recursive: true });
|
||||
const buildDir = await mkdtemp(join(tmpdir(), "scriptc-vendor-curl-stub-"));
|
||||
try {
|
||||
const src = join(buildDir, "stub.c");
|
||||
await writeFile(src, CURL_STUB_SYMBOLS.map((s) => `void ${s}(void) {}\n`).join(""));
|
||||
await execFileAsync(driver.argv[0] ?? "clang", [
|
||||
...driver.argv.slice(1),
|
||||
...driver.targetArgs,
|
||||
"-shared",
|
||||
"-fPIC",
|
||||
"-Wl,-soname,libcurl.so.4",
|
||||
src,
|
||||
"-o",
|
||||
join(buildDir, "libcurl.so"),
|
||||
]);
|
||||
await publishVendorArtifact(join(buildDir, "libcurl.so"), lib);
|
||||
} finally {
|
||||
await rm(buildDir, { recursive: true, force: true });
|
||||
}
|
||||
return cacheDir;
|
||||
}
|
||||
|
||||
function vendorTlsDir(): string {
|
||||
return join(runtimeSrcDir(), "..", "vendor", "mbedtls");
|
||||
}
|
||||
@@ -562,8 +346,8 @@ export function createVendorArchives(context: VendorArchiveContext) {
|
||||
}
|
||||
|
||||
/** The mbedTLS archive for one flavor, compiled lazily on the first
|
||||
* TLS-using build (~15s over ~110 TUs, parallelized) and cached like the
|
||||
* engine archive — <build-cache>/vendor/mbedtls-<version>-<flavor>-<target>-<toolchain>/libmbedtls.a —
|
||||
* TLS-using build (~15s over ~110 TUs, parallelized) and cached under
|
||||
* <build-cache>/vendor/mbedtls-<version>-<flavor>-<target>-<toolchain>/libmbedtls.a
|
||||
* with the same atomic-rename publish (parallel first builds race safely;
|
||||
* losers discard their work). Plain is -Os (the TLS stack is link-gated
|
||||
* but still wants its size class small); asan matches the final link so
|
||||
@@ -637,24 +421,18 @@ export function createVendorArchives(context: VendorArchiveContext) {
|
||||
return {
|
||||
vendorEngineDir,
|
||||
vendorTlsDir,
|
||||
vendorCurlDir,
|
||||
vendorBuildCacheRoot,
|
||||
vendorCacheTargetFlavor,
|
||||
vendorCacheBuildIdentity,
|
||||
currentVendorCacheBuildIdentity,
|
||||
engineArchivePath,
|
||||
stageVendorInputs,
|
||||
ensureEngineArchive,
|
||||
lreObjectPaths,
|
||||
ensureLreObjects,
|
||||
vendorZlibDir,
|
||||
zlibObjectPaths,
|
||||
ensureZlibObjects,
|
||||
curlStubDirPath,
|
||||
ensureCurlStub,
|
||||
tlsArchivePath,
|
||||
ensureTlsArchive,
|
||||
QJS_ENGINE_SOURCES,
|
||||
LRE_SOURCES,
|
||||
ZLIB_SOURCES,
|
||||
};
|
||||
|
||||
@@ -4,7 +4,6 @@ export const MBEDTLS_VERSION = "3.6.7";
|
||||
export const ZLIB_VERSION = "1.3.1";
|
||||
/** Exact translation-unit membership shared by cache builds and external
|
||||
* source-pack recipes. */
|
||||
export const QJS_ENGINE_SOURCES = ["dtoa.c", "libregexp.c", "libunicode.c", "quickjs.c"] as const;
|
||||
export const LRE_SOURCES = ["libregexp.c", "libunicode.c"] as const;
|
||||
export const ZLIB_SOURCES = [
|
||||
"adler32.c",
|
||||
|
||||
@@ -12,11 +12,10 @@ import {
|
||||
export async function warmNativeCaches(
|
||||
options: WarmNativeCachesOptions = {},
|
||||
): Promise<WarmNativeCachesResult> {
|
||||
if (options.sanitize || process.env["SCRIPTC_FETCH_CURL"] === "1")
|
||||
return warmDevelopmentRuntime(options);
|
||||
if (options.sanitize) return warmDevelopmentRuntime(options);
|
||||
const cacheRoot = await prepareBuildCacheRoot(buildCacheRoot());
|
||||
if (cacheRoot === null) throw new Error("the native build cache is disabled or unavailable");
|
||||
const known = new Set<NativeCacheWarmProfile>(["runtime", "tls", "dynamic"]);
|
||||
const known = new Set<NativeCacheWarmProfile>(["runtime", "tls"]);
|
||||
for (const profile of options.profiles ?? [])
|
||||
if (!known.has(profile)) throw new Error(`unknown native cache warm profile '${profile}'`);
|
||||
const target = nativeCodegenTarget();
|
||||
@@ -26,7 +25,7 @@ export async function warmNativeCaches(
|
||||
throw new Error(
|
||||
`native cache warming requires an executable target; ${target.name} supports library archives`,
|
||||
);
|
||||
const profiles = [...new Set(options.profiles ?? (["runtime", "tls", "dynamic"] as const))];
|
||||
const profiles = [...new Set(options.profiles ?? (["runtime", "tls"] as const))];
|
||||
const results = [];
|
||||
for (const profile of profiles) {
|
||||
const started = performance.now();
|
||||
@@ -34,13 +33,11 @@ export async function warmNativeCaches(
|
||||
target,
|
||||
optimization: options.optimization ?? "release",
|
||||
features: {
|
||||
dynamic: profile === "dynamic",
|
||||
fetch: profile === "tls",
|
||||
regex: false,
|
||||
copying: false,
|
||||
textDecoderLegacy: false,
|
||||
fileHandle: false,
|
||||
netIsland: false,
|
||||
zlib: false,
|
||||
assert: false,
|
||||
inspect: false,
|
||||
|
||||
@@ -77,7 +77,6 @@ async function main(args: string[], host: CliHost): Promise<number> {
|
||||
fail(`unknown cache command "${inputArg ?? ""}" (supported: warm)\n\n${USAGE}`);
|
||||
if (
|
||||
values.lib ||
|
||||
values.dynamic ||
|
||||
values.backend !== undefined ||
|
||||
values.emit !== undefined ||
|
||||
values.print !== undefined ||
|
||||
@@ -103,10 +102,10 @@ async function main(args: string[], host: CliHost): Promise<number> {
|
||||
fail(`unknown optimization "${optimization}" (supported: release, dev, speed)\n\n${USAGE}`);
|
||||
}
|
||||
const profileArgs = positionals.slice(2);
|
||||
const knownProfiles = new Set<NativeCacheWarmProfile>(["runtime", "tls", "dynamic"]);
|
||||
const knownProfiles = new Set<NativeCacheWarmProfile>(["runtime", "tls"]);
|
||||
for (const profile of profileArgs) {
|
||||
if (!knownProfiles.has(profile as NativeCacheWarmProfile)) {
|
||||
fail(`unknown cache warm profile "${profile}" (supported: runtime, tls, dynamic)`);
|
||||
fail(`unknown cache warm profile "${profile}" (supported: runtime, tls)`);
|
||||
}
|
||||
}
|
||||
let result;
|
||||
@@ -143,7 +142,6 @@ async function main(args: string[], host: CliHost): Promise<number> {
|
||||
fail("scriptc build --lib takes no input positional: the profile names the entry module");
|
||||
}
|
||||
if (
|
||||
values.dynamic ||
|
||||
values.backend !== undefined ||
|
||||
values.emit !== undefined ||
|
||||
values.print !== undefined ||
|
||||
@@ -157,7 +155,7 @@ async function main(args: string[], host: CliHost): Promise<number> {
|
||||
externalTypeArgs.length > 0
|
||||
) {
|
||||
fail(
|
||||
"scriptc build --lib takes no --dynamic/--backend/--emit/--print/--optimization/--strip/--windows-subsystem/--detached-exit/--npm-static/--ffi/--external-types: the profile pins the emission and optimization, npm imports are judged automatically, outbound FFI belongs to executable builds, and external type mappings belong to coverage",
|
||||
"scriptc build --lib takes no --backend/--emit/--print/--optimization/--strip/--windows-subsystem/--detached-exit/--npm-static/--ffi/--external-types: the profile pins the emission and optimization, npm imports are judged automatically, outbound FFI belongs to executable builds, and external type mappings belong to coverage",
|
||||
);
|
||||
}
|
||||
const profilePath = resolve(profileArg);
|
||||
@@ -339,7 +337,6 @@ async function main(args: string[], host: CliHost): Promise<number> {
|
||||
|
||||
if (command === "coverage") {
|
||||
const { coverage, sourceTexts } = await host.analyze(input, {
|
||||
dynamic: values.dynamic,
|
||||
...(npmStatic !== undefined ? { npmStatic } : {}),
|
||||
...(ffiProfilePath !== undefined ? { ffiProfilePath } : {}),
|
||||
...(Object.keys(externalTypes).length > 0 ? { externalTypes } : {}),
|
||||
@@ -381,7 +378,6 @@ async function main(args: string[], host: CliHost): Promise<number> {
|
||||
outputKind: output.outputKind,
|
||||
emitIr: output.emitIr,
|
||||
sanitize: values.sanitize,
|
||||
dynamic: values.dynamic,
|
||||
...(backend !== undefined ? { backend } : {}),
|
||||
...(optimization !== undefined ? { optimization } : {}),
|
||||
...(values.strip ? { strip: true } : {}),
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
import type { NativeOptimization } from "../backend/optimization.js";
|
||||
import type { ProvenanceSources } from "../frontend/provenance-registry.js";
|
||||
|
||||
export type NativeCacheWarmProfile = "runtime" | "tls" | "dynamic";
|
||||
export type NativeCacheWarmProfile = "runtime" | "tls";
|
||||
|
||||
/** Only host operations vary between the seed and installed compiler. */
|
||||
export interface CliHost {
|
||||
|
||||
@@ -4,7 +4,6 @@ Usage:
|
||||
scriptc build <file.ts|.js> [options] compile to an executable or source artifact
|
||||
scriptc run <file.ts|.js> [options] compile and run
|
||||
scriptc coverage <file.ts|.js> how much compiles statically, and why not
|
||||
scriptc coverage <file.ts|.js> --dynamic what a --dynamic build compiles, and what still blocks it
|
||||
scriptc coverage <file.ts|.js> --external-types <specifier=file.d.ts>
|
||||
type-resolve an embedder-provided module for analysis
|
||||
scriptc build --lib --profile <p.json> library mode: compile the profile's entry
|
||||
@@ -14,7 +13,7 @@ Usage:
|
||||
exporting the profile symbols; a profile
|
||||
with a sidecar section also gets the
|
||||
contract sidecar JSON beside the archive
|
||||
scriptc cache warm [runtime|tls|dynamic…] prebuild expensive native cache families
|
||||
scriptc cache warm [runtime|tls…] verify and load precompiled runtime families
|
||||
for the current compiler/SDK/target
|
||||
|
||||
Options:
|
||||
@@ -52,22 +51,19 @@ Options:
|
||||
--emit-ir also write IR beside an executable or library archive;
|
||||
deprecated for executables: use --emit=ir for primary IR
|
||||
--sanitize build with ASan + runtime RC audit
|
||||
--dynamic embed the dynamic engine (adds ~620KB; static stays the default)
|
||||
--ffi <file> bind signature-only TypeScript declarations to native
|
||||
C symbols and link the manifest's archives/libraries
|
||||
--npm-static <pkg[,pkg…]|auto>
|
||||
compile the named npm packages' shipped JS statically as
|
||||
program modules (repeatable; "auto" opts in every eligible
|
||||
direct import: own .d.ts, unminified JS, no build-transform
|
||||
markers). A package preflight refuses falls back to the
|
||||
island (--dynamic) with a coverage-report note — opt-in,
|
||||
experimental
|
||||
markers). Packages that fail preflight are refused with a
|
||||
diagnostic. Eligible dependencies compile automatically.
|
||||
--provenance-sources
|
||||
EXPERIMENTAL: compile npm dependencies from their
|
||||
provenance-attested SOURCE (fetched at the attested
|
||||
commit) as static program modules; packages without a
|
||||
usable attestation keep the island path (a note, never
|
||||
a failure)
|
||||
usable attestation use their published source
|
||||
--external-types <specifier=file.d.ts>
|
||||
coverage only: map an exact bare module specifier to a
|
||||
local declaration file. The declaration supplies types
|
||||
@@ -90,7 +86,6 @@ export const CLI_OPTIONS = {
|
||||
"keep-llvm": { type: "boolean", default: true },
|
||||
"emit-ir": { type: "boolean", default: false },
|
||||
sanitize: { type: "boolean", default: false },
|
||||
dynamic: { type: "boolean", default: false },
|
||||
ffi: { type: "string" },
|
||||
"npm-static": { type: "string", multiple: true },
|
||||
"provenance-sources": { type: "boolean", default: false },
|
||||
|
||||
@@ -51,7 +51,7 @@ export interface FetchCompatOption {
|
||||
evidence: readonly FetchCompatEvidence[];
|
||||
}
|
||||
|
||||
export type FetchCompatInventoryStatus = "static" | "dynamic-only" | "unsupported" | "out-of-scope";
|
||||
export type FetchCompatInventoryStatus = "static" | "unsupported" | "out-of-scope";
|
||||
|
||||
export type FetchCompatInventoryPlacement =
|
||||
| "global"
|
||||
@@ -64,7 +64,7 @@ export type FetchCompatInventoryPlacement =
|
||||
|
||||
/** One public property in the pinned runtime census, or one WebIDL
|
||||
* dictionary member observed through its conversion reads. Static rows
|
||||
* must resolve to an operation/option above; dynamic-only rows are the
|
||||
* must resolve to an operation/option above; unsupported rows are the
|
||||
* implementation queue; out-of-scope rows make intentional omissions
|
||||
* explicit instead of letting absence masquerade as a support claim. */
|
||||
export interface FetchCompatInventoryEntry {
|
||||
@@ -119,22 +119,6 @@ const staticEntry = (
|
||||
placement: FetchCompatInventoryPlacement,
|
||||
): FetchCompatInventoryEntry => ({ id, owner, member, placement, status: "static" });
|
||||
|
||||
const dynamicEntry = (
|
||||
id: string,
|
||||
owner: string,
|
||||
member: string,
|
||||
placement: FetchCompatInventoryPlacement,
|
||||
reason: string,
|
||||
): FetchCompatInventoryEntry => ({
|
||||
id,
|
||||
owner,
|
||||
member,
|
||||
placement,
|
||||
status: "dynamic-only",
|
||||
code: "SC2020",
|
||||
reason,
|
||||
});
|
||||
|
||||
const unsupportedEntry = (
|
||||
id: string,
|
||||
owner: string,
|
||||
@@ -167,8 +151,7 @@ const outOfScopeEntry = (
|
||||
});
|
||||
|
||||
const widerMemberFence = "the member is outside the native static handle projection";
|
||||
const typedInterfaceUnsupported =
|
||||
"typed source has no compiler bridge for this interface in either tier";
|
||||
const typedInterfaceUnsupported = "typed source has no compiler bridge for this interface";
|
||||
const metadataExclusion =
|
||||
"WebIDL brand metadata is observable reflection, not an executable compatibility operation";
|
||||
|
||||
@@ -581,7 +564,7 @@ export const NODE24_FETCH_COMPAT_PROFILE = {
|
||||
"AbortSignal",
|
||||
"constructor",
|
||||
"constructor",
|
||||
"Node exposes the interface object but constructing it throws; neither compiler tier currently preserves that constructor behavior",
|
||||
"Node exposes the interface object but constructing it throws; the compiler does not preserve that constructor behavior",
|
||||
),
|
||||
staticEntry("stdlib.abort-signal.abort", "AbortSignal", "abort", "static"),
|
||||
staticEntry("stdlib.abort-signal.timeout", "AbortSignal", "timeout", "static"),
|
||||
@@ -626,7 +609,7 @@ export const NODE24_FETCH_COMPAT_PROFILE = {
|
||||
staticEntry(`stdlib.headers.${member}`, "Headers", member, "prototype"),
|
||||
),
|
||||
...["keys", "values", "entries"].map((member) =>
|
||||
dynamicEntry(
|
||||
unsupportedEntry(
|
||||
`stdlib.headers.${member}`,
|
||||
"Headers",
|
||||
member,
|
||||
@@ -635,7 +618,7 @@ export const NODE24_FETCH_COMPAT_PROFILE = {
|
||||
),
|
||||
),
|
||||
staticEntry("stdlib.headers.forEach", "Headers", "forEach", "prototype"),
|
||||
dynamicEntry(
|
||||
unsupportedEntry(
|
||||
"stdlib.headers.symbol.iterator",
|
||||
"Headers",
|
||||
"[Symbol.iterator]",
|
||||
@@ -704,10 +687,10 @@ export const NODE24_FETCH_COMPAT_PROFILE = {
|
||||
"Response",
|
||||
member,
|
||||
"static",
|
||||
"Response static constructor-object operations have no compiler lowering in either tier",
|
||||
"Response static constructor-object operations have no compiler lowering",
|
||||
),
|
||||
),
|
||||
dynamicEntry("stdlib.response.type", "Response", "type", "prototype", widerMemberFence),
|
||||
unsupportedEntry("stdlib.response.type", "Response", "type", "prototype", widerMemberFence),
|
||||
...["url", "redirected", "status", "ok", "statusText", "headers", "body", "bodyUsed"].map(
|
||||
(member) => staticEntry(`stdlib.response.${member}`, "Response", member, "prototype"),
|
||||
),
|
||||
@@ -718,7 +701,7 @@ export const NODE24_FETCH_COMPAT_PROFILE = {
|
||||
"Response",
|
||||
member,
|
||||
"prototype",
|
||||
"the dynamic fetch bridge does not implement this Response operation",
|
||||
"the native fetch runtime does not implement this Response operation",
|
||||
),
|
||||
),
|
||||
...["text", "json"].map((member) =>
|
||||
@@ -744,13 +727,13 @@ export const NODE24_FETCH_COMPAT_PROFILE = {
|
||||
staticEntry("stdlib.readable-stream.cancel", "ReadableStream", "cancel", "prototype"),
|
||||
staticEntry("stdlib.readable-stream.get-reader", "ReadableStream", "getReader", "prototype"),
|
||||
...["pipeThrough", "values"].map((member) =>
|
||||
dynamicEntry(
|
||||
unsupportedEntry(
|
||||
`stdlib.readable-stream.${member}`,
|
||||
"ReadableStream",
|
||||
member,
|
||||
"prototype",
|
||||
member === "values"
|
||||
? "direct for-await consumption compiles statically; storing or driving the first-class iterator handle requires --dynamic"
|
||||
? "direct for-await consumption compiles statically; storing or driving the first-class iterator handle is unsupported"
|
||||
: "the wider Web Streams graph is outside the native readable-stream slice",
|
||||
),
|
||||
),
|
||||
@@ -760,7 +743,7 @@ export const NODE24_FETCH_COMPAT_PROFILE = {
|
||||
"ReadableStream",
|
||||
member,
|
||||
"prototype",
|
||||
"the dynamic Web Streams bridge exposes only an explicit unsupported stub for this operation",
|
||||
"this operation is outside the native readable-stream implementation",
|
||||
),
|
||||
),
|
||||
unsupportedEntry(
|
||||
@@ -768,7 +751,7 @@ export const NODE24_FETCH_COMPAT_PROFILE = {
|
||||
"ReadableStream",
|
||||
"[Symbol.asyncIterator]",
|
||||
"prototype-symbol",
|
||||
"direct for-await consumption compiles statically; explicit symbol access and stored iterator handles have no compiler lowering in either tier",
|
||||
"direct for-await consumption compiles statically; explicit symbol access and stored iterator handles have no compiler lowering",
|
||||
),
|
||||
outOfScopeEntry(
|
||||
"stdlib.readable-stream.symbol.toStringTag",
|
||||
@@ -783,7 +766,7 @@ export const NODE24_FETCH_COMPAT_PROFILE = {
|
||||
"ReadableStreamDefaultReader",
|
||||
"constructor",
|
||||
"constructor",
|
||||
"the constructor object is unavailable in the dynamic engine; static reader handles come from ReadableStream.getReader()",
|
||||
"reader handles come from ReadableStream.getReader(); the constructor object is unsupported",
|
||||
),
|
||||
staticEntry(
|
||||
"stdlib.readable-stream-default-reader.read",
|
||||
@@ -822,7 +805,7 @@ export const NODE24_FETCH_COMPAT_PROFILE = {
|
||||
"ReadableStreamDefaultController",
|
||||
"constructor",
|
||||
"constructor",
|
||||
"the constructor object is unavailable in the dynamic engine; static controller handles come from underlying-source callbacks",
|
||||
"controller handles come from underlying-source callbacks; the constructor object is unsupported",
|
||||
),
|
||||
staticEntry(
|
||||
"stdlib.readable-stream-default-controller.desired-size",
|
||||
@@ -879,19 +862,19 @@ export const NODE24_FETCH_COMPAT_PROFILE = {
|
||||
return supported.has(member)
|
||||
? staticEntry(id, "RequestInit", member, "dictionary")
|
||||
: member === "dispatcher"
|
||||
? dynamicEntry(
|
||||
? unsupportedEntry(
|
||||
id,
|
||||
"RequestInit",
|
||||
member,
|
||||
"dictionary",
|
||||
"the dynamic tier recognizes Vercel CLI's EnvProxyDispatcher and applies equivalent native environment-proxy routing",
|
||||
"custom dispatchers are unsupported; native environment-proxy routing uses NODE_USE_ENV_PROXY",
|
||||
)
|
||||
: unsupportedEntry(
|
||||
id,
|
||||
"RequestInit",
|
||||
member,
|
||||
"dictionary",
|
||||
"neither compiler tier preserves this RequestInit member's conversion or transport behavior",
|
||||
"the compiler does not preserve this RequestInit member's conversion or transport behavior",
|
||||
);
|
||||
}),
|
||||
...["headers", "status", "statusText"].map((member) =>
|
||||
@@ -906,7 +889,7 @@ export const NODE24_FETCH_COMPAT_PROFILE = {
|
||||
{
|
||||
name: "WritableStream/TransformStream",
|
||||
reason:
|
||||
"the static tier currently targets readable fetch bodies, not general Web Streams graphs",
|
||||
"native support currently covers readable fetch bodies; general Web Streams graphs are unsupported",
|
||||
},
|
||||
{
|
||||
name: "Readable byte/BYOB streams",
|
||||
|
||||
@@ -17,10 +17,6 @@ export interface CompileBaseOptions {
|
||||
* The CLI's deprecated --emit-ir flag supplies this option. */
|
||||
emitIr?: boolean;
|
||||
sanitize?: boolean;
|
||||
/** Embed the dynamic-island engine (--dynamic). Off = the static default:
|
||||
* island constructs are diagnostics and nothing about codegen or linking
|
||||
* changes. */
|
||||
dynamic?: boolean;
|
||||
/** LLVM is the production code generator. */
|
||||
backend?: "llvm";
|
||||
/** Native optimization posture. Release is the shipped -O2 default; dev
|
||||
@@ -39,15 +35,8 @@ export interface CompileBaseOptions {
|
||||
* executable mapped and the memory charged for up to ~100 ms after exit.
|
||||
* Executables only; no effect on other platforms. */
|
||||
detachedExit?: boolean;
|
||||
/** --npm-static: package names whose shipped, unminified JS compiles
|
||||
* STATICALLY as program modules (inference types the bodies; statements
|
||||
* the lowering cannot prove become runtime fences). "auto" opts in every
|
||||
* directly-imported package passing the eligibility heuristics (own
|
||||
* .d.ts, unminified JS, no build-transform markers). A package whose
|
||||
* preflight refuses marks itself an offender and falls back to the
|
||||
* island (--dynamic) or the requires-dynamic diagnostic (static builds)
|
||||
* — never a silent misbuild. Off by default: nothing changes without
|
||||
* the flag. */
|
||||
|
||||
/** Eligible packages compile automatically. Explicit names override automatic selection heuristics. */
|
||||
npmStatic?: readonly string[] | "auto";
|
||||
/** Outbound native FFI manifest. Its signature-only TypeScript bindings
|
||||
* lower to direct C ABI calls. Source outputs retain those declarations;
|
||||
@@ -161,12 +150,9 @@ export type CompileLibraryResult =
|
||||
| { ok: false; diagnostics: ScrDiagnostic[]; sourceTexts: Map<string, string> };
|
||||
|
||||
export interface AnalyzeOptions {
|
||||
/** Analyze as a --dynamic build (island constructs lower instead of
|
||||
* producing requires-dynamic diagnostics). */
|
||||
dynamic?: boolean;
|
||||
/** --npm-static (see CompileOptions.npmStatic): the analysis compiles
|
||||
* opted-in packages' JS as program modules and the coverage report
|
||||
* carries each package's static/fallback status. */
|
||||
* carries each package's native/refused status. */
|
||||
npmStatic?: readonly string[] | "auto";
|
||||
/** Analyze with the outbound native bindings from this FFI manifest. */
|
||||
ffiProfilePath?: string;
|
||||
|
||||
@@ -768,7 +768,7 @@ class Int32Contracts {
|
||||
else if (dynamic.has(family))
|
||||
this.refuse(
|
||||
note,
|
||||
"instances of the class reach dynamic code (any, unknown casts, islands) where a store to the field is not checked",
|
||||
"instances of the class reach dynamic code (any or unknown casts) where a store to the field is not checked",
|
||||
"keep instances of this class out of any-typed values, or remove Int32",
|
||||
);
|
||||
}
|
||||
@@ -1516,7 +1516,7 @@ class ImmutableContracts {
|
||||
if (PASS_THROUGH.has(e.kind) || OBSERVE.has(e.kind)) return;
|
||||
if (e.kind === "fieldGet" || e.kind === "recordGet" || e.kind === "arrayGet") return;
|
||||
// Any other parent: a derived child goes somewhere this pass does
|
||||
// not follow (dynamic conversions, islands, runtime helpers).
|
||||
// not follow (dynamic conversions, runtime helpers).
|
||||
everyExprChild(
|
||||
e,
|
||||
(child) => {
|
||||
@@ -1759,10 +1759,6 @@ function describeSink(e: IrExpr): string {
|
||||
case "dynObjLit":
|
||||
case "dynArrLit":
|
||||
return "dynamic (any or unknown) code";
|
||||
case "jsOp":
|
||||
case "jsMarshal":
|
||||
case "jsExit":
|
||||
return "the JavaScript island";
|
||||
case "intrinsic":
|
||||
return `the runtime (${e.name})`;
|
||||
default:
|
||||
|
||||
@@ -10,7 +10,6 @@ import type { Milestone, ScrDiagnostic } from "../diagnostics/diagnostic.js";
|
||||
import {
|
||||
DIAGNOSTICS_SCHEMA,
|
||||
DIAGNOSTICS_SCHEMA_VERSION,
|
||||
DYNAMIC_CAPABLE_CODES,
|
||||
SourceLocator,
|
||||
groupDiagnostics,
|
||||
type DiagnosticGroup,
|
||||
@@ -21,15 +20,10 @@ import {
|
||||
} from "../diagnostics/envelope.js";
|
||||
import { renderDiagnostics } from "../diagnostics/render.js";
|
||||
import type { LowerStats } from "../frontend/lowering/lowerer.js";
|
||||
import type { NpmBuiltinUse, NpmLazyTrap } from "../frontend/npm.js";
|
||||
import type { ProvenanceSources } from "../frontend/provenance-registry.js";
|
||||
|
||||
export interface CoverageInput {
|
||||
file: string;
|
||||
/** Analyzed as a --dynamic build: island sites LOWER (their statements
|
||||
* land in stats.statementsIsland) instead of SC2013-fencing, and the
|
||||
* report renders them as "compile dynamically". */
|
||||
dynamic?: boolean;
|
||||
stats: LowerStats;
|
||||
diagnostics: ScrDiagnostic[];
|
||||
/** JS statements whose compile fences deferred to RUNTIME (runtimeFence
|
||||
@@ -44,19 +38,8 @@ export interface CoverageInput {
|
||||
* the program compiles, but these sites can behave differently natively.
|
||||
* Never blockers; `--fail-on=divergences` opts into failing on them. */
|
||||
divergences?: ScrDiagnostic[];
|
||||
/** --dynamic only: every Node builtin the embedded npm graph imports —
|
||||
* what the island must provide for this program (unshimmed ones also
|
||||
* appear as SC2030 blockers, EXCEPT lazily-reached ones, which embed
|
||||
* the island's call-time throw instead of failing the build). */
|
||||
npmBuiltins?: NpmBuiltinUse[];
|
||||
/** --dynamic only: unresolvable specifiers reached ONLY by require()/
|
||||
* import() edges — embedded as runtime traps with Node's call-time
|
||||
* error shape; listed in the builtins table so the build output stays
|
||||
* honest about what the binary cannot reach. */
|
||||
npmLazyTraps?: NpmLazyTrap[];
|
||||
/** --npm-static: each requested (or auto-detected) package's outcome —
|
||||
* compiled statically as program modules, or fallen back to the island
|
||||
* with the first refusal reason. */
|
||||
/** Each dependency's native compilation outcome, with the first refusal
|
||||
* reason for a refused package. */
|
||||
npmStatic?: NpmStaticStatus[];
|
||||
/** --provenance-sources: the resolved source-mapped packages and the
|
||||
* fallback notes — the report renders per-package attribution from
|
||||
@@ -64,7 +47,7 @@ export interface CoverageInput {
|
||||
provenance?: ProvenanceSources;
|
||||
/** --provenance-sources: per-file statement attribution from the emit
|
||||
* pass (LowerResult.statsByFile). */
|
||||
statsByFile?: Map<string, { total: number; failed: number; island: number }>;
|
||||
statsByFile?: Map<string, { total: number; failed: number }>;
|
||||
/** --provenance-sources: diagnostics of elided pure-annotated dead
|
||||
* consts (off the build; the provenance section names them). */
|
||||
provenanceElided?: ScrDiagnostic[];
|
||||
@@ -72,11 +55,11 @@ export interface CoverageInput {
|
||||
preflightFailed: boolean;
|
||||
}
|
||||
|
||||
/** One --npm-static package's outcome for the report. */
|
||||
/** One dependency's native compilation outcome. */
|
||||
export interface NpmStaticStatus {
|
||||
package: string;
|
||||
status: "static" | "fallback";
|
||||
/** The fallback's first refusal reason (fallback rows only). */
|
||||
status: "static" | "refused";
|
||||
/** The first refusal reason for a refused package. */
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
@@ -89,10 +72,6 @@ const RESET = "\x1b[0m";
|
||||
|
||||
const MILESTONE_ORDER: Milestone[] = ["M1", "M2", "M3", "M4", "M5", "later"];
|
||||
|
||||
/** Codes whose construct RUNS under --dynamic (the embedded engine) — the
|
||||
* report separates "one flag away" from "cannot compile at all". */
|
||||
const DYNAMIC_CAPABLE = DYNAMIC_CAPABLE_CODES;
|
||||
|
||||
/** Human rows cap their message: long embedded type text would otherwise
|
||||
* push lines past several hundred columns. The full message is in the
|
||||
* --print=diagnostics envelope (and in `scriptc build` output). */
|
||||
@@ -190,28 +169,18 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions
|
||||
return out.join("\n");
|
||||
}
|
||||
|
||||
// Top-line numbers stay whole-program: reached statements plus the
|
||||
// unreached remainder the analysis lowered anyway. Under --dynamic the
|
||||
// island-lowered statements split out as "compile dynamically" — they
|
||||
// BUILD, but their work runs in the embedded engine, and counting them
|
||||
// as static would overstate how native the program is.
|
||||
// Whole-program totals include reached and unreached statements.
|
||||
const un = input.unreached;
|
||||
const total = input.stats.statementsTotal + (un?.stats.statementsTotal ?? 0);
|
||||
const failed = input.stats.statementsFailed + (un?.stats.statementsFailed ?? 0);
|
||||
const island = input.stats.statementsIsland + (un?.stats.statementsIsland ?? 0);
|
||||
const skipped = input.stats.functionsSkipped + (un?.stats.functionsSkipped ?? 0);
|
||||
const ok = total - failed - island;
|
||||
const ok = total - failed;
|
||||
const pct = total === 0 ? 100 : Math.floor((ok / total) * 100);
|
||||
const pctColor = pct === 100 ? GREEN : pct >= 75 ? YELLOW : RED;
|
||||
|
||||
out.push(` statements analyzed ${total}`);
|
||||
out.push(` compile statically ${ok} ${c(pctColor, c(BOLD, `(${pct}%)`))}`);
|
||||
if (input.dynamic) {
|
||||
const ipct = total === 0 ? 0 : Math.floor((island / total) * 100);
|
||||
out.push(
|
||||
` compile dynamically ${island} ${c(YELLOW, c(BOLD, `(${ipct}%)`))} ${c(DIM, "(island sites — the embedded engine runs them)")}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (skipped > 0) {
|
||||
out.push(
|
||||
` ${c(DIM, `(${skipped} function${skipped === 1 ? "" : "s"} not analyzed — signature blocked; their statements count as not static)`)}`,
|
||||
@@ -219,72 +188,21 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions
|
||||
}
|
||||
out.push("");
|
||||
|
||||
// --npm-static outcomes: which opted-in packages compiled statically as
|
||||
// program modules and which fell back to the island (with the first
|
||||
// refusal reason) — the flag's honesty section.
|
||||
// Report native package compilation and the first reason for each refusal.
|
||||
const npmStatic = input.npmStatic ?? [];
|
||||
if (npmStatic.length > 0) {
|
||||
out.push(` ${c(DIM, "npm packages compiled statically (--npm-static):")}`);
|
||||
out.push(` ${c(DIM, "npm package compilation:")}`);
|
||||
const widestP = Math.max(...npmStatic.map((s) => s.package.length));
|
||||
for (const s of npmStatic) {
|
||||
const status =
|
||||
s.status === "static"
|
||||
? c(GREEN, "static")
|
||||
: c(YELLOW, "island fallback") +
|
||||
(s.detail !== undefined ? ` ${c(DIM, `(${s.detail})`)}` : "");
|
||||
: c(RED, "refused") + (s.detail !== undefined ? ` ${c(DIM, `(${s.detail})`)}` : "");
|
||||
out.push(` ${s.package.padEnd(widestP)} ${status}`);
|
||||
}
|
||||
out.push("");
|
||||
}
|
||||
|
||||
// What the embedded npm code asks of the island: every Node builtin the
|
||||
// graph imports, with its shim status, plus every unresolvable specifier
|
||||
// only require()/import() edges reach. Eagerly-blocked builtins also
|
||||
// surface as SC2030 blockers below; "lazy trap" rows DON'T fail the
|
||||
// build — the binary embeds Node's call-time error and the call throws
|
||||
// at runtime, exactly where Node would have failed.
|
||||
const builtins = input.dynamic ? (input.npmBuiltins ?? []) : [];
|
||||
const traps = input.dynamic ? (input.npmLazyTraps ?? []) : [];
|
||||
if (builtins.length > 0 || traps.length > 0) {
|
||||
out.push(
|
||||
` ${c(DIM, traps.length > 0 ? "embedded npm code imports Node builtins and unresolved specifiers:" : "embedded npm code imports Node builtins:")}`,
|
||||
);
|
||||
const widestB = Math.max(
|
||||
...[...builtins.map((b) => b.builtin.length), ...traps.map((t) => t.specifier.length)],
|
||||
);
|
||||
// Pad the plain words before coloring — escape codes have no width.
|
||||
const widestS = Math.max(
|
||||
...[...builtins.map((b) => (b.shimmed ? 7 : b.lazy ? 23 : 11)), ...traps.map(() => 24)],
|
||||
);
|
||||
for (const b of builtins) {
|
||||
const status = b.shimmed
|
||||
? c(GREEN, "shimmed".padEnd(widestS))
|
||||
: b.lazy
|
||||
? c(YELLOW, "not shimmed — lazy trap".padEnd(widestS))
|
||||
: c(RED, "not shimmed".padEnd(widestS));
|
||||
out.push(
|
||||
` ${b.builtin.padEnd(widestB)} ${status} ${c(DIM, `(${b.packages.join(", ")})`)}`,
|
||||
);
|
||||
}
|
||||
for (const t of traps) {
|
||||
// A native .node addon RESOLVED — the island cannot dlopen it, so
|
||||
// the build embedded a throwing ERR_DLOPEN_FAILED stub instead of
|
||||
// the addon's machine code; unresolvable rows keep Node's not-found
|
||||
// error shapes.
|
||||
const what = t.native ? "native addon — lazy trap" : "unresolvable — lazy trap";
|
||||
out.push(
|
||||
` ${t.specifier.padEnd(widestB)} ${c(YELLOW, what.padEnd(widestS))} ` +
|
||||
c(DIM, `(${t.via.join("/")} in ${t.packages.join(", ")})`),
|
||||
);
|
||||
}
|
||||
if (builtins.some((b) => b.lazy) || traps.length > 0) {
|
||||
out.push(
|
||||
` ${c(DIM, "(lazy trap: only reachable through require()/import() boundaries — the build embeds Node's call-time error; the call throws at runtime)")}`,
|
||||
);
|
||||
}
|
||||
out.push("");
|
||||
}
|
||||
|
||||
// --provenance-sources: per-package attribution — the answer to "did
|
||||
// the DEPENDENCY's statements compile static?", computed by summing
|
||||
// statsByFile entries under each package's source dir. Elided
|
||||
@@ -298,19 +216,17 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions
|
||||
for (const pkg of prov.packages) {
|
||||
let pTotal = 0;
|
||||
let pFailed = 0;
|
||||
let pIsland = 0;
|
||||
const dir = pkg.dir.endsWith("/") ? pkg.dir : `${pkg.dir}/`;
|
||||
for (const [file, s] of input.statsByFile ??
|
||||
new Map<string, { total: number; failed: number; island: number }>()) {
|
||||
new Map<string, { total: number; failed: number }>()) {
|
||||
if (!file.startsWith(dir)) continue;
|
||||
pTotal += s.total;
|
||||
pFailed += s.failed;
|
||||
pIsland += s.island;
|
||||
}
|
||||
const elided = (input.provenanceElided ?? []).filter((d) =>
|
||||
d.loc.file.startsWith(dir),
|
||||
).length;
|
||||
const pOk = pTotal - pFailed - pIsland;
|
||||
const pOk = pTotal - pFailed;
|
||||
const pPct = pTotal === 0 ? 100 : Math.floor((pOk / pTotal) * 100);
|
||||
const pColor = pPct === 100 ? GREEN : pPct >= 75 ? YELLOW : RED;
|
||||
const shortRepo = pkg.repo
|
||||
@@ -321,7 +237,7 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions
|
||||
` ${pkg.name}@${pkg.version} ${c(DIM, `${shortRepo} @ ${pkg.commit.slice(0, 12)}`)}`,
|
||||
);
|
||||
out.push(
|
||||
` ${pTotal} statements: ${pOk} static ${c(pColor, c(BOLD, `(${pPct}%)`))}, ${pIsland} island, ${pFailed} fenced` +
|
||||
` ${pTotal} statements: ${pOk} static ${c(pColor, c(BOLD, `(${pPct}%)`))}, ${pFailed} fenced` +
|
||||
(elided > 0
|
||||
? ` ${c(DIM, `(${elided} fence${elided === 1 ? "" : "s"} elided as @__PURE__ dead consts — uses would fence per site)`)}`
|
||||
: ""),
|
||||
@@ -357,11 +273,7 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions
|
||||
|
||||
const unreachedDiags = [...(un?.diagnostics ?? []), ...(un?.runtimeFences ?? [])];
|
||||
if (failed === 0 && input.diagnostics.length === 0 && unreachedDiags.length === 0) {
|
||||
out.push(
|
||||
island > 0
|
||||
? ` ${c(GREEN, "builds with --dynamic")} — no remaining blockers (the island sites above run in the embedded engine).`
|
||||
: ` ${c(GREEN, "fully static")} — this program has no dynamic remainder.`,
|
||||
);
|
||||
out.push(` ${c(GREEN, "fully static")} — all analyzed statements compile to native code.`);
|
||||
if (divergences.length > 0) {
|
||||
out.push("");
|
||||
renderDivergences();
|
||||
@@ -372,12 +284,10 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions
|
||||
// Ordered by internal scheduling (soonest first), then by frequency —
|
||||
// the top line is the blocker most likely to disappear next. The
|
||||
// milestone labels themselves are maintainer business and never rendered.
|
||||
// Blockers split by what a flag can fix: sites that RUN under --dynamic
|
||||
// (the embedded engine) versus constructs with no lowering at all.
|
||||
|
||||
const blockers = groupBlockers(input.diagnostics);
|
||||
const unreachedBlockers = groupBlockers(unreachedDiags, "unreached");
|
||||
const dynamic = blockers.filter((b) => DYNAMIC_CAPABLE.has(b.code));
|
||||
const rejected = blockers.filter((b) => !DYNAMIC_CAPABLE.has(b.code));
|
||||
const rejected = blockers;
|
||||
const renderGroup = (group: Blocker[], dim = false) => renderRows(group, RED, dim);
|
||||
if (
|
||||
failed > 0 &&
|
||||
@@ -391,14 +301,6 @@ export function renderCoverage(input: CoverageInput, opts: CoverageRenderOptions
|
||||
` ${c(RED, "blockers:")} ${failed} statement${failed === 1 ? "" : "s"} failed without a diagnostic (a compiler bug — please report it)`,
|
||||
);
|
||||
}
|
||||
if (dynamic.length > 0) {
|
||||
const sites = dynamic.reduce((n, b) => n + b.count, 0);
|
||||
out.push(
|
||||
` ${c(YELLOW, `runs with --dynamic`)} ${sites} site${sites === 1 ? "" : "s"} ${c(DIM, "(embeds a JS engine, ~620KB — static stays the default)")}`,
|
||||
);
|
||||
renderGroup(dynamic);
|
||||
out.push("");
|
||||
}
|
||||
if (rejected.length > 0) {
|
||||
out.push(` blockers:`);
|
||||
renderGroup(rejected);
|
||||
@@ -510,9 +412,8 @@ export function coverageEnvelope(
|
||||
const un = input.unreached;
|
||||
const total = input.stats.statementsTotal + (un?.stats.statementsTotal ?? 0);
|
||||
const failed = input.stats.statementsFailed + (un?.stats.statementsFailed ?? 0);
|
||||
const island = input.stats.statementsIsland + (un?.stats.statementsIsland ?? 0);
|
||||
const skipped = input.stats.functionsSkipped + (un?.stats.functionsSkipped ?? 0);
|
||||
const ok = total - failed - island;
|
||||
const ok = total - failed;
|
||||
return {
|
||||
schema: DIAGNOSTICS_SCHEMA,
|
||||
schemaVersion: DIAGNOSTICS_SCHEMA_VERSION,
|
||||
@@ -531,7 +432,7 @@ export function coverageEnvelope(
|
||||
stats: {
|
||||
statementsTotal: total,
|
||||
statementsStatic: ok,
|
||||
statementsDynamic: island,
|
||||
statementsDynamic: 0,
|
||||
statementsFailed: failed,
|
||||
functionsSkipped: skipped,
|
||||
percentStatic: total === 0 ? 100 : Math.floor((ok / total) * 100),
|
||||
|
||||
@@ -9,10 +9,10 @@ import { InternalCompilerError } from "../errors.js";
|
||||
* (tests/harness/surface-manifest.test.ts) fails until the committed file
|
||||
* is regenerated (`pnpm manifest`).
|
||||
*
|
||||
* Schema (schemaVersion 1 — additive changes only; removals or meaning
|
||||
* Schema (schemaVersion 2 — additive changes only; removals or meaning
|
||||
* changes bump the number):
|
||||
* {
|
||||
* schemaVersion: 1,
|
||||
* schemaVersion: 2,
|
||||
* compilerVersion: string, // the exact published release version —
|
||||
* // a version pin matches this string
|
||||
* coverage: string[], // what is and is not projected (the
|
||||
@@ -23,7 +23,7 @@ import { InternalCompilerError } from "../errors.js";
|
||||
* // names ("stdlib.string.charCodeAt")
|
||||
* kind: "syntax" | "stdlib" | "node-builtin" | "diagnostic-fence",
|
||||
* name: string, // human-readable surface name
|
||||
* status: "static" | "dynamic-only" | "unsupported",
|
||||
* status: "static" | "unsupported",
|
||||
* code?: "SC____", // REQUIRED on every non-static entry:
|
||||
* // the diagnostic code the compiler
|
||||
* // raises where the refusal applies
|
||||
@@ -49,7 +49,7 @@ import {
|
||||
BUILTIN_MODULE_FNS,
|
||||
COLLECTION_ITERATOR_METHODS,
|
||||
COMPOUND_ASSIGN_OPS,
|
||||
ISLAND_SURFACE,
|
||||
STDLIB_SURFACE,
|
||||
MAP_METHODS,
|
||||
SET_COMBINE_METHODS,
|
||||
SET_METHODS,
|
||||
@@ -65,7 +65,7 @@ import {
|
||||
|
||||
export type SurfaceEntryKind = "syntax" | "stdlib" | "node-builtin" | "diagnostic-fence";
|
||||
|
||||
export type SurfaceEntryStatus = "static" | "dynamic-only" | "unsupported";
|
||||
export type SurfaceEntryStatus = "static" | "unsupported";
|
||||
|
||||
export interface SurfaceManifestEntry {
|
||||
id: string;
|
||||
@@ -77,13 +77,13 @@ export interface SurfaceManifestEntry {
|
||||
}
|
||||
|
||||
export interface SurfaceManifest {
|
||||
schemaVersion: 1;
|
||||
schemaVersion: 2;
|
||||
compilerVersion: string;
|
||||
coverage: string[];
|
||||
entries: SurfaceManifestEntry[];
|
||||
}
|
||||
|
||||
export const MANIFEST_SCHEMA_VERSION = 1;
|
||||
export const MANIFEST_SCHEMA_VERSION = 2;
|
||||
|
||||
/** The manifest's own scope statement: what the projection covers and —
|
||||
* just as load-bearing — what it deliberately leaves out, so absence is
|
||||
@@ -93,10 +93,9 @@ const COVERAGE_NOTES: string[] = [
|
||||
"Absence from this manifest means 'not projected', never 'unsupported'. Surfaces lowered through dedicated code paths rather than tables are not yet projected: console, JSON, Promise/async and the timer surface, the net/http/tls/https/dgram/dns/assert/test/stream/readline module member surfaces, template literals, the regex slice, global functions (parseInt, parseFloat, isNaN, isFinite), and the process surface outside its ambient slice.",
|
||||
"The ambient-nondeterminism and ambient-authority surfaces the library sidecar's determinism attestation scans ARE projected even where they lower through dedicated code paths — the Date compositions (stdlib.date.*), perf_hooks' performance.now, and the process global's ambient reads and authority calls (node-builtin.process.*) — so a determinism fence can name every surface the attestation demotes on.",
|
||||
"stdlib and node-builtin member entries name surface whose LOWERED call forms are constrained (arity, argument shapes); declared call forms outside the lowered set are refused per site, with code SC2020 for standard-library and node-builtin surface.",
|
||||
"Entries with status 'unsupported' or 'dynamic-only' describe where the named code is raised: forms of the construct outside the supported subset are refused with that code — not that every form of the named feature is refused. Supported forms appear as their own static entries where a table projects them.",
|
||||
"Entries with status 'dynamic-only' compile when the build embeds the dynamic engine (--dynamic); without the flag each use site is refused with the entry's code.",
|
||||
"Entries with status 'unsupported' describe where the named code is raised: forms of the construct outside the supported subset are refused with that code — not that every form of the named feature is refused. Supported forms appear as their own static entries where a table projects them.",
|
||||
`The engine-free fetch projection targets Node ${NODE24_FETCH_COMPAT_PROFILE.target.node} with bundled Undici ${NODE24_FETCH_COMPAT_PROFILE.target.undici}. Each projected row names the differential evidence that guards it; changing the pinned Node or Undici version is an explicit profile update.`,
|
||||
"The fetch profile also contains a runtime-reflected census of the selected fetch, abort, Headers, and readable-stream interfaces plus RequestInit/ResponseInit dictionary reads. Static, dynamic-only, and unsupported census rows are projected here; its explicitly out-of-scope metadata rows and adjacent-interface exclusions remain in the profile so absence is deliberate rather than ambiguous.",
|
||||
"The fetch profile also contains a runtime-reflected census of the selected fetch, abort, Headers, and readable-stream interfaces plus RequestInit/ResponseInit dictionary reads. Static and unsupported census rows are projected here; its explicitly out-of-scope metadata rows and adjacent-interface exclusions remain in the profile so absence is deliberate rather than ambiguous.",
|
||||
"Process-level diagnostic codes are not surface entries: SC0001-SC0004 are preflight gates, SC1110 is a comptime evaluation failure, SC3001/SC3002 are backend/target tier refusals, SC9001/SC9002 are internal errors.",
|
||||
"Entry statuses are projected for the desktop targets. The mobile targets (aarch64-apple-ios, aarch64-apple-ios-simulator, aarch64-linux-android) compile library-mode archives only: the library-admissible surface (what SC4005's async_free requirement and the library link set admit) is supported there, the executable lane refuses those triples with SC3002, and no entry outside the library-admissible surface carries a mobile support claim. iOS archives build for iOS 15.0 on darwin hosts; Android archives build against NDK API level 26.",
|
||||
"No scheduling metadata is published; entry ids are the stable diff keys across releases.",
|
||||
@@ -304,30 +303,29 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes
|
||||
});
|
||||
}
|
||||
|
||||
// ── stdlib: the Math surface — static table first, island remainder ───
|
||||
const mathNames = new Set([
|
||||
...Object.keys(STATIC_MATH_FNS),
|
||||
...Object.keys(ISLAND_SURFACE.math.fns),
|
||||
...Object.keys(STDLIB_SURFACE.math.fns),
|
||||
]);
|
||||
for (const name of mathNames) {
|
||||
const stat = STATIC_MATH_FNS[name];
|
||||
const island = ISLAND_SURFACE.math.fns[name];
|
||||
const declared = STDLIB_SURFACE.math.fns[name];
|
||||
if (stat !== undefined) {
|
||||
add({
|
||||
id: `stdlib.math.${name}`,
|
||||
kind: "stdlib",
|
||||
name: `Math.${name}`,
|
||||
status: "static",
|
||||
// Only the island overlap earns a tail: the tables prove where the
|
||||
// untabled arities go there. Elsewhere untabled shapes may have
|
||||
|
||||
// Untabled shapes may have
|
||||
// their own special-cased lowerings (Math.min/max over one array
|
||||
// spread), so no claim is made about them.
|
||||
note:
|
||||
name === "hypot"
|
||||
? "compiles statically at any arity, including number[] spreads"
|
||||
: `compiles statically at arity ${stat.arity}` +
|
||||
(island !== undefined
|
||||
? "; other declared call shapes run only in the embedded dynamic engine (SC2012 without --dynamic)"
|
||||
(declared !== undefined
|
||||
? "; other declared call shapes are refused with SC2012"
|
||||
: ""),
|
||||
});
|
||||
} else {
|
||||
@@ -335,17 +333,17 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes
|
||||
id: `stdlib.math.${name}`,
|
||||
kind: "stdlib",
|
||||
name: `Math.${name}`,
|
||||
status: "dynamic-only",
|
||||
status: "unsupported",
|
||||
code: "SC2012",
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const name of Object.keys(ISLAND_SURFACE.math.props)) {
|
||||
for (const name of Object.keys(STDLIB_SURFACE.math.props)) {
|
||||
add({
|
||||
id: `stdlib.math.${name}`,
|
||||
kind: "stdlib",
|
||||
name: `Math.${name}`,
|
||||
status: "dynamic-only",
|
||||
status: "unsupported",
|
||||
code: "SC2012",
|
||||
});
|
||||
}
|
||||
@@ -360,7 +358,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes
|
||||
}
|
||||
const numberNames = new Set([
|
||||
...Object.keys(STATIC_NUMBER_METHODS),
|
||||
...Object.keys(ISLAND_SURFACE.number),
|
||||
...Object.keys(STDLIB_SURFACE.number),
|
||||
]);
|
||||
for (const name of numberNames) {
|
||||
const stat = Object.hasOwn(STATIC_NUMBER_METHODS, name)
|
||||
@@ -379,26 +377,24 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes
|
||||
id: `stdlib.number.${name}`,
|
||||
kind: "stdlib",
|
||||
name: `number.prototype.${name}`,
|
||||
status: "dynamic-only",
|
||||
status: "unsupported",
|
||||
code: "SC2012",
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const name of Object.keys(ISLAND_SURFACE.string)) {
|
||||
for (const name of Object.keys(STDLIB_SURFACE.string)) {
|
||||
if (STRING_INDEX_METHODS.has(name) || STRING_REPLACE_METHODS.has(name)) continue;
|
||||
add({
|
||||
id: `stdlib.string.${name}`,
|
||||
kind: "stdlib",
|
||||
name: `string.prototype.${name}`,
|
||||
status: "dynamic-only",
|
||||
status: "unsupported",
|
||||
code: "SC2012",
|
||||
});
|
||||
}
|
||||
// ISLAND_SURFACE.globals (parseFloat, isFinite) is deliberately NOT
|
||||
// STDLIB_SURFACE.globals (parseFloat, isFinite) is deliberately NOT
|
||||
// projected: those globals also lower statically over exactly-typed
|
||||
// arguments through untabled call paths, so neither status would be
|
||||
// truthful from the island table alone — the coverage notes carry the
|
||||
// exclusion.
|
||||
// arguments through untabled call paths; the table alone cannot classify them.
|
||||
|
||||
// ── node-builtin: modules, members, constants, named fences ───────────
|
||||
const moduleId = (mod: string): string => `node-builtin.${mod.split("/").join(".")}`;
|
||||
@@ -488,7 +484,7 @@ export function generateSurfaceManifest(compilerVersion: string): SurfaceManifes
|
||||
}
|
||||
}
|
||||
for (const row of NODE24_FETCH_COMPAT_PROFILE.inventory.entries) {
|
||||
if (row.status !== "dynamic-only" && row.status !== "unsupported") continue;
|
||||
if (row.status !== "unsupported") continue;
|
||||
if (row.code === undefined || row.reason === undefined) {
|
||||
throw new InternalCompilerError(`non-static fetch inventory row '${row.id}' is incomplete`);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user