@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.
Files changed (120) hide show
  1. package/dist/backend/cc-cache.test.js +430 -1
  2. package/dist/backend/cc-cache.test.js.map +1 -1
  3. package/dist/backend/cc.d.ts +25 -0
  4. package/dist/backend/cc.js +818 -88
  5. package/dist/backend/cc.js.map +1 -1
  6. package/dist/backend/emission/emit-exprs.js +79 -3
  7. package/dist/backend/emission/emit-exprs.js.map +1 -1
  8. package/dist/backend/emission/emit-shapes.js +37 -4
  9. package/dist/backend/emission/emit-shapes.js.map +1 -1
  10. package/dist/backend/emission/emitter.d.ts +23 -7
  11. package/dist/backend/emission/emitter.js +171 -27
  12. package/dist/backend/emission/emitter.js.map +1 -1
  13. package/dist/backend/emission/may-throw.js +7 -4
  14. package/dist/backend/emission/may-throw.js.map +1 -1
  15. package/dist/backend/ffi-callbacks.d.ts +38 -1
  16. package/dist/backend/ffi-callbacks.js +76 -3
  17. package/dist/backend/ffi-callbacks.js.map +1 -1
  18. package/dist/backend/library-identity.d.ts +22 -0
  19. package/dist/backend/library-identity.js +103 -0
  20. package/dist/backend/library-identity.js.map +1 -0
  21. package/dist/backend/llvm/emitter.d.ts +3 -0
  22. package/dist/backend/llvm/emitter.js +278 -17
  23. package/dist/backend/llvm/emitter.js.map +1 -1
  24. package/dist/backend/llvm/shapes.d.ts +1 -0
  25. package/dist/backend/llvm/shapes.js +25 -1
  26. package/dist/backend/llvm/shapes.js.map +1 -1
  27. package/dist/backend/mangle.d.ts +1 -0
  28. package/dist/backend/mangle.js +3 -0
  29. package/dist/backend/mangle.js.map +1 -1
  30. package/dist/backend/object-localize.d.ts +5 -5
  31. package/dist/backend/object-localize.js +19 -16
  32. package/dist/backend/object-localize.js.map +1 -1
  33. package/dist/backend/object-localize.test.js +37 -13
  34. package/dist/backend/object-localize.test.js.map +1 -1
  35. package/dist/diagnostics/diagnostic.js +2 -0
  36. package/dist/diagnostics/diagnostic.js.map +1 -1
  37. package/dist/ffi/profile.d.ts +17 -5
  38. package/dist/ffi/profile.js +169 -22
  39. package/dist/ffi/profile.js.map +1 -1
  40. package/dist/frontend/input-tracker.d.ts +82 -0
  41. package/dist/frontend/input-tracker.js +380 -0
  42. package/dist/frontend/input-tracker.js.map +1 -0
  43. package/dist/frontend/input-tracker.test.d.ts +1 -0
  44. package/dist/frontend/input-tracker.test.js +137 -0
  45. package/dist/frontend/input-tracker.test.js.map +1 -0
  46. package/dist/frontend/lowering/lower-builtins.js +2 -8
  47. package/dist/frontend/lowering/lower-builtins.js.map +1 -1
  48. package/dist/frontend/lowering/lower-calls.d.ts +1 -0
  49. package/dist/frontend/lowering/lower-calls.js +244 -163
  50. package/dist/frontend/lowering/lower-calls.js.map +1 -1
  51. package/dist/frontend/lowering/lower-classes.js +3 -0
  52. package/dist/frontend/lowering/lower-classes.js.map +1 -1
  53. package/dist/frontend/lowering/lower-containers.js +113 -28
  54. package/dist/frontend/lowering/lower-containers.js.map +1 -1
  55. package/dist/frontend/lowering/lower-exprs.js +67 -1
  56. package/dist/frontend/lowering/lower-exprs.js.map +1 -1
  57. package/dist/frontend/lowering/lower-inspect.js +45 -26
  58. package/dist/frontend/lowering/lower-inspect.js.map +1 -1
  59. package/dist/frontend/lowering/lower-modules.js +1 -1
  60. package/dist/frontend/lowering/lower-modules.js.map +1 -1
  61. package/dist/frontend/lowering/lower-stmts.js +48 -3
  62. package/dist/frontend/lowering/lower-stmts.js.map +1 -1
  63. package/dist/frontend/lowering/lowerer.d.ts +79 -29
  64. package/dist/frontend/lowering/lowerer.js +429 -87
  65. package/dist/frontend/lowering/lowerer.js.map +1 -1
  66. package/dist/frontend/npm-static-rewrite.js +25 -26
  67. package/dist/frontend/npm-static-rewrite.js.map +1 -1
  68. package/dist/frontend/npm-static.js +29 -30
  69. package/dist/frontend/npm-static.js.map +1 -1
  70. package/dist/frontend/npm-static.test.d.ts +1 -0
  71. package/dist/frontend/npm-static.test.js +15 -0
  72. package/dist/frontend/npm-static.test.js.map +1 -0
  73. package/dist/frontend/npm.js +5 -17
  74. package/dist/frontend/npm.js.map +1 -1
  75. package/dist/frontend/program.js +2 -6
  76. package/dist/frontend/program.js.map +1 -1
  77. package/dist/frontend/resolve.js +8 -29
  78. package/dist/frontend/resolve.js.map +1 -1
  79. package/dist/frontend/ts7/checker.d.ts +2 -0
  80. package/dist/frontend/ts7/checker.js +20 -3
  81. package/dist/frontend/ts7/checker.js.map +1 -1
  82. package/dist/frontend/ts7/program.js +17 -18
  83. package/dist/frontend/ts7/program.js.map +1 -1
  84. package/dist/frontend/types.d.ts +41 -0
  85. package/dist/frontend/types.js +147 -8
  86. package/dist/frontend/types.js.map +1 -1
  87. package/dist/index.d.ts +1 -1
  88. package/dist/index.js +275 -41
  89. package/dist/index.js.map +1 -1
  90. package/dist/ir/nodes.d.ts +42 -12
  91. package/dist/ir/nodes.js +7 -1
  92. package/dist/ir/nodes.js.map +1 -1
  93. package/dist/ir/serialize.d.ts +1 -1
  94. package/dist/ir/serialize.js +1 -1
  95. package/dist/ir/validate.js +104 -1
  96. package/dist/ir/validate.js.map +1 -1
  97. package/dist/library/early-cache.d.ts +65 -0
  98. package/dist/library/early-cache.js +380 -0
  99. package/dist/library/early-cache.js.map +1 -0
  100. package/dist/library/early-cache.test.d.ts +1 -0
  101. package/dist/library/early-cache.test.js +528 -0
  102. package/dist/library/early-cache.test.js.map +1 -0
  103. package/dist/library/int-infer.js +14 -0
  104. package/dist/library/int-infer.js.map +1 -1
  105. package/dist/library/int-infer.test.js +21 -4
  106. package/dist/library/int-infer.test.js.map +1 -1
  107. package/dist/library/profile.d.ts +3 -0
  108. package/dist/library/profile.js +9 -1
  109. package/dist/library/profile.js.map +1 -1
  110. package/dist/library/semantic-source.d.ts +13 -0
  111. package/dist/library/semantic-source.js +257 -0
  112. package/dist/library/semantic-source.js.map +1 -0
  113. package/dist/library/semantic-source.test.d.ts +1 -0
  114. package/dist/library/semantic-source.test.js +113 -0
  115. package/dist/library/semantic-source.test.js.map +1 -0
  116. package/dist/library/sidecar.d.ts +6 -0
  117. package/dist/library/sidecar.js +11 -0
  118. package/dist/library/sidecar.js.map +1 -1
  119. package/package.json +2 -2
  120. 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":"AAEA,MAAM,CAAC,MAAM,wBAAwB,GAAG,uCAAuC,CAAC;AAChF,MAAM,CAAC,MAAM,sBAAsB,GAAG,qCAAqC,CAAC;AAC5E,MAAM,CAAC,MAAM,2BAA2B,GAAG,mCAAmC,CAAC;AAC/E,MAAM,CAAC,MAAM,yBAAyB,GAAG,iCAAiC,CAAC;AAS3E,SAAS,eAAe,CAAC,QAAyB;IAChD,OAAO,QAAQ,KAAK,GAAG;QACrB,CAAC,CAAC,EAAE,KAAK,EAAE,wBAAwB,EAAE,GAAG,EAAE,sBAAsB,EAAE;QAClE,CAAC,CAAC,EAAE,KAAK,EAAE,2BAA2B,EAAE,GAAG,EAAE,yBAAyB,EAAE,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,QAAyB,EACzB,QAA+B,EAC/B,iBAAiB,GAAG,IAAI;IAExB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IACD,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;QACrB,OAAO;YACL,wBAAwB;YACxB,YAAY,QAAQ,CAAC,aAAa,UAAU;YAC5C,uBAAuB,QAAQ,CAAC,OAAO,IAAI;YAC3C,GAAG;YACH,EAAE;YACF,YAAY,QAAQ,CAAC,gBAAgB,UAAU;YAC/C,YAAY,QAAQ,CAAC,UAAU,IAAI;YACnC,GAAG;YACH,EAAE;YACF,sBAAsB;SACvB,CAAC;IACJ,CAAC;IACD,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,KAAK,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IACpF,OAAO;QACL,2BAA2B;QAC3B,eAAe,QAAQ,CAAC,aAAa,MAAM,iBAAiB,mCAAmC,QAAQ,CAAC,OAAO,EAAE;QACjH,QAAQ;QACR,aAAa,aAAa,EAAE;QAC5B,GAAG;QACH,EAAE;QACF,eAAe,QAAQ,CAAC,gBAAgB,MAAM,iBAAiB,kCAAkC;QACjG,QAAQ;QACR,aAAa,QAAQ,CAAC,UAAU,EAAE;QAClC,GAAG;QACH,EAAE;QACF,yBAAyB;KAC1B,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CACtB,MAAc,EACd,QAAyB;IAEzB,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC;IAC3C,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3B,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IAC7E,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3D,IAAI,QAAQ,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9F,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;AAC/C,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,sBAAsB,CACpC,MAAc,EACd,QAAyB,EACzB,QAA+B;IAE/B,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAClD,IAAI,OAAO,KAAK,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IAC5F,MAAM,WAAW,GAAG,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5E,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,GAAG,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAClF,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,oBAAoB,CAAC,UAAkB;IAC9C,wEAAwE;IACxE,yEAAyE;IACzE,sEAAsE;IACtE,sEAAsE;IACtE,0EAA0E;IAC1E,OAAO,IAAI,MAAM,CACf,SAAS,aAAa,CAAC,UAAU,CAAC,cAAc;QAC9C,uDAAuD,EACzD,GAAG,CACJ,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,2BAA2B,CACzC,MAAc,EACd,UAAkB,EAClB,UAAoC;IAEpC,OAAO,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,IAAY,EAAE,EAAE,CAC/E,OAAO,UAAU,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,0BAA0B,CAAC,MAAc,EAAE,UAAkB;IAC3E,OAAO,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED;;6EAE6E;AAC7E,MAAM,UAAU,oBAAoB,CAClC,MAAc,EACd,QAAyB;IAEzB,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAClD,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IACpC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC;IAC9B,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACtE,IAAI,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC5C,2EAA2E;IAC3E,yEAAyE;IACzE,2EAA2E;IAC3E,IAAI,MAAM,KAAK,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACxD,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AACvC,CAAC"}
@@ -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 { 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 { 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
- const params = cb.params.map((param, i) => isFfiContextParam(param) ? `ptr %ctx` : `${ffiNativeTypeLl(param)} %a${i}`);
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
- defs.push(`define internal ${ret} @${adapter.symbol}(${params.join(", ")}) ${FN_ATTRS} {`, `entry:`, adapter.tls === null ? ` %cb = getelementptr inbounds i8, ptr %ctx, i64 0` : ` %cb = load ptr, ptr @${adapter.tls}`, ` %missing = icmp eq ptr %cb, null`, ` br i1 %missing, label %expired, label %ready`, `expired:`, ` call void @scr_trap(ptr ${expired})`, ` unreachable`, `ready:`, ` %pending = call zeroext i1 @scr_exc_pending()`, ` br i1 %pending, label %skip, label %invoke`, `skip:`, ` ret ${ffiCallbackDummyLl(cb)}`, `invoke:`, ` %fnp = getelementptr inbounds %ScrClosure, ptr %cb, i64 0, i32 1`, ` %fn = load ptr, ptr %fnp`);
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
- if (usesEvents && hasRefGlobals) {
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 (!usesEvents || !hasRefGlobals)
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 && usesEvents && hasRefGlobals) {
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 && usesEvents && hasRefGlobals;
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
- const buildId = BigInt.asIntN(64, BigInt(`0x${lib.identity.buildId}`)).toString();
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 callbacksMayThrow = callbackArgs.size > 0;
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)