mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
The native compiler ran four large module initializers at every startup: the builtin call signatures (LIB_FN_SIGS), the Node 24 fetch compatibility profile, and the builtin module function and ambient surface tables. Together their init functions are about 1.3 MB of code, and --version and warm cache-hit builds paged them in and built the tables without reading them. Each table is now built by a function on its first call and kept in a module variable: - libFnSignatures() replaces LIB_FN_SIGS - node24FetchCompatProfile() and staticFetchMembers() replace NODE24_FETCH_COMPAT_PROFILE and the STATIC_* member sets - builtinModuleFns() and ambientSurfaceFns() replace BUILTIN_MODULE_FNS and AMBIENT_SURFACE_FNS The @scriptc/compiler entry point keeps LIB_FN_SIGS and NODE24_FETCH_COMPAT_PROFILE as deprecated constants built from the new functions when the entry module loads. The native compiler does not load that module. The surface manifest's coverage notes read the fetch target through the profile function, so building the manifest module no longer builds the profile. The table contents are unchanged.
444 lines
12 KiB
TypeScript
444 lines
12 KiB
TypeScript
import type { FetchCompatProfile } from "../../packages/compiler/src/compat/fetch-profile.js";
|
|
|
|
export const FETCH_CONFORMANCE_SEED = 0x24_15_07_24;
|
|
|
|
export const FETCH_CONFORMANCE_SCENARIOS = [
|
|
"abort-events",
|
|
"stream-traces",
|
|
"webidl-operations",
|
|
] as const;
|
|
|
|
type GeneratedScenario = (typeof FETCH_CONFORMANCE_SCENARIOS)[number];
|
|
|
|
class XorShift32 {
|
|
constructor(private state: number) {
|
|
if (state === 0) this.state = 0x9e3779b9;
|
|
}
|
|
|
|
next(): number {
|
|
let value = this.state | 0;
|
|
value ^= value << 13;
|
|
value ^= value >>> 17;
|
|
value ^= value << 5;
|
|
this.state = value | 0;
|
|
return value >>> 0;
|
|
}
|
|
|
|
pick(size: number): number {
|
|
return this.next() % size;
|
|
}
|
|
}
|
|
|
|
function webIdlOperations(): string {
|
|
return String.raw`
|
|
let webIdlEffects = "";
|
|
function webIdlEffect(label) {
|
|
webIdlEffects += label;
|
|
return "ignored";
|
|
}
|
|
|
|
const abortReason = JSON.parse('{"value":"before"}');
|
|
const abortedSignal = AbortSignal.abort(
|
|
abortReason,
|
|
webIdlEffect("abort "),
|
|
);
|
|
abortReason.value = "after";
|
|
console.log(
|
|
"webidl abort:",
|
|
webIdlEffects,
|
|
abortedSignal.aborted,
|
|
abortedSignal.reason.value,
|
|
);
|
|
|
|
const combinedSignal = AbortSignal.any(
|
|
[abortedSignal],
|
|
webIdlEffect("any "),
|
|
);
|
|
console.log(
|
|
"webidl any:",
|
|
webIdlEffects,
|
|
combinedSignal.aborted,
|
|
combinedSignal.reason.value,
|
|
);
|
|
|
|
try {
|
|
combinedSignal.throwIfAborted();
|
|
console.log("webidl throwIfAborted: no-error");
|
|
} catch (error) {
|
|
console.log(
|
|
"webidl throwIfAborted:",
|
|
error.value,
|
|
);
|
|
}
|
|
|
|
webIdlEffects = "";
|
|
try {
|
|
AbortSignal.timeout(
|
|
JSON.parse('{"delay":1}'),
|
|
webIdlEffect("timeout-surplus"),
|
|
);
|
|
console.log("webidl invalid timeout: no-error");
|
|
} catch (error) {
|
|
console.log(
|
|
"webidl invalid timeout:",
|
|
webIdlEffects,
|
|
error.name,
|
|
error.code || "no-code",
|
|
error.message,
|
|
);
|
|
}
|
|
|
|
try {
|
|
AbortSignal.timeout();
|
|
console.log("webidl missing timeout: no-error");
|
|
} catch (error) {
|
|
console.log(
|
|
"webidl missing timeout:",
|
|
error.name,
|
|
error.code || "no-code",
|
|
error.message,
|
|
);
|
|
}
|
|
|
|
try {
|
|
AbortSignal.any();
|
|
console.log("webidl missing any: no-error");
|
|
} catch (error) {
|
|
console.log(
|
|
"webidl missing any:",
|
|
error.name,
|
|
error.code || "no-code",
|
|
error.message,
|
|
);
|
|
}
|
|
|
|
try {
|
|
AbortSignal.any(JSON.parse('{"not":"a sequence"}'));
|
|
console.log("webidl invalid any: no-error");
|
|
} catch (error) {
|
|
console.log(
|
|
"webidl invalid any:",
|
|
error.name,
|
|
error.code || "no-code",
|
|
error.message,
|
|
);
|
|
}
|
|
|
|
try {
|
|
ReadableStream.from();
|
|
console.log("webidl missing from: no-error");
|
|
} catch (error) {
|
|
console.log(
|
|
"webidl missing from:",
|
|
error.name,
|
|
error.code || "no-code",
|
|
error.message,
|
|
);
|
|
}
|
|
|
|
const fromItem = { value: "before" };
|
|
const fromItems = [fromItem];
|
|
webIdlEffects = "";
|
|
const fromStream = ReadableStream.from(
|
|
fromItems,
|
|
webIdlEffect("from-surplus"),
|
|
);
|
|
fromItems[0].value = "after";
|
|
const fromReader = fromStream.getReader();
|
|
const fromPart = await fromReader.read();
|
|
const fromDone = await fromReader.read();
|
|
console.log(
|
|
"webidl from:",
|
|
webIdlEffects,
|
|
fromPart.done,
|
|
fromPart.done ? "done" : fromPart.value.value,
|
|
fromDone.done,
|
|
fromStream.locked,
|
|
);
|
|
await fromReader.closed;
|
|
`;
|
|
}
|
|
|
|
function abortEvents(): string {
|
|
return String.raw`
|
|
let abortEventOrder = "";
|
|
let abortEventNameEffects = "";
|
|
const eventSignal = AbortSignal.timeout(0);
|
|
|
|
function abortEventNameToString() {
|
|
abortEventNameEffects += this.operation;
|
|
return "abort";
|
|
}
|
|
|
|
function keptAbortListener(event) {
|
|
abortEventOrder +=
|
|
this === eventSignal &&
|
|
event.target === eventSignal &&
|
|
event.currentTarget === eventSignal
|
|
? "listener-this "
|
|
: "listener-wrong ";
|
|
}
|
|
|
|
function removedAbortListener() {
|
|
abortEventOrder += "removed ";
|
|
}
|
|
|
|
function onAbortListener(event) {
|
|
abortEventOrder +=
|
|
this === eventSignal && event.target === eventSignal
|
|
? "onabort-this"
|
|
: "onabort-wrong";
|
|
}
|
|
|
|
eventSignal.addEventListener(
|
|
{ operation: "add ", toString: abortEventNameToString },
|
|
keptAbortListener,
|
|
);
|
|
eventSignal.addEventListener("abort", removedAbortListener);
|
|
eventSignal.removeEventListener(
|
|
{ operation: "remove", toString: abortEventNameToString },
|
|
removedAbortListener,
|
|
);
|
|
eventSignal.onabort = onAbortListener;
|
|
console.log("abort events before:", eventSignal.aborted);
|
|
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
console.log(
|
|
"abort events after:",
|
|
abortEventNameEffects,
|
|
abortEventOrder,
|
|
eventSignal.aborted,
|
|
);
|
|
`;
|
|
}
|
|
|
|
interface TraceState {
|
|
locked: boolean;
|
|
reader: string;
|
|
readerSerial: number;
|
|
reads: number;
|
|
terminal: boolean;
|
|
}
|
|
|
|
function acquireReader(lines: string[], trace: number, state: TraceState): void {
|
|
state.readerSerial++;
|
|
state.reader = `trace${trace}Reader${state.readerSerial}`;
|
|
lines.push(`const ${state.reader} = trace${trace}Stream.getReader();`);
|
|
lines.push(
|
|
`console.log("trace ${trace} acquire ${state.readerSerial}:", trace${trace}Stream.locked);`,
|
|
);
|
|
state.locked = true;
|
|
}
|
|
|
|
function readOnce(lines: string[], trace: number, state: TraceState): void {
|
|
const part = `trace${trace}Part${state.reads}`;
|
|
lines.push(`const ${part} = await ${state.reader}.read();`);
|
|
lines.push(
|
|
`console.log("trace ${trace} read ${state.reads}:", ${part}.done, ${part}.done ? "done" : ${part}.value);`,
|
|
);
|
|
state.reads++;
|
|
}
|
|
|
|
function releaseReader(lines: string[], trace: number, state: TraceState): void {
|
|
lines.push(`${state.reader}.releaseLock();`);
|
|
lines.push(
|
|
`console.log("trace ${trace} release ${state.readerSerial}:", trace${trace}Stream.locked);`,
|
|
);
|
|
state.locked = false;
|
|
}
|
|
|
|
function oneStreamTrace(rng: XorShift32, trace: number): string {
|
|
const startChunk = rng.pick(2) === 0;
|
|
const chunks = 2 + rng.pick(3);
|
|
const pullChunks = chunks - (startChunk ? 1 : 0);
|
|
const lines: string[] = [
|
|
`let trace${trace}Pulls = 0;`,
|
|
`let trace${trace}Cancel = "none";`,
|
|
`function trace${trace}Start(controller) {`,
|
|
` console.log("trace ${trace} start:", this.marker, controller.desiredSize);`,
|
|
...(startChunk
|
|
? [
|
|
` controller.enqueue("trace-${trace}-start");`,
|
|
` console.log("trace ${trace} start enqueue:", controller.desiredSize);`,
|
|
]
|
|
: []),
|
|
`}`,
|
|
`function trace${trace}Pull(controller) {`,
|
|
` trace${trace}Pulls++;`,
|
|
` console.log("trace ${trace} pull:", this.marker, trace${trace}Pulls, controller.desiredSize);`,
|
|
` if (trace${trace}Pulls <= ${pullChunks}) {`,
|
|
` controller.enqueue("trace-${trace}-pull-" + trace${trace}Pulls);`,
|
|
` }`,
|
|
` if (trace${trace}Pulls === ${pullChunks}) controller.close();`,
|
|
`}`,
|
|
`function trace${trace}CancelFn(reason) {`,
|
|
` trace${trace}Cancel = reason;`,
|
|
` console.log("trace ${trace} cancel callback:", this.marker, reason);`,
|
|
`}`,
|
|
`const trace${trace}Source = {`,
|
|
` marker: "source-${trace}",`,
|
|
` start: trace${trace}Start,`,
|
|
` pull: trace${trace}Pull,`,
|
|
` cancel: trace${trace}CancelFn,`,
|
|
`};`,
|
|
`const trace${trace}Stream = new ReadableStream(trace${trace}Source);`,
|
|
];
|
|
const state: TraceState = {
|
|
locked: false,
|
|
reader: "",
|
|
readerSerial: 0,
|
|
reads: 0,
|
|
terminal: false,
|
|
};
|
|
|
|
acquireReader(lines, trace, state);
|
|
readOnce(lines, trace, state);
|
|
|
|
// Each residue class forces one important transition; the remaining
|
|
// steps are seeded model choices. This keeps coverage stable while still
|
|
// exploring different valid traces when the seed or count changes.
|
|
if (trace % 4 === 1) {
|
|
releaseReader(lines, trace, state);
|
|
acquireReader(lines, trace, state);
|
|
} else if (trace % 4 === 2) {
|
|
lines.push(`await ${state.reader}.cancel("reader-cancel-${trace}");`);
|
|
lines.push(`console.log("trace ${trace} reader cancel:", trace${trace}Stream.locked);`);
|
|
state.terminal = true;
|
|
} else if (trace % 4 === 3) {
|
|
releaseReader(lines, trace, state);
|
|
lines.push(`await trace${trace}Stream.cancel("stream-cancel-${trace}");`);
|
|
lines.push(`console.log("trace ${trace} stream cancel:", trace${trace}Stream.locked);`);
|
|
state.terminal = true;
|
|
}
|
|
|
|
const randomSteps = 2 + rng.pick(5);
|
|
for (let step = 0; step < randomSteps && !state.terminal; step++) {
|
|
if (!state.locked) {
|
|
if (rng.pick(3) === 0) {
|
|
lines.push(`await trace${trace}Stream.cancel("random-cancel-${trace}-${step}");`);
|
|
lines.push(
|
|
`console.log("trace ${trace} random stream cancel ${step}:", trace${trace}Stream.locked);`,
|
|
);
|
|
state.terminal = true;
|
|
} else {
|
|
acquireReader(lines, trace, state);
|
|
}
|
|
continue;
|
|
}
|
|
switch (rng.pick(4)) {
|
|
case 0:
|
|
case 1:
|
|
readOnce(lines, trace, state);
|
|
if (state.reads >= chunks) state.terminal = true;
|
|
break;
|
|
case 2:
|
|
releaseReader(lines, trace, state);
|
|
break;
|
|
default:
|
|
lines.push(`await ${state.reader}.cancel("random-reader-cancel-${trace}-${step}");`);
|
|
lines.push(
|
|
`console.log("trace ${trace} random reader cancel ${step}:", trace${trace}Stream.locked);`,
|
|
);
|
|
state.terminal = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!state.locked) acquireReader(lines, trace, state);
|
|
if (!state.terminal) {
|
|
while (state.reads <= chunks) readOnce(lines, trace, state);
|
|
} else {
|
|
readOnce(lines, trace, state);
|
|
}
|
|
lines.push(`await ${state.reader}.closed;`);
|
|
lines.push(
|
|
`console.log("trace ${trace} final:", trace${trace}Pulls, trace${trace}Cancel, trace${trace}Stream.locked);`,
|
|
);
|
|
return lines.join("\n");
|
|
}
|
|
|
|
function streamTraces(seed: number, traceCount: number): string {
|
|
const rng = new XorShift32(seed);
|
|
const traces: string[] = [];
|
|
for (let trace = 0; trace < traceCount; trace++) {
|
|
traces.push(`{\n${oneStreamTrace(rng, trace)}\n}`);
|
|
}
|
|
traces.push(String.raw`
|
|
{
|
|
const streamErrorReason = { marker: "stream-error" };
|
|
const erroredStream = new ReadableStream({
|
|
start(controller) {
|
|
console.log("stream error desired size:", controller.desiredSize);
|
|
controller.error(streamErrorReason);
|
|
console.log("stream error after:", controller.desiredSize);
|
|
},
|
|
});
|
|
const erroredReader = erroredStream.getReader();
|
|
try {
|
|
await erroredReader.read();
|
|
console.log("stream error read: no-error");
|
|
} catch (error) {
|
|
console.log("stream error read:", error.marker);
|
|
}
|
|
try {
|
|
await erroredReader.closed;
|
|
console.log("stream error closed: no-error");
|
|
} catch (error) {
|
|
console.log("stream error closed:", error.marker);
|
|
}
|
|
}
|
|
|
|
try {
|
|
new ReadableStream({
|
|
start(controller) {
|
|
controller.close();
|
|
controller.close();
|
|
},
|
|
});
|
|
console.log("stream double close: no-error");
|
|
} catch (error) {
|
|
console.log("stream double close:", error.name, error.message);
|
|
}
|
|
`);
|
|
return traces.join("\n");
|
|
}
|
|
|
|
const SCENARIO_GENERATORS: Record<GeneratedScenario, (seed: number, traceCount: number) => string> =
|
|
{
|
|
"abort-events": () => abortEvents(),
|
|
"stream-traces": (seed, traceCount) => streamTraces(seed, traceCount),
|
|
"webidl-operations": () => webIdlOperations(),
|
|
};
|
|
|
|
export function generatedScenarioIds(profile: FetchCompatProfile): string[] {
|
|
return [
|
|
...new Set(
|
|
profile.operations.flatMap((operation) =>
|
|
operation.evidence.flatMap((item) =>
|
|
item.generated === undefined ? [] : [item.generated],
|
|
),
|
|
),
|
|
),
|
|
].sort();
|
|
}
|
|
|
|
export function generateFetchConformanceProgram(
|
|
profile: FetchCompatProfile,
|
|
options: { seed?: number; traceCount?: number } = {},
|
|
): string {
|
|
const seed = options.seed ?? FETCH_CONFORMANCE_SEED;
|
|
const traceCount = options.traceCount ?? 12;
|
|
const scenarios = generatedScenarioIds(profile);
|
|
const sections: string[] = [
|
|
`// Generated from node24FetchCompatProfile(). Seed: 0x${seed.toString(16)}.`,
|
|
];
|
|
for (const scenario of scenarios) {
|
|
if (!Object.hasOwn(SCENARIO_GENERATORS, scenario)) {
|
|
throw new Error(`unknown generated fetch conformance scenario: ${scenario}`);
|
|
}
|
|
sections.push(`// scenario: ${scenario}`);
|
|
sections.push(SCENARIO_GENERATORS[scenario as GeneratedScenario](seed, traceCount));
|
|
}
|
|
sections.push("export {};", "");
|
|
return sections.join("\n");
|
|
}
|