mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-24 06:14:01 +08:00
Merge main
This commit is contained in:
@@ -140,7 +140,6 @@ Never hand-edit these files:
|
||||
- `internal/compatibility/generated/node-v24-internal.json`
|
||||
- `internal/compatibility/generated/node-v24-backlog.json`
|
||||
- `docs/src/generated/node-v24-compatibility.json`
|
||||
- `docs/src/generated/node-v24-compatibility-meta.json`
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ Application-shaped workloads for measuring the speed of compiled executables. Ea
|
||||
| `numeric-kernels` | cpu | sieve, hashing, `number[][]` matmul, `Float64Array` |
|
||||
| `typed-array-scan`, `typed-array-scan-workers` | cpu | element-by-element scan loops over `Uint8Array`, `Uint16Array`, `Int32Array` and `Float64Array`; the second entry is a worker-enabled executable (it can start a worker), so each access also checks for shared storage |
|
||||
| `intern-table`, `intern-table-map`, `intern-table-1`, `intern-table-map-1` | cpu | worker threads interning the same type-like objects under string keys and records under number keys, then hits and misses: one shared `InternTable` per kind of key, or a module-level `Map` per thread (the `-map` entries), with 8 threads or 1 (the `-1` entries); under Node.js every thread has its own tables |
|
||||
| `thread-slots`, `thread-slots-cache` | cpu | worker threads reading one published type graph from an `InternTable` and keeping per-thread state for each type they visit: a `ThreadSlots` family, or a direct-mapped cache by id in front of a `Map` by id (the `-cache` entry); under Node.js both are per thread |
|
||||
| `atomics-contended` | cpu | four worker threads updating `SharedArrayBuffer` counters with `Atomics`: one shared counter, one counter per worker, a `compareExchange` loop, and `Atomics.wait`/`notify` handoffs |
|
||||
| `value-objects` | cpu | small immutable records and class instances: vector helpers, method chains, particle updates |
|
||||
| `value-records` | cpu | records that hold strings, spread state updates and destructured tuples: token helpers, an immutable reducer, tuple returns |
|
||||
|
||||
@@ -127,6 +127,26 @@
|
||||
"100000"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "thread-slots",
|
||||
"entry": "workloads/thread-slots.ts",
|
||||
"args": [
|
||||
"slots",
|
||||
"8",
|
||||
"50000",
|
||||
"20"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "thread-slots-cache",
|
||||
"entry": "workloads/thread-slots.ts",
|
||||
"args": [
|
||||
"cache",
|
||||
"8",
|
||||
"50000",
|
||||
"20"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "atomics-contended",
|
||||
"entry": "workloads/atomics-contended.ts",
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// Per-thread state for shared objects: worker threads read the same
|
||||
// published type graph from an InternTable and keep per-thread state for
|
||||
// each type they visit (a resolved flag and a visit count), then walk the
|
||||
// graph many times. Arguments: the store ("slots" for a ThreadSlots family,
|
||||
// "cache" for a direct-mapped cache by id in front of a Map by id, the
|
||||
// layout programs use without slots), the thread count, the type count and
|
||||
// the number of walks. Under Node.js both stores are per-thread.
|
||||
import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
|
||||
import { InternTable, ThreadSlots } from "@scriptc/experimental";
|
||||
|
||||
class Type {
|
||||
readonly id: number;
|
||||
readonly members: readonly Type[];
|
||||
constructor(id: number, members: readonly Type[]) {
|
||||
this.id = id;
|
||||
this.members = members;
|
||||
}
|
||||
}
|
||||
|
||||
class TypeState {
|
||||
resolved = false;
|
||||
visits = 0;
|
||||
}
|
||||
|
||||
const types = new InternTable<number, Type>("bench.slot-types");
|
||||
const states = new ThreadSlots<Type, TypeState>("bench.type-states");
|
||||
|
||||
const CACHE = 1024;
|
||||
const cacheIds: number[] = new Array<number>(CACHE).fill(-1);
|
||||
const cacheStates: (TypeState | undefined)[] = new Array<TypeState | undefined>(CACHE).fill(
|
||||
undefined,
|
||||
);
|
||||
const byId = new Map<number, TypeState>();
|
||||
|
||||
function slotState(t: Type): TypeState {
|
||||
let state = states.get(t);
|
||||
if (state === undefined) {
|
||||
state = new TypeState();
|
||||
states.set(t, state);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
function cachedState(t: Type): TypeState {
|
||||
const slot = t.id & (CACHE - 1);
|
||||
if (cacheIds[slot] === t.id) return cacheStates[slot]!;
|
||||
let state = byId.get(t.id);
|
||||
if (state === undefined) {
|
||||
state = new TypeState();
|
||||
byId.set(t.id, state);
|
||||
}
|
||||
cacheIds[slot] = t.id;
|
||||
cacheStates[slot] = state;
|
||||
return state;
|
||||
}
|
||||
|
||||
function typeOf(id: number): Type {
|
||||
return types.getOrInsertComputed(id, () => {
|
||||
const members: Type[] = [];
|
||||
if (id >= 3) members.push(typeOf(Math.floor(id / 3)));
|
||||
if (id >= 7) members.push(typeOf(id - 7));
|
||||
return new Type(id, members);
|
||||
});
|
||||
}
|
||||
|
||||
function run(useSlots: boolean, count: number, walks: number): string {
|
||||
const all: Type[] = [];
|
||||
for (let id = 0; id < count; id++) all.push(typeOf(id));
|
||||
// Visits follow member edges from a pseudo-random start, as a checker's
|
||||
// relation checks do: neighbours are often visited together.
|
||||
let seed = 1;
|
||||
let resolved = 0;
|
||||
let visits = 0;
|
||||
for (let w = 0; w < walks; w++) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
seed = (seed * 48271) % 2147483647;
|
||||
let t = all[seed % count]!;
|
||||
for (let depth = 0; depth < 4; depth++) {
|
||||
const state = useSlots ? slotState(t) : cachedState(t);
|
||||
if (!state.resolved) {
|
||||
state.resolved = true;
|
||||
resolved++;
|
||||
}
|
||||
state.visits++;
|
||||
visits++;
|
||||
if (t.members.length === 0) break;
|
||||
t = t.members[depth % t.members.length]!;
|
||||
}
|
||||
}
|
||||
}
|
||||
return `${resolved} resolved, ${visits} visits`;
|
||||
}
|
||||
|
||||
if (isMainThread) {
|
||||
const store = process.argv[2] ?? "slots";
|
||||
const threads = Number(process.argv[3] ?? "8");
|
||||
const count = Number(process.argv[4] ?? "50000");
|
||||
const walks = Number(process.argv[5] ?? "20");
|
||||
const results: string[] = [];
|
||||
let pending = threads;
|
||||
for (let i = 0; i < threads; i++) {
|
||||
const worker = new Worker(new URL(import.meta.url), {
|
||||
workerData: { useSlots: store === "slots", count, walks },
|
||||
});
|
||||
worker.on("message", (text: string) => {
|
||||
results[i] = text;
|
||||
});
|
||||
worker.on("exit", () => {
|
||||
if (--pending === 0) {
|
||||
const same = results.every((r) => r === results[0]);
|
||||
console.log(`${threads} threads: ${results[0]}, same in every thread ${same}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const { useSlots, count, walks } = workerData as {
|
||||
useSlots: boolean;
|
||||
count: number;
|
||||
walks: number;
|
||||
};
|
||||
parentPort!.postMessage(run(useSlots, count, walks));
|
||||
}
|
||||
@@ -238,8 +238,99 @@ No lock is held while a callback runs. Two threads that miss the same key at the
|
||||
2. It does not compare identities or ids across threads.
|
||||
3. A thread does not depend on another thread's inserts: `size` and `has` reflect every thread's inserts in scriptc and the calling thread's under Node.js.
|
||||
|
||||
State that a thread computes about a shared value later (lazily resolved members, caches) belongs in thread-local state, such as a module-level `Map` keyed by the value or an array indexed by an id the value carries: module state is per thread in both runtimes. In scriptc an array indexed by a dense id is the fastest of these.
|
||||
State that a thread computes about a shared value later (lazily resolved members, caches) belongs in thread-local state, such as a [`ThreadSlots`](#threadslots) family, a module-level `Map` keyed by the value or an array indexed by an id the value carries: module state is per thread in both runtimes. In scriptc an array indexed by a dense id is the fastest of these.
|
||||
|
||||
### Memory
|
||||
|
||||
Lookups take no lock and write nothing. Inserts lock one of 64 segments of the table for a probe and a few stores. Stored values are immortal in scriptc builds: they stay allocated until the process exits, including a value a callback computed for a key that another thread stored first. Programs that never construct an `InternTable` do not run any of its code.
|
||||
|
||||
## ThreadSlots
|
||||
|
||||
Threads that share values through an `InternTable` often compute more about each shared value later: resolved members, cached results, flags. That state differs per thread, so it cannot live in the shared value, which is immutable. A `ThreadSlots` family holds one value per key and thread, like a `WeakMap` that each thread has its own copy of. A thread sees only the values it set, including for keys that every thread shares.
|
||||
|
||||
```ts:slots.ts
|
||||
import { Worker, isMainThread, parentPort, threadId } from "node:worker_threads";
|
||||
import { InternTable, ThreadSlots } from "@scriptc/experimental";
|
||||
|
||||
class Shape {
|
||||
readonly name: string;
|
||||
constructor(name: string) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
class ShapeState {
|
||||
uses = 0;
|
||||
readonly thread: number;
|
||||
constructor(thread: number) {
|
||||
this.thread = thread;
|
||||
}
|
||||
}
|
||||
|
||||
const shapes = new InternTable<string, Shape>("example.slot-shapes");
|
||||
// Module scope: every thread that evaluates this line opens the same family.
|
||||
const states = new ThreadSlots<Shape, ShapeState>("example.shape-states");
|
||||
|
||||
function use(name: string): ShapeState {
|
||||
const shape = shapes.getOrInsertComputed(name, () => new Shape(name));
|
||||
let state = states.get(shape);
|
||||
if (state === undefined) {
|
||||
state = new ShapeState(threadId);
|
||||
states.set(shape, state);
|
||||
}
|
||||
state.uses++;
|
||||
return state;
|
||||
}
|
||||
|
||||
if (isMainThread) {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
new Worker(new URL(import.meta.url)).on("message", (text: string) => console.log(text));
|
||||
}
|
||||
} else {
|
||||
use("square");
|
||||
const state = use("square");
|
||||
parentPort!.postMessage(`worker: ${state.uses} uses, own state ${state.thread === threadId}`);
|
||||
}
|
||||
```
|
||||
|
||||
In the scriptc executable both workers read the same `Shape` object from the table, and each keeps its own `ShapeState` for it. Both runtimes print the same:
|
||||
|
||||
```console
|
||||
$ node slots.ts
|
||||
worker: 2 uses, own state true
|
||||
worker: 2 uses, own state true
|
||||
$ scriptc build slots.ts -o slots >/dev/null
|
||||
$ ./slots
|
||||
worker: 2 uses, own state true
|
||||
worker: 2 uses, own state true
|
||||
```
|
||||
|
||||
### Slot methods
|
||||
|
||||
<dl>
|
||||
<dt><code>new ThreadSlots<K, V>(name)</code></dt>
|
||||
<dd>Opens the family called <code>name</code>. Within one thread every construction with the same name refers to the same family.</dd>
|
||||
<dt><code>slots.get(key)</code>, <code>slots.has(key)</code></dt>
|
||||
<dd>Read the calling thread's value for <code>key</code>.</dd>
|
||||
<dt><code>slots.set(key, value)</code></dt>
|
||||
<dd>Sets the calling thread's value for <code>key</code> and returns the slots. It never writes the key, so it works on published keys.</dd>
|
||||
<dt><code>slots.delete(key)</code></dt>
|
||||
<dd>Removes the calling thread's value for <code>key</code> and returns whether there was one.</dd>
|
||||
</dl>
|
||||
|
||||
Under Node.js each family is a `WeakMap` per thread.
|
||||
|
||||
### Keys and values
|
||||
|
||||
scriptc requires:
|
||||
|
||||
- the name to be a string literal, and every construction of one name to use the same type arguments;
|
||||
- the key type to be a class declared in the program; keys may be instances of its subclasses;
|
||||
- the value type to be a class, an object type, an array, a `Map` or a `Set`;
|
||||
- the methods to be called through a module-level `const` initialized with `new ThreadSlots<K, V>("name")`, as in the example. A family's storage is found by its name when the program is compiled.
|
||||
|
||||
### Slot cost
|
||||
|
||||
Each instance of a key class, and of every class in its hierarchy, carries a 4-byte slot index, 0 until a value is set for it. The first `set` for a key assigns the index; indices are never reused. `get` and `has` read the index, a page of the thread's family and the entry, with no lock, hash or atomic update. A family stores values in pages of 1024 entries, allocated as the thread sets values in them.
|
||||
|
||||
A family holds a reference to each of the thread's values. A value is released when it is replaced or deleted, when its key is freed, and when the thread exits. Keys that are freed with a [`LifetimeRegion`](#lifetimeregion) keep their values until the thread exits, a value that belongs to a region keeps the region allocated while the family holds it, and a value that refers to its own key keeps the key alive. Programs that never construct a `ThreadSlots` do not run any of its code, and their classes have no slot index.
|
||||
|
||||
@@ -64,6 +64,8 @@ title: "Limitations"
|
||||
|
||||
<include>../limitations/builtin-apis/eventemitter.mdx</include>
|
||||
|
||||
<include>../limitations/builtin-apis/asynclocalstorage.mdx</include>
|
||||
|
||||
<include>../limitations/builtin-apis/streams-and-readline.mdx</include>
|
||||
|
||||
<include>../limitations/builtin-apis/commonjs-metadata.mdx</include>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
- **AsyncLocalStorage:** timer, interval, `setImmediate`, `process.nextTick` and `queueMicrotask` callbacks run with the stores that were active when they were scheduled, as in Node. Completion callbacks of callback-style I/O APIs, such as `fs.readFile` and `crypto.randomBytes`, run with no store, where Node runs them with the stores that were active when the operation started.
|
||||
@@ -1 +1 @@
|
||||
- **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`, `toJSON` (a `string | null`: null for an invalid date, as in Node), calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile. JavaScript Dates retain identity through checked storage and support Date-copy construction and JSON serialization. A TypeScript Date converted to `unknown` becomes a Date with the same time value; each conversion creates a new Date, so two `unknown` values converted from one TypeScript Date are not `===`. See [Dates and timezones](#dates-and-timezones) for parsing restrictions.
|
||||
- **Date:** Date unions, year/month constructors, setters, statically typed TypeScript identity comparisons, thrown Date values, and locale formatters are unsupported. Zero-argument and single number/string constructors, storage and passing, `getTime`, `valueOf`, `toISOString`, `toUTCString`, `toJSON` (a `string | null`: null for an invalid date, as in Node), `toString`, `toDateString`, `toTimeString`, `String(date)` and template literal conversion, calendar getters, `getTimezoneOffset`, and single-string `Date.parse` compile. JavaScript Dates retain identity through checked storage and support Date-copy construction and JSON serialization. A TypeScript Date converted to `unknown` becomes a Date with the same time value; each conversion creates a new Date, so two `unknown` values converted from one TypeScript Date are not `===`. See [Dates and timezones](#dates-and-timezones) for parsing restrictions.
|
||||
|
||||
@@ -1 +1 @@
|
||||
- **Iterator helpers:** `map`, `filter`, `take`, `drop`, `flatMap`, `toArray`, `forEach`, `reduce`, `some`, `every` and `find` compile on generators, on array, Map and Set iterators, and on the objects the helpers return. Helper objects support `.next()`, `for...of`, spreads, `Array.from` and `yield*`, pull their source lazily, and close it (run its `return()`) at the same points as Node: when `take` reaches its count, when a short-circuiting method finds its answer, when a callback throws, and when the loop consuming the helper exits early. `flatMap` callbacks return arrays or generators. Iterator helpers on string iterators, `Iterator.from`, the `Iterator` global, calling `return()` on a helper object in TypeScript, printing helper objects, and helper calls on untyped iterators in JavaScript are unsupported.
|
||||
- **Iterator helpers:** `map`, `filter`, `take`, `drop`, `flatMap`, `toArray`, `forEach`, `reduce`, `some`, `every` and `find` compile on generators, on array, typed array, string, Map and Set iterators, and on the objects the helpers return. Helper objects support `.next()`, `.return()`, `for...of`, spreads, `Array.from` and `yield*`, pull their source lazily, and close it (run its `return()`) at the same points as Node: when `take` reaches its count, when a short-circuiting method finds its answer, when a callback throws, and when the loop consuming the helper exits early. In TypeScript, `h.return!()` and `h.return?.()` compile when `h` is a helper call or a `const` initialized with one. `flatMap` callbacks return arrays, generators, Sets or Maps; a string result throws Node's TypeError. `Iterator.from` accepts arrays, strings, typed arrays, Sets, Maps and iterator objects such as generators. In JavaScript, untyped array, string, Map and Set iterators answer the same methods. `console.log`, `util.inspect` and `Object.prototype.toString` print iterator objects as Node does: `Object [Iterator Helper] {}`, `Object [Generator] {}`, `Object [Array Iterator] {}`, and Map and Set iterators with the entries they have left. `Iterator.from` over an object with its own `next` method or over an untyped value, the `Iterator` global as a value, and `String()` and template literals of typed iterator objects are unsupported.
|
||||
|
||||
@@ -3,3 +3,7 @@
|
||||
- **TestContext values:** `t.filePath` is the entry file's path when the program was compiled. `t.attempt` is `0`, since a test runs once. `t.passed` is `false` while the body runs and the test's outcome in its `afterEach` and `t.after` hooks. `t.workerId` is `Number(process.env.NODE_TEST_WORKER_ID) || undefined`, as in Node. `t.tags` is `[]`, since the `tags` test option throws `SC2020`; the array is not frozen as Node's is.
|
||||
- **Suite and hook contexts:** a `describe` body may take its `SuiteContext`, which has `name`, `fullName`, `filePath`, `attempt`, `passed` and `diagnostic`. Hooks may take their context, as in Node: the suite's for `before` and `after`, the test's for `beforeEach`, `afterEach`, `t.before` and `t.after`. The `SuiteContext` `signal` and `log` members, and hook `done` callbacks, throw `SC2020`.
|
||||
- **Module-level hooks:** `before` hooks outside any suite run at their `before()` call, and the first top-level test waits for the async ones; `after` hooks outside any suite run after the last top-level test. Both get the root's context, named `<root>`. As in Node, a throwing root `before` fails each top-level test with its error, without running its body, and cancels the tests and suites inside each top-level suite. A throwing root `after` reports the entry file as a failed result, and the exit code is unchanged.
|
||||
- **Timeouts:** when a test's `{ timeout }` elapses, its running subtests are cancelled, as in Node. Subtests do not take their parent's `timeout` as their own, where Node's do, and the subtests of a timed-out test with `{ concurrency }` above 1 run to completion before the test reports. A body that keeps running after its timeout and calls `t.test` again runs that subtest, where Node reports it as not started.
|
||||
- **Thrown values:** a test that throws an object or array that is not an `Error`, such as `throw { code: 42 }`, reports `[object Object]` in the failing section, where Node prints the value as `util.inspect` renders it.
|
||||
- **Test names:** a test without a name, or with an empty one, takes its function's name or `<anonymous>`, as in Node, when the function's name is known at compile time: a function expression, a function declaration, or a `const` bound to an arrow or anonymous function. A name computed at run time, with a function the program received as a parameter, is used as given, so an empty one stays empty where Node uses the function's name. `test(fn)` and `test("", fn)` with such a function throw `SC2020`.
|
||||
- **Current context:** `getTestContext()` returns the context of the test, suite or hook whose code is running, as in Node: `undefined` at module top level, the root's context in module-level hooks, the suite's in a suite body and in the suite's hooks, and the test's in its body, in the code it awaits, and in the timers, immediates, `process.nextTick` and `queueMicrotask` callbacks it schedules. Node returns, for a suite, a `TestContext` that is a different object from the `SuiteContext` passed to the suite body and its `before` and `after` hooks; in a compiled program they are the same object, so comparing them with `===` gives `true`. Completion callbacks of callback-style I/O APIs, such as `fs.readFile`, get `undefined`, as AsyncLocalStorage stores do.
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
String constructors and `Date.parse` accept ECMAScript date-only forms, date-times with an explicit `Z` or numeric offset, and GMT certificate-validity strings from the supported X509 APIs. Other V8-specific forms and offset-less local date-times produce an Invalid Date or `NaN`; `SC6005` reports string literals in those forms.
|
||||
|
||||
Local calendar getters use the operating system's timezone database. Historical results can differ from Node.js when timezone data differs. For valid extreme years outside a platform calendar API's range, scriptc uses a calendar-equivalent year in the 400-year Gregorian cycle to obtain the timezone rule.
|
||||
|
||||
`toString`, `toTimeString`, `String(date)` and template literals print the zone's long name after the GMT offset, as Node does (`GMT-0500 (Central Daylight Time)`). scriptc identifies the zone the way Node does, from `TZ` when it names a zone, else from `/etc/localtime`. Outside Windows, a write to `process.env.TZ` changes the zone for later conversions and getters. The names are the en-US names for the IANA zones; Node prints them in the language of the locale that `LC_ALL`, `LC_MESSAGES` or `LANG` selects, so with a non-English locale the name differs from Node's. A `TZ` value that is a file path or a POSIX rule string (such as `EST5EDT,M3.2.0,M11.1.0`) gives offsets and names that differ from Node's, which ignores those forms. On Windows, the name is the `GMT+hh:mm` form unless `TZ` names a zone. On `wasm32-wasi`, local time is UTC and the name is `Coordinated Universal Time`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
`process.argv[0]` is `"scriptc"`, `process.argv[1]` is the binary's path, and program arguments begin at index 2. In programs that use `node:test`, a leading `--test-update-snapshots` argument is Node's snapshot update flag: it is kept out of `process.argv` and appears in `process.execArgv`, which is otherwise `[]`. `process.version` and `process.versions.node` report the Node.js release the runtime is verified against.
|
||||
|
||||
Uncaught exceptions print `Uncaught <value>` to stderr instead of Node.js's source excerpt and stack-trace block; thrown strings and numbers print their value. Exit codes and output written before the exception are preserved. Runtime errors expose `message` and Node's `code`, but not `errno`, `syscall`, or `path`.
|
||||
Uncaught exceptions print `Uncaught <value>` to stderr instead of Node.js's source excerpt and stack-trace block; thrown strings and numbers print their value. Exit codes and output written before the exception are preserved. Runtime errors expose `message` and Node's `code` as typed members. The other own properties Node gives them, such as a system error's `errno`, `syscall`, and `path`, a child process error's `status`, `stdout`, and `stderr`, or an assertion error's `actual` and `expected`, are readable through an untyped view of the error (for example a cast of a caught value) and appear in `util.inspect` output.
|
||||
|
||||
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`.
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { createGeistdocs } from "@vercel/geistdocs/next";
|
||||
|
||||
const withGeistdocs = createGeistdocs();
|
||||
const legacySlugs = ["quickstart", "coverage", "dependencies", "ffi", "native-objects", "wasm", "platforms", "cli", "how-it-works", "limitations"];
|
||||
|
||||
// The compatibility page fetches its data under a content hash of the
|
||||
// committed artifact, so a deploy never serves a cached older copy, and
|
||||
// checks the row count it expects. Both are derived here at build time.
|
||||
const compatibilityArtifact = readFileSync(new URL("./src/generated/node-v24-compatibility.json", import.meta.url), "utf8");
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
env: {
|
||||
COMPATIBILITY_ARTIFACT_VERSION: createHash("sha256").update(compatibilityArtifact).digest("hex").slice(0, 20),
|
||||
COMPATIBILITY_ROW_COUNT: String(JSON.parse(compatibilityArtifact).rows.length),
|
||||
},
|
||||
agentRules: false,
|
||||
allowedDevOrigins: ["127.0.0.1"],
|
||||
distDir: process.env.NEXT_DIST_DIR || ".next",
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
// @refresh reset
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import compatibilityMeta from "@/generated/node-v24-compatibility-meta.json";
|
||||
|
||||
// Set by next.config.mjs from the committed artifact.
|
||||
const artifactVersion = process.env.COMPATIBILITY_ARTIFACT_VERSION;
|
||||
const rowCount = Number(process.env.COMPATIBILITY_ROW_COUNT);
|
||||
|
||||
type Status =
|
||||
| "supported"
|
||||
@@ -211,14 +214,14 @@ export function NodeCompatibilityMatrix() {
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
fetch(`/compatibility/data?v=${compatibilityMeta.artifactVersion}`, { cache: "no-store", signal: controller.signal })
|
||||
fetch(`/compatibility/data?v=${artifactVersion}`, { cache: "no-store", signal: controller.signal })
|
||||
.then((response) => {
|
||||
if (!response.ok) throw new Error(`compatibility data: ${response.status}`);
|
||||
return response.json() as Promise<Snapshot>;
|
||||
})
|
||||
.then((data) => {
|
||||
if (!Array.isArray(data.rows) || data.rows.length !== compatibilityMeta.rowCount) {
|
||||
throw new Error(`compatibility data is stale or malformed (expected ${compatibilityMeta.rowCount} rows)`);
|
||||
if (!Array.isArray(data.rows) || data.rows.length !== rowCount) {
|
||||
throw new Error(`compatibility data is stale or malformed (expected ${rowCount} rows)`);
|
||||
}
|
||||
setSnapshot(data);
|
||||
})
|
||||
@@ -281,7 +284,7 @@ export function NodeCompatibilityMatrix() {
|
||||
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>;
|
||||
}
|
||||
if (!snapshot) {
|
||||
return <div className="flex h-full items-center justify-center p-6 text-sm text-gray-900">Loading {compatibilityMeta.rowCount.toLocaleString()} Node.js API rows…</div>;
|
||||
return <div className="flex h-full items-center justify-center p-6 text-sm text-gray-900">Loading {rowCount.toLocaleString()} Node.js API rows…</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"schemaVersion": 3,
|
||||
"nodeVersion": "24.22.0",
|
||||
"nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93",
|
||||
"artifactVersion": "ef4244aca5b727e3683c",
|
||||
"rowCount": 3880
|
||||
}
|
||||
@@ -689,9 +689,9 @@
|
||||
"apiEntries": 117,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 64,
|
||||
"partial": 65,
|
||||
"refused": 0,
|
||||
"not-implemented": 53,
|
||||
"not-implemented": 52,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -67344,9 +67344,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+16
@@ -300,6 +300,22 @@
|
||||
{
|
||||
"text": "Memory",
|
||||
"id": "memory"
|
||||
},
|
||||
{
|
||||
"text": "ThreadSlots",
|
||||
"id": "threadslots"
|
||||
},
|
||||
{
|
||||
"text": "Slot methods",
|
||||
"id": "slot-methods"
|
||||
},
|
||||
{
|
||||
"text": "Keys and values",
|
||||
"id": "keys-and-values"
|
||||
},
|
||||
{
|
||||
"text": "Slot cost",
|
||||
"id": "slot-cost"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -151,8 +151,11 @@ test("compatibility page and data endpoints keep their existing contracts", asyn
|
||||
const response = await get("/compatibility/data");
|
||||
assert.equal(response.status, 200);
|
||||
const dataset = await response.json();
|
||||
const metadata = JSON.parse(await readFile(new URL("../src/generated/node-v24-compatibility-meta.json", import.meta.url), "utf8"));
|
||||
assert.equal(dataset.rows.length, metadata.rowCount);
|
||||
const artifact = JSON.parse(await readFile(new URL("../src/generated/node-v24-compatibility.json", import.meta.url), "utf8"));
|
||||
assert.equal(dataset.rows.length, artifact.rows.length);
|
||||
// The row count next.config.mjs derives reaches the page (React separates
|
||||
// adjacent text nodes with empty comments).
|
||||
assert.ok(html.replaceAll("<!-- -->", "").includes(`Loading ${artifact.rows.length.toLocaleString()} Node.js API rows`));
|
||||
assert.equal((await get("/node-compatibility")).status, 308);
|
||||
assert.equal((await get("/node-compatibility/data")).status, 308);
|
||||
});
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"tierItemCount": 3456,
|
||||
"tiers": {
|
||||
"static": {
|
||||
"audit-partial": 1175,
|
||||
"verify-gap": 2153,
|
||||
"audit-partial": 1176,
|
||||
"verify-gap": 2152,
|
||||
"replace-refusal": 41,
|
||||
"classify": 2,
|
||||
"implement": 85
|
||||
@@ -880,7 +880,8 @@
|
||||
"confidence": "high",
|
||||
"source": "compiler-dedicated:AsyncLocalStorage",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -932,7 +933,8 @@
|
||||
"confidence": "high",
|
||||
"source": "compiler-dedicated:asyncLocalStorage.disable",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -959,7 +961,8 @@
|
||||
"confidence": "high",
|
||||
"source": "compiler-dedicated:asyncLocalStorage.getStore",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -986,7 +989,8 @@
|
||||
"confidence": "high",
|
||||
"source": "compiler-dedicated:asyncLocalStorage.enterWith",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1013,7 +1017,8 @@
|
||||
"confidence": "high",
|
||||
"source": "compiler-dedicated:asyncLocalStorage.run",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1040,7 +1045,8 @@
|
||||
"confidence": "high",
|
||||
"source": "compiler-dedicated:asyncLocalStorage.exit",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1771,7 +1777,8 @@
|
||||
"confidence": "high",
|
||||
"source": "compiler-dedicated:AsyncLocalStorage",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -68887,12 +68894,16 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:test",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:test.getTestContext",
|
||||
"tests": [
|
||||
"tests/fixtures/node-test/cases/get-test-context.ts",
|
||||
"tests/fixtures/node-test/cases/get-test-context-cancelled.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -746,9 +746,9 @@
|
||||
"apiEntries": 117,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 64,
|
||||
"partial": 65,
|
||||
"refused": 0,
|
||||
"not-implemented": 53,
|
||||
"not-implemented": 52,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -1953,7 +1953,8 @@
|
||||
"status": "partial",
|
||||
"evidence": "derived:descendants",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "chapter"
|
||||
@@ -2002,7 +2003,8 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-dedicated:AsyncLocalStorage",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
@@ -2074,7 +2076,8 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-dedicated:asyncLocalStorage.disable",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
@@ -2100,7 +2103,8 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-dedicated:asyncLocalStorage.getStore",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
@@ -2126,7 +2130,8 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-dedicated:asyncLocalStorage.enterWith",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
@@ -2152,7 +2157,8 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-dedicated:asyncLocalStorage.run",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
@@ -2178,7 +2184,8 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-dedicated:asyncLocalStorage.exit",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
@@ -2481,6 +2488,7 @@
|
||||
"evidence": "derived:descendants",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
},
|
||||
@@ -3042,7 +3050,8 @@
|
||||
"status": "partial",
|
||||
"evidence": "compiler-dedicated:AsyncLocalStorage",
|
||||
"tests": [
|
||||
"tests/corpus/2213-async-local-storage.cjs"
|
||||
"tests/corpus/2213-async-local-storage.cjs",
|
||||
"tests/corpus/5815-als-callback-context.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
@@ -91065,6 +91074,8 @@
|
||||
"tests/fixtures/node-test/cases/ctx-hooks.ts",
|
||||
"tests/fixtures/node-test/cases/ctx-members.ts",
|
||||
"tests/fixtures/node-test/cases/ctx-plan.ts",
|
||||
"tests/fixtures/node-test/cases/get-test-context-cancelled.ts",
|
||||
"tests/fixtures/node-test/cases/get-test-context.ts",
|
||||
"tests/fixtures/node-test/cases/mock-fn.ts",
|
||||
"tests/fixtures/node-test/cases/mock-method.ts",
|
||||
"tests/fixtures/node-test/cases/mock-timers-date.ts",
|
||||
@@ -94813,8 +94824,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:test"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:test.getTestContext",
|
||||
"tests": [
|
||||
"tests/fixtures/node-test/cases/get-test-context.ts",
|
||||
"tests/fixtures/node-test/cases/get-test-context-cancelled.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
|
||||
@@ -24,7 +24,6 @@ const root = fileURLToPath(new URL("../../../", import.meta.url));
|
||||
const internalOutputPath = `${packageRoot}generated/node-v24-internal.json`;
|
||||
const backlogOutputPath = `${packageRoot}generated/node-v24-backlog.json`;
|
||||
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 pinPath = `${packageRoot}node-v24.json`;
|
||||
@@ -1009,17 +1008,6 @@ function publicSnapshot(snapshot) {
|
||||
};
|
||||
}
|
||||
|
||||
function publicMetadata(publicRendered, snapshot) {
|
||||
const matrix = projectPublicMatrix(snapshot);
|
||||
return {
|
||||
schemaVersion: 3,
|
||||
nodeVersion: snapshot.nodeVersion,
|
||||
nodeCommit: snapshot.nodeCommit,
|
||||
artifactVersion: sha(publicRendered).slice(0, 20),
|
||||
rowCount: matrix.rows.length,
|
||||
};
|
||||
}
|
||||
|
||||
function checkLocal() {
|
||||
const snapshot = JSON.parse(readFileSync(internalOutputPath, "utf8"));
|
||||
if (snapshot.nodeVersion !== pin.version || snapshot.nodeCommit !== pin.commit) {
|
||||
@@ -1071,11 +1059,8 @@ function checkLocal() {
|
||||
if (readFileSync(backlogOutputPath, "utf8") !== render(buildBacklog(snapshot))) {
|
||||
throw new Error("the Node compatibility backlog is stale; run 'pnpm node-compat'");
|
||||
}
|
||||
const publicRendered = render(publicSnapshot(snapshot));
|
||||
if (
|
||||
readFileSync(publicMetaOutputPath, "utf8") !== render(publicMetadata(publicRendered, snapshot))
|
||||
) {
|
||||
throw new Error("the docs compatibility metadata is stale; run 'pnpm node-compat'");
|
||||
if (readFileSync(publicOutputPath, "utf8") !== render(publicSnapshot(snapshot))) {
|
||||
throw new Error("the docs compatibility artifact is stale; run 'pnpm node-compat'");
|
||||
}
|
||||
for (const row of snapshot.rows) {
|
||||
for (const key of ["static"]) {
|
||||
@@ -1141,7 +1126,6 @@ async function main() {
|
||||
const internalRendered = render(snapshot);
|
||||
const backlogRendered = render(buildBacklog(snapshot));
|
||||
const publicRendered = render(publicSnapshot(snapshot));
|
||||
const publicMetaRendered = render(publicMetadata(publicRendered, snapshot));
|
||||
|
||||
if (args.has("--check")) {
|
||||
if (readFileSync(internalOutputPath, "utf8") !== internalRendered)
|
||||
@@ -1150,8 +1134,6 @@ async function main() {
|
||||
throw new Error("Node compatibility backlog is stale; run 'pnpm node-compat'");
|
||||
if (readFileSync(publicOutputPath, "utf8") !== publicRendered)
|
||||
throw new Error("public Node compatibility snapshot is stale; run 'pnpm node-compat'");
|
||||
if (readFileSync(publicMetaOutputPath, "utf8") !== publicMetaRendered)
|
||||
throw new Error("public Node compatibility metadata is stale; run 'pnpm node-compat'");
|
||||
console.log(
|
||||
`Node compatibility snapshot matches ${pin.tag} (${snapshot.rows.length} API rows, commit ${pin.commit.slice(0, 12)})`,
|
||||
);
|
||||
@@ -1160,7 +1142,6 @@ async function main() {
|
||||
writeFileSync(internalOutputPath, internalRendered);
|
||||
writeFileSync(backlogOutputPath, backlogRendered);
|
||||
writeFileSync(publicOutputPath, publicRendered);
|
||||
writeFileSync(publicMetaOutputPath, publicMetaRendered);
|
||||
console.log(
|
||||
`wrote compatibility ledger, backlog, and public artifact (${snapshot.rows.length} source rows, ${projectPublicMatrix(snapshot).rows.length} public matrix rows, ${pin.tag})`,
|
||||
);
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"syntax.for-await-readable-streams": ["tests/corpus/2861-readable-iterator-close.ts", "tests/corpus/2862-web-readable-async-iteration.ts"],
|
||||
"syntax.for-await-class-async-iterators": ["tests/corpus/2863-class-async-iterator.ts"],
|
||||
"node-builtin.assert": ["tests/corpus/1600-assert-passing.ts", "tests/corpus/1609-assert-async.ts"],
|
||||
"node-builtin.async_hooks": ["tests/corpus/2213-async-local-storage.cjs"],
|
||||
"node-builtin.async_hooks": ["tests/corpus/2213-async-local-storage.cjs", "tests/corpus/5815-als-callback-context.ts"],
|
||||
"node-builtin.buffer": ["tests/corpus/1402-buffer-encodings.ts", "tests/corpus/1660-buffer-read-write-num.ts", "tests/corpus/2902-bigint-bytes.ts", "tests/corpus/2904-bigint-errors.ts"],
|
||||
"node-builtin.child_process": ["tests/corpus/1361-spawn-events.ts", "tests/corpus/1565-spawn-pipe-streams.ts", "tests/corpus/2911-execfile-callback.ts", "tests/corpus/2939-child-shell-text.ts", "tests/corpus/2946-child-fork-ipc/main.ts", "tests/corpus/3127-spawn-runtime-options.ts", "tests/corpus/3130-child-generic-streams.ts", "tests/corpus/3131-child-rest-listeners.js", "tests/corpus/3132-child-generic-stream-completion.ts"],
|
||||
"node-builtin.cluster": ["tests/corpus/957-builtins-namespace.ts"],
|
||||
@@ -372,6 +372,7 @@
|
||||
{ "chapter": "test", "parent": "Class: `MockTimers`", "signatures": ["`timers[Symbol.dispose]()`"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-timers-date.ts", "tests/harness/node-test.test.ts"] },
|
||||
{ "chapter": "test", "parent": "Class: `MockFunctionContext`", "symbols": ["calls", "ctx.callCount", "ctx.mockImplementation", "ctx.mockImplementationOnce", "ctx.resetCalls", "ctx.restore"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-fn.ts", "tests/fixtures/node-test/cases/mock-method.ts", "tests/harness/node-test.test.ts"] },
|
||||
{ "chapter": "test", "parent": "Class: `TestContext`", "symbols": ["context.before", "context.after", "context.beforeEach", "context.afterEach", "context.plan", "context.fullName"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/ctx-hooks.ts", "tests/fixtures/node-test/cases/ctx-plan.ts", "tests/harness/node-test.test.ts"] },
|
||||
{ "chapter": "test", "symbols": ["getTestContext"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/get-test-context.ts", "tests/fixtures/node-test/cases/get-test-context-cancelled.ts", "tests/harness/node-test.test.ts"] },
|
||||
{ "chapter": "test", "symbols": ["describe.skip", "describe.todo", "describe.only", "suite.skip", "suite.todo", "suite.only", "it.skip", "it.todo", "it.only"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/suite-variants.ts", "tests/fixtures/node-test/cases/only-nested.ts", "tests/fixtures/node-test/cases/todo-suites.ts", "tests/harness/node-test.test.ts"] },
|
||||
{ "chapter": "util", "signatures": ["`util.promisify(original)`"], "status": "partial", "evidence": ["tests/corpus/1473-promisify-execfile.ts", "tests/corpus/1573-promisify-execfile-env-spread.ts", "tests/corpus/2912-promisify-fs-readfile.ts", "tests/corpus/5625-promisify-exec.ts", "tests/corpus/5626-promisify-forms.ts", "tests/fixtures/node-types/promisify-fs.ts", "tests/corpus/5628-promisify-timers.ts", "tests/fixtures/node-types/promisify-timers.ts", "tests/corpus/5629-promisify-stream.ts", "tests/corpus/5630-promisify-zlib.ts", "tests/fixtures/node-types/promisify-zlib.ts", "tests/corpus/5631-promisify-crypto.ts", "tests/fixtures/node-types/promisify-crypto.ts", "tests/corpus/5632-promisify-dns.ts", "tests/fixtures/node-types/promisify-dns.ts", "tests/harness/project-config.test.ts"] },
|
||||
{ "chapter": "typescript", "symbols": ["type"], "status": "supported", "evidence": ["tests/corpus/2702-type-import-link-static/main.ts", "tests/corpus/2703-type-import-link-static-reexport/main.ts", "tests/corpus/2704-type-import-link-static-success/main.ts", "tests/corpus/2707-type-import-link-static-default-reexport/main.ts", "tests/harness/errors.test.ts"] },
|
||||
@@ -401,7 +402,7 @@
|
||||
{ "symbols": ["crypto.hkdf", "crypto.hkdfSync", "crypto.scrypt", "crypto.scryptSync"], "status": "partial", "evidence": ["tests/corpus/node24-crypto-kdf.ts", "tests/corpus/node24-crypto-kdf-errors.ts", "tests/corpus/node24-crypto-kdf-options.ts", "tests/corpus/5438-crypto-kdf-digests.ts"] },
|
||||
{ "symbols": ["EventEmitter", "emitter.on", "emitter.addListener", "emitter.once", "emitter.prependListener", "emitter.prependOnceListener", "emitter.off", "emitter.removeListener", "emitter.removeAllListeners", "emitter.emit", "emitter.listenerCount", "emitter.listeners", "emitter.rawListeners", "emitter.eventNames", "emitter.setMaxListeners", "emitter.getMaxListeners", "events.once"], "status": "partial", "evidence": ["tests/corpus/1677-emitter-listeners.ts", "tests/corpus/2620-ee-override-once-order.ts"] },
|
||||
{ "symbols": ["events.getEventListeners", "events.getMaxListeners", "events.listenerCount", "events.setMaxListeners", "events.defaultMaxListeners"], "status": "partial", "evidence": ["tests/corpus/5885-events-max-listener-helpers.ts", "tests/corpus/5846-events-esm-default-max.ts", "tests/corpus/5847-events-cjs-default-max.cjs"] },
|
||||
{ "symbols": ["AsyncLocalStorage", "asyncLocalStorage.run", "asyncLocalStorage.exit", "asyncLocalStorage.getStore", "asyncLocalStorage.enterWith", "asyncLocalStorage.disable"], "status": "partial", "evidence": ["tests/corpus/2213-async-local-storage.cjs"] },
|
||||
{ "symbols": ["AsyncLocalStorage", "asyncLocalStorage.run", "asyncLocalStorage.exit", "asyncLocalStorage.getStore", "asyncLocalStorage.enterWith", "asyncLocalStorage.disable"], "status": "partial", "evidence": ["tests/corpus/2213-async-local-storage.cjs", "tests/corpus/5815-als-callback-context.ts"] },
|
||||
{ "symbols": ["StringDecoder", "stringDecoder.write", "stringDecoder.end"], "status": "partial", "evidence": ["tests/corpus/1662-string-decoder-encodings.ts", "tests/corpus/node24-string-decoder.ts", "tests/corpus/5882-string-decoder-runtime-encoding.ts"] },
|
||||
{ "symbols": ["setImmediate", "setInterval", "setTimeout", "clearImmediate", "clearInterval", "clearTimeout", "timersPromises.setTimeout", "timersPromises.setImmediate", "timersPromises.setInterval", "immediate.hasRef", "immediate.ref", "immediate.unref", "timeout.hasRef", "timeout.ref", "timeout.unref", "timeout.refresh"], "status": "partial", "evidence": ["tests/corpus/1804-timers-module.ts", "tests/corpus/1805-timer-callback-args.ts", "tests/corpus/2093-timers-promises.ts", "tests/corpus/2852-timers-promises-interval.ts", "tests/corpus/5830-timers-promises-signal.ts"] },
|
||||
{ "symbols": ["timersPromises.scheduler.wait", "timersPromises.scheduler.yield"], "status": "partial", "evidence": ["tests/corpus/5839-timers-promises-scheduler.ts", "tests/corpus/5889-timers-scheduler-signal.ts"] },
|
||||
|
||||
+42
-15
@@ -3510,8 +3510,10 @@ declare module "node:test" {
|
||||
readonly tags: readonly string[];
|
||||
readonly assert: TestContextAssert;
|
||||
readonly mock: MockTracker;
|
||||
test(name: string, fn?: TestFn): Promise<void>;
|
||||
test(name: string, options: TestOptions, fn?: TestFn): Promise<void>;
|
||||
test(name?: string, fn?: TestFn): Promise<void>;
|
||||
test(name?: string, options?: TestOptions, fn?: TestFn): Promise<void>;
|
||||
test(options?: TestOptions, fn?: TestFn): Promise<void>;
|
||||
test(fn?: TestFn): Promise<void>;
|
||||
skip(message?: string): void;
|
||||
todo(message?: string): void;
|
||||
diagnostic(message: string): void;
|
||||
@@ -3541,23 +3543,44 @@ declare module "node:test" {
|
||||
match(value: string, regExp: RegExp, message?: string): void;
|
||||
doesNotMatch(value: string, regExp: RegExp, message?: string): void;
|
||||
}
|
||||
function test(name: string, fn?: TestFn): Promise<void>;
|
||||
function test(name: string, options: TestOptions, fn?: TestFn): Promise<void>;
|
||||
/* The name, options and function are each optional, as in Node: a test
|
||||
* without a name takes the function's name, or "<anonymous>". */
|
||||
function test(name?: string, fn?: TestFn): Promise<void>;
|
||||
function test(name?: string, options?: TestOptions, fn?: TestFn): Promise<void>;
|
||||
function test(options?: TestOptions, fn?: TestFn): Promise<void>;
|
||||
function test(fn?: TestFn): Promise<void>;
|
||||
namespace test {
|
||||
function skip(name: string, fn?: TestFn): Promise<void>;
|
||||
function skip(name: string, options: TestOptions, fn?: TestFn): Promise<void>;
|
||||
function todo(name: string, fn?: TestFn): Promise<void>;
|
||||
function todo(name: string, options: TestOptions, fn?: TestFn): Promise<void>;
|
||||
function only(name: string, fn?: TestFn): Promise<void>;
|
||||
function only(name: string, options: TestOptions, fn?: TestFn): Promise<void>;
|
||||
function skip(name?: string, fn?: TestFn): Promise<void>;
|
||||
function skip(name?: string, options?: TestOptions, fn?: TestFn): Promise<void>;
|
||||
function skip(options?: TestOptions, fn?: TestFn): Promise<void>;
|
||||
function skip(fn?: TestFn): Promise<void>;
|
||||
function todo(name?: string, fn?: TestFn): Promise<void>;
|
||||
function todo(name?: string, options?: TestOptions, fn?: TestFn): Promise<void>;
|
||||
function todo(options?: TestOptions, fn?: TestFn): Promise<void>;
|
||||
function todo(fn?: TestFn): Promise<void>;
|
||||
function only(name?: string, fn?: TestFn): Promise<void>;
|
||||
function only(name?: string, options?: TestOptions, fn?: TestFn): Promise<void>;
|
||||
function only(options?: TestOptions, fn?: TestFn): Promise<void>;
|
||||
function only(fn?: TestFn): Promise<void>;
|
||||
}
|
||||
const it: typeof test;
|
||||
function describe(name: string, fn?: SuiteFn): void;
|
||||
function describe(name: string, options: TestOptions, fn?: SuiteFn): void;
|
||||
function describe(name?: string, fn?: SuiteFn): void;
|
||||
function describe(name?: string, options?: TestOptions, fn?: SuiteFn): void;
|
||||
function describe(options?: TestOptions, fn?: SuiteFn): void;
|
||||
function describe(fn?: SuiteFn): void;
|
||||
namespace describe {
|
||||
function skip(name: string, fn?: SuiteFn): void;
|
||||
function todo(name: string, fn?: SuiteFn): void;
|
||||
function only(name: string, fn?: SuiteFn): void;
|
||||
function skip(name?: string, fn?: SuiteFn): void;
|
||||
function skip(name?: string, options?: TestOptions, fn?: SuiteFn): void;
|
||||
function skip(options?: TestOptions, fn?: SuiteFn): void;
|
||||
function skip(fn?: SuiteFn): void;
|
||||
function todo(name?: string, fn?: SuiteFn): void;
|
||||
function todo(name?: string, options?: TestOptions, fn?: SuiteFn): void;
|
||||
function todo(options?: TestOptions, fn?: SuiteFn): void;
|
||||
function todo(fn?: SuiteFn): void;
|
||||
function only(name?: string, fn?: SuiteFn): void;
|
||||
function only(name?: string, options?: TestOptions, fn?: SuiteFn): void;
|
||||
function only(options?: TestOptions, fn?: SuiteFn): void;
|
||||
function only(fn?: SuiteFn): void;
|
||||
}
|
||||
const suite: typeof describe;
|
||||
function before(fn: HookFn): void;
|
||||
@@ -3632,6 +3655,9 @@ declare module "node:test" {
|
||||
[Symbol.dispose](): void;
|
||||
}
|
||||
const mock: MockTracker;
|
||||
/* The context of the test, suite or hook running in the current async
|
||||
* context; undefined outside one. */
|
||||
function getTestContext(): TestContext | SuiteContext | undefined;
|
||||
/* Declared for typechecking only — every use fences (no lowering).
|
||||
* `assert` is the module-level custom-assertion registry (v24's
|
||||
* `const { assert } = require('node:test')`), not TestContext.assert. */
|
||||
@@ -3649,6 +3675,7 @@ declare module "node:test" {
|
||||
afterEach,
|
||||
mock,
|
||||
assert,
|
||||
getTestContext,
|
||||
run,
|
||||
snapshot,
|
||||
TestContext,
|
||||
|
||||
@@ -97,16 +97,20 @@ export function computeCycleMutability(
|
||||
// can store; a commit rewrites every field, reference fields possibly
|
||||
// with fresh conversions). Without any such store in the program,
|
||||
// crossing into checked-dynamic code writes nothing. FFI calls and
|
||||
// intrinsics other than publish (which only marks objects immortal) and
|
||||
// the intern-table operations (which store published values) stay
|
||||
// conservative.
|
||||
// intrinsics other than publish (which only marks objects immortal), the
|
||||
// intern-table operations (which store published values) and ThreadSlots
|
||||
// reads stay conservative.
|
||||
const dynStores = dynamicFieldStores(mod);
|
||||
const dynamicStores = dynStores.all || dynStores.names.size > 0;
|
||||
result.emptyPropertyBags = !dynamicStores;
|
||||
const boundaryWrites = (e: IrExpr): boolean =>
|
||||
dynamicStores ||
|
||||
e.kind === "ffiCall" ||
|
||||
(e.kind === "intrinsic" && e.name !== "threads.publish" && !e.name.startsWith("itable."));
|
||||
(e.kind === "intrinsic" &&
|
||||
e.name !== "threads.publish" &&
|
||||
!e.name.startsWith("itable.") &&
|
||||
e.name !== "slots.get" &&
|
||||
e.name !== "slots.has");
|
||||
|
||||
// Types that can become checked-dynamic values: mark their whole closure.
|
||||
const exposedTypes = new Set<string>();
|
||||
|
||||
@@ -3,11 +3,13 @@ import { type IrModule } from "../ir/ir.js";
|
||||
import { moduleRuntimeFeatures } from "../ir/runtime-features.js";
|
||||
import { hasForeignFfiCallback } from "./ffi-callbacks.js";
|
||||
import type { NativeLinkFeatures } from "./native-link-info.js";
|
||||
import { youngNurseryRequested } from "./young-nursery.js";
|
||||
|
||||
export function executableLinkFeatures(mod: IrModule): NativeLinkFeatures {
|
||||
const features = moduleRuntimeFeatures(mod);
|
||||
return {
|
||||
...(mod.workers ? { workers: true } : {}),
|
||||
...(youngNurseryRequested() && mod.lib === undefined ? { youngNursery: true } : {}),
|
||||
regex: features.regex,
|
||||
copying: features.copying,
|
||||
textDecoderLegacy: features.legacyTextDecoder,
|
||||
|
||||
@@ -42,5 +42,10 @@ export function borrowsJsonInputs(fn: string): boolean {
|
||||
* loop. Runtime steps borrow that state but may invoke indexed getters, so
|
||||
* this permits stable-local borrowing without preserving reference edges. */
|
||||
export function borrowsIteratorInputs(fn: string): boolean {
|
||||
return fn === "dyn.iteratorCanStep" || fn === "dyn.iteratorStep" || fn === "dyn.iteratorStepDone";
|
||||
return (
|
||||
fn === "dyn.iteratorCanStep" ||
|
||||
fn === "dyn.iteratorStep" ||
|
||||
fn === "dyn.iteratorStepDone" ||
|
||||
fn === "dyn.collectionIteratorIndex"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { F64, STRING, VOID, type IrModule } from "../../ir/ir.js";
|
||||
import { DYN, F64, STRING, VOID, type IrModule } from "../../ir/ir.js";
|
||||
import { buildClassGraph, classMembershipIntervals, emitClassMembershipHelper } from "./classes.js";
|
||||
import { emitLlvmModule } from "./emitter.js";
|
||||
import {
|
||||
@@ -316,3 +316,52 @@ test("unconstructed narrowing layouts may omit abstract slots while live classes
|
||||
});
|
||||
expect(() => emitLlvmModule(mod)).toThrow("no implementation of run for Dormant");
|
||||
});
|
||||
|
||||
test("the young-in nursery flag adds the nursery entry to traced constructors only", () => {
|
||||
const linked = { kind: "object" as const, className: "Linked" };
|
||||
const mod: IrModule = {
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [{ name: "main", params: [], returnType: VOID, locals: [], body: [], loc }],
|
||||
classes: [
|
||||
{ name: "Scalar", fields: [{ name: "value", type: F64 }], methods: [], loc },
|
||||
// A dyn field can reach anything: the class carries a collector header.
|
||||
{
|
||||
name: "Linked",
|
||||
fields: [
|
||||
{ name: "next", type: linked },
|
||||
{ name: "hook", type: DYN },
|
||||
],
|
||||
methods: [],
|
||||
loc,
|
||||
},
|
||||
],
|
||||
};
|
||||
const helper = (llvm: string, name: string): string => {
|
||||
const start = llvm.indexOf(`define internal ptr @${name}(`);
|
||||
expect(start).toBeGreaterThanOrEqual(0);
|
||||
return llvm.slice(start, llvm.indexOf("\n}\n", start));
|
||||
};
|
||||
const saved = process.env["SCRIPTC_YOUNG_NURSERY"];
|
||||
try {
|
||||
delete process.env["SCRIPTC_YOUNG_NURSERY"];
|
||||
const off = emitLlvmModule(mod, { pointerBits: 64 });
|
||||
expect(off).not.toContain("scr_yin");
|
||||
|
||||
process.env["SCRIPTC_YOUNG_NURSERY"] = "1";
|
||||
const on = emitLlvmModule(mod, { pointerBits: 64 });
|
||||
const linkedNew = helper(on, mangleClassNew("Linked"));
|
||||
expect(linkedNew).toContain("@scr_yin_skip");
|
||||
expect(linkedNew).toContain("call void @scr_yin_enter_slow(ptr %o)");
|
||||
expect(linkedNew).toContain("store i8 3, ptr %ye.genp");
|
||||
// An acyclic class has no collector header and never enters.
|
||||
expect(helper(on, mangleClassNew("Scalar"))).not.toContain("scr_yin");
|
||||
// Targets without the inline allocator call the whole entry.
|
||||
const narrow = emitLlvmModule(mod, { pointerBits: 32 });
|
||||
expect(helper(narrow, mangleClassNew("Linked"))).toContain("call void @scr_yin_enter(ptr %o)");
|
||||
} finally {
|
||||
if (saved === undefined) delete process.env["SCRIPTC_YOUNG_NURSERY"];
|
||||
else process.env["SCRIPTC_YOUNG_NURSERY"] = saved;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
RUNTIME_EVENT_TARGET_CLASS,
|
||||
RUNTIME_ERROR_CLASSES,
|
||||
RUNTIME_STREAM_CLASSES,
|
||||
classValueConstructor,
|
||||
} from "../../ir/ir.js";
|
||||
import { streamRooted, undefinedArmTag } from "../../ir/analysis.js";
|
||||
import {
|
||||
@@ -51,6 +52,7 @@ import {
|
||||
retainBody,
|
||||
traceAdapter,
|
||||
type ShapeHost,
|
||||
youngEntryLines,
|
||||
} from "./shapes.js";
|
||||
|
||||
/** One virtual method slot of a hierarchy: the ROOT-MOST declaring class
|
||||
@@ -75,6 +77,10 @@ export class LlClassMeta {
|
||||
membership: { pre: number; post: number }[] = [];
|
||||
/** Type-only references retain layouts without retaining construction. */
|
||||
constructorRetained = false;
|
||||
/** The hierarchy holds ThreadSlots keys (IrModule.threadSlotKeys): every
|
||||
* member's struct carries the i32 slot word before its fields
|
||||
* (classSlotWordIndex). */
|
||||
threadSlot = false;
|
||||
|
||||
constructor(readonly def: IrClassDef) {
|
||||
this.root = this;
|
||||
@@ -116,6 +122,12 @@ export function buildClassGraph(
|
||||
if (meta.base === null) number(meta, meta);
|
||||
meta.hierarchy = meta.base !== null || meta.children.length > 0;
|
||||
}
|
||||
const slotRoots = new Set<LlClassMeta>();
|
||||
for (const key of mod.threadSlotKeys ?? []) {
|
||||
const meta = metaMap.get(key);
|
||||
if (meta !== undefined) slotRoots.add(meta.root);
|
||||
}
|
||||
for (const meta of metaMap.values()) meta.threadSlot = slotRoots.has(meta.root);
|
||||
const declares = (m: LlClassMeta, method: string): boolean =>
|
||||
m.def.methods?.includes(method) ?? false;
|
||||
const declaredBelow = (m: LlClassMeta, method: string): boolean =>
|
||||
@@ -323,16 +335,31 @@ function runtimePrefixOf(meta: LlClassMeta): RuntimePrefix | null {
|
||||
return RUNTIME_PREFIXES.get(meta.root.def.name) ?? null;
|
||||
}
|
||||
|
||||
/** The GEP index where a class's own fields start: rc at 0, the vtable
|
||||
* word at 1 on hierarchy members, then a runtime prefix (state + display
|
||||
* name) on classes rooted at a runtime base, then the stream-state slot
|
||||
* on stream-rooted ones. */
|
||||
function fieldBase(meta: LlClassMeta): number {
|
||||
/** The GEP index after a class's header words: rc at 0, the vtable word
|
||||
* at 1 on hierarchy members, then a runtime prefix (state + display name)
|
||||
* on classes rooted at a runtime base, then the stream-state slot on
|
||||
* stream-rooted ones. */
|
||||
function headerEnd(meta: LlClassMeta): number {
|
||||
if (!meta.hierarchy) return 1;
|
||||
if (streamRooted(meta)) return 5;
|
||||
return runtimePrefixOf(meta) ? 4 : 2;
|
||||
}
|
||||
|
||||
/** The GEP index of the ThreadSlots slot word (an i32) in a class whose
|
||||
* hierarchy holds ThreadSlots keys: right after the header words, so every
|
||||
* member of the hierarchy has it at the same offset. */
|
||||
export function classSlotWordIndex(meta: LlClassMeta): number {
|
||||
if (!meta.threadSlot)
|
||||
throw new InternalCompilerError(`llvm emitter bug: class ${meta.def.name} has no slot word`);
|
||||
return headerEnd(meta);
|
||||
}
|
||||
|
||||
/** The GEP index where a class's own fields start: after the header words
|
||||
* and the slot word. */
|
||||
export function classFieldBase(meta: LlClassMeta): number {
|
||||
return headerEnd(meta) + (meta.threadSlot ? 1 : 0);
|
||||
}
|
||||
|
||||
/** Append the lexical environment after inherited fields to preserve base layouts. */
|
||||
export function classEnvironmentIndex(meta: LlClassMeta): number {
|
||||
return classFieldIndex(meta, `%classEnvironment:${meta.def.name}`).index;
|
||||
@@ -346,7 +373,28 @@ export function classFieldIndex(meta: LlClassMeta, field: string): { index: numb
|
||||
throw new InternalCompilerError(
|
||||
`llvm emitter bug: unknown field ${field} on class ${meta.def.name}`,
|
||||
);
|
||||
return { index: fieldBase(meta) + idx, type: meta.def.fields[idx]!.type };
|
||||
return { index: classFieldBase(meta) + idx, type: meta.def.fields[idx]!.type };
|
||||
}
|
||||
|
||||
/** Lines that release the ThreadSlots values of the object `%o` before it
|
||||
* is freed: its slot word is 0 unless a value was ever set for it. */
|
||||
function slotForgetLines(
|
||||
host: ClassHost,
|
||||
struct: string,
|
||||
meta: LlClassMeta,
|
||||
tag: string,
|
||||
): string[] {
|
||||
host.declare(`declare void @scr_slots_forget(i32)`);
|
||||
return [
|
||||
` %${tag}p = getelementptr inbounds %${struct}, ptr %o, i64 0, i32 ${classSlotWordIndex(meta)}`,
|
||||
` %${tag} = load i32, ptr %${tag}p, align 4`,
|
||||
` %${tag}.set = icmp ne i32 %${tag}, 0`,
|
||||
` br i1 %${tag}.set, label %${tag}.forget, label %${tag}.done`,
|
||||
`${tag}.forget: ; release its ThreadSlots values`,
|
||||
` call void @scr_slots_forget(i32 %${tag})`,
|
||||
` br label %${tag}.done`,
|
||||
`${tag}.done:`,
|
||||
];
|
||||
}
|
||||
|
||||
/** What class emission needs beyond ShapeHost: interned unit instances for
|
||||
@@ -483,7 +531,7 @@ export function emitClassShapes(
|
||||
? ["ptr", "ptr", "ptr"]
|
||||
: ["ptr"]
|
||||
: [];
|
||||
const members = [...prefix, ...fieldTys];
|
||||
const members = [...prefix, ...(meta.threadSlot ? ["i32"] : []), ...fieldTys];
|
||||
typeDefs.push(
|
||||
`%${mangleClassStruct(cls.name)} = type { ${host.sizeType}${members.length ? ", " + members.join(", ") : ""} } ` +
|
||||
`; class ${cls.name}${meta.hierarchy ? " (vt at 1)" : ""}${streamRooted(meta) ? " (ScrStream prefix at 2)" : runtimePrefix ? ` (${runtimePrefix.struct} prefix at 2)` : ""} { ${cls.fields.map((f) => llvmCommentText(f.name)).join("; ")} }`,
|
||||
@@ -569,7 +617,7 @@ export function emitClassShapes(
|
||||
const traced = host.tracedShapes.has(`object:${cls.name}`);
|
||||
const runtimePrefix = runtimePrefixOf(meta);
|
||||
const isStreamRooted = streamRooted(meta);
|
||||
const fieldIndex = (i: number): number => fieldBase(meta) + i;
|
||||
const fieldIndex = (i: number): number => classFieldBase(meta) + i;
|
||||
// Teardown, trace and gcFree see each slot's storage type: a nullable
|
||||
// class field holds the instance pointer (NULL for the unit arm), and
|
||||
// every class retain/release/visit entry point is NULL-tolerant.
|
||||
@@ -640,6 +688,7 @@ export function emitClassShapes(
|
||||
});
|
||||
if (runtimePrefix) lines.push(...regCall("td", runtimePrefix.drop, ""));
|
||||
if (isStreamRooted) lines.push(...stCall("tds", "scr_stream_st_release", ""));
|
||||
if (meta.threadSlot) lines.push(...slotForgetLines(host, struct, meta, "tdw"));
|
||||
lines.push(...emitObjectFree(host, traced));
|
||||
};
|
||||
|
||||
@@ -803,6 +852,11 @@ export function emitClassShapes(
|
||||
);
|
||||
}
|
||||
nw.push(...undefFieldInits(host, meta, nullable));
|
||||
// Young instances: program-owned traced classes. Classes rooted at a
|
||||
// runtime base (errors, emitters, events, streams) carry runtime state
|
||||
// and stay with trial deletion, like every runtime family.
|
||||
if (traced && !lifetime && (!meta.hierarchy || meta.root.def.runtime !== true))
|
||||
nw.push(...youngEntryLines(host));
|
||||
nw.push(` ret ptr ${meta.hierarchy ? "%o.vt" : "%o"}`, `}`, ``);
|
||||
defs.push(...nw);
|
||||
|
||||
@@ -841,6 +895,7 @@ export function emitClassShapes(
|
||||
` call void ${releaseSym(host, f.type)}(ptr %v${i}) ; ${llvmCommentText(f.name)} (acyclic)`,
|
||||
);
|
||||
});
|
||||
if (meta.threadSlot) gf.push(...slotForgetLines(host, struct, meta, "gfw"));
|
||||
gf.push(...emitObjectFree(host, true), ` ret void`, `}`, ``);
|
||||
defs.push(...gf);
|
||||
}
|
||||
@@ -878,14 +933,32 @@ export function emitClassObjDefs(
|
||||
`llvm emitter bug: class object for ${className} names unknown family ${meta.def.genericOf ?? ""}`,
|
||||
);
|
||||
}
|
||||
const ctor = fnByName.get(`%${className}.constructor`);
|
||||
const valueCtor = classValueConstructor(meta.def);
|
||||
const ctor = fnByName.get(valueCtor.fn);
|
||||
if (!ctor)
|
||||
throw new InternalCompilerError(
|
||||
`llvm emitter bug: class object for ${className} without a constructor`,
|
||||
);
|
||||
const params = ctor.params.slice(1);
|
||||
const params = ctor.params.slice(valueCtor.firstArg);
|
||||
const paramDecls = params.map((p, i) => `${llType(p.type)} %a${i}`).join(", ");
|
||||
const ctorArgs = params.map((p, i) => `${llType(p.type)} %a${i}`);
|
||||
const classObj =
|
||||
`@${mangleClassObj(className)} = internal ${threadLocal ? "thread_local " : ""}global %ScrClassObj ` +
|
||||
`{ ${host.sizeType} -1, ${host.sizeType} ${intervalMeta.pre}, ${host.sizeType} ${intervalMeta.post}, ptr @${mangleCtorThunk(className)}, ptr ${nameSym}, ${host.sizeType} 0, ${host.sizeType} ${meta.def.jsLength ?? (meta.def.runtime ? 1 : 0)}, ptr null, ptr null } ; class ${className}`;
|
||||
if (meta.def.runtime) {
|
||||
// A runtime-provided class: %C.construct allocates through the
|
||||
// runtime and returns the instance.
|
||||
out.push(
|
||||
`define internal ptr @${mangleCtorThunk(className)}(ptr %class${paramDecls ? ", " + paramDecls : ""}) ${FN_ATTRS} { ; construct thunk ${className}`,
|
||||
`entry:`,
|
||||
` %o = call ptr @${mangleFunction(valueCtor.fn)}(${ctorArgs.join(", ")})`,
|
||||
` ret ptr %o`,
|
||||
`}`,
|
||||
classObj,
|
||||
``,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (meta.def.localCaptures !== undefined)
|
||||
host.declare(`declare ptr @scr_classobj_retain_v(ptr)`);
|
||||
const localBaseInits: string[] = [];
|
||||
@@ -944,8 +1017,7 @@ export function emitClassObjDefs(
|
||||
` call void @${mangleFunction(`%${className}.constructor`)}(${[`ptr %r`, ...ctorArgs].join(", ")})`,
|
||||
` ret ptr %o`,
|
||||
`}`,
|
||||
`@${mangleClassObj(className)} = internal ${threadLocal ? "thread_local " : ""}global %ScrClassObj ` +
|
||||
`{ ${host.sizeType} -1, ${host.sizeType} ${intervalMeta.pre}, ${host.sizeType} ${intervalMeta.post}, ptr @${mangleCtorThunk(className)}, ptr ${nameSym}, ${host.sizeType} 0, ${host.sizeType} ${meta.def.jsLength ?? 0}, ptr null, ptr null } ; class ${className}`,
|
||||
classObj,
|
||||
``,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ export function emitDenseSlotUnlink(
|
||||
B.condBr(same, swap, done);
|
||||
B.startBlock(swap);
|
||||
B.line(`store ptr ${placeholder}, ptr ${valuePtr}${host.fieldAliasAttachment(valuePtr)}`);
|
||||
host.releaseValue(current, element);
|
||||
host.releaseHeap(current, element);
|
||||
B.br(done);
|
||||
B.startBlock(done);
|
||||
}
|
||||
@@ -315,7 +315,7 @@ export function emitDenseArrayGet(
|
||||
fastLabel = B.newLabel("arr.get.ref");
|
||||
B.condBr(nonnull, fastLabel, slow);
|
||||
B.startBlock(fastLabel);
|
||||
fast = retain ? host.retainValue(raw, element) : raw;
|
||||
fast = retain ? host.retainFrame(raw, element) : raw;
|
||||
}
|
||||
B.br(join);
|
||||
B.startBlock(slow);
|
||||
@@ -493,7 +493,7 @@ export function emitDenseArraySet(
|
||||
const releaseLabel = B.newLabel("arr.set.release");
|
||||
B.condBr(hadValue, releaseLabel, join);
|
||||
B.startBlock(releaseLabel);
|
||||
host.releaseValue(old, element);
|
||||
host.releaseHeap(old, element);
|
||||
}
|
||||
B.br(join);
|
||||
// Below the bound the slot holds a present value: replace it, leaving
|
||||
@@ -508,7 +508,7 @@ export function emitDenseArraySet(
|
||||
);
|
||||
}
|
||||
storeValue(filledValuePtr);
|
||||
if (filledOld) host.releaseValue(filledOld, element);
|
||||
if (filledOld) host.releaseHeap(filledOld, element);
|
||||
B.br(join);
|
||||
B.startBlock(slow);
|
||||
call(" cold");
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { expect, test } from "vitest";
|
||||
import { DYN_HANDLE_KINDS } from "../../ir/ir.js";
|
||||
import { DYN_HANDLE_TAG_NUM } from "./dyn.js";
|
||||
|
||||
// ScrDynHandleTag in scr_dyn.h lists its tags by name, each with an explicit
|
||||
// value, so changes that add tags edit different lines. These checks stand
|
||||
// in for what the enum's order used to guarantee.
|
||||
const header = readFileSync(new URL("../../../../runtime/src/scr_dyn.h", import.meta.url), "utf8");
|
||||
const body = /typedef enum \{\n([\s\S]*?)\} ScrDynHandleTag;/.exec(header)![1]!;
|
||||
const tags = [...body.matchAll(/^\s*(SCR_DYNH_[A-Z0-9_]+) = (\d+),/gm)].map(([, name, value]) => ({
|
||||
name: name!,
|
||||
value: Number(value),
|
||||
}));
|
||||
const capacity = Number(/#define SCR_DYNH_CAPACITY (\d+)/.exec(header)![1]);
|
||||
|
||||
test("every ScrDynHandleTag has an explicit value, in name order", () => {
|
||||
const lines = body.split("\n").filter((line) => line.trim().startsWith("SCR_DYNH_"));
|
||||
expect(tags.length).toBe(lines.length);
|
||||
expect(tags.map((tag) => tag.name)).toEqual(tags.map((tag) => tag.name).toSorted());
|
||||
});
|
||||
|
||||
test("ScrDynHandleTag values are unique and below SCR_DYNH_CAPACITY", () => {
|
||||
const owners = new Map<number, string>();
|
||||
for (const { name, value } of tags) {
|
||||
expect(owners.get(value), `${name} = ${value}`).toBeUndefined();
|
||||
owners.set(value, name);
|
||||
expect(value, name).toBeLessThan(capacity);
|
||||
}
|
||||
});
|
||||
|
||||
test("the backend's handle tag numbers match scr_dyn.h", () => {
|
||||
const values = new Map(tags.map((tag) => [tag.name, tag.value]));
|
||||
for (const [kind, num] of Object.entries(DYN_HANDLE_TAG_NUM)) {
|
||||
const tag = DYN_HANDLE_KINDS.get(kind)?.tag;
|
||||
expect(tag, kind).toBeDefined();
|
||||
expect(values.get(tag!), `${kind} (${tag})`).toBe(num);
|
||||
}
|
||||
});
|
||||
@@ -72,8 +72,8 @@ export function recordDynKeepsIdentity(shape: IrRecordShape): boolean {
|
||||
}
|
||||
|
||||
/** ScrDynKind values (scr_runtime.h). */
|
||||
/** ScrDynHandleTag numeric values (scr_runtime.h's enum order). */
|
||||
const DYN_HANDLE_TAG_NUM: Record<string, number> = {
|
||||
/** ScrDynHandleTag values (scr_dyn.h's explicit values; dyn-handle-tags.test.ts checks them). */
|
||||
export const DYN_HANDLE_TAG_NUM: Record<string, number> = {
|
||||
httpReq: 0,
|
||||
httpRes: 1,
|
||||
netSocket: 2,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { type ArraySearchKind, emitArraySearchHelpers } from "./array-search.js";
|
||||
import { emitStringInputs } from "./string-lifetimes.js";
|
||||
import { emitLlvmLayouts } from "./layouts.js";
|
||||
import { emitThreadSlotsIntrinsic, threadSlotsBlockDefinitions } from "./thread-slots.js";
|
||||
import { compileRegexMatcher, type RegexMatcherCode } from "./regex-matchers.js";
|
||||
import {
|
||||
CYC_BUF,
|
||||
@@ -10,6 +11,7 @@ import {
|
||||
RECORD_SOURCES_GATE,
|
||||
WEAK_KEYS_GATE,
|
||||
} from "./alloc.js";
|
||||
import { youngNurseryRequested } from "../young-nursery.js";
|
||||
import { hasLoneSurrogate, llvmBytes as llBytes, wtf8Bytes } from "../literals.js";
|
||||
import { InternalCompilerError } from "../../errors.js";
|
||||
import { createHash } from "node:crypto";
|
||||
@@ -55,6 +57,7 @@ import {
|
||||
typeKey,
|
||||
STRING,
|
||||
VOID,
|
||||
classJsName,
|
||||
} from "../../ir/ir.js";
|
||||
import { moduleRuntimeFeatures, moduleUsesMockTimers } from "../../ir/runtime-features.js";
|
||||
import { captureFreeFunctions, observesStacks } from "../stack-observations.js";
|
||||
@@ -914,6 +917,8 @@ export class LlEmitter {
|
||||
/** Constant-key reads on checked-dynamic objects, each with its own
|
||||
* zero-initialized ScrDynReadIc (scr_runtime.h). */
|
||||
private dynReadIcSites = 0;
|
||||
/** ThreadSlots blocks by family name (thread-slots.ts). */
|
||||
private readonly threadSlotsBlocks = new Map<string, string>();
|
||||
/** May-throw analysis (computeMayThrow): pending
|
||||
* checks are emitted only after calls that can actually raise. */
|
||||
readonly mayThrow: Set<string>;
|
||||
@@ -965,6 +970,14 @@ export class LlEmitter {
|
||||
/** The program calls mock.timers members (moduleUsesMockTimers): timer
|
||||
* calls emit scr_test.c's mock-aware entry points. */
|
||||
readonly mockTimers: boolean;
|
||||
private schedulesInContextValue: boolean | undefined = undefined;
|
||||
/** Whether timer and tick scheduling goes through the *_als entry
|
||||
* points (RuntimeFeatures asyncContext). */
|
||||
get schedulesInContext(): boolean {
|
||||
if (this.schedulesInContextValue === undefined)
|
||||
this.schedulesInContextValue = moduleRuntimeFeatures(this.mod).asyncContext;
|
||||
return this.schedulesInContextValue;
|
||||
}
|
||||
/** Emitted ref-kind resolve thunks for new Promise, interned per inner
|
||||
* typeKey → thunk symbol. */
|
||||
readonly resolveThunks = new Map<string, string>();
|
||||
@@ -1451,6 +1464,7 @@ export class LlEmitter {
|
||||
...(this.rcMode !== undefined ? { rcMode: this.rcMode } : {}),
|
||||
...(this.rcHelpersNoinline ? { rcHelpersNoinline: true } : {}),
|
||||
nextCycKind: () => FIRST_EMITTED_CYC_KIND + this.cycKinds++,
|
||||
...(youngNurseryRequested() && mod.lib === undefined ? { youngNursery: true } : {}),
|
||||
unionsById: this.unionsById,
|
||||
declare: (decl) => this.declare(decl),
|
||||
needOom: () => this.needOom(),
|
||||
@@ -2507,6 +2521,7 @@ export class LlEmitter {
|
||||
{
|
||||
const tl =
|
||||
this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : "";
|
||||
out.push(...threadSlotsBlockDefinitions(this.threadSlotsBlocks, tl !== ""));
|
||||
for (let site = 0; site < this.dynReadIcSites; site++)
|
||||
out.push(
|
||||
`@sc_dyn_read_ic_${site} = internal ${tl}global { ptr, ${this.sizeType}, ${this.sizeType}, ptr } zeroinitializer`,
|
||||
@@ -4632,6 +4647,9 @@ export class LlEmitter {
|
||||
// An iterator helper's return() before its first next() closes the
|
||||
// source generator in its first argument slot.
|
||||
const closesSource = fn.generator.closesSource === true;
|
||||
if (fn.generator.tag !== undefined) this.declare(`declare void @scr_gen_set_tag(ptr, ptr)`);
|
||||
if (fn.generator.inheritsTag === true)
|
||||
this.declare(`declare void @scr_gen_copy_tag(ptr, ptr)`);
|
||||
if (closesSource) {
|
||||
const field = lifted ? 1 : 0;
|
||||
this.declare(`declare void @scr_gen_close(ptr)`);
|
||||
@@ -4664,6 +4682,12 @@ export class LlEmitter {
|
||||
settleAsync === null
|
||||
? ` %gg = call ptr @${genNew}(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)})`
|
||||
: ` %gg = call ptr @scr_async_gen_new(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)}, ptr @${settleAsync})`,
|
||||
...(fn.generator.tag !== undefined
|
||||
? [` call void @scr_gen_set_tag(ptr %gg, ptr ${this.cstr(fn.generator.tag)})`]
|
||||
: []),
|
||||
...(fn.generator.inheritsTag === true
|
||||
? [` call void @scr_gen_copy_tag(ptr %gg, ptr %a${lifted ? 1 : 0})`]
|
||||
: []),
|
||||
...(closesSource
|
||||
? [
|
||||
` call void @scr_gen_set_unstarted_return(ptr %gg, ptr @${mangleGenSourceClose(fn.name)})`,
|
||||
@@ -4941,8 +4965,8 @@ export class LlEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
/** Strike a refcounted temp from its frame: ownership is being moved. */
|
||||
moveTemp(v: LlValue): void {
|
||||
/** Strike a refcounted temp from its frame. */
|
||||
private disownTemp(v: LlValue): void {
|
||||
if (!isRefCounted(v.type) || this.isImmortal(v.name)) return;
|
||||
for (let i = this.frames.length - 1; i >= 0; i--) {
|
||||
const idx = this.frames[i]!.findIndex((e) => e.name === v.name);
|
||||
@@ -4956,23 +4980,69 @@ export class LlEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
/* Ownership transfers name their destination. A heap reference is one
|
||||
* that heap memory holds: a field, an array or map element, a capture
|
||||
* box, a union box, a module global, a promise or generator slot, the
|
||||
* exception cell, or any other runtime structure. A frame reference is
|
||||
* one that a local, a parameter, a return value, a stack object or a
|
||||
* temporary holds. Both forms of each operation emit the same code. */
|
||||
|
||||
/** Ends a temp's frame ownership: its reference moves into heap memory. */
|
||||
toHeap(v: LlValue): void {
|
||||
this.disownTemp(v);
|
||||
}
|
||||
|
||||
/** Ends a temp's frame ownership: its reference moves to another frame
|
||||
* owner (a local or parameter slot, a callee's parameter, a return
|
||||
* value, a stack object, another temp). */
|
||||
toFrame(v: LlValue): void {
|
||||
this.disownTemp(v);
|
||||
}
|
||||
|
||||
/** The retained (+1) read of a refcounted value — type-directed through
|
||||
* the `_v` table (immortals skip, exactly the C retain calls). */
|
||||
retainValue(name: string, type: IrType): string {
|
||||
private retainCall(name: string, type: IrType): string {
|
||||
if (this.isImmortal(name)) return name;
|
||||
const t = this.B.tmp();
|
||||
this.B.line(`${t} = call ptr ${retainSym(this.shapeHost, type)}(ptr ${name})`);
|
||||
return t;
|
||||
}
|
||||
|
||||
/** A +1 of `name` that heap memory keeps. */
|
||||
retainHeap(name: string, type: IrType): string {
|
||||
return this.retainCall(name, type);
|
||||
}
|
||||
|
||||
/** A +1 of `name` that a frame owns. */
|
||||
retainFrame(name: string, type: IrType): string {
|
||||
return this.retainCall(name, type);
|
||||
}
|
||||
|
||||
/** The release call for one owned refcounted value — type-directed like
|
||||
* releaseCallC (all runtime releases are NULL-tolerant). */
|
||||
releaseValue(name: string, type: IrType): void {
|
||||
private releaseCall(name: string, type: IrType): void {
|
||||
if (this.isImmortal(name)) return;
|
||||
this.B.line(`call void ${releaseSym(this.shapeHost, type)}(ptr ${name})`);
|
||||
}
|
||||
|
||||
releaseFrame(frame: LlValue[]): void {
|
||||
/** Releases a reference that heap memory held. */
|
||||
releaseHeap(name: string, type: IrType): void {
|
||||
this.releaseCall(name, type);
|
||||
}
|
||||
|
||||
/** Releases a reference that a frame owned. */
|
||||
releaseFrame(name: string, type: IrType): void {
|
||||
this.releaseCall(name, type);
|
||||
}
|
||||
|
||||
/** A +1 that the runtime made with the heap retain (a box, generator or
|
||||
* await read) becomes a frame value. Emits nothing; answers `name`. */
|
||||
adoptHeap(name: string, _type: IrType): string {
|
||||
return name;
|
||||
}
|
||||
|
||||
/** Releases a statement frame's temps and owned slots. */
|
||||
releaseTemps(frame: LlValue[]): void {
|
||||
for (const v of frame) {
|
||||
if (v.slot) {
|
||||
const t = this.B.tmp();
|
||||
@@ -4980,10 +5050,10 @@ export class LlEmitter {
|
||||
if (v.boxed) {
|
||||
this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
|
||||
} else {
|
||||
this.releaseValue(t, v.type);
|
||||
this.releaseFrame(t, v.type);
|
||||
}
|
||||
} else {
|
||||
this.releaseValue(v.name, v.type);
|
||||
this.releaseFrame(v.name, v.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5008,7 +5078,7 @@ export class LlEmitter {
|
||||
} else if (e.boxed) {
|
||||
this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
|
||||
} else {
|
||||
this.releaseValue(t, e.type); // runtime releases are NULL-tolerant
|
||||
this.releaseFrame(t, e.type); // runtime releases are NULL-tolerant
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5018,7 +5088,7 @@ export class LlEmitter {
|
||||
* everything whose normal fall-through releases the jump bypasses.
|
||||
* Release every scope crossed by the control-flow edge. */
|
||||
private releaseForJump(frameDepth: number, scopeDepth: number): void {
|
||||
for (let i = this.frames.length - 1; i >= frameDepth; i--) this.releaseFrame(this.frames[i]!);
|
||||
for (let i = this.frames.length - 1; i >= frameDepth; i--) this.releaseTemps(this.frames[i]!);
|
||||
for (let i = this.scopes.length - 1; i >= scopeDepth; i--) this.releaseScope(this.scopes[i]!);
|
||||
}
|
||||
|
||||
@@ -5091,7 +5161,7 @@ export class LlEmitter {
|
||||
const target = this.tryStack[this.tryStack.length - 1];
|
||||
const frameDepth = target?.frameDepth ?? 0;
|
||||
const scopeDepth = target?.scopeDepth ?? 0;
|
||||
for (let i = this.frames.length - 1; i >= frameDepth; i--) this.releaseFrame(this.frames[i]!);
|
||||
for (let i = this.frames.length - 1; i >= frameDepth; i--) this.releaseTemps(this.frames[i]!);
|
||||
let terminator: string;
|
||||
if (target) {
|
||||
target.used = true;
|
||||
@@ -5621,7 +5691,7 @@ export class LlEmitter {
|
||||
if (target.value !== null) {
|
||||
this.frames.push([]);
|
||||
for (const read of speculativeReads(target.value)) reads.set(read, this.emitExpr(read));
|
||||
this.releaseFrame(this.frames.pop()!);
|
||||
this.releaseTemps(this.frames.pop()!);
|
||||
}
|
||||
this.frames.push([]);
|
||||
let result: string;
|
||||
@@ -5945,7 +6015,7 @@ export class LlEmitter {
|
||||
unionExtract(uName: string, unionId: string, arm: IrType): string {
|
||||
if (arm.kind === "f64" || arm.kind === "procStream") return this.unionGetF64(uName);
|
||||
if (arm.kind === "bool") return this.unionGetBool(uName);
|
||||
return this.retainValue(this.unionPeek(uName, unionId), arm);
|
||||
return this.retainFrame(this.unionPeek(uName, unionId), arm);
|
||||
}
|
||||
|
||||
/** A scalar f64 arm's payload (scr_union_get_f64 inlined). Inline loads
|
||||
@@ -6056,7 +6126,7 @@ export class LlEmitter {
|
||||
classObjSym(className: string): string {
|
||||
if (!this.classObjs.has(className)) {
|
||||
const meta = this.classMetaOf(className);
|
||||
this.classObjs.set(className, { nameSym: this.internLiteral(meta.def.jsName ?? "") });
|
||||
this.classObjs.set(className, { nameSym: this.internLiteral(classJsName(meta.def)) });
|
||||
}
|
||||
return mangleClassObj(className);
|
||||
}
|
||||
@@ -6187,11 +6257,11 @@ export class LlEmitter {
|
||||
* in, and the old value is released AFTER the field is overwritten. */
|
||||
replaceField(ptr: string, type: IrType, inline: boolean, v: LlValue): void {
|
||||
if (!inline && isRefCounted(type)) {
|
||||
this.moveTemp(v);
|
||||
this.toHeap(v);
|
||||
const old = this.B.tmp();
|
||||
this.B.line(`${old} = load ptr, ptr ${ptr}`);
|
||||
this.storeField(ptr, type, v.name, false);
|
||||
this.releaseValue(old, type);
|
||||
this.releaseHeap(old, type);
|
||||
return;
|
||||
}
|
||||
this.storeField(ptr, type, v.name, inline);
|
||||
@@ -6199,7 +6269,7 @@ export class LlEmitter {
|
||||
|
||||
/** Stores `v` (from emitFieldValue) into a fresh field slot. */
|
||||
initField(ptr: string, type: IrType, inline: boolean, v: LlValue): void {
|
||||
if (!inline && isRefCounted(type)) this.moveTemp(v);
|
||||
if (!inline && isRefCounted(type)) this.toHeap(v);
|
||||
this.storeField(ptr, type, v.name, inline);
|
||||
}
|
||||
|
||||
@@ -6365,7 +6435,7 @@ export class LlEmitter {
|
||||
if (!stack) B.line(`${box} = ${boxNewCall(this.shapeHost, type)}`);
|
||||
const value = B.tmp();
|
||||
B.line(`${value} = load ${this.llType(type)}, ptr %${mangleLocal(localId)}`);
|
||||
this.boxSet(box, type, isRefCounted(type) ? this.retainValue(value, type) : value);
|
||||
this.boxSet(box, type, isRefCounted(type) ? this.retainHeap(value, type) : value);
|
||||
B.line(`store ptr ${box}, ptr ${cache}`);
|
||||
B.br(join);
|
||||
B.startBlock(join);
|
||||
@@ -6511,7 +6581,7 @@ export class LlEmitter {
|
||||
this.boxSet(
|
||||
box,
|
||||
local.type,
|
||||
borrowed && isRefCounted(local.type) ? this.retainValue(value, local.type) : value,
|
||||
borrowed && isRefCounted(local.type) ? this.retainHeap(value, local.type) : value,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7536,14 +7606,14 @@ export class LlEmitter {
|
||||
B.line(`${present} = icmp ne ptr %result, null`);
|
||||
B.condBr(present, own, done);
|
||||
B.startBlock(own);
|
||||
B.line(`store ptr ${this.retainValue("%result", fn.returnType)}, ptr ${slot}`);
|
||||
B.line(`store ptr ${this.retainFrame("%result", fn.returnType)}, ptr ${slot}`);
|
||||
B.br(done);
|
||||
B.startBlock(done);
|
||||
result = B.tmp();
|
||||
B.line(`${result} = load ptr, ptr ${slot}`);
|
||||
}
|
||||
for (const index of borrowed)
|
||||
if (!kept.has(index)) this.releaseValue(`%p${index}`, fn.params[index]!.type);
|
||||
if (!kept.has(index)) this.releaseFrame(`%p${index}`, fn.params[index]!.type);
|
||||
B.terminate(ret === "void" ? "ret void" : `ret ${ret} ${result}`);
|
||||
const symbol =
|
||||
kept.size > 0 ? `${mangleBorrowedFunction(fn.name)}.virtual` : mangleFunction(fn.name);
|
||||
@@ -7704,7 +7774,7 @@ export class LlEmitter {
|
||||
private emitAdoptingReturn(s: IrStmt & { kind: "return" }, adopted: ExprOf<"awaitExpr">): void {
|
||||
const B = this.B;
|
||||
const promise = this.emitExpr(adopted.value);
|
||||
this.moveTemp(promise);
|
||||
this.toFrame(promise);
|
||||
const slot = B.slot();
|
||||
B.entryAllocas.push(`${slot} = alloca ptr ; adopted return promise`);
|
||||
B.line(`store ptr ${promise.name}, ptr ${slot}`);
|
||||
@@ -7738,7 +7808,8 @@ export class LlEmitter {
|
||||
const v = this.emitExpr(s.value!);
|
||||
if (this.adoptedOperand !== null)
|
||||
throw new InternalCompilerError("llvm emitter bug: adopted return promise was not awaited");
|
||||
this.moveTemp(v);
|
||||
if (this.currentCoro !== null) this.toHeap(v);
|
||||
else this.toFrame(v);
|
||||
this.releaseForJump(0, 0);
|
||||
if (!B.isTerminated()) {
|
||||
if (this.currentCoro !== null) {
|
||||
@@ -8083,7 +8154,7 @@ export class LlEmitter {
|
||||
const union = emitStackUnion(this, localUnion);
|
||||
B.line(`store ptr ${union.value.name}, ptr ${b.slot}`);
|
||||
if (union.payload) {
|
||||
this.moveTemp(union.payload);
|
||||
this.toFrame(union.payload);
|
||||
this.scopes[this.scopes.length - 1]!.push({
|
||||
slot: union.payloadSlot,
|
||||
type: union.payload.type,
|
||||
@@ -8130,7 +8201,7 @@ export class LlEmitter {
|
||||
if (stack.owner) this.scopes[this.scopes.length - 1]!.push(stack.owner);
|
||||
if (s.init) {
|
||||
const value = this.emitExpr(s.init);
|
||||
if (isRefCounted(value.type)) this.moveTemp(value);
|
||||
if (isRefCounted(value.type)) this.toFrame(value);
|
||||
this.boxSet(stack.box, b.type, value.name);
|
||||
}
|
||||
break;
|
||||
@@ -8157,7 +8228,7 @@ export class LlEmitter {
|
||||
break;
|
||||
}
|
||||
const v = this.emitExpr(s.init);
|
||||
if (isRefCounted(v.type)) this.moveTemp(v); // the box takes ownership
|
||||
if (isRefCounted(v.type)) this.toHeap(v); // the box takes ownership
|
||||
if (b.local!.tdz === true && boxAccess(b.type) !== "ref") {
|
||||
this.tdzScalarInit(box, b.type, v.name);
|
||||
} else {
|
||||
@@ -8215,7 +8286,8 @@ export class LlEmitter {
|
||||
break;
|
||||
}
|
||||
const v = this.emitExpr(s.init);
|
||||
this.moveTemp(v);
|
||||
if (b.kind === "global") this.toHeap(v);
|
||||
else this.toFrame(v);
|
||||
B.line(`store ${this.llType(b.type)} ${v.name}, ptr ${b.slot}`);
|
||||
this.storeIntegerView(s.localId, v, s.init);
|
||||
if (isRefCounted(b.type)) {
|
||||
@@ -8238,10 +8310,10 @@ export class LlEmitter {
|
||||
const param = this.binding(s.localId);
|
||||
const slot = param.slot;
|
||||
const v = this.emitExpr(s.value);
|
||||
this.moveTemp(v);
|
||||
this.toFrame(v);
|
||||
const old = B.tmp();
|
||||
B.line(`${old} = load ptr, ptr ${owner}`);
|
||||
this.releaseValue(old, param.type);
|
||||
this.releaseFrame(old, param.type);
|
||||
B.line(`store ptr ${v.name}, ptr ${slot}`);
|
||||
B.line(`store ptr ${v.name}, ptr ${owner}`);
|
||||
break;
|
||||
@@ -8268,7 +8340,7 @@ export class LlEmitter {
|
||||
B.line(`${bits} = load ${struct}, ptr ${value.name}`);
|
||||
B.line(`store ${struct} ${bits}, ptr ${target}`);
|
||||
this.disownSlots(owned);
|
||||
for (const field of old) this.releaseValue(field.name, field.type);
|
||||
for (const field of old) this.releaseFrame(field.name, field.type);
|
||||
break;
|
||||
}
|
||||
const localStorage = this.localUnionStorage.get(s.localId);
|
||||
@@ -8292,14 +8364,16 @@ export class LlEmitter {
|
||||
if (b.kind === "boxed") {
|
||||
this.writeBindingBox(this.loadBox(b.slot), b.local!, v.name, s.initializes);
|
||||
// The RHS remains frame-owned until a possible TDZ throw passes.
|
||||
if (isRefCounted(v.type)) this.moveTemp(v);
|
||||
if (isRefCounted(v.type)) this.toHeap(v);
|
||||
break;
|
||||
}
|
||||
this.moveTemp(v);
|
||||
if (b.kind === "global") this.toHeap(v);
|
||||
else this.toFrame(v);
|
||||
if (isRefCounted(b.type)) {
|
||||
const old = B.tmp();
|
||||
B.line(`${old} = load ptr, ptr ${b.slot}`);
|
||||
this.releaseValue(old, b.type);
|
||||
if (b.kind === "global") this.releaseHeap(old, b.type);
|
||||
else this.releaseFrame(old, b.type);
|
||||
}
|
||||
B.line(`store ${this.llType(b.type)} ${v.name}, ptr ${b.slot}`);
|
||||
this.storeIntegerView(s.localId, v, s.value);
|
||||
@@ -8350,7 +8424,7 @@ export class LlEmitter {
|
||||
throw new InternalCompilerError("llvm emitter bug: arraySet on non-array");
|
||||
const acc = elemAccess(s.arr.type.elem);
|
||||
this.emitPublishedArrayGuard(arr.name, s.arr.type, 0, idx.name);
|
||||
if (acc === "ref") this.moveTemp(v);
|
||||
if (acc === "ref") this.toHeap(v);
|
||||
emitDenseArraySet(
|
||||
this,
|
||||
arr.name,
|
||||
@@ -8415,7 +8489,7 @@ export class LlEmitter {
|
||||
if (acc === "ref" && isRefCounted(elem)) {
|
||||
const old = B.tmp();
|
||||
B.line(`${old} = inttoptr i64 ${raw} to ptr`);
|
||||
this.releaseValue(old, elem);
|
||||
this.releaseHeap(old, elem);
|
||||
}
|
||||
B.br(done);
|
||||
B.startBlock(call);
|
||||
@@ -8565,7 +8639,7 @@ export class LlEmitter {
|
||||
const v = this.emitExpr(s.value);
|
||||
if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
|
||||
this.emitPublishedFieldGuard(obj.name, null, "[key]");
|
||||
if (isRefCounted(v.type)) this.moveTemp(v);
|
||||
if (isRefCounted(v.type)) this.toHeap(v);
|
||||
const shape = this.recordShape(s.shapeId);
|
||||
// Signature-free shapes dispatch over their (one-typed) declared
|
||||
// fields and TRAP on a miss (scr_record_key_miss — JS would add
|
||||
@@ -8631,12 +8705,12 @@ export class LlEmitter {
|
||||
if (inline) {
|
||||
// The slot copies the checked value's bits; nv is released below.
|
||||
this.storeField(ptr, type, nv, true);
|
||||
this.releaseValue(nv, type);
|
||||
this.releaseFrame(nv, type);
|
||||
} else if (isRefCounted(type)) {
|
||||
const old = B.tmp();
|
||||
B.line(`${old} = load ptr, ptr ${ptr}`);
|
||||
this.storeField(ptr, type, nv, false);
|
||||
this.releaseValue(old, type);
|
||||
this.releaseHeap(old, type);
|
||||
} else {
|
||||
this.storeField(ptr, type, nv, false);
|
||||
}
|
||||
@@ -8666,12 +8740,12 @@ export class LlEmitter {
|
||||
if (inline) {
|
||||
// The write owns the moved-in value; the slot copies its bits.
|
||||
this.storeField(ptr, type, v.name, true);
|
||||
this.releaseValue(v.name, type);
|
||||
this.releaseHeap(v.name, type);
|
||||
} else if (isRefCounted(type)) {
|
||||
const old = B.tmp();
|
||||
B.line(`${old} = load ptr, ptr ${ptr}`);
|
||||
this.storeField(ptr, type, v.name, false);
|
||||
this.releaseValue(old, type);
|
||||
this.releaseHeap(old, type);
|
||||
} else {
|
||||
this.storeField(ptr, type, v.name, false);
|
||||
}
|
||||
@@ -8682,7 +8756,7 @@ export class LlEmitter {
|
||||
// The MISS on a fixed shape: release the moved-in value, throw
|
||||
// the catchable TypeError naming the key (scr_record_key_miss —
|
||||
// JS would add the property, the documented divergence).
|
||||
if (isRefCounted(iv)) this.releaseValue(v.name, iv);
|
||||
if (isRefCounted(iv)) this.releaseHeap(v.name, iv);
|
||||
this.declare(`declare void @scr_record_key_miss(ptr)`);
|
||||
B.line(`call void @scr_record_key_miss(ptr ${key.name})`);
|
||||
B.br(join);
|
||||
@@ -9325,7 +9399,8 @@ export class LlEmitter {
|
||||
} else v = this.emitReadReceiver(s.value);
|
||||
} else if (s.value !== null) {
|
||||
v = this.emitExpr(s.value);
|
||||
this.moveTemp(v);
|
||||
if (this.currentCoro !== null) this.toHeap(v);
|
||||
else this.toFrame(v);
|
||||
}
|
||||
// An async generator's `return v` awaits v before any crossed
|
||||
// finally runs. Past finally regions that turn is taken here and
|
||||
@@ -9394,7 +9469,7 @@ export class LlEmitter {
|
||||
// unwind unconditionally (the innermost try handler, or out of the
|
||||
// function) — the same release path as return/break/continue.
|
||||
const v = this.emitExpr(s.value);
|
||||
if (isRefCounted(s.value.type)) this.moveTemp(v); // the cell takes ownership
|
||||
if (isRefCounted(s.value.type)) this.toHeap(v); // the cell takes ownership
|
||||
this.emitThrowValue({ name: v.name, type: s.value.type });
|
||||
this.emitUnwind();
|
||||
break;
|
||||
@@ -9440,7 +9515,7 @@ export class LlEmitter {
|
||||
s.kind !== "rethrow" &&
|
||||
s.kind !== "runtimeFence"
|
||||
) {
|
||||
this.releaseFrame(frame);
|
||||
this.releaseTemps(frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9652,7 +9727,7 @@ export class LlEmitter {
|
||||
} else {
|
||||
B.line(`${hit} = fcmp oeq double ${disc.name}, ${t.name}`);
|
||||
}
|
||||
this.releaseFrame(this.frames.pop()!);
|
||||
this.releaseTemps(this.frames.pop()!);
|
||||
const next = B.newLabel("sw.t");
|
||||
B.condBr(hit, caseLabels[i]!, next);
|
||||
B.startBlock(next);
|
||||
@@ -9693,7 +9768,7 @@ export class LlEmitter {
|
||||
private emitCondition(cond: IrExpr): string {
|
||||
const v = this.emitExpr(cond);
|
||||
const frame = this.currentFrame();
|
||||
this.releaseFrame(frame);
|
||||
this.releaseTemps(frame);
|
||||
frame.length = 0;
|
||||
return v.name;
|
||||
}
|
||||
@@ -9705,11 +9780,11 @@ export class LlEmitter {
|
||||
emitBranchInto(slot: string, expr: IrExpr, integerSlot?: string): boolean {
|
||||
this.frames.push([]);
|
||||
const v = this.emitExpr(expr);
|
||||
this.moveTemp(v);
|
||||
this.toFrame(v);
|
||||
this.B.line(`store ${this.llType(expr.type)} ${v.name}, ptr ${slot}`);
|
||||
if (integerSlot !== undefined && v.uint32 !== undefined)
|
||||
this.B.line(`store i32 ${v.uint32}, ptr ${integerSlot}`);
|
||||
this.releaseFrame(this.frames.pop()!);
|
||||
this.releaseTemps(this.frames.pop()!);
|
||||
return v.uint32 !== undefined;
|
||||
}
|
||||
|
||||
@@ -9863,12 +9938,13 @@ export class LlEmitter {
|
||||
const old = B.tmp();
|
||||
B.line(`${old} = load ptr, ptr ${b.slot}`);
|
||||
B.line(`store ptr null, ptr ${b.slot}`);
|
||||
this.releaseValue(old, b.type);
|
||||
if (b.kind === "global") this.releaseHeap(old, b.type);
|
||||
else this.releaseFrame(old, b.type);
|
||||
}
|
||||
let raw: string;
|
||||
if (charCode !== null) {
|
||||
// The snapshot moves into the consuming character append.
|
||||
this.moveTemp(snapshot);
|
||||
this.toFrame(snapshot);
|
||||
this.declare(`declare ptr @scr_str_concat_move_char_code(ptr, double)`);
|
||||
raw = B.tmp();
|
||||
B.line(
|
||||
@@ -9883,11 +9959,15 @@ export class LlEmitter {
|
||||
}
|
||||
const result = this.own({ name: raw, type: left.type });
|
||||
if (retainForYield) {
|
||||
const stored = this.retainValue(result.name, result.type);
|
||||
const stored =
|
||||
b.kind === "local"
|
||||
? this.retainFrame(result.name, result.type)
|
||||
: this.retainHeap(result.name, result.type);
|
||||
if (b.kind === "boxed") this.boxSet(this.loadBox(b.slot), b.type, stored);
|
||||
else B.line(`store ptr ${stored}, ptr ${b.slot}`);
|
||||
} else {
|
||||
this.moveTemp(result);
|
||||
if (b.kind === "local") this.toFrame(result);
|
||||
else this.toHeap(result);
|
||||
if (b.kind === "boxed") this.boxSet(this.loadBox(b.slot), b.type, result.name);
|
||||
else B.line(`store ptr ${result.name}, ptr ${b.slot}`);
|
||||
}
|
||||
@@ -9950,7 +10030,7 @@ export class LlEmitter {
|
||||
if (owned) {
|
||||
const old = this.loadField(ptr, fieldType, false);
|
||||
this.storeField(ptr, fieldType, "null", false);
|
||||
this.releaseValue(old, fieldType);
|
||||
this.releaseHeap(old, fieldType);
|
||||
}
|
||||
let raw: string;
|
||||
if (right === null) {
|
||||
@@ -10179,9 +10259,20 @@ export class LlEmitter {
|
||||
)
|
||||
return this.emitRegionIntrinsic(e);
|
||||
if (e.name.startsWith("itable.")) return this.emitInternTableIntrinsic(e);
|
||||
if (e.name.startsWith("slots.")) return emitThreadSlotsIntrinsic(this, e);
|
||||
return emitIntrinsicExpr(this, e);
|
||||
}
|
||||
|
||||
/** The ScrSlots block of the ThreadSlots family `name`. */
|
||||
threadSlotsBlock(name: string): string {
|
||||
let sym = this.threadSlotsBlocks.get(name);
|
||||
if (sym === undefined) {
|
||||
sym = `@sc_slots_${this.threadSlotsBlocks.size}`;
|
||||
this.threadSlotsBlocks.set(name, sym);
|
||||
}
|
||||
return sym;
|
||||
}
|
||||
|
||||
/** InternTable's runtime calls (scr_itable.c). Table and entry handles
|
||||
* are f64 values carrying pointer bits; entry values are 64-bit slots
|
||||
* (double bits, a bool, or a pointer to an immortal value) that the
|
||||
@@ -10384,7 +10475,7 @@ export class LlEmitter {
|
||||
}
|
||||
// The result IS the operand: ownership moves to the result temp.
|
||||
if (!isRefCounted(value.type)) return { name: value.name, type: e.type };
|
||||
this.moveTemp(value);
|
||||
this.toFrame(value);
|
||||
return this.own({ name: value.name, type: e.type });
|
||||
}
|
||||
|
||||
@@ -10637,7 +10728,7 @@ export class LlEmitter {
|
||||
this.frames.push([]);
|
||||
const result = this.emitReadReceiver(value);
|
||||
B.line(`store ptr ${result.name}, ptr ${slot}`);
|
||||
this.releaseFrame(this.frames.pop()!);
|
||||
this.releaseTemps(this.frames.pop()!);
|
||||
B.br(join);
|
||||
}
|
||||
B.startBlock(join);
|
||||
@@ -10733,7 +10824,7 @@ export class LlEmitter {
|
||||
const { ptr } = this.classFieldPtr(receiver.name, e.className, e.field);
|
||||
const p = this.B.tmp();
|
||||
this.B.line(`${p} = load ptr, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
|
||||
const owner = this.own({ name: this.retainValue(p, nullable.arm), type: nullable.arm });
|
||||
const owner = this.own({ name: this.retainFrame(p, nullable.arm), type: nullable.arm });
|
||||
return { box: this.stackNullableBox(owner.name, nullable), owner };
|
||||
}
|
||||
|
||||
@@ -10772,7 +10863,7 @@ export class LlEmitter {
|
||||
B.line(`store ptr ${this.unitInstanceRef(nullable.unionId, nullable.unitTag)}, ptr ${slot}`);
|
||||
B.br(join);
|
||||
B.startBlock(ref);
|
||||
const owned = this.retainValue(p, nullable.arm);
|
||||
const owned = this.retainHeap(p, nullable.arm);
|
||||
const box = this.unionNewOwned(nullable.unionId, nullable.refTag, {
|
||||
name: owned,
|
||||
type: nullable.arm,
|
||||
@@ -10795,7 +10886,7 @@ export class LlEmitter {
|
||||
B.line(`${isRef} = icmp eq i32 ${tag}, ${nullable.refTag}`);
|
||||
const payload = this.unionPeek(u, nullable.unionId);
|
||||
B.line(`${sel} = select i1 ${isRef}, ptr ${payload}, ptr null`);
|
||||
return this.retainValue(sel, nullable.arm);
|
||||
return this.retainHeap(sel, nullable.arm);
|
||||
}
|
||||
|
||||
/** `obj.f = value` for a nullable-pointer field: the new instance pointer
|
||||
@@ -10820,7 +10911,7 @@ export class LlEmitter {
|
||||
) {
|
||||
if (value.tag === nullable.refTag) {
|
||||
const inner = this.emitExpr(value.value);
|
||||
this.moveTemp(inner);
|
||||
this.toHeap(inner);
|
||||
next = inner.name;
|
||||
} else {
|
||||
// Unit payloads carry nothing; a void payload runs for effects.
|
||||
@@ -10836,7 +10927,7 @@ export class LlEmitter {
|
||||
const old = B.tmp();
|
||||
B.line(`${old} = load ptr, ptr ${ptr}`);
|
||||
B.line(`store ptr ${next}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
|
||||
this.releaseValue(old, nullable.arm);
|
||||
this.releaseHeap(old, nullable.arm);
|
||||
}
|
||||
|
||||
/** A union binding whose slot may hold a private stack box: stack locals
|
||||
|
||||
@@ -162,7 +162,7 @@ export function emitIntrinsicExpr(host: LlvmEmitterContext, e: ExprOf<"intrinsic
|
||||
const p = B.tmp();
|
||||
B.line(`${p} = call ptr @scr_promise_new()`);
|
||||
const out = host.own({ name: p, type: e.type });
|
||||
host.moveTemp(reason); // the cell takes ownership
|
||||
host.toHeap(reason); // the cell takes ownership
|
||||
host.emitThrowValue({ name: reason.name, type: reasonT });
|
||||
B.line(`call void @scr_promise_reject_pending(ptr ${p})`);
|
||||
return out;
|
||||
@@ -191,12 +191,12 @@ export function emitIntrinsicExpr(host: LlvmEmitterContext, e: ExprOf<"intrinsic
|
||||
host.declare(`declare void @scr_promise_fulfill_bool(ptr, i1 zeroext)`);
|
||||
B.line(`call void @scr_promise_fulfill_bool(ptr ${p}, i1 ${v.name})`);
|
||||
} else if (t.kind === "string") {
|
||||
host.moveTemp(v);
|
||||
host.toHeap(v);
|
||||
host.declare(`declare void @scr_promise_fulfill_str(ptr, ptr)`);
|
||||
B.line(`call void @scr_promise_fulfill_str(ptr ${p}, ptr ${v.name})`);
|
||||
} else {
|
||||
const rc = vAdapters(host.shapeHost, t);
|
||||
host.moveTemp(v);
|
||||
host.toHeap(v);
|
||||
host.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`);
|
||||
B.line(
|
||||
`call void @scr_promise_fulfill_ref(ptr ${p}, ptr ${v.name}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(host.shapeHost, t)})`,
|
||||
@@ -537,7 +537,7 @@ export function emitAsyncExpr(
|
||||
B.line(`call void @scr_gen_yield_bool(i1 ${v.name})`);
|
||||
}
|
||||
} else {
|
||||
host.moveTemp(v); // the OUT slot takes ownership
|
||||
host.toHeap(v); // the OUT slot takes ownership
|
||||
if (host.currentCoro !== null) {
|
||||
const coro = host.currentCoro;
|
||||
const release = vAdapters(host.shapeHost, yt).release;
|
||||
@@ -601,7 +601,7 @@ export function emitAsyncExpr(
|
||||
// Refcounted channels: the slot's +1 moves out.
|
||||
host.declare(`declare ptr @scr_gen_take_in_ref()`);
|
||||
B.line(`${t} = call ptr @scr_gen_take_in_ref()`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
return host.own({ name: host.adoptHeap(t, e.type), type: e.type });
|
||||
}
|
||||
case "genResume": {
|
||||
// One consumer resume: park the sent value (typed per mode), hop
|
||||
@@ -731,7 +731,7 @@ export function emitAsyncExpr(
|
||||
);
|
||||
host.declare(`declare ptr @scr_gen_take_out_ref(ptr)`);
|
||||
B.line(`${t} = call ptr @scr_gen_take_out_ref(ptr ${gb.name})`);
|
||||
return host.own({ name: t, type: e.type });
|
||||
return host.own({ name: host.adoptHeap(t, e.type), type: e.type });
|
||||
}
|
||||
// Valueless: an empty IN slot reads as undefined on any channel.
|
||||
host.declare(`declare zeroext i1 @scr_gen_step(ptr)`);
|
||||
@@ -748,7 +748,7 @@ export function emitAsyncExpr(
|
||||
const g = host.emitExpr(e.gen); // borrowed for the calls below
|
||||
const sendArg = (store: (aName: string, t: IrType) => void): void => {
|
||||
const a = host.emitExpr(e.arg!);
|
||||
if (isRefCounted(e.arg!.type)) host.moveTemp(a); // the slot takes ownership
|
||||
if (isRefCounted(e.arg!.type)) host.toHeap(a); // the slot takes ownership
|
||||
store(a.name, e.arg!.type);
|
||||
};
|
||||
const parkIn = (name: string, t: IrType): void => {
|
||||
@@ -786,7 +786,7 @@ export function emitAsyncExpr(
|
||||
} else {
|
||||
const a = host.emitExpr(e.arg);
|
||||
const t = e.arg.type;
|
||||
if (isRefCounted(t)) host.moveTemp(a);
|
||||
if (isRefCounted(t)) host.toHeap(a);
|
||||
if (t.kind === "f64" || t.kind === "date") {
|
||||
host.declare(`declare ptr @scr_async_gen_next_f64(ptr, double)`);
|
||||
call = `call ptr @scr_async_gen_next_f64(ptr ${g.name}, double ${a.name})`;
|
||||
@@ -805,7 +805,7 @@ export function emitAsyncExpr(
|
||||
} else {
|
||||
const a = host.emitExpr(e.arg);
|
||||
const t = e.arg.type;
|
||||
if (isRefCounted(t)) host.moveTemp(a);
|
||||
if (isRefCounted(t)) host.toHeap(a);
|
||||
if (t.kind === "f64" || t.kind === "date") {
|
||||
host.declare(`declare ptr @scr_async_gen_return_f64(ptr, double)`);
|
||||
call = `call ptr @scr_async_gen_return_f64(ptr ${g.name}, double ${a.name})`;
|
||||
@@ -823,7 +823,7 @@ export function emitAsyncExpr(
|
||||
"llvm emitter bug: async genResume throw with no payload",
|
||||
);
|
||||
const a = host.emitExpr(e.arg);
|
||||
if (isRefCounted(e.arg.type)) host.moveTemp(a);
|
||||
if (isRefCounted(e.arg.type)) host.toHeap(a);
|
||||
host.emitThrowValue({ name: a.name, type: e.arg.type });
|
||||
host.declare(`declare ptr @scr_async_gen_throw(ptr)`);
|
||||
call = `call ptr @scr_async_gen_throw(ptr ${g.name})`;
|
||||
@@ -873,7 +873,7 @@ export function emitAsyncExpr(
|
||||
if (e.arg === null)
|
||||
throw new InternalCompilerError("llvm emitter bug: genResume throw with no payload");
|
||||
const a = host.emitExpr(e.arg);
|
||||
if (isRefCounted(e.arg.type)) host.moveTemp(a); // the cell takes ownership
|
||||
if (isRefCounted(e.arg.type)) host.toHeap(a); // the cell takes ownership
|
||||
host.emitThrowValue({ name: a.name, type: e.arg.type });
|
||||
host.declare(`declare void @scr_gen_resume_throw(ptr)`);
|
||||
B.line(`call void @scr_gen_resume_throw(ptr ${g.name})`);
|
||||
@@ -955,7 +955,7 @@ export function emitAsyncExpr(
|
||||
host.declare(`declare ptr @${refAwait}(ptr)`);
|
||||
B.line(`${t} = call ptr @${refAwait}(ptr ${pr.name})`);
|
||||
}
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
const out = host.own({ name: host.adoptHeap(t, e.type), type: e.type });
|
||||
host.emitPendingCheck();
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
typeKey,
|
||||
type IrExpr,
|
||||
type IrFunction,
|
||||
classValueConstructor,
|
||||
} from "../../ir/ir.js";
|
||||
import { liveRefView } from "./expr-stream-bridges.js";
|
||||
import { typedRefConstructor, vAdapters } from "./shapes.js";
|
||||
@@ -165,7 +166,7 @@ export function emitCallExpr(
|
||||
// This also preserves snapshots when a later argument mutates its
|
||||
// source, and releases earlier arguments if evaluation throws.
|
||||
args.forEach((a, index) => {
|
||||
if (!borrowed?.has(index)) host.moveTemp(a);
|
||||
if (!borrowed?.has(index)) host.toFrame(a);
|
||||
});
|
||||
const abi = awaitEntry ? undefined : host.int32Abis.get(e.callee);
|
||||
const argList = args
|
||||
@@ -224,14 +225,14 @@ export function emitCallExpr(
|
||||
if (e.type.kind === "void") {
|
||||
B.line(`call void ${target}(${argList})`);
|
||||
if (host.mayThrow.has(e.callee)) host.emitPendingCheck();
|
||||
if (borrowed) host.releaseFrame(host.frames.pop()!);
|
||||
if (borrowed) host.releaseTemps(host.frames.pop()!);
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
const t = B.tmp();
|
||||
if (abi?.union) {
|
||||
B.line(`${t} = call ${SCALAR_UNION_RESULT} ${target}(${argList})`);
|
||||
if (host.mayThrow.has(e.callee)) host.emitPendingCheck();
|
||||
if (borrowed) host.releaseFrame(host.frames.pop()!);
|
||||
if (borrowed) host.releaseTemps(host.frames.pop()!);
|
||||
if (stackResult) return host.stackScalarUnion(t, e.type);
|
||||
return host.own({ name: host.boxScalarUnion(t, e.type), type: e.type });
|
||||
}
|
||||
@@ -239,7 +240,7 @@ export function emitCallExpr(
|
||||
B.line(`${t} = call i32 ${target}(${argList})`);
|
||||
const out = host.int32Result(t, e.type);
|
||||
if (host.mayThrow.has(e.callee)) host.emitPendingCheck(out);
|
||||
if (borrowed) host.releaseFrame(host.frames.pop()!);
|
||||
if (borrowed) host.releaseTemps(host.frames.pop()!);
|
||||
return out;
|
||||
}
|
||||
B.line(`${t} = call ${host.llType(e.type)} ${target}(${argList})`);
|
||||
@@ -249,14 +250,14 @@ export function emitCallExpr(
|
||||
// test the exception first, then own the result before the
|
||||
// argument snapshots that keep it reachable are released.
|
||||
if (host.mayThrow.has(e.callee)) host.emitPendingCheck();
|
||||
out = host.own({ name: host.retainValue(t, e.type), type: e.type });
|
||||
out = host.own({ name: host.retainFrame(t, e.type), type: e.type });
|
||||
} else {
|
||||
out = host.own({ name: t, type: e.type });
|
||||
if (host.mayThrow.has(e.callee)) host.emitPendingCheck(out);
|
||||
}
|
||||
if (borrowed) {
|
||||
host.moveTemp(out);
|
||||
host.releaseFrame(host.frames.pop()!);
|
||||
host.toFrame(out);
|
||||
host.releaseTemps(host.frames.pop()!);
|
||||
return host.own(out);
|
||||
}
|
||||
return out;
|
||||
@@ -718,7 +719,7 @@ export function emitCallExpr(
|
||||
// every mention yields the same pointer, so `f === f` is true.
|
||||
host.fnValues.add(e.fnName);
|
||||
return host.own({
|
||||
name: host.retainValue(`@${mangleFnClosure(e.fnName)}`, e.type),
|
||||
name: host.retainFrame(`@${mangleFnClosure(e.fnName)}`, e.type),
|
||||
type: e.type,
|
||||
});
|
||||
}
|
||||
@@ -880,7 +881,7 @@ export function emitCallExpr(
|
||||
lent?.has(i) && lend[i] ? host.emitReadReceiver(a) : host.emitExpr(a),
|
||||
);
|
||||
args.forEach((a, i) => {
|
||||
if (!lent?.has(i)) host.moveTemp(a);
|
||||
if (!lent?.has(i)) host.toFrame(a);
|
||||
});
|
||||
const argListOf = (values: readonly LlValue[]): string =>
|
||||
[
|
||||
@@ -891,7 +892,7 @@ export function emitCallExpr(
|
||||
const ownedArgs = (): LlValue[] =>
|
||||
lent
|
||||
? args.map((a, i) =>
|
||||
lent.has(i) ? { name: host.retainValue(a.name, a.type), type: a.type } : a,
|
||||
lent.has(i) ? { name: host.retainFrame(a.name, a.type), type: a.type } : a,
|
||||
)
|
||||
: args;
|
||||
const invoke = (target: string, needsReceiver: boolean, values = args): string => {
|
||||
@@ -978,7 +979,7 @@ export function emitCallExpr(
|
||||
}
|
||||
if (e.type.kind === "void") {
|
||||
if (host.indirectMayThrow) host.emitPendingCheck();
|
||||
if (lent) host.releaseFrame(host.frames.pop()!);
|
||||
if (lent) host.releaseTemps(host.frames.pop()!);
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
// The check runs AFTER the result temp joins the frame: an unwind
|
||||
@@ -986,15 +987,15 @@ export function emitCallExpr(
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
if (host.indirectMayThrow) host.emitPendingCheck();
|
||||
if (lent) {
|
||||
host.moveTemp(out);
|
||||
host.releaseFrame(host.frames.pop()!);
|
||||
host.toFrame(out);
|
||||
host.releaseTemps(host.frames.pop()!);
|
||||
return host.own(out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
case "selfRef":
|
||||
// The running closure itself (env is borrowed; the result owned).
|
||||
return host.own({ name: host.retainValue("%sc_env", e.type), type: e.type });
|
||||
return host.own({ name: host.retainFrame("%sc_env", e.type), type: e.type });
|
||||
case "new": {
|
||||
// Allocate (fields zeroed, vt stamped), then run the ctor. The
|
||||
// ctor owns and releases its `this` param like any callee, so it
|
||||
@@ -1034,7 +1035,7 @@ export function emitCallExpr(
|
||||
});
|
||||
return host.own({ name: value, type: e.type });
|
||||
}
|
||||
return host.own({ name: host.retainValue(`@${sym}`, e.type), type: e.type });
|
||||
return host.own({ name: host.retainFrame(`@${sym}`, e.type), type: e.type });
|
||||
}
|
||||
case "newValue": {
|
||||
// Construction through a class VALUE: call the class object's
|
||||
@@ -1043,20 +1044,21 @@ export function emitCallExpr(
|
||||
if (e.callee.type.kind !== "classval")
|
||||
throw new InternalCompilerError("llvm emitter bug: newValue on non-classval callee");
|
||||
const cls = e.callee.type.className;
|
||||
const ctor = host.fnByName.get(`%${cls}.constructor`);
|
||||
const valueCtor = classValueConstructor(host.classMetaOf(cls).def);
|
||||
const ctor = host.fnByName.get(valueCtor.fn);
|
||||
if (!ctor)
|
||||
throw new InternalCompilerError(
|
||||
`llvm emitter bug: newValue on ${cls} without a constructor`,
|
||||
);
|
||||
const callee = host.emitExpr(e.callee);
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
for (const a of args) host.moveTemp(a); // the constructor owns its params
|
||||
for (const a of args) host.toFrame(a); // the constructor owns its params
|
||||
const ctorp = B.tmp();
|
||||
const thunk = B.tmp();
|
||||
B.line(`${ctorp} = getelementptr inbounds %ScrClassObj, ptr ${callee.name}, i64 0, i32 3`);
|
||||
B.line(`${thunk} = load ptr, ptr ${ctorp}`);
|
||||
const argList = args
|
||||
.map((a, i) => `${host.llType(ctor.params[i + 1]!.type)} ${a.name}`)
|
||||
.map((a, i) => `${host.llType(ctor.params[i + valueCtor.firstArg]!.type)} ${a.name}`)
|
||||
.join(", ");
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = call ptr ${thunk}(ptr ${callee.name}${argList ? ", " + argList : ""})`);
|
||||
@@ -1121,7 +1123,7 @@ export function emitCallExpr(
|
||||
// One ScrPromise* either way — ownership transfers, type-only
|
||||
//.
|
||||
const v = host.emitExpr(e.value);
|
||||
host.moveTemp(v);
|
||||
host.toFrame(v);
|
||||
return host.own({ name: v.name, type: e.type });
|
||||
}
|
||||
case "upcast":
|
||||
@@ -1134,7 +1136,7 @@ export function emitCallExpr(
|
||||
const v = host.emitExpr(e.value);
|
||||
if (e.kind === "downcast" && e.type.kind === "object" && v.type.kind === "object")
|
||||
emitDowncastGuard(host, v.name, v.type.className, e.type.className);
|
||||
if (isRefCounted(v.type)) host.moveTemp(v);
|
||||
if (isRefCounted(v.type)) host.toFrame(v);
|
||||
return host.own({ name: v.name, type: e.type });
|
||||
}
|
||||
case "instanceOf": {
|
||||
@@ -1188,7 +1190,7 @@ export function emitCallExpr(
|
||||
borrowed.has(index) && inputs[index] ? host.emitReadReceiver(a) : host.emitExpr(a),
|
||||
);
|
||||
args.forEach((a, index) => {
|
||||
if (!borrowed.has(index)) host.moveTemp(a);
|
||||
if (!borrowed.has(index)) host.toFrame(a);
|
||||
});
|
||||
const recv = args[0]!.name;
|
||||
const vtp = B.tmp();
|
||||
@@ -1239,7 +1241,7 @@ export function emitCallExpr(
|
||||
B.startBlock(done);
|
||||
if (e.type.kind === "void") {
|
||||
if (host.mayThrowMethods.has(e.method)) host.emitPendingCheck();
|
||||
if (borrowed.size > 0) host.releaseFrame(host.frames.pop()!);
|
||||
if (borrowed.size > 0) host.releaseTemps(host.frames.pop()!);
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
const t = B.tmp();
|
||||
@@ -1247,8 +1249,8 @@ export function emitCallExpr(
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
if (host.mayThrowMethods.has(e.method)) host.emitPendingCheck();
|
||||
if (borrowed.size > 0) {
|
||||
host.moveTemp(out);
|
||||
host.releaseFrame(host.frames.pop()!);
|
||||
host.toFrame(out);
|
||||
host.releaseTemps(host.frames.pop()!);
|
||||
return host.own(out);
|
||||
}
|
||||
return out;
|
||||
@@ -1256,7 +1258,7 @@ export function emitCallExpr(
|
||||
if (e.type.kind === "void") {
|
||||
B.line(`call void ${fn}(${argList})`);
|
||||
if (host.mayThrowMethods.has(e.method)) host.emitPendingCheck();
|
||||
if (borrowed.size > 0) host.releaseFrame(host.frames.pop()!);
|
||||
if (borrowed.size > 0) host.releaseTemps(host.frames.pop()!);
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
const t = B.tmp();
|
||||
@@ -1264,8 +1266,8 @@ export function emitCallExpr(
|
||||
const out = host.own({ name: t, type: e.type });
|
||||
if (host.mayThrowMethods.has(e.method)) host.emitPendingCheck();
|
||||
if (borrowed.size > 0) {
|
||||
host.moveTemp(out);
|
||||
host.releaseFrame(host.frames.pop()!);
|
||||
host.toFrame(out);
|
||||
host.releaseTemps(host.frames.pop()!);
|
||||
return host.own(out);
|
||||
}
|
||||
return out;
|
||||
@@ -1392,7 +1394,7 @@ export function emitConstruction(
|
||||
return host.emitExpr(a);
|
||||
});
|
||||
args.forEach((a, i) => {
|
||||
if (!borrowed.has(i + 1)) host.moveTemp(a);
|
||||
if (!borrowed.has(i + 1)) host.toFrame(a);
|
||||
});
|
||||
const self = borrowed.has(0)
|
||||
? o
|
||||
@@ -1408,11 +1410,11 @@ export function emitConstruction(
|
||||
`call void @${mangleBorrowedFunction(ctorName)}${host.int32Abis.has(ctorName) ? INT32_ABI_SUFFIX : ""}(${argList})`,
|
||||
);
|
||||
if (host.mayThrow.has(ctorName)) host.emitPendingCheck();
|
||||
host.releaseFrame(host.frames.pop()!);
|
||||
host.releaseTemps(host.frames.pop()!);
|
||||
return;
|
||||
}
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
for (const a of args) host.moveTemp(a);
|
||||
for (const a of args) host.toFrame(a);
|
||||
const r = B.tmp();
|
||||
B.line(`${r} = call ptr @${mangleClassRetain(e.className)}(ptr ${o})`);
|
||||
const argList = [`ptr ${r}`, ...ctorArguments(host, ctorName, ctor, args, e.args)].join(", ");
|
||||
@@ -1454,8 +1456,8 @@ function emitNumericIndexRead(
|
||||
B.startBlock(slow);
|
||||
host.frames.push([]);
|
||||
const read = emitCallExpr(host, e, false, false, undefined, true);
|
||||
host.moveTemp(read);
|
||||
host.releaseFrame(host.frames.pop()!);
|
||||
host.toFrame(read);
|
||||
host.releaseTemps(host.frames.pop()!);
|
||||
const slowLabel = B.currentLabel();
|
||||
B.terminate(`br label %${join}`);
|
||||
B.startBlock(join);
|
||||
|
||||
@@ -118,7 +118,7 @@ export function emitArrayValues(
|
||||
sharedBuffer?: string,
|
||||
): string {
|
||||
const B = host.B;
|
||||
if (acc === "ref") values.forEach((value) => host.moveTemp(value));
|
||||
if (acc === "ref") values.forEach((value) => host.toHeap(value));
|
||||
if (values.length === 0) {
|
||||
host.declare("declare double @scr_arr_len(ptr)");
|
||||
const result = B.tmp();
|
||||
@@ -219,7 +219,7 @@ function emitDiscarded(host: LlvmEmitterContext, e: IrExpr): void {
|
||||
B.startBlock(label);
|
||||
host.frames.push([]);
|
||||
emitDiscarded(host, arm);
|
||||
host.releaseFrame(host.frames.pop()!);
|
||||
host.releaseTemps(host.frames.pop()!);
|
||||
B.br(lj);
|
||||
}
|
||||
B.startBlock(lj);
|
||||
@@ -235,7 +235,7 @@ function emitDiscarded(host: LlvmEmitterContext, e: IrExpr): void {
|
||||
const start = host.emitExpr(e.args[0]!);
|
||||
const count = e.args[1] ? host.emitExpr(e.args[1]).name : F64_INF;
|
||||
const values = items.map((item) => host.emitExpr(item));
|
||||
if (acc === "ref") values.forEach((value) => host.moveTemp(value));
|
||||
if (acc === "ref") values.forEach((value) => host.toHeap(value));
|
||||
let buffer = "null";
|
||||
if (values.length > 0) {
|
||||
buffer = B.tmp();
|
||||
@@ -1069,7 +1069,7 @@ export function emitMapNew(host: LlvmEmitterContext, e: IrExpr & { kind: "mapNew
|
||||
for (const pair of e.seed ?? []) {
|
||||
const k = emitBorrowedInput(host, pair.key);
|
||||
const v = host.emitExpr(pair.value);
|
||||
if (vAcc === "ref") host.moveTemp(v); // the value MOVES in
|
||||
if (vAcc === "ref") host.toHeap(v); // the value MOVES in
|
||||
host.mapSet(m, kAcc, vAcc, k.name, v.name);
|
||||
}
|
||||
return out;
|
||||
@@ -1370,7 +1370,7 @@ export function emitMapLikeIntrinsic(
|
||||
const k = emitBorrowedInput(host, e.args[0]!);
|
||||
const v = host.emitExpr(e.args[1]!);
|
||||
const vAcc = elemAccess(receiverType.value);
|
||||
if (vAcc === "ref") host.moveTemp(v);
|
||||
if (vAcc === "ref") host.toHeap(v);
|
||||
const ordinary = (): void => host.mapSet(r.name, kAcc, vAcc, k.name, v.name);
|
||||
if (kAcc === "f64" && vAcc !== "ref" && host.readNumberMapWrites.has(e)) {
|
||||
const bits = B.tmp();
|
||||
|
||||
@@ -58,7 +58,7 @@ export function emitControlExpr(
|
||||
// and the right runs in its own frame.
|
||||
const ty = host.llType(e.type);
|
||||
const l = host.emitExpr(e.left);
|
||||
host.moveTemp(l);
|
||||
host.toFrame(l);
|
||||
const slot = B.slot();
|
||||
B.entryAllocas.push(`${slot} = alloca ${ty}`);
|
||||
B.line(`store ${ty} ${l.name}, ptr ${slot}`);
|
||||
@@ -73,7 +73,7 @@ export function emitControlExpr(
|
||||
if (e.op === "&&") B.condBr(truthy, rightLabel, joinLabel);
|
||||
else B.condBr(truthy, joinLabel, rightLabel);
|
||||
B.startBlock(rightLabel);
|
||||
if (isRefCounted(e.type)) host.releaseValue(l.name, e.type);
|
||||
if (isRefCounted(e.type)) host.releaseFrame(l.name, e.type);
|
||||
const rightInteger = host.emitBranchInto(slot, e.right, integerSlot);
|
||||
B.br(joinLabel);
|
||||
B.startBlock(joinLabel);
|
||||
@@ -157,7 +157,7 @@ export function emitControlExpr(
|
||||
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.releaseTemps(host.frames.pop()!);
|
||||
host.chainSlots.delete(e.id);
|
||||
B.br(lj);
|
||||
B.startBlock(lj);
|
||||
@@ -203,7 +203,7 @@ export function emitControlExpr(
|
||||
bindPresent();
|
||||
host.frames.push([]);
|
||||
host.emitExpr(e.body);
|
||||
host.releaseFrame(host.frames.pop()!);
|
||||
host.releaseTemps(host.frames.pop()!);
|
||||
host.chainSlots.delete(e.id);
|
||||
B.br(lj);
|
||||
B.startBlock(lj);
|
||||
@@ -272,7 +272,7 @@ export function emitControlExpr(
|
||||
const v = B.tmp();
|
||||
B.line(`${v} = load ${host.llType(bound.type)}, ptr ${bound.name}`);
|
||||
if (!isRefCounted(e.type)) return { name: v, type: e.type };
|
||||
return host.own({ name: host.retainValue(v, e.type), type: e.type });
|
||||
return host.own({ name: host.retainFrame(v, e.type), type: e.type });
|
||||
}
|
||||
case "orDefault": {
|
||||
// `u || d` narrowed to the single non-unit arm: nullish's dance
|
||||
@@ -309,7 +309,7 @@ export function emitControlExpr(
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
const l = host.emitExpr(e.left);
|
||||
host.moveTemp(l);
|
||||
host.toFrame(l);
|
||||
const ty = host.llType(e.type);
|
||||
const slot = B.slot();
|
||||
B.entryAllocas.push(`${slot} = alloca ${ty}`);
|
||||
@@ -337,11 +337,11 @@ export function emitControlExpr(
|
||||
B.line(
|
||||
`store ${ty} ${host.unionExtract(l.name, e.left.type.unionId, e.type)}, ptr ${slot}`,
|
||||
);
|
||||
host.releaseValue(l.name, e.left.type);
|
||||
host.releaseFrame(l.name, e.left.type);
|
||||
}
|
||||
B.br(lj);
|
||||
B.startBlock(lf);
|
||||
host.releaseValue(l.name, e.left.type);
|
||||
host.releaseFrame(l.name, e.left.type);
|
||||
host.emitBranchInto(slot, e.right);
|
||||
B.br(lj);
|
||||
B.startBlock(lj);
|
||||
@@ -359,7 +359,7 @@ export function emitControlExpr(
|
||||
// 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.toFrame(l);
|
||||
host.declare(`declare zeroext i1 @scr_dyn_is_nullish(ptr)`);
|
||||
const slot = B.slot();
|
||||
B.entryAllocas.push(`${slot} = alloca ptr`);
|
||||
@@ -370,7 +370,7 @@ export function emitControlExpr(
|
||||
const lj = B.newLabel("nuld.j");
|
||||
B.condBr(isN, lu, lv);
|
||||
B.startBlock(lu);
|
||||
host.releaseValue(l.name, e.left.type);
|
||||
host.releaseFrame(l.name, e.left.type);
|
||||
host.emitBranchInto(slot, e.right);
|
||||
B.br(lj);
|
||||
B.startBlock(lv);
|
||||
@@ -426,7 +426,7 @@ export function emitControlExpr(
|
||||
return host.own({ name: t, type: e.type });
|
||||
}
|
||||
const l = host.emitExpr(e.left);
|
||||
host.moveTemp(l);
|
||||
host.toFrame(l);
|
||||
const ty = host.llType(e.type);
|
||||
const slot = B.slot();
|
||||
B.entryAllocas.push(`${slot} = alloca ${ty}`);
|
||||
@@ -436,7 +436,7 @@ export function emitControlExpr(
|
||||
const lj = B.newLabel("nul.j");
|
||||
B.condBr(isUnit, lu, lv);
|
||||
B.startBlock(lu);
|
||||
host.releaseValue(l.name, e.left.type);
|
||||
host.releaseFrame(l.name, e.left.type);
|
||||
host.emitBranchInto(slot, e.right);
|
||||
B.br(lj);
|
||||
B.startBlock(lv);
|
||||
@@ -445,7 +445,7 @@ export function emitControlExpr(
|
||||
B.line(`store ${ty} ${l.name}, ptr ${slot}`);
|
||||
} else {
|
||||
B.line(`store ${ty} ${host.unionExtract(l.name, def.id, e.type)}, ptr ${slot}`);
|
||||
host.releaseValue(l.name, e.left.type);
|
||||
host.releaseFrame(l.name, e.left.type);
|
||||
}
|
||||
B.br(lj);
|
||||
B.startBlock(lj);
|
||||
@@ -533,7 +533,7 @@ export function emitOptionalChainReceiver(
|
||||
const isUnit = host.tagInSet(r.name, def.id, unitTags);
|
||||
const extract = (): string =>
|
||||
multiple
|
||||
? host.retainValue(r.name, narrowed)
|
||||
? host.retainFrame(r.name, narrowed)
|
||||
: borrowBind
|
||||
? host.unionPeek(r.name, def.id)
|
||||
: host.unionExtract(r.name, def.id, narrowed);
|
||||
|
||||
@@ -161,7 +161,7 @@ export function emitDynamicExpr(
|
||||
);
|
||||
host.emitPendingCheck();
|
||||
} else {
|
||||
host.moveTemp(v);
|
||||
host.toHeap(v);
|
||||
B.line(`call void @scr_dyn_arr_push(ptr ${pack}, ptr ${v.name})`);
|
||||
}
|
||||
});
|
||||
@@ -253,7 +253,7 @@ export function emitDynamicExpr(
|
||||
const out = host.own({ name: arr, type: e.type });
|
||||
for (const el of e.elems) {
|
||||
const v = host.emitExpr(el);
|
||||
host.moveTemp(v);
|
||||
host.toHeap(v);
|
||||
B.line(`call void @scr_dyn_arr_push(ptr ${arr}, ptr ${v.name})`);
|
||||
}
|
||||
return out;
|
||||
@@ -296,7 +296,7 @@ export function emitDynamicExpr(
|
||||
return host.own({ name: host.unitInstanceRef(e.unionId, e.tag), type: e.type });
|
||||
}
|
||||
const v = host.emitExpr(e.value);
|
||||
if (isRefCounted(arm)) host.moveTemp(v);
|
||||
if (isRefCounted(arm)) host.toHeap(v);
|
||||
return host.own({ name: host.unionNewOwned(e.unionId, e.tag, v), type: e.type });
|
||||
}
|
||||
case "unionNarrow": {
|
||||
@@ -353,7 +353,7 @@ export function emitDynamicExpr(
|
||||
const value = nullable
|
||||
? host.nullableToOwnedUnion(v, nullable)
|
||||
: isRefCounted(e.type)
|
||||
? host.retainValue(v, e.type)
|
||||
? host.retainFrame(v, e.type)
|
||||
: v;
|
||||
B.line(`store ${ty} ${value}, ptr ${slot}`);
|
||||
B.br(join);
|
||||
@@ -465,7 +465,7 @@ export function emitDynamicExpr(
|
||||
const owned = inline
|
||||
? host.inlineFieldUnion(ptr, ft)
|
||||
: isRefCounted(ft)
|
||||
? host.retainValue(v, ft)
|
||||
? host.retainFrame(v, ft)
|
||||
: v;
|
||||
B.line(`store ${ty} ${owned}, ptr ${slot}`);
|
||||
B.br(join);
|
||||
@@ -487,7 +487,7 @@ export function emitDynamicExpr(
|
||||
ft.kind === "f64" || ft.kind === "bool"
|
||||
? host.unionNewOwned(e.type.unionId, tag, { name: v, type: ft })
|
||||
: host.unionNewOwned(e.type.unionId, tag, {
|
||||
name: host.retainValue(v, ft),
|
||||
name: host.retainHeap(v, ft),
|
||||
type: ft,
|
||||
});
|
||||
B.line(`store ptr ${wrapped}, ptr ${slot}`);
|
||||
@@ -1087,7 +1087,7 @@ export function emitDynamicExpr(
|
||||
B.line(`${pp} = getelementptr inbounds %ScrCaught, ptr ${c.name}, i64 0, i32 4`);
|
||||
B.line(`${payload} = load ptr, ptr ${pp}`);
|
||||
if (e.type.kind === "string") {
|
||||
return host.own({ name: host.retainValue(payload, e.type), type: e.type });
|
||||
return host.own({ name: host.retainFrame(payload, e.type), type: e.type });
|
||||
}
|
||||
if (e.type.kind === "object") {
|
||||
// Retain through the snapshot's own entry point (the payload's
|
||||
|
||||
@@ -47,11 +47,11 @@ export function emitLiteralExpr(
|
||||
return { name: e.value ? "true" : "false", type: e.type };
|
||||
case "strLit": {
|
||||
const sym = host.internLiteral(e.value);
|
||||
return host.own({ name: host.retainValue(sym, e.type), type: e.type });
|
||||
return host.own({ name: host.retainFrame(sym, e.type), type: e.type });
|
||||
}
|
||||
case "moduleNsRef": {
|
||||
const sym = host.internLiteral(`module:${e.moduleId}`);
|
||||
return host.own({ name: host.retainValue(sym, e.type), type: e.type });
|
||||
return host.own({ name: host.retainFrame(sym, e.type), type: e.type });
|
||||
}
|
||||
case "unitLit":
|
||||
// unitLits are consumed inline by the unionWrap case (a unit arm is
|
||||
@@ -110,10 +110,10 @@ export function emitLiteralExpr(
|
||||
const box = host.loadBox(b.slot);
|
||||
if (b.local!.tdz === true) {
|
||||
const v = host.tdzBoxRead(box, e.type, b.local!.name);
|
||||
return host.own({ name: v, type: e.type });
|
||||
return host.own({ name: host.adoptHeap(v, e.type), type: e.type });
|
||||
}
|
||||
const v = host.boxGet(box, e.type);
|
||||
return host.own({ name: v, type: e.type });
|
||||
return host.own({ name: host.adoptHeap(v, e.type), type: e.type });
|
||||
}
|
||||
const t = B.tmp();
|
||||
B.line(`${t} = load ${host.llType(b.type)}, ptr ${b.slot}`);
|
||||
@@ -131,7 +131,7 @@ export function emitLiteralExpr(
|
||||
return host.markImmortal({ name: t, type: e.type });
|
||||
// The binding's last read takes over its reference (lastUseRead).
|
||||
if (host.takeLastUseMove(e, b.slot, b.type)) return host.own({ name: t, type: e.type });
|
||||
return host.own({ name: host.retainValue(t, e.type), type: e.type });
|
||||
return host.own({ name: host.retainFrame(t, e.type), type: e.type });
|
||||
}
|
||||
return { name: t, type: e.type };
|
||||
}
|
||||
@@ -488,8 +488,11 @@ export function emitOperatorExpr(
|
||||
if (isRefCounted(b.type)) {
|
||||
const old = B.tmp();
|
||||
B.line(`${old} = load ptr, ptr ${b.slot}`);
|
||||
host.releaseValue(old, b.type);
|
||||
B.line(`store ptr ${host.retainValue(v.name, v.type)}, ptr ${b.slot}`);
|
||||
if (b.kind === "global") host.releaseHeap(old, b.type);
|
||||
else host.releaseFrame(old, b.type);
|
||||
B.line(
|
||||
`store ptr ${b.kind === "global" ? host.retainHeap(v.name, v.type) : host.retainFrame(v.name, v.type)}, ptr ${b.slot}`,
|
||||
);
|
||||
} else {
|
||||
B.line(`store ${host.llType(b.type)} ${v.name}, ptr ${b.slot}`);
|
||||
host.storeIntegerView(e.localId, v, e.value);
|
||||
@@ -632,12 +635,12 @@ export function emitStringExpr(
|
||||
case "undefinedT":
|
||||
case "nullT": {
|
||||
const lit = host.internLiteral(arm.kind === "undefinedT" ? "undefined" : "null");
|
||||
B.line(`store ptr ${host.retainValue(lit, e.type)}, ptr ${slot}`);
|
||||
B.line(`store ptr ${host.retainFrame(lit, e.type)}, ptr ${slot}`);
|
||||
break;
|
||||
}
|
||||
case "string":
|
||||
B.line(
|
||||
`store ptr ${host.retainValue(host.unionPeek(v.name, def.id), e.type)}, ptr ${slot}`,
|
||||
`store ptr ${host.retainFrame(host.unionPeek(v.name, def.id), e.type)}, ptr ${slot}`,
|
||||
);
|
||||
break;
|
||||
case "f64": {
|
||||
@@ -691,7 +694,7 @@ export function emitStringExpr(
|
||||
// String(record) / `${record}`: Object.prototype.toString's
|
||||
// constant — the interned literal, retained like a strLit.
|
||||
const sym = host.internLiteral("[object Object]");
|
||||
return host.own({ name: host.retainValue(sym, e.type), type: e.type });
|
||||
return host.own({ name: host.retainFrame(sym, e.type), type: e.type });
|
||||
}
|
||||
if (v.type.kind === "caught") {
|
||||
// String(e) / `${e}` on a catch binding: JS's String() over the
|
||||
@@ -748,7 +751,7 @@ export function emitStringExpr(
|
||||
};
|
||||
host.templateStringsInstances.set(e.key, inst);
|
||||
}
|
||||
return host.own({ name: host.retainValue(`@${inst.sym}`, e.type), type: e.type });
|
||||
return host.own({ name: host.retainFrame(`@${inst.sym}`, e.type), type: e.type });
|
||||
}
|
||||
case "regexIntrinsic":
|
||||
return host.emitRegexIntrinsic(e);
|
||||
@@ -975,7 +978,7 @@ export function emitRecordExpr(
|
||||
}
|
||||
const v = host.loadField(ptr, type, false);
|
||||
if (isRefCounted(e.type))
|
||||
return host.own({ name: host.retainValue(v, e.type), type: e.type });
|
||||
return host.own({ name: host.retainFrame(v, e.type), type: e.type });
|
||||
return { name: v, type: e.type };
|
||||
}
|
||||
case "recordGet": {
|
||||
@@ -984,7 +987,7 @@ export function emitRecordExpr(
|
||||
if (inline) return host.own({ name: host.inlineFieldUnion(ptr, type), type: e.type });
|
||||
const v = host.loadRecordField(ptr, type, false);
|
||||
if (isRefCounted(e.type))
|
||||
return host.own({ name: host.retainValue(v, e.type), type: e.type });
|
||||
return host.own({ name: host.retainFrame(v, e.type), type: e.type });
|
||||
return { name: v, type: e.type };
|
||||
}
|
||||
case "recordLit": {
|
||||
@@ -1090,7 +1093,7 @@ export function emitRecordLitFields(
|
||||
if (f.overflow) {
|
||||
const lit = host.internLiteral(f.name);
|
||||
const acc = v.type.kind === "f64" ? "f64" : v.type.kind === "bool" ? "bool" : "ref";
|
||||
if (acc === "ref") host.moveTemp(v);
|
||||
if (acc === "ref") host.toHeap(v);
|
||||
const ovf = host.recordOvfPtr(rec, shapeId);
|
||||
const argTy = acc === "f64" ? "double" : acc === "bool" ? "i1 zeroext" : "ptr";
|
||||
host.declare(`declare void @scr_map_set_str_${acc}(ptr, ptr, ${argTy})`);
|
||||
|
||||
@@ -41,7 +41,7 @@ export function emitUnionWiden(
|
||||
B.br(join);
|
||||
});
|
||||
B.startBlock(join);
|
||||
if (owned) host.releaseValue(value, { kind: "union", unionId: fromId });
|
||||
if (owned) host.releaseFrame(value, { kind: "union", unionId: fromId });
|
||||
const result = B.tmp();
|
||||
B.line(`${result} = load ptr, ptr ${slot}`);
|
||||
return result;
|
||||
@@ -58,7 +58,7 @@ export function emitUnionRetype(
|
||||
e: IrExpr & { kind: "unionRetype" },
|
||||
): LlValue {
|
||||
const value = host.emitExpr(e.value);
|
||||
host.moveTemp(value);
|
||||
host.toFrame(value);
|
||||
const from = host.nullableUnions.get(e.fromId);
|
||||
const to = host.nullableUnions.get(e.unionId);
|
||||
if (from && to) {
|
||||
@@ -131,7 +131,7 @@ function rebuildRetype(
|
||||
B.br(join);
|
||||
});
|
||||
B.startBlock(join);
|
||||
host.releaseValue(value, { kind: "union", unionId: e.fromId });
|
||||
host.releaseFrame(value, { kind: "union", unionId: e.fromId });
|
||||
const result = B.tmp();
|
||||
B.line(`${result} = load ptr, ptr ${slot}`);
|
||||
return result;
|
||||
@@ -147,7 +147,9 @@ function directRegexOperandMethod(method: string): boolean {
|
||||
case "match":
|
||||
case "search":
|
||||
case "matchAll":
|
||||
case "matchAllCaptures":
|
||||
case "matchAllInto":
|
||||
case "matchAllCapturesInto":
|
||||
case "replace":
|
||||
case "replaceAll":
|
||||
case "split":
|
||||
@@ -192,7 +194,7 @@ export function emitRegexIntrinsic(
|
||||
} else {
|
||||
values = preservesRegexInputs(method)
|
||||
? emitBorrowedInputs(host, inputs)
|
||||
: method === "matchAllInto"
|
||||
: method === "matchAllInto" || method === "matchAllCapturesInto"
|
||||
? inputs.map((a) => emitBorrowedInput(host, a))
|
||||
: inputs.map((a) => host.emitExpr(a));
|
||||
}
|
||||
@@ -216,6 +218,9 @@ export function emitRegexIntrinsic(
|
||||
// Every match drained eagerly into a fresh +1 string[][]; throws
|
||||
// Node's TypeError on a non-global regex (catchable).
|
||||
return fallible("scr_regex_match_all", `ptr ${r.name}, ptr ${args[0]!.name}`);
|
||||
case "matchAllCaptures":
|
||||
// matchAll whose rows carry no index, input or groups properties.
|
||||
return fallible("scr_regex_match_all_captures", `ptr ${r.name}, ptr ${args[0]!.name}`);
|
||||
case "matchAllInto":
|
||||
// matchAll's companion-index form: args[1] (a number[]) also
|
||||
// receives each match's UTF-16 start index.
|
||||
@@ -223,6 +228,11 @@ export function emitRegexIntrinsic(
|
||||
"scr_regex_match_all_into",
|
||||
`ptr ${r.name}, ptr ${args[0]!.name}, ptr ${args[1]!.name}`,
|
||||
);
|
||||
case "matchAllCapturesInto":
|
||||
return fallible(
|
||||
"scr_regex_match_all_captures_into",
|
||||
`ptr ${r.name}, ptr ${args[0]!.name}, ptr ${args[1]!.name}`,
|
||||
);
|
||||
case "replaceAll":
|
||||
return fallible(
|
||||
"scr_regex_replace_all",
|
||||
@@ -476,10 +486,10 @@ export function keyedRecordReadInto(
|
||||
if (!typeEquals(vt, resultType)) {
|
||||
throw new LlvmUnsupportedError(`recordKeyGet:narrow:${vt.kind}`, loc);
|
||||
}
|
||||
return owned || !isRefCounted(vt) ? expr : host.retainValue(expr, vt);
|
||||
return owned || !isRefCounted(vt) ? expr : host.retainFrame(expr, vt);
|
||||
}
|
||||
if (typeEquals(vt, resultType)) {
|
||||
return owned ? expr : host.retainValue(expr, vt);
|
||||
return owned ? expr : host.retainFrame(expr, vt);
|
||||
}
|
||||
if (vt.kind === "union" && resultType.kind === "union") {
|
||||
return emitUnionWiden(host, expr, vt.unionId, resultType.unionId, owned);
|
||||
@@ -490,7 +500,7 @@ export function keyedRecordReadInto(
|
||||
if (vt.kind === "f64" || vt.kind === "bool") {
|
||||
return host.unionNewOwned(def.id, tag, { name: expr, type: vt });
|
||||
}
|
||||
const payload = owned ? expr : host.retainValue(expr, vt);
|
||||
const payload = owned ? expr : host.retainHeap(expr, vt);
|
||||
return host.unionNewOwned(def.id, tag, { name: payload, type: vt });
|
||||
};
|
||||
for (const f of overflowOnly ? [] : shape.fields) {
|
||||
|
||||
@@ -708,6 +708,10 @@ export function streamTypedRefMaterializeAdapter(
|
||||
}
|
||||
// Publish both symbols before their bodies: class-array and callback
|
||||
// converters can request this same adapter while emitting the commit.
|
||||
// A recursive field type binds capsules of this class there too, so the
|
||||
// class view ops are published first.
|
||||
const opsMeta = isDynTypedRefType(t) ? host.classMeta.get(t.className) : undefined;
|
||||
if (opsMeta && liveClassOpsEligible(opsMeta)) adapter.classOps = `@${snapshot}_cops`;
|
||||
adapter.commit = `@${snapshot}_commit`;
|
||||
adapter.commit = host.streamTypedRefCommitAdapter(t, snapshot);
|
||||
const B = new BlockBuilder();
|
||||
@@ -719,10 +723,7 @@ export function streamTypedRefMaterializeAdapter(
|
||||
`llvm emitter bug: typed-ref materialize of unknown class ${t.className}`,
|
||||
);
|
||||
}
|
||||
if (liveClassOpsEligible(meta)) {
|
||||
adapter.classOps = `@${snapshot}_cops`;
|
||||
liveClassOps(host, snapshot, meta);
|
||||
}
|
||||
if (adapter.classOps !== undefined) liveClassOps(host, snapshot, meta);
|
||||
if (meta.hierarchy && meta.children.length) {
|
||||
const vtSlot = B.tmp(),
|
||||
vt = B.tmp(),
|
||||
|
||||
@@ -85,7 +85,7 @@ export function callbackAdapter(
|
||||
symbol: string,
|
||||
params: string[],
|
||||
): AbiArgument[] {
|
||||
host.moveTemp(value);
|
||||
host.toHeap(value);
|
||||
host.declare(`declare void @${symbol}(${params.join(", ")})`);
|
||||
return [ptr(value.name), ptr(`@${symbol}`)];
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export function emitConnectListener(host: LlvmEmitterContext, e: LibCallExpr): L
|
||||
type = callbackType(cb),
|
||||
arity = type.params.length,
|
||||
second = type.params[1];
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
let h1: string,
|
||||
h2 = "null";
|
||||
if (second?.kind === "union") {
|
||||
|
||||
@@ -131,7 +131,7 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L
|
||||
case "dgram.bindCb":
|
||||
case "dgram.connect":
|
||||
case "dgram.connectCb":
|
||||
if (args[3] !== undefined) host.moveTemp(args[3]);
|
||||
if (args[3] !== undefined) host.toHeap(args[3]);
|
||||
return finishRuntimeCall(
|
||||
host,
|
||||
e,
|
||||
@@ -140,7 +140,7 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L
|
||||
);
|
||||
case "dgram.close":
|
||||
case "dgram.closeCb":
|
||||
if (args[1] !== undefined) host.moveTemp(args[1]);
|
||||
if (args[1] !== undefined) host.toHeap(args[1]);
|
||||
return finishRuntimeCall(host, e, "scr_dgram_close", [a[0]!, a[1] ?? ptr()]);
|
||||
case "dgram.address": {
|
||||
const type = e.type;
|
||||
@@ -151,7 +151,7 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L
|
||||
const record = host.B.tmp();
|
||||
host.B.line(`${record} = call ptr @${mangleRecordNew(type.shapeId)}()`);
|
||||
const out = host.own({ name: record, type });
|
||||
host.moveTemp(ip);
|
||||
host.toHeap(ip);
|
||||
const shape = host.recordsById.get(type.shapeId);
|
||||
if (shape === undefined) throw new InternalCompilerError("missing datagram address shape");
|
||||
for (const field of ["address", "family", "port"]) {
|
||||
@@ -185,7 +185,7 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L
|
||||
? "scr_dgram_msg_thunk1"
|
||||
: messageAdapter(host, type.params[1]!);
|
||||
// Program-specific adapters are definitions, so do not redeclare them.
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
if (type.params.length < 2)
|
||||
host.declare(`declare void @${adapter}(ptr, ptr, ptr, ptr, double, double)`);
|
||||
return finishRuntimeCall(host, e, "scr_dgram_on_message", [
|
||||
@@ -213,7 +213,7 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L
|
||||
case "dgram.onListening":
|
||||
case "dgram.onClose":
|
||||
case "dgram.onConnect":
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
return finishRuntimeCall(
|
||||
host,
|
||||
e,
|
||||
@@ -227,7 +227,7 @@ export function emitDatagramLibCall(host: LlvmEmitterContext, e: LibCallExpr): L
|
||||
case "dns.lookup": {
|
||||
const cb = args[2]!,
|
||||
symbol = lookupAdapter(host, cb);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
if (callbackType(cb).params.length === 0)
|
||||
host.declare("declare void @scr_dns_thunk0(ptr, ptr, ptr, double)");
|
||||
return finishRuntimeCall(host, e, "scr_dns_lookup", [...a, ptr("@" + symbol)]);
|
||||
|
||||
@@ -92,7 +92,7 @@ export function emitIoLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: rl.question callback not a func");
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.moveTemp(args[2]!);
|
||||
host.toHeap(args[2]!);
|
||||
const adapter =
|
||||
cbT.params.length === 0
|
||||
? "scr_rl_answer_thunk0"
|
||||
@@ -165,7 +165,7 @@ export function emitIoLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue
|
||||
const ev = host.emitExpr(evArg);
|
||||
const cb = host.emitExpr(e.args[2]!);
|
||||
const once = host.emitExpr(e.args[3]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
let adapter = "null";
|
||||
if (evArg.value === 0 || evArg.value === 4) {
|
||||
const param = cbT.params[0];
|
||||
@@ -287,7 +287,7 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
const cbT = e.args[4]!.type;
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: randomFill callback not a func");
|
||||
host.moveTemp(args[4]!);
|
||||
host.toHeap(args[4]!);
|
||||
const dyn = e.fn === "crypto.randomFillCbDyn";
|
||||
const adapter = host.cryptoBytesThunkFor(cbT, false, dyn ? "dyn" : "view");
|
||||
const symbol = dyn ? "scr_crypto_random_fill_async_dyn" : "scr_crypto_random_fill_async";
|
||||
@@ -305,7 +305,7 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
const cbT = e.args[2]!.type;
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: generateKey callback not a func");
|
||||
host.moveTemp(args[2]!);
|
||||
host.toHeap(args[2]!);
|
||||
const adapter = host.cryptoBytesThunkFor(cbT, false, "dyn");
|
||||
host.declare(`declare void @scr_crypto_generate_key_async(ptr, ptr, ptr, ptr)`);
|
||||
B.line(
|
||||
@@ -320,7 +320,7 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
const cbT = e.args[3]!.type;
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: randomInt callback not a func");
|
||||
host.moveTemp(args[3]!);
|
||||
host.toHeap(args[3]!);
|
||||
const adapter = host.cryptoIntThunkFor(cbT);
|
||||
host.declare(`declare void @scr_crypto_random_int_async(double, double, double, ptr, ptr)`);
|
||||
B.line(
|
||||
@@ -429,7 +429,7 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
const cbT = e.args[cbIndex]!.type;
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: crypto callback not a func");
|
||||
host.moveTemp(args[cbIndex]!);
|
||||
host.toHeap(args[cbIndex]!);
|
||||
const adapter = host.cryptoBytesThunkFor(cbT, e.fn === "crypto.hkdfCb");
|
||||
if (e.fn === "crypto.randomBytesCb") {
|
||||
host.declare(`declare void @scr_crypto_random_bytes_async(double, ptr, ptr)`);
|
||||
@@ -486,7 +486,7 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
const cbT = e.args[cbIdx]!.type;
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
|
||||
host.moveTemp(args[cbIdx]!);
|
||||
host.toHeap(args[cbIdx]!);
|
||||
cb = args[cbIdx]!.name;
|
||||
const sym = cbT.params.length === 0 ? "scr_http_resp_thunk0" : "scr_http_resp_thunk_res";
|
||||
host.declare(`declare void @${sym}(ptr, ptr)`);
|
||||
@@ -526,7 +526,13 @@ export function emitGenericLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
if (MAY_THROW_LIB_FNS.has(e.fn) && LIB_FN_SYMS[e.fn] === undefined) {
|
||||
throw new LlvmUnsupportedError(`libCall:${e.fn}`, e.loc);
|
||||
}
|
||||
const sym = mockTimerSym(host, e) ?? LIB_FN_SYMS[e.fn];
|
||||
// queueMicrotask's checked-dynamic form carries the AsyncLocalStorage
|
||||
// context like the static one (lib-process.ts).
|
||||
const sym =
|
||||
mockTimerSym(host, e) ??
|
||||
(e.fn === "timers.queueMicrotaskDyn" && host.schedulesInContext
|
||||
? "scr_queue_microtask_dyn_als"
|
||||
: LIB_FN_SYMS[e.fn]);
|
||||
if (sym === undefined) throw new LlvmUnsupportedError(`libCall:${e.fn}`, e.loc);
|
||||
const args = READ_ONLY_STRING_LIB_FNS.has(e.fn)
|
||||
? emitBorrowedInputs(host, e.args)
|
||||
@@ -716,7 +722,7 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
|
||||
// Literals (`123n`) and constant BigInt("...") texts are immortal
|
||||
// statics instead of a parse at every evaluation.
|
||||
const sym = host.internBigIntLiteral(e.args[0].value);
|
||||
if (sym) return host.own({ name: host.retainValue(sym, e.type), type: e.type });
|
||||
if (sym) return host.own({ name: host.retainFrame(sym, e.type), type: e.type });
|
||||
}
|
||||
if (e.fn.startsWith("bigint.")) {
|
||||
// Small-value operator trees run in 64-bit registers (bigint-wrap.ts).
|
||||
|
||||
@@ -193,7 +193,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
|
||||
const cbT = e.args[1]!.type;
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: zlib callback not a func");
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
const adapter = host.zlibBytesThunkFor(cbT);
|
||||
const compressing =
|
||||
e.fn === "zlib.deflateCb" || e.fn === "zlib.deflateRawCb" || e.fn === "zlib.gzipCb";
|
||||
@@ -246,7 +246,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
|
||||
const cbT = e.args[3]!.type;
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: zlib callback not a func");
|
||||
host.moveTemp(args[3]!);
|
||||
host.toHeap(args[3]!);
|
||||
const adapter = host.zlibBytesThunkFor(cbT);
|
||||
const sym =
|
||||
e.fn === "zlib.cbOpts" ? "scr_zlib_codec_async_opts" : "scr_zlib_codec_async_opts_dyn";
|
||||
@@ -293,7 +293,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
|
||||
const operands = args.slice(0, withCb ? -1 : undefined).map((a) => `ptr ${a.name}`);
|
||||
let cb = "null";
|
||||
if (withCb) {
|
||||
host.moveTemp(args[args.length - 1]!);
|
||||
host.toHeap(args[args.length - 1]!);
|
||||
cb = args[args.length - 1]!.name;
|
||||
}
|
||||
const sym =
|
||||
@@ -356,7 +356,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
|
||||
const cbT = e.args[2]!.type;
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: fs.rename callback not a func");
|
||||
host.moveTemp(args[2]!);
|
||||
host.toHeap(args[2]!);
|
||||
const adapter = host.fsRenameThunkFor(cbT);
|
||||
host.declare(`declare void @scr_fs_rename_async(ptr, ptr, ptr, ptr)`);
|
||||
B.line(
|
||||
@@ -403,7 +403,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
|
||||
const countField =
|
||||
e.fn === "fileHandle.read" || e.fn === "fileHandle.readv" ? "bytesRead" : "bytesWritten";
|
||||
host.storeField(host.recordFieldPtr(rec, inner.shapeId, countField).ptr, F64, count, false);
|
||||
const payload = host.retainValue(args[1]!.name, e.args[1]!.type);
|
||||
const payload = host.retainHeap(args[1]!.name, e.args[1]!.type);
|
||||
B.line(
|
||||
`store ptr ${payload}, ptr ${host.recordFieldPtr(rec, inner.shapeId, vector ? "buffers" : "buffer").ptr}`,
|
||||
);
|
||||
@@ -465,7 +465,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
|
||||
throw new InternalCompilerError("llvm emitter bug: fs.watchCb callback not a func");
|
||||
const path = host.emitExpr(e.args[0]!);
|
||||
const cb = host.emitExpr(e.args[1]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
const adapter = cbT.params.length === 0 ? "scr_watch_thunk0" : "scr_watch_thunk_event";
|
||||
host.declare(`declare void @${adapter}(ptr, ptr)`);
|
||||
host.declare(`declare ptr @scr_fs_watch(ptr, ptr, ptr)`);
|
||||
@@ -618,7 +618,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
|
||||
B.line(`${nm} = call ptr @scr_fs_scandir_name(ptr ${snap}, ${host.sizeType} ${i}) ; +1`);
|
||||
B.line(`store ptr ${nm}, ptr ${host.recordFieldPtr(row, recT.shapeId, "name").ptr}`);
|
||||
B.line(
|
||||
`store ptr ${host.retainValue(path.name, STRING)}, ptr ${host.recordFieldPtr(row, recT.shapeId, "parentPath").ptr}`,
|
||||
`store ptr ${host.retainHeap(path.name, STRING)}, ptr ${host.recordFieldPtr(row, recT.shapeId, "parentPath").ptr}`,
|
||||
);
|
||||
host.arrPush(arr, "ref", row); // push takes ownership of the row
|
||||
const i2 = B.tmp();
|
||||
@@ -630,7 +630,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
|
||||
if (!promiseForm) return arrayOut;
|
||||
const rc = vAdapters(host.shapeHost, arrayT);
|
||||
host.declare(`declare ptr @scr_promise_settled_ref(ptr, ptr, ptr, ptr)`);
|
||||
host.moveTemp(arrayOut); // promise fulfillment owns the result array
|
||||
host.toHeap(arrayOut); // promise fulfillment owns the result array
|
||||
const promise = B.tmp();
|
||||
B.line(
|
||||
`${promise} = call ptr @scr_promise_settled_ref(ptr ${arr}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(host.shapeHost, arrayT)})`,
|
||||
@@ -696,7 +696,7 @@ export function emitFilesystemLibCall(host: LlvmEmitterContext, e: LibCallExpr):
|
||||
if (!promiseForm) return value;
|
||||
const rc = vAdapters(host.shapeHost, unionT);
|
||||
host.declare(`declare ptr @scr_promise_settled_ref(ptr, ptr, ptr, ptr)`);
|
||||
host.moveTemp(value);
|
||||
host.toHeap(value);
|
||||
const promise = B.tmp();
|
||||
B.line(
|
||||
`${promise} = call ptr @scr_promise_settled_ref(ptr ${value.name}, ptr ${rc.retain}, ptr ${rc.release}, ptr ${traceArg(host.shapeHost, unionT)})`,
|
||||
@@ -941,7 +941,7 @@ export function emitPathUrlLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
const retained = B.tmp();
|
||||
B.line(`${retained} = call ptr @scr_arr_retain_v(ptr ${peeked})`);
|
||||
B.line(`store ptr ${retained}, ptr ${rowsSlot}`);
|
||||
host.releaseValue(cell, iv);
|
||||
host.releaseFrame(cell, iv);
|
||||
B.br(lj);
|
||||
B.startBlock(lm);
|
||||
const fresh = B.tmp();
|
||||
|
||||
@@ -170,7 +170,7 @@ export function emitHttp2LibCall(host: LlvmEmitterContext, e: LibCallExpr): LlVa
|
||||
if (tag < 0) throw new InternalCompilerError("SNI callback union lacks its function arm");
|
||||
type = arms[tag]!;
|
||||
value = host.unwrapNullableClosure(value, tag);
|
||||
} else host.moveTemp(cb);
|
||||
} else host.toHeap(cb);
|
||||
if (type.kind !== "func" || type.params[1] === undefined)
|
||||
throw new InternalCompilerError("SNI callback lacks its answer function");
|
||||
sni = [ptr(value), ptr("@" + sniAnswer(host, type.params[1]))];
|
||||
@@ -207,7 +207,7 @@ export function emitHttp2LibCall(host: LlvmEmitterContext, e: LibCallExpr): LlVa
|
||||
case "http2.streamCloseCb": {
|
||||
const stream = e.fn.startsWith("http2.stream"),
|
||||
cbIndex = stream ? 2 : 1;
|
||||
if (args[cbIndex] !== undefined) host.moveTemp(args[cbIndex]!);
|
||||
if (args[cbIndex] !== undefined) host.toHeap(args[cbIndex]!);
|
||||
return finishRuntimeCall(
|
||||
host,
|
||||
e,
|
||||
@@ -310,7 +310,7 @@ export function emitHttp2LibCall(host: LlvmEmitterContext, e: LibCallExpr): LlVa
|
||||
default:
|
||||
throw new InternalCompilerError(`unhandled HTTP/2 listener ${e.fn}`);
|
||||
}
|
||||
if (thunk === null) host.moveTemp(cb);
|
||||
if (thunk === null) host.toHeap(cb);
|
||||
const handler = thunk === null ? [a[1]!] : callbackAdapter(host, cb, thunk, params);
|
||||
return finishRuntimeCall(host, e, runtime, [a[0]!, ...handler, ...a.slice(2)]);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ export function emitLookupConnect(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
);
|
||||
}
|
||||
host.usesTimers = true;
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
return finishRuntimeCall(host, e, "scr_net_connect_lookup", [
|
||||
...args.map((arg) => abiValue(host, arg)),
|
||||
ptr("@" + symbol),
|
||||
|
||||
@@ -42,7 +42,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
if (e.fn === "http.requestConn" || e.fn === "http.requestConnCb") {
|
||||
const args = e.args.map((arg) => host.emitExpr(arg));
|
||||
host.usesTimers = true;
|
||||
host.moveTemp(args[0]!);
|
||||
host.toHeap(args[0]!);
|
||||
const cb = args[6];
|
||||
const handler =
|
||||
cb === undefined
|
||||
@@ -72,7 +72,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
const cbT = e.args[0]!.type;
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: net.createServerCb handler not a func");
|
||||
host.moveTemp(args[0]!);
|
||||
host.toHeap(args[0]!);
|
||||
cb = args[0]!.name;
|
||||
adapter = cbT.params.length === 0 ? "@scr_net_conn_thunk0" : "@scr_net_conn_thunk_sock";
|
||||
host.declare(`declare void ${adapter}(ptr, ptr)`);
|
||||
@@ -86,7 +86,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
let cb = "null";
|
||||
if (e.fn === "net.listenCb") {
|
||||
host.moveTemp(args[2]!);
|
||||
host.toHeap(args[2]!);
|
||||
cb = args[2]!.name;
|
||||
}
|
||||
host.declare(`declare void @scr_net_listen(ptr, double, ptr)`);
|
||||
@@ -109,7 +109,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
if (withCallback) {
|
||||
const cbT = e.args[cbIndex]!.type;
|
||||
if (cbT.kind === "func") {
|
||||
host.moveTemp(args[cbIndex]!);
|
||||
host.toHeap(args[cbIndex]!);
|
||||
cb = args[cbIndex]!.name;
|
||||
} else {
|
||||
if (cbT.kind !== "union")
|
||||
@@ -192,7 +192,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
let cb = "null";
|
||||
if (e.fn === "net.serverCloseCb") {
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
cb = args[1]!.name;
|
||||
}
|
||||
host.declare(`declare void @scr_net_server_close(ptr, ptr)`);
|
||||
@@ -233,7 +233,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
throw new InternalCompilerError("llvm emitter bug: close override not a func");
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
const wrapSym = host.closeOverrideWrapFor(cbT.params[0]!, cbT.ret.kind === "netServer");
|
||||
host.moveTemp(args[1]!); // ownership moves into the wrapper's env box
|
||||
host.toHeap(args[1]!); // ownership moves into the wrapper's env box
|
||||
const wrap = host.wrapEmitterListener(args[1]!.name, wrapSym);
|
||||
host.declare(`declare void @scr_net_server_set_close_override(ptr, ptr)`);
|
||||
B.line(`call void @scr_net_server_set_close_override(ptr ${args[0]!.name}, ptr ${wrap})`);
|
||||
@@ -244,7 +244,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
const adapter = cbT.params.length === 0 ? "scr_child_err_thunk0" : "scr_child_err_thunk_error";
|
||||
const entry =
|
||||
e.fn === "net.serverOnError" ? "scr_net_server_on_error" : "scr_net_sock_on_error";
|
||||
@@ -295,7 +295,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
"http.clientOnContinue": "scr_http_client_on_continue",
|
||||
}[e.fn]!;
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
host.declare(`declare void @${entry}(ptr, ptr, i1 zeroext)`);
|
||||
B.line(`call void @${entry}(ptr ${args[0]!.name}, ptr ${args[1]!.name}, i1 ${args[2]!.name})`);
|
||||
return { name: "", type: e.type };
|
||||
@@ -312,7 +312,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.moveTemp(args[cbIndex]!);
|
||||
host.toHeap(args[cbIndex]!);
|
||||
const adapter = cbT.params.length === 0 ? "scr_net_conn_thunk0" : "scr_net_conn_thunk_sock";
|
||||
host.declare(`declare void @${adapter}(ptr, ptr)`);
|
||||
if (e.fn === "http.serverSetTimeoutCb") {
|
||||
@@ -340,7 +340,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
let cb = "null";
|
||||
if (e.fn === "net.connectCb") {
|
||||
host.moveTemp(args[2]!);
|
||||
host.toHeap(args[2]!);
|
||||
cb = args[2]!.name;
|
||||
}
|
||||
host.declare(`declare ptr @scr_net_connect_checked(double, ptr, ptr)`);
|
||||
@@ -357,7 +357,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
const adapter =
|
||||
cbT.params.length === 0
|
||||
? "scr_net_data_thunk0"
|
||||
@@ -533,7 +533,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
const cbT = e.args[0]!.type;
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: http.createServer handler not a func");
|
||||
host.moveTemp(args[0]!);
|
||||
host.toHeap(args[0]!);
|
||||
cb = args[0]!.name;
|
||||
const sym =
|
||||
cbT.params.length === 2
|
||||
@@ -554,7 +554,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} handler not a func`);
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
const adapter =
|
||||
cbT.params.length === 2
|
||||
? "scr_http_handler_thunk2"
|
||||
@@ -580,7 +580,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} listener not a func`);
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
const adapter =
|
||||
cbT.params.length === 3
|
||||
? "scr_http_upgrade_thunk3"
|
||||
@@ -607,7 +607,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
const adapter =
|
||||
e.fn === "http.clientOnResponse"
|
||||
? cbT.params.length === 0
|
||||
@@ -630,7 +630,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
}
|
||||
if (e.fn === "http.resOnFinish" || e.fn === "http.resOnEndCallback") {
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
const entry =
|
||||
e.fn === "http.resOnFinish" ? "scr_http_res_on_finish" : "scr_http_res_on_end_callback";
|
||||
host.declare(`declare void @${entry}(ptr, ptr)`);
|
||||
@@ -639,7 +639,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
}
|
||||
if (e.fn === "http.resSetTimeoutCb" || e.fn === "http.reqSetTimeoutCb") {
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.moveTemp(args[2]!);
|
||||
host.toHeap(args[2]!);
|
||||
const entry =
|
||||
e.fn === "http.resSetTimeoutCb" ? "scr_http_res_set_timeout" : "scr_http_req_set_timeout";
|
||||
host.declare(`declare void @${entry}(ptr, double, ptr)`);
|
||||
@@ -650,7 +650,7 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
}
|
||||
if (e.fn === "http.clientSetTimeoutCb") {
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.moveTemp(args[2]!);
|
||||
host.toHeap(args[2]!);
|
||||
host.declare("declare void @scr_http_client_set_timeout_cb(ptr, double, ptr)");
|
||||
B.line(
|
||||
`call void @scr_http_client_set_timeout_cb(ptr ${args[0]!.name}, double ${args[1]!.name}, ptr ${args[2]!.name})`,
|
||||
@@ -659,21 +659,21 @@ export function emitNetworkHttpLibCall(host: LlvmEmitterContext, e: LibCallExpr)
|
||||
}
|
||||
if (e.fn === "net.sockOnFinish") {
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
host.declare(`declare void @scr_net_sock_on_finish(ptr, ptr)`);
|
||||
B.line(`call void @scr_net_sock_on_finish(ptr ${args[0]!.name}, ptr ${args[1]!.name})`);
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
if (e.fn === "net.sockOnWriteFlush") {
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
host.declare(`declare void @scr_net_sock_on_write_flush(ptr, ptr)`);
|
||||
B.line(`call void @scr_net_sock_on_write_flush(ptr ${args[0]!.name}, ptr ${args[1]!.name})`);
|
||||
return { name: "", type: e.type };
|
||||
}
|
||||
if (e.fn === "net.sockSetTimeoutCb") {
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
host.moveTemp(args[2]!);
|
||||
host.toHeap(args[2]!);
|
||||
host.declare("declare void @scr_net_sock_set_timeout_cb(ptr, double, ptr)");
|
||||
B.line(
|
||||
`call void @scr_net_sock_set_timeout_cb(ptr ${args[0]!.name}, double ${args[1]!.name}, ptr ${args[2]!.name})`,
|
||||
|
||||
@@ -18,7 +18,7 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
const cmd = host.emitExpr(e.args[0]!);
|
||||
const argv = host.emitExpr(e.args[1]!);
|
||||
const cb = host.emitExpr(callbackArg);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
const shell = host.emitExpr(e.args[3]!);
|
||||
const adapter = host.execFileThunkFor(cbT);
|
||||
host.usesTimers = true;
|
||||
@@ -72,7 +72,7 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
if (e.fn === "child.onSpawn") {
|
||||
const child = host.emitExpr(e.args[0]!);
|
||||
const cb = host.emitExpr(e.args[1]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
host.declare("declare void @scr_child_on_spawn(ptr, ptr)");
|
||||
B.line(`call void @scr_child_on_spawn(ptr ${child.name}, ptr ${cb.name})`);
|
||||
return { name: "", type: e.type };
|
||||
@@ -87,7 +87,7 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
|
||||
const child = host.emitExpr(e.args[0]!);
|
||||
const cb = host.emitExpr(e.args[1]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
let adapter: string;
|
||||
if (cbT.params.length === 0) {
|
||||
adapter = "scr_child_exit_thunk0";
|
||||
@@ -110,7 +110,7 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
throw new InternalCompilerError("llvm emitter bug: child.onError callback not a func");
|
||||
const child = host.emitExpr(e.args[0]!);
|
||||
const cb = host.emitExpr(e.args[1]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
const adapter = cbT.params.length === 0 ? "scr_child_err_thunk0" : "scr_child_err_thunk_error";
|
||||
host.declare(`declare void @${adapter}(ptr, ptr)`);
|
||||
host.declare(`declare void @scr_child_on_error(ptr, ptr, ptr)`);
|
||||
@@ -136,7 +136,7 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: child.send callback not a func");
|
||||
const cb = host.emitExpr(e.args[2]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
suffix = `, ptr ${cb.name}, ptr @${host.ipcSendThunkFor(cbT)}`;
|
||||
decl += ", ptr, ptr";
|
||||
}
|
||||
@@ -160,7 +160,7 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
|
||||
const cb = host.emitExpr(e.args[1]!);
|
||||
const once = host.emitExpr(e.args[2]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
if (e.fn === "child.onMessage") {
|
||||
host.declare(`declare void @scr_child_ipc_on_message(ptr, ptr, ptr, i1 zeroext)`);
|
||||
B.line(
|
||||
@@ -337,7 +337,7 @@ export function emitChildProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
|
||||
const cb = host.emitExpr(e.args[1]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
const once = host.emitExpr(e.args[2]!);
|
||||
if (e.fn === "writer.onError") {
|
||||
const adapter =
|
||||
@@ -373,17 +373,19 @@ export function emitAsyncContextLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
// main runs the loop.
|
||||
host.usesTimers = true;
|
||||
const cb = host.emitExpr(e.args[0]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
// Programs that enter AsyncLocalStorage stores schedule through the
|
||||
// *_als entry points, which carry the active stores into the callback.
|
||||
const sym =
|
||||
mockTimerSym(host, e) ??
|
||||
{
|
||||
"timers.setTimeout": "scr_set_timeout",
|
||||
"timers.setInterval": "scr_set_interval",
|
||||
"timers.setTimeoutHandle": "scr_set_timeout_handle",
|
||||
"timers.setImmediate": "scr_set_immediate",
|
||||
"process.nextTick": "scr_next_tick",
|
||||
"timers.queueMicrotask": "scr_queue_microtask",
|
||||
}[e.fn];
|
||||
(mockTimerSym(host, e) ??
|
||||
{
|
||||
"timers.setTimeout": "scr_set_timeout",
|
||||
"timers.setInterval": "scr_set_interval",
|
||||
"timers.setTimeoutHandle": "scr_set_timeout_handle",
|
||||
"timers.setImmediate": "scr_set_immediate",
|
||||
"process.nextTick": "scr_next_tick",
|
||||
"timers.queueMicrotask": "scr_queue_microtask",
|
||||
}[e.fn]!) + (host.schedulesInContext ? "_als" : "");
|
||||
const rest = e.args.slice(1).map((a) => host.emitExpr(a));
|
||||
const argList = [`ptr ${cb.name}`, ...rest.map((a) => `${host.llType(a.type)} ${a.name}`)].join(
|
||||
", ",
|
||||
@@ -464,7 +466,7 @@ export function emitProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: process.send callback not a func");
|
||||
const cb = host.emitExpr(e.args[1]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
suffix = `, ptr ${cb.name}, ptr @${host.ipcSendThunkFor(cbT)}`;
|
||||
decl += ", ptr, ptr";
|
||||
}
|
||||
@@ -486,7 +488,7 @@ export function emitProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
|
||||
const cb = host.emitExpr(e.args[0]!);
|
||||
const once = host.emitExpr(e.args[1]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
if (e.fn === "process.onMessage") {
|
||||
host.declare(`declare void @scr_process_ipc_on_message(ptr, ptr, i1 zeroext)`);
|
||||
B.line(
|
||||
@@ -507,7 +509,7 @@ export function emitProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
const cbT = e.args[2]!.type;
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: process write callback not a func");
|
||||
host.moveTemp(args[2]!);
|
||||
host.toHeap(args[2]!);
|
||||
const adapter = host.fsRenameThunkFor(cbT);
|
||||
const write =
|
||||
e.fn === "process.stdoutWriteBytesCb"
|
||||
@@ -538,7 +540,7 @@ export function emitProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.fn} callback not a func`);
|
||||
const cb = host.emitExpr(e.args[0]!);
|
||||
const once = host.emitExpr(e.args[1]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
if (e.fn === "stdin.onEnd") {
|
||||
host.declare(`declare void @scr_stdin_on_end(ptr, i1 zeroext)`);
|
||||
B.line(`call void @scr_stdin_on_end(ptr ${cb.name}, i1 ${once.name})`);
|
||||
@@ -586,7 +588,7 @@ export function emitProcessLibCall(host: LlvmEmitterContext, e: LibCallExpr): Ll
|
||||
throw new InternalCompilerError("llvm emitter bug: process.onExit callback not a func");
|
||||
const cb = host.emitExpr(e.args[0]!);
|
||||
const once = host.emitExpr(e.args[1]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
const adapter = cbT.params.length === 0 ? "scr_exit_thunk0" : "scr_exit_thunk_code";
|
||||
host.declare(`declare void @${adapter}(ptr, double)`);
|
||||
host.declare(`declare void @scr_process_on_exit(ptr, ptr, i1 zeroext)`);
|
||||
@@ -741,7 +743,7 @@ export function emitErrorsEventsLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
const cbr = B.tmp();
|
||||
B.line(`${cbr} = call ptr @scr_closure_retain_v(ptr ${args[2]!.name})`);
|
||||
const wrapped = host.wrapEmitterListener(cbr, adapterFn);
|
||||
host.moveTemp(args[2]!);
|
||||
host.toHeap(args[2]!);
|
||||
host.declare(
|
||||
`declare ptr @scr_emitter_on_via(ptr, ptr, ptr, ptr, ptr, i1 zeroext, i1 zeroext)`,
|
||||
);
|
||||
@@ -765,7 +767,7 @@ export function emitErrorsEventsLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
throw new InternalCompilerError("llvm emitter bug: emitter.onDyn adapter not a func");
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
const { fn: adapterFn, shim } = host.emitterFixedAdapter(adT);
|
||||
host.moveTemp(args[3]!); // the frame's +1 moves into the wrapper's box
|
||||
host.toHeap(args[3]!); // the frame's +1 moves into the wrapper's box
|
||||
const wrapped = host.wrapEmitterListener(args[3]!.name, adapterFn);
|
||||
host.declare(
|
||||
`declare ptr @scr_emitter_on_dyn(ptr, ptr, ptr, ptr, ptr, i1 zeroext, i1 zeroext)`,
|
||||
@@ -864,7 +866,7 @@ export function emitErrorsEventsLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
host.declare(`declare void @scr_ee_inv_fixed2(ptr, ptr)`);
|
||||
const t = B.tmp();
|
||||
if (isDyn) {
|
||||
host.moveTemp(args[3]!);
|
||||
host.toHeap(args[3]!);
|
||||
const wrapped = host.wrapEmitterListener(args[3]!.name, adapterFn);
|
||||
host.declare(
|
||||
`declare ptr @scr_emitter_on_dyn(ptr, ptr, ptr, ptr, ptr, i1 zeroext, i1 zeroext)`,
|
||||
@@ -878,7 +880,7 @@ export function emitErrorsEventsLibCall(host: LlvmEmitterContext, e: LibCallExpr
|
||||
const cbr = B.tmp();
|
||||
B.line(`${cbr} = call ptr @scr_closure_retain_v(ptr ${args[2]!.name})`);
|
||||
const wrapped = host.wrapEmitterListener(cbr, adapterFn);
|
||||
host.moveTemp(args[2]!);
|
||||
host.toHeap(args[2]!);
|
||||
host.declare(
|
||||
`declare ptr @scr_emitter_on_via(ptr, ptr, ptr, ptr, ptr, i1 zeroext, i1 zeroext)`,
|
||||
);
|
||||
|
||||
@@ -316,6 +316,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"date.getUTCDate": "scr_date_get_date_utc",
|
||||
"date.getDay": "scr_date_get_day_local",
|
||||
"date.getUTCDay": "scr_date_get_day_utc",
|
||||
"date.toUTCString": "scr_date_to_utc_string",
|
||||
"date.getHours": "scr_date_get_hours_local",
|
||||
"date.getUTCHours": "scr_date_get_hours_utc",
|
||||
"date.getMinutes": "scr_date_get_minutes_local",
|
||||
@@ -325,6 +326,9 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"date.getMilliseconds": "scr_date_get_milliseconds",
|
||||
"date.getUTCMilliseconds": "scr_date_get_milliseconds",
|
||||
"date.getTimezoneOffset": "scr_date_get_timezone_offset",
|
||||
"date.toString": "scr_date_to_string",
|
||||
"date.toDateString": "scr_date_to_date_string",
|
||||
"date.toTimeString": "scr_date_to_time_string",
|
||||
"fs.existsSync": "scr_fs_exists",
|
||||
// ── the throwing slice (MAY_THROW_LIB_FNS members): the generic path
|
||||
// emits the standard pending check after each — exprs.ts's finish.
|
||||
@@ -825,6 +829,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
// ambient-this read never throw. The fs dyn read is the sync-fs story.
|
||||
"fetch.start": "scr_fetch_static",
|
||||
"fetch.responseNew": "scr_fetch_response_new",
|
||||
"fetch.responseJsonStatic": "scr_fetch_response_json_static",
|
||||
"fetch.input": "scr_fetch_input",
|
||||
"abort.controllerNew": "scr_fetch_abort_controller_new",
|
||||
"event.targetNew": "scr_event_target_new",
|
||||
@@ -892,6 +897,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"dyn.iteratorCanStep": "scr_dyn_iterator_can_step",
|
||||
"dyn.iteratorStep": "scr_dyn_iterator_step",
|
||||
"dyn.iteratorStepDone": "scr_dyn_iterator_step_done",
|
||||
"dyn.collectionIteratorIndex": "scr_dyn_collection_iterator_index",
|
||||
"dyn.mapSeedEntries": "scr_dyn_map_seed_entries",
|
||||
"dyn.mapSeedEntry": "scr_dyn_map_seed_entry",
|
||||
"dyn.arrLen": "scr_dyn_arr_len",
|
||||
@@ -1412,6 +1418,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"regex.groupEquals": "scr_regex_group_eq",
|
||||
"regex.groupNumber": "scr_regex_group_number",
|
||||
"regex.groupParseInt": "scr_regex_group_parse_int",
|
||||
"regex.resultProp": "scr_regex_result_prop",
|
||||
// queueMicrotask's checked-dynamic form (borrowed dyn; a non-function
|
||||
// throws synchronously), the minted setImmediate value, and the
|
||||
// timers/promises immediate — all mark the loop live.
|
||||
|
||||
@@ -48,7 +48,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
const s0 = host.emitExpr(e.args[0]!);
|
||||
const cb = host.emitExpr(e.args[1]!);
|
||||
const once = host.emitExpr(e.args[2]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
const param = cbT.params[0];
|
||||
let adapter: string;
|
||||
if (param === undefined) {
|
||||
@@ -70,7 +70,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
const s0 = host.emitExpr(e.args[0]!);
|
||||
const cb = host.emitExpr(e.args[1]!);
|
||||
const once = host.emitExpr(e.args[2]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
host.declare(`declare void @scr_child_stream_on_end(ptr, ptr, i1 zeroext)`);
|
||||
B.line(`call void @scr_child_stream_on_end(ptr ${s0.name}, ptr ${cb.name}, i1 ${once.name})`);
|
||||
return { name: "", type: e.type };
|
||||
@@ -86,7 +86,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
const s0 = host.emitExpr(e.args[0]!);
|
||||
const cb = host.emitExpr(e.args[1]!);
|
||||
const once = host.emitExpr(e.args[2]!);
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
host.declare(`declare void @scr_child_stream_thunk_str(ptr, ptr)`);
|
||||
host.declare(`declare void @scr_child_stream_on_data_str(ptr, ptr, ptr, i1 zeroext)`);
|
||||
B.line(
|
||||
@@ -127,7 +127,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
}
|
||||
const cb = args[at]!;
|
||||
const cbT = e.args[at]!.type;
|
||||
host.moveTemp(cb); // the callback closure MOVES into the stream
|
||||
host.toHeap(cb); // the callback closure MOVES into the stream
|
||||
cbArgs.push(`ptr ${cb.name}`, `ptr @${host.streamCbThunkFor(canonical[i]!, cbT)}`);
|
||||
at++;
|
||||
}
|
||||
@@ -182,7 +182,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
const recv = host.emitExpr(e.args[0]!);
|
||||
const cb = host.emitExpr(e.args[1]!);
|
||||
const cbT = e.args[1]!.type;
|
||||
host.moveTemp(cb); // the callback closure MOVES into the stream
|
||||
host.toHeap(cb); // the callback closure MOVES into the stream
|
||||
const sym = symOf[e.fn]!;
|
||||
host.declare(`declare void @${sym}(ptr, ptr, ptr)`);
|
||||
B.line(
|
||||
@@ -210,7 +210,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
}
|
||||
const cb = args[at]!;
|
||||
const cbT = e.args[at]!.type;
|
||||
host.moveTemp(cb);
|
||||
host.toHeap(cb);
|
||||
cbArgs.push(`ptr ${cb.name}`, `ptr @${host.streamCbThunkFor(canonical[i]!, cbT)}`);
|
||||
at++;
|
||||
}
|
||||
@@ -321,7 +321,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
B.line(`${raw} = call ptr @scr_stream_read(ptr ${args[0]!.name}, double ${args[1]!.name})`);
|
||||
const b = host.own({ name: raw, type: def!.arms[bytesTag]! });
|
||||
host.emitPendingCheck();
|
||||
host.moveTemp(b); // moves into the union arm when present
|
||||
host.toHeap(b); // moves into the union arm when present
|
||||
return host.wrapNullable(raw, raw, def!.arms[bytesTag]!, bytesTag, e.type, nullTag);
|
||||
}
|
||||
if (e.fn === "readable.flowing") {
|
||||
@@ -383,7 +383,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
const args = e.args.map((a) => host.emitExpr(a));
|
||||
let cb = "null";
|
||||
if (e.fn !== "writable.writeDyn" && e.args.length > 2) {
|
||||
host.moveTemp(args[2]!);
|
||||
host.toHeap(args[2]!);
|
||||
cb = args[2]!.name;
|
||||
}
|
||||
host.declare(`declare zeroext i1 @${entry}(ptr, ptr, ptr)`);
|
||||
@@ -412,7 +412,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
else if (flags & 8) chunkD = args[at++]!.name;
|
||||
let cbName = "null";
|
||||
if (flags & 4) {
|
||||
host.moveTemp(args[at]!);
|
||||
host.toHeap(args[at]!);
|
||||
cbName = args[at]!.name;
|
||||
}
|
||||
if (chunkD !== null) {
|
||||
@@ -490,7 +490,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
const cbT = e.args[1]!.type;
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: stream.finished callback not a func");
|
||||
host.moveTemp(args[1]!); // the watcher closure MOVES into the stream
|
||||
host.toHeap(args[1]!); // the watcher closure MOVES into the stream
|
||||
const thunk = host.streamCbThunkFor("e", cbT);
|
||||
host.declare(`declare ptr @scr_stream_finished(ptr, ptr, ptr)`);
|
||||
B.line(
|
||||
@@ -599,7 +599,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
throw new InternalCompilerError(
|
||||
"llvm emitter bug: stream.finishedOpts callback not a func",
|
||||
);
|
||||
host.moveTemp(args[cbIdx]!);
|
||||
host.toHeap(args[cbIdx]!);
|
||||
const thunk = host.streamCbThunkFor("e", cbT);
|
||||
host.declare(
|
||||
`declare ptr @scr_stream_finished_opts(ptr, ptr, ptr, double, double, i1, ptr, ptr)`,
|
||||
@@ -696,7 +696,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
throw new InternalCompilerError(
|
||||
"llvm emitter bug: stream.pipelineStages callback not a func",
|
||||
);
|
||||
host.moveTemp(args[args.length - 1]!);
|
||||
host.toHeap(args[args.length - 1]!);
|
||||
const thunk = host.streamCbThunkFor("e", cbT);
|
||||
host.declare(`declare ptr @scr_stream_pipeline_stages(${params}, ptr, ptr)`);
|
||||
B.line(
|
||||
@@ -732,7 +732,7 @@ export function emitStreamLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlV
|
||||
const cbT = e.args[1 + n]!.type;
|
||||
if (cbT.kind !== "func")
|
||||
throw new InternalCompilerError("llvm emitter bug: stream.pipeline callback not a func");
|
||||
host.moveTemp(args[1 + n]!);
|
||||
host.toHeap(args[1 + n]!);
|
||||
const thunk = host.streamCbThunkFor("e", cbT);
|
||||
host.declare(`declare ptr @scr_stream_pipeline(double, ptr, ptr, ptr)`);
|
||||
B.line(
|
||||
|
||||
@@ -8,7 +8,7 @@ export function emitTestLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlVal
|
||||
host.usesTimers = true;
|
||||
switch (e.fn) {
|
||||
case "test.register":
|
||||
host.moveTemp(args[3]!);
|
||||
host.toHeap(args[3]!);
|
||||
return finishRuntimeCall(host, e, "scr_test_register", a);
|
||||
case "test.registerEmpty":
|
||||
return finishRuntimeCall(host, e, "scr_test_register", [
|
||||
@@ -17,13 +17,13 @@ export function emitTestLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlVal
|
||||
...a.slice(3),
|
||||
]);
|
||||
case "test.suite":
|
||||
host.moveTemp(args[3]!);
|
||||
host.toHeap(args[3]!);
|
||||
return finishRuntimeCall(host, e, "scr_test_suite", a);
|
||||
case "test.hook":
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
return finishRuntimeCall(host, e, "scr_test_hook", a);
|
||||
case "test.sub":
|
||||
host.moveTemp(args[4]!);
|
||||
host.toHeap(args[4]!);
|
||||
return finishRuntimeCall(host, e, "scr_test_sub", a);
|
||||
case "test.subEmpty": {
|
||||
const promise = callRuntime(host, "scr_test_sub", "ptr", [
|
||||
@@ -49,22 +49,26 @@ export function emitTestLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlVal
|
||||
return finishRuntimeCall(host, e, "scr_test_ctx_passed", a);
|
||||
case "test.workerId":
|
||||
return finishRuntimeCall(host, e, "scr_test_worker_id", a);
|
||||
case "test.hasContext":
|
||||
return finishRuntimeCall(host, e, "scr_test_has_context", a);
|
||||
case "test.context":
|
||||
return finishRuntimeCall(host, e, "scr_test_context", a);
|
||||
case "test.ctxHook":
|
||||
host.moveTemp(args[2]!);
|
||||
host.toHeap(args[2]!);
|
||||
return finishRuntimeCall(host, e, "scr_test_ctx_hook", a);
|
||||
case "test.ctxPlan":
|
||||
return finishRuntimeCall(host, e, "scr_test_ctx_plan", a);
|
||||
case "test.ctxCount":
|
||||
return finishRuntimeCall(host, e, "scr_test_ctx_count", a);
|
||||
case "test.mockTrack":
|
||||
host.moveTemp(args[0]!);
|
||||
host.toHeap(args[0]!);
|
||||
return finishRuntimeCall(host, e, "scr_test_mock_track", a);
|
||||
case "test.mockRestoreAll":
|
||||
return finishRuntimeCall(host, e, "scr_test_mock_restore_all", a);
|
||||
case "test.mockReset":
|
||||
return finishRuntimeCall(host, e, "scr_test_mock_reset", a);
|
||||
case "test.ctxMockTrack":
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
return finishRuntimeCall(host, e, "scr_test_ctx_mock_track", a);
|
||||
case "test.ctxMockRestoreAll":
|
||||
return finishRuntimeCall(host, e, "scr_test_ctx_mock_restore_all", a);
|
||||
|
||||
@@ -94,10 +94,10 @@ export function emitTlsLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValu
|
||||
}
|
||||
case "tls.connect":
|
||||
case "tls.connectCb":
|
||||
if (args[3] !== undefined) host.moveTemp(args[3]);
|
||||
if (args[3] !== undefined) host.toHeap(args[3]);
|
||||
return finishRuntimeCall(host, e, "scr_tls_connect_dyn", [...a.slice(0, 3), a[3] ?? ptr()]);
|
||||
case "tls.sockOnSecureConnect":
|
||||
host.moveTemp(args[1]!);
|
||||
host.toHeap(args[1]!);
|
||||
return finishRuntimeCall(host, e, "scr_tls_sock_on_secure_connect", a);
|
||||
case "tls.sockOnSession": {
|
||||
const cb = args[1]!,
|
||||
|
||||
@@ -629,7 +629,7 @@ export function emitLocalArrayRead(
|
||||
// A slot of a nullable-union array may hold NULL even when present.
|
||||
const owned = read.nullableElement ? read.type : read.element;
|
||||
const store = (value: string): void => {
|
||||
B.line(`store ptr ${read.borrow ? value : host.retainValue(value, owned)}, ptr ${localSlot}`);
|
||||
B.line(`store ptr ${read.borrow ? value : host.retainFrame(value, owned)}, ptr ${localSlot}`);
|
||||
};
|
||||
if (inline)
|
||||
emitDenseReferenceArrayRead(host, array, index, integerIndex, store, no, slow, join, "ptr");
|
||||
@@ -676,7 +676,7 @@ export function emitLocalArrayRead(
|
||||
B.line(`store i64 ${bits}, ptr ${payload}`);
|
||||
} else {
|
||||
B.line(
|
||||
`store ptr ${read.borrow ? value : host.retainValue(value, read.element)}, ptr ${payload}`,
|
||||
`store ptr ${read.borrow ? value : host.retainFrame(value, read.element)}, ptr ${payload}`,
|
||||
);
|
||||
}
|
||||
B.line(`store i32 ${read.presentTag}, ptr ${tag}`);
|
||||
|
||||
@@ -202,7 +202,7 @@ export function storeLocalUnion(
|
||||
B.startBlock(label);
|
||||
host.frames.push([]);
|
||||
storeLocalUnion(host, storage, value);
|
||||
host.releaseFrame(host.frames.pop()!);
|
||||
host.releaseTemps(host.frames.pop()!);
|
||||
B.br(join);
|
||||
}
|
||||
B.startBlock(join);
|
||||
@@ -241,7 +241,7 @@ export function storeLocalUnion(
|
||||
const copied = host.emitOwnedNullableStack(expr);
|
||||
source = copied.box;
|
||||
owner = copied.owner.name;
|
||||
host.moveTemp(copied.owner);
|
||||
host.toFrame(copied.owner);
|
||||
} else if (expr.kind === "varRef" && !canStackUnion(expr, host.boxedUnionsById)) {
|
||||
// A copy of another binding: snapshot the tag and bits, and retain the
|
||||
// reference payload (scalar and unit arms own nothing).
|
||||
@@ -260,7 +260,7 @@ export function storeLocalUnion(
|
||||
B.line(`${isRef} = icmp eq i32 ${tag}, ${refTag}`);
|
||||
B.condBr(isRef, yes, join);
|
||||
B.startBlock(yes);
|
||||
const retained = host.retainValue(host.unionPeek(source, unionId), storage.ownerType);
|
||||
const retained = host.retainFrame(host.unionPeek(source, unionId), storage.ownerType);
|
||||
B.line(`store ptr ${retained}, ptr ${held}`);
|
||||
B.br(join);
|
||||
B.startBlock(join);
|
||||
@@ -272,7 +272,7 @@ export function storeLocalUnion(
|
||||
source = wrapped.value.name;
|
||||
if (wrapped.payload) {
|
||||
owner = wrapped.payload.name;
|
||||
host.moveTemp(wrapped.payload);
|
||||
host.toFrame(wrapped.payload);
|
||||
}
|
||||
} else {
|
||||
const read = matchMapRead(expr, host.boxedUnionsById)!;
|
||||
@@ -294,7 +294,7 @@ export function storeLocalUnion(
|
||||
const old = B.tmp();
|
||||
B.line(`${old} = load ptr, ptr ${storage.owner}`);
|
||||
B.line(`store ptr ${owner}, ptr ${storage.owner}`);
|
||||
host.releaseValue(old, storage.ownerType);
|
||||
host.releaseFrame(old, storage.ownerType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +326,7 @@ export function emitScalarOptional(
|
||||
bindPresent();
|
||||
host.frames.push([]);
|
||||
storeLocalUnion(host, storage, expr.body);
|
||||
host.releaseFrame(host.frames.pop()!);
|
||||
host.releaseTemps(host.frames.pop()!);
|
||||
host.chainSlots.delete(expr.id);
|
||||
B.br(join);
|
||||
} else {
|
||||
@@ -339,7 +339,7 @@ export function emitScalarOptional(
|
||||
B.startBlock(fallback);
|
||||
host.frames.push([]);
|
||||
storeLocalUnion(host, storage, expr.right);
|
||||
host.releaseFrame(host.frames.pop()!);
|
||||
host.releaseTemps(host.frames.pop()!);
|
||||
B.br(join);
|
||||
}
|
||||
B.startBlock(join);
|
||||
|
||||
@@ -105,7 +105,7 @@ function emitMapEntryGetRef(host: LlvmEmitterContext, entry: string, value: IrTy
|
||||
join = B.newLabel("mapn.join");
|
||||
B.condBr(found, hit, miss);
|
||||
B.startBlock(hit);
|
||||
const retained = host.retainValue(emitNumberMapSlot(host, entry, "ptr"), value);
|
||||
const retained = host.retainFrame(emitNumberMapSlot(host, entry, "ptr"), value);
|
||||
B.br(join);
|
||||
B.startBlock(miss);
|
||||
B.br(join);
|
||||
@@ -251,7 +251,7 @@ export function emitMapIterSlot(
|
||||
B.line(`${loaded} = icmp ne i64 ${bits}, 0`);
|
||||
} else {
|
||||
B.line(`${loaded} = load ${llTy}, ptr ${slot}`);
|
||||
if (access === "ref") loaded = host.retainValue(loaded, type);
|
||||
if (access === "ref") loaded = host.retainFrame(loaded, type);
|
||||
}
|
||||
const hitEnd = B.currentLabel();
|
||||
B.br(join);
|
||||
|
||||
@@ -29,6 +29,7 @@ export function preservesRegexInputs(method: string): boolean {
|
||||
case "match":
|
||||
case "search":
|
||||
case "matchAll":
|
||||
case "matchAllCaptures":
|
||||
case "replace":
|
||||
case "replaceAll":
|
||||
case "split":
|
||||
|
||||
@@ -104,6 +104,7 @@ const INERT_LIB_CALLS = new Set([
|
||||
"regex.groupEquals",
|
||||
"regex.groupNumber",
|
||||
"regex.groupParseInt",
|
||||
"regex.resultProp",
|
||||
"process.cwd",
|
||||
"process.argv",
|
||||
"process.execArgv",
|
||||
@@ -469,11 +470,14 @@ export class ReferenceWrites {
|
||||
return;
|
||||
case "intrinsic":
|
||||
// A TDZ test reads; publication only makes objects immortal; intern
|
||||
// tables store published values and write no script object.
|
||||
// tables store published values and write no script object;
|
||||
// ThreadSlots reads write nothing.
|
||||
if (
|
||||
e.name !== "module.tdzCheck" &&
|
||||
e.name !== "threads.publish" &&
|
||||
!e.name.startsWith("itable.")
|
||||
!e.name.startsWith("itable.") &&
|
||||
e.name !== "slots.get" &&
|
||||
e.name !== "slots.has"
|
||||
)
|
||||
sink.all();
|
||||
return;
|
||||
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
mangleRecordStruct,
|
||||
} from "../mangle.js";
|
||||
import { threadHotAddressLines } from "./alloc.js";
|
||||
import type { LlClassMeta } from "./classes.js";
|
||||
import { classFieldBase, type LlClassMeta } from "./classes.js";
|
||||
import { llvmCommentText } from "./common.js";
|
||||
import type { NullableRefFields } from "./nullable-fields.js";
|
||||
import { FN_ATTRS, rcMembers, releaseSym, vAdapters, type ShapeHost } from "./shapes.js";
|
||||
@@ -84,7 +84,7 @@ export class LifetimeRegions {
|
||||
|
||||
/** The reference-holding fields; inline scalar unions own nothing. */
|
||||
private classFields(def: IrClassDef, meta: LlClassMeta): Field[] {
|
||||
const base = meta.hierarchy ? 2 : 1;
|
||||
const base = classFieldBase(meta);
|
||||
return def.fields.flatMap((f, i) =>
|
||||
this.host.inlineUnions.classField(def.name, f.name)
|
||||
? []
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
mangleRecordTrace,
|
||||
} from "../mangle.js";
|
||||
import {
|
||||
CYC_HDR,
|
||||
cycTypeDescriptor,
|
||||
emitObjectAlloc,
|
||||
emitObjectFree,
|
||||
@@ -58,67 +59,132 @@ import {
|
||||
import { recordMayKeepSource } from "./record-sources.js";
|
||||
import { LlvmUnsupportedError } from "./unsupported.js";
|
||||
|
||||
/** SCR_CYC_MAX_TYPES (scr_rc.h): the registry's size, and the length of
|
||||
* the young-in nursery's per-id skip countdown. */
|
||||
const CYC_MAX_TYPES = 32768;
|
||||
|
||||
/** What the tables need from the emitter: the extern-declaration ledger
|
||||
* and the module-wide cycle/shape indexes. */
|
||||
export interface ShapeHost {
|
||||
declare(decl: string): void;
|
||||
/** The hierarchy root of a class (a program class or a runtime base),
|
||||
* or undefined for classes outside the class graph. */
|
||||
classRootOf?(className: string): string | undefined;
|
||||
/** Request the shared OOM abort helper (@sc_oom) — emitted once. */
|
||||
needOom(): void;
|
||||
/** Byte offset from an object pointer back to ScrCycHdr.buf. */
|
||||
readonly cycleBufOffset: number;
|
||||
declare(decl: string): void;
|
||||
/** Inline the small-object allocator's fast paths (llvm/alloc.ts). */
|
||||
readonly inlineAlloc?: boolean;
|
||||
/** Record and class fields that store a scalar union's tag and bits
|
||||
* inline (inline-unions.ts): they own nothing. */
|
||||
readonly inlineUnions: InlineUnionFields;
|
||||
/** The immutable ScrMapOps descriptor (scr_map.h) of a Map or Set type:
|
||||
* a module constant holding the LLVM operands { key retain, key release,
|
||||
* key trace, value retain, value release, value trace, union key kinds },
|
||||
* or `null` when every operand is null. */
|
||||
mapOps(operands: readonly string[]): string;
|
||||
/** The target C ABI's `size_t` / refcount integer type. */
|
||||
readonly sizeType: "i32" | "i64";
|
||||
/** Byte offset from an object pointer back to ScrCycHdr.buf. */
|
||||
readonly cycleBufOffset: number;
|
||||
readonly tracedShapes: Set<string>;
|
||||
readonly tracedUnions: Set<string>;
|
||||
/** Request the shared OOM abort helper (@sc_oom) — emitted once. */
|
||||
needOom(): void;
|
||||
/** The next collector statistics index for an emitted ScrCycType
|
||||
* descriptor (cycTypeDescriptor), counting from FIRST_EMITTED_CYC_KIND
|
||||
* in emission order.
|
||||
* Absent: every descriptor gets 0, which keeps no statistics. */
|
||||
nextCycKind?(): number;
|
||||
/** Unions represented as a nullable arm pointer (nullable-unions.ts):
|
||||
* their RC and trace entry points are the arm's NULL-tolerant ones. */
|
||||
readonly nullableUnions: NullableUnions;
|
||||
/** Record and class fields that store a scalar union's tag and bits
|
||||
* inline (inline-unions.ts): they own nothing. */
|
||||
readonly inlineUnions: InlineUnionFields;
|
||||
/** The zeroed `%ScrUnion` an inline store reads for a NULL dummy. */
|
||||
unionNoneRef(): string;
|
||||
readonly recordsById: Map<string, IrRecordShape>;
|
||||
readonly recordCloneShapes: ReadonlySet<string>;
|
||||
/** The program constructs lifetime regions: the ProgramLifetime classes
|
||||
* allocate in regions and count references in their region chunk instead
|
||||
* of being empty (llvm/regions.ts). */
|
||||
regions?: RegionHooks | null;
|
||||
/** The alias tag of a runtime word that lifetime-region code reads and
|
||||
* writes outside every field: the chunk count, the chunk owner, the class
|
||||
* cursors and scr_region_counting (", !tbaa !N", or "" in debug builds). */
|
||||
regionAliasTag?(word: "count" | "owner" | "cursor" | "counting"): string;
|
||||
/** Emit the live-object audit notes (`scr_obj_alloc_note` /
|
||||
* `scr_obj_free_note`) in emitted new/free helpers. Only runtimes built
|
||||
* with SCR_RC_AUDIT (the sanitized lane) count them; release and dev
|
||||
* runtime packs define them empty, so plain builds omit the calls.
|
||||
* Absent means emit. */
|
||||
readonly objectAudit?: boolean;
|
||||
/** Inline the small-object allocator's fast paths (llvm/alloc.ts). */
|
||||
readonly inlineAlloc?: boolean;
|
||||
/** Worker executables: the inline allocator state is thread-local. */
|
||||
readonly threadLocalAlloc?: boolean;
|
||||
/** Inline RC fast-path helpers requested so far ("<family>:<op>"); the
|
||||
* emitter defines each once through {@link emitInlineRcHelpers}. Null
|
||||
* keeps every retain/release a runtime call. */
|
||||
readonly rcHelpers: Set<string> | null;
|
||||
/** Which fast paths are inlined (see {@link InlineRcMode}). */
|
||||
readonly rcMode?: InlineRcMode;
|
||||
/** Define the fast-path helpers `noinline` (see {@link inlineRcOption}). */
|
||||
readonly rcHelpersNoinline?: boolean;
|
||||
/** The next collector statistics index for an emitted ScrCycType
|
||||
* descriptor (cycTypeDescriptor), counting from FIRST_EMITTED_CYC_KIND
|
||||
* in emission order.
|
||||
* Absent: every descriptor gets 0, which keeps no statistics. */
|
||||
nextCycKind?(): number;
|
||||
/** Which fast paths are inlined (see {@link InlineRcMode}). */
|
||||
readonly rcMode?: InlineRcMode;
|
||||
readonly recordCloneShapes: ReadonlySet<string>;
|
||||
readonly recordsById: Map<string, IrRecordShape>;
|
||||
/** The alias tag of a runtime word that lifetime-region code reads and
|
||||
* writes outside every field: the chunk count, the chunk owner, the class
|
||||
* cursors and scr_region_counting (", !tbaa !N", or "" in debug builds). */
|
||||
regionAliasTag?(word: "count" | "owner" | "cursor" | "counting"): string;
|
||||
/** The program constructs lifetime regions: the ProgramLifetime classes
|
||||
* allocate in regions and count references in their region chunk instead
|
||||
* of being empty (llvm/regions.ts). */
|
||||
regions?: RegionHooks | null;
|
||||
/** The target C ABI's `size_t` / refcount integer type. */
|
||||
readonly sizeType: "i32" | "i64";
|
||||
/** Worker executables: the inline allocator state is thread-local. */
|
||||
readonly threadLocalAlloc?: boolean;
|
||||
readonly tracedShapes: Set<string>;
|
||||
readonly tracedUnions: Set<string>;
|
||||
/** The zeroed `%ScrUnion` an inline store reads for a NULL dummy. */
|
||||
unionNoneRef(): string;
|
||||
/** The young-in nursery (backend/young-nursery.ts): `new` hands each
|
||||
* traced program-owned instance to scr_yin_enter. Absent: off. */
|
||||
readonly youngNursery?: boolean;
|
||||
}
|
||||
|
||||
/** The nursery entry of a new traced instance in `%o`, after its count is
|
||||
* stored; nothing when the nursery is off. Where the allocator is inlined
|
||||
* (64-bit, process-global state) the entry is inline too: a type that is
|
||||
* skipping the nursery (its scr_yin_skip countdown is nonzero) counts down
|
||||
* and stays out; otherwise, while the nursery has room (n < lim in ScrYin,
|
||||
* scr_rc.h), list the object at index n and mark its header young
|
||||
* (generation SCR_CYC_YOUNG, `buf` the buffered bit and the index), and
|
||||
* call scr_yin_enter_slow when it has none (it collects the nursery
|
||||
* first). Other targets call scr_yin_enter, which does all of it.
|
||||
* Labels use the `ye.` prefix; the lines end in an open block. */
|
||||
export function youngEntryLines(host: ShapeHost): string[] {
|
||||
if (host.youngNursery !== true) return [];
|
||||
if (host.inlineAlloc !== true || host.threadLocalAlloc === true) {
|
||||
host.declare(`declare void @scr_yin_enter(ptr)`);
|
||||
return [` call void @scr_yin_enter(ptr %o) ; the young-in nursery`];
|
||||
}
|
||||
host.declare(`@scr_yin = external global { ptr, i64, i64, i64 }`);
|
||||
host.declare(`@scr_yin_skip = external global [${CYC_MAX_TYPES} x i32]`);
|
||||
host.declare(`declare void @scr_yin_enter_slow(ptr)`);
|
||||
return [
|
||||
` %ye.idp = getelementptr inbounds i8, ptr %o, i64 -${CYC_HDR} ; the young-in nursery`,
|
||||
` %ye.id16 = load i16, ptr %ye.idp ; ScrCycHdr.id`,
|
||||
` %ye.id = zext i16 %ye.id16 to i64`,
|
||||
` %ye.sp = getelementptr inbounds [${CYC_MAX_TYPES} x i32], ptr @scr_yin_skip, i64 0, i64 %ye.id`,
|
||||
` %ye.s = load i32, ptr %ye.sp`,
|
||||
` %ye.skip = icmp ne i32 %ye.s, 0`,
|
||||
` br i1 %ye.skip, label %ye.skipped, label %ye.enter`,
|
||||
`ye.skipped: ; a type that does not die young: counted down, stays out`,
|
||||
` %ye.s1 = sub i32 %ye.s, 1`,
|
||||
` store i32 %ye.s1, ptr %ye.sp`,
|
||||
` br label %ye.done`,
|
||||
`ye.enter:`,
|
||||
` %ye.v = load ptr, ptr @scr_yin`,
|
||||
` %ye.np = getelementptr inbounds i8, ptr @scr_yin, i64 8`,
|
||||
` %ye.n = load i64, ptr %ye.np`,
|
||||
` %ye.lp = getelementptr inbounds i8, ptr @scr_yin, i64 16`,
|
||||
` %ye.l = load i64, ptr %ye.lp`,
|
||||
` %ye.room = icmp ult i64 %ye.n, %ye.l`,
|
||||
` br i1 %ye.room, label %ye.fast, label %ye.slow`,
|
||||
`ye.fast:`,
|
||||
` %ye.slot = getelementptr inbounds ptr, ptr %ye.v, i64 %ye.n`,
|
||||
` store ptr %o, ptr %ye.slot`,
|
||||
` %ye.n1 = add i64 %ye.n, 1`,
|
||||
` store i64 %ye.n1, ptr %ye.np`,
|
||||
` %ye.genp = getelementptr inbounds i8, ptr %o, i64 -5`,
|
||||
` store i8 3, ptr %ye.genp ; ScrCycHdr.gen = SCR_CYC_YOUNG`,
|
||||
` %ye.bufp = getelementptr inbounds i8, ptr %o, i64 -4`,
|
||||
` %ye.i = trunc i64 %ye.n to i32`,
|
||||
` %ye.buf = or i32 %ye.i, -2147483648`,
|
||||
` store i32 %ye.buf, ptr %ye.bufp ; ScrCycHdr.buf = buffered bit | index`,
|
||||
` br label %ye.done`,
|
||||
`ye.slow:`,
|
||||
` call void @scr_yin_enter_slow(ptr %o)`,
|
||||
` br label %ye.done`,
|
||||
`ye.done:`,
|
||||
];
|
||||
}
|
||||
|
||||
/** What class emission asks of lifetime regions (LifetimeRegions in
|
||||
@@ -1622,6 +1688,7 @@ export function emitRecordShapes(
|
||||
` store ptr %ovf, ptr %ovfp`,
|
||||
);
|
||||
}
|
||||
if (traced) nw.push(...youngEntryLines(host));
|
||||
nw.push(` ret ptr %o`, `}`, ``);
|
||||
defs.push(...nw);
|
||||
|
||||
|
||||
@@ -102,8 +102,8 @@ export function storeSplitSnapshot(
|
||||
host.B.line(`store ptr ${snapshot.source}, ptr ${sourceSlot}`);
|
||||
host.B.line(`store ptr ${snapshot.separator}, ptr ${separatorSlot}`);
|
||||
host.B.line(`store i32 ${snapshot.limit}, ptr ${limitSlot}`);
|
||||
host.moveTemp({ name: snapshot.source, type: STRING });
|
||||
host.moveTemp({ name: snapshot.separator, type: STRING });
|
||||
host.toFrame({ name: snapshot.source, type: STRING });
|
||||
host.toFrame({ name: snapshot.separator, type: STRING });
|
||||
return { sourceSlot, separatorSlot, limitSlot };
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,10 @@ export type StackObjectFieldOwner = (fields: readonly StackObjectField[]) => voi
|
||||
* (stackObjectFields). Published shapes keep heap storage: their field
|
||||
* guards treat the immortal header as a frozen object. */
|
||||
export function canStackObject(host: LlvmEmitterContext, e: IrExpr): e is StackObjectSource {
|
||||
// A ThreadSlots key keeps its slot index for its lifetime and has its
|
||||
// values released when it is freed: it lives on the heap.
|
||||
if (e.type.kind === "object" && host.classMeta.get(e.type.className)?.threadSlot === true)
|
||||
return false;
|
||||
if (e.type.kind === "record" && host.publishedTypes?.recordGuarded(e.type.shapeId) === true)
|
||||
return false;
|
||||
// A producer with a result-slot entry builds its fresh result in the
|
||||
@@ -232,7 +236,7 @@ function emitRecordCloneFields(
|
||||
const fieldTy = llFieldType(from.type, from.inline);
|
||||
const v = B.tmp();
|
||||
B.line(`${v} = load ${fieldTy}, ptr ${from.ptr}`);
|
||||
const stored = isRefCounted(from.type) && !from.inline ? host.retainValue(v, from.type) : v;
|
||||
const stored = isRefCounted(from.type) && !from.inline ? host.retainFrame(v, from.type) : v;
|
||||
B.line(`store ${fieldTy} ${stored}, ptr ${to.ptr}`);
|
||||
}
|
||||
const initialized = new Set<string>();
|
||||
|
||||
@@ -91,7 +91,7 @@ export function emitStringSliceSnapshot(
|
||||
B.line(
|
||||
`call void @scr_str_slice_range(ptr ${source.name}, double ${args[0]?.name ?? f64Lit(0)}, double ${args[1]?.name ?? F64_INF}, i1 ${slice.method === "substring" ? "true" : "false"}, ptr ${range})`,
|
||||
);
|
||||
host.moveTemp(source);
|
||||
host.toFrame(source);
|
||||
return { source: sourceSlot, range };
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { BOOL, F64, STRING, VOID, type IrExpr, type IrModule, type IrType } from "../../ir/ir.js";
|
||||
import { validateModule } from "../../ir/validate.js";
|
||||
import { emitLlvmModule } from "./emitter.js";
|
||||
import { mangleClassStruct } from "../mangle.js";
|
||||
|
||||
const loc = { file: "thread-slots.ts", start: 0, end: 1 };
|
||||
const slotsType: IrType = { kind: "object", className: "%ThreadSlots" };
|
||||
const node: IrType = { kind: "object", className: "Node" };
|
||||
const leaf: IrType = { kind: "object", className: "Leaf" };
|
||||
const plain: IrType = { kind: "object", className: "Plain" };
|
||||
const state: IrType = { kind: "object", className: "State" };
|
||||
const maybeState: IrType = { kind: "union", unionId: "u0" };
|
||||
|
||||
/** A key hierarchy (Node, Leaf), a class that is not a key (Plain), and the
|
||||
* value class (State), with `slots` a module global of the slots class. */
|
||||
function module(body: IrExpr[]): IrModule {
|
||||
const cls = (name: string, fields: [string, IrType][], base?: string) => ({
|
||||
name,
|
||||
...(base !== undefined ? { base } : {}),
|
||||
fields: fields.map(([field, type]) => ({ name: field, type })),
|
||||
methods: [],
|
||||
loc,
|
||||
});
|
||||
return {
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
threadSlotKeys: ["Node"],
|
||||
globals: [
|
||||
["slots", slotsType],
|
||||
["node", node],
|
||||
["leaf", leaf],
|
||||
["plain", plain],
|
||||
["state", state],
|
||||
].map(([name, type]) => ({ id: `%g.${name as string}`, name, type, mutable: false })),
|
||||
functions: [
|
||||
{
|
||||
name: "main",
|
||||
params: [],
|
||||
returnType: VOID,
|
||||
locals: [],
|
||||
body: body.map((expr) => ({ kind: "exprStmt" as const, expr, loc })),
|
||||
loc,
|
||||
},
|
||||
],
|
||||
classes: [
|
||||
cls("%ThreadSlots", []),
|
||||
cls("Node", [["name", STRING]]),
|
||||
cls(
|
||||
"Leaf",
|
||||
[
|
||||
["name", STRING],
|
||||
["weight", F64],
|
||||
],
|
||||
"Node",
|
||||
),
|
||||
cls("Plain", [["name", STRING]]),
|
||||
cls("State", [["count", F64]]),
|
||||
],
|
||||
unions: [{ id: "u0", arms: [state, { kind: "undefinedT" }] }],
|
||||
} as unknown as IrModule;
|
||||
}
|
||||
|
||||
function body(llvm: string, name: string): string {
|
||||
const start = llvm.indexOf(`@${name}(`);
|
||||
return start < 0 ? "" : llvm.slice(start, llvm.indexOf("\n}", start));
|
||||
}
|
||||
|
||||
const intrinsic = (name: string, args: IrExpr[], type: IrType): IrExpr =>
|
||||
({ kind: "intrinsic", name, args, type, loc }) as IrExpr;
|
||||
const str = (value: string): IrExpr => ({ kind: "strLit", value, type: STRING, loc });
|
||||
const global = (id: string, type: IrType): IrExpr =>
|
||||
({ kind: "varRef", localId: `%g.${id}`, type, loc }) as IrExpr;
|
||||
const slots = global("slots", slotsType);
|
||||
const fresh = (type: IrType & { kind: "object" }): IrExpr =>
|
||||
global(type.className.toLowerCase(), type);
|
||||
|
||||
test("slots.get reads the slot word, the page and the entry inline", () => {
|
||||
const calls = [
|
||||
intrinsic("slots.get", [str("s"), slots, fresh(leaf)], maybeState),
|
||||
intrinsic("slots.has", [str("s"), slots, fresh(node)], BOOL),
|
||||
intrinsic("slots.set", [str("s"), slots, fresh(leaf), fresh(state)], VOID),
|
||||
intrinsic("slots.delete", [str("t"), slots, fresh(node)], BOOL),
|
||||
];
|
||||
expect(validateModule(module(calls))).toEqual([]);
|
||||
const llvm = emitLlvmModule(module(calls), { pointerBits: 64 });
|
||||
// The slot word sits after the header words of every hierarchy member.
|
||||
expect(llvm).toMatch(
|
||||
new RegExp(`%${mangleClassStruct("Node")} = type \\{ i64, ptr, i32, ptr \\}`),
|
||||
);
|
||||
expect(llvm).toMatch(
|
||||
new RegExp(`%${mangleClassStruct("Leaf")} = type \\{ i64, ptr, i32, ptr, (i32|double) \\}`),
|
||||
);
|
||||
expect(llvm).toMatch(new RegExp(`%${mangleClassStruct("Plain")} = type \\{ i64, ptr \\}`));
|
||||
// One block per family name.
|
||||
expect(llvm).toMatch(
|
||||
/@sc_slots_0 = internal global \{ ptr, i32, i32, ptr, ptr \} zeroinitializer/,
|
||||
);
|
||||
expect(llvm).toMatch(
|
||||
/@sc_slots_1 = internal global \{ ptr, i32, i32, ptr, ptr \} zeroinitializer/,
|
||||
);
|
||||
const main = body(llvm, "sc_f_main");
|
||||
expect(main).toMatch(
|
||||
/getelementptr inbounds %\S+, ptr %\S+, i64 0, i32 2\n {2}(%\S+) = load atomic i32, ptr %\S+ monotonic, align 4\n {2}%\S+ = lshr i32 \1, 10/,
|
||||
);
|
||||
expect(main).not.toMatch(/call ptr @scr_slots_get/);
|
||||
expect(main).toMatch(
|
||||
/call void @scr_slots_set\(ptr @sc_slots_0, ptr %\S+, ptr %\S+, ptr %\S+, ptr @\S+\)/,
|
||||
);
|
||||
expect(main).toMatch(/call zeroext i1 @scr_slots_delete\(ptr @sc_slots_1, ptr %\S+\)/);
|
||||
});
|
||||
|
||||
test("freeing a key releases its values when its slot word is set", () => {
|
||||
const llvm = emitLlvmModule(
|
||||
module([intrinsic("slots.has", [str("s"), slots, fresh(node)], BOOL)]),
|
||||
{ pointerBits: 64 },
|
||||
);
|
||||
expect(llvm).toMatch(/icmp ne i32 %tdw, 0\n {2}br i1 %tdw\.set, label %tdw\.forget/);
|
||||
expect(llvm).toMatch(/call void @scr_slots_forget\(i32 %tdw\)/);
|
||||
});
|
||||
|
||||
test("the validator checks ThreadSlots intrinsic operands", () => {
|
||||
const bad = [
|
||||
intrinsic("slots.get", [str("s"), slots], maybeState),
|
||||
intrinsic("slots.has", [slots, slots, fresh(node)], BOOL),
|
||||
intrinsic("slots.has", [str("s"), slots, fresh(plain)], BOOL),
|
||||
intrinsic(
|
||||
"slots.set",
|
||||
[str("s"), slots, fresh(node), { kind: "numLit", value: 1, type: F64, loc }],
|
||||
VOID,
|
||||
),
|
||||
intrinsic("slots.get", [str("s"), slots, fresh(node)], state),
|
||||
];
|
||||
expect(validateModule(module(bad)).map((e) => e.message)).toEqual([
|
||||
"in main: slots.get takes 3 arguments",
|
||||
"in main: slots.has takes a name literal and a %ThreadSlots value",
|
||||
"in main: slots.has key object:Plain is not a ThreadSlots key class",
|
||||
"in main: slots.set value f64 is not a ThreadSlots value type",
|
||||
"in main: slots.get has a V | undefined union type",
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,170 @@
|
||||
/* ThreadSlots (@scriptc/experimental): the slots.* intrinsics (scr_slots.c,
|
||||
* frontend/lowering/thread-slots.ts).
|
||||
*
|
||||
* Each family name has one `{ ptr, i32, i32, ptr, ptr }` ScrSlots block,
|
||||
* an internal global that is thread-local where module globals are. A key
|
||||
* class's hierarchy carries an i32 slot word after its header words
|
||||
* (classSlotWordIndex). A read is inline: the slot word, then the page
|
||||
* and the entry,
|
||||
*
|
||||
* page = word >> 10 < block.npages ? block.pages[word >> 10] : none
|
||||
* value = page[word & 1023]
|
||||
*
|
||||
* and absent pages are a shared page of NULL entries (scr_slots_none), so
|
||||
* a key without a value reads NULL, the undefined of `V | undefined`. Set
|
||||
* and delete call the runtime, which holds one reference to each value and
|
||||
* releases the value it replaces or removes. */
|
||||
import { InternalCompilerError } from "../../errors.js";
|
||||
import { isUnitType, typeKey, type IrType } from "../../ir/ir.js";
|
||||
import { classSlotWordIndex, classStructSym } from "./classes.js";
|
||||
import { llvmCommentText } from "./common.js";
|
||||
import type { ExprOf, LlValue, LlvmEmitterContext } from "./expr-context.js";
|
||||
import { releaseSym } from "./shapes.js";
|
||||
|
||||
const BLOCK = "{ ptr, i32, i32, ptr, ptr }";
|
||||
|
||||
/** The slots.* intrinsics. */
|
||||
export function emitThreadSlotsIntrinsic(
|
||||
host: LlvmEmitterContext,
|
||||
e: ExprOf<"intrinsic">,
|
||||
): LlValue {
|
||||
const B = host.B;
|
||||
const [nameArg, slotsArg, keyArg, valueArg] = e.args;
|
||||
if (nameArg?.kind !== "strLit" || slotsArg === undefined || keyArg === undefined)
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.name} operands`);
|
||||
const block = host.threadSlotsBlock(nameArg.value);
|
||||
// The slots value is read for its effects (a module const's TDZ check).
|
||||
const slots = host.emitReadReceiver(slotsArg);
|
||||
const key = host.emitReadReceiver(keyArg);
|
||||
const value = valueArg !== undefined ? host.emitExpr(valueArg) : null;
|
||||
if (B.isTerminated()) return { name: "", type: e.type };
|
||||
if (keyArg.type.kind !== "object")
|
||||
throw new InternalCompilerError(`llvm emitter bug: ${e.name} key of kind ${keyArg.type.kind}`);
|
||||
const className = keyArg.type.className;
|
||||
const word = B.tmp();
|
||||
B.line(
|
||||
`${word} = getelementptr inbounds %${classStructSym(className)}, ptr ${key.name}, i64 0, i32 ${classSlotWordIndex(host.classMetaOf(className))}`,
|
||||
);
|
||||
switch (e.name) {
|
||||
case "slots.get": {
|
||||
const read = readSlot(host, block, word);
|
||||
if (host.nullableUnions.of(e.type) !== null)
|
||||
return host.own({ name: host.retainFrame(read, e.type), type: e.type });
|
||||
return boxedRead(host, read, e.type);
|
||||
}
|
||||
case "slots.has": {
|
||||
const read = readSlot(host, block, word);
|
||||
const has = B.tmp();
|
||||
B.line(`${has} = icmp ne ptr ${read}, null`);
|
||||
return { name: has, type: e.type };
|
||||
}
|
||||
case "slots.delete": {
|
||||
host.declare(`declare zeroext i1 @scr_slots_delete(ptr, ptr)`);
|
||||
const had = B.tmp();
|
||||
B.line(`${had} = call zeroext i1 @scr_slots_delete(ptr ${block}, ptr ${word})`);
|
||||
return { name: had, type: e.type };
|
||||
}
|
||||
case "slots.set": {
|
||||
if (value === null) throw new InternalCompilerError("llvm emitter bug: slots.set value");
|
||||
// The value moves into the block.
|
||||
host.toHeap(value);
|
||||
host.declare(`declare void @scr_slots_set(ptr, ptr, ptr, ptr, ptr)`);
|
||||
B.line(
|
||||
`call void @scr_slots_set(ptr ${block}, ptr ${key.name}, ptr ${word}, ptr ${value.name}, ptr ${releaseSym(host.shapeHost, value.type)})`,
|
||||
);
|
||||
if (e.type.kind === "void") return { name: "", type: e.type };
|
||||
return host.own({ name: host.retainFrame(slots.name, e.type), type: e.type });
|
||||
}
|
||||
default:
|
||||
throw new InternalCompilerError(`llvm emitter bug: unknown intrinsic ${e.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** A read as a tagged union box, for `V | undefined` types that the module
|
||||
* keeps boxed (Map keys and Set elements of that type): the value's arm, or
|
||||
* the interned undefined instance for NULL. */
|
||||
function boxedRead(host: LlvmEmitterContext, read: string, type: IrType): LlValue {
|
||||
const B = host.B;
|
||||
const def = type.kind === "union" ? host.unionsById.get(type.unionId) : undefined;
|
||||
const refTag = def?.arms.findIndex((a) => !isUnitType(a)) ?? -1;
|
||||
const unitTag = def?.arms.findIndex((a) => a.kind === "undefinedT") ?? -1;
|
||||
if (def === undefined || refTag < 0 || unitTag < 0 || def.arms.length !== 2)
|
||||
throw new InternalCompilerError(`llvm emitter bug: slots.get of type ${typeKey(type)}`);
|
||||
const result = B.slot();
|
||||
B.entryAllocas.push(`${result} = alloca ptr`);
|
||||
const has = B.tmp();
|
||||
B.line(`${has} = icmp ne ptr ${read}, null`);
|
||||
const some = B.newLabel("slots.some"),
|
||||
none = B.newLabel("slots.none"),
|
||||
done = B.newLabel("slots.box");
|
||||
B.condBr(has, some, none);
|
||||
B.startBlock(some);
|
||||
const arm = def.arms[refTag]!;
|
||||
const box = host.unionNewOwned(def.id, refTag, { name: host.retainHeap(read, arm), type: arm });
|
||||
B.line(`store ptr ${box}, ptr ${result}`);
|
||||
B.br(done);
|
||||
B.startBlock(none);
|
||||
B.line(`store ptr ${host.unitInstanceRef(def.id, unitTag)}, ptr ${result}`);
|
||||
B.br(done);
|
||||
B.startBlock(done);
|
||||
const out = B.tmp();
|
||||
B.line(`${out} = load ptr, ptr ${result}`);
|
||||
return host.own({ name: out, type });
|
||||
}
|
||||
|
||||
/** This thread's value for the slot word at `word`, borrowed, or NULL. */
|
||||
function readSlot(host: LlvmEmitterContext, block: string, word: string): string {
|
||||
const B = host.B;
|
||||
host.declare(`@scr_slots_none = external constant [1024 x ptr]`);
|
||||
const index = B.tmp(),
|
||||
page = B.tmp(),
|
||||
pages = B.tmp(),
|
||||
count = B.tmp(),
|
||||
inRange = B.tmp();
|
||||
B.line(`${index} = load atomic i32, ptr ${word} monotonic, align 4`);
|
||||
B.line(`${page} = lshr i32 ${index}, 10`);
|
||||
B.line(`${count} = getelementptr inbounds ${BLOCK}, ptr ${block}, i64 0, i32 1`);
|
||||
const npages = B.tmp();
|
||||
B.line(`${npages} = load i32, ptr ${count}`);
|
||||
B.line(`${inRange} = icmp ult i32 ${page}, ${npages}`);
|
||||
const result = B.slot();
|
||||
B.entryAllocas.push(`${result} = alloca ptr`);
|
||||
B.line(`store ptr null, ptr ${result}`);
|
||||
const found = B.newLabel("slots.page"),
|
||||
done = B.newLabel("slots.j");
|
||||
B.condBr(inRange, found, done);
|
||||
B.startBlock(found);
|
||||
B.line(`${pages} = load ptr, ptr ${block}`);
|
||||
const pageIndex = B.tmp(),
|
||||
pagePtr = B.tmp(),
|
||||
entries = B.tmp(),
|
||||
entryIndex = B.tmp(),
|
||||
entryWide = B.tmp(),
|
||||
entry = B.tmp(),
|
||||
v = B.tmp();
|
||||
B.line(`${pageIndex} = zext i32 ${page} to i64`);
|
||||
B.line(`${pagePtr} = getelementptr inbounds ptr, ptr ${pages}, i64 ${pageIndex}`);
|
||||
B.line(`${entries} = load ptr, ptr ${pagePtr}`);
|
||||
B.line(`${entryIndex} = and i32 ${index}, 1023`);
|
||||
B.line(`${entryWide} = zext i32 ${entryIndex} to i64`);
|
||||
B.line(`${entry} = getelementptr inbounds ptr, ptr ${entries}, i64 ${entryWide}`);
|
||||
B.line(`${v} = load ptr, ptr ${entry}`);
|
||||
B.line(`store ptr ${v}, ptr ${result}`);
|
||||
B.br(done);
|
||||
B.startBlock(done);
|
||||
const out = B.tmp();
|
||||
B.line(`${out} = load ptr, ptr ${result}`);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** The ScrSlots block definitions of the families a module uses. */
|
||||
export function threadSlotsBlockDefinitions(
|
||||
blocks: ReadonlyMap<string, string>,
|
||||
threadLocal: boolean,
|
||||
): string[] {
|
||||
const tl = threadLocal ? "thread_local " : "";
|
||||
return [...blocks].map(
|
||||
([name, sym]) =>
|
||||
`${sym} = internal ${tl}global ${BLOCK} zeroinitializer ; ThreadSlots ${llvmCommentText(JSON.stringify(name))}`,
|
||||
);
|
||||
}
|
||||
@@ -297,7 +297,9 @@ export function computeMayThrow(
|
||||
rec.method === "replaceAll" ||
|
||||
rec.method === "split" ||
|
||||
rec.method === "matchAll" ||
|
||||
rec.method === "matchAllInto"
|
||||
rec.method === "matchAllCaptures" ||
|
||||
rec.method === "matchAllInto" ||
|
||||
rec.method === "matchAllCapturesInto"
|
||||
) {
|
||||
f.throws = true;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ import type { NativeTargetSpec } from "./targets.js";
|
||||
|
||||
export interface NativeLinkFeatures {
|
||||
workers?: boolean;
|
||||
/** The young-in nursery (backend/young-nursery.ts): the SCR_YIN
|
||||
* variant of the collector unit. Absent when off. */
|
||||
youngNursery?: boolean;
|
||||
regex: boolean;
|
||||
copying: boolean;
|
||||
textDecoderLegacy: boolean;
|
||||
|
||||
@@ -46,6 +46,8 @@ export interface CcOptions {
|
||||
frameworks?: readonly string[];
|
||||
/** Isolated native Worker contexts; every runtime object must use TLS. */
|
||||
workers?: boolean;
|
||||
/** Compile the runtime with SCR_YIN (backend/young-nursery.ts). */
|
||||
youngNursery?: boolean;
|
||||
|
||||
regex?: boolean;
|
||||
/** The program uses one of the copying/typed-array bridge intrinsics
|
||||
|
||||
@@ -445,6 +445,7 @@ export async function compileCInternal(
|
||||
: [optimization === "dev" ? "-O0" : "-O2"]),
|
||||
...executableSectionFlags.compile,
|
||||
...(opts.textDecoderLegacy ? ["-DSCR_TEXT_DECODER_LEGACY"] : []),
|
||||
...(opts.youngNursery ? ["-DSCR_YIN"] : []),
|
||||
"-fno-math-errno",
|
||||
// The runtime object model uses type-punned C: a hierarchy
|
||||
// upcast is a raw pointer cast, so one object's header (rc, vt) and
|
||||
@@ -644,6 +645,7 @@ export async function compileCInternal(
|
||||
: [optimization === "dev" ? "-O0" : "-O2"]),
|
||||
...executableSectionFlags.compile,
|
||||
...(opts.textDecoderLegacy ? ["-DSCR_TEXT_DECODER_LEGACY"] : []),
|
||||
...(opts.youngNursery ? ["-DSCR_YIN"] : []),
|
||||
"-fno-math-errno",
|
||||
"-fno-strict-aliasing", // the emitted object model type-puns — see buildArgs
|
||||
"-Wno-deprecated-declarations",
|
||||
|
||||
@@ -70,6 +70,7 @@ export const EXECUTABLE_RUNTIME_SOURCES = [
|
||||
"scr_publish.c",
|
||||
"scr_region.c",
|
||||
"scr_itable.c",
|
||||
"scr_slots.c",
|
||||
"scr_alloc.c",
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ export interface RuntimePackManifest {
|
||||
|
||||
export interface RuntimeFeatureSet extends NativeLinkFeatures {
|
||||
workers: boolean;
|
||||
youngNursery: boolean;
|
||||
eventTarget: boolean;
|
||||
nativeFetch: boolean;
|
||||
netEffective: boolean;
|
||||
@@ -266,6 +267,7 @@ export function effectiveRuntimeFeatures(
|
||||
return {
|
||||
...features,
|
||||
workers: features.workers === true,
|
||||
youngNursery: features.youngNursery === true,
|
||||
eventTarget: features.eventTarget === true,
|
||||
nativeFetch,
|
||||
netEffective,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/** The young-in nursery (phase 1, experimental): compile-time opt-in with
|
||||
* SCRIPTC_YOUNG_NURSERY=1. Executables then allocate compiled class and
|
||||
* record instances young (the emitted constructors call scr_yin_enter) and
|
||||
* link the collector unit built with SCR_YIN, which collects young cycles
|
||||
* in nursery collections (scr_cycle.c, "young-in nursery"). Unset, nothing
|
||||
* the compiler emits or links changes. */
|
||||
export function youngNurseryRequested(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
return env["SCRIPTC_YOUNG_NURSERY"] === "1";
|
||||
}
|
||||
@@ -518,6 +518,15 @@ export const NODE24_FETCH_COMPAT_PROFILE = {
|
||||
evidence: [fixture("static")],
|
||||
}),
|
||||
),
|
||||
{
|
||||
id: "stdlib.response.static.json",
|
||||
name: "Response.json",
|
||||
kind: "static-method",
|
||||
facets: ["webidl-conversion", "error-shape"],
|
||||
scope:
|
||||
"data that crosses the checked-dynamic boundary, serialized with JSON.stringify (toJSON hooks included); ResponseInit as for the constructor",
|
||||
evidence: [fixture("static-response-json")],
|
||||
},
|
||||
...["json", "text", "bytes", "arrayBuffer"].map((member): FetchCompatOperation => ({
|
||||
id: `stdlib.response.${member}`,
|
||||
name: `Response.${member}`,
|
||||
@@ -681,7 +690,8 @@ export const NODE24_FETCH_COMPAT_PROFILE = {
|
||||
),
|
||||
|
||||
staticEntry("stdlib.response.constructor", "Response", "constructor", "constructor"),
|
||||
...["error", "json", "redirect"].map((member) =>
|
||||
staticEntry("stdlib.response.static.json", "Response", "json", "static"),
|
||||
...["error", "redirect"].map((member) =>
|
||||
unsupportedEntry(
|
||||
`stdlib.response.static.${member}`,
|
||||
"Response",
|
||||
|
||||
@@ -49,6 +49,7 @@ export interface EarlyExecutableNativeFeatures {
|
||||
/** Omitted is the historical release posture. */
|
||||
optimization?: "dev" | "speed";
|
||||
workers?: boolean;
|
||||
youngNursery?: boolean;
|
||||
regex: boolean;
|
||||
copying: boolean;
|
||||
textDecoderLegacy: boolean;
|
||||
@@ -112,6 +113,8 @@ export interface EarlyExecutableCacheOptions {
|
||||
windowsSubsystem?: "gui";
|
||||
/** Omitted unless --detached-exit is set. */
|
||||
detachedExit?: true;
|
||||
/** Omitted unless SCRIPTC_YOUNG_NURSERY=1 (backend/young-nursery.ts). */
|
||||
youngNursery?: true;
|
||||
npmStatic: readonly string[] | "auto" | null;
|
||||
/** Raw manifest identity: path and bytes. Native archives remain under the
|
||||
* stricter native cache's independent dependency validation. */
|
||||
@@ -202,7 +205,8 @@ function validNativeFeatures(value: unknown): value is EarlyExecutableNativeFeat
|
||||
(native.optimization === undefined ||
|
||||
native.optimization === "dev" ||
|
||||
native.optimization === "speed") &&
|
||||
(native.workers === undefined || typeof native.workers === "boolean"),
|
||||
(native.workers === undefined || typeof native.workers === "boolean") &&
|
||||
(native.youngNursery === undefined || typeof native.youngNursery === "boolean"),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -223,6 +227,7 @@ function cacheKey(options: EarlyExecutableCacheOptions): string {
|
||||
...(options.strip ? ["strip"] : []),
|
||||
...(options.windowsSubsystem === "gui" ? ["windows-subsystem-gui"] : []),
|
||||
...(options.detachedExit ? ["detached-exit"] : []),
|
||||
...(options.youngNursery ? ["young-nursery"] : []),
|
||||
options.npmStatic === null
|
||||
? "<npm-static-off>"
|
||||
: options.npmStatic === "auto"
|
||||
@@ -262,6 +267,7 @@ function routeKey(options: Omit<EarlyExecutableRouteOptions, "nativeEnvironment"
|
||||
if (options.strip) hash.update("strip\0");
|
||||
if (options.windowsSubsystem === "gui") hash.update("windows-subsystem-gui\0");
|
||||
if (options.detachedExit) hash.update("detached-exit\0");
|
||||
if (options.youngNursery) hash.update("young-nursery\0");
|
||||
hash
|
||||
.update(
|
||||
options.npmStatic === null
|
||||
|
||||
@@ -56,8 +56,8 @@ function identPrefixOf(node: ts.Node, sf: ts.SourceFile): string {
|
||||
export function bareRequireSpecOf(e: ts.Node): string | null {
|
||||
if (!ts.isCallExpression(e) || e.questionDotToken !== undefined) return null;
|
||||
if (!ts.isIdentifier(e.expression) || e.expression.text !== "require") return null;
|
||||
if (e.arguments.length !== 1) return null;
|
||||
const arg = e.arguments[0]!;
|
||||
if (e.arguments.nodes.length !== 1) return null;
|
||||
const arg = e.arguments.nodes[0]!;
|
||||
return ts.isStringLiteral(arg) ? arg.text : null;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ function scanTableLiteral(
|
||||
reexports: string[],
|
||||
): void {
|
||||
const text = sf.text;
|
||||
for (const prop of obj.properties) {
|
||||
for (const prop of obj.properties.nodes) {
|
||||
if (ts.isGetAccessor(prop)) {
|
||||
// `get name() {...}` — merve's explicit early termination... unless
|
||||
// the accessor's NAME is not a plain identifier, where the lexer
|
||||
@@ -253,8 +253,8 @@ function scanDefineProperty(call: ts.CallExpression, sf: ts.SourceFile, out: Set
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (call.arguments.length !== 3) return;
|
||||
const [recv, nameArg, desc] = call.arguments as unknown as [
|
||||
if (call.arguments.nodes.length !== 3) return;
|
||||
const [recv, nameArg, desc] = call.arguments.nodes as unknown as [
|
||||
ts.Expression,
|
||||
ts.Expression,
|
||||
ts.Expression,
|
||||
@@ -263,7 +263,7 @@ function scanDefineProperty(call: ts.CallExpression, sf: ts.SourceFile, out: Set
|
||||
if (!ts.isStringLiteral(nameArg)) return;
|
||||
if (!ts.isObjectLiteralExpression(desc)) return;
|
||||
let i = 0;
|
||||
const props = desc.properties;
|
||||
const props = desc.properties.nodes;
|
||||
const first = props[0];
|
||||
if (
|
||||
first !== undefined &&
|
||||
@@ -287,8 +287,8 @@ function scanDefineProperty(call: ts.CallExpression, sf: ts.SourceFile, out: Set
|
||||
// and the getter must be the descriptor's LAST property.
|
||||
const body = getterBodyOf(p);
|
||||
if (body === undefined || i !== props.length - 1) return;
|
||||
if (body.statements.length !== 1) return;
|
||||
const ret = body.statements[0]!;
|
||||
if (body.statements.nodes.length !== 1) return;
|
||||
const ret = body.statements.nodes[0]!;
|
||||
if (!ts.isReturnStatement(ret) || ret.expression === undefined) return;
|
||||
const r = ret.expression;
|
||||
const returnsIdentish =
|
||||
@@ -313,7 +313,7 @@ function getterBodyOf(p: ts.ObjectLiteralElementLike): ts.Block | undefined {
|
||||
p.name.text === "get" &&
|
||||
p.asteriskToken === undefined &&
|
||||
(ts.getModifiers(p) ?? []).length === 0 &&
|
||||
p.parameters.length === 0
|
||||
p.parameters.nodes.length === 0
|
||||
) {
|
||||
return p.body;
|
||||
}
|
||||
@@ -322,7 +322,7 @@ function getterBodyOf(p: ts.ObjectLiteralElementLike): ts.Block | undefined {
|
||||
ts.isIdentifier(p.name) &&
|
||||
p.name.text === "get" &&
|
||||
ts.isFunctionExpression(p.initializer) &&
|
||||
p.initializer.parameters.length === 0 &&
|
||||
p.initializer.parameters.nodes.length === 0 &&
|
||||
p.initializer.asteriskToken === undefined &&
|
||||
(ts.getModifiers(p.initializer) ?? []).length === 0
|
||||
) {
|
||||
@@ -349,7 +349,7 @@ function starExportSpecOf(call: ts.CallExpression, sf: ts.SourceFile): string |
|
||||
if (nameNode === undefined) return null;
|
||||
if (nameNode.text !== "__export" && nameNode.text !== "__exportStar") return null;
|
||||
if (!sourceSpellsIdentifier(nameNode, sf)) return null;
|
||||
const arg0 = call.arguments[0];
|
||||
const arg0 = call.arguments.nodes[0];
|
||||
if (arg0 === undefined) return null;
|
||||
if (!byteAdjacentParen(sf, nameNode.getEnd(), arg0.getStart(sf))) return null;
|
||||
return leadingRequireOf(arg0, sf)?.spec ?? null;
|
||||
@@ -369,7 +369,7 @@ function byteAdjacentParen(sf: ts.SourceFile, nameEnd: number, argStart: number)
|
||||
* probed: a member-qualified helper or one space after its paren misses).
|
||||
* Answers [ID, spec]. */
|
||||
function starAssignOf(stmt: ts.VariableStatement, sf: ts.SourceFile): [string, string] | null {
|
||||
const decl = stmt.declarationList.declarations[0];
|
||||
const decl = stmt.declarationList.declarations.nodes[0];
|
||||
if (decl === undefined || !ts.isIdentifier(decl.name) || !sourceSpellsIdentifier(decl.name, sf))
|
||||
return null;
|
||||
const init = decl.initializer;
|
||||
@@ -381,9 +381,9 @@ function starAssignOf(stmt: ts.VariableStatement, sf: ts.SourceFile): [string, s
|
||||
init.questionDotToken === undefined &&
|
||||
ts.isIdentifier(init.expression) &&
|
||||
init.expression.text === "_interopRequireWildcard" &&
|
||||
init.arguments.length >= 1
|
||||
init.arguments.nodes.length >= 1
|
||||
) {
|
||||
const arg0 = init.arguments[0]!;
|
||||
const arg0 = init.arguments.nodes[0]!;
|
||||
if (!byteAdjacentParen(sf, init.expression.getEnd(), arg0.getStart(sf))) return null;
|
||||
const spec = bareRequireSpecOf(arg0);
|
||||
if (spec !== null) return [decl.name.text, spec];
|
||||
@@ -434,8 +434,8 @@ function isHasOwnCall(e: ts.Expression, key: string): boolean {
|
||||
)
|
||||
cur = cur.expression;
|
||||
if (!ts.isIdentifier(cur) || cur.text !== "Object") return false;
|
||||
if (e.arguments.length !== 2) return false;
|
||||
const [recv, k] = e.arguments as unknown as [ts.Expression, ts.Expression];
|
||||
if (e.arguments.nodes.length !== 2) return false;
|
||||
const [recv, k] = e.arguments.nodes as unknown as [ts.Expression, ts.Expression];
|
||||
return ts.isIdentifier(recv) && ts.isIdentifier(k) && k.text === key;
|
||||
}
|
||||
|
||||
@@ -448,9 +448,9 @@ function isDirectHasOwnCall(e: ts.Expression, key: string): boolean {
|
||||
ts.isIdentifier(e.expression.name) &&
|
||||
e.expression.name.text === "hasOwnProperty" &&
|
||||
ts.isIdentifier(e.expression.expression) &&
|
||||
e.arguments.length === 1 &&
|
||||
ts.isIdentifier(e.arguments[0]!) &&
|
||||
(e.arguments[0] as ts.Identifier).text === key
|
||||
e.arguments.nodes.length === 1 &&
|
||||
ts.isIdentifier(e.arguments.nodes[0]!) &&
|
||||
(e.arguments.nodes[0] as ts.Identifier).text === key
|
||||
);
|
||||
}
|
||||
|
||||
@@ -477,16 +477,16 @@ function isCopyStatement(stmt: ts.Statement, id1: string, key: string): boolean
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (e.arguments.length !== 3) return false;
|
||||
const [recv, nameArg, desc] = e.arguments as unknown as [
|
||||
if (e.arguments.nodes.length !== 3) return false;
|
||||
const [recv, nameArg, desc] = e.arguments.nodes as unknown as [
|
||||
ts.Expression,
|
||||
ts.Expression,
|
||||
ts.Expression,
|
||||
];
|
||||
if (!isExportsTarget(recv)) return false;
|
||||
if (!ts.isIdentifier(nameArg) || nameArg.text !== key) return false;
|
||||
if (!ts.isObjectLiteralExpression(desc) || desc.properties.length !== 2) return false;
|
||||
const [en, get] = desc.properties as unknown as [
|
||||
if (!ts.isObjectLiteralExpression(desc) || desc.properties.nodes.length !== 2) return false;
|
||||
const [en, get] = desc.properties.nodes as unknown as [
|
||||
ts.ObjectLiteralElementLike,
|
||||
ts.ObjectLiteralElementLike,
|
||||
];
|
||||
@@ -494,8 +494,8 @@ function isCopyStatement(stmt: ts.Statement, id1: string, key: string): boolean
|
||||
return false;
|
||||
if (en.initializer.kind !== ts.SyntaxKind.TrueKeyword) return false;
|
||||
const body = getterBodyOf(get);
|
||||
if (body === undefined || body.statements.length !== 1) return false;
|
||||
const ret = body.statements[0]!;
|
||||
if (body === undefined || body.statements.nodes.length !== 1) return false;
|
||||
const ret = body.statements.nodes[0]!;
|
||||
if (!ts.isReturnStatement(ret) || ret.expression === undefined) return false;
|
||||
return isKeyedAccess(ret.expression, (x) => ts.isIdentifier(x) && x.text === id1, key);
|
||||
}
|
||||
@@ -541,16 +541,16 @@ function starLoopIdOf(call: ts.CallExpression, sf: ts.SourceFile): string | null
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
if (keysCall.arguments.length !== 1) return null;
|
||||
const id1Node = keysCall.arguments[0]!;
|
||||
if (keysCall.arguments.nodes.length !== 1) return null;
|
||||
const id1Node = keysCall.arguments.nodes[0]!;
|
||||
if (!ts.isIdentifier(id1Node) || !sourceSpellsIdentifier(id1Node, sf)) return null;
|
||||
const id1 = id1Node.text;
|
||||
const fn = call.arguments[0];
|
||||
const fn = call.arguments.nodes[0];
|
||||
if (fn === undefined || !ts.isFunctionExpression(fn)) return null; // arrows never match (probed)
|
||||
if (fn.name !== undefined) return null; // a NAMED callback misses (probed)
|
||||
if (fn.asteriskToken !== undefined || (ts.getModifiers(fn) ?? []).length > 0) return null;
|
||||
if (fn.parameters.length !== 1) return null;
|
||||
const keyParam = fn.parameters[0]!;
|
||||
if (fn.parameters.nodes.length !== 1) return null;
|
||||
const keyParam = fn.parameters.nodes[0]!;
|
||||
if (
|
||||
!ts.isIdentifier(keyParam.name) ||
|
||||
keyParam.initializer !== undefined ||
|
||||
@@ -558,7 +558,7 @@ function starLoopIdOf(call: ts.CallExpression, sf: ts.SourceFile): string | null
|
||||
)
|
||||
return null;
|
||||
const key = keyParam.name.text;
|
||||
const stmts = fn.body.statements;
|
||||
const stmts = fn.body.statements.nodes;
|
||||
|
||||
// Form B: the single `if (KEY !== 'default' [&& !hasOwn]) COPY` statement.
|
||||
if (stmts.length === 1 && ts.isIfStatement(stmts[0]!) && stmts[0]!.elseStatement === undefined) {
|
||||
|
||||
@@ -262,7 +262,7 @@ function placeHeritage(
|
||||
return unsupported(out, node, marker);
|
||||
// `implements Acyclic<C>` would make the class implement C's own type,
|
||||
// which the lowering treats as a protocol of C.
|
||||
if ((node.typeArguments?.length ?? 0) > 0) return unsupported(out, node, marker);
|
||||
if ((node.typeArguments?.nodes.length ?? 0) > 0) return unsupported(out, node, marker);
|
||||
const note = (name: string): ContractNote => ({ marker, loc: locOf(node), name });
|
||||
if (
|
||||
(ts.isClassDeclaration(owner) || ts.isClassExpression(owner)) &&
|
||||
@@ -324,8 +324,8 @@ function place(
|
||||
ts.isTypeReferenceNode(parent) &&
|
||||
ts.isIdentifier(parent.typeName) &&
|
||||
(parent.typeName.text === "Array" || parent.typeName.text === "ReadonlyArray") &&
|
||||
parent.typeArguments?.length === 1 &&
|
||||
parent.typeArguments[0] === ref
|
||||
parent.typeArguments?.nodes.length === 1 &&
|
||||
parent.typeArguments.nodes[0] === ref
|
||||
) {
|
||||
node = parent;
|
||||
parent = parent.parent;
|
||||
@@ -336,7 +336,7 @@ function place(
|
||||
// contract of the function the NoThrow annotates.
|
||||
if (marker !== "NoThrow" && parent !== undefined && ts.isTypeReferenceNode(parent)) {
|
||||
const wrapper = markerOf(checker, parent);
|
||||
if (wrapper === "NoThrow" && parent.typeArguments?.[0] === node) {
|
||||
if (wrapper === "NoThrow" && parent.typeArguments?.nodes[0] === node) {
|
||||
node = parent;
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
/* Provenance of the @scriptc/experimental exports. The package is scriptc's
|
||||
* own (packages/experimental): Node.js runs its JavaScript implementation,
|
||||
* scriptc builds lower its exports (lowering/lifetime-regions.ts,
|
||||
* lowering/intern-tables.ts) and never compile its body. */
|
||||
* lowering/intern-tables.ts, lowering/thread-slots.ts) and never compile
|
||||
* its body. */
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import * as ts from "./ts7/adapter.js";
|
||||
|
||||
export const EXPERIMENTAL_MODULE = "@scriptc/experimental";
|
||||
|
||||
export type ExperimentalMember = "LifetimeRegion" | "InternTable";
|
||||
export type ExperimentalMember = "LifetimeRegion" | "InternTable" | "ThreadSlots";
|
||||
|
||||
/** The @scriptc/experimental export an identifier is a named-import binding of. */
|
||||
export function experimentalImportOf(
|
||||
@@ -27,7 +28,9 @@ export function experimentalImportOf(
|
||||
)
|
||||
return null;
|
||||
const member = decl.propertyName?.text ?? decl.name.text;
|
||||
return member === "LifetimeRegion" || member === "InternTable" ? member : null;
|
||||
return member === "LifetimeRegion" || member === "InternTable" || member === "ThreadSlots"
|
||||
? member
|
||||
: null;
|
||||
}
|
||||
|
||||
/** Whether `sf` is the package's own declaration file: its directory's
|
||||
@@ -114,3 +117,27 @@ export function internTableMemberOf(
|
||||
? name
|
||||
: null;
|
||||
}
|
||||
|
||||
export type ThreadSlotsMember = "get" | "has" | "set" | "delete";
|
||||
|
||||
/** The ThreadSlots member a property access names (`slots.get`), by the
|
||||
* declaration it resolves to. */
|
||||
export function threadSlotsMemberOf(
|
||||
program: ts.Program,
|
||||
access: ts.PropertyAccessExpression,
|
||||
known: Map<string, boolean>,
|
||||
): ThreadSlotsMember | null {
|
||||
const checker = program.getTypeChecker();
|
||||
const symbol = checker.getSymbolAtLocation(access.name);
|
||||
const decl = symbol ? checker.declarationsOf(symbol)[0] : undefined;
|
||||
if (decl === undefined || !ts.isMethodDeclaration(decl)) return null;
|
||||
const owner = decl.parent;
|
||||
if (
|
||||
!ts.isClassDeclaration(owner) ||
|
||||
owner.name?.text !== "ThreadSlots" ||
|
||||
!isExperimentalDeclarationFile(owner.getSourceFile(), known)
|
||||
)
|
||||
return null;
|
||||
const name = access.name.text;
|
||||
return name === "get" || name === "has" || name === "set" || name === "delete" ? name : null;
|
||||
}
|
||||
|
||||
@@ -158,13 +158,13 @@ function staticBoolean(
|
||||
if (
|
||||
ts.isCallExpression(current) &&
|
||||
!current.questionDotToken &&
|
||||
current.arguments.length === 1 &&
|
||||
current.arguments.nodes.length === 1 &&
|
||||
ts.isPropertyAccessExpression(current.expression) &&
|
||||
!current.expression.questionDotToken &&
|
||||
(current.expression.name.text === "endsWith" || current.expression.name.text === "startsWith")
|
||||
) {
|
||||
const receiver = staticString(program, current.expression.expression, seen);
|
||||
const needle = staticString(program, current.arguments[0]!, seen);
|
||||
const needle = staticString(program, current.arguments.nodes[0]!, seen);
|
||||
if (receiver === null || needle === null) return null;
|
||||
return current.expression.name.text === "endsWith"
|
||||
? receiver.endsWith(needle)
|
||||
@@ -194,7 +194,7 @@ function staticString(
|
||||
if (importMetaUrl(current)) return pathToFileURL(current.getSourceFile().fileName).href;
|
||||
if (ts.isTemplateExpression(current)) {
|
||||
let value = current.head.text;
|
||||
for (const span of current.templateSpans) {
|
||||
for (const span of current.templateSpans.nodes) {
|
||||
const part = staticString(program, span.expression, seen);
|
||||
if (part === null) return null;
|
||||
value += part + span.literal.text;
|
||||
@@ -219,11 +219,11 @@ function staticString(
|
||||
if (
|
||||
ts.isCallExpression(current) &&
|
||||
!current.questionDotToken &&
|
||||
current.arguments.length === 1 &&
|
||||
current.arguments.nodes.length === 1 &&
|
||||
ts.isIdentifier(current.expression) &&
|
||||
isBuiltinMemberImport(program, current.expression, "url", "fileURLToPath")
|
||||
) {
|
||||
const href = staticString(program, current.arguments[0]!, seen);
|
||||
const href = staticString(program, current.arguments.nodes[0]!, seen);
|
||||
if (href === null) return null;
|
||||
try {
|
||||
return fileURLToPath(href);
|
||||
@@ -237,7 +237,7 @@ function staticString(
|
||||
(isBuiltinMemberImport(program, current.expression, "url", "URL") ||
|
||||
(current.expression.text === "URL" && isNodeGlobal(program, current.expression)))
|
||||
) {
|
||||
const args = current.arguments ?? [];
|
||||
const args = current.arguments?.nodes ?? [];
|
||||
if (args.length < 1 || args.length > 2) return null;
|
||||
const input = staticString(program, args[0]!, seen);
|
||||
const base = args[1] === undefined ? null : staticString(program, args[1], seen);
|
||||
@@ -261,7 +261,7 @@ export function staticForkModulePath(program: ts.Program, expr: ts.Expression):
|
||||
const supported =
|
||||
(ts.isCallExpression(current) &&
|
||||
!current.questionDotToken &&
|
||||
current.arguments.length === 1 &&
|
||||
current.arguments.nodes.length === 1 &&
|
||||
ts.isIdentifier(current.expression) &&
|
||||
isBuiltinMemberImport(program, current.expression, "url", "fileURLToPath")) ||
|
||||
(ts.isNewExpression(current) &&
|
||||
@@ -279,7 +279,7 @@ export function staticForkModulePath(program: ts.Program, expr: ts.Expression):
|
||||
}
|
||||
|
||||
export function forkCallModulePath(program: ts.Program, call: ts.CallExpression): string | null {
|
||||
if (call.questionDotToken || call.arguments[0] === undefined) return null;
|
||||
if (call.questionDotToken || call.arguments.nodes[0] === undefined) return null;
|
||||
const callee = strip(call.expression);
|
||||
const isFork =
|
||||
(ts.isIdentifier(callee) && isBuiltinMemberImport(program, callee, "child_process", "fork")) ||
|
||||
@@ -291,7 +291,7 @@ export function forkCallModulePath(program: ts.Program, call: ts.CallExpression)
|
||||
if (!isFork) {
|
||||
return null;
|
||||
}
|
||||
return staticForkModulePath(program, call.arguments[0]);
|
||||
return staticForkModulePath(program, call.arguments.nodes[0]);
|
||||
}
|
||||
|
||||
/** Source-order static fork targets in the supplied files, deduplicated by
|
||||
@@ -304,7 +304,7 @@ export function forkTargetPaths(program: ts.Program, files: readonly ts.SourceFi
|
||||
const calls = moduleSourceCandidates(program, sourceFile).calls;
|
||||
const bindings: ts.Node[] = [];
|
||||
for (const call of calls) {
|
||||
if (call.questionDotToken || call.arguments[0] === undefined) continue;
|
||||
if (call.questionDotToken || call.arguments.nodes[0] === undefined) continue;
|
||||
const callee = strip(call.expression);
|
||||
if (ts.isIdentifier(callee)) bindings.push(callee);
|
||||
else if (
|
||||
|
||||
@@ -6,14 +6,14 @@ export function functionCanFallThrough(declaration: ts.Node): boolean {
|
||||
if (
|
||||
!ts.isFunctionLike(declaration) ||
|
||||
declaration.asteriskToken ||
|
||||
declaration.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)
|
||||
declaration.modifiers?.nodes.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)
|
||||
)
|
||||
return false;
|
||||
const body = declaration.body as ts.Block | ts.Expression | undefined;
|
||||
if (!body || !ts.isBlock(body)) return false;
|
||||
const completes = (statement: ts.Statement): boolean => {
|
||||
if (ts.isReturnStatement(statement) || ts.isThrowStatement(statement)) return false;
|
||||
if (ts.isBlock(statement)) return sequence(statement.statements);
|
||||
if (ts.isBlock(statement)) return sequence(statement.statements.nodes);
|
||||
if (ts.isIfStatement(statement))
|
||||
return (
|
||||
completes(statement.thenStatement) ||
|
||||
@@ -29,13 +29,13 @@ export function functionCanFallThrough(declaration: ts.Node): boolean {
|
||||
}
|
||||
if (ts.isSwitchStatement(statement))
|
||||
return (
|
||||
!statement.caseBlock.clauses.some(ts.isDefaultClause) ||
|
||||
statement.caseBlock.clauses.some((clause) => sequence(clause.statements))
|
||||
!statement.caseBlock.clauses.nodes.some(ts.isDefaultClause) ||
|
||||
statement.caseBlock.clauses.nodes.some((clause) => sequence(clause.statements.nodes))
|
||||
);
|
||||
return true;
|
||||
};
|
||||
const sequence = (statements: readonly ts.Statement[]): boolean => statements.every(completes);
|
||||
return sequence(body.statements);
|
||||
return sequence(body.statements.nodes);
|
||||
}
|
||||
|
||||
/** A documented JavaScript result must also represent explicit empty returns. */
|
||||
@@ -44,7 +44,7 @@ export function functionCanReturnUndefined(declaration: ts.Node): boolean {
|
||||
if (
|
||||
!ts.isFunctionLike(declaration) ||
|
||||
declaration.asteriskToken ||
|
||||
declaration.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)
|
||||
declaration.modifiers?.nodes.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword)
|
||||
)
|
||||
return false;
|
||||
const body = declaration.body;
|
||||
|
||||
@@ -50,9 +50,10 @@ export function inferredJsDiagnosticSuppressed(
|
||||
(ts.isFunctionDeclaration(declaration) ||
|
||||
ts.isFunctionExpression(declaration) ||
|
||||
ts.isMethodDeclaration(declaration)) &&
|
||||
node.arguments.length > declaration.parameters.length &&
|
||||
node.arguments[declaration.parameters.length]?.getStart(file) === diagnostic.pos &&
|
||||
node.arguments[node.arguments.length - 1]?.getEnd() === diagnostic.end &&
|
||||
node.arguments.nodes.length > declaration.parameters.nodes.length &&
|
||||
node.arguments.nodes[declaration.parameters.nodes.length]?.getStart(file) ===
|
||||
diagnostic.pos &&
|
||||
node.arguments.nodes[node.arguments.nodes.length - 1]?.getEnd() === diagnostic.end &&
|
||||
bodyReadsArguments(declaration)
|
||||
) {
|
||||
suppressed = true;
|
||||
@@ -92,8 +93,8 @@ export function inferredJsDiagnosticSuppressed(
|
||||
ts.isMethodDeclaration(declaration) &&
|
||||
declaration.type === undefined &&
|
||||
isJsSourceFileName(declaration.getSourceFile().fileName) &&
|
||||
declaration.body?.statements.length === 1 &&
|
||||
ts.isThrowStatement(declaration.body.statements[0]!)
|
||||
declaration.body?.statements?.nodes.length === 1 &&
|
||||
ts.isThrowStatement(declaration.body.statements.nodes[0]!)
|
||||
) {
|
||||
suppressed = true;
|
||||
return "stop";
|
||||
|
||||
@@ -141,7 +141,7 @@ function localTypeBindings(file: ts.SourceFile, bindings: TypeBindings): Set<str
|
||||
const add = (name: ts.Identifier | undefined): void => {
|
||||
if (name !== undefined && CONTRACT_GLOBAL_TYPES.has(name.text)) names.add(name.text);
|
||||
};
|
||||
for (const stmt of file.statements) {
|
||||
for (const stmt of file.statements.nodes) {
|
||||
if (
|
||||
ts.isInterfaceDeclaration(stmt) ||
|
||||
ts.isTypeAliasDeclaration(stmt) ||
|
||||
@@ -160,7 +160,7 @@ function localTypeBindings(file: ts.SourceFile, bindings: TypeBindings): Set<str
|
||||
add(clause.name);
|
||||
if (clause.namedBindings === undefined) continue;
|
||||
if (ts.isNamespaceImport(clause.namedBindings)) add(clause.namedBindings.name);
|
||||
else for (const el of clause.namedBindings.elements) add(el.name);
|
||||
else for (const el of clause.namedBindings.elements.nodes) add(el.name);
|
||||
}
|
||||
bindings.set(file, names);
|
||||
return names;
|
||||
@@ -176,7 +176,7 @@ function isUnshadowedGlobalType(
|
||||
): boolean {
|
||||
if (localTypeBindings(file, bindings).has(name)) return false;
|
||||
for (let scope = node.parent; scope !== undefined && scope !== file; scope = scope.parent) {
|
||||
const params = scope.typeParameters;
|
||||
const params = scope.typeParameters?.nodes;
|
||||
if (params?.some((p) => ts.isTypeParameterDeclaration(p) && p.name.text === name) === true)
|
||||
return false;
|
||||
}
|
||||
@@ -254,7 +254,7 @@ function typeShape(
|
||||
return { k: "array", elem: typeShape(file, node.elementType, bindings) };
|
||||
if (ts.isTupleTypeNode(node)) {
|
||||
const elems: ContractTypeShape[] = [];
|
||||
for (const e of node.elements) {
|
||||
for (const e of node.elements.nodes) {
|
||||
// Named tuple members carry the type under `.type`.
|
||||
const t = ts.isNamedTupleMember(e) ? e.type : (e as ts.TypeNode);
|
||||
elems.push(typeShape(file, t, bindings));
|
||||
@@ -262,13 +262,13 @@ function typeShape(
|
||||
return { k: "tuple", elems };
|
||||
}
|
||||
if (ts.isUnionTypeNode(node)) {
|
||||
return { k: "union", parts: node.types.map((t) => typeShape(file, t, bindings)) };
|
||||
return { k: "union", parts: node.types.nodes.map((t) => typeShape(file, t, bindings)) };
|
||||
}
|
||||
if (ts.isTypeLiteralNode(node)) {
|
||||
let bad: string | null = null;
|
||||
const fields = shapeOfMembers(
|
||||
file,
|
||||
node.members,
|
||||
node.members.nodes,
|
||||
(text) => {
|
||||
bad = text;
|
||||
},
|
||||
@@ -292,16 +292,16 @@ function typeShape(
|
||||
const name = node.typeName.text;
|
||||
const global =
|
||||
CONTRACT_GLOBAL_TYPES.has(name) && isUnshadowedGlobalType(file, node, name, bindings);
|
||||
if (global && name === "Uint8Array" && (node.typeArguments?.length ?? 0) === 0)
|
||||
if (global && name === "Uint8Array" && (node.typeArguments?.nodes.length ?? 0) === 0)
|
||||
return { k: "bytes" };
|
||||
if (
|
||||
global &&
|
||||
(name === "Array" || name === "ReadonlyArray") &&
|
||||
node.typeArguments?.length === 1
|
||||
node.typeArguments?.nodes.length === 1
|
||||
) {
|
||||
return { k: "array", elem: typeShape(file, node.typeArguments[0]!, bindings) };
|
||||
return { k: "array", elem: typeShape(file, node.typeArguments.nodes[0]!, bindings) };
|
||||
}
|
||||
if ((node.typeArguments?.length ?? 0) > 0)
|
||||
if ((node.typeArguments?.nodes.length ?? 0) > 0)
|
||||
return { k: "unsupported", text: node.getText(file) };
|
||||
return { k: "ref", name };
|
||||
}
|
||||
@@ -328,7 +328,7 @@ function stringArray(expr: ts.Expression): string[] | null {
|
||||
const e = unwrapConst(expr);
|
||||
if (!ts.isArrayLiteralExpression(e)) return null;
|
||||
const out: string[] = [];
|
||||
for (const el of e.elements) {
|
||||
for (const el of e.elements.nodes) {
|
||||
const v = unwrapConst(el as ts.Expression);
|
||||
if (!ts.isStringLiteral(v)) return null;
|
||||
out.push(v.text);
|
||||
@@ -340,12 +340,12 @@ function envMsgArray(expr: ts.Expression): { env: string; msg: string }[] | null
|
||||
const e = unwrapConst(expr);
|
||||
if (!ts.isArrayLiteralExpression(e)) return null;
|
||||
const out: { env: string; msg: string }[] = [];
|
||||
for (const el of e.elements) {
|
||||
for (const el of e.elements.nodes) {
|
||||
const v = unwrapConst(el as ts.Expression);
|
||||
if (!ts.isObjectLiteralExpression(v)) return null;
|
||||
let env: string | null = null;
|
||||
let msg: string | null = null;
|
||||
for (const prop of v.properties) {
|
||||
for (const prop of v.properties.nodes) {
|
||||
if (!ts.isPropertyAssignment(prop)) return null;
|
||||
const name = propName(prop.name);
|
||||
const val = unwrapConst(prop.initializer);
|
||||
@@ -410,7 +410,7 @@ export function entryContractFacts(
|
||||
* and the refusal names every site. */
|
||||
const collectTypes = (file: ts.SourceFile): void => {
|
||||
const exportedHere = new Set<string>();
|
||||
for (const stmt of file.statements) {
|
||||
for (const stmt of file.statements.nodes) {
|
||||
if (
|
||||
(ts.isInterfaceDeclaration(stmt) || ts.isTypeAliasDeclaration(stmt)) &&
|
||||
isExported(stmt)
|
||||
@@ -418,7 +418,7 @@ export function entryContractFacts(
|
||||
exportedHere.add(stmt.name.text);
|
||||
}
|
||||
}
|
||||
for (const stmt of file.statements) {
|
||||
for (const stmt of file.statements.nodes) {
|
||||
if (ts.isInterfaceDeclaration(stmt) || ts.isTypeAliasDeclaration(stmt)) {
|
||||
const name = stmt.name.text;
|
||||
const exported = isExported(stmt);
|
||||
@@ -433,14 +433,14 @@ export function entryContractFacts(
|
||||
let bad: string | null = null;
|
||||
const fields = shapeOfMembers(
|
||||
file,
|
||||
stmt.members,
|
||||
stmt.members.nodes,
|
||||
(text) => {
|
||||
bad = text;
|
||||
},
|
||||
bindings,
|
||||
);
|
||||
const generic = (stmt.typeParameters?.length ?? 0) > 0;
|
||||
const heritage = (stmt.heritageClauses?.length ?? 0) > 0;
|
||||
const generic = (stmt.typeParameters?.nodes.length ?? 0) > 0;
|
||||
const heritage = (stmt.heritageClauses?.nodes.length ?? 0) > 0;
|
||||
facts.types.push({
|
||||
name,
|
||||
form: "interface",
|
||||
@@ -455,7 +455,7 @@ export function entryContractFacts(
|
||||
loc: locOf(file, stmt),
|
||||
});
|
||||
} else {
|
||||
const generic = (stmt.typeParameters?.length ?? 0) > 0;
|
||||
const generic = (stmt.typeParameters?.nodes.length ?? 0) > 0;
|
||||
facts.types.push({
|
||||
name,
|
||||
form: "alias",
|
||||
@@ -473,7 +473,7 @@ export function entryContractFacts(
|
||||
stmt.body !== undefined &&
|
||||
ts.isModuleBlock(stmt.body)
|
||||
) {
|
||||
for (const inner of stmt.body.statements) {
|
||||
for (const inner of stmt.body.statements.nodes) {
|
||||
if (ts.isInterfaceDeclaration(inner) || ts.isTypeAliasDeclaration(inner)) {
|
||||
addSite(inner.name.text, siteOf(file, inner));
|
||||
}
|
||||
@@ -494,25 +494,25 @@ export function entryContractFacts(
|
||||
if (list.length > 1) facts.multiSiteTypes.push({ name, sites: list });
|
||||
}
|
||||
|
||||
for (const stmt of entry.statements) {
|
||||
for (const stmt of entry.statements.nodes) {
|
||||
if (ts.isFunctionDeclaration(stmt) && stmt.name !== undefined) {
|
||||
if (!isExported(stmt)) continue;
|
||||
if (stmt.body === undefined && facts.functions.some((f) => f.name === stmt.name!.text))
|
||||
continue; // overload signature
|
||||
facts.functions.push({
|
||||
name: stmt.name.text,
|
||||
params: stmt.parameters.map((p) => ({
|
||||
params: stmt.parameters.nodes.map((p) => ({
|
||||
name: ts.isIdentifier(p.name) ? p.name.text : "<pattern>",
|
||||
shape: p.type !== undefined ? typeShape(entry, p.type, bindings) : null,
|
||||
})),
|
||||
returns: stmt.type !== undefined ? typeShape(entry, stmt.type, bindings) : null,
|
||||
generic: (stmt.typeParameters?.length ?? 0) > 0,
|
||||
generic: (stmt.typeParameters?.nodes.length ?? 0) > 0,
|
||||
loc: locOf(entry, stmt),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (ts.isVariableStatement(stmt) && isExported(stmt)) {
|
||||
for (const decl of stmt.declarationList.declarations) {
|
||||
for (const decl of stmt.declarationList.declarations.nodes) {
|
||||
if (!ts.isIdentifier(decl.name)) continue;
|
||||
const name = decl.name.text;
|
||||
if (!CONVENTION_CONSTS.has(name)) continue;
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface EntryExportInfo {
|
||||
* Overload signatures collapse onto one entry (same name, one body). */
|
||||
export function entryFunctionExports(entry: ts.SourceFile): Map<string, EntryExportInfo> {
|
||||
const out = new Map<string, EntryExportInfo>();
|
||||
for (const stmt of entry.statements) {
|
||||
for (const stmt of entry.statements.nodes) {
|
||||
if (!ts.isFunctionDeclaration(stmt) || stmt.name === undefined) continue;
|
||||
const mods = ts.canHaveModifiers(stmt) ? ts.getModifiers(stmt) : undefined;
|
||||
const exported = mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) === true;
|
||||
@@ -36,7 +36,7 @@ export function entryFunctionExports(entry: ts.SourceFile): Map<string, EntryExp
|
||||
out.set(name, {
|
||||
name,
|
||||
loc: { file: entry.fileName, start: stmt.getStart(), end: stmt.end },
|
||||
generic: (stmt.typeParameters?.length ?? 0) > 0,
|
||||
generic: (stmt.typeParameters?.nodes.length ?? 0) > 0,
|
||||
async: mods?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) === true,
|
||||
generator: stmt.asteriskToken !== undefined,
|
||||
});
|
||||
|
||||
@@ -110,14 +110,14 @@ export function isAssertClassNew(lowerer: Lowerer, expr: ts.Expression): boolean
|
||||
/** The options of `new Assert(<literal options>)`, or null when an option is
|
||||
* not a literal or the argument list has another shape. */
|
||||
export function literalAssertOptions(expr: ts.NewExpression): AssertInstanceOptions | null {
|
||||
const args = expr.arguments ?? [];
|
||||
const args = expr.arguments?.nodes ?? [];
|
||||
const options: AssertInstanceOptions = { strict: true, diff: "simple", skipPrototype: false };
|
||||
if (args.length === 0) return options;
|
||||
if (args.length > 1) return null;
|
||||
const arg = stripTypeCasts(args[0]!);
|
||||
if (ts.isIdentifier(arg) && arg.text === "undefined") return options;
|
||||
if (!ts.isObjectLiteralExpression(arg)) return null;
|
||||
for (const prop of arg.properties) {
|
||||
for (const prop of arg.properties.nodes) {
|
||||
if (!ts.isPropertyAssignment(prop)) return null;
|
||||
const name =
|
||||
ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name) ? prop.name.text : null;
|
||||
|
||||
@@ -24,7 +24,7 @@ export function lowerAddAbortListener(
|
||||
expr: ts.CallExpression,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
const args = expr.arguments;
|
||||
const args = expr.arguments.nodes;
|
||||
if (args.length > 2 || args.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(`addAbortListener with this argument list`, expr);
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ export function literalBoolOptions(
|
||||
if (!ts.isObjectLiteralExpression(node)) return null;
|
||||
const bools: Record<string, boolean> = {};
|
||||
const exprs: Record<string, ts.Expression> = {};
|
||||
for (const p of node.properties) {
|
||||
for (const p of node.properties.nodes) {
|
||||
if (!ts.isPropertyAssignment(p) || !ts.isIdentifier(p.name)) return null;
|
||||
if (exprKeys.includes(p.name.text)) {
|
||||
exprs[p.name.text] = p.initializer;
|
||||
|
||||
@@ -106,14 +106,14 @@ export function lowerDiagnosticsChannelMethodCall(
|
||||
if (!lowerer.isStdlibMember(access)) return null;
|
||||
const name = access.name.text;
|
||||
const loc = locOf(call);
|
||||
if (name === "publish" && call.arguments.length === 1) {
|
||||
if (name === "publish" && call.arguments.nodes.length === 1) {
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
|
||||
const msg = lowerDiagnosticsMessage(lowerer, call.arguments[0]!);
|
||||
const msg = lowerDiagnosticsMessage(lowerer, call.arguments.nodes[0]!);
|
||||
return { kind: "libCall", fn: "dc.publish", args: [receiver, msg], type: VOID, loc };
|
||||
}
|
||||
if ((name === "subscribe" || name === "unsubscribe") && call.arguments.length === 1) {
|
||||
if ((name === "subscribe" || name === "unsubscribe") && call.arguments.nodes.length === 1) {
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
|
||||
const cb = lowerDiagnosticsSubscriber(lowerer, call.arguments[0]!);
|
||||
const cb = lowerDiagnosticsSubscriber(lowerer, call.arguments.nodes[0]!);
|
||||
return name === "subscribe"
|
||||
? { kind: "libCall", fn: "dc.chanSubscribe", args: [receiver, cb], type: VOID, loc }
|
||||
: { kind: "libCall", fn: "dc.chanUnsubscribe", args: [receiver, cb], type: BOOL, loc };
|
||||
@@ -126,18 +126,18 @@ export function lowerDiagnosticsChannelMethodCall(
|
||||
// this/arguments to fn exactly like the trace calls.
|
||||
if (
|
||||
(name === "bindStore" || name === "unbindStore") &&
|
||||
call.arguments.length >= 1 &&
|
||||
call.arguments.length <= (name === "bindStore" ? 2 : 1)
|
||||
call.arguments.nodes.length >= 1 &&
|
||||
call.arguments.nodes.length <= (name === "bindStore" ? 2 : 1)
|
||||
) {
|
||||
if (!isBuiltinClassInstance(lowerer, call.arguments[0]!, "AsyncLocalStorage")) {
|
||||
if (!isBuiltinClassInstance(lowerer, call.arguments.nodes[0]!, "AsyncLocalStorage")) {
|
||||
lowerer.noLowering(
|
||||
`Channel.${name} with this store argument`,
|
||||
call.arguments[0]!,
|
||||
call.arguments.nodes[0]!,
|
||||
"an AsyncLocalStorage instance (node:async_hooks) is the supported store",
|
||||
);
|
||||
}
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
|
||||
const store = lowerer.lowerExprExpecting(call.arguments[0]!, F64);
|
||||
const store = lowerer.lowerExprExpecting(call.arguments.nodes[0]!, F64);
|
||||
if (name === "unbindStore") {
|
||||
return {
|
||||
kind: "libCall",
|
||||
@@ -148,8 +148,8 @@ export function lowerDiagnosticsChannelMethodCall(
|
||||
};
|
||||
}
|
||||
const transform: IrExpr =
|
||||
call.arguments[1] !== undefined
|
||||
? lowerDiagnosticsSubscriber(lowerer, call.arguments[1]!)
|
||||
call.arguments.nodes[1] !== undefined
|
||||
? lowerDiagnosticsSubscriber(lowerer, call.arguments.nodes[1]!)
|
||||
: dynUndefinedExpr(loc);
|
||||
return {
|
||||
kind: "libCall",
|
||||
@@ -159,15 +159,15 @@ export function lowerDiagnosticsChannelMethodCall(
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if (name === "runStores" && call.arguments.length >= 2) {
|
||||
if (name === "runStores" && call.arguments.nodes.length >= 2) {
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
|
||||
const data = lowerDiagnosticsMessage(lowerer, call.arguments[0]!);
|
||||
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments[1]!);
|
||||
const data = lowerDiagnosticsMessage(lowerer, call.arguments.nodes[0]!);
|
||||
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments.nodes[1]!);
|
||||
const thisArg: IrExpr =
|
||||
call.arguments[2] !== undefined
|
||||
? lowerDiagnosticsMessage(lowerer, call.arguments[2]!)
|
||||
call.arguments.nodes[2] !== undefined
|
||||
? lowerDiagnosticsMessage(lowerer, call.arguments.nodes[2]!)
|
||||
: dynUndefinedExpr(loc);
|
||||
const rest = lowerTracingArguments(lowerer, call.arguments.slice(3), loc);
|
||||
const rest = lowerTracingArguments(lowerer, call.arguments.nodes.slice(3), loc);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "dc.chanRunStores",
|
||||
@@ -198,7 +198,7 @@ export function lowerAsyncLocalStorageMethodCall(
|
||||
if (!lowerer.isStdlibMember(access)) return null;
|
||||
const name = access.name.text;
|
||||
const loc = locOf(call);
|
||||
if (name === "getStore" && call.arguments.length === 0) {
|
||||
if (name === "getStore" && call.arguments.nodes.length === 0) {
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
|
||||
return asyncLocalStorageResult(lowerer, call, {
|
||||
kind: "libCall",
|
||||
@@ -208,11 +208,11 @@ export function lowerAsyncLocalStorageMethodCall(
|
||||
loc,
|
||||
});
|
||||
}
|
||||
if (name === "run" && call.arguments.length >= 2) {
|
||||
if (name === "run" && call.arguments.nodes.length >= 2) {
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
|
||||
const value = lowerDiagnosticsMessage(lowerer, call.arguments[0]!);
|
||||
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments[1]!);
|
||||
const rest = lowerTracingArguments(lowerer, call.arguments.slice(2), loc);
|
||||
const value = lowerDiagnosticsMessage(lowerer, call.arguments.nodes[0]!);
|
||||
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments.nodes[1]!);
|
||||
const rest = lowerTracingArguments(lowerer, call.arguments.nodes.slice(2), loc);
|
||||
return asyncLocalStorageResult(lowerer, call, {
|
||||
kind: "libCall",
|
||||
fn: "als.run",
|
||||
@@ -221,10 +221,10 @@ export function lowerAsyncLocalStorageMethodCall(
|
||||
loc,
|
||||
});
|
||||
}
|
||||
if (name === "exit" && call.arguments.length >= 1) {
|
||||
if (name === "exit" && call.arguments.nodes.length >= 1) {
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
|
||||
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments[0]!);
|
||||
const rest = lowerTracingArguments(lowerer, call.arguments.slice(1), loc);
|
||||
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments.nodes[0]!);
|
||||
const rest = lowerTracingArguments(lowerer, call.arguments.nodes.slice(1), loc);
|
||||
return asyncLocalStorageResult(lowerer, call, {
|
||||
kind: "libCall",
|
||||
fn: "als.exitRun",
|
||||
@@ -233,12 +233,12 @@ export function lowerAsyncLocalStorageMethodCall(
|
||||
loc,
|
||||
});
|
||||
}
|
||||
if (name === "enterWith" && call.arguments.length === 1) {
|
||||
if (name === "enterWith" && call.arguments.nodes.length === 1) {
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
|
||||
const value = lowerDiagnosticsMessage(lowerer, call.arguments[0]!);
|
||||
const value = lowerDiagnosticsMessage(lowerer, call.arguments.nodes[0]!);
|
||||
return { kind: "libCall", fn: "als.enterWith", args: [receiver, value], type: VOID, loc };
|
||||
}
|
||||
if (name === "disable" && call.arguments.length === 0) {
|
||||
if (name === "disable" && call.arguments.nodes.length === 0) {
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
|
||||
return { kind: "libCall", fn: "als.disable", args: [receiver], type: VOID, loc };
|
||||
}
|
||||
@@ -301,13 +301,13 @@ function lowerTracingHandlers(lowerer: Lowerer, node: ts.Expression): IrExpr {
|
||||
if (
|
||||
ts.isObjectLiteralExpression(e) &&
|
||||
lowerer.mapTypeOf(lowerer.typeOf(e))?.kind === "record" &&
|
||||
e.properties.length > 0 &&
|
||||
e.properties.every((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name))
|
||||
e.properties.nodes.length > 0 &&
|
||||
e.properties.nodes.every((p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name))
|
||||
) {
|
||||
const loc = locOf(e);
|
||||
return {
|
||||
kind: "dynObjLit",
|
||||
fields: e.properties.map((p) => {
|
||||
fields: e.properties.nodes.map((p) => {
|
||||
const pa = p as ts.PropertyAssignment;
|
||||
return {
|
||||
key: {
|
||||
@@ -366,32 +366,32 @@ export function lowerTracingChannelMethodCall(
|
||||
if (!lowerer.isStdlibMember(access)) return null;
|
||||
const name = access.name.text;
|
||||
const loc = locOf(call);
|
||||
if ((name === "subscribe" || name === "unsubscribe") && call.arguments.length === 1) {
|
||||
if ((name === "subscribe" || name === "unsubscribe") && call.arguments.nodes.length === 1) {
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
|
||||
const handlers = lowerTracingHandlers(lowerer, call.arguments[0]!);
|
||||
const handlers = lowerTracingHandlers(lowerer, call.arguments.nodes[0]!);
|
||||
return name === "subscribe"
|
||||
? { kind: "libCall", fn: "dc.tcSubscribe", args: [receiver, handlers], type: VOID, loc }
|
||||
: { kind: "libCall", fn: "dc.tcUnsubscribe", args: [receiver, handlers], type: BOOL, loc };
|
||||
}
|
||||
if (
|
||||
(name === "traceSync" || name === "traceCallback" || name === "tracePromise") &&
|
||||
call.arguments.length >= 1
|
||||
call.arguments.nodes.length >= 1
|
||||
) {
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, F64);
|
||||
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments[0]!);
|
||||
const fn = lowerDiagnosticsSubscriber(lowerer, call.arguments.nodes[0]!);
|
||||
// traceSync(fn, context?, thisArg?, ...args) /
|
||||
// tracePromise(fn, context?, thisArg?, ...args) /
|
||||
// traceCallback(fn, position?, context?, thisArg?, ...args)
|
||||
const shift = name === "traceCallback" ? 1 : 0;
|
||||
const ctxNode = call.arguments[1 + shift];
|
||||
const thisNode = call.arguments[2 + shift];
|
||||
const ctxNode = call.arguments.nodes[1 + shift];
|
||||
const thisNode = call.arguments.nodes[2 + shift];
|
||||
const ctx: IrExpr =
|
||||
ctxNode !== undefined
|
||||
? lowerDiagnosticsMessage(lowerer, ctxNode)
|
||||
: { kind: "dynObjLit", fields: [], type: DYN, loc };
|
||||
const thisArg: IrExpr =
|
||||
thisNode !== undefined ? lowerDiagnosticsMessage(lowerer, thisNode) : dynUndefinedExpr(loc);
|
||||
const rest = lowerTracingArguments(lowerer, call.arguments.slice(3 + shift), loc);
|
||||
const rest = lowerTracingArguments(lowerer, call.arguments.nodes.slice(3 + shift), loc);
|
||||
if (name === "traceSync") {
|
||||
return {
|
||||
kind: "libCall",
|
||||
@@ -416,8 +416,8 @@ export function lowerTracingChannelMethodCall(
|
||||
};
|
||||
}
|
||||
const pos: IrExpr =
|
||||
call.arguments[1] !== undefined
|
||||
? lowerer.lowerExprExpecting(call.arguments[1]!, F64)
|
||||
call.arguments.nodes[1] !== undefined
|
||||
? lowerer.lowerExprExpecting(call.arguments.nodes[1]!, F64)
|
||||
: { kind: "numLit", value: -1, type: F64, loc };
|
||||
return {
|
||||
kind: "libCall",
|
||||
@@ -473,29 +473,32 @@ export function lowerDiagnosticsChannelCall(
|
||||
bi: { module: string; member: string },
|
||||
loc: SrcLoc,
|
||||
): IrExpr | null {
|
||||
if (bi.member === "channel" && expr.arguments.length === 1) {
|
||||
const name = diagnosticsChannelNameArgument(lowerer, expr.arguments[0]!);
|
||||
if (bi.member === "channel" && expr.arguments.nodes.length === 1) {
|
||||
const name = diagnosticsChannelNameArgument(lowerer, expr.arguments.nodes[0]!);
|
||||
return { kind: "libCall", fn: "dc.channel", args: [name], type: F64, loc };
|
||||
}
|
||||
if ((bi.member === "subscribe" || bi.member === "unsubscribe") && expr.arguments.length === 2) {
|
||||
const name = diagnosticsChannelNameArgument(lowerer, expr.arguments[0]!);
|
||||
const cb = lowerDiagnosticsSubscriber(lowerer, expr.arguments[1]!);
|
||||
if (
|
||||
(bi.member === "subscribe" || bi.member === "unsubscribe") &&
|
||||
expr.arguments.nodes.length === 2
|
||||
) {
|
||||
const name = diagnosticsChannelNameArgument(lowerer, expr.arguments.nodes[0]!);
|
||||
const cb = lowerDiagnosticsSubscriber(lowerer, expr.arguments.nodes[1]!);
|
||||
return bi.member === "subscribe"
|
||||
? { kind: "libCall", fn: "dc.subscribe", args: [name, cb], type: VOID, loc }
|
||||
: { kind: "libCall", fn: "dc.unsubscribe", args: [name, cb], type: BOOL, loc };
|
||||
}
|
||||
if (bi.member === "hasSubscribers" && expr.arguments.length === 1) {
|
||||
const name = diagnosticsChannelNameArgument(lowerer, expr.arguments[0]!);
|
||||
if (bi.member === "hasSubscribers" && expr.arguments.nodes.length === 1) {
|
||||
const name = diagnosticsChannelNameArgument(lowerer, expr.arguments.nodes[0]!);
|
||||
return { kind: "libCall", fn: "dc.hasSubscribers", args: [name], type: BOOL, loc };
|
||||
}
|
||||
// tracingChannel: the string form interns the five tracing:<name>:*
|
||||
// channels; the collection form takes an object literal whose five
|
||||
// event members are Channel-typed values (Node's TracingChannelCollection).
|
||||
if (bi.member === "tracingChannel" && expr.arguments.length === 1) {
|
||||
const a = expr.arguments[0]!;
|
||||
if (bi.member === "tracingChannel" && expr.arguments.nodes.length === 1) {
|
||||
const a = expr.arguments.nodes[0]!;
|
||||
if (ts.isObjectLiteralExpression(a)) {
|
||||
const byEvent = new Map<string, IrExpr>();
|
||||
for (const p of a.properties) {
|
||||
for (const p of a.properties.nodes) {
|
||||
if (
|
||||
!ts.isPropertyAssignment(p) ||
|
||||
!ts.isIdentifier(p.name) ||
|
||||
|
||||
@@ -45,9 +45,9 @@ export function lowerAtomicsCall(
|
||||
: 3;
|
||||
const maximum = member === "wait" ? 4 : member === "notify" ? 3 : minimum;
|
||||
if (
|
||||
call.arguments.length < minimum ||
|
||||
call.arguments.length > maximum ||
|
||||
call.arguments.some(ts.isSpreadElement)
|
||||
call.arguments.nodes.length < minimum ||
|
||||
call.arguments.nodes.length > maximum ||
|
||||
call.arguments.nodes.some(ts.isSpreadElement)
|
||||
) {
|
||||
lowerer.noLowering(
|
||||
`Atomics.${member} arguments`,
|
||||
@@ -55,7 +55,7 @@ export function lowerAtomicsCall(
|
||||
`use ${minimum}${maximum > minimum ? `–${maximum}` : ""} arguments`,
|
||||
);
|
||||
}
|
||||
const source = call.arguments[0]!;
|
||||
const source = call.arguments.nodes[0]!;
|
||||
// Generic constraints can remain unions in the checker while this body
|
||||
// has a concrete typed-array binding. Validate the actual lowered storage.
|
||||
const array = lowerer.lowerExpr(source);
|
||||
@@ -75,8 +75,8 @@ export function lowerAtomicsCall(
|
||||
}
|
||||
const number = (value: number): IrExpr => ({ kind: "numLit", value, type: F64, loc });
|
||||
const arg = (index: number, fallback: number): IrExpr =>
|
||||
call.arguments[index]
|
||||
? lowerOptionalArgument(lowerer, call.arguments[index]!, F64, number(fallback))
|
||||
call.arguments.nodes[index]
|
||||
? lowerOptionalArgument(lowerer, call.arguments.nodes[index]!, F64, number(fallback))
|
||||
: number(fallback);
|
||||
const index = arg(1, 0);
|
||||
if (member === "wait")
|
||||
|
||||
@@ -70,7 +70,7 @@ function memberNameOf(access: ts.PropertyAccessExpression | ts.ElementAccessExpr
|
||||
export function lowerBlobNew(lowerer: Lowerer, expr: ts.NewExpression): IrExpr | null {
|
||||
const cls = blobClassOf(lowerer, expr.expression);
|
||||
if (cls === null) return null;
|
||||
const args = expr.arguments ?? [];
|
||||
const args = expr.arguments?.nodes ?? [];
|
||||
if (args.some(ts.isSpreadElement)) return null;
|
||||
const loc = locOf(expr);
|
||||
const values = args.map((arg) =>
|
||||
@@ -94,10 +94,10 @@ export function lowerStaticBlobCall(lowerer: Lowerer, call: ts.CallExpression):
|
||||
const member = access.name.text;
|
||||
if (access.questionDotToken || !BLOB_CALLS.has(member)) return null;
|
||||
if (blobReceiverClass(lowerer, access.expression) === null) return null;
|
||||
if (call.arguments.some(ts.isSpreadElement)) return null;
|
||||
if (call.arguments.nodes.some(ts.isSpreadElement)) return null;
|
||||
const loc = locOf(call);
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, DYN);
|
||||
const args = call.arguments.map((arg) => lowerer.lowerExprExpecting(arg, DYN));
|
||||
const args = call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN));
|
||||
if (member === "slice") {
|
||||
return {
|
||||
kind: "libCall",
|
||||
|
||||
@@ -126,7 +126,7 @@ export function registerPromisifiedBuiltinDecl(
|
||||
if (registerHttpClientFnBinding(lowerer, nameNode, init)) return true;
|
||||
const e = isPromisifyCall(lowerer, init);
|
||||
if (!e) return false;
|
||||
const argNode = e.arguments.length === 1 ? e.arguments[0]! : null;
|
||||
const argNode = e.arguments.nodes.length === 1 ? e.arguments.nodes[0]! : null;
|
||||
// `promisify(exec)` over a named import, or `promisify(cp.exec)` over a
|
||||
// namespace import.
|
||||
const target = !argNode
|
||||
|
||||
@@ -42,9 +42,9 @@ export function lowerExecFileAsyncCall(
|
||||
): IrExpr {
|
||||
const what = shell ? "the promisified exec" : "the promisified execFile";
|
||||
if (
|
||||
expr.arguments.length < 1 ||
|
||||
expr.arguments.length > (shell ? 2 : 3) ||
|
||||
expr.arguments.some(ts.isSpreadElement)
|
||||
expr.arguments.nodes.length < 1 ||
|
||||
expr.arguments.nodes.length > (shell ? 2 : 3) ||
|
||||
expr.arguments.nodes.some(ts.isSpreadElement)
|
||||
) {
|
||||
lowerer.noLowering(
|
||||
`${what} with this argument shape`,
|
||||
@@ -54,7 +54,7 @@ export function lowerExecFileAsyncCall(
|
||||
: "the supported form is execFileAsync(file, args?, options?)",
|
||||
);
|
||||
}
|
||||
const command = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
|
||||
const command = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
|
||||
// exec: /bin/sh -c <command>, execSync's lowering (the command string
|
||||
// rides as the argv[1] the failure display reads); execFile: file + its
|
||||
// args list.
|
||||
@@ -66,8 +66,8 @@ export function lowerExecFileAsyncCall(
|
||||
type: arrayOf(STRING),
|
||||
loc,
|
||||
}
|
||||
: lowerChildArgsArg(lowerer, expr.arguments[1], loc);
|
||||
const optsNode = expr.arguments[shell ? 1 : 2];
|
||||
: lowerChildArgsArg(lowerer, expr.arguments.nodes[1], loc);
|
||||
const optsNode = expr.arguments.nodes[shell ? 1 : 2];
|
||||
|
||||
const emptyStr: IrExpr = { kind: "strLit", value: "", type: STRING, loc };
|
||||
const helper = execFileAsyncHelper(lowerer, loc, shell);
|
||||
@@ -93,7 +93,7 @@ export function lowerExecFileAsyncCall(
|
||||
"pass the options inline so each member can be checked",
|
||||
);
|
||||
}
|
||||
for (const p of optsNode.properties) {
|
||||
for (const p of optsNode.properties.nodes) {
|
||||
// The conditional-spread idiom carrying `env` — the portless
|
||||
// opensslAsync shape: `...(c ? { env: { ...process.env, ...extra
|
||||
// } } : {})` (either orientation). The condition evaluates ONCE
|
||||
|
||||
@@ -35,22 +35,22 @@ export function lowerChildMethodCall(
|
||||
const loc = locOf(call);
|
||||
if (name === "send") {
|
||||
if (
|
||||
call.arguments.length < 1 ||
|
||||
call.arguments.length > 2 ||
|
||||
call.arguments.some(ts.isSpreadElement)
|
||||
call.arguments.nodes.length < 1 ||
|
||||
call.arguments.nodes.length > 2 ||
|
||||
call.arguments.nodes.some(ts.isSpreadElement)
|
||||
) {
|
||||
lowerer.noLowering(
|
||||
`child.send with ${call.arguments.length} arguments`,
|
||||
`child.send with ${call.arguments.nodes.length} arguments`,
|
||||
call,
|
||||
"send(message[, callback]) is supported; handle transfer and options are not",
|
||||
);
|
||||
}
|
||||
const receiver = lowerer.lowerExpr(access.expression);
|
||||
const json = ipcJsonMessage(lowerer, call.arguments[0]!, loc);
|
||||
if (call.arguments[1] === undefined) {
|
||||
const json = ipcJsonMessage(lowerer, call.arguments.nodes[0]!, loc);
|
||||
if (call.arguments.nodes[1] === undefined) {
|
||||
return { kind: "libCall", fn: "child.send", args: [receiver, json], type: BOOL, loc };
|
||||
}
|
||||
const callback = ipcSendCallback(lowerer, call.arguments[1]);
|
||||
const callback = ipcSendCallback(lowerer, call.arguments.nodes[1]);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "child.sendCb",
|
||||
@@ -59,7 +59,7 @@ export function lowerChildMethodCall(
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if (name === "disconnect" && call.arguments.length === 0) {
|
||||
if (name === "disconnect" && call.arguments.nodes.length === 0) {
|
||||
if (!ts.isExpressionStatement(call.parent)) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
@@ -75,8 +75,8 @@ export function lowerChildMethodCall(
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if ((name === "on" || name === "once") && call.arguments.length === 2) {
|
||||
const evT = lowerer.typeOf(call.arguments[0]!);
|
||||
if ((name === "on" || name === "once") && call.arguments.nodes.length === 2) {
|
||||
const evT = lowerer.typeOf(call.arguments.nodes[0]!);
|
||||
const event = evT.isStringLiteralType() ? evT.value : null;
|
||||
if (
|
||||
event !== "spawn" &&
|
||||
@@ -88,7 +88,7 @@ export function lowerChildMethodCall(
|
||||
) {
|
||||
lowerer.noLowering(
|
||||
`child.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`,
|
||||
call.arguments[0]!,
|
||||
call.arguments.nodes[0]!,
|
||||
'"exit", "close", "error", "message", and "disconnect" are the supported child events (as literals)',
|
||||
);
|
||||
}
|
||||
@@ -105,7 +105,7 @@ export function lowerChildMethodCall(
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "child.onMessage",
|
||||
args: [receiver, ipcMessageListener(lowerer, call.arguments[1]!), once],
|
||||
args: [receiver, ipcMessageListener(lowerer, call.arguments.nodes[1]!), once],
|
||||
type: VOID,
|
||||
loc,
|
||||
};
|
||||
@@ -114,24 +114,24 @@ export function lowerChildMethodCall(
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "child.onDisconnect",
|
||||
args: [receiver, ipcDisconnectListener(lowerer, call.arguments[1]!), once],
|
||||
args: [receiver, ipcDisconnectListener(lowerer, call.arguments.nodes[1]!), once],
|
||||
type: VOID,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
const listenerNode = stripTypeCasts(call.arguments[1]!);
|
||||
const listenerNode = stripTypeCasts(call.arguments.nodes[1]!);
|
||||
if (
|
||||
isJsSourceFile(listenerNode.getSourceFile()) &&
|
||||
(ts.isArrowFunction(listenerNode) || ts.isFunctionExpression(listenerNode)) &&
|
||||
listenerNode.parameters.some((param) => param.dotDotDotToken) &&
|
||||
listenerNode.parameters.every((param) => !param.type) &&
|
||||
listenerNode.parameters.nodes.some((param) => param.dotDotDotToken) &&
|
||||
listenerNode.parameters.nodes.every((param) => !param.type) &&
|
||||
!/@(?:param|type)\b/.test(
|
||||
listenerNode.getSourceFile().text.slice(listenerNode.pos, listenerNode.getStart()),
|
||||
)
|
||||
) {
|
||||
for (const param of listenerNode.parameters) lowerer.checkedCallbackParams.add(param);
|
||||
for (const param of listenerNode.parameters.nodes) lowerer.checkedCallbackParams.add(param);
|
||||
}
|
||||
const cb = lowerer.lowerExpr(call.arguments[1]!);
|
||||
const cb = lowerer.lowerExpr(call.arguments.nodes[1]!);
|
||||
if (
|
||||
(isJsSourceFile(listenerNode.getSourceFile()) && cb.type.kind === "dyn") ||
|
||||
(cb.type.kind === "func" &&
|
||||
@@ -149,7 +149,7 @@ export function lowerChildMethodCall(
|
||||
) {
|
||||
lowerer.noLowering(
|
||||
"child event listeners with non-representable parameters",
|
||||
call.arguments[1]!,
|
||||
call.arguments.nodes[1]!,
|
||||
);
|
||||
}
|
||||
return {
|
||||
@@ -168,7 +168,7 @@ export function lowerChildMethodCall(
|
||||
if (cb.type.kind !== "func" || cb.type.params.length !== 0 || cb.type.ret.kind !== "void") {
|
||||
lowerer.noLowering(
|
||||
"spawn event listeners with parameters or a returned value",
|
||||
call.arguments[1]!,
|
||||
call.arguments.nodes[1]!,
|
||||
);
|
||||
}
|
||||
return { kind: "libCall", fn: "child.onSpawn", args: [receiver, cb], type: VOID, loc };
|
||||
@@ -176,7 +176,7 @@ export function lowerChildMethodCall(
|
||||
if (cb.type.kind !== "func" || cb.type.params.length > (event === "error" ? 1 : 2)) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call.arguments[1]!,
|
||||
call.arguments.nodes[1]!,
|
||||
event !== "error"
|
||||
? `${event} listeners with more than two parameters (use (code, signal), (code), or ())`
|
||||
: "error listeners with more than one parameter (use (err) or ())",
|
||||
@@ -188,7 +188,7 @@ export function lowerChildMethodCall(
|
||||
// fenced instead of being called through the wrong ABI.
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call.arguments[1]!,
|
||||
call.arguments.nodes[1]!,
|
||||
"listeners returning a value (make the callback body a block, or return nothing)",
|
||||
);
|
||||
}
|
||||
@@ -206,7 +206,7 @@ export function lowerChildMethodCall(
|
||||
if (!armsOk) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call.arguments[1]!,
|
||||
call.arguments.nodes[1]!,
|
||||
`${event} listeners whose parameter is not 'number | null' (got '${lowerer.fmt(param!)}')`,
|
||||
);
|
||||
}
|
||||
@@ -228,7 +228,7 @@ export function lowerChildMethodCall(
|
||||
if (!sigOk) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call.arguments[1]!,
|
||||
call.arguments.nodes[1]!,
|
||||
`${event} listeners whose signal parameter is not 'Signals | null' (got '${lowerer.fmt(sigParam!)}')`,
|
||||
);
|
||||
}
|
||||
@@ -243,7 +243,7 @@ export function lowerChildMethodCall(
|
||||
if (param !== undefined && !(param.kind === "object" && param.className === "%Error")) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call.arguments[1]!,
|
||||
call.arguments.nodes[1]!,
|
||||
`error listeners whose parameter is not 'Error' (got '${lowerer.fmt(param)}')`,
|
||||
);
|
||||
}
|
||||
@@ -256,11 +256,11 @@ export function lowerChildMethodCall(
|
||||
// reaped or never spawned (Node's null-handle answer), and a successful
|
||||
// send sets `killed`.
|
||||
if (name === "kill") {
|
||||
if (call.arguments.length > 1) {
|
||||
lowerer.noLowering(`child.kill with ${call.arguments.length} arguments`, call);
|
||||
if (call.arguments.nodes.length > 1) {
|
||||
lowerer.noLowering(`child.kill with ${call.arguments.nodes.length} arguments`, call);
|
||||
}
|
||||
const receiver = lowerer.lowerExpr(access.expression);
|
||||
const sigNode = call.arguments[0];
|
||||
const sigNode = call.arguments.nodes[0];
|
||||
if (!sigNode) {
|
||||
const dflt: IrExpr = { kind: "strLit", value: "SIGTERM", type: STRING, loc };
|
||||
return { kind: "libCall", fn: "child.kill", args: [receiver, dflt], type: BOOL, loc };
|
||||
@@ -281,7 +281,7 @@ export function lowerChildMethodCall(
|
||||
// child.unref(): drops the child from the event loop's keep-alive set
|
||||
// (the process may exit while the child runs — Node's semantics; the
|
||||
// child is still reaped while the loop runs for other reasons).
|
||||
if ((name === "unref" || name === "ref") && call.arguments.length === 0) {
|
||||
if ((name === "unref" || name === "ref") && call.arguments.nodes.length === 0) {
|
||||
const receiver = lowerer.lowerExpr(access.expression);
|
||||
return {
|
||||
kind: "libCall",
|
||||
@@ -320,9 +320,9 @@ export function lowerChildStreamMethodCall(
|
||||
if (!lowerer.isStdlibMember(access)) return null;
|
||||
const name = access.name.text;
|
||||
const loc = locOf(call);
|
||||
if (name === "setEncoding" && call.arguments.length === 1) {
|
||||
if (name === "setEncoding" && call.arguments.nodes.length === 1) {
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, CHILDSTREAM_T);
|
||||
const enc = bufEncoding(lowerer, "child stream setEncoding", call.arguments[0]!);
|
||||
const enc = bufEncoding(lowerer, "child stream setEncoding", call.arguments.nodes[0]!);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "stream.childSetEncoding",
|
||||
@@ -331,13 +331,13 @@ export function lowerChildStreamMethodCall(
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if ((name === "on" || name === "once") && call.arguments.length === 2) {
|
||||
const evT = lowerer.typeOf(call.arguments[0]!);
|
||||
if ((name === "on" || name === "once") && call.arguments.nodes.length === 2) {
|
||||
const evT = lowerer.typeOf(call.arguments.nodes[0]!);
|
||||
const event = evT.isStringLiteralType() ? evT.value : null;
|
||||
if (event !== "data" && event !== "end") {
|
||||
lowerer.noLowering(
|
||||
`stream.${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`,
|
||||
call.arguments[0]!,
|
||||
call.arguments.nodes[0]!,
|
||||
'"data" and "end" are the supported child-stream events (as literals)',
|
||||
);
|
||||
}
|
||||
@@ -349,7 +349,7 @@ export function lowerChildStreamMethodCall(
|
||||
);
|
||||
}
|
||||
const receiver = lowerer.lowerExpr(access.expression);
|
||||
const cb = lowerer.lowerExpr(call.arguments[1]!);
|
||||
const cb = lowerer.lowerExpr(call.arguments.nodes[1]!);
|
||||
const once: IrExpr = { kind: "boolLit", value: name === "once", type: BOOL, loc };
|
||||
if (
|
||||
cb.type.kind !== "func" ||
|
||||
@@ -358,7 +358,7 @@ export function lowerChildStreamMethodCall(
|
||||
) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call.arguments[1]!,
|
||||
call.arguments.nodes[1]!,
|
||||
event === "data"
|
||||
? "data listeners with more than one parameter or a return value (use (chunk) or ())"
|
||||
: "end listeners with parameters or a return value (use ())",
|
||||
@@ -381,7 +381,7 @@ export function lowerChildStreamMethodCall(
|
||||
) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call.arguments[1]!,
|
||||
call.arguments.nodes[1]!,
|
||||
`data listeners whose parameter is not 'Buffer', 'string', or a Buffer-armed union (got '${lowerer.fmt(param)}')`,
|
||||
);
|
||||
}
|
||||
@@ -417,15 +417,15 @@ export function lowerChildWriterMethodCall(
|
||||
const loc = locOf(call);
|
||||
|
||||
if (name === "write") {
|
||||
if (call.arguments.length !== 1 || call.arguments.some(ts.isSpreadElement)) {
|
||||
if (call.arguments.nodes.length !== 1 || call.arguments.nodes.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(
|
||||
`child stdin write with ${call.arguments.length} arguments`,
|
||||
`child stdin write with ${call.arguments.nodes.length} arguments`,
|
||||
call,
|
||||
"write(string | Uint8Array) with no encoding or callback is supported",
|
||||
);
|
||||
}
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, CHILDWRITER_T);
|
||||
const data = lowerer.lowerExpr(call.arguments[0]!);
|
||||
const data = lowerer.lowerExpr(call.arguments.nodes[0]!);
|
||||
if (data.type.kind === "string") {
|
||||
return { kind: "libCall", fn: "writer.writeString", args: [receiver, data], type: BOOL, loc };
|
||||
}
|
||||
@@ -434,12 +434,12 @@ export function lowerChildWriterMethodCall(
|
||||
}
|
||||
lowerer.noLowering(
|
||||
`child stdin write of '${lowerer.fmt(data.type)}'`,
|
||||
call.arguments[0]!,
|
||||
call.arguments.nodes[0]!,
|
||||
"write one string, Buffer, or Uint8Array value (narrow unions first)",
|
||||
);
|
||||
}
|
||||
|
||||
if ((name === "end" || name === "destroy") && call.arguments.length === 0) {
|
||||
if ((name === "end" || name === "destroy") && call.arguments.nodes.length === 0) {
|
||||
if (!ts.isExpressionStatement(call.parent)) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
@@ -457,13 +457,13 @@ export function lowerChildWriterMethodCall(
|
||||
};
|
||||
}
|
||||
|
||||
if ((name === "on" || name === "once") && call.arguments.length === 2) {
|
||||
const eventType = lowerer.typeOf(call.arguments[0]!);
|
||||
if ((name === "on" || name === "once") && call.arguments.nodes.length === 2) {
|
||||
const eventType = lowerer.typeOf(call.arguments.nodes[0]!);
|
||||
const event = eventType.isStringLiteralType() ? eventType.value : null;
|
||||
if (event !== "drain" && event !== "finish" && event !== "error") {
|
||||
lowerer.noLowering(
|
||||
`child stdin ${name}(${event === null ? "non-literal event" : `"${event}"`}, ...)`,
|
||||
call.arguments[0]!,
|
||||
call.arguments.nodes[0]!,
|
||||
'"drain", "finish", and "error" are the supported child stdin events',
|
||||
);
|
||||
}
|
||||
@@ -471,7 +471,7 @@ export function lowerChildWriterMethodCall(
|
||||
lowerer.unsupported("SC1090", call, "chaining child stdin listener registration");
|
||||
}
|
||||
const receiver = lowerer.lowerExprExpecting(access.expression, CHILDWRITER_T);
|
||||
const cb = lowerer.lowerExpr(call.arguments[1]!);
|
||||
const cb = lowerer.lowerExpr(call.arguments.nodes[1]!);
|
||||
const maxParams = event === "error" ? 1 : 0;
|
||||
if (
|
||||
cb.type.kind !== "func" ||
|
||||
@@ -480,7 +480,7 @@ export function lowerChildWriterMethodCall(
|
||||
) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call.arguments[1]!,
|
||||
call.arguments.nodes[1]!,
|
||||
event === "error"
|
||||
? "error listeners with at most one Error parameter and no return value"
|
||||
: `${event} listeners with no parameters or return value`,
|
||||
@@ -493,7 +493,7 @@ export function lowerChildWriterMethodCall(
|
||||
) {
|
||||
lowerer.unsupported(
|
||||
"SC1090",
|
||||
call.arguments[1]!,
|
||||
call.arguments.nodes[1]!,
|
||||
"child stdin error listeners whose parameter is Error",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,15 +48,15 @@ export function lowerExecSyncCall(
|
||||
shell: boolean,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
if (expr.arguments.some(ts.isSpreadElement)) {
|
||||
if (expr.arguments.nodes.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(`${shell ? "execSync" : "execFileSync"} with a spread call`, expr);
|
||||
}
|
||||
const cmd = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
|
||||
const cmd = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
|
||||
// execSync: /bin/sh -c <command>; execFileSync: file + its args list.
|
||||
let argvExpr: IrExpr;
|
||||
let optsNode: ts.Expression | undefined;
|
||||
if (shell) {
|
||||
if (expr.arguments.length > 2) {
|
||||
if (expr.arguments.nodes.length > 2) {
|
||||
lowerer.noLowering(
|
||||
"execSync with this argument shape",
|
||||
expr,
|
||||
@@ -69,17 +69,17 @@ export function lowerExecSyncCall(
|
||||
type: arrayOf(STRING),
|
||||
loc,
|
||||
};
|
||||
optsNode = expr.arguments[1];
|
||||
optsNode = expr.arguments.nodes[1];
|
||||
} else {
|
||||
if (expr.arguments.length > 3) {
|
||||
if (expr.arguments.nodes.length > 3) {
|
||||
lowerer.noLowering(
|
||||
"execFileSync with this argument shape",
|
||||
expr,
|
||||
"the supported form is execFileSync(file, args?, options?)",
|
||||
);
|
||||
}
|
||||
argvExpr = lowerChildArgsArg(lowerer, expr.arguments[1], loc);
|
||||
optsNode = expr.arguments[2];
|
||||
argvExpr = lowerChildArgsArg(lowerer, expr.arguments.nodes[1], loc);
|
||||
optsNode = expr.arguments.nodes[2];
|
||||
}
|
||||
// The shell command itself is /bin/sh; the "command" string rides as
|
||||
// the argv[1] the display formatter reads.
|
||||
@@ -102,6 +102,8 @@ export function lowerExecSyncCall(
|
||||
let stdoutMode = 1;
|
||||
let stderrMode = 0;
|
||||
let stdinInherit = false;
|
||||
// Without an encoding option, a failure's error carries Buffer outputs.
|
||||
let encodingGiven = false;
|
||||
// A conditional env spread (`...(c ? { env: ... } : {})`): the call
|
||||
// itself splits into a ternary of the two env-nesses at the tail.
|
||||
let condEnvSpread: { cond: IrExpr; pairs: IrExpr; whenTrue: boolean } | null = null;
|
||||
@@ -134,6 +136,8 @@ export function lowerExecSyncCall(
|
||||
runtime.timeout,
|
||||
runtime.stdoutMode,
|
||||
runtime.stderrMode,
|
||||
// The runtime options carry a utf8 encoding (others throw).
|
||||
boolLit(false, loc),
|
||||
],
|
||||
type: STRING,
|
||||
loc,
|
||||
@@ -182,8 +186,8 @@ export function lowerExecSyncCall(
|
||||
modeOf(t.value, 2);
|
||||
return;
|
||||
}
|
||||
if (ts.isArrayLiteralExpression(node) && node.elements.length === 3) {
|
||||
node.elements.forEach((el, i) => {
|
||||
if (ts.isArrayLiteralExpression(node) && node.elements.nodes.length === 3) {
|
||||
node.elements.nodes.forEach((el, i) => {
|
||||
const et = lowerer.typeOf(el);
|
||||
if (!et.isStringLiteralType()) {
|
||||
lowerer.noLowering(
|
||||
@@ -202,7 +206,7 @@ export function lowerExecSyncCall(
|
||||
);
|
||||
};
|
||||
|
||||
for (const p of optsNode.properties) {
|
||||
for (const p of optsNode.properties.nodes) {
|
||||
// The conditional-spread idiom carrying `env` — the openssl-runner
|
||||
// shape: `...(c ? { env: { ...process.env, ...extra } } : {})`
|
||||
// (either orientation). The condition evaluates ONCE and picks
|
||||
@@ -251,6 +255,7 @@ export function lowerExecSyncCall(
|
||||
'outputs are captured as utf8 — pass { encoding: "utf8" }',
|
||||
);
|
||||
}
|
||||
encodingGiven = true;
|
||||
break;
|
||||
}
|
||||
case "cwd":
|
||||
@@ -395,6 +400,7 @@ export function lowerExecSyncCall(
|
||||
timeout,
|
||||
numLit(stdoutMode + (stdinInherit ? 4 : 0), loc),
|
||||
numLit(stderrMode, loc),
|
||||
boolLit(!encodingGiven, loc),
|
||||
],
|
||||
type: STRING,
|
||||
loc,
|
||||
|
||||
@@ -31,8 +31,8 @@ export function lowerChildArgsArg(
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
if (!node) return { kind: "arrayLit", elems: [], type: arrayOf(STRING), loc };
|
||||
if (ts.isArrayLiteralExpression(node) && !node.elements.some(ts.isSpreadElement)) {
|
||||
const elems = node.elements.map((el) => lowerer.lowerExprExpecting(el, STRING));
|
||||
if (ts.isArrayLiteralExpression(node) && !node.elements.nodes.some(ts.isSpreadElement)) {
|
||||
const elems = node.elements.nodes.map((el) => lowerer.lowerExprExpecting(el, STRING));
|
||||
return { kind: "arrayLit", elems, type: arrayOf(STRING), loc: locOf(node) };
|
||||
}
|
||||
return lowerer.lowerExprExpecting(node, arrayOf(STRING));
|
||||
@@ -92,16 +92,16 @@ const NODE_SIGNAL_NAMES = new Set([
|
||||
* fences by name. The bare `{ encoding: "utf8" }` shape keeps its
|
||||
* historical cp.spawnSync lowering. */
|
||||
export function lowerSpawnSyncCall(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr {
|
||||
if (expr.arguments.length > 3 || expr.arguments.some(ts.isSpreadElement)) {
|
||||
if (expr.arguments.nodes.length > 3 || expr.arguments.nodes.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(
|
||||
"spawnSync with this argument shape",
|
||||
expr,
|
||||
"the supported form is spawnSync(command, args?, options?)",
|
||||
);
|
||||
}
|
||||
const cmd = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
|
||||
const argv = lowerChildArgsArg(lowerer, expr.arguments[1], loc);
|
||||
const optsNode = expr.arguments[2];
|
||||
const cmd = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
|
||||
const argv = lowerChildArgsArg(lowerer, expr.arguments.nodes[1], loc);
|
||||
const optsNode = expr.arguments.nodes[2];
|
||||
|
||||
let timeout: IrExpr = numLit(0, loc);
|
||||
let killSignal: IrExpr = { kind: "strLit", value: "", type: STRING, loc };
|
||||
@@ -152,8 +152,8 @@ export function lowerSpawnSyncCall(lowerer: Lowerer, expr: ts.CallExpression, lo
|
||||
modeOf(t.value, 2);
|
||||
return;
|
||||
}
|
||||
if (ts.isArrayLiteralExpression(node) && node.elements.length === 3) {
|
||||
node.elements.forEach((el, i) => {
|
||||
if (ts.isArrayLiteralExpression(node) && node.elements.nodes.length === 3) {
|
||||
node.elements.nodes.forEach((el, i) => {
|
||||
const et = lowerer.typeOf(el);
|
||||
if (!et.isStringLiteralType()) {
|
||||
lowerer.noLowering("spawnSync stdio tuple entries beyond string literals", el);
|
||||
@@ -182,7 +182,7 @@ export function lowerSpawnSyncCall(lowerer: Lowerer, expr: ts.CallExpression, lo
|
||||
'stdio takes a "pipe"/"ignore"/"inherit" literal (or a value typed as a union of those), or a 3-tuple of literals',
|
||||
);
|
||||
};
|
||||
for (const p of optsNode.properties) {
|
||||
for (const p of optsNode.properties.nodes) {
|
||||
const m = optionMember(p);
|
||||
if (!m) {
|
||||
lowerer.noLowering(
|
||||
@@ -280,21 +280,21 @@ export function lowerSpawnSyncCall(lowerer: Lowerer, expr: ts.CallExpression, lo
|
||||
* `windowsHide` (a POSIX no-op). The bare `{ stdio: "ignore" }` shape
|
||||
* keeps its historical cp.spawn lowering. */
|
||||
export function lowerSpawnCall(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr {
|
||||
if (expr.arguments.length > 3 || expr.arguments.some(ts.isSpreadElement)) {
|
||||
if (expr.arguments.nodes.length > 3 || expr.arguments.nodes.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(
|
||||
"spawn with this argument shape",
|
||||
expr,
|
||||
'the supported form is spawn(command, args?, { stdio: "pipe" | "ignore" | "inherit", detached?, env?, cwd? })',
|
||||
);
|
||||
}
|
||||
const cmd = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
|
||||
const cmd = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
|
||||
let argsNode: ts.Expression | undefined;
|
||||
let optsNode: ts.Expression | undefined;
|
||||
if (expr.arguments.length === 3) {
|
||||
argsNode = expr.arguments[1];
|
||||
optsNode = expr.arguments[2];
|
||||
} else if (expr.arguments.length === 2) {
|
||||
const second = expr.arguments[1]!;
|
||||
if (expr.arguments.nodes.length === 3) {
|
||||
argsNode = expr.arguments.nodes[1];
|
||||
optsNode = expr.arguments.nodes[2];
|
||||
} else if (expr.arguments.nodes.length === 2) {
|
||||
const second = expr.arguments.nodes[1]!;
|
||||
const secondType = lowerer.typeOf(second);
|
||||
const mapped = lowerer.mapTypeOf(secondType);
|
||||
if (
|
||||
@@ -338,7 +338,7 @@ export function lowerSpawnCall(lowerer: Lowerer, expr: ts.CallExpression, loc: S
|
||||
let plain = true; // exactly { stdio: "ignore" }: the historical libCall
|
||||
|
||||
if (optsNode && ts.isObjectLiteralExpression(optsNode)) {
|
||||
for (const p of optsNode.properties) {
|
||||
for (const p of optsNode.properties.nodes) {
|
||||
// The conditional-spread idiom `...(isWindows ? {} : { detached:
|
||||
// true })` (either orientation): the one carried member supported
|
||||
// is `detached` with a boolean literal — the platform-conditional
|
||||
@@ -393,7 +393,7 @@ export function lowerSpawnCall(lowerer: Lowerer, expr: ts.CallExpression, loc: S
|
||||
// The 3-tuple form: every slot accepts a literal, including
|
||||
// "pipe" (child.stdin/stdout/stderr); stdout/stderr also accept
|
||||
// a number-typed fd (an openSync result — dup2'd into the child).
|
||||
if (ts.isArrayLiteralExpression(m.value) && m.value.elements.length === 3) {
|
||||
if (ts.isArrayLiteralExpression(m.value) && m.value.elements.nodes.length === 3) {
|
||||
const slot = (el: ts.Expression, which: 0 | 1 | 2): void => {
|
||||
const t = lowerer.typeOf(el);
|
||||
if (t.isStringLiteralType()) {
|
||||
@@ -434,7 +434,7 @@ export function lowerSpawnCall(lowerer: Lowerer, expr: ts.CallExpression, loc: S
|
||||
errFd = fd;
|
||||
}
|
||||
};
|
||||
m.value.elements.forEach((el, i) => slot(el, i as 0 | 1 | 2));
|
||||
m.value.elements.nodes.forEach((el, i) => slot(el, i as 0 | 1 | 2));
|
||||
sawStdio = true;
|
||||
plain = false;
|
||||
break;
|
||||
@@ -512,7 +512,7 @@ export function lowerSpawnCall(lowerer: Lowerer, expr: ts.CallExpression, loc: S
|
||||
}
|
||||
if (shellEnabled && argsNode !== undefined) {
|
||||
const shellArgs = stripTypeCasts(argsNode);
|
||||
if (!ts.isArrayLiteralExpression(shellArgs) || shellArgs.elements.length !== 0) {
|
||||
if (!ts.isArrayLiteralExpression(shellArgs) || shellArgs.elements.nodes.length !== 0) {
|
||||
lowerer.noLowering(
|
||||
"spawn with shell enabled and separate arguments",
|
||||
argsNode,
|
||||
@@ -551,9 +551,9 @@ export function lowerSpawnCall(lowerer: Lowerer, expr: ts.CallExpression, loc: S
|
||||
* Node's default JSON IPC channel is always present. */
|
||||
export function lowerForkCall(lowerer: Lowerer, expr: ts.CallExpression, loc: SrcLoc): IrExpr {
|
||||
if (
|
||||
expr.arguments.length < 1 ||
|
||||
expr.arguments.length > 3 ||
|
||||
expr.arguments.some(ts.isSpreadElement)
|
||||
expr.arguments.nodes.length < 1 ||
|
||||
expr.arguments.nodes.length > 3 ||
|
||||
expr.arguments.nodes.some(ts.isSpreadElement)
|
||||
) {
|
||||
lowerer.noLowering(
|
||||
"fork with this argument shape",
|
||||
@@ -561,24 +561,24 @@ export function lowerForkCall(lowerer: Lowerer, expr: ts.CallExpression, loc: Sr
|
||||
"use fork(staticModulePath[, stringArgs][, inlineOptions])",
|
||||
);
|
||||
}
|
||||
const targetPath = staticForkModulePath(lowerer.program, expr.arguments[0]!);
|
||||
const targetPath = staticForkModulePath(lowerer.program, expr.arguments.nodes[0]!);
|
||||
const targetId =
|
||||
targetPath === null ? undefined : lowerer.forkTargetIdByPath.get(tsgoPath(resolve(targetPath)));
|
||||
if (targetId === undefined) {
|
||||
lowerer.noLowering(
|
||||
"fork with a runtime-valued module path",
|
||||
expr.arguments[0]!,
|
||||
expr.arguments.nodes[0]!,
|
||||
'resolve a relative worker with new URL("./worker.ts", import.meta.url) or fileURLToPath(...) and bind it to a const if needed',
|
||||
);
|
||||
}
|
||||
|
||||
let argsNode: ts.Expression | undefined;
|
||||
let optsNode: ts.Expression | undefined;
|
||||
if (expr.arguments.length === 3) {
|
||||
argsNode = expr.arguments[1];
|
||||
optsNode = expr.arguments[2];
|
||||
} else if (expr.arguments.length === 2) {
|
||||
const second = expr.arguments[1]!;
|
||||
if (expr.arguments.nodes.length === 3) {
|
||||
argsNode = expr.arguments.nodes[1];
|
||||
optsNode = expr.arguments.nodes[2];
|
||||
} else if (expr.arguments.nodes.length === 2) {
|
||||
const second = expr.arguments.nodes[1]!;
|
||||
const secondType = lowerer.typeOf(second);
|
||||
const mapped = lowerer.mapTypeOf(secondType);
|
||||
if (
|
||||
@@ -622,7 +622,7 @@ export function lowerForkCall(lowerer: Lowerer, expr: ts.CallExpression, loc: Sr
|
||||
};
|
||||
|
||||
if (optsNode && ts.isObjectLiteralExpression(optsNode)) {
|
||||
for (const property of optsNode.properties) {
|
||||
for (const property of optsNode.properties.nodes) {
|
||||
const member = optionMember(property);
|
||||
if (!member) {
|
||||
lowerer.noLowering(
|
||||
@@ -651,14 +651,14 @@ export function lowerForkCall(lowerer: Lowerer, expr: ts.CallExpression, loc: Sr
|
||||
break;
|
||||
case "stdio": {
|
||||
if (ts.isArrayLiteralExpression(member.value)) {
|
||||
if (member.value.elements.length !== 4) {
|
||||
if (member.value.elements.nodes.length !== 4) {
|
||||
lowerer.noLowering(
|
||||
"fork with this stdio tuple",
|
||||
member.value,
|
||||
'use [stdin, stdout, stderr, "ipc"] with exactly four literal entries',
|
||||
);
|
||||
}
|
||||
const ipc = member.value.elements[3]!;
|
||||
const ipc = member.value.elements.nodes[3]!;
|
||||
const ipcType = lowerer.typeOf(ipc);
|
||||
if (!ipcType.isStringLiteralType() || ipcType.value !== "ipc") {
|
||||
lowerer.noLowering(
|
||||
@@ -667,9 +667,9 @@ export function lowerForkCall(lowerer: Lowerer, expr: ts.CallExpression, loc: Sr
|
||||
'the fourth stdio entry must be "ipc"',
|
||||
);
|
||||
}
|
||||
inMode = stdioMode(member.value.elements[0]!, "stdin");
|
||||
outMode = stdioMode(member.value.elements[1]!, "stdout");
|
||||
errMode = stdioMode(member.value.elements[2]!, "stderr");
|
||||
inMode = stdioMode(member.value.elements.nodes[0]!, "stdin");
|
||||
outMode = stdioMode(member.value.elements.nodes[1]!, "stdout");
|
||||
errMode = stdioMode(member.value.elements.nodes[2]!, "stderr");
|
||||
} else {
|
||||
const mode = stdioMode(member.value, "scalar");
|
||||
inMode = outMode = errMode = mode;
|
||||
@@ -743,20 +743,20 @@ export function lowerExecFileCall(
|
||||
): IrExpr {
|
||||
const what = shell ? "exec" : "execFile";
|
||||
if (
|
||||
expr.arguments.some(ts.isSpreadElement) ||
|
||||
expr.arguments.length < 2 ||
|
||||
expr.arguments.length > (shell ? 3 : 4)
|
||||
expr.arguments.nodes.some(ts.isSpreadElement) ||
|
||||
expr.arguments.nodes.length < 2 ||
|
||||
expr.arguments.nodes.length > (shell ? 3 : 4)
|
||||
) {
|
||||
lowerer.noLowering(
|
||||
`${what} with ${expr.arguments.length} arguments`,
|
||||
`${what} with ${expr.arguments.nodes.length} arguments`,
|
||||
expr,
|
||||
shell
|
||||
? "the supported callback forms are exec(command, callback) and exec(command, { encoding: 'utf8', maxBuffer: N }, callback)"
|
||||
: "the supported callback forms are execFile(file, callback), execFile(file, args, callback), and execFile(file, args, { encoding: 'utf8', maxBuffer: N }, callback)",
|
||||
);
|
||||
}
|
||||
const first = lowerer.lowerExprExpecting(expr.arguments[0]!, STRING);
|
||||
const callbackNode = expr.arguments[expr.arguments.length - 1]!;
|
||||
const first = lowerer.lowerExprExpecting(expr.arguments.nodes[0]!, STRING);
|
||||
const callbackNode = expr.arguments.nodes[expr.arguments.nodes.length - 1]!;
|
||||
// exec: /bin/sh -c <command>, execSync's lowering (the runtime reads the
|
||||
// command line back from argv[1] for failure messages); execFile: the
|
||||
// file and its args list.
|
||||
@@ -768,9 +768,15 @@ export function lowerExecFileCall(
|
||||
type: arrayOf(STRING),
|
||||
loc,
|
||||
}
|
||||
: lowerChildArgsArg(lowerer, expr.arguments.length >= 3 ? expr.arguments[1] : undefined, loc);
|
||||
: lowerChildArgsArg(
|
||||
lowerer,
|
||||
expr.arguments.nodes.length >= 3 ? expr.arguments.nodes[1] : undefined,
|
||||
loc,
|
||||
);
|
||||
const optionsNode =
|
||||
expr.arguments.length === (shell ? 3 : 4) ? expr.arguments[shell ? 1 : 2] : undefined;
|
||||
expr.arguments.nodes.length === (shell ? 3 : 4)
|
||||
? expr.arguments.nodes[shell ? 1 : 2]
|
||||
: undefined;
|
||||
if (optionsNode) {
|
||||
const options = optionsNode;
|
||||
if (!ts.isObjectLiteralExpression(options)) {
|
||||
@@ -792,7 +798,7 @@ export function lowerExecFileCall(
|
||||
].includes(node.operatorToken.kind) &&
|
||||
pureNumber(node.left) &&
|
||||
pureNumber(node.right));
|
||||
for (const property of options.properties) {
|
||||
for (const property of options.properties.nodes) {
|
||||
const member = optionMember(property);
|
||||
if (!member)
|
||||
lowerer.noLowering(
|
||||
|
||||
@@ -7,6 +7,7 @@ import { stripTypeCasts } from "./arguments.js";
|
||||
import { streamClassAliasDecl } from "../lower-stream.js";
|
||||
import { zlibClassName, zlibStreamModeOf } from "./compression.js";
|
||||
import { stdlibGlobalNameOf } from "../surfaces.js";
|
||||
import { RUNTIME_ERROR_CLASSES } from "../../../ir/ir.js";
|
||||
|
||||
/** A builtin class an expression names at compile time: the error
|
||||
* classes, node:events EventEmitter, the node:stream classes (each a
|
||||
@@ -123,6 +124,60 @@ export function registerBuiltinClassAlias(
|
||||
return symbol !== undefined && builtinClassAliasOwnsNoSlot(lowerer, symbol);
|
||||
}
|
||||
|
||||
/** The runtime-provided class (EventEmitter, a stream class, an error
|
||||
* class) `expr` names, when values of it construct (valueCtorParams), or
|
||||
* null. */
|
||||
export function runtimeClassValueOf(lowerer: Lowerer, expr: ts.Expression): ClassInfo | null {
|
||||
const ref = builtinClassRefOf(lowerer, expr);
|
||||
if (ref === null || ref.kind === "zlib" || ref.kind === "collection") return null;
|
||||
return ref.info.valueCtorParams !== undefined ? ref.info : null;
|
||||
}
|
||||
|
||||
/** The runtime-provided class whose static side `type` is (`typeof
|
||||
* EventEmitter`, `typeof Readable`, `ErrorConstructor`), when values of
|
||||
* it construct, or null. Instance types have no construct signatures and
|
||||
* answer null. A union of such static sides (`[Readable, Writable]`'s
|
||||
* element type) is their nearest common ancestor's value when every arm
|
||||
* constructs through the same parameters: a slot of the ancestor holds
|
||||
* any of them. */
|
||||
export function runtimeClassValueOfType(lowerer: Lowerer, type: ts.Type): string | null {
|
||||
if (type.isUnionType()) {
|
||||
const arms: ClassInfo[] = [];
|
||||
for (const part of ts.constituentTypes(type)) {
|
||||
const name = runtimeClassValueOfType(lowerer, part);
|
||||
const info = name !== null ? lowerer.classes.get(name) : undefined;
|
||||
if (!info) return null;
|
||||
arms.push(info);
|
||||
}
|
||||
const ancestor = commonRuntimeAncestor(arms);
|
||||
if (ancestor === null) return null;
|
||||
return arms.every((arm) => lowerer.ctorAbiEquals(arm, ancestor)) ? ancestor.def.name : null;
|
||||
}
|
||||
if (lowerer.checker.getConstructSignatures(type).length === 0) return null;
|
||||
const symbol = type.getSymbol();
|
||||
if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null;
|
||||
let info = lowerer.builtinEmitterInfoOf(symbol) ?? lowerer.builtinStreamInfoOf(symbol);
|
||||
if (!info && symbol.name.endsWith("Constructor")) {
|
||||
for (const [irName, rec] of RUNTIME_ERROR_CLASSES) {
|
||||
if (`${rec.lib}Constructor` === symbol.name) info = lowerer.classes.get(irName) ?? null;
|
||||
}
|
||||
}
|
||||
return info?.valueCtorParams !== undefined ? info.def.name : null;
|
||||
}
|
||||
|
||||
/** The nearest class every one of `infos` is or extends, when it is a
|
||||
* runtime class whose values construct, or null. */
|
||||
function commonRuntimeAncestor(infos: ClassInfo[]): ClassInfo | null {
|
||||
for (let candidate = infos[0] ?? null; candidate; candidate = candidate.base) {
|
||||
const holdsAll = infos.every((info) => {
|
||||
for (let c: ClassInfo | null = info; c; c = c.base) if (c === candidate) return true;
|
||||
return false;
|
||||
});
|
||||
if (holdsAll) return candidate.def.runtime && candidate.valueCtorParams ? candidate : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** True when two references name the same builtin class. */
|
||||
export function sameBuiltinClass(a: BuiltinClassRef, b: BuiltinClassRef): boolean {
|
||||
if (a.kind === "zlib") return b.kind === "zlib" && a.mode === b.mode;
|
||||
|
||||
@@ -197,7 +197,7 @@ export function lowerZlibStreamMethodCall(
|
||||
): IrExpr {
|
||||
const loc = locOf(call);
|
||||
const member = access.name.text;
|
||||
const args = call.arguments;
|
||||
const args = call.arguments.nodes;
|
||||
if (args.some(ts.isSpreadElement)) lowerer.noLowering(`${member} with spread arguments`, call);
|
||||
const isFunc = (node: ts.Expression): boolean =>
|
||||
lowerer.mapTypeOf(lowerer.typeOf(node))?.kind === "func";
|
||||
@@ -304,25 +304,25 @@ export function lowerPromisifiedZlibCall(
|
||||
member: string,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
if (expr.arguments.some(ts.isSpreadElement)) {
|
||||
if (expr.arguments.nodes.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(`the promisified zlib.${member} with spread arguments`, expr);
|
||||
}
|
||||
if (expr.arguments.length !== 1 && expr.arguments.length !== 2) {
|
||||
if (expr.arguments.nodes.length !== 1 && expr.arguments.nodes.length !== 2) {
|
||||
lowerer.noLowering(
|
||||
`the promisified zlib.${member} with ${expr.arguments.length} arguments`,
|
||||
`the promisified zlib.${member} with ${expr.arguments.nodes.length} arguments`,
|
||||
expr,
|
||||
`the supported forms are ${member}Async(data) and ${member}Async(data, options)`,
|
||||
);
|
||||
}
|
||||
const input = zlibInput(lowerer, expr.arguments[0]!, loc);
|
||||
const input = zlibInput(lowerer, expr.arguments.nodes[0]!, loc);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: input.dyn ? "zlib.promiseDyn" : "zlib.promise",
|
||||
args: [
|
||||
numLit(ZLIB_ONE_SHOT_MODES[member]!, loc),
|
||||
input.value,
|
||||
expr.arguments[1] !== undefined
|
||||
? zlibOptionsValue(lowerer, expr.arguments[1], `zlib.${member}`, loc)
|
||||
expr.arguments.nodes[1] !== undefined
|
||||
? zlibOptionsValue(lowerer, expr.arguments.nodes[1], `zlib.${member}`, loc)
|
||||
: dynUndefinedExpr(loc),
|
||||
],
|
||||
type: { kind: "promise", inner: BYTES_U8 },
|
||||
@@ -346,19 +346,26 @@ export function lowerZlibModuleCall(
|
||||
bi: { module: string; member: string },
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
if (expr.arguments.some(ts.isSpreadElement)) {
|
||||
if (expr.arguments.nodes.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(`zlib.${bi.member} with spread arguments`, expr);
|
||||
}
|
||||
const streamMode = ZLIB_STREAM_FACTORIES[bi.member];
|
||||
if (streamMode !== undefined) {
|
||||
return lowerZlibStreamNew(lowerer, expr.arguments, streamMode, `zlib.${bi.member}`, expr, loc);
|
||||
return lowerZlibStreamNew(
|
||||
lowerer,
|
||||
expr.arguments.nodes,
|
||||
streamMode,
|
||||
`zlib.${bi.member}`,
|
||||
expr,
|
||||
loc,
|
||||
);
|
||||
}
|
||||
const syncFn = ZLIB_SYNC_FNS[bi.member];
|
||||
if (syncFn !== undefined) {
|
||||
if (expr.arguments.length === 0 || expr.arguments.length > 2) {
|
||||
lowerer.noLowering(`${bi.member} with ${expr.arguments.length} arguments`, expr);
|
||||
if (expr.arguments.nodes.length === 0 || expr.arguments.nodes.length > 2) {
|
||||
lowerer.noLowering(`${bi.member} with ${expr.arguments.nodes.length} arguments`, expr);
|
||||
}
|
||||
const input = zlibInput(lowerer, expr.arguments[0]!, loc);
|
||||
const input = zlibInput(lowerer, expr.arguments.nodes[0]!, loc);
|
||||
if (input.dyn) {
|
||||
return {
|
||||
kind: "libCall",
|
||||
@@ -366,8 +373,8 @@ export function lowerZlibModuleCall(
|
||||
args: [
|
||||
numLit(ZLIB_ONE_SHOT_MODES[bi.member]!, loc),
|
||||
input.value,
|
||||
expr.arguments[1] !== undefined
|
||||
? zlibOptionsValue(lowerer, expr.arguments[1], `zlib.${bi.member}`, loc)
|
||||
expr.arguments.nodes[1] !== undefined
|
||||
? zlibOptionsValue(lowerer, expr.arguments.nodes[1], `zlib.${bi.member}`, loc)
|
||||
: dynUndefinedExpr(loc),
|
||||
],
|
||||
type: BYTES_U8,
|
||||
@@ -375,15 +382,15 @@ export function lowerZlibModuleCall(
|
||||
};
|
||||
}
|
||||
if (
|
||||
expr.arguments.length === 2 &&
|
||||
expr.arguments.nodes.length === 2 &&
|
||||
(bi.member === "deflateSync" || bi.member === "deflateRawSync" || bi.member === "gzipSync")
|
||||
) {
|
||||
const options = expr.arguments[1]!;
|
||||
const options = expr.arguments.nodes[1]!;
|
||||
// A lone literal level keeps the direct compression entry (build-time
|
||||
// compression in the native emitters uses it); other options take the
|
||||
// runtime option ladder below.
|
||||
if (ts.isObjectLiteralExpression(options) && options.properties.length === 1) {
|
||||
const prop = options.properties[0]!;
|
||||
if (ts.isObjectLiteralExpression(options) && options.properties.nodes.length === 1) {
|
||||
const prop = options.properties.nodes[0]!;
|
||||
if (
|
||||
ts.isPropertyAssignment(prop) &&
|
||||
(ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) &&
|
||||
@@ -416,14 +423,14 @@ export function lowerZlibModuleCall(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expr.arguments.length === 2) {
|
||||
if (expr.arguments.nodes.length === 2) {
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "zlib.syncOpts",
|
||||
args: [
|
||||
numLit(ZLIB_ONE_SHOT_MODES[bi.member]!, loc),
|
||||
input.value,
|
||||
zlibOptionsValue(lowerer, expr.arguments[1]!, `zlib.${bi.member}`, loc),
|
||||
zlibOptionsValue(lowerer, expr.arguments.nodes[1]!, `zlib.${bi.member}`, loc),
|
||||
],
|
||||
type: BYTES_U8,
|
||||
loc,
|
||||
@@ -439,19 +446,19 @@ export function lowerZlibModuleCall(
|
||||
}
|
||||
const callbackFn = ZLIB_CALLBACK_FNS[bi.member];
|
||||
if (callbackFn !== undefined) {
|
||||
if (expr.arguments.length !== 2 && expr.arguments.length !== 3) {
|
||||
if (expr.arguments.nodes.length !== 2 && expr.arguments.nodes.length !== 3) {
|
||||
lowerer.noLowering(
|
||||
`${bi.member} with ${expr.arguments.length} arguments`,
|
||||
`${bi.member} with ${expr.arguments.nodes.length} arguments`,
|
||||
expr,
|
||||
`${bi.member}(data, callback) and ${bi.member}(data, options, callback) are supported`,
|
||||
);
|
||||
}
|
||||
const input = zlibInput(lowerer, expr.arguments[0]!, loc);
|
||||
const input = zlibInput(lowerer, expr.arguments.nodes[0]!, loc);
|
||||
const dynData = input.dyn;
|
||||
if (expr.arguments.length === 3 || dynData) {
|
||||
if (expr.arguments.nodes.length === 3 || dynData) {
|
||||
// (data, options, callback): Node validates the options before the
|
||||
// callback and the input, so the runtime ladder runs first.
|
||||
const withOptions = expr.arguments.length === 3;
|
||||
const withOptions = expr.arguments.nodes.length === 3;
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: dynData ? "zlib.cbOptsDyn" : "zlib.cbOpts",
|
||||
@@ -459,9 +466,9 @@ export function lowerZlibModuleCall(
|
||||
numLit(ZLIB_ONE_SHOT_MODES[bi.member]!, loc),
|
||||
input.value,
|
||||
withOptions
|
||||
? zlibOptionsValue(lowerer, expr.arguments[1]!, `zlib.${bi.member}`, loc)
|
||||
? zlibOptionsValue(lowerer, expr.arguments.nodes[1]!, `zlib.${bi.member}`, loc)
|
||||
: dynUndefinedExpr(loc),
|
||||
errorFirstBytesCallback(lowerer, expr.arguments[withOptions ? 2 : 1]!, "zlib"),
|
||||
errorFirstBytesCallback(lowerer, expr.arguments.nodes[withOptions ? 2 : 1]!, "zlib"),
|
||||
],
|
||||
type: VOID,
|
||||
loc,
|
||||
@@ -470,15 +477,15 @@ export function lowerZlibModuleCall(
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: callbackFn,
|
||||
args: [input.value, errorFirstBytesCallback(lowerer, expr.arguments[1]!, "zlib")],
|
||||
args: [input.value, errorFirstBytesCallback(lowerer, expr.arguments.nodes[1]!, "zlib")],
|
||||
type: VOID,
|
||||
loc,
|
||||
};
|
||||
}
|
||||
if (bi.member === "crc32") {
|
||||
if (expr.arguments.length < 1 || expr.arguments.length > 2) {
|
||||
if (expr.arguments.nodes.length < 1 || expr.arguments.nodes.length > 2) {
|
||||
lowerer.noLowering(
|
||||
`zlib.crc32 with ${expr.arguments.length} arguments`,
|
||||
`zlib.crc32 with ${expr.arguments.nodes.length} arguments`,
|
||||
expr,
|
||||
"crc32(data[, initialValue]) is supported",
|
||||
);
|
||||
@@ -487,9 +494,9 @@ export function lowerZlibModuleCall(
|
||||
kind: "libCall",
|
||||
fn: "zlib.crc32",
|
||||
args: [
|
||||
lowerBuiltinByteInput(lowerer, expr.arguments[0]!, loc, "zlib"),
|
||||
expr.arguments[1]
|
||||
? lowerer.lowerExprExpecting(expr.arguments[1]!, F64)
|
||||
lowerBuiltinByteInput(lowerer, expr.arguments.nodes[0]!, loc, "zlib"),
|
||||
expr.arguments.nodes[1]
|
||||
? lowerer.lowerExprExpecting(expr.arguments.nodes[1]!, F64)
|
||||
: { kind: "numLit", value: 0, type: F64, loc },
|
||||
],
|
||||
type: F64,
|
||||
|
||||
@@ -88,7 +88,7 @@ export function builtinConstantsDestructureDecl(
|
||||
const module = builtinConstantsModuleOf(lowerer, init);
|
||||
if (module === null) return false;
|
||||
const table = BUILTIN_CONSTANTS_TABLES[module]!.table;
|
||||
return nameNode.elements.every(
|
||||
return nameNode.elements.nodes.every(
|
||||
(el) =>
|
||||
!el.dotDotDotToken &&
|
||||
el.initializer === undefined &&
|
||||
|
||||
@@ -47,17 +47,17 @@ export function lowerCryptoComposedCall(
|
||||
// "hex"/"base64" encoding. Anything else falls through (null): bare
|
||||
// randomBytes lowers to a real Buffer through the crypto table, and
|
||||
// the .toString rides the ordinary Buffer method lowering.
|
||||
if (recv.arguments.length !== 1) return null;
|
||||
const encNode = call.arguments[0];
|
||||
if (recv.arguments.nodes.length !== 1) return null;
|
||||
const encNode = call.arguments.nodes[0];
|
||||
const encT = encNode ? lowerer.typeOf(encNode) : undefined;
|
||||
if (
|
||||
call.arguments.length !== 1 ||
|
||||
call.arguments.nodes.length !== 1 ||
|
||||
!encT?.isStringLiteralType() ||
|
||||
(encT.value !== "hex" && encT.value !== "base64")
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const size = lowerer.lowerExprExpecting(recv.arguments[0]!, F64);
|
||||
const size = lowerer.lowerExprExpecting(recv.arguments.nodes[0]!, F64);
|
||||
const enc = lowerer.lowerExprExpecting(encNode!, STRING);
|
||||
return {
|
||||
kind: "libCall",
|
||||
@@ -96,28 +96,30 @@ function lowerHashDigestChain(
|
||||
: null;
|
||||
if (!bi || bi.module !== "crypto" || bi.member !== "createHash") return null;
|
||||
const loc = locOf(call);
|
||||
const algT = chCall.arguments.length === 1 ? lowerer.typeOf(chCall.arguments[0]!) : undefined;
|
||||
const algT =
|
||||
chCall.arguments.nodes.length === 1 ? lowerer.typeOf(chCall.arguments.nodes[0]!) : undefined;
|
||||
if (!algT?.isStringLiteralType() || (algT.value !== "sha256" && algT.value !== "sha1"))
|
||||
return null;
|
||||
if (updateCall.arguments.length !== 1) {
|
||||
if (updateCall.arguments.nodes.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
const encT = call.arguments.length === 1 ? lowerer.typeOf(call.arguments[0]!) : undefined;
|
||||
const encT =
|
||||
call.arguments.nodes.length === 1 ? lowerer.typeOf(call.arguments.nodes[0]!) : undefined;
|
||||
if (!encT?.isStringLiteralType() || (encT.value !== "hex" && encT.value !== "base64")) {
|
||||
return null;
|
||||
}
|
||||
// alg and enc are proven literals (fenced above), so lowering them
|
||||
// out of source position observes nothing; the data lowers between
|
||||
// them in its own source order.
|
||||
const alg = lowerer.lowerExprExpecting(chCall.arguments[0]!, STRING);
|
||||
const alg = lowerer.lowerExprExpecting(chCall.arguments.nodes[0]!, STRING);
|
||||
// The data picks the runtime entry by its static type, the
|
||||
// fileURLToPath convention: strings hash their UTF-8 bytes (Node's
|
||||
// default input encoding), Buffers/typed arrays hash their bytes.
|
||||
const dataNode = updateCall.arguments[0]!;
|
||||
const dataNode = updateCall.arguments.nodes[0]!;
|
||||
const dataIr = lowerer.mapTypeOf(lowerer.typeOf(dataNode));
|
||||
if (dataIr?.kind === "bytes") {
|
||||
const data = cryptoByteView(lowerer, dataNode, lowerer.lowerExpr(dataNode));
|
||||
const enc = lowerer.lowerExprExpecting(call.arguments[0]!, STRING);
|
||||
const enc = lowerer.lowerExprExpecting(call.arguments.nodes[0]!, STRING);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "crypto.hashDigestBytes",
|
||||
@@ -128,7 +130,7 @@ function lowerHashDigestChain(
|
||||
}
|
||||
if (dataIr?.kind === "string") {
|
||||
const data = lowerer.lowerExprExpecting(dataNode, STRING);
|
||||
const enc = lowerer.lowerExprExpecting(call.arguments[0]!, STRING);
|
||||
const enc = lowerer.lowerExprExpecting(call.arguments.nodes[0]!, STRING);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "crypto.hashDigestStr",
|
||||
@@ -257,7 +259,7 @@ export function lowerCryptoModuleCall(
|
||||
loc: SrcLoc,
|
||||
): IrExpr | null {
|
||||
if (bi.module !== "crypto") return null;
|
||||
const args = expr.arguments;
|
||||
const args = expr.arguments.nodes;
|
||||
if (args.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(`crypto.${bi.member} with spread arguments`, expr);
|
||||
}
|
||||
@@ -465,8 +467,8 @@ export function lowerCryptoModuleCall(
|
||||
let options = args[2];
|
||||
// An options literal holding only a literal string-digest
|
||||
// outputEncoding is that encoding; evaluating it observes nothing.
|
||||
if (options && ts.isObjectLiteralExpression(options) && options.properties.length === 1) {
|
||||
const property = options.properties[0]!;
|
||||
if (options && ts.isObjectLiteralExpression(options) && options.properties.nodes.length === 1) {
|
||||
const property = options.properties.nodes[0]!;
|
||||
if (
|
||||
ts.isPropertyAssignment(property) &&
|
||||
ts.isIdentifier(property.name) &&
|
||||
@@ -820,8 +822,8 @@ export function lowerCryptoModuleCall(
|
||||
};
|
||||
const list = own(LISTS, bi.member);
|
||||
if (bi.member !== "getFips" && list === undefined) return null;
|
||||
if (expr.arguments.length !== 0) {
|
||||
lowerer.noLowering(`crypto.${bi.member} with ${expr.arguments.length} arguments`, expr);
|
||||
if (expr.arguments.nodes.length !== 0) {
|
||||
lowerer.noLowering(`crypto.${bi.member} with ${expr.arguments.nodes.length} arguments`, expr);
|
||||
}
|
||||
if (bi.member === "getFips") {
|
||||
return { kind: "numLit", value: 0, type: F64, loc };
|
||||
@@ -916,7 +918,7 @@ export function lowerCipherMethodCall(
|
||||
"update(), final(), setAAD(), setAutoPadding(), getAuthTag(), and setAuthTag() are supported; cipher streams are not",
|
||||
);
|
||||
}
|
||||
if (call.arguments.some(ts.isSpreadElement))
|
||||
if (call.arguments.nodes.some(ts.isSpreadElement))
|
||||
lowerer.noLowering(`Cipheriv/Decipheriv.${name} with spread arguments`, call);
|
||||
const loc = locOf(call);
|
||||
return {
|
||||
@@ -924,7 +926,7 @@ export function lowerCipherMethodCall(
|
||||
recv: lowerer.lowerExprExpecting(access.expression, DYN),
|
||||
method: name,
|
||||
calleeName: v8CalleeText(access),
|
||||
args: call.arguments.map((arg) => lowerer.lowerExprExpecting(arg, DYN)),
|
||||
args: call.arguments.nodes.map((arg) => lowerer.lowerExprExpecting(arg, DYN)),
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
@@ -946,9 +948,9 @@ export function lowerCryptoHashMethodCall(
|
||||
// Hmac has no copy(): Node reads the member as undefined and throws a
|
||||
// TypeError once the arguments are evaluated.
|
||||
if (receiverType.kind === "cryptoHmac" && name === "copy") {
|
||||
if (call.arguments.some(ts.isSpreadElement))
|
||||
if (call.arguments.nodes.some(ts.isSpreadElement))
|
||||
lowerer.noLowering("Hmac.copy with spread arguments", call);
|
||||
const evaluated = [access.expression, ...call.arguments].map((node): IrStmt => ({
|
||||
const evaluated = [access.expression, ...call.arguments.nodes].map((node): IrStmt => ({
|
||||
kind: "exprStmt",
|
||||
expr: lowerer.lowerExpr(node),
|
||||
loc,
|
||||
@@ -965,19 +967,19 @@ export function lowerCryptoHashMethodCall(
|
||||
const receiver = (): IrExpr => lowerer.lowerExprExpecting(access.expression, receiverType);
|
||||
const hash = receiverType.kind === "cryptoHash";
|
||||
const label = hash ? "Hash" : "Hmac";
|
||||
if (call.arguments.some(ts.isSpreadElement))
|
||||
if (call.arguments.nodes.some(ts.isSpreadElement))
|
||||
lowerer.noLowering(`${label}.${name} with spread arguments`, call);
|
||||
if (name === "update") {
|
||||
if (call.arguments.length < 1 || call.arguments.length > 2) {
|
||||
if (call.arguments.nodes.length < 1 || call.arguments.nodes.length > 2) {
|
||||
lowerer.noLowering(
|
||||
`${label}.update with ${call.arguments.length} arguments`,
|
||||
`${label}.update with ${call.arguments.nodes.length} arguments`,
|
||||
call,
|
||||
"update(data[, inputEncoding]) is supported",
|
||||
);
|
||||
}
|
||||
const target = receiver();
|
||||
const dataNode = call.arguments[0]!;
|
||||
const encodingNode = call.arguments[1];
|
||||
const dataNode = call.arguments.nodes[0]!;
|
||||
const encodingNode = call.arguments.nodes[1];
|
||||
const dataType = lowerer.mapTypeOf(lowerer.typeOf(dataNode));
|
||||
// Indexed reads can retain optional storage after a nullish fallback
|
||||
// or narrowing. Use the proven argument type through a checked coercion.
|
||||
@@ -1032,14 +1034,14 @@ export function lowerCryptoHashMethodCall(
|
||||
};
|
||||
}
|
||||
if (name === "digest") {
|
||||
if (call.arguments.length > 1) {
|
||||
if (call.arguments.nodes.length > 1) {
|
||||
lowerer.noLowering(
|
||||
`${label}.digest with ${call.arguments.length} arguments`,
|
||||
`${label}.digest with ${call.arguments.nodes.length} arguments`,
|
||||
call,
|
||||
"digest([outputEncoding]) is supported",
|
||||
);
|
||||
}
|
||||
const encodingNode = call.arguments[0];
|
||||
const encodingNode = call.arguments.nodes[0];
|
||||
if (encodingNode === undefined) {
|
||||
return {
|
||||
kind: "libCall",
|
||||
@@ -1078,13 +1080,13 @@ export function lowerCryptoHashMethodCall(
|
||||
};
|
||||
}
|
||||
if (name === "copy" && hash) {
|
||||
if (call.arguments.length > 1)
|
||||
if (call.arguments.nodes.length > 1)
|
||||
lowerer.noLowering(
|
||||
`Hash.copy with ${call.arguments.length} arguments`,
|
||||
`Hash.copy with ${call.arguments.nodes.length} arguments`,
|
||||
call,
|
||||
"copy([options]) is supported",
|
||||
);
|
||||
if (call.arguments.length === 0)
|
||||
if (call.arguments.nodes.length === 0)
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "crypto.hashCopy",
|
||||
@@ -1095,7 +1097,7 @@ export function lowerCryptoHashMethodCall(
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "crypto.hashCopyOpts",
|
||||
args: [receiver(), lowerer.lowerExprExpecting(call.arguments[0]!, DYN)],
|
||||
args: [receiver(), lowerer.lowerExprExpecting(call.arguments.nodes[0]!, DYN)],
|
||||
type: CRYPTOHASH_T,
|
||||
loc,
|
||||
};
|
||||
@@ -1138,7 +1140,7 @@ export function lowerPromisifiedCryptoCall(
|
||||
member: string,
|
||||
loc: SrcLoc,
|
||||
): IrExpr {
|
||||
const args = expr.arguments;
|
||||
const args = expr.arguments.nodes;
|
||||
if (args.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering(`the promisified crypto.${member} with spread arguments`, expr);
|
||||
}
|
||||
|
||||
@@ -33,8 +33,14 @@ const DATE_GETTER_FNS: Readonly<Record<string, IrLibFn>> = {
|
||||
getTimezoneOffset: "date.getTimezoneOffset",
|
||||
};
|
||||
|
||||
const DATE_TEXT_FNS: ReadonlyMap<string, IrLibFn> = new Map([
|
||||
["toString", "date.toString"],
|
||||
["toDateString", "date.toDateString"],
|
||||
["toTimeString", "date.toTimeString"],
|
||||
]);
|
||||
|
||||
const DATE_METHOD_HINT =
|
||||
"getTime(), valueOf(), toISOString(), toJSON(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale/string formatters have no lowering";
|
||||
"getTime(), valueOf(), toISOString(), toUTCString(), toJSON(), toString(), toDateString(), toTimeString(), the local/UTC calendar getters, and getTimezoneOffset() are supported; Date setters and locale formatters have no lowering";
|
||||
|
||||
/** The Date slice: statics plus read-only Date values backed by one
|
||||
* TimeClip'd millisecond scalar. Construction lives in lowerNew;
|
||||
@@ -47,8 +53,8 @@ export function lowerDateCall(
|
||||
if (call.questionDotToken || access.questionDotToken) return null;
|
||||
const loc = locOf(call);
|
||||
if (lowerer.stdlibGlobalMember(access, "Date") === "now") {
|
||||
if (call.arguments.length !== 0) {
|
||||
lowerer.noLowering(`Date.now with ${call.arguments.length} arguments`, call);
|
||||
if (call.arguments.nodes.length !== 0) {
|
||||
lowerer.noLowering(`Date.now with ${call.arguments.nodes.length} arguments`, call);
|
||||
}
|
||||
return { kind: "libCall", fn: "date.now", args: [], type: F64, loc };
|
||||
}
|
||||
@@ -56,10 +62,10 @@ export function lowerDateCall(
|
||||
// new Date(dateString).getTime(); keep their NaN and TimeClip behavior
|
||||
// identical for the ISO timestamps used by CLI applications.
|
||||
if (lowerer.stdlibGlobalMember(access, "Date") === "parse") {
|
||||
if (call.arguments.length !== 1 || ts.isSpreadElement(call.arguments[0]!)) {
|
||||
lowerer.noLowering(`Date.parse with ${call.arguments.length} arguments`, call);
|
||||
if (call.arguments.nodes.length !== 1 || ts.isSpreadElement(call.arguments.nodes[0]!)) {
|
||||
lowerer.noLowering(`Date.parse with ${call.arguments.nodes.length} arguments`, call);
|
||||
}
|
||||
const input = lowerer.lowerExprExpecting(call.arguments[0]!, STRING);
|
||||
const input = lowerer.lowerExprExpecting(call.arguments.nodes[0]!, STRING);
|
||||
return { kind: "libCall", fn: "date.parse", args: [input], type: F64, loc };
|
||||
}
|
||||
// Date.UTC(year[, month[, date[, hours[, minutes[, seconds[, ms]]]]]]):
|
||||
@@ -69,16 +75,16 @@ export function lowerDateCall(
|
||||
// argument to number, so the seven-f64 ABI is exact.
|
||||
if (lowerer.stdlibGlobalMember(access, "Date") === "UTC") {
|
||||
if (
|
||||
call.arguments.length < 1 ||
|
||||
call.arguments.length > 7 ||
|
||||
call.arguments.some((a) => ts.isSpreadElement(a))
|
||||
call.arguments.nodes.length < 1 ||
|
||||
call.arguments.nodes.length > 7 ||
|
||||
call.arguments.nodes.some((a) => ts.isSpreadElement(a))
|
||||
) {
|
||||
lowerer.noLowering(`Date.UTC with ${call.arguments.length} arguments`, call);
|
||||
lowerer.noLowering(`Date.UTC with ${call.arguments.nodes.length} arguments`, call);
|
||||
}
|
||||
const defaults = [0, 0, 1, 0, 0, 0, 0]; // year is always present (arity ≥ 1)
|
||||
const args: IrExpr[] = [];
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const a = call.arguments[i];
|
||||
const a = call.arguments.nodes[i];
|
||||
args.push(
|
||||
a !== undefined
|
||||
? lowerer.lowerExprExpecting(a, F64)
|
||||
@@ -91,7 +97,7 @@ export function lowerDateCall(
|
||||
if (!lowerer.isStdlibMember(access)) return null;
|
||||
const name = access.name.text;
|
||||
if (name === "toJSON") return lowerDateToJson(lowerer, call, access);
|
||||
if (call.arguments.length !== 0) {
|
||||
if (call.arguments.nodes.length !== 0) {
|
||||
lowerer.noLowering(`Date.prototype.${name} with arguments`, call, DATE_METHOD_HINT);
|
||||
}
|
||||
const raw = lowerer.lowerExpr(access.expression);
|
||||
@@ -113,10 +119,17 @@ export function lowerDateCall(
|
||||
if (name === "toISOString") {
|
||||
return { kind: "libCall", fn: "date.toISOStringValue", args: [receiver], type: STRING, loc };
|
||||
}
|
||||
if (name === "toUTCString") {
|
||||
return { kind: "libCall", fn: "date.toUTCString", args: [receiver], type: STRING, loc };
|
||||
}
|
||||
const fn = own(DATE_GETTER_FNS, name);
|
||||
if (fn !== undefined) {
|
||||
return { kind: "libCall", fn, args: [receiver], type: F64, loc };
|
||||
}
|
||||
const textFn = DATE_TEXT_FNS.get(name);
|
||||
if (textFn !== undefined) {
|
||||
return { kind: "libCall", fn: textFn, args: [receiver], type: STRING, loc };
|
||||
}
|
||||
lowerer.noLowering(
|
||||
`Date.prototype.${name}`,
|
||||
call,
|
||||
@@ -134,8 +147,8 @@ function lowerDateToJson(
|
||||
access: ts.PropertyAccessExpression,
|
||||
): IrExpr {
|
||||
const loc = locOf(call);
|
||||
if (call.arguments.length > 1 || call.arguments.some((a) => ts.isSpreadElement(a)))
|
||||
lowerer.noLowering(`Date.prototype.toJSON with ${call.arguments.length} arguments`, call);
|
||||
if (call.arguments.nodes.length > 1 || call.arguments.nodes.some((a) => ts.isSpreadElement(a)))
|
||||
lowerer.noLowering(`Date.prototype.toJSON with ${call.arguments.nodes.length} arguments`, call);
|
||||
const raw = lowerer.lowerExpr(access.expression);
|
||||
const receiver: IrExpr =
|
||||
raw.type.kind === "dyn"
|
||||
@@ -145,7 +158,7 @@ function lowerDateToJson(
|
||||
lowerer.badType(access.expression, lowerer.typeOf(access.expression));
|
||||
const date = lowerer.declareHiddenLocal("%date", receiver.type);
|
||||
const stmts: IrStmt[] = [{ kind: "varDecl", localId: date.id, init: receiver, loc }];
|
||||
const key = call.arguments[0];
|
||||
const key = call.arguments.nodes[0];
|
||||
if (key) stmts.push({ kind: "exprStmt", expr: lowerer.lowerExpr(key), loc });
|
||||
const value: IrExpr = { kind: "varRef", localId: date.id, type: receiver.type, loc };
|
||||
const time: IrExpr = { kind: "libCall", fn: "date.getTime", args: [value], type: F64, loc };
|
||||
|
||||
@@ -22,7 +22,7 @@ export function lowerDisposableStackNew(lowerer: Lowerer, expr: ts.NewExpression
|
||||
if (!ts.isIdentifier(expr.expression) || expr.expression.text !== "DisposableStack") return null;
|
||||
const symbol = lowerer.resolveValueSymbol(expr.expression);
|
||||
if (!symbol || !lowerer.isStdlibSymbol(symbol)) return null;
|
||||
if ((expr.arguments ?? []).length !== 0) return null;
|
||||
if ((expr.arguments?.nodes ?? []).length !== 0) return null;
|
||||
return { kind: "libCall", fn: "disposable.stackNew", args: [], type: DYN, loc: locOf(expr) };
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ export function lowerDisposableStackUseCall(
|
||||
access: ts.PropertyAccessExpression,
|
||||
): IrExpr | null {
|
||||
if (call.questionDotToken || access.questionDotToken) return null;
|
||||
if (access.name.text !== "use" || call.arguments.length !== 1) return null;
|
||||
const argNode = call.arguments[0]!;
|
||||
if (access.name.text !== "use" || call.arguments.nodes.length !== 1) return null;
|
||||
const argNode = call.arguments.nodes[0]!;
|
||||
if (ts.isSpreadElement(argNode)) return null;
|
||||
if (!isStdlibDisposableStack(lowerer, access.expression)) return null;
|
||||
const valueT = lowerer.mapTypeOf(lowerer.typeOf(argNode));
|
||||
|
||||
@@ -78,12 +78,12 @@ export function lowerProcessEnvGet(
|
||||
}
|
||||
|
||||
export function lowerProcessLoadEnvFile(lowerer: Lowerer, call: ts.CallExpression): IrExpr {
|
||||
if (call.arguments.length > 1 || call.arguments.some(ts.isSpreadElement)) {
|
||||
if (call.arguments.nodes.length > 1 || call.arguments.nodes.some(ts.isSpreadElement)) {
|
||||
lowerer.noLowering("process.loadEnvFile with spread or extra arguments", call);
|
||||
}
|
||||
const loc = locOf(call);
|
||||
const path: IrExpr = call.arguments[0]
|
||||
? lowerer.lowerExprExpecting(call.arguments[0], DYN)
|
||||
const path: IrExpr = call.arguments.nodes[0]
|
||||
? lowerer.lowerExprExpecting(call.arguments.nodes[0], DYN)
|
||||
: dynUndefinedExpr(loc);
|
||||
const load: IrExpr = {
|
||||
kind: "libCall",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user