Link AbortController without the fetch unit (#2412)

This commit is contained in:
Chris Tate
2026-10-27 07:57:03 -05:00
committed by GitHub
18 changed files with 39 additions and 44 deletions
@@ -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,
},
{
+1
View File
@@ -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",