@scriptc/compiler 0.0.31 → 0.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ambient/scriptc-node-fallback.d.ts +1 -1
- package/dist/backend/cc-cache.test.js +308 -1
- package/dist/backend/cc-cache.test.js.map +1 -1
- package/dist/backend/cc.d.ts +17 -5
- package/dist/backend/cc.js +745 -75
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +53 -2
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +12 -5
- package/dist/backend/emission/emitter.js +160 -22
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/emission/may-throw.js +7 -4
- package/dist/backend/emission/may-throw.js.map +1 -1
- package/dist/backend/ffi-callbacks.d.ts +38 -1
- package/dist/backend/ffi-callbacks.js +76 -3
- package/dist/backend/ffi-callbacks.js.map +1 -1
- package/dist/backend/llvm/emitter.js +243 -12
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/diagnostics/diagnostic.js +2 -0
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/ffi/profile.d.ts +17 -5
- package/dist/ffi/profile.js +169 -22
- package/dist/ffi/profile.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +1 -0
- package/dist/frontend/lowering/lower-calls.js +89 -23
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.js +65 -19
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +12 -0
- package/dist/frontend/lowering/lowerer.js +20 -1
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/ts7/checker.d.ts +2 -0
- package/dist/frontend/ts7/checker.js +20 -3
- package/dist/frontend/ts7/checker.js.map +1 -1
- package/dist/frontend/types.d.ts +8 -0
- package/dist/frontend/types.js +33 -5
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +22 -9
- package/dist/ir/nodes.js +7 -1
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/serialize.d.ts +1 -1
- package/dist/ir/serialize.js +1 -1
- package/dist/ir/validate.js +74 -1
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/int-infer.js +7 -1
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +73 -4
- package/dist/library/int-infer.test.js.map +1 -1
- package/dist/library/profile.d.ts +3 -0
- package/dist/library/profile.js +9 -1
- package/dist/library/profile.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +1 -1
|
@@ -61,9 +61,9 @@
|
|
|
61
61
|
* lazy-inflate representation as the C debugging backend.
|
|
62
62
|
*/
|
|
63
63
|
import { deflateRawSync } from "node:zlib";
|
|
64
|
-
import { canMarshalFuncIntoIsland, CAUGHT, DYN, F64, ffiCallbackType, islandCallbackRet, islandPromisePayloadTag, isFfiCallbackParam, isFfiContextParam, isRefCounted, isUnitType, MAY_THROW_LIB_FNS, moduleEmbedsBuiltin, moduleEmbedsCompressedNpm, moduleUsesDynInvoke, moduleUsesFetch, moduleUsesFsWatch, moduleUsesHttpServer, moduleUsesNet, moduleUsesNodeTest, moduleUsesProcessEvents, moduleUsesStream, moduleUsesTls, moduleUsesTlsCa, NPM_COMPRESS_MIN, RUNTIME_EMITTER_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, STRING, typeEquals, typeKey, VOID } from "../../ir/nodes.js";
|
|
64
|
+
import { canMarshalFuncIntoIsland, CAUGHT, DYN, F64, ffiCallbackType, islandCallbackRet, islandPromisePayloadTag, isFfiCallbackParam, isFfiContextParam, isFfiReleaseParam, isRefCounted, isUnitType, MAY_THROW_LIB_FNS, moduleEmbedsBuiltin, moduleEmbedsCompressedNpm, moduleUsesDynInvoke, moduleUsesFetch, moduleUsesFsWatch, moduleUsesHttpServer, moduleUsesNet, moduleUsesNodeTest, moduleUsesProcessEvents, moduleUsesStream, moduleUsesTls, moduleUsesTlsCa, NPM_COMPRESS_MIN, RUNTIME_EMITTER_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, STRING, typeEquals, typeKey, VOID } from "../../ir/nodes.js";
|
|
65
65
|
import { matchIntegerBytesForLoop } from "../../ir/integer-loops.js";
|
|
66
|
-
import { allocateFfiCallbackAdapters } from "../ffi-callbacks.js";
|
|
66
|
+
import { allocateFfiCallbackAdapters, collectFfiRetainedOps, hasForeignFfiCallback, hasRetainedFfiCallback, parseFfiCallbackKey } from "../ffi-callbacks.js";
|
|
67
67
|
import { computeMayThrow } from "../emission/may-throw.js";
|
|
68
68
|
import { mangleArgPack, mangleAsyncSpawn, mangleClassNew, mangleClassObj, mangleClassRetain, mangleFnClosure, mangleFunction, mangleGenDrop, mangleGenResThunk, mangleGenSpawn, mangleGlobal, mangleLocal, mangleRecordNew, mangleRecordStruct, mangleResolveThunk, mangleTrampoline, mangleVtStruct, mangleWrapper } from "../mangle.js";
|
|
69
69
|
import { BlockBuilder } from "./blocks.js";
|
|
@@ -81,6 +81,8 @@ function ffiNativeTypeLl(cls) {
|
|
|
81
81
|
case "u32":
|
|
82
82
|
case "i32":
|
|
83
83
|
return "i32";
|
|
84
|
+
case "cstring":
|
|
85
|
+
return "ptr";
|
|
84
86
|
case "string":
|
|
85
87
|
case "bytes":
|
|
86
88
|
throw new Error(`llvm emitter bug: span class '${cls}' has no scalar LLVM type`);
|
|
@@ -917,6 +919,12 @@ class LlEmitter {
|
|
|
917
919
|
/** C-ABI callback trampolines and (for raw/no-userdata callbacks) their
|
|
918
920
|
* distinct call-scoped TLS closure slots. */
|
|
919
921
|
ffiCallbackAdapters;
|
|
922
|
+
/** Module-level constant consulted per ffiCall: with a retained
|
|
923
|
+
* descriptor anywhere in the manifest, every native call is a
|
|
924
|
+
* pending-exception checkpoint (may-throw derives the same fact from
|
|
925
|
+
* the same helper). */
|
|
926
|
+
ffiHasRetainedCallback;
|
|
927
|
+
ffiHasForeignCallback;
|
|
920
928
|
globalTypes = new Map();
|
|
921
929
|
/** May-throw analysis (the C emitter's computeMayThrow, shared): pending
|
|
922
930
|
* checks are emitted only after calls that can actually raise. */
|
|
@@ -1019,6 +1027,8 @@ class LlEmitter {
|
|
|
1019
1027
|
// bytes behind a wasm32 object and 16 bytes behind a 64-bit object.
|
|
1020
1028
|
this.cycleColorOffset = options.pointerBits === 32 ? 12 : 16;
|
|
1021
1029
|
this.ffiCallbackAdapters = allocateFfiCallbackAdapters(mod.ffiImports ?? []);
|
|
1030
|
+
this.ffiHasRetainedCallback = hasRetainedFfiCallback(mod.ffiImports ?? []);
|
|
1031
|
+
this.ffiHasForeignCallback = hasForeignFfiCallback(mod.ffiImports ?? []);
|
|
1022
1032
|
for (const fn of mod.functions)
|
|
1023
1033
|
this.fnByName.set(fn.name, fn);
|
|
1024
1034
|
for (const entry of mod.ffiImports ?? []) {
|
|
@@ -1172,13 +1182,129 @@ class LlEmitter {
|
|
|
1172
1182
|
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
1173
1183
|
this.declare(`declare void @scr_trap(ptr)`);
|
|
1174
1184
|
const expired = this.cstr("scriptc: native callback invoked outside its call-scoped lifetime\n");
|
|
1185
|
+
const released = this.cstr("scriptc: native callback invoked outside its retained lifetime\n");
|
|
1175
1186
|
for (const adapter of this.ffiCallbackAdapters.values()) {
|
|
1176
1187
|
const cb = adapter.callback;
|
|
1177
1188
|
if (adapter.tls !== null)
|
|
1178
1189
|
globals.push(`@${adapter.tls} = internal thread_local global ptr null`);
|
|
1179
|
-
|
|
1190
|
+
if (adapter.global !== null)
|
|
1191
|
+
globals.push(`@${adapter.global} = internal global ptr null`);
|
|
1192
|
+
if (adapter.table !== null) {
|
|
1193
|
+
globals.push(`@${adapter.table} = internal global %ScrFfiTable zeroinitializer`);
|
|
1194
|
+
}
|
|
1195
|
+
const params = cb.params.flatMap((param, i) => {
|
|
1196
|
+
if (isFfiContextParam(param))
|
|
1197
|
+
return [`ptr %ctx`];
|
|
1198
|
+
if (param === "string" || param === "bytes") {
|
|
1199
|
+
return [`ptr %a${i}`, `${this.sizeType} %a${i}_len`];
|
|
1200
|
+
}
|
|
1201
|
+
return [`${ffiNativeTypeLl(param)} %a${i}`];
|
|
1202
|
+
});
|
|
1180
1203
|
const ret = ffiNativeTypeLl(cb.returns);
|
|
1181
|
-
|
|
1204
|
+
if (cb.invoke === "foreign") {
|
|
1205
|
+
if (adapter.table === null || !cb.params.some(isFfiContextParam) || cb.returns !== "void") {
|
|
1206
|
+
throw new Error("llvm emitter bug: invalid foreign FFI callback descriptor");
|
|
1207
|
+
}
|
|
1208
|
+
const dispatch = `${adapter.symbol}_dispatch`;
|
|
1209
|
+
const scriptArgs = [];
|
|
1210
|
+
const dispatchBody = [
|
|
1211
|
+
`define internal void @${dispatch}(ptr %cb, ptr %call) ${FN_ATTRS} {`,
|
|
1212
|
+
`entry:`,
|
|
1213
|
+
` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`,
|
|
1214
|
+
` %fn = load ptr, ptr %fnp`,
|
|
1215
|
+
];
|
|
1216
|
+
for (let i = 0; i < cb.params.length; i++) {
|
|
1217
|
+
const param = cb.params[i];
|
|
1218
|
+
if (isFfiContextParam(param))
|
|
1219
|
+
continue;
|
|
1220
|
+
switch (param) {
|
|
1221
|
+
case "f64":
|
|
1222
|
+
this.declare(`declare double @scr_ffi_call_get_f64(ptr, ${this.sizeType})`);
|
|
1223
|
+
dispatchBody.push(` %s${i} = call double @scr_ffi_call_get_f64(ptr %call, ${this.sizeType} ${i})`);
|
|
1224
|
+
scriptArgs.push(`double %s${i}`);
|
|
1225
|
+
break;
|
|
1226
|
+
case "bool":
|
|
1227
|
+
this.declare(`declare zeroext i1 @scr_ffi_call_get_bool(ptr, ${this.sizeType})`);
|
|
1228
|
+
dispatchBody.push(` %s${i} = call zeroext i1 @scr_ffi_call_get_bool(ptr %call, ${this.sizeType} ${i})`);
|
|
1229
|
+
scriptArgs.push(`i1 %s${i}`);
|
|
1230
|
+
break;
|
|
1231
|
+
case "u8":
|
|
1232
|
+
case "u32":
|
|
1233
|
+
case "i32":
|
|
1234
|
+
this.declare(`declare double @scr_ffi_call_get_${param}(ptr, ${this.sizeType})`);
|
|
1235
|
+
dispatchBody.push(` %s${i} = call double @scr_ffi_call_get_${param}(ptr %call, ${this.sizeType} ${i})`);
|
|
1236
|
+
scriptArgs.push(`double %s${i}`);
|
|
1237
|
+
break;
|
|
1238
|
+
case "cstring":
|
|
1239
|
+
case "string":
|
|
1240
|
+
case "bytes": {
|
|
1241
|
+
this.declare(`declare ptr @scr_ffi_call_get_data(ptr, ${this.sizeType})`);
|
|
1242
|
+
this.declare(`declare ${this.sizeType} @scr_ffi_call_get_len(ptr, ${this.sizeType})`);
|
|
1243
|
+
dispatchBody.push(` %data${i} = call ptr @scr_ffi_call_get_data(ptr %call, ${this.sizeType} ${i})`, ` %len${i} = call ${this.sizeType} @scr_ffi_call_get_len(ptr %call, ${this.sizeType} ${i})`);
|
|
1244
|
+
if (param === "bytes") {
|
|
1245
|
+
this.declare(`declare ptr @scr_bytes_from_data(ptr, ${this.sizeType})`);
|
|
1246
|
+
dispatchBody.push(` %s${i} = call ptr @scr_bytes_from_data(ptr %data${i}, ${this.sizeType} %len${i})`);
|
|
1247
|
+
}
|
|
1248
|
+
else {
|
|
1249
|
+
this.declare(`declare ptr @scr_str_from_utf8_lossy(ptr, ${this.sizeType})`);
|
|
1250
|
+
dispatchBody.push(` %s${i} = call ptr @scr_str_from_utf8_lossy(ptr %data${i}, ${this.sizeType} %len${i})`);
|
|
1251
|
+
}
|
|
1252
|
+
scriptArgs.push(`ptr %s${i}`);
|
|
1253
|
+
break;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
dispatchBody.push(` call void %fn(${[`ptr %cb`, ...scriptArgs].join(", ")})`, ` ret void`, `}`, ``);
|
|
1258
|
+
defs.push(...dispatchBody);
|
|
1259
|
+
this.declare(`declare ptr @scr_ffi_call_new(ptr, ptr, ptr, ${this.sizeType})`);
|
|
1260
|
+
this.declare(`declare void @scr_ffi_post(ptr)`);
|
|
1261
|
+
defs.push(`define internal void @${adapter.symbol}(${params.join(", ")}) ${FN_ATTRS} {`, `entry:`, ` %cb = getelementptr inbounds i8, ptr %ctx, i64 0`, ` %call = call ptr @scr_ffi_call_new(ptr @${adapter.table}, ptr %cb, ptr @${dispatch}, ${this.sizeType} ${cb.params.length})`);
|
|
1262
|
+
for (let i = 0; i < cb.params.length; i++) {
|
|
1263
|
+
const param = cb.params[i];
|
|
1264
|
+
if (isFfiContextParam(param))
|
|
1265
|
+
continue;
|
|
1266
|
+
if (param === "cstring") {
|
|
1267
|
+
this.declare(`declare void @scr_ffi_call_copy_cstring(ptr, ${this.sizeType}, ptr)`);
|
|
1268
|
+
defs.push(` call void @scr_ffi_call_copy_cstring(ptr %call, ${this.sizeType} ${i}, ptr %a${i})`);
|
|
1269
|
+
}
|
|
1270
|
+
else if (param === "string" || param === "bytes") {
|
|
1271
|
+
this.declare(`declare void @scr_ffi_call_copy_${param}(ptr, ${this.sizeType}, ptr, ${this.sizeType})`);
|
|
1272
|
+
defs.push(` call void @scr_ffi_call_copy_${param}(ptr %call, ${this.sizeType} ${i}, ptr %a${i}, ${this.sizeType} %a${i}_len)`);
|
|
1273
|
+
}
|
|
1274
|
+
else {
|
|
1275
|
+
const nativeTy = ffiNativeTypeLl(param);
|
|
1276
|
+
this.declare(`declare void @scr_ffi_call_set_${param}(ptr, ${this.sizeType}, ${nativeTy})`);
|
|
1277
|
+
defs.push(` call void @scr_ffi_call_set_${param}(ptr %call, ${this.sizeType} ${i}, ${nativeTy} %a${i})`);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
defs.push(` call void @scr_ffi_post(ptr %call)`, ` ret void`, `}`, ``);
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
defs.push(`define internal ${ret} @${adapter.symbol}(${params.join(", ")}) ${FN_ATTRS} {`, `entry:`, adapter.tls !== null
|
|
1284
|
+
? ` %cb = load ptr, ptr @${adapter.tls}`
|
|
1285
|
+
: adapter.global !== null
|
|
1286
|
+
? ` %cb = load ptr, ptr @${adapter.global}`
|
|
1287
|
+
: ` %cb = getelementptr inbounds i8, ptr %ctx, i64 0`, ` %missing = icmp eq ptr %cb, null`, ` br i1 %missing, label %expired, label %ready`, `expired:`, ` call void @scr_trap(ptr ${adapter.callback.lifetime === "call" ? expired : released})`, ` unreachable`, `ready:`, ` %pending = call zeroext i1 @scr_exc_pending()`, ` br i1 %pending, label %skip, label %invoke`, `skip:`, ` ret ${ffiCallbackDummyLl(cb)}`, `invoke:`);
|
|
1288
|
+
// Validate every native pointer before allocating any copy-in value.
|
|
1289
|
+
// A later bad slot therefore cannot leak an earlier materialization.
|
|
1290
|
+
for (let i = 0; i < cb.params.length; i++) {
|
|
1291
|
+
const param = cb.params[i];
|
|
1292
|
+
if (param !== "cstring" && param !== "string" && param !== "bytes")
|
|
1293
|
+
continue;
|
|
1294
|
+
const invalid = `%invalid${i}`;
|
|
1295
|
+
defs.push(` %null${i} = icmp eq ptr %a${i}, null`);
|
|
1296
|
+
if (param === "cstring") {
|
|
1297
|
+
defs.push(` ${invalid} = or i1 %null${i}, false`);
|
|
1298
|
+
}
|
|
1299
|
+
else {
|
|
1300
|
+
defs.push(` %nonempty${i} = icmp ne ${this.sizeType} %a${i}_len, 0`, ` ${invalid} = and i1 %null${i}, %nonempty${i}`);
|
|
1301
|
+
}
|
|
1302
|
+
const message = param === "cstring"
|
|
1303
|
+
? "scriptc: native callback passed a NULL cstring\n"
|
|
1304
|
+
: `scriptc: native callback passed a NULL ${param} span with nonzero length\n`;
|
|
1305
|
+
defs.push(` br i1 ${invalid}, label %invalid_param${i}, label %param_ok${i}`, `invalid_param${i}:`, ` call void @scr_trap(ptr ${this.cstr(message)})`, ` unreachable`, `param_ok${i}:`);
|
|
1306
|
+
}
|
|
1307
|
+
defs.push(` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`, ` %fn = load ptr, ptr %fnp`);
|
|
1182
1308
|
const scriptArgs = [];
|
|
1183
1309
|
for (let i = 0; i < cb.params.length; i++) {
|
|
1184
1310
|
const param = cb.params[i];
|
|
@@ -1204,16 +1330,40 @@ class LlEmitter {
|
|
|
1204
1330
|
defs.push(` %s${i} = sitofp i32 %a${i} to double`);
|
|
1205
1331
|
scriptArgs.push(`double %s${i}`);
|
|
1206
1332
|
break;
|
|
1333
|
+
case "cstring":
|
|
1334
|
+
this.declare(`declare ${this.sizeType} @strlen(ptr)`);
|
|
1335
|
+
this.declare(`declare ptr @scr_str_from_utf8_lossy(ptr, ${this.sizeType})`);
|
|
1336
|
+
defs.push(` %len${i} = call ${this.sizeType} @strlen(ptr %a${i})`, ` %s${i} = call ptr @scr_str_from_utf8_lossy(ptr %a${i}, ${this.sizeType} %len${i})`);
|
|
1337
|
+
scriptArgs.push(`ptr %s${i}`);
|
|
1338
|
+
break;
|
|
1339
|
+
case "string":
|
|
1340
|
+
this.declare(`declare ptr @scr_str_from_utf8_lossy(ptr, ${this.sizeType})`);
|
|
1341
|
+
defs.push(` %s${i} = call ptr @scr_str_from_utf8_lossy(ptr %a${i}, ${this.sizeType} %a${i}_len)`);
|
|
1342
|
+
scriptArgs.push(`ptr %s${i}`);
|
|
1343
|
+
break;
|
|
1344
|
+
case "bytes":
|
|
1345
|
+
this.declare(`declare ptr @scr_bytes_from_data(ptr, ${this.sizeType})`);
|
|
1346
|
+
defs.push(` %s${i} = call ptr @scr_bytes_from_data(ptr %a${i}, ${this.sizeType} %a${i}_len)`);
|
|
1347
|
+
scriptArgs.push(`ptr %s${i}`);
|
|
1348
|
+
break;
|
|
1207
1349
|
}
|
|
1208
1350
|
}
|
|
1209
1351
|
const ft = ffiCallbackType(cb);
|
|
1210
1352
|
const internalRet = this.llType(ft.ret);
|
|
1211
1353
|
const callArgs = [`ptr %cb`, ...scriptArgs].join(", ");
|
|
1354
|
+
if (cb.lifetime === "retained") {
|
|
1355
|
+
this.declare(`declare ptr @scr_closure_retain_v(ptr)`);
|
|
1356
|
+
this.declare(`declare void @scr_closure_release_v(ptr)`);
|
|
1357
|
+
defs.push(` %invoke_pin = call ptr @scr_closure_retain_v(ptr %cb)`);
|
|
1358
|
+
}
|
|
1212
1359
|
if (cb.returns === "void") {
|
|
1213
|
-
defs.push(` call void %fn(${callArgs})`, ` ret void`, `}`, ``);
|
|
1360
|
+
defs.push(` call void %fn(${callArgs})`, ...(cb.lifetime === "retained" ? [` call void @scr_closure_release_v(ptr %invoke_pin)`] : []), ` ret void`, `}`, ``);
|
|
1214
1361
|
continue;
|
|
1215
1362
|
}
|
|
1216
1363
|
defs.push(` %result = call ${internalRet} %fn(${callArgs})`);
|
|
1364
|
+
if (cb.lifetime === "retained") {
|
|
1365
|
+
defs.push(` call void @scr_closure_release_v(ptr %invoke_pin)`);
|
|
1366
|
+
}
|
|
1217
1367
|
switch (cb.returns) {
|
|
1218
1368
|
case "f64":
|
|
1219
1369
|
defs.push(` ret double %result`);
|
|
@@ -1337,7 +1487,6 @@ class LlEmitter {
|
|
|
1337
1487
|
const snapshotsTlsCa = moduleUsesTls(this.mod) || moduleUsesTlsCa(this.mod) ||
|
|
1338
1488
|
moduleEmbedsBuiltin(this.mod, "node:https") ||
|
|
1339
1489
|
moduleEmbedsBuiltin(this.mod, "node:tls");
|
|
1340
|
-
const hasRefGlobals = globals.some((g) => isRefCounted(g.type)) || fnValueProps.length > 0;
|
|
1341
1490
|
// The process verdict has the same precedence as the C reference
|
|
1342
1491
|
// emitter: node:test owns the final status when present; otherwise an
|
|
1343
1492
|
// embedded process.exitCode owns it; ordinary programs return zero.
|
|
@@ -1348,6 +1497,8 @@ class LlEmitter {
|
|
|
1348
1497
|
this.declare(`declare void @scr_events_install()`);
|
|
1349
1498
|
if (usesFsWatch)
|
|
1350
1499
|
this.declare(`declare void @scr_watch_install()`);
|
|
1500
|
+
if (this.ffiHasForeignCallback)
|
|
1501
|
+
this.declare(`declare void @scr_ffi_install()`);
|
|
1351
1502
|
if (usesStream)
|
|
1352
1503
|
this.declare(`declare void @scr_stream_install()`);
|
|
1353
1504
|
if (usesNet) {
|
|
@@ -1375,12 +1526,21 @@ class LlEmitter {
|
|
|
1375
1526
|
if (snapshotsTlsCa) {
|
|
1376
1527
|
this.declare(`declare void @scr_tls_ca_install()`);
|
|
1377
1528
|
}
|
|
1378
|
-
|
|
1529
|
+
// Inline exit listeners run when something they must beat exists:
|
|
1530
|
+
// the refcounted-global releases, or the retained-FFI atexit ledger
|
|
1531
|
+
// sweep (a listener may legitimately release or pump a registration,
|
|
1532
|
+
// and only the inline call orders ahead of every atexit handler —
|
|
1533
|
+
// the C emitter's runExitListeners stance). Plain event programs
|
|
1534
|
+
// with neither keep the atexit path, so their listener timing is
|
|
1535
|
+
// unchanged.
|
|
1536
|
+
const hasRefGlobals = globals.some((g) => isRefCounted(g.type)) || fnValueProps.length > 0;
|
|
1537
|
+
const inlineExitListeners = usesEvents && (hasRefGlobals || this.ffiHasRetainedCallback);
|
|
1538
|
+
if (inlineExitListeners) {
|
|
1379
1539
|
this.declare(`declare void @scr_run_exit_listeners(double)`);
|
|
1380
1540
|
this.declare(`declare i32 @scr_exit_code_hint_get()`);
|
|
1381
1541
|
}
|
|
1382
1542
|
const exitListenerLines = (prefix) => {
|
|
1383
|
-
if (!
|
|
1543
|
+
if (!inlineExitListeners)
|
|
1384
1544
|
return [];
|
|
1385
1545
|
return [
|
|
1386
1546
|
` %${prefix}h = call i32 @scr_exit_code_hint_get()`,
|
|
@@ -1399,6 +1559,7 @@ class LlEmitter {
|
|
|
1399
1559
|
// async loop-exhaustion story.
|
|
1400
1560
|
const runsLoop = this.usesTimers ||
|
|
1401
1561
|
usesIsland ||
|
|
1562
|
+
this.ffiHasForeignCallback ||
|
|
1402
1563
|
this.mod.functions.some((f) => f.async === true || f.generator !== undefined);
|
|
1403
1564
|
const uncaughtReleases = entryMayThrow && !asyncEntry ? globalReleaseLines("gu") : [];
|
|
1404
1565
|
const loopReleasesU = runsLoop ? globalReleaseLines("gl") : [];
|
|
@@ -1421,7 +1582,7 @@ class LlEmitter {
|
|
|
1421
1582
|
this.declare(`declare void @scr_promise_rethrow_top_level(ptr)`);
|
|
1422
1583
|
this.declare(`declare void @scr_promise_release(ptr)`);
|
|
1423
1584
|
this.declare(`declare void @scr_exit_code_note(i32)`);
|
|
1424
|
-
if (programExitUsesIsland &&
|
|
1585
|
+
if (programExitUsesIsland && inlineExitListeners) {
|
|
1425
1586
|
this.declare(`declare ${this.sizeType} @scr_island_exit_code_version()`);
|
|
1426
1587
|
}
|
|
1427
1588
|
}
|
|
@@ -1439,7 +1600,7 @@ class LlEmitter {
|
|
|
1439
1600
|
lines.push(` %tla_program_exit_zero = icmp eq i32 %tla_program_exit, 0`, ` %tla_exit_status = select i1 %tla_program_exit_zero, i32 %tla_status, i32 %tla_program_exit`);
|
|
1440
1601
|
}
|
|
1441
1602
|
const exitStatus = usesNodeTest || usesIsland ? "%tla_exit_status" : "%tla_status";
|
|
1442
|
-
const tracksIslandExit = programExitUsesIsland &&
|
|
1603
|
+
const tracksIslandExit = programExitUsesIsland && inlineExitListeners;
|
|
1443
1604
|
if (tracksIslandExit) {
|
|
1444
1605
|
lines.push(` %tla_exit_version = call ${this.sizeType} @scr_island_exit_code_version()`);
|
|
1445
1606
|
}
|
|
@@ -1508,6 +1669,7 @@ class LlEmitter {
|
|
|
1508
1669
|
`%ScrVt = type { ${this.sizeType}, ${this.sizeType}, ptr }`,
|
|
1509
1670
|
`%ScrUnion = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
|
|
1510
1671
|
`%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr }`,
|
|
1672
|
+
`%ScrFfiTable = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, i8, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`,
|
|
1511
1673
|
`%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr }`,
|
|
1512
1674
|
// ScrArr mirror { rc, len, cap, elem(i32+pad), elem_retain,
|
|
1513
1675
|
// elem_release, elem_trace, data } — the immortal tagged-template
|
|
@@ -1718,7 +1880,7 @@ class LlEmitter {
|
|
|
1718
1880
|
...(usesEvents ? [` call void @scr_events_install()`] : []),
|
|
1719
1881
|
// fs.watch programs fill the loop's watch hooks the same way —
|
|
1720
1882
|
// scr_watch.c links only when this line is emitted.
|
|
1721
|
-
...(usesFsWatch ? [` call void @scr_watch_install()`] : []), ...(snapshotsTlsCa ? [` call void @scr_tls_ca_install()`] : []), ...(usesFetch ? [` call void @scr_fetch_install()`] : []), ...(embedsZlib ? [` call void @scr_zlib_island_install()`] : []), ...(embedsNet ? [` call void @scr_net_island_install()`] : []), ...(usesNet ? [` call void @scr_net_install()`, ` call void @scr_net_dyn_install()`] : []), ...(usesHttp ? [` call void @scr_http_dyn_install()`] : []), ...(usesStream ? [` call void @scr_stream_install()`] : []), ` call void @scr_lib_init(i32 %argc, ptr %argv)`, ...(usesIsland
|
|
1883
|
+
...(usesFsWatch ? [` call void @scr_watch_install()`] : []), ...(this.ffiHasForeignCallback ? [` call void @scr_ffi_install()`] : []), ...(snapshotsTlsCa ? [` call void @scr_tls_ca_install()`] : []), ...(usesFetch ? [` call void @scr_fetch_install()`] : []), ...(embedsZlib ? [` call void @scr_zlib_island_install()`] : []), ...(embedsNet ? [` call void @scr_net_island_install()`] : []), ...(usesNet ? [` call void @scr_net_install()`, ` call void @scr_net_dyn_install()`] : []), ...(usesHttp ? [` call void @scr_http_dyn_install()`] : []), ...(usesStream ? [` call void @scr_stream_install()`] : []), ` call void @scr_lib_init(i32 %argc, ptr %argv)`, ...(usesIsland
|
|
1722
1884
|
? [
|
|
1723
1885
|
...(moduleEmbedsCompressedNpm(this.mod)
|
|
1724
1886
|
? [` call void @scr_island_set_inflate(ptr @scr_zlib_inflate_exact)`]
|
|
@@ -5605,7 +5767,48 @@ class LlEmitter {
|
|
|
5605
5767
|
sourceArgs.set(abiIndex, arg);
|
|
5606
5768
|
if (isFfiCallbackParam(param))
|
|
5607
5769
|
callbackArgs.set(param.callback.id, arg);
|
|
5770
|
+
if (isFfiReleaseParam(param))
|
|
5771
|
+
callbackArgs.set(param.callback.release, arg);
|
|
5608
5772
|
});
|
|
5773
|
+
const { registrations: retainedRegistrations, releases: retainedReleases } = collectFfiRetainedOps(entry, callbackArgs, (binding, id) => this.ffiCallbackAdapter(binding, id));
|
|
5774
|
+
if (retainedRegistrations.length > 0) {
|
|
5775
|
+
this.declare(`declare void @scr_ffi_retain(ptr, ptr)`);
|
|
5776
|
+
if (retainedRegistrations.some((registration) => registration.global !== null)) {
|
|
5777
|
+
this.declare(`declare void @scr_ffi_retain_slot(ptr, ptr, ptr)`);
|
|
5778
|
+
this.declare(`declare void @scr_ffi_commit_slot(ptr, ptr)`);
|
|
5779
|
+
}
|
|
5780
|
+
}
|
|
5781
|
+
if (retainedReleases.length > 0) {
|
|
5782
|
+
this.declare(`declare void @scr_ffi_require(ptr, ptr)`);
|
|
5783
|
+
if (retainedReleases.some((release) => release.foreign)) {
|
|
5784
|
+
this.declare(`declare void @scr_ffi_require_foreign(ptr, ptr)`);
|
|
5785
|
+
}
|
|
5786
|
+
}
|
|
5787
|
+
// Pin before registration. Raw retained descriptors are native
|
|
5788
|
+
// singletons: the incoming closure is pinned (and an EMPTY slot
|
|
5789
|
+
// armed) before the native set call, but a replaced registration
|
|
5790
|
+
// stays live and dispatching until the call returns — a native
|
|
5791
|
+
// setter may flush the outgoing callback one last time mid-replace.
|
|
5792
|
+
// scr_ffi_commit_slot below repoints the slot and retires the
|
|
5793
|
+
// superseded pins after the call.
|
|
5794
|
+
for (const registration of retainedRegistrations) {
|
|
5795
|
+
if (registration.global !== null) {
|
|
5796
|
+
B.line(`call void @scr_ffi_retain_slot(ptr @${registration.table}, ptr @${registration.global}, ptr ${registration.callback.name})`);
|
|
5797
|
+
}
|
|
5798
|
+
else if (registration.foreign) {
|
|
5799
|
+
this.declare(`declare void @scr_ffi_retain_foreign(ptr, ptr)`);
|
|
5800
|
+
B.line(`call void @scr_ffi_retain_foreign(ptr @${registration.table}, ptr ${registration.callback.name})`);
|
|
5801
|
+
}
|
|
5802
|
+
else {
|
|
5803
|
+
B.line(`call void @scr_ffi_retain(ptr @${registration.table}, ptr ${registration.callback.name})`);
|
|
5804
|
+
}
|
|
5805
|
+
}
|
|
5806
|
+
// Validate releases BEFORE the native removal call runs: a bogus
|
|
5807
|
+
// release traps without native code observing any side effect. The
|
|
5808
|
+
// registration itself is unpinned only after the call returns.
|
|
5809
|
+
for (const release of retainedReleases) {
|
|
5810
|
+
B.line(`call void @scr_ffi_require${release.foreign ? "_foreign" : ""}(ptr @${release.table}, ptr ${release.callback.name})`);
|
|
5811
|
+
}
|
|
5609
5812
|
const rawContexts = [];
|
|
5610
5813
|
for (const param of entry.params) {
|
|
5611
5814
|
if (!isFfiCallbackParam(param))
|
|
@@ -5628,6 +5831,13 @@ class LlEmitter {
|
|
|
5628
5831
|
nativeArgs.push(`ptr @${adapter.symbol}`);
|
|
5629
5832
|
return;
|
|
5630
5833
|
}
|
|
5834
|
+
if (isFfiReleaseParam(param)) {
|
|
5835
|
+
const { binding, id } = parseFfiCallbackKey(param.callback.release);
|
|
5836
|
+
const adapter = this.ffiCallbackAdapter(binding, id);
|
|
5837
|
+
nativeParamTypes.push("ptr");
|
|
5838
|
+
nativeArgs.push(`ptr @${adapter.symbol}`);
|
|
5839
|
+
return;
|
|
5840
|
+
}
|
|
5631
5841
|
if (isFfiContextParam(param)) {
|
|
5632
5842
|
const callback = callbackArgs.get(param.context);
|
|
5633
5843
|
if (!callback)
|
|
@@ -5713,10 +5923,30 @@ class LlEmitter {
|
|
|
5713
5923
|
B.line(`store ptr ${saved.previous}, ptr @${saved.tls}`);
|
|
5714
5924
|
}
|
|
5715
5925
|
};
|
|
5716
|
-
const
|
|
5926
|
+
const finishRetainedReleases = () => {
|
|
5927
|
+
// Commit raw replacements first (repoint the slot, retire the
|
|
5928
|
+
// superseded pins), then unpin explicit releases — the runtime
|
|
5929
|
+
// disarms the slot itself when the released closure holds it.
|
|
5930
|
+
for (const registration of retainedRegistrations) {
|
|
5931
|
+
if (registration.global !== null) {
|
|
5932
|
+
B.line(`call void @scr_ffi_commit_slot(ptr @${registration.table}, ptr ${registration.callback.name})`);
|
|
5933
|
+
}
|
|
5934
|
+
}
|
|
5935
|
+
if (retainedReleases.some((release) => !release.foreign)) {
|
|
5936
|
+
this.declare(`declare void @scr_ffi_release(ptr, ptr)`);
|
|
5937
|
+
}
|
|
5938
|
+
if (retainedReleases.some((release) => release.foreign)) {
|
|
5939
|
+
this.declare(`declare void @scr_ffi_release_foreign(ptr, ptr)`);
|
|
5940
|
+
}
|
|
5941
|
+
for (const release of retainedReleases) {
|
|
5942
|
+
B.line(`call void @scr_ffi_release${release.foreign ? "_foreign" : ""}(ptr @${release.table}, ptr ${release.callback.name})`);
|
|
5943
|
+
}
|
|
5944
|
+
};
|
|
5945
|
+
const callbacksMayThrow = callbackArgs.size > 0 || this.ffiHasRetainedCallback;
|
|
5717
5946
|
if (entry.returns === "void") {
|
|
5718
5947
|
B.line(call);
|
|
5719
5948
|
restoreRawContexts();
|
|
5949
|
+
finishRetainedReleases();
|
|
5720
5950
|
if (callbacksMayThrow)
|
|
5721
5951
|
this.emitPendingCheck();
|
|
5722
5952
|
return { name: "", type: e.type };
|
|
@@ -5724,6 +5954,7 @@ class LlEmitter {
|
|
|
5724
5954
|
const raw = B.tmp();
|
|
5725
5955
|
B.line(`${raw} = ${call}`);
|
|
5726
5956
|
restoreRawContexts();
|
|
5957
|
+
finishRetainedReleases();
|
|
5727
5958
|
if (entry.returns === "f64") {
|
|
5728
5959
|
const result = { name: raw, type: e.type };
|
|
5729
5960
|
if (callbacksMayThrow)
|