From e84d3322641859de7bbb7ca76f4b03bf6d996036 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Tue, 27 Oct 2026 07:05:47 -0500 Subject: [PATCH] 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. --- .../src/backend/executable-features.ts | 1 + .../compiler/src/backend/llvm/lib-shared.ts | 7 ------- .../src/backend/native-link-info-core.ts | 3 +++ .../compiler/src/backend/native/contracts.ts | 3 +++ .../compiler/src/backend/native/executable.ts | 6 +++++- .../compiler/src/backend/runtime-pack-core.ts | 6 ++++++ .../src/backend/target-diagnostics.test.ts | 2 +- .../src/backend/target-diagnostics.ts | 20 +------------------ .../src/executable/executable-cache.ts | 1 + .../src/frontend/lowering/lower-stdlib.ts | 10 +++++----- .../src/frontend/lowering/lower-stream.ts | 8 ++++---- packages/compiler/src/index.ts | 1 + packages/compiler/src/ir/builtin-effects.ts | 2 -- .../compiler/src/ir/builtin-signatures.ts | 4 ---- packages/compiler/src/ir/runtime-features.ts | 3 +++ .../compiler/src/native/sanitizer.test.ts | 2 ++ .../runtime-pack-matrix.mjs | 3 ++- .../runtime-wasm32-wasi/scripts/build.mjs | 1 + 18 files changed, 39 insertions(+), 44 deletions(-) diff --git a/packages/compiler/src/backend/executable-features.ts b/packages/compiler/src/backend/executable-features.ts index 1d95285b4..e43a64780 100644 --- a/packages/compiler/src/backend/executable-features.ts +++ b/packages/compiler/src/backend/executable-features.ts @@ -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 } : {}), diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 747c9c883..dc5ec61e0 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -1030,10 +1030,6 @@ export const LIB_FN_SYMS: Record = { "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([ "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", diff --git a/packages/compiler/src/backend/native-link-info-core.ts b/packages/compiler/src/backend/native-link-info-core.ts index a1ecc66a3..e467f39c4 100644 --- a/packages/compiler/src/backend/native-link-info-core.ts +++ b/packages/compiler/src/backend/native-link-info-core.ts @@ -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 diff --git a/packages/compiler/src/backend/native/contracts.ts b/packages/compiler/src/backend/native/contracts.ts index ee0533ce3..56b44e505 100644 --- a/packages/compiler/src/backend/native/contracts.ts +++ b/packages/compiler/src/backend/native/contracts.ts @@ -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: diff --git a/packages/compiler/src/backend/native/executable.ts b/packages/compiler/src/backend/native/executable.ts index 22e7cf2cb..60e61ff0b 100644 --- a/packages/compiler/src/backend/native/executable.ts +++ b/packages/compiler/src/backend/native/executable.ts @@ -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 diff --git a/packages/compiler/src/backend/runtime-pack-core.ts b/packages/compiler/src/backend/runtime-pack-core.ts index 1ac7e2718..589e3963f 100644 --- a/packages/compiler/src/backend/runtime-pack-core.ts +++ b/packages/compiler/src/backend/runtime-pack-core.ts @@ -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, diff --git a/packages/compiler/src/backend/target-diagnostics.test.ts b/packages/compiler/src/backend/target-diagnostics.test.ts index e1b344f37..d0bef0cf7 100644 --- a/packages/compiler/src/backend/target-diagnostics.test.ts +++ b/packages/compiler/src/backend/target-diagnostics.test.ts @@ -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(); diff --git a/packages/compiler/src/backend/target-diagnostics.ts b/packages/compiler/src/backend/target-diagnostics.ts index 236ce12cf..e98b671b2 100644 --- a/packages/compiler/src/backend/target-diagnostics.ts +++ b/packages/compiler/src/backend/target-diagnostics.ts @@ -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])); diff --git a/packages/compiler/src/executable/executable-cache.ts b/packages/compiler/src/executable/executable-cache.ts index 737a78b98..4bd6a2b4a 100644 --- a/packages/compiler/src/executable/executable-cache.ts +++ b/packages/compiler/src/executable/executable-cache.ts @@ -81,6 +81,7 @@ export interface EarlyExecutableNativeFeatures { dynForAwait?: boolean; eventsOnceSignal?: boolean; mime?: boolean; + abortSignal?: boolean; utilTiming?: boolean; dynBuffer?: boolean; builtinValues?: boolean; diff --git a/packages/compiler/src/frontend/lowering/lower-stdlib.ts b/packages/compiler/src/frontend/lowering/lower-stdlib.ts index fe748bb93..068e3a1dc 100644 --- a/packages/compiler/src/frontend/lowering/lower-stdlib.ts +++ b/packages/compiler/src/frontend/lowering/lower-stdlib.ts @@ -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, diff --git a/packages/compiler/src/frontend/lowering/lower-stream.ts b/packages/compiler/src/frontend/lowering/lower-stream.ts index 2ae952aaa..d8c370511 100644 --- a/packages/compiler/src/frontend/lowering/lower-stream.ts +++ b/packages/compiler/src/frontend/lowering/lower-stream.ts @@ -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, }, { diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index a41a6b4c7..e81e4353a 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -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 } : {}), diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index a0d75c864..e97d89419 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -151,8 +151,6 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = new Set([ "fetch.headersNew", "fetch.responseNew", "fetch.responseJsonStatic", - "fetch.abortTimeout", - "fetch.abortAny", "abort.timeout", "abort.any", "event.new", diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index e50e61a92..784d08216 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -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 diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 0fb4ce815..99610eaf6 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -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, diff --git a/packages/compiler/src/native/sanitizer.test.ts b/packages/compiler/src/native/sanitizer.test.ts index c4b1bd651..c44cc239d 100644 --- a/packages/compiler/src/native/sanitizer.test.ts +++ b/packages/compiler/src/native/sanitizer.test.ts @@ -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, diff --git a/packages/runtime-pack-common/runtime-pack-matrix.mjs b/packages/runtime-pack-common/runtime-pack-matrix.mjs index 0f2a69c49..fae696d1b 100644 --- a/packages/runtime-pack-common/runtime-pack-matrix.mjs +++ b/packages/runtime-pack-common/runtime-pack-matrix.mjs @@ -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")], ]; diff --git a/packages/runtime-wasm32-wasi/scripts/build.mjs b/packages/runtime-wasm32-wasi/scripts/build.mjs index 2a2766f4b..285be6641 100644 --- a/packages/runtime-wasm32-wasi/scripts/build.mjs +++ b/packages/runtime-wasm32-wasi/scripts/build.mjs @@ -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",