@scriptc/compiler 0.0.32 → 0.0.34
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/dist/backend/cc-cache.test.js +430 -1
- package/dist/backend/cc-cache.test.js.map +1 -1
- package/dist/backend/cc.d.ts +25 -0
- package/dist/backend/cc.js +818 -88
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +79 -3
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-shapes.js +37 -4
- package/dist/backend/emission/emit-shapes.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +23 -7
- package/dist/backend/emission/emitter.js +171 -27
- 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/library-identity.d.ts +22 -0
- package/dist/backend/library-identity.js +103 -0
- package/dist/backend/library-identity.js.map +1 -0
- package/dist/backend/llvm/emitter.d.ts +3 -0
- package/dist/backend/llvm/emitter.js +278 -17
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/shapes.d.ts +1 -0
- package/dist/backend/llvm/shapes.js +25 -1
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/mangle.d.ts +1 -0
- package/dist/backend/mangle.js +3 -0
- package/dist/backend/mangle.js.map +1 -1
- package/dist/backend/object-localize.d.ts +5 -5
- package/dist/backend/object-localize.js +19 -16
- package/dist/backend/object-localize.js.map +1 -1
- package/dist/backend/object-localize.test.js +37 -13
- package/dist/backend/object-localize.test.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/input-tracker.d.ts +82 -0
- package/dist/frontend/input-tracker.js +380 -0
- package/dist/frontend/input-tracker.js.map +1 -0
- package/dist/frontend/input-tracker.test.d.ts +1 -0
- package/dist/frontend/input-tracker.test.js +137 -0
- package/dist/frontend/input-tracker.test.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.js +2 -8
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +1 -0
- package/dist/frontend/lowering/lower-calls.js +244 -163
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +3 -0
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.js +113 -28
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.js +67 -1
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +45 -26
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +1 -1
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +48 -3
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +79 -29
- package/dist/frontend/lowering/lowerer.js +429 -87
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/npm-static-rewrite.js +25 -26
- package/dist/frontend/npm-static-rewrite.js.map +1 -1
- package/dist/frontend/npm-static.js +29 -30
- package/dist/frontend/npm-static.js.map +1 -1
- package/dist/frontend/npm-static.test.d.ts +1 -0
- package/dist/frontend/npm-static.test.js +15 -0
- package/dist/frontend/npm-static.test.js.map +1 -0
- package/dist/frontend/npm.js +5 -17
- package/dist/frontend/npm.js.map +1 -1
- package/dist/frontend/program.js +2 -6
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/resolve.js +8 -29
- package/dist/frontend/resolve.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/ts7/program.js +17 -18
- package/dist/frontend/ts7/program.js.map +1 -1
- package/dist/frontend/types.d.ts +41 -0
- package/dist/frontend/types.js +147 -8
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +275 -41
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +42 -12
- 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 +104 -1
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/early-cache.d.ts +65 -0
- package/dist/library/early-cache.js +380 -0
- package/dist/library/early-cache.js.map +1 -0
- package/dist/library/early-cache.test.d.ts +1 -0
- package/dist/library/early-cache.test.js +528 -0
- package/dist/library/early-cache.test.js.map +1 -0
- package/dist/library/int-infer.js +14 -0
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/int-infer.test.js +21 -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/dist/library/semantic-source.d.ts +13 -0
- package/dist/library/semantic-source.js +257 -0
- package/dist/library/semantic-source.js.map +1 -0
- package/dist/library/semantic-source.test.d.ts +1 -0
- package/dist/library/semantic-source.test.js +113 -0
- package/dist/library/semantic-source.test.js.map +1 -0
- package/dist/library/sidecar.d.ts +6 -0
- package/dist/library/sidecar.js +11 -0
- package/dist/library/sidecar.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +1 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export const C_LIBRARY_IDENTITY_BEGIN = "/* scriptc-library-identity: begin */";
|
|
2
|
+
export const C_LIBRARY_IDENTITY_END = "/* scriptc-library-identity: end */";
|
|
3
|
+
export const LLVM_LIBRARY_IDENTITY_BEGIN = "; scriptc-library-identity: begin";
|
|
4
|
+
export const LLVM_LIBRARY_IDENTITY_END = "; scriptc-library-identity: end";
|
|
5
|
+
function identityMarkers(emission) {
|
|
6
|
+
return emission === "c"
|
|
7
|
+
? { begin: C_LIBRARY_IDENTITY_BEGIN, end: C_LIBRARY_IDENTITY_END }
|
|
8
|
+
: { begin: LLVM_LIBRARY_IDENTITY_BEGIN, end: LLVM_LIBRARY_IDENTITY_END };
|
|
9
|
+
}
|
|
10
|
+
export function emitLibraryIdentityLines(emission, identity, llvmFunctionAttrs = "#0") {
|
|
11
|
+
if (!/^[0-9a-f]{16}$/.test(identity.buildId)) {
|
|
12
|
+
throw new Error("library build id must be exactly 16 lowercase hex digits");
|
|
13
|
+
}
|
|
14
|
+
if (emission === "c") {
|
|
15
|
+
return [
|
|
16
|
+
C_LIBRARY_IDENTITY_BEGIN,
|
|
17
|
+
`uint64_t ${identity.buildIdSymbol}(void) {`,
|
|
18
|
+
` return UINT64_C(0x${identity.buildId});`,
|
|
19
|
+
`}`,
|
|
20
|
+
``,
|
|
21
|
+
`uint32_t ${identity.abiVersionSymbol}(void) {`,
|
|
22
|
+
` return ${identity.abiVersion}u;`,
|
|
23
|
+
`}`,
|
|
24
|
+
``,
|
|
25
|
+
C_LIBRARY_IDENTITY_END,
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
const signedBuildId = BigInt.asIntN(64, BigInt(`0x${identity.buildId}`)).toString();
|
|
29
|
+
return [
|
|
30
|
+
LLVM_LIBRARY_IDENTITY_BEGIN,
|
|
31
|
+
`define i64 @${identity.buildIdSymbol}() ${llvmFunctionAttrs} { ; identity getter build_id 0x${identity.buildId}`,
|
|
32
|
+
`entry:`,
|
|
33
|
+
` ret i64 ${signedBuildId}`,
|
|
34
|
+
`}`,
|
|
35
|
+
``,
|
|
36
|
+
`define i32 @${identity.abiVersionSymbol}() ${llvmFunctionAttrs} { ; identity getter abi_version`,
|
|
37
|
+
`entry:`,
|
|
38
|
+
` ret i32 ${identity.abiVersion}`,
|
|
39
|
+
`}`,
|
|
40
|
+
``,
|
|
41
|
+
LLVM_LIBRARY_IDENTITY_END,
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
function identityOffsets(source, emission) {
|
|
45
|
+
const { begin, end } = identityMarkers(emission);
|
|
46
|
+
const start = source.indexOf(`${begin}\n`);
|
|
47
|
+
if (start < 0)
|
|
48
|
+
return null;
|
|
49
|
+
if (source.indexOf(begin, start + begin.length) >= 0) {
|
|
50
|
+
throw new Error("generated library TU contains multiple identity regions");
|
|
51
|
+
}
|
|
52
|
+
const endStart = source.indexOf(end, start + begin.length);
|
|
53
|
+
if (endStart < 0)
|
|
54
|
+
throw new Error("generated library TU has an unterminated identity region");
|
|
55
|
+
return { start, end: endStart + end.length };
|
|
56
|
+
}
|
|
57
|
+
/** Refresh only the volatile identity block in a cached public TU. */
|
|
58
|
+
export function replaceLibraryIdentity(source, emission, identity) {
|
|
59
|
+
const offsets = identityOffsets(source, emission);
|
|
60
|
+
if (offsets === null)
|
|
61
|
+
throw new Error("generated public library TU has no identity region");
|
|
62
|
+
const replacement = emitLibraryIdentityLines(emission, identity).join("\n");
|
|
63
|
+
return source.slice(0, offsets.start) + replacement + source.slice(offsets.end);
|
|
64
|
+
}
|
|
65
|
+
function escapedRegExp(text) {
|
|
66
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
67
|
+
}
|
|
68
|
+
function sourceCommentPattern(sourceFile) {
|
|
69
|
+
// Emission appends these comments at physical line ends. Requiring that
|
|
70
|
+
// boundary prevents source-looking text inside a generated C string from
|
|
71
|
+
// being mistaken for an annotation and changing program semantics. An
|
|
72
|
+
// uninitialized reference declaration appends its own `/* let ... */`
|
|
73
|
+
// explanation after the location, so admit that one generated suffix too.
|
|
74
|
+
return new RegExp(` /\\* ${escapedRegExp(sourceFile)}:(\\d+) \\*/` +
|
|
75
|
+
`(?= /\\* let [^\\r\\n]*; \\*/(?=\\r?\\n|$)|\\r?\\n|$)`, "g");
|
|
76
|
+
}
|
|
77
|
+
/** Refresh the source-line annotations retained in a caller-visible C TU. */
|
|
78
|
+
export function rebaseLibrarySourceComments(source, sourceFile, rebaseLine) {
|
|
79
|
+
return source.replace(sourceCommentPattern(sourceFile), (_match, line) => ` /* ${sourceFile}:${rebaseLine(Number(line))} */`);
|
|
80
|
+
}
|
|
81
|
+
/** Remove C-only debugging annotations from the private native-cache input. */
|
|
82
|
+
export function stripLibrarySourceComments(source, sourceFile) {
|
|
83
|
+
return source.replace(sourceCommentPattern(sourceFile), "");
|
|
84
|
+
}
|
|
85
|
+
/** Remove the generated identity region from a complete caller-visible
|
|
86
|
+
* library TU. Archive assembly compiles this stable projection beside the
|
|
87
|
+
* small volatile identity C object, while the public TU remains complete. */
|
|
88
|
+
export function stripLibraryIdentity(source, emission) {
|
|
89
|
+
const offsets = identityOffsets(source, emission);
|
|
90
|
+
if (offsets === null)
|
|
91
|
+
return source;
|
|
92
|
+
const endOffset = offsets.end;
|
|
93
|
+
const suffix = source[endOffset] === "\n" ? endOffset + 1 : endOffset;
|
|
94
|
+
let prefix = source.slice(0, offsets.start);
|
|
95
|
+
// An omitted final region leaves the emitter's preceding empty array entry
|
|
96
|
+
// as one trailing newline; a marked final region has material after that
|
|
97
|
+
// entry and therefore renders it as two. Restore the omitted form exactly.
|
|
98
|
+
if (suffix === source.length && prefix.endsWith("\n\n")) {
|
|
99
|
+
prefix = prefix.slice(0, -1);
|
|
100
|
+
}
|
|
101
|
+
return prefix + source.slice(suffix);
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=library-identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"library-identity.js","sourceRoot":"","sources":["../../src/backend/library-identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,5 +5,8 @@ export interface LlvmTargetOptions {
|
|
|
5
5
|
pointerBits?: 32 | 64;
|
|
6
6
|
/** Select the WASI libc entry-point convention. */
|
|
7
7
|
wasi?: boolean;
|
|
8
|
+
/** Library archive assembly may move the volatile identity getters into a
|
|
9
|
+
* separate translation unit. Public/direct emission keeps them by default. */
|
|
10
|
+
emitLibraryIdentity?: boolean;
|
|
8
11
|
}
|
|
9
12
|
export declare function emitLlvmModule(mod: IrModule, options?: LlvmTargetOptions): string;
|
|
@@ -61,11 +61,12 @@
|
|
|
61
61
|
* lazy-inflate representation as the C debugging backend.
|
|
62
62
|
*/
|
|
63
63
|
import { deflateRawSync } from "node:zlib";
|
|
64
|
-
import {
|
|
64
|
+
import { emitLibraryIdentityLines } from "../library-identity.js";
|
|
65
|
+
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
66
|
import { matchIntegerBytesForLoop } from "../../ir/integer-loops.js";
|
|
66
|
-
import { allocateFfiCallbackAdapters } from "../ffi-callbacks.js";
|
|
67
|
+
import { allocateFfiCallbackAdapters, collectFfiRetainedOps, hasForeignFfiCallback, hasRetainedFfiCallback, parseFfiCallbackKey } from "../ffi-callbacks.js";
|
|
67
68
|
import { computeMayThrow } from "../emission/may-throw.js";
|
|
68
|
-
import { mangleArgPack, mangleAsyncSpawn, mangleClassNew, mangleClassObj, mangleClassRetain, mangleFnClosure, mangleFunction, mangleGenDrop, mangleGenResThunk, mangleGenSpawn, mangleGlobal, mangleLocal, mangleRecordNew, mangleRecordStruct, mangleResolveThunk, mangleTrampoline, mangleVtStruct, mangleWrapper } from "../mangle.js";
|
|
69
|
+
import { mangleArgPack, mangleAsyncSpawn, mangleClassNew, mangleClassObj, mangleClassRetain, mangleFnClosure, mangleFunction, mangleGenDrop, mangleGenResThunk, mangleGenSpawn, mangleGlobal, mangleLocal, mangleRecordClone, mangleRecordNew, mangleRecordStruct, mangleResolveThunk, mangleTrampoline, mangleVtStruct, mangleWrapper } from "../mangle.js";
|
|
69
70
|
import { BlockBuilder } from "./blocks.js";
|
|
70
71
|
import { buildClassGraph, classFieldIndex, classStructSym, emitClassObjDefs, emitClassShapes, } from "./classes.js";
|
|
71
72
|
import { DK, LlDyn } from "./dyn.js";
|
|
@@ -81,6 +82,8 @@ function ffiNativeTypeLl(cls) {
|
|
|
81
82
|
case "u32":
|
|
82
83
|
case "i32":
|
|
83
84
|
return "i32";
|
|
85
|
+
case "cstring":
|
|
86
|
+
return "ptr";
|
|
84
87
|
case "string":
|
|
85
88
|
case "bytes":
|
|
86
89
|
throw new Error(`llvm emitter bug: span class '${cls}' has no scalar LLVM type`);
|
|
@@ -874,6 +877,7 @@ class LlEmitter {
|
|
|
874
877
|
sizeType;
|
|
875
878
|
cycleColorOffset;
|
|
876
879
|
wasi;
|
|
880
|
+
emitLibraryIdentity;
|
|
877
881
|
/** Interned string literals: UTF-8 text → { symbol, byte length } —
|
|
878
882
|
* first-use order, the C emitter's determinism discipline. */
|
|
879
883
|
literals = new Map();
|
|
@@ -917,6 +921,12 @@ class LlEmitter {
|
|
|
917
921
|
/** C-ABI callback trampolines and (for raw/no-userdata callbacks) their
|
|
918
922
|
* distinct call-scoped TLS closure slots. */
|
|
919
923
|
ffiCallbackAdapters;
|
|
924
|
+
/** Module-level constant consulted per ffiCall: with a retained
|
|
925
|
+
* descriptor anywhere in the manifest, every native call is a
|
|
926
|
+
* pending-exception checkpoint (may-throw derives the same fact from
|
|
927
|
+
* the same helper). */
|
|
928
|
+
ffiHasRetainedCallback;
|
|
929
|
+
ffiHasForeignCallback;
|
|
920
930
|
globalTypes = new Map();
|
|
921
931
|
/** May-throw analysis (the C emitter's computeMayThrow, shared): pending
|
|
922
932
|
* checks are emitted only after calls that can actually raise. */
|
|
@@ -943,6 +953,7 @@ class LlEmitter {
|
|
|
943
953
|
dynPromiseAdapters = new Map();
|
|
944
954
|
unionsById = new Map();
|
|
945
955
|
recordsById = new Map();
|
|
956
|
+
recordCloneShapes = new Set();
|
|
946
957
|
tracedShapes;
|
|
947
958
|
tracedUnions;
|
|
948
959
|
/** The class graph (buildClassGraph): preorder numbering, hierarchy
|
|
@@ -1014,11 +1025,14 @@ class LlEmitter {
|
|
|
1014
1025
|
this.mod = mod;
|
|
1015
1026
|
this.sizeType = options.pointerBits === 32 ? "i32" : "i64";
|
|
1016
1027
|
this.wasi = options.wasi === true;
|
|
1028
|
+
this.emitLibraryIdentity = options.emitLibraryIdentity !== false;
|
|
1017
1029
|
// ScrCycHdr is { ptr trace; ptr free; i32 color; i16 buffered;
|
|
1018
1030
|
// i16 gen; size_t buf_index }. The object follows it, so color is 12
|
|
1019
1031
|
// bytes behind a wasm32 object and 16 bytes behind a 64-bit object.
|
|
1020
1032
|
this.cycleColorOffset = options.pointerBits === 32 ? 12 : 16;
|
|
1021
1033
|
this.ffiCallbackAdapters = allocateFfiCallbackAdapters(mod.ffiImports ?? []);
|
|
1034
|
+
this.ffiHasRetainedCallback = hasRetainedFfiCallback(mod.ffiImports ?? []);
|
|
1035
|
+
this.ffiHasForeignCallback = hasForeignFfiCallback(mod.ffiImports ?? []);
|
|
1022
1036
|
for (const fn of mod.functions)
|
|
1023
1037
|
this.fnByName.set(fn.name, fn);
|
|
1024
1038
|
for (const entry of mod.ffiImports ?? []) {
|
|
@@ -1172,13 +1186,129 @@ class LlEmitter {
|
|
|
1172
1186
|
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
1173
1187
|
this.declare(`declare void @scr_trap(ptr)`);
|
|
1174
1188
|
const expired = this.cstr("scriptc: native callback invoked outside its call-scoped lifetime\n");
|
|
1189
|
+
const released = this.cstr("scriptc: native callback invoked outside its retained lifetime\n");
|
|
1175
1190
|
for (const adapter of this.ffiCallbackAdapters.values()) {
|
|
1176
1191
|
const cb = adapter.callback;
|
|
1177
1192
|
if (adapter.tls !== null)
|
|
1178
1193
|
globals.push(`@${adapter.tls} = internal thread_local global ptr null`);
|
|
1179
|
-
|
|
1194
|
+
if (adapter.global !== null)
|
|
1195
|
+
globals.push(`@${adapter.global} = internal global ptr null`);
|
|
1196
|
+
if (adapter.table !== null) {
|
|
1197
|
+
globals.push(`@${adapter.table} = internal global %ScrFfiTable zeroinitializer`);
|
|
1198
|
+
}
|
|
1199
|
+
const params = cb.params.flatMap((param, i) => {
|
|
1200
|
+
if (isFfiContextParam(param))
|
|
1201
|
+
return [`ptr %ctx`];
|
|
1202
|
+
if (param === "string" || param === "bytes") {
|
|
1203
|
+
return [`ptr %a${i}`, `${this.sizeType} %a${i}_len`];
|
|
1204
|
+
}
|
|
1205
|
+
return [`${ffiNativeTypeLl(param)} %a${i}`];
|
|
1206
|
+
});
|
|
1180
1207
|
const ret = ffiNativeTypeLl(cb.returns);
|
|
1181
|
-
|
|
1208
|
+
if (cb.invoke === "foreign") {
|
|
1209
|
+
if (adapter.table === null || !cb.params.some(isFfiContextParam) || cb.returns !== "void") {
|
|
1210
|
+
throw new Error("llvm emitter bug: invalid foreign FFI callback descriptor");
|
|
1211
|
+
}
|
|
1212
|
+
const dispatch = `${adapter.symbol}_dispatch`;
|
|
1213
|
+
const scriptArgs = [];
|
|
1214
|
+
const dispatchBody = [
|
|
1215
|
+
`define internal void @${dispatch}(ptr %cb, ptr %call) ${FN_ATTRS} {`,
|
|
1216
|
+
`entry:`,
|
|
1217
|
+
` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`,
|
|
1218
|
+
` %fn = load ptr, ptr %fnp`,
|
|
1219
|
+
];
|
|
1220
|
+
for (let i = 0; i < cb.params.length; i++) {
|
|
1221
|
+
const param = cb.params[i];
|
|
1222
|
+
if (isFfiContextParam(param))
|
|
1223
|
+
continue;
|
|
1224
|
+
switch (param) {
|
|
1225
|
+
case "f64":
|
|
1226
|
+
this.declare(`declare double @scr_ffi_call_get_f64(ptr, ${this.sizeType})`);
|
|
1227
|
+
dispatchBody.push(` %s${i} = call double @scr_ffi_call_get_f64(ptr %call, ${this.sizeType} ${i})`);
|
|
1228
|
+
scriptArgs.push(`double %s${i}`);
|
|
1229
|
+
break;
|
|
1230
|
+
case "bool":
|
|
1231
|
+
this.declare(`declare zeroext i1 @scr_ffi_call_get_bool(ptr, ${this.sizeType})`);
|
|
1232
|
+
dispatchBody.push(` %s${i} = call zeroext i1 @scr_ffi_call_get_bool(ptr %call, ${this.sizeType} ${i})`);
|
|
1233
|
+
scriptArgs.push(`i1 %s${i}`);
|
|
1234
|
+
break;
|
|
1235
|
+
case "u8":
|
|
1236
|
+
case "u32":
|
|
1237
|
+
case "i32":
|
|
1238
|
+
this.declare(`declare double @scr_ffi_call_get_${param}(ptr, ${this.sizeType})`);
|
|
1239
|
+
dispatchBody.push(` %s${i} = call double @scr_ffi_call_get_${param}(ptr %call, ${this.sizeType} ${i})`);
|
|
1240
|
+
scriptArgs.push(`double %s${i}`);
|
|
1241
|
+
break;
|
|
1242
|
+
case "cstring":
|
|
1243
|
+
case "string":
|
|
1244
|
+
case "bytes": {
|
|
1245
|
+
this.declare(`declare ptr @scr_ffi_call_get_data(ptr, ${this.sizeType})`);
|
|
1246
|
+
this.declare(`declare ${this.sizeType} @scr_ffi_call_get_len(ptr, ${this.sizeType})`);
|
|
1247
|
+
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})`);
|
|
1248
|
+
if (param === "bytes") {
|
|
1249
|
+
this.declare(`declare ptr @scr_bytes_from_data(ptr, ${this.sizeType})`);
|
|
1250
|
+
dispatchBody.push(` %s${i} = call ptr @scr_bytes_from_data(ptr %data${i}, ${this.sizeType} %len${i})`);
|
|
1251
|
+
}
|
|
1252
|
+
else {
|
|
1253
|
+
this.declare(`declare ptr @scr_str_from_utf8_lossy(ptr, ${this.sizeType})`);
|
|
1254
|
+
dispatchBody.push(` %s${i} = call ptr @scr_str_from_utf8_lossy(ptr %data${i}, ${this.sizeType} %len${i})`);
|
|
1255
|
+
}
|
|
1256
|
+
scriptArgs.push(`ptr %s${i}`);
|
|
1257
|
+
break;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
dispatchBody.push(` call void %fn(${[`ptr %cb`, ...scriptArgs].join(", ")})`, ` ret void`, `}`, ``);
|
|
1262
|
+
defs.push(...dispatchBody);
|
|
1263
|
+
this.declare(`declare ptr @scr_ffi_call_new(ptr, ptr, ptr, ${this.sizeType})`);
|
|
1264
|
+
this.declare(`declare void @scr_ffi_post(ptr)`);
|
|
1265
|
+
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})`);
|
|
1266
|
+
for (let i = 0; i < cb.params.length; i++) {
|
|
1267
|
+
const param = cb.params[i];
|
|
1268
|
+
if (isFfiContextParam(param))
|
|
1269
|
+
continue;
|
|
1270
|
+
if (param === "cstring") {
|
|
1271
|
+
this.declare(`declare void @scr_ffi_call_copy_cstring(ptr, ${this.sizeType}, ptr)`);
|
|
1272
|
+
defs.push(` call void @scr_ffi_call_copy_cstring(ptr %call, ${this.sizeType} ${i}, ptr %a${i})`);
|
|
1273
|
+
}
|
|
1274
|
+
else if (param === "string" || param === "bytes") {
|
|
1275
|
+
this.declare(`declare void @scr_ffi_call_copy_${param}(ptr, ${this.sizeType}, ptr, ${this.sizeType})`);
|
|
1276
|
+
defs.push(` call void @scr_ffi_call_copy_${param}(ptr %call, ${this.sizeType} ${i}, ptr %a${i}, ${this.sizeType} %a${i}_len)`);
|
|
1277
|
+
}
|
|
1278
|
+
else {
|
|
1279
|
+
const nativeTy = ffiNativeTypeLl(param);
|
|
1280
|
+
this.declare(`declare void @scr_ffi_call_set_${param}(ptr, ${this.sizeType}, ${nativeTy})`);
|
|
1281
|
+
defs.push(` call void @scr_ffi_call_set_${param}(ptr %call, ${this.sizeType} ${i}, ${nativeTy} %a${i})`);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
defs.push(` call void @scr_ffi_post(ptr %call)`, ` ret void`, `}`, ``);
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
defs.push(`define internal ${ret} @${adapter.symbol}(${params.join(", ")}) ${FN_ATTRS} {`, `entry:`, adapter.tls !== null
|
|
1288
|
+
? ` %cb = load ptr, ptr @${adapter.tls}`
|
|
1289
|
+
: adapter.global !== null
|
|
1290
|
+
? ` %cb = load ptr, ptr @${adapter.global}`
|
|
1291
|
+
: ` %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:`);
|
|
1292
|
+
// Validate every native pointer before allocating any copy-in value.
|
|
1293
|
+
// A later bad slot therefore cannot leak an earlier materialization.
|
|
1294
|
+
for (let i = 0; i < cb.params.length; i++) {
|
|
1295
|
+
const param = cb.params[i];
|
|
1296
|
+
if (param !== "cstring" && param !== "string" && param !== "bytes")
|
|
1297
|
+
continue;
|
|
1298
|
+
const invalid = `%invalid${i}`;
|
|
1299
|
+
defs.push(` %null${i} = icmp eq ptr %a${i}, null`);
|
|
1300
|
+
if (param === "cstring") {
|
|
1301
|
+
defs.push(` ${invalid} = or i1 %null${i}, false`);
|
|
1302
|
+
}
|
|
1303
|
+
else {
|
|
1304
|
+
defs.push(` %nonempty${i} = icmp ne ${this.sizeType} %a${i}_len, 0`, ` ${invalid} = and i1 %null${i}, %nonempty${i}`);
|
|
1305
|
+
}
|
|
1306
|
+
const message = param === "cstring"
|
|
1307
|
+
? "scriptc: native callback passed a NULL cstring\n"
|
|
1308
|
+
: `scriptc: native callback passed a NULL ${param} span with nonzero length\n`;
|
|
1309
|
+
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}:`);
|
|
1310
|
+
}
|
|
1311
|
+
defs.push(` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`, ` %fn = load ptr, ptr %fnp`);
|
|
1182
1312
|
const scriptArgs = [];
|
|
1183
1313
|
for (let i = 0; i < cb.params.length; i++) {
|
|
1184
1314
|
const param = cb.params[i];
|
|
@@ -1204,16 +1334,40 @@ class LlEmitter {
|
|
|
1204
1334
|
defs.push(` %s${i} = sitofp i32 %a${i} to double`);
|
|
1205
1335
|
scriptArgs.push(`double %s${i}`);
|
|
1206
1336
|
break;
|
|
1337
|
+
case "cstring":
|
|
1338
|
+
this.declare(`declare ${this.sizeType} @strlen(ptr)`);
|
|
1339
|
+
this.declare(`declare ptr @scr_str_from_utf8_lossy(ptr, ${this.sizeType})`);
|
|
1340
|
+
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})`);
|
|
1341
|
+
scriptArgs.push(`ptr %s${i}`);
|
|
1342
|
+
break;
|
|
1343
|
+
case "string":
|
|
1344
|
+
this.declare(`declare ptr @scr_str_from_utf8_lossy(ptr, ${this.sizeType})`);
|
|
1345
|
+
defs.push(` %s${i} = call ptr @scr_str_from_utf8_lossy(ptr %a${i}, ${this.sizeType} %a${i}_len)`);
|
|
1346
|
+
scriptArgs.push(`ptr %s${i}`);
|
|
1347
|
+
break;
|
|
1348
|
+
case "bytes":
|
|
1349
|
+
this.declare(`declare ptr @scr_bytes_from_data(ptr, ${this.sizeType})`);
|
|
1350
|
+
defs.push(` %s${i} = call ptr @scr_bytes_from_data(ptr %a${i}, ${this.sizeType} %a${i}_len)`);
|
|
1351
|
+
scriptArgs.push(`ptr %s${i}`);
|
|
1352
|
+
break;
|
|
1207
1353
|
}
|
|
1208
1354
|
}
|
|
1209
1355
|
const ft = ffiCallbackType(cb);
|
|
1210
1356
|
const internalRet = this.llType(ft.ret);
|
|
1211
1357
|
const callArgs = [`ptr %cb`, ...scriptArgs].join(", ");
|
|
1358
|
+
if (cb.lifetime === "retained") {
|
|
1359
|
+
this.declare(`declare ptr @scr_closure_retain_v(ptr)`);
|
|
1360
|
+
this.declare(`declare void @scr_closure_release_v(ptr)`);
|
|
1361
|
+
defs.push(` %invoke_pin = call ptr @scr_closure_retain_v(ptr %cb)`);
|
|
1362
|
+
}
|
|
1212
1363
|
if (cb.returns === "void") {
|
|
1213
|
-
defs.push(` call void %fn(${callArgs})`, ` ret void`, `}`, ``);
|
|
1364
|
+
defs.push(` call void %fn(${callArgs})`, ...(cb.lifetime === "retained" ? [` call void @scr_closure_release_v(ptr %invoke_pin)`] : []), ` ret void`, `}`, ``);
|
|
1214
1365
|
continue;
|
|
1215
1366
|
}
|
|
1216
1367
|
defs.push(` %result = call ${internalRet} %fn(${callArgs})`);
|
|
1368
|
+
if (cb.lifetime === "retained") {
|
|
1369
|
+
defs.push(` call void @scr_closure_release_v(ptr %invoke_pin)`);
|
|
1370
|
+
}
|
|
1217
1371
|
switch (cb.returns) {
|
|
1218
1372
|
case "f64":
|
|
1219
1373
|
defs.push(` ret double %result`);
|
|
@@ -1253,6 +1407,7 @@ class LlEmitter {
|
|
|
1253
1407
|
const wrappers = this.emitFnValueDefs();
|
|
1254
1408
|
const asyncDefs = this.emitAsyncScaffolding();
|
|
1255
1409
|
const ffiCallbacks = this.emitFfiCallbackDefs();
|
|
1410
|
+
const hasNoInlineRecordClone = [...this.recordCloneShapes].some((shapeId) => (this.recordsById.get(shapeId)?.fields.length ?? 0) >= 16);
|
|
1256
1411
|
const embedded = this.mod.embedded;
|
|
1257
1412
|
const usesIsland = embedded !== undefined && embedded.modules.length > 0;
|
|
1258
1413
|
const storeNpmText = (text) => {
|
|
@@ -1337,7 +1492,6 @@ class LlEmitter {
|
|
|
1337
1492
|
const snapshotsTlsCa = moduleUsesTls(this.mod) || moduleUsesTlsCa(this.mod) ||
|
|
1338
1493
|
moduleEmbedsBuiltin(this.mod, "node:https") ||
|
|
1339
1494
|
moduleEmbedsBuiltin(this.mod, "node:tls");
|
|
1340
|
-
const hasRefGlobals = globals.some((g) => isRefCounted(g.type)) || fnValueProps.length > 0;
|
|
1341
1495
|
// The process verdict has the same precedence as the C reference
|
|
1342
1496
|
// emitter: node:test owns the final status when present; otherwise an
|
|
1343
1497
|
// embedded process.exitCode owns it; ordinary programs return zero.
|
|
@@ -1348,6 +1502,8 @@ class LlEmitter {
|
|
|
1348
1502
|
this.declare(`declare void @scr_events_install()`);
|
|
1349
1503
|
if (usesFsWatch)
|
|
1350
1504
|
this.declare(`declare void @scr_watch_install()`);
|
|
1505
|
+
if (this.ffiHasForeignCallback)
|
|
1506
|
+
this.declare(`declare void @scr_ffi_install()`);
|
|
1351
1507
|
if (usesStream)
|
|
1352
1508
|
this.declare(`declare void @scr_stream_install()`);
|
|
1353
1509
|
if (usesNet) {
|
|
@@ -1375,12 +1531,21 @@ class LlEmitter {
|
|
|
1375
1531
|
if (snapshotsTlsCa) {
|
|
1376
1532
|
this.declare(`declare void @scr_tls_ca_install()`);
|
|
1377
1533
|
}
|
|
1378
|
-
|
|
1534
|
+
// Inline exit listeners run when something they must beat exists:
|
|
1535
|
+
// the refcounted-global releases, or the retained-FFI atexit ledger
|
|
1536
|
+
// sweep (a listener may legitimately release or pump a registration,
|
|
1537
|
+
// and only the inline call orders ahead of every atexit handler —
|
|
1538
|
+
// the C emitter's runExitListeners stance). Plain event programs
|
|
1539
|
+
// with neither keep the atexit path, so their listener timing is
|
|
1540
|
+
// unchanged.
|
|
1541
|
+
const hasRefGlobals = globals.some((g) => isRefCounted(g.type)) || fnValueProps.length > 0;
|
|
1542
|
+
const inlineExitListeners = usesEvents && (hasRefGlobals || this.ffiHasRetainedCallback);
|
|
1543
|
+
if (inlineExitListeners) {
|
|
1379
1544
|
this.declare(`declare void @scr_run_exit_listeners(double)`);
|
|
1380
1545
|
this.declare(`declare i32 @scr_exit_code_hint_get()`);
|
|
1381
1546
|
}
|
|
1382
1547
|
const exitListenerLines = (prefix) => {
|
|
1383
|
-
if (!
|
|
1548
|
+
if (!inlineExitListeners)
|
|
1384
1549
|
return [];
|
|
1385
1550
|
return [
|
|
1386
1551
|
` %${prefix}h = call i32 @scr_exit_code_hint_get()`,
|
|
@@ -1399,6 +1564,7 @@ class LlEmitter {
|
|
|
1399
1564
|
// async loop-exhaustion story.
|
|
1400
1565
|
const runsLoop = this.usesTimers ||
|
|
1401
1566
|
usesIsland ||
|
|
1567
|
+
this.ffiHasForeignCallback ||
|
|
1402
1568
|
this.mod.functions.some((f) => f.async === true || f.generator !== undefined);
|
|
1403
1569
|
const uncaughtReleases = entryMayThrow && !asyncEntry ? globalReleaseLines("gu") : [];
|
|
1404
1570
|
const loopReleasesU = runsLoop ? globalReleaseLines("gl") : [];
|
|
@@ -1421,7 +1587,7 @@ class LlEmitter {
|
|
|
1421
1587
|
this.declare(`declare void @scr_promise_rethrow_top_level(ptr)`);
|
|
1422
1588
|
this.declare(`declare void @scr_promise_release(ptr)`);
|
|
1423
1589
|
this.declare(`declare void @scr_exit_code_note(i32)`);
|
|
1424
|
-
if (programExitUsesIsland &&
|
|
1590
|
+
if (programExitUsesIsland && inlineExitListeners) {
|
|
1425
1591
|
this.declare(`declare ${this.sizeType} @scr_island_exit_code_version()`);
|
|
1426
1592
|
}
|
|
1427
1593
|
}
|
|
@@ -1439,7 +1605,7 @@ class LlEmitter {
|
|
|
1439
1605
|
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
1606
|
}
|
|
1441
1607
|
const exitStatus = usesNodeTest || usesIsland ? "%tla_exit_status" : "%tla_status";
|
|
1442
|
-
const tracksIslandExit = programExitUsesIsland &&
|
|
1608
|
+
const tracksIslandExit = programExitUsesIsland && inlineExitListeners;
|
|
1443
1609
|
if (tracksIslandExit) {
|
|
1444
1610
|
lines.push(` %tla_exit_version = call ${this.sizeType} @scr_island_exit_code_version()`);
|
|
1445
1611
|
}
|
|
@@ -1508,6 +1674,7 @@ class LlEmitter {
|
|
|
1508
1674
|
`%ScrVt = type { ${this.sizeType}, ${this.sizeType}, ptr }`,
|
|
1509
1675
|
`%ScrUnion = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
|
|
1510
1676
|
`%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr }`,
|
|
1677
|
+
`%ScrFfiTable = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, i8, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`,
|
|
1511
1678
|
`%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr }`,
|
|
1512
1679
|
// ScrArr mirror { rc, len, cap, elem(i32+pad), elem_retain,
|
|
1513
1680
|
// elem_release, elem_trace, data } — the immortal tagged-template
|
|
@@ -1708,6 +1875,8 @@ class LlEmitter {
|
|
|
1708
1875
|
out.push(`attributes #0 = { sanitize_address }`);
|
|
1709
1876
|
if (this.wasi)
|
|
1710
1877
|
out.push(`attributes #1 = { sanitize_address presplitcoroutine }`);
|
|
1878
|
+
if (hasNoInlineRecordClone)
|
|
1879
|
+
out.push(`attributes #2 = { noinline sanitize_address }`);
|
|
1711
1880
|
out.push(``);
|
|
1712
1881
|
return out.join("\n");
|
|
1713
1882
|
}
|
|
@@ -1718,7 +1887,7 @@ class LlEmitter {
|
|
|
1718
1887
|
...(usesEvents ? [` call void @scr_events_install()`] : []),
|
|
1719
1888
|
// fs.watch programs fill the loop's watch hooks the same way —
|
|
1720
1889
|
// 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
|
|
1890
|
+
...(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
1891
|
? [
|
|
1723
1892
|
...(moduleEmbedsCompressedNpm(this.mod)
|
|
1724
1893
|
? [` call void @scr_island_set_inflate(ptr @scr_zlib_inflate_exact)`]
|
|
@@ -1810,7 +1979,7 @@ class LlEmitter {
|
|
|
1810
1979
|
// sanitize_address is inert under the plain pipeline; the sanitized
|
|
1811
1980
|
// lane's -fsanitize=address link activates instrumentation over the
|
|
1812
1981
|
// emitted functions too (the runtime TUs get theirs from clang).
|
|
1813
|
-
`attributes #0 = { sanitize_address }`, ...(this.wasi ? [`attributes #1 = { sanitize_address presplitcoroutine }`] : []), ``);
|
|
1982
|
+
`attributes #0 = { sanitize_address }`, ...(this.wasi ? [`attributes #1 = { sanitize_address presplitcoroutine }`] : []), ...(hasNoInlineRecordClone ? [`attributes #2 = { noinline sanitize_address }`] : []), ``);
|
|
1814
1983
|
return out.join("\n");
|
|
1815
1984
|
}
|
|
1816
1985
|
/** LIBRARY mode: the profile-declared external definitions — the
|
|
@@ -1891,14 +2060,13 @@ class LlEmitter {
|
|
|
1891
2060
|
});
|
|
1892
2061
|
out.push(`miss:`, ` ret i32 -1`, `}`, ``);
|
|
1893
2062
|
}
|
|
1894
|
-
if (lib.identity !== undefined) {
|
|
2063
|
+
if (lib.identity !== undefined && this.emitLibraryIdentity) {
|
|
1895
2064
|
// Profile-declared identity getters (the ask-2 sidecar's boot-time
|
|
1896
2065
|
// pairing fence): pure data returns with NO entry prologue — exempt
|
|
1897
2066
|
// from the poisoned guard and every runtime touch (ratified), so a
|
|
1898
2067
|
// host can read them before init and after a trap. The u64 rides
|
|
1899
2068
|
// i64 two's-complement (LLVM integer constants are signed).
|
|
1900
|
-
|
|
1901
|
-
out.push(`define i64 @${lib.identity.buildIdSymbol}() ${FN_ATTRS} { ; identity getter build_id 0x${lib.identity.buildId}`, `entry:`, ` ret i64 ${buildId}`, `}`, ``, `define i32 @${lib.identity.abiVersionSymbol}() ${FN_ATTRS} { ; identity getter abi_version`, `entry:`, ` ret i32 ${lib.identity.abiVersion}`, `}`, ``);
|
|
2069
|
+
out.push(...emitLibraryIdentityLines("llvm", lib.identity, FN_ATTRS));
|
|
1902
2070
|
}
|
|
1903
2071
|
if (lib.resultResetSymbol !== null) {
|
|
1904
2072
|
out.push(`define void @${lib.resultResetSymbol}() ${FN_ATTRS} {`, `entry:`, ` call void @scr_library_entry(i1 zeroext false, ptr ${symConst(lib.resultResetSymbol)})`, ` call void @scr_library_arena_reset()`, ` ret void`, `}`, ``);
|
|
@@ -4729,6 +4897,30 @@ class LlEmitter {
|
|
|
4729
4897
|
}
|
|
4730
4898
|
return out;
|
|
4731
4899
|
}
|
|
4900
|
+
case "recordClone": {
|
|
4901
|
+
if (e.type.kind !== "record")
|
|
4902
|
+
throw new Error("llvm emitter bug: recordClone of non-record type");
|
|
4903
|
+
this.recordCloneShapes.add(e.type.shapeId);
|
|
4904
|
+
const source = this.emitExpr(e.source);
|
|
4905
|
+
const rec = B.tmp();
|
|
4906
|
+
B.line(`${rec} = call ptr @${mangleRecordClone(e.type.shapeId)}(ptr ${source.name})`);
|
|
4907
|
+
const out = this.own({ name: rec, type: e.type });
|
|
4908
|
+
for (const f of e.overrides) {
|
|
4909
|
+
const v = this.emitExpr(f.value);
|
|
4910
|
+
const { ptr, type } = this.recordFieldPtr(rec, e.type.shapeId, f.name);
|
|
4911
|
+
if (isRefCounted(type)) {
|
|
4912
|
+
this.moveTemp(v);
|
|
4913
|
+
const old = B.tmp();
|
|
4914
|
+
B.line(`${old} = load ptr, ptr ${ptr}`);
|
|
4915
|
+
this.storeField(ptr, type, v.name);
|
|
4916
|
+
this.releaseValue(old, type);
|
|
4917
|
+
}
|
|
4918
|
+
else {
|
|
4919
|
+
this.storeField(ptr, type, v.name);
|
|
4920
|
+
}
|
|
4921
|
+
}
|
|
4922
|
+
return out;
|
|
4923
|
+
}
|
|
4732
4924
|
case "recordGet": {
|
|
4733
4925
|
const obj = this.emitExpr(e.obj);
|
|
4734
4926
|
const { ptr, type } = this.recordFieldPtr(obj.name, e.shapeId, e.field);
|
|
@@ -5605,7 +5797,48 @@ class LlEmitter {
|
|
|
5605
5797
|
sourceArgs.set(abiIndex, arg);
|
|
5606
5798
|
if (isFfiCallbackParam(param))
|
|
5607
5799
|
callbackArgs.set(param.callback.id, arg);
|
|
5800
|
+
if (isFfiReleaseParam(param))
|
|
5801
|
+
callbackArgs.set(param.callback.release, arg);
|
|
5608
5802
|
});
|
|
5803
|
+
const { registrations: retainedRegistrations, releases: retainedReleases } = collectFfiRetainedOps(entry, callbackArgs, (binding, id) => this.ffiCallbackAdapter(binding, id));
|
|
5804
|
+
if (retainedRegistrations.length > 0) {
|
|
5805
|
+
this.declare(`declare void @scr_ffi_retain(ptr, ptr)`);
|
|
5806
|
+
if (retainedRegistrations.some((registration) => registration.global !== null)) {
|
|
5807
|
+
this.declare(`declare void @scr_ffi_retain_slot(ptr, ptr, ptr)`);
|
|
5808
|
+
this.declare(`declare void @scr_ffi_commit_slot(ptr, ptr)`);
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5811
|
+
if (retainedReleases.length > 0) {
|
|
5812
|
+
this.declare(`declare void @scr_ffi_require(ptr, ptr)`);
|
|
5813
|
+
if (retainedReleases.some((release) => release.foreign)) {
|
|
5814
|
+
this.declare(`declare void @scr_ffi_require_foreign(ptr, ptr)`);
|
|
5815
|
+
}
|
|
5816
|
+
}
|
|
5817
|
+
// Pin before registration. Raw retained descriptors are native
|
|
5818
|
+
// singletons: the incoming closure is pinned (and an EMPTY slot
|
|
5819
|
+
// armed) before the native set call, but a replaced registration
|
|
5820
|
+
// stays live and dispatching until the call returns — a native
|
|
5821
|
+
// setter may flush the outgoing callback one last time mid-replace.
|
|
5822
|
+
// scr_ffi_commit_slot below repoints the slot and retires the
|
|
5823
|
+
// superseded pins after the call.
|
|
5824
|
+
for (const registration of retainedRegistrations) {
|
|
5825
|
+
if (registration.global !== null) {
|
|
5826
|
+
B.line(`call void @scr_ffi_retain_slot(ptr @${registration.table}, ptr @${registration.global}, ptr ${registration.callback.name})`);
|
|
5827
|
+
}
|
|
5828
|
+
else if (registration.foreign) {
|
|
5829
|
+
this.declare(`declare void @scr_ffi_retain_foreign(ptr, ptr)`);
|
|
5830
|
+
B.line(`call void @scr_ffi_retain_foreign(ptr @${registration.table}, ptr ${registration.callback.name})`);
|
|
5831
|
+
}
|
|
5832
|
+
else {
|
|
5833
|
+
B.line(`call void @scr_ffi_retain(ptr @${registration.table}, ptr ${registration.callback.name})`);
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
// Validate releases BEFORE the native removal call runs: a bogus
|
|
5837
|
+
// release traps without native code observing any side effect. The
|
|
5838
|
+
// registration itself is unpinned only after the call returns.
|
|
5839
|
+
for (const release of retainedReleases) {
|
|
5840
|
+
B.line(`call void @scr_ffi_require${release.foreign ? "_foreign" : ""}(ptr @${release.table}, ptr ${release.callback.name})`);
|
|
5841
|
+
}
|
|
5609
5842
|
const rawContexts = [];
|
|
5610
5843
|
for (const param of entry.params) {
|
|
5611
5844
|
if (!isFfiCallbackParam(param))
|
|
@@ -5628,6 +5861,13 @@ class LlEmitter {
|
|
|
5628
5861
|
nativeArgs.push(`ptr @${adapter.symbol}`);
|
|
5629
5862
|
return;
|
|
5630
5863
|
}
|
|
5864
|
+
if (isFfiReleaseParam(param)) {
|
|
5865
|
+
const { binding, id } = parseFfiCallbackKey(param.callback.release);
|
|
5866
|
+
const adapter = this.ffiCallbackAdapter(binding, id);
|
|
5867
|
+
nativeParamTypes.push("ptr");
|
|
5868
|
+
nativeArgs.push(`ptr @${adapter.symbol}`);
|
|
5869
|
+
return;
|
|
5870
|
+
}
|
|
5631
5871
|
if (isFfiContextParam(param)) {
|
|
5632
5872
|
const callback = callbackArgs.get(param.context);
|
|
5633
5873
|
if (!callback)
|
|
@@ -5713,10 +5953,30 @@ class LlEmitter {
|
|
|
5713
5953
|
B.line(`store ptr ${saved.previous}, ptr @${saved.tls}`);
|
|
5714
5954
|
}
|
|
5715
5955
|
};
|
|
5716
|
-
const
|
|
5956
|
+
const finishRetainedReleases = () => {
|
|
5957
|
+
// Commit raw replacements first (repoint the slot, retire the
|
|
5958
|
+
// superseded pins), then unpin explicit releases — the runtime
|
|
5959
|
+
// disarms the slot itself when the released closure holds it.
|
|
5960
|
+
for (const registration of retainedRegistrations) {
|
|
5961
|
+
if (registration.global !== null) {
|
|
5962
|
+
B.line(`call void @scr_ffi_commit_slot(ptr @${registration.table}, ptr ${registration.callback.name})`);
|
|
5963
|
+
}
|
|
5964
|
+
}
|
|
5965
|
+
if (retainedReleases.some((release) => !release.foreign)) {
|
|
5966
|
+
this.declare(`declare void @scr_ffi_release(ptr, ptr)`);
|
|
5967
|
+
}
|
|
5968
|
+
if (retainedReleases.some((release) => release.foreign)) {
|
|
5969
|
+
this.declare(`declare void @scr_ffi_release_foreign(ptr, ptr)`);
|
|
5970
|
+
}
|
|
5971
|
+
for (const release of retainedReleases) {
|
|
5972
|
+
B.line(`call void @scr_ffi_release${release.foreign ? "_foreign" : ""}(ptr @${release.table}, ptr ${release.callback.name})`);
|
|
5973
|
+
}
|
|
5974
|
+
};
|
|
5975
|
+
const callbacksMayThrow = callbackArgs.size > 0 || this.ffiHasRetainedCallback;
|
|
5717
5976
|
if (entry.returns === "void") {
|
|
5718
5977
|
B.line(call);
|
|
5719
5978
|
restoreRawContexts();
|
|
5979
|
+
finishRetainedReleases();
|
|
5720
5980
|
if (callbacksMayThrow)
|
|
5721
5981
|
this.emitPendingCheck();
|
|
5722
5982
|
return { name: "", type: e.type };
|
|
@@ -5724,6 +5984,7 @@ class LlEmitter {
|
|
|
5724
5984
|
const raw = B.tmp();
|
|
5725
5985
|
B.line(`${raw} = ${call}`);
|
|
5726
5986
|
restoreRawContexts();
|
|
5987
|
+
finishRetainedReleases();
|
|
5727
5988
|
if (entry.returns === "f64") {
|
|
5728
5989
|
const result = { name: raw, type: e.type };
|
|
5729
5990
|
if (callbacksMayThrow)
|