mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-03 07:54:40 +08:00
Link AbortController without the fetch unit
AbortController and AbortSignal lowered to fetch.abort* entry points, so a program that only used them linked the fetch unit with its socket, HTTP, TLS and zlib dependencies. They now lower to the abort.* entry points on every target, a new abortSignal feature links scr_abort.c (the fetch unit's signal implementation built without its transport, which WASI already ships) and the EventTarget unit, and programs that also fetch keep the fetch unit's copy.
This commit is contained in:
@@ -36,6 +36,7 @@ export function executableLinkFeatures(mod: IrModule, largePages = false): Nativ
|
||||
...(features.dynForAwait ? { dynForAwait: true } : {}),
|
||||
...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}),
|
||||
...(features.mime ? { mime: true } : {}),
|
||||
...(features.abortSignal ? { abortSignal: true } : {}),
|
||||
...(features.utilTiming ? { utilTiming: true } : {}),
|
||||
...(features.dynBuffer ? { dynBuffer: true } : {}),
|
||||
...(features.builtinValues ? { builtinValues: true } : {}),
|
||||
|
||||
@@ -1030,10 +1030,6 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"fetch.responseJson": "scr_fetch_response_json",
|
||||
"fetch.responseText": "scr_fetch_response_text",
|
||||
"fetch.responseBytes": "scr_fetch_response_bytes",
|
||||
"fetch.abortControllerNew": "scr_fetch_abort_controller_new",
|
||||
"fetch.abortTimeout": "scr_fetch_abort_timeout",
|
||||
"fetch.abortNow": "scr_fetch_abort_now",
|
||||
"fetch.abortAny": "scr_fetch_abort_any",
|
||||
"fetch.webIs": "scr_fetch_web_is",
|
||||
"fetch.streamIs": "scr_fetch_stream_is",
|
||||
"zlib.streamIs": "scr_zlib_stream_is",
|
||||
@@ -1838,14 +1834,11 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
|
||||
"fetch.input",
|
||||
"fetch.function",
|
||||
"fetch.requestNew",
|
||||
"fetch.abortTimeout",
|
||||
"abort.timeout",
|
||||
// EventTarget listeners report their exceptions from a nextTick.
|
||||
"event.targetNew",
|
||||
"abort.controllerNew",
|
||||
"abort.any",
|
||||
"fetch.abortControllerNew",
|
||||
"fetch.abortAny",
|
||||
"fetch.streamNew",
|
||||
"fetch.streamFrom",
|
||||
"fetch.transformNew",
|
||||
|
||||
@@ -67,6 +67,9 @@ export interface NativeLinkFeatures {
|
||||
/** `--large-pages`: scr_text_pages.c, which moves the executable's code
|
||||
* onto transparent huge pages at startup. Absent (false) without it. */
|
||||
largePages?: boolean;
|
||||
/** AbortController and AbortSignal: scr_abort.c, unless the program
|
||||
* links the fetch unit, which has them. */
|
||||
abortSignal?: boolean;
|
||||
/** util.debounce and util.throttle: scr_util_timing.c. */
|
||||
utilTiming?: boolean;
|
||||
/** Buffer.prototype methods called on checked-dynamic values link
|
||||
|
||||
@@ -131,6 +131,9 @@ export interface CcOptions {
|
||||
mime?: boolean;
|
||||
/** `--large-pages`: compiles scr_text_pages.c. */
|
||||
largePages?: boolean;
|
||||
/** AbortController and AbortSignal: compiles scr_abort.c, unless the
|
||||
* program compiles the fetch unit, which has them. */
|
||||
abortSignal?: boolean;
|
||||
/** util.debounce and util.throttle: compiles scr_util_timing.c. */
|
||||
utilTiming?: boolean;
|
||||
/** Buffer.prototype methods called on checked-dynamic values:
|
||||
|
||||
@@ -195,7 +195,10 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
|
||||
unit((u) => u.opts.computedRequire === true, "scr_require.c"),
|
||||
unit((u) => u.opts.dc, "scr_dc.c"),
|
||||
unit((u) => u.opts.dynTier, "scr_js_value.c", "scr_js_object.c"),
|
||||
unit((u) => u.opts.eventTarget || u.nativeFetch, "scr_event_target.c"),
|
||||
unit(
|
||||
(u) => u.opts.eventTarget || u.nativeFetch || u.opts.abortSignal === true,
|
||||
"scr_event_target.c",
|
||||
),
|
||||
unit((u) => u.opts.wasm, "scr_wasm.c"),
|
||||
// Module namespace objects as checked-dynamic values.
|
||||
unit((u) => u.opts.moduleNamespace === true, "scr_module_namespace.c"),
|
||||
@@ -248,6 +251,7 @@ const RUNTIME_UNITS: readonly RuntimeUnit[] = [
|
||||
unit((u) => u.opts.eventsOnceSignal === true, "scr_events_once_signal.c"),
|
||||
unit((u) => u.opts.mime === true, "scr_mime.c"),
|
||||
unit((u) => u.opts.largePages === true, "scr_text_pages.c"),
|
||||
unit((u) => u.opts.abortSignal === true && !u.nativeFetch, "scr_abort.c"),
|
||||
unit((u) => u.opts.utilTiming === true, "scr_util_timing.c"),
|
||||
// The checked-dynamic HANDLE support unit (listener gate + runtime
|
||||
// adapter closures): every referencing unit is one of the emitter or
|
||||
|
||||
@@ -92,6 +92,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures {
|
||||
eventsOnceSignal: boolean;
|
||||
mime: boolean;
|
||||
largePages: boolean;
|
||||
abortSignal: boolean;
|
||||
utilTiming: boolean;
|
||||
dynBuffer: boolean;
|
||||
builtinValues: boolean;
|
||||
@@ -101,6 +102,7 @@ export interface RuntimeFeatureSet extends NativeLinkFeatures {
|
||||
brotli: boolean;
|
||||
moduleNamespace: boolean;
|
||||
nativeFetch: boolean;
|
||||
abortSignalOnly: boolean;
|
||||
netEffective: boolean;
|
||||
httpEffective: boolean;
|
||||
tlsEffective: boolean;
|
||||
@@ -297,6 +299,7 @@ export function effectiveRuntimeFeatures(
|
||||
eventsOnceSignal: features.eventsOnceSignal === true,
|
||||
mime: features.mime === true,
|
||||
largePages: features.largePages === true,
|
||||
abortSignal: features.abortSignal === true,
|
||||
utilTiming: features.utilTiming === true,
|
||||
dynBuffer: features.dynBuffer === true,
|
||||
builtinValues: features.builtinValues === true,
|
||||
@@ -306,6 +309,9 @@ export function effectiveRuntimeFeatures(
|
||||
brotli: features.brotli === true,
|
||||
moduleNamespace: features.moduleNamespace === true,
|
||||
nativeFetch,
|
||||
// The fetch unit has AbortController and AbortSignal; scr_abort.c is
|
||||
// the same implementation without the fetch transport.
|
||||
abortSignalOnly: features.abortSignal === true && !nativeFetch,
|
||||
netEffective,
|
||||
httpEffective,
|
||||
tlsEffective,
|
||||
|
||||
@@ -117,7 +117,7 @@ test("prototype tables store optional methods without invoking their host capabi
|
||||
test("local abort signals remain available without a reflection dispatcher", () => {
|
||||
const mod = module();
|
||||
mod.functions = [
|
||||
fn("%main", [{ kind: "libCall", fn: "fetch.abortControllerNew", args: [], type: DYN, loc }]),
|
||||
fn("%main", [{ kind: "libCall", fn: "abort.controllerNew", args: [], type: DYN, loc }]),
|
||||
];
|
||||
fenceSpeculativeWasiFunctions(mod);
|
||||
expect(moduleWasiUnavailableSurface(mod)).toBeNull();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { everyModuleNode, everyStmtList, transformStmtList } from "../ir/traverse.js";
|
||||
import { everyModuleNode, everyStmtList } from "../ir/traverse.js";
|
||||
import type { LlvmUnsupportedError } from "./llvm/emitter.js";
|
||||
import type { ScrDiagnostic } from "../diagnostics/diagnostic.js";
|
||||
import { type IrModule, type SrcLoc } from "../ir/ir.js";
|
||||
@@ -14,24 +14,6 @@ import {
|
||||
* calls, escaped callables, constructors, and exported roots still retain
|
||||
* the ordinary target diagnostic. ABI types remain checked by the gate. */
|
||||
export function fenceSpeculativeWasiFunctions(mod: IrModule): void {
|
||||
for (const fn of mod.functions)
|
||||
fn.body = transformStmtList(fn.body, {
|
||||
stmt: (statement) => statement,
|
||||
expr: (expr) => {
|
||||
if (expr.kind !== "libCall") return expr;
|
||||
const portable =
|
||||
expr.fn === "fetch.abortControllerNew"
|
||||
? "abort.controllerNew"
|
||||
: expr.fn === "fetch.abortTimeout"
|
||||
? "abort.timeout"
|
||||
: expr.fn === "fetch.abortNow"
|
||||
? "abort.now"
|
||||
: expr.fn === "fetch.abortAny"
|
||||
? "abort.any"
|
||||
: null;
|
||||
return portable ? { ...expr, fn: portable } : expr;
|
||||
},
|
||||
});
|
||||
if (!mod.functions.some((fn) => fn.speculativeDispatch)) return;
|
||||
const functions = new Map(mod.functions.map((fn) => [fn.name, fn]));
|
||||
const classes = new Map((mod.classes ?? []).map((info) => [info.name, info]));
|
||||
|
||||
@@ -81,6 +81,7 @@ export interface EarlyExecutableNativeFeatures {
|
||||
dynForAwait?: boolean;
|
||||
eventsOnceSignal?: boolean;
|
||||
mime?: boolean;
|
||||
abortSignal?: boolean;
|
||||
utilTiming?: boolean;
|
||||
dynBuffer?: boolean;
|
||||
builtinValues?: boolean;
|
||||
|
||||
@@ -1500,7 +1500,7 @@ export function lowerStaticFetchCompanionCall(
|
||||
: dynUndefinedExpr(loc),
|
||||
(first) => ({
|
||||
kind: "libCall",
|
||||
fn: "fetch.abortTimeout",
|
||||
fn: "abort.timeout",
|
||||
args: [first],
|
||||
type: DYN,
|
||||
loc,
|
||||
@@ -1510,7 +1510,7 @@ export function lowerStaticFetchCompanionCall(
|
||||
if (call.arguments.nodes.length === 0) {
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "fetch.abortNow",
|
||||
fn: "abort.now",
|
||||
args: [dynUndefinedExpr(loc)],
|
||||
type: DYN,
|
||||
loc,
|
||||
@@ -1523,7 +1523,7 @@ export function lowerStaticFetchCompanionCall(
|
||||
lowerLiveWebValue(lowerer, call.arguments.nodes[0]!),
|
||||
(first) => ({
|
||||
kind: "libCall",
|
||||
fn: "fetch.abortNow",
|
||||
fn: "abort.now",
|
||||
args: [first],
|
||||
type: DYN,
|
||||
loc,
|
||||
@@ -1541,7 +1541,7 @@ export function lowerStaticFetchCompanionCall(
|
||||
: dynUndefinedExpr(loc),
|
||||
(first) => ({
|
||||
kind: "libCall",
|
||||
fn: "fetch.abortAny",
|
||||
fn: "abort.any",
|
||||
args: [first],
|
||||
type: DYN,
|
||||
loc,
|
||||
@@ -1886,7 +1886,7 @@ export function lowerAbortControllerNew(lowerer: Lowerer, expr: ts.NewExpression
|
||||
|
||||
const result: IrExpr = {
|
||||
kind: "libCall",
|
||||
fn: "fetch.abortControllerNew",
|
||||
fn: "abort.controllerNew",
|
||||
args: [],
|
||||
type: DYN,
|
||||
loc,
|
||||
|
||||
@@ -5176,7 +5176,7 @@ function lowerPipelineStages(
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: ac.id,
|
||||
init: { kind: "libCall", fn: "fetch.abortControllerNew", args: [], type: DYN, loc },
|
||||
init: { kind: "libCall", fn: "abort.controllerNew", args: [], type: DYN, loc },
|
||||
loc,
|
||||
},
|
||||
],
|
||||
@@ -5502,7 +5502,7 @@ function duplexFromBody(lowerer: Lowerer, node: ts.Expression, loc: SrcLoc): IrE
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: acLocal.id,
|
||||
init: { kind: "libCall", fn: "fetch.abortControllerNew", args: [], type: DYN, loc },
|
||||
init: { kind: "libCall", fn: "abort.controllerNew", args: [], type: DYN, loc },
|
||||
loc,
|
||||
},
|
||||
];
|
||||
@@ -5954,7 +5954,7 @@ export function operatorSignalOptions(lowerer: Lowerer, options: IrExpr, loc: Sr
|
||||
type: DYN,
|
||||
loc,
|
||||
};
|
||||
const signal: IrExpr = { kind: "libCall", fn: "fetch.abortAny", args: [list], type: DYN, loc };
|
||||
const signal: IrExpr = { kind: "libCall", fn: "abort.any", args: [list], type: DYN, loc };
|
||||
return { kind: "libCall", fn: "readable.opSignalOpts", args: [signal], type: DYN, loc };
|
||||
}
|
||||
|
||||
@@ -5978,7 +5978,7 @@ export function reduceSignalSetup(
|
||||
{
|
||||
kind: "varDecl",
|
||||
localId: acLocal.id,
|
||||
init: { kind: "libCall", fn: "fetch.abortControllerNew", args: [], type: DYN, loc },
|
||||
init: { kind: "libCall", fn: "abort.controllerNew", args: [], type: DYN, loc },
|
||||
loc,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -702,6 +702,7 @@ async function compileExecutableNative(
|
||||
...(features.eventsOnceSignal ? { eventsOnceSignal: true } : {}),
|
||||
...(features.mime ? { mime: true } : {}),
|
||||
...(features.largePages ? { largePages: true } : {}),
|
||||
...(features.abortSignal ? { abortSignal: true } : {}),
|
||||
...(features.utilTiming ? { utilTiming: true } : {}),
|
||||
...(features.dynBuffer ? { dynBuffer: true } : {}),
|
||||
...(features.builtinValues ? { builtinValues: true } : {}),
|
||||
|
||||
@@ -151,8 +151,6 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"fetch.headersNew",
|
||||
"fetch.responseNew",
|
||||
"fetch.responseJsonStatic",
|
||||
"fetch.abortTimeout",
|
||||
"fetch.abortAny",
|
||||
"abort.timeout",
|
||||
"abort.any",
|
||||
"event.new",
|
||||
|
||||
@@ -159,10 +159,6 @@ function buildLibFnSignatures() {
|
||||
"fetch.responseJson": { argTypes: [DYN], result: { kind: "promise", inner: DYN } },
|
||||
"fetch.responseText": { argTypes: [DYN], result: { kind: "promise", inner: STRING } },
|
||||
"fetch.responseBytes": { argTypes: [DYN], result: { kind: "promise", inner: BYTES_U8 } },
|
||||
"fetch.abortControllerNew": { argTypes: [], result: DYN },
|
||||
"fetch.abortTimeout": { argTypes: [DYN], result: DYN },
|
||||
"fetch.abortNow": { argTypes: [DYN], result: DYN },
|
||||
"fetch.abortAny": { argTypes: [DYN], result: DYN },
|
||||
"fetch.streamNew": { argTypes: [DYN], result: DYN },
|
||||
"fetch.transformNew": { argTypes: [], result: DYN },
|
||||
// Program-dependent iterable: typed arrays/bytes/string stay intact so
|
||||
|
||||
@@ -1033,6 +1033,8 @@ const RUNTIME_FEATURE_RULES = {
|
||||
}),
|
||||
/** util.MIMEType and util.MIMEParams (scr_mime.c). */
|
||||
mime: rule({ prefixes: ["mime."] }),
|
||||
/** AbortController and AbortSignal (scr_abort.c, or the fetch unit). */
|
||||
abortSignal: rule({ prefixes: ["abort."] }),
|
||||
/** util.debounce and util.throttle (scr_util_timing.c). */
|
||||
utilTiming: rule({ fns: ["util.debounce", "util.throttle"] }),
|
||||
/** events.once(emitter, name, { signal }) (scr_events_once_signal.c). */
|
||||
@@ -1254,6 +1256,7 @@ function noRuntimeFeatures(): RuntimeFeatures {
|
||||
inspect: false,
|
||||
legacyTextDecoder: false,
|
||||
mime: false,
|
||||
abortSignal: false,
|
||||
utilTiming: false,
|
||||
mockTimers: false,
|
||||
net: false,
|
||||
|
||||
@@ -23,6 +23,7 @@ const features = {
|
||||
eventsOnceSignal: false,
|
||||
mime: false,
|
||||
largePages: false,
|
||||
abortSignal: false,
|
||||
utilTiming: false,
|
||||
dynBuffer: false,
|
||||
builtinValues: false,
|
||||
@@ -74,6 +75,7 @@ const features = {
|
||||
webcrypto: false,
|
||||
perfLoop: false,
|
||||
nativeFetch: false,
|
||||
abortSignalOnly: false,
|
||||
netEffective: false,
|
||||
httpEffective: false,
|
||||
tlsEffective: false,
|
||||
|
||||
@@ -80,7 +80,7 @@ export const OPTIONAL_RUNTIME_SOURCES = [
|
||||
// programs whose IR has dynamic-tier operations.
|
||||
["scr_js_value.c", "dynTier"],
|
||||
["scr_js_object.c", "dynTier"],
|
||||
["scr_event_target.c", any("eventTarget", "nativeFetch")],
|
||||
["scr_event_target.c", any("eventTarget", "nativeFetch", "abortSignal")],
|
||||
["scr_async_dyn.c", any("dynAsync", "dynInvoke", "dc", "fileHandle", "nativeFetch")],
|
||||
// The crypto global's object extends scr_async_dyn.c's crypto object.
|
||||
["scr_webcrypto_object.c", all("webcrypto", "dynAsync")],
|
||||
@@ -184,6 +184,7 @@ export const OPTIONAL_RUNTIME_SOURCES = [
|
||||
["scr_tls_x509.c", all("tlsEffective", "x509")],
|
||||
// WebCrypto's AES ciphers run over the cipher feature's mbedTLS archive.
|
||||
["scr_webcrypto_aes.c", all("cipher", "webcrypto")],
|
||||
["scr_abort.c", "abortSignalOnly"],
|
||||
["scr_fetch.c", "nativeFetch"],
|
||||
["scr_stream_from_web.c", all("stream", "nativeFetch")],
|
||||
];
|
||||
|
||||
@@ -8,6 +8,7 @@ process.env.SCRIPTC_RUNTIME_PACK_CONFIG = JSON.stringify({
|
||||
runtimeDefines: ["_GNU_SOURCE", "_WASI_EMULATED_SIGNAL", "_WASI_EMULATED_PROCESS_CLOCKS"],
|
||||
omitRuntimeSources: ["scr_child.c"],
|
||||
omitOptionalSources: [
|
||||
"scr_abort.c",
|
||||
"scr_event_target.c",
|
||||
"scr_loop_kqueue.c",
|
||||
"scr_loop_epoll.c",
|
||||
|
||||
Reference in New Issue
Block a user