@scriptc/compiler 0.3.0 → 0.3.1
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 +5 -2
- package/dist/backend/llvm/bigint-wrap.d.ts +4 -0
- package/dist/backend/llvm/bigint-wrap.js +590 -0
- package/dist/backend/llvm/bigint-wrap.js.map +1 -0
- package/dist/backend/llvm/blocks.d.ts +2 -0
- package/dist/backend/llvm/blocks.js +4 -0
- package/dist/backend/llvm/blocks.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.js +15 -4
- package/dist/backend/llvm/call-lifetimes.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.test.js +14 -3
- package/dist/backend/llvm/call-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/checked-narrows.test.js +1 -1
- package/dist/backend/llvm/checked-narrows.test.js.map +1 -1
- package/dist/backend/llvm/classes.js +57 -12
- package/dist/backend/llvm/classes.js.map +1 -1
- package/dist/backend/llvm/classes.test.js +4 -4
- package/dist/backend/llvm/classes.test.js.map +1 -1
- package/dist/backend/llvm/common.d.ts +7 -5
- package/dist/backend/llvm/common.js +7 -5
- package/dist/backend/llvm/common.js.map +1 -1
- package/dist/backend/llvm/constant-callbacks.js +10 -28
- package/dist/backend/llvm/constant-callbacks.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.js +133 -45
- package/dist/backend/llvm/dense-array-access.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.test.js +53 -1
- package/dist/backend/llvm/dense-array-access.test.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +2 -0
- package/dist/backend/llvm/dyn.js +4 -0
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +120 -6
- package/dist/backend/llvm/emitter.js +676 -77
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.test.js +14 -7
- package/dist/backend/llvm/emitter.test.js.map +1 -1
- package/dist/backend/llvm/expr-async.js +120 -43
- package/dist/backend/llvm/expr-async.js.map +1 -1
- package/dist/backend/llvm/expr-async.test.js +1 -1
- package/dist/backend/llvm/expr-async.test.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.js +51 -36
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-callbacks.js +72 -3
- package/dist/backend/llvm/expr-callbacks.js.map +1 -1
- package/dist/backend/llvm/expr-calls.d.ts +1 -1
- package/dist/backend/llvm/expr-calls.js +159 -30
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-calls.test.d.ts +1 -0
- package/dist/backend/llvm/expr-calls.test.js +132 -0
- package/dist/backend/llvm/expr-calls.test.js.map +1 -0
- package/dist/backend/llvm/expr-containers.js +13 -0
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-context.d.ts +2 -0
- package/dist/backend/llvm/expr-dispatch.js +3 -0
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +26 -4
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.d.ts +7 -0
- package/dist/backend/llvm/expr-primitives.js +80 -22
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.test.js +95 -5
- package/dist/backend/llvm/expr-primitives.test.js.map +1 -1
- package/dist/backend/llvm/expr-records.d.ts +9 -0
- package/dist/backend/llvm/expr-records.js +134 -16
- package/dist/backend/llvm/expr-records.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.d.ts +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +80 -1
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.test.js +50 -1
- package/dist/backend/llvm/expr-stream-bridges.test.js.map +1 -1
- package/dist/backend/llvm/int32-abi.d.ts +31 -0
- package/dist/backend/llvm/int32-abi.js +62 -0
- package/dist/backend/llvm/int32-abi.js.map +1 -0
- package/dist/backend/llvm/int32-abi.test.d.ts +1 -0
- package/dist/backend/llvm/int32-abi.test.js +107 -0
- package/dist/backend/llvm/int32-abi.test.js.map +1 -0
- package/dist/backend/llvm/int32-locals.d.ts +30 -0
- package/dist/backend/llvm/int32-locals.js +95 -0
- package/dist/backend/llvm/int32-locals.js.map +1 -0
- package/dist/backend/llvm/integer-values.d.ts +4 -0
- package/dist/backend/llvm/integer-values.js +30 -1
- package/dist/backend/llvm/integer-values.js.map +1 -1
- package/dist/backend/llvm/integer-views.d.ts +8 -1
- package/dist/backend/llvm/integer-views.js +47 -2
- package/dist/backend/llvm/integer-views.js.map +1 -1
- package/dist/backend/llvm/lazy-captures.d.ts +16 -0
- package/dist/backend/llvm/lazy-captures.js +88 -2
- package/dist/backend/llvm/lazy-captures.js.map +1 -1
- package/dist/backend/llvm/lazy-captures.test.js +48 -0
- package/dist/backend/llvm/lazy-captures.test.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +14 -0
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +3 -0
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +7 -0
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.js +21 -2
- package/dist/backend/llvm/local-array-reads.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.test.js +3 -2
- package/dist/backend/llvm/local-array-reads.test.js.map +1 -1
- package/dist/backend/llvm/local-union-storage.test.js +2 -2
- package/dist/backend/llvm/local-union-storage.test.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.test.js +2 -2
- package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/reference-effects.js +2 -0
- package/dist/backend/llvm/reference-effects.js.map +1 -1
- package/dist/backend/llvm/reference-writes.d.ts +3 -0
- package/dist/backend/llvm/reference-writes.js +10 -2
- package/dist/backend/llvm/reference-writes.js.map +1 -1
- package/dist/backend/llvm/regex-matchers.d.ts +39 -0
- package/dist/backend/llvm/regex-matchers.js +1649 -0
- package/dist/backend/llvm/regex-matchers.js.map +1 -0
- package/dist/backend/llvm/regex-matchers.test.d.ts +1 -0
- package/dist/backend/llvm/regex-matchers.test.js +96 -0
- package/dist/backend/llvm/regex-matchers.test.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +36 -1
- package/dist/backend/llvm/shapes.js +127 -14
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/stack-unions.test.js +1 -1
- package/dist/backend/llvm/stack-unions.test.js.map +1 -1
- package/dist/backend/llvm/string-construction.test.js +2 -2
- package/dist/backend/llvm/string-construction.test.js.map +1 -1
- package/dist/backend/llvm/string-lifetimes.test.js +5 -5
- package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/string-literal-equality.d.ts +22 -0
- package/dist/backend/llvm/string-literal-equality.js +78 -0
- package/dist/backend/llvm/string-literal-equality.js.map +1 -0
- package/dist/backend/llvm/switch-dispatch.js +59 -13
- package/dist/backend/llvm/switch-dispatch.js.map +1 -1
- package/dist/backend/llvm/switch-dispatch.test.js +71 -6
- package/dist/backend/llvm/switch-dispatch.test.js.map +1 -1
- package/dist/backend/llvm/union-retype.test.d.ts +1 -0
- package/dist/backend/llvm/union-retype.test.js +229 -0
- package/dist/backend/llvm/union-retype.test.js.map +1 -0
- package/dist/backend/llvm/wrapping-sums.d.ts +17 -0
- package/dist/backend/llvm/wrapping-sums.js +113 -0
- package/dist/backend/llvm/wrapping-sums.js.map +1 -0
- package/dist/backend/loop-polls.js +1 -0
- package/dist/backend/loop-polls.js.map +1 -1
- package/dist/backend/mangle.d.ts +9 -0
- package/dist/backend/mangle.js +15 -0
- package/dist/backend/mangle.js.map +1 -1
- package/dist/backend/may-throw.js +6 -3
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/runtime-abi.d.ts +2 -2
- package/dist/backend/runtime-abi.js +2 -2
- package/dist/backend/runtime-pack-targets.test.js +1 -1
- package/dist/backend/runtime-pack.test.js +1 -1
- package/dist/contracts/check.js +4 -0
- package/dist/contracts/check.js.map +1 -1
- package/dist/frontend/lowering/array-values.d.ts +14 -0
- package/dist/frontend/lowering/array-values.js +29 -0
- package/dist/frontend/lowering/array-values.js.map +1 -1
- package/dist/frontend/lowering/containers/array-callbacks.js +40 -0
- package/dist/frontend/lowering/containers/array-callbacks.js.map +1 -1
- package/dist/frontend/lowering/containers/array-pipelines.d.ts +54 -0
- package/dist/frontend/lowering/containers/array-pipelines.js +1827 -0
- package/dist/frontend/lowering/containers/array-pipelines.js.map +1 -0
- package/dist/frontend/lowering/containers/exec-result-uses.d.ts +10 -0
- package/dist/frontend/lowering/containers/exec-result-uses.js +88 -0
- package/dist/frontend/lowering/containers/exec-result-uses.js.map +1 -0
- package/dist/frontend/lowering/containers/string-and-regexp.js +15 -7
- package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
- package/dist/frontend/lowering/containers/string-replacement.d.ts +2 -1
- package/dist/frontend/lowering/containers/string-replacement.js +32 -136
- package/dist/frontend/lowering/containers/string-replacement.js.map +1 -1
- package/dist/frontend/lowering/devirtualize.d.ts +33 -0
- package/dist/frontend/lowering/devirtualize.js +199 -0
- package/dist/frontend/lowering/devirtualize.js.map +1 -0
- package/dist/frontend/lowering/expressions/nullish-assignment.js +55 -7
- package/dist/frontend/lowering/expressions/nullish-assignment.js.map +1 -1
- package/dist/frontend/lowering/identifier-uses.d.ts +7 -0
- package/dist/frontend/lowering/identifier-uses.js +23 -0
- package/dist/frontend/lowering/identifier-uses.js.map +1 -0
- package/dist/frontend/lowering/iterator-adapters.js +51 -3
- package/dist/frontend/lowering/iterator-adapters.js.map +1 -1
- package/dist/frontend/lowering/lower-array-sort.js +13 -0
- package/dist/frontend/lowering/lower-array-sort.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +64 -30
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +1 -11
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-console.d.ts +14 -0
- package/dist/frontend/lowering/lower-console.js +195 -0
- package/dist/frontend/lowering/lower-console.js.map +1 -0
- package/dist/frontend/lowering/lower-exprs.js +135 -3
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-functions.js +7 -3
- package/dist/frontend/lowering/lower-functions.js.map +1 -1
- package/dist/frontend/lowering/lower-generators.d.ts +10 -0
- package/dist/frontend/lowering/lower-generators.js +108 -41
- package/dist/frontend/lowering/lower-generators.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +4 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +13 -10
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +25 -4
- package/dist/frontend/lowering/lowerer.js +120 -47
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/nan-coded-reads.d.ts +16 -0
- package/dist/frontend/lowering/nan-coded-reads.js +108 -1
- package/dist/frontend/lowering/nan-coded-reads.js.map +1 -1
- package/dist/frontend/lowering/runtime-optional-bounds.d.ts +4 -0
- package/dist/frontend/lowering/runtime-optional-bounds.js +2 -0
- package/dist/frontend/lowering/runtime-optional-bounds.js.map +1 -1
- package/dist/frontend/lowering/runtime-optional-bounds.test.js +27 -0
- package/dist/frontend/lowering/runtime-optional-bounds.test.js.map +1 -1
- package/dist/frontend/lowering/split-consumers.js +2 -18
- package/dist/frontend/lowering/split-consumers.js.map +1 -1
- package/dist/frontend/lowering/surfaces.js +3 -8
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/lowering/union-retag.d.ts +6 -1
- package/dist/frontend/lowering/union-retag.js +64 -1
- package/dist/frontend/lowering/union-retag.js.map +1 -1
- package/dist/frontend/lowering/union-retag.test.js +46 -0
- package/dist/frontend/lowering/union-retag.test.js.map +1 -1
- package/dist/frontend/type-mapper.js +16 -3
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/ir/builtin-effects.js +2 -0
- package/dist/ir/builtin-effects.js.map +1 -1
- package/dist/ir/builtin-signatures.d.ts +1 -1
- package/dist/ir/builtin-signatures.js +12 -1
- package/dist/ir/builtin-signatures.js.map +1 -1
- package/dist/ir/function-references.d.ts +12 -0
- package/dist/ir/function-references.js +29 -0
- package/dist/ir/function-references.js.map +1 -0
- package/dist/ir/function-references.test.d.ts +1 -0
- package/dist/ir/function-references.test.js +276 -0
- package/dist/ir/function-references.test.js.map +1 -0
- package/dist/ir/int32-slots.d.ts +7 -4
- package/dist/ir/int32-slots.js +214 -49
- package/dist/ir/int32-slots.js.map +1 -1
- package/dist/ir/int32-slots.test.js +38 -1
- package/dist/ir/int32-slots.test.js.map +1 -1
- package/dist/ir/integer-ranges.d.ts +4 -4
- package/dist/ir/integer-ranges.js +89 -64
- package/dist/ir/integer-ranges.js.map +1 -1
- package/dist/ir/integer-ranges.test.js +71 -9
- package/dist/ir/integer-ranges.test.js.map +1 -1
- package/dist/ir/intrinsic-signatures.js +1 -0
- package/dist/ir/intrinsic-signatures.js.map +1 -1
- package/dist/ir/ir.d.ts +52 -6
- package/dist/ir/ir.js +1 -0
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/runtime-features.js +3 -2
- package/dist/ir/runtime-features.js.map +1 -1
- package/dist/ir/traverse.js +4 -0
- package/dist/ir/traverse.js.map +1 -1
- package/dist/ir/validate.js +57 -6
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.js +25 -7
- package/dist/ir/validate.test.js.map +1 -1
- package/package.json +20 -20
- package/surface-manifest.json +1 -17
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { emitLlvmLayouts } from "./layouts.js";
|
|
2
|
+
import { compileRegexMatcher } from "./regex-matchers.js";
|
|
2
3
|
import { FIRST_EMITTED_CYC_KIND, inlineAllocSupported } from "./alloc.js";
|
|
3
4
|
import { llvmBytes as llBytes } from "../literals.js";
|
|
4
5
|
import { InternalCompilerError } from "../../errors.js";
|
|
@@ -16,19 +17,22 @@ import { emitByteWindowGuard, findInitializedByteLoopBindings, matchByteWindow,
|
|
|
16
17
|
import { canDeferSplitPiece, emitSplitSpanNext, materializeSplitPiece, emitSplitCursor, emitSplitNext, emitSplitScratch, emitSplitSnapshot, findPrivateSplitLocals, loadSplitSnapshot, storeSplitSnapshot, stringSplit, } from "./split-loops.js";
|
|
17
18
|
import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
|
|
18
19
|
import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js";
|
|
19
|
-
import { everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
|
|
20
|
+
import { everyExpr, everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
|
|
20
21
|
import { analyzeIntegerRanges, INT32_RANGE } from "../../ir/integer-ranges.js";
|
|
21
22
|
import { unshareNodes } from "../../ir/unshare.js";
|
|
22
23
|
import { analyzeInt32Slots } from "../../ir/int32-slots.js";
|
|
23
24
|
import { analyzeFinalFields, constructorClass } from "../../ir/final-fields.js";
|
|
24
25
|
import { foldConstantGlobals } from "../../ir/constant-globals.js";
|
|
25
26
|
import { findIntegerViews } from "./integer-views.js";
|
|
27
|
+
import { findInt32Abis, INT32_ABI_SUFFIX } from "./int32-abi.js";
|
|
28
|
+
import { findInt32Locals } from "./int32-locals.js";
|
|
29
|
+
import { exactInteger, narrowInteger } from "./integer-values.js";
|
|
26
30
|
import { findInitializerBindings, withInitializerBindings } from "../../ir/initializer-bindings.js";
|
|
27
31
|
import { findConstantNumericTables } from "../../ir/constant-tables.js";
|
|
28
32
|
import { allocateFfiCallbackAdapters, hasForeignFfiCallback, hasRetainedFfiCallback, } from "../ffi-callbacks.js";
|
|
29
33
|
import { RUNTIME_ABI_MARKER } from "../runtime-abi.js";
|
|
30
34
|
import { computeMayThrow } from "../may-throw.js";
|
|
31
|
-
import { mangleArgPack, mangleAsyncSpawn, mangleBorrowedFunction, mangleClassObj, mangleClassStruct, mangleFnClosure, mangleFunction, mangleGenDrop, mangleGenSpawn, mangleGlobal, mangleLocal, mangleRecordStruct, mangleTrampoline, mangleWrapper, mangleVtInstance, } from "../mangle.js";
|
|
35
|
+
import { mangleArgPack, mangleAsyncAdoptCall, mangleAsyncAwaitCall, mangleAsyncSpawn, mangleBorrowedFunction, mangleClassObj, mangleClassStruct, mangleFnClosure, mangleFunction, mangleGenDrop, mangleGenSpawn, mangleGlobal, mangleLocal, mangleRecordStruct, mangleTrampoline, mangleWrapper, mangleBorrowingWrapper, mangleVtInstance, } from "../mangle.js";
|
|
32
36
|
import { analyzeCallLifetimes } from "./call-lifetimes.js";
|
|
33
37
|
import { canStackUnion, emitStackUnion, findLocalStackUnions } from "./stack-unions.js";
|
|
34
38
|
import { allocateLocalUnion, findLocalUnionStorage, storeLocalUnion, } from "./local-union-storage.js";
|
|
@@ -85,6 +89,7 @@ import { NUMBER_MAP_ENTRY_DECL, STRING_MAP_ENTRY_DECL } from "./map-number-looku
|
|
|
85
89
|
import { STRING_READ_HELPERS } from "./string-reads.js";
|
|
86
90
|
import { emitConcatInputs, emitMixedConcat, numberPart, stringParts, } from "./string-construction.js";
|
|
87
91
|
import { arrNewCall, boxAccess, boxNewCall, boxReleaseSym, computeTraced, elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, FN_ATTRS, inlineRcDecls, llFieldType, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
|
|
92
|
+
import { INLINE_LITERAL_BYTES, literalEqualityDefinition } from "./string-literal-equality.js";
|
|
88
93
|
export { LlvmUnsupportedError } from "./unsupported.js";
|
|
89
94
|
function ffiCallbackDummyLl(callback) {
|
|
90
95
|
switch (callback.returns) {
|
|
@@ -220,6 +225,14 @@ export class LlEmitter {
|
|
|
220
225
|
* indices; see ShapeHost.nextCycKind). */
|
|
221
226
|
cycKinds = 0;
|
|
222
227
|
wasi;
|
|
228
|
+
/** Synchronous generator bodies compile to LLVM switched coroutines (a
|
|
229
|
+
* heap frame resumed by an ordinary call) instead of running on a fiber
|
|
230
|
+
* stack. Worker programs keep fibers (context shutdown unwinds suspended
|
|
231
|
+
* generator stacks) and so do debug builds (the debugger reads locals
|
|
232
|
+
* from the native frame). */
|
|
233
|
+
stacklessGenerators = false;
|
|
234
|
+
/** Some function in the module is lowered as a coroutine. */
|
|
235
|
+
usesCoroutines = false;
|
|
223
236
|
/** The target lacks a rounding instruction at its baseline CPU (x86-64
|
|
224
237
|
* before SSE4.1), so `llvm.trunc` would lower to a libm call: emit
|
|
225
238
|
* truncation inline through an integer round trip instead. */
|
|
@@ -239,6 +252,10 @@ export class LlEmitter {
|
|
|
239
252
|
/** Interned string literals: UTF-8 text → { symbol, byte length } —
|
|
240
253
|
* first-use order, the runtime ABI’s determinism discipline. */
|
|
241
254
|
literals = new Map();
|
|
255
|
+
/** Literal equality helpers: literal text → helper symbol (literalEquals). */
|
|
256
|
+
literalEqualities = new Map();
|
|
257
|
+
/** Interned bigint literals: hex magnitude with sign → { symbol, value }. */
|
|
258
|
+
bigintLiterals = new Map();
|
|
242
259
|
/** Symbols of interned constant data, named by content (contentSymbol). */
|
|
243
260
|
contentSymbols = new Set();
|
|
244
261
|
/** Interned unit-armed union instances: "unionId:tag" → symbol — one
|
|
@@ -247,6 +264,10 @@ export class LlEmitter {
|
|
|
247
264
|
unitInstances = new Map();
|
|
248
265
|
/** unionId → symbol of the union's immortal ABSENT field-slot instance. */
|
|
249
266
|
absentInstances = new Map();
|
|
267
|
+
/** unionId → symbol of a flag set once a checked-dynamic cast caches one
|
|
268
|
+
* of the union's boxes. A cached box is refreshed in place, possibly with
|
|
269
|
+
* a tag that another union numbers apart, so it is never shared. */
|
|
270
|
+
retypeGuards = new Map();
|
|
250
271
|
/** The nullable-union ABSENT sentinel was referenced (absentInstanceRef). */
|
|
251
272
|
needsNullableAbsent = false;
|
|
252
273
|
immortalValues = new Set();
|
|
@@ -312,6 +333,9 @@ export class LlEmitter {
|
|
|
312
333
|
lazyCaptureBoxes = new Map();
|
|
313
334
|
lazyStackBoxes = new Map();
|
|
314
335
|
borrowedParameters = new Set();
|
|
336
|
+
/** Captures of the current closure body that its environment holds
|
|
337
|
+
* unchanged for the closure's whole life (LazyCaptures.stableCaptures). */
|
|
338
|
+
stableCaptureIds = new Set();
|
|
315
339
|
/** Parameters proven projection-only: callers may pass stack boxes. */
|
|
316
340
|
projectedParameters = new Set();
|
|
317
341
|
stableCallBindings = new Set();
|
|
@@ -367,6 +391,21 @@ export class LlEmitter {
|
|
|
367
391
|
/** Method names with at least one may-throw implementation — the
|
|
368
392
|
* virtualCall pending check's key. */
|
|
369
393
|
mayThrowMethods = new Set();
|
|
394
|
+
/** Async functions awaited directly at some `await f(...)` site; each
|
|
395
|
+
* gets an await-call entry (emitAsyncAwaitCall). */
|
|
396
|
+
awaitCallees = new Set();
|
|
397
|
+
/** Async functions returned directly at some `return f(...)` site of an
|
|
398
|
+
* async function; each gets an adopting entry (emitAsyncAwaitCall). */
|
|
399
|
+
adoptCallees = new Set();
|
|
400
|
+
/** The promise a `return` emitted past its finally regions already
|
|
401
|
+
* produced for its adopting await (borrowed from the return's parked
|
|
402
|
+
* slot); that await takes it instead of evaluating its operand. */
|
|
403
|
+
adoptedOperand = null;
|
|
404
|
+
takeAdoptedOperand() {
|
|
405
|
+
const value = this.adoptedOperand;
|
|
406
|
+
this.adoptedOperand = null;
|
|
407
|
+
return value;
|
|
408
|
+
}
|
|
370
409
|
/** setTimeout and friends appeared somewhere: main must run the event
|
|
371
410
|
* loop even in programs with no async functions. */
|
|
372
411
|
usesTimers = false;
|
|
@@ -440,6 +479,8 @@ export class LlEmitter {
|
|
|
440
479
|
initializerBindings;
|
|
441
480
|
/** Whole-program int32 storage and parameter/result facts. */
|
|
442
481
|
int32Slots;
|
|
482
|
+
/** Functions whose direct calls use i32 parameters or results. */
|
|
483
|
+
int32Abis;
|
|
443
484
|
/** Immutable module constants (contract types) whose graphs become
|
|
444
485
|
* immortal once initialized: their reads need no reference counting. */
|
|
445
486
|
immortalGlobals;
|
|
@@ -457,10 +498,17 @@ export class LlEmitter {
|
|
|
457
498
|
* not a constructor (where they change), not async or a generator. */
|
|
458
499
|
finalFieldBorrows = false;
|
|
459
500
|
integerRanges = new Map();
|
|
501
|
+
/** Sums whose result only feeds 32-bit conversions (wrapping-sums.ts). */
|
|
502
|
+
wrappingSums = new Set();
|
|
460
503
|
byteWindowsEnabled = true;
|
|
461
504
|
byteWindowEntries = new Map();
|
|
462
505
|
bytesBounds = new Set();
|
|
463
506
|
integerViews = new Map();
|
|
507
|
+
/** Number locals of the current function stored only as an i32
|
|
508
|
+
* (int32-locals.ts); their slots are `alloca i32`. */
|
|
509
|
+
int32Locals = new Map();
|
|
510
|
+
/** The written values (and `++`/`--` nodes) the int32 proof covers. */
|
|
511
|
+
int32LocalWrites = new Set();
|
|
464
512
|
/** Enclosing try-with-FINALLY regions, innermost last: a `return`
|
|
465
513
|
* inside one runs every crossed finally (innermost first) before the
|
|
466
514
|
* actual ret — the runtime ABI’s pending-return path, with the finally
|
|
@@ -480,9 +528,12 @@ export class LlEmitter {
|
|
|
480
528
|
/** Return type of the function being emitted — the unwind path returns
|
|
481
529
|
* a dummy of this type (never read: callers check the flag first). */
|
|
482
530
|
currentReturnType = VOID;
|
|
483
|
-
/**
|
|
484
|
-
|
|
485
|
-
|
|
531
|
+
/** The current body returns its number result as i32. */
|
|
532
|
+
currentInt32Result = false;
|
|
533
|
+
/** Active only while emitting a body lowered with LLVM's
|
|
534
|
+
* switched-coroutine intrinsics (wasm32 async bodies and generators,
|
|
535
|
+
* native synchronous generators; see coroLowered). */
|
|
536
|
+
currentCoro = null;
|
|
486
537
|
/** The generator channels of the function being emitted (null outside
|
|
487
538
|
* generator bodies): yieldExpr emission reads them, and emitTryCatch's
|
|
488
539
|
* catch prologue emits the GENRET sentinel re-unwind exactly here. */
|
|
@@ -505,6 +556,7 @@ export class LlEmitter {
|
|
|
505
556
|
this.constantNumericTables = findConstantNumericTables(mod);
|
|
506
557
|
this.initializerBindings = findInitializerBindings(mod);
|
|
507
558
|
this.int32Slots = analyzeInt32Slots(mod);
|
|
559
|
+
this.int32Abis = this.debug === null ? findInt32Abis(mod, this.int32Slots) : new Map();
|
|
508
560
|
this.immortalGlobals = new Set(mod.contracts?.immortalGlobals ?? []);
|
|
509
561
|
this.programLifetime = new Set(mod.contracts?.programLifetime ?? []);
|
|
510
562
|
this.finalFields = analyzeFinalFields(mod);
|
|
@@ -512,6 +564,7 @@ export class LlEmitter {
|
|
|
512
564
|
this.ffiExtendNarrowIntegers =
|
|
513
565
|
options.wasi === true || ffiExtendsNarrowIntegers(options.targetTriple);
|
|
514
566
|
this.wasi = options.wasi === true;
|
|
567
|
+
this.stacklessGenerators = !this.wasi && mod.workers !== true && this.debug === null;
|
|
515
568
|
// An empty triple targets the host, as for executableTls below: the
|
|
516
569
|
// Node-hosted CLI passes none, the native CLI passes its toolchain's, and
|
|
517
570
|
// both must emit the same module for the same target.
|
|
@@ -663,6 +716,7 @@ export class LlEmitter {
|
|
|
663
716
|
internLiteral: (text) => this.internLiteral(text),
|
|
664
717
|
cstr: (text) => this.cstr(text),
|
|
665
718
|
unitInstanceRef: (unionId, tag) => this.unitInstanceRef(unionId, tag),
|
|
719
|
+
retypeGuardRef: (unionId) => this.retypeGuardRef(unionId),
|
|
666
720
|
fieldAbsentTestIn: (B, value, unionId) => this.fieldAbsentTestIn(B, value, unionId),
|
|
667
721
|
absentInstanceRef: (unionId) => this.absentInstanceRef(unionId),
|
|
668
722
|
liveDynRefAdapter: (type) => this.liveDynRefAdapter(type),
|
|
@@ -1351,6 +1405,16 @@ export class LlEmitter {
|
|
|
1351
1405
|
];
|
|
1352
1406
|
if (this.constantNumericTables.size > 0)
|
|
1353
1407
|
this.declare(`declare double @scr_arr_get_number(ptr, double)`);
|
|
1408
|
+
// Native matchers of the regex literals inside the compiled subset.
|
|
1409
|
+
const regexMatchers = new Map();
|
|
1410
|
+
for (const [key, re] of this.regexInstances) {
|
|
1411
|
+
const code = compileRegexMatcher(`${re.sym}_match`, re.pattern, re.flags, this.sizeType, !this.wasi);
|
|
1412
|
+
if (code === null)
|
|
1413
|
+
continue;
|
|
1414
|
+
regexMatchers.set(key, code);
|
|
1415
|
+
for (const decl of code.decls)
|
|
1416
|
+
this.declare(decl);
|
|
1417
|
+
}
|
|
1354
1418
|
const out = [
|
|
1355
1419
|
`; Generated by scriptc (LLVM backend) from ${this.mod.sourceFile}. Do not edit.`,
|
|
1356
1420
|
``,
|
|
@@ -1364,17 +1428,17 @@ export class LlEmitter {
|
|
|
1364
1428
|
`%ScrLogArg = type { i32, i64 }`,
|
|
1365
1429
|
`%ScrVt = type { ${this.sizeType}, ${this.sizeType}, ptr }`,
|
|
1366
1430
|
`%ScrUnion = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
|
|
1367
|
-
`%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32, i32, ptr }`,
|
|
1431
|
+
`%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32, i32, ptr, ptr }`,
|
|
1368
1432
|
`%ScrFfiTable = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, i8, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`,
|
|
1369
|
-
`%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr, ptr }`,
|
|
1433
|
+
`%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr, ptr, ptr }`,
|
|
1370
1434
|
// ScrArr mirrors scr_runtime.h field-for-field: { rc, len, cap, elem,
|
|
1371
|
-
// cyc, ops, data, present, ext } (field numbers in
|
|
1372
|
-
// where ops points at the element type's constant RC descriptor
|
|
1435
|
+
// cyc, spare, filled, ops, data, present, ext } (field numbers in
|
|
1436
|
+
// SCR_ARR_FIELD), where filled bounds the hole-free prefix, ops points at the element type's constant RC descriptor
|
|
1373
1437
|
// (%ScrArrOps, SCR_ELEM_REF only) and ext at the out-of-line sparse,
|
|
1374
1438
|
// property and metadata storage (NULL until first used). Live dynamic
|
|
1375
1439
|
// stream commits swap its dense, presence and out-of-line storage while
|
|
1376
1440
|
// preserving the target object's identity.
|
|
1377
|
-
`%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType},
|
|
1441
|
+
`%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i8, i8, i16, i32, ptr, ptr, ptr, ptr }`,
|
|
1378
1442
|
`%ScrArrOps = type { ptr, ptr, ptr }`,
|
|
1379
1443
|
// The ScrMap prefix { rc, key_kind, val_kind, val_retain, val_release,
|
|
1380
1444
|
// val_trace, key_retain, key_release, key_trace } — the inline RC
|
|
@@ -1472,6 +1536,26 @@ export class LlEmitter {
|
|
|
1472
1536
|
}
|
|
1473
1537
|
if (this.literals.size > 0)
|
|
1474
1538
|
out.push(``);
|
|
1539
|
+
for (const lit of this.bigintLiterals.values()) {
|
|
1540
|
+
// Immortal ScrBigInt: { rc = SIZE_MAX, len, sign, cap, limbs } with
|
|
1541
|
+
// pointer-sized little-endian limbs (scr_bigint.c); zero keeps one
|
|
1542
|
+
// zero limb so limb 0 is always readable.
|
|
1543
|
+
const size = this.sizeType;
|
|
1544
|
+
const limbBits = size === "i64" ? 64n : 32n;
|
|
1545
|
+
const mask = (1n << limbBits) - 1n;
|
|
1546
|
+
const limbs = [];
|
|
1547
|
+
for (let m = lit.value < 0n ? -lit.value : lit.value; m > 0n; m = m >> limbBits) {
|
|
1548
|
+
limbs.push(`${size} ${m & mask}`);
|
|
1549
|
+
}
|
|
1550
|
+
const len = limbs.length;
|
|
1551
|
+
if (len === 0)
|
|
1552
|
+
limbs.push(`${size} 0`);
|
|
1553
|
+
const sign = lit.value < 0n ? -1 : lit.value > 0n ? 1 : 0;
|
|
1554
|
+
out.push(`@${lit.sym} = internal global { ${size}, ${size}, i32, i32, [${limbs.length} x ${size}] } ` +
|
|
1555
|
+
`{ ${size} -1, ${size} ${len}, i32 ${sign}, i32 ${limbs.length}, [${limbs.length} x ${size}] [${limbs.join(", ")}] }`);
|
|
1556
|
+
}
|
|
1557
|
+
if (this.bigintLiterals.size > 0)
|
|
1558
|
+
out.push(``);
|
|
1475
1559
|
for (const [key, sym] of this.unitInstances) {
|
|
1476
1560
|
// One immortal instance per unit-armed (union, tag): tag set, payload
|
|
1477
1561
|
// slot and RC entry points zero — retain/release/collector all skip
|
|
@@ -1481,6 +1565,13 @@ export class LlEmitter {
|
|
|
1481
1565
|
}
|
|
1482
1566
|
if (this.unitInstances.size > 0)
|
|
1483
1567
|
out.push(``);
|
|
1568
|
+
for (const [unionId, sym] of this.retypeGuards) {
|
|
1569
|
+
// Thread-local like module globals: a thread's cast caches hold only
|
|
1570
|
+
// its own boxes. Never written, the flag folds to false.
|
|
1571
|
+
out.push(`@${sym} = internal ${tl}global i8 0 ; ${unionId} boxes in a cast cache`);
|
|
1572
|
+
}
|
|
1573
|
+
if (this.retypeGuards.size > 0)
|
|
1574
|
+
out.push(``);
|
|
1484
1575
|
for (const [unionId, sym] of this.absentInstances) {
|
|
1485
1576
|
// One immortal ABSENT field-slot instance per undefined-armed union:
|
|
1486
1577
|
// the undefined arm's tag with payload 1 (ordinary unit instances
|
|
@@ -1508,22 +1599,25 @@ export class LlEmitter {
|
|
|
1508
1599
|
}
|
|
1509
1600
|
for (const [key, re] of this.regexInstances) {
|
|
1510
1601
|
// One immortal ScrRegex per (pattern, flags) literal, pointing at
|
|
1511
|
-
// the interned source/flags strings. The bc and
|
|
1512
|
-
// start null (lazy compile, cached by the runtime) — a mutable
|
|
1513
|
-
// global, not constant.
|
|
1514
|
-
|
|
1602
|
+
// the interned source/flags strings. The bc and translated-program
|
|
1603
|
+
// slots start null (lazy compile, cached by the runtime) — a mutable
|
|
1604
|
+
// global, not constant. The last slot is the build-time matcher.
|
|
1605
|
+
const matcher = regexMatchers.has(key) ? `@${re.sym}_match` : "null";
|
|
1606
|
+
out.push(`@${re.sym} = internal ${tl}global %ScrRegex { ${this.sizeType} -1, ptr ${re.src}, ptr ${re.fl}, ptr null, double 0.0, ptr null, ptr null, ptr ${matcher} } ; ${key.replace(/\n/g, "\\n")}`);
|
|
1515
1607
|
}
|
|
1516
1608
|
if (this.regexInstances.size > 0)
|
|
1517
1609
|
out.push(``);
|
|
1610
|
+
for (const code of regexMatchers.values())
|
|
1611
|
+
out.push(...code.lines, ``);
|
|
1518
1612
|
for (const [, inst] of this.templateStringsInstances) {
|
|
1519
1613
|
// One immortal ScrArr per tagged-template site: a [N x ptr] data
|
|
1520
1614
|
// global of interned cooked-string literals, and the ScrArr header
|
|
1521
|
-
// over it (rc == SIZE_MAX, len == cap, SCR_ELEM_STR = 2,
|
|
1522
|
-
// entry points). Every dense slot is present; reads retain
|
|
1523
|
-
// strings — a no-op.
|
|
1615
|
+
// over it (rc == SIZE_MAX, len == cap == filled, SCR_ELEM_STR = 2,
|
|
1616
|
+
// no REF entry points). Every dense slot is present; reads retain
|
|
1617
|
+
// immortal strings — a no-op.
|
|
1524
1618
|
const n = inst.slots.length;
|
|
1525
1619
|
const present = n === 0 ? "zeroinitializer" : `[ ${inst.slots.map(() => "i8 1").join(", ")} ]`;
|
|
1526
|
-
out.push(`@${inst.sym}_data = internal constant [${n} x ptr] [ ${inst.slots.map((s) => `ptr ${s}`).join(", ")} ]`, `@${inst.sym}_present = internal constant [${n} x i8] ${present}`, `@${inst.sym} = internal global %ScrArr { ${this.sizeType} -1, ${this.sizeType} ${n}, ${this.sizeType} ${n},
|
|
1620
|
+
out.push(`@${inst.sym}_data = internal constant [${n} x ptr] [ ${inst.slots.map((s) => `ptr ${s}`).join(", ")} ]`, `@${inst.sym}_present = internal constant [${n} x i8] ${present}`, `@${inst.sym} = internal global %ScrArr { ${this.sizeType} -1, ${this.sizeType} ${n}, ${this.sizeType} ${n}, i8 2, i8 0, i16 0, i32 ${n}, ptr null, ptr @${inst.sym}_data, ptr @${inst.sym}_present, ptr null }`);
|
|
1527
1621
|
}
|
|
1528
1622
|
if (this.templateStringsInstances.size > 0)
|
|
1529
1623
|
out.push(``);
|
|
@@ -1622,7 +1716,7 @@ export class LlEmitter {
|
|
|
1622
1716
|
out.push(line);
|
|
1623
1717
|
out.push(...this.inlineRcTail(flushedDecls));
|
|
1624
1718
|
out.push(`attributes #0 = { sanitize_address }`);
|
|
1625
|
-
if (this.
|
|
1719
|
+
if (this.usesCoroutines)
|
|
1626
1720
|
out.push(`attributes #1 = { sanitize_address presplitcoroutine }`);
|
|
1627
1721
|
if (hasNoInlineRecordClone)
|
|
1628
1722
|
out.push(`attributes #2 = { noinline sanitize_address }`);
|
|
@@ -1737,7 +1831,7 @@ export class LlEmitter {
|
|
|
1737
1831
|
// sanitize_address is inert under the plain pipeline; the sanitized
|
|
1738
1832
|
// lane's -fsanitize=address link activates instrumentation over the
|
|
1739
1833
|
// emitted functions too (the runtime TUs get theirs from clang).
|
|
1740
|
-
`attributes #0 = { sanitize_address }`, ...(this.
|
|
1834
|
+
`attributes #0 = { sanitize_address }`, ...(this.usesCoroutines ? [`attributes #1 = { sanitize_address presplitcoroutine }`] : []), ...(hasNoInlineRecordClone ? [`attributes #2 = { noinline sanitize_address }`] : []), ``);
|
|
1741
1835
|
if (this.mod.workers) {
|
|
1742
1836
|
out.push(`declare void @scr_context_cleanup()`, `declare i32 @scr_worker_argc()`, `declare ptr @scr_worker_argv()`, `define i32 @sc_worker_entry(i32 %root) ${FN_ATTRS} {`, `entry:`, ` %argc = call i32 @scr_worker_argc()`, ` %argv = call ptr @scr_worker_argv()`, ` %result = call i32 @sc_context_entry(i32 %argc, ptr %argv)`, ` ret i32 %result`, `}`, `define i32 @main(i32 %argc, ptr %argv) ${FN_ATTRS} {`, `entry:`, ` %result = call i32 @sc_context_entry(i32 %argc, ptr %argv)`, ` call void @scr_context_cleanup()`, ` ret i32 %result`, `}`);
|
|
1743
1837
|
}
|
|
@@ -2045,6 +2139,9 @@ export class LlEmitter {
|
|
|
2045
2139
|
if (this.decls.has(`declare zeroext i1 @scr_str_eq(ptr, ptr)`)) {
|
|
2046
2140
|
defs.push(`define internal zeroext i1 @sc_str_eq(ptr %a, ptr %b) ${FN_ATTRS} {`, `entry:`, ` %same = icmp eq ptr %a, %b`, ` br i1 %same, label %yes, label %lengths`, `lengths:`, ` %alen.p = getelementptr inbounds %ScrStr, ptr %a, i32 0, i32 1`, ` %blen.p = getelementptr inbounds %ScrStr, ptr %b, i32 0, i32 1`, ` %alen = load ${this.sizeType}, ptr %alen.p`, ` %blen = load ${this.sizeType}, ptr %blen.p`, ` %samelen = icmp eq ${this.sizeType} %alen, %blen`, ` br i1 %samelen, label %bytes, label %no`, `bytes:`, ` %eq = call zeroext i1 @scr_str_eq(ptr %a, ptr %b)`, ` ret i1 %eq`, `yes:`, ` ret i1 true`, `no:`, ` ret i1 false`, `}`, ``);
|
|
2047
2141
|
}
|
|
2142
|
+
for (const [text, name] of this.literalEqualities) {
|
|
2143
|
+
defs.push(...literalEqualityDefinition(name, Buffer.from(text, "utf8"), this.internLiteral(text), this.sizeType, FN_ATTRS), ``);
|
|
2144
|
+
}
|
|
2048
2145
|
// String length and charCodeAt read proven-ASCII strings inline: bit 31
|
|
2049
2146
|
// of the capacity word (SCR_STR_ASCII_BIT, the low half's sign bit on
|
|
2050
2147
|
// little-endian targets) makes UTF-16 units bytes. Unproven strings and
|
|
@@ -2128,7 +2225,12 @@ export class LlEmitter {
|
|
|
2128
2225
|
// never the inner return type.
|
|
2129
2226
|
const ret = fn.async === true || fn.generator !== undefined ? "ptr" : this.llType(fn.returnType);
|
|
2130
2227
|
const call = `call ${ret} @${this.callTarget(name)}(${args})`;
|
|
2131
|
-
|
|
2228
|
+
const lending = this.lendsEveryArgument(name);
|
|
2229
|
+
out.push(`define internal ${ret} @${mangleWrapper(name)}(ptr %env${params.length ? ", " + params.join(", ") : ""}) ${FN_ATTRS} { ; ${name} as a value`, `entry:`, ret === "void" ? ` ${call}` : ` %r = ${call}`, ret === "void" ? ` ret void` : ` ret ${ret} %r`, `}`, `@${mangleFnClosure(name)} = internal ${this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : ""}global %ScrClosure { ${this.sizeType} -1, ptr @${mangleWrapper(name)}, ${this.sizeType} 0, ptr null, i32 ${(fn.generator ? 1 : 0) + (fn.async ? 2 : 0) + (fn.ownsPrototype ? 4 : 0)}, i32 0, ptr null, ptr ${lending ? `@${mangleBorrowingWrapper(name)}` : "null"} }`, ``);
|
|
2230
|
+
if (lending)
|
|
2231
|
+
out.push(`define internal ${ret} @${mangleBorrowingWrapper(name)}(ptr %env${params.length ? ", " + params.join(", ") : ""}) ${FN_ATTRS} { ; ${name} as a value, lending its arguments`, `entry:`, ret === "void"
|
|
2232
|
+
? ` call void @${mangleBorrowedFunction(name)}(${args})`
|
|
2233
|
+
: ` %r = call ${ret} @${mangleBorrowedFunction(name)}(${args})`, ret === "void" ? ` ret void` : ` ret ${ret} %r`, `}`, ``);
|
|
2132
2234
|
}
|
|
2133
2235
|
return out;
|
|
2134
2236
|
}
|
|
@@ -2141,8 +2243,14 @@ export class LlEmitter {
|
|
|
2141
2243
|
const fieldTys = [...(lifted ? ["ptr"] : []), ...fn.params.map((p) => this.llType(p.type))];
|
|
2142
2244
|
const definitions = [`%${pack} = type { ${fieldTys.join(", ") || "i8"} } ; ${fn.name} args`];
|
|
2143
2245
|
const sizeOf = `ptrtoint (ptr getelementptr (%${pack}, ptr null, i32 1) to ${this.sizeType})`;
|
|
2144
|
-
|
|
2145
|
-
|
|
2246
|
+
// Async argument packs live from the spawn to the trampoline's first
|
|
2247
|
+
// instructions, so they take the runtime's small-block allocator.
|
|
2248
|
+
// Generator packs keep malloc: the runtime frees never-started ones.
|
|
2249
|
+
const smallPack = fn.generator === undefined;
|
|
2250
|
+
const packAlloc = smallPack ? "scr_rt_calloc" : "malloc";
|
|
2251
|
+
const packFree = smallPack ? "scr_rt_free" : "free";
|
|
2252
|
+
this.declare(`declare void @${packFree}(ptr)`);
|
|
2253
|
+
this.declare(`declare ptr @${packAlloc}(${this.sizeType})`);
|
|
2146
2254
|
const tr = [
|
|
2147
2255
|
`define internal void @${mangleTrampoline(fn.name)}(ptr %self, ptr %ap) ${FN_ATTRS} {`,
|
|
2148
2256
|
`entry:`,
|
|
@@ -2152,17 +2260,17 @@ export class LlEmitter {
|
|
|
2152
2260
|
tr.push(` %fp${i} = getelementptr inbounds %${pack}, ptr %ap, i64 0, i32 ${i}`, ` %a${i} = load ${ty}, ptr %fp${i}`);
|
|
2153
2261
|
loads.push(`${ty} %a${i}`);
|
|
2154
2262
|
});
|
|
2155
|
-
tr.push(` call void
|
|
2263
|
+
tr.push(` call void @${packFree}(ptr %ap)`);
|
|
2156
2264
|
const ret = fn.returnType;
|
|
2157
2265
|
const retTy = this.llType(ret);
|
|
2158
2266
|
const bodyCall = `call ${retTy} @${mangleFunction(fn.name)}(${loads.join(", ")})`;
|
|
2159
2267
|
tr.push(retTy === "void" ? ` ${bodyCall}` : ` %r = ${bodyCall}`);
|
|
2160
|
-
if (lifted && !this.
|
|
2268
|
+
if (lifted && !this.coroLowered(fn)) {
|
|
2161
2269
|
tr.push(` call void @scr_closure_release(ptr %a0)`);
|
|
2162
2270
|
}
|
|
2163
2271
|
const spawnParams = fieldTys.map((ty, i) => `${ty} %a${i}`);
|
|
2164
2272
|
const argPackLines = [
|
|
2165
|
-
` %ap = call ptr
|
|
2273
|
+
` %ap = call ptr @${packAlloc}(${this.sizeType} ${sizeOf})`,
|
|
2166
2274
|
` %isnull = icmp eq ptr %ap, null`,
|
|
2167
2275
|
` br i1 %isnull, label %oom, label %ok`,
|
|
2168
2276
|
`oom:`,
|
|
@@ -2229,7 +2337,7 @@ export class LlEmitter {
|
|
|
2229
2337
|
const eligible = new Set();
|
|
2230
2338
|
if (this.wasi || this.mod.workers === true)
|
|
2231
2339
|
return eligible;
|
|
2232
|
-
const suspends = /@scr_(?:await_|module_await\b|promise_await_settled\b|test_sub\b|gen_yield_|async_gen_|fiber_)/;
|
|
2340
|
+
const suspends = /@(?:scr_(?:await_|module_await\b|promise_await_settled\b|test_sub\b|gen_yield_|async_gen_|fiber_)|sc_a[wd]_)/;
|
|
2233
2341
|
for (const decl of this.decls)
|
|
2234
2342
|
if (/@scr_test_sub\b/.test(decl))
|
|
2235
2343
|
return eligible;
|
|
@@ -2325,6 +2433,133 @@ export class LlEmitter {
|
|
|
2325
2433
|
out.push(` call void @scr_promise_reject_pending(ptr %p)`, ` ret ptr %p`, `}`, ``);
|
|
2326
2434
|
return out;
|
|
2327
2435
|
}
|
|
2436
|
+
/** Whether `await f(...)` may enter f through its await-call entry. The
|
|
2437
|
+
* entry's result is the awaited value of f's own body type; direct calls
|
|
2438
|
+
* never carry a lifted environment. */
|
|
2439
|
+
awaitCallEligible(fnName) {
|
|
2440
|
+
if (this.wasi || this.mod.workers === true)
|
|
2441
|
+
return false;
|
|
2442
|
+
const fn = this.fnByName.get(fnName);
|
|
2443
|
+
if (fn === undefined || fn.async !== true || fn.generator !== undefined)
|
|
2444
|
+
return false;
|
|
2445
|
+
if (fn.captures !== undefined || fn.returnType.kind === "dyn")
|
|
2446
|
+
return false;
|
|
2447
|
+
return fn.asyncCacheGlobal === undefined && fn.asyncCycleCacheGlobal === undefined;
|
|
2448
|
+
}
|
|
2449
|
+
/** The await-call entry of an async function (`sc_aw_*`): one direct
|
|
2450
|
+
* `await f(...)` as a call returning the awaited value, so the call site
|
|
2451
|
+
* needs no promise of its own. A fiberless body runs on the caller's
|
|
2452
|
+
* stack as in its spawn wrapper; a clean result then takes the await's
|
|
2453
|
+
* one microtask turn (scr_await_hop) and is returned without ever
|
|
2454
|
+
* materializing the promise, which nothing else could observe. A throw
|
|
2455
|
+
* builds the rejected promise exactly as the spawn wrapper would and
|
|
2456
|
+
* awaits it, so the rejection reaches the awaiter on the same turn. Other
|
|
2457
|
+
* bodies pack their arguments as the spawn wrapper does and enter
|
|
2458
|
+
* scr_async_spawn_await_*, which parks the caller on the promise as soon
|
|
2459
|
+
* as the body first suspends. The adopting entry (`sc_ad_*`, for
|
|
2460
|
+
* `return f(...)` in an async function) adds the turn of the then job
|
|
2461
|
+
* that adopts the returned promise, before the await proper. */
|
|
2462
|
+
emitAsyncAwaitCall(fn, argPackLines, adopt = false) {
|
|
2463
|
+
const ret = fn.returnType;
|
|
2464
|
+
const retTy = this.llType(ret);
|
|
2465
|
+
const params = fn.params.map((p, i) => `${this.llType(p.type)} %a${i}`);
|
|
2466
|
+
const awaitLine = (dest, promise) => {
|
|
2467
|
+
switch (ret.kind) {
|
|
2468
|
+
case "void":
|
|
2469
|
+
this.declare(`declare void @scr_await_void(ptr)`);
|
|
2470
|
+
return ` call void @scr_await_void(ptr ${promise})`;
|
|
2471
|
+
case "f64":
|
|
2472
|
+
case "date":
|
|
2473
|
+
this.declare(`declare double @scr_await_f64(ptr)`);
|
|
2474
|
+
return ` ${dest} = call double @scr_await_f64(ptr ${promise})`;
|
|
2475
|
+
case "bool":
|
|
2476
|
+
this.declare(`declare zeroext i1 @scr_await_bool(ptr)`);
|
|
2477
|
+
return ` ${dest} = call zeroext i1 @scr_await_bool(ptr ${promise})`;
|
|
2478
|
+
case "string":
|
|
2479
|
+
this.declare(`declare ptr @scr_await_str(ptr)`);
|
|
2480
|
+
return ` ${dest} = call ptr @scr_await_str(ptr ${promise})`;
|
|
2481
|
+
default:
|
|
2482
|
+
this.declare(`declare ptr @scr_await_ref(ptr)`);
|
|
2483
|
+
return ` ${dest} = call ptr @scr_await_ref(ptr ${promise})`;
|
|
2484
|
+
}
|
|
2485
|
+
};
|
|
2486
|
+
const retLine = (v) => retTy === "void" ? ` ret void` : ` ret ${retTy} ${v}`;
|
|
2487
|
+
const head = adopt
|
|
2488
|
+
? `define internal ${retTy} @${mangleAsyncAdoptCall(fn.name)}(${params.join(", ")}) ${FN_ATTRS} { ; adopt ${fn.name}`
|
|
2489
|
+
: `define internal ${retTy} @${mangleAsyncAwaitCall(fn.name)}(${params.join(", ")}) ${FN_ATTRS} { ; await ${fn.name}`;
|
|
2490
|
+
const args = params.join(", ");
|
|
2491
|
+
if (adopt && argPackLines !== null) {
|
|
2492
|
+
// A body that may suspend settles later than the adoption's then job
|
|
2493
|
+
// runs: materialize its promise, take that turn, then await it.
|
|
2494
|
+
this.declare(`declare void @scr_await_hop()`);
|
|
2495
|
+
this.declare(`declare void @scr_promise_release(ptr)`);
|
|
2496
|
+
return [
|
|
2497
|
+
head,
|
|
2498
|
+
`entry:`,
|
|
2499
|
+
` %p = call ptr @${mangleAsyncSpawn(fn.name)}(${args})`,
|
|
2500
|
+
` call void @scr_await_hop()`,
|
|
2501
|
+
awaitLine("%v", "%p"),
|
|
2502
|
+
` call void @scr_promise_release(ptr %p)`,
|
|
2503
|
+
retLine("%v"),
|
|
2504
|
+
`}`,
|
|
2505
|
+
``,
|
|
2506
|
+
];
|
|
2507
|
+
}
|
|
2508
|
+
if (argPackLines !== null) {
|
|
2509
|
+
const kind = ret.kind === "void"
|
|
2510
|
+
? "void"
|
|
2511
|
+
: ret.kind === "f64" || ret.kind === "date"
|
|
2512
|
+
? "f64"
|
|
2513
|
+
: ret.kind === "bool"
|
|
2514
|
+
? "bool"
|
|
2515
|
+
: ret.kind === "string"
|
|
2516
|
+
? "str"
|
|
2517
|
+
: "ref";
|
|
2518
|
+
const cret = kind === "bool" ? "zeroext i1" : retTy;
|
|
2519
|
+
this.declare(`declare ${cret} @scr_async_spawn_await_${kind}(ptr, ptr)`);
|
|
2520
|
+
this.needOom();
|
|
2521
|
+
const call = `call ${cret} @scr_async_spawn_await_${kind}(ptr @${mangleTrampoline(fn.name)}, ptr %ap)`;
|
|
2522
|
+
return [
|
|
2523
|
+
head,
|
|
2524
|
+
`entry:`,
|
|
2525
|
+
...argPackLines,
|
|
2526
|
+
kind === "void" ? ` ${call}` : ` %v = ${call}`,
|
|
2527
|
+
retLine("%v"),
|
|
2528
|
+
`}`,
|
|
2529
|
+
``,
|
|
2530
|
+
];
|
|
2531
|
+
}
|
|
2532
|
+
this.declare(`declare void @scr_async_inline_enter(ptr)`);
|
|
2533
|
+
this.declare(`declare void @scr_async_inline_exit(ptr)`);
|
|
2534
|
+
this.declare(`declare void @scr_await_hop()`);
|
|
2535
|
+
this.declare(`declare ptr @scr_promise_new()`);
|
|
2536
|
+
this.declare(`declare void @scr_promise_reject_pending(ptr)`);
|
|
2537
|
+
this.declare(`declare void @scr_promise_release(ptr)`);
|
|
2538
|
+
const bodyCall = `call ${retTy} @${mangleFunction(fn.name)}(${args})`;
|
|
2539
|
+
const out = [
|
|
2540
|
+
head,
|
|
2541
|
+
`entry:`,
|
|
2542
|
+
` %frame = alloca { ptr, ptr }`,
|
|
2543
|
+
` call void @scr_async_inline_enter(ptr %frame)`,
|
|
2544
|
+
retTy === "void" ? ` ${bodyCall}` : ` %r = ${bodyCall}`,
|
|
2545
|
+
...this.pendingTestLines("%pend"),
|
|
2546
|
+
` call void @scr_async_inline_exit(ptr %frame)`,
|
|
2547
|
+
// Adopting: the then job's turn comes before the settled promise's
|
|
2548
|
+
// own reaction, whichever way the body completed.
|
|
2549
|
+
...(adopt ? [` call void @scr_await_hop()`] : []),
|
|
2550
|
+
` br i1 %pend, label %thrown, label %clean`,
|
|
2551
|
+
`clean:`,
|
|
2552
|
+
` call void @scr_await_hop()`,
|
|
2553
|
+
retLine("%r"),
|
|
2554
|
+
`thrown:`,
|
|
2555
|
+
];
|
|
2556
|
+
if (ret.kind !== "void" && isRefCounted(ret)) {
|
|
2557
|
+
// An escaping throw means %r is the never-read dummy (NULL).
|
|
2558
|
+
out.push(` call void ${releaseSym(this.shapeHost, ret)}(ptr %r)`);
|
|
2559
|
+
}
|
|
2560
|
+
out.push(` %p = call ptr @scr_promise_new()`, ` call void @scr_promise_reject_pending(ptr %p)`, awaitLine("%v", "%p"), ` call void @scr_promise_release(ptr %p)`, retLine("%v"), `}`, ``);
|
|
2561
|
+
return out;
|
|
2562
|
+
}
|
|
2328
2563
|
/** Per-async-function machinery — async.ts's scaffolding, .ll
|
|
2329
2564
|
* flavored: an argument-pack struct type, a fiber trampoline (unpacks,
|
|
2330
2565
|
* frees the pack, runs the ordinary compiled body, settles the
|
|
@@ -2343,6 +2578,14 @@ export class LlEmitter {
|
|
|
2343
2578
|
for (const fn of this.mod.functions) {
|
|
2344
2579
|
if (fn.async !== true || fn.generator !== undefined)
|
|
2345
2580
|
continue;
|
|
2581
|
+
if (fiberless.has(fn.name) && this.awaitCallees.has(fn.name)) {
|
|
2582
|
+
for (const line of this.emitAsyncAwaitCall(fn, null))
|
|
2583
|
+
out.push(line);
|
|
2584
|
+
}
|
|
2585
|
+
if (fiberless.has(fn.name) && this.adoptCallees.has(fn.name)) {
|
|
2586
|
+
for (const line of this.emitAsyncAwaitCall(fn, null, true))
|
|
2587
|
+
out.push(line);
|
|
2588
|
+
}
|
|
2346
2589
|
if (fiberless.has(fn.name)) {
|
|
2347
2590
|
const fieldTys = [
|
|
2348
2591
|
...(fn.captures !== undefined ? ["ptr"] : []),
|
|
@@ -2356,6 +2599,14 @@ export class LlEmitter {
|
|
|
2356
2599
|
const { definitions, ret, tr, spawnParams, argPackLines } = this.emitArgPackAndTrampolinePrologue(fn);
|
|
2357
2600
|
for (const line of definitions)
|
|
2358
2601
|
out.push(line);
|
|
2602
|
+
if (this.awaitCallees.has(fn.name)) {
|
|
2603
|
+
for (const line of this.emitAsyncAwaitCall(fn, argPackLines))
|
|
2604
|
+
out.push(line);
|
|
2605
|
+
}
|
|
2606
|
+
if (this.adoptCallees.has(fn.name)) {
|
|
2607
|
+
for (const line of this.emitAsyncAwaitCall(fn, argPackLines, true))
|
|
2608
|
+
out.push(line);
|
|
2609
|
+
}
|
|
2359
2610
|
this.declare(`declare ptr @scr_fiber_promise(ptr)`);
|
|
2360
2611
|
this.declare(`declare ptr @scr_async_spawn(ptr, ptr)`);
|
|
2361
2612
|
this.needOom();
|
|
@@ -2447,6 +2698,13 @@ export class LlEmitter {
|
|
|
2447
2698
|
out.push(line);
|
|
2448
2699
|
return out;
|
|
2449
2700
|
}
|
|
2701
|
+
/** Whether `fn`'s body is emitted as an LLVM switched coroutine: every
|
|
2702
|
+
* wasm32 async or generator body, and native synchronous generators. */
|
|
2703
|
+
coroLowered(fn) {
|
|
2704
|
+
if (this.wasi)
|
|
2705
|
+
return fn.async === true || fn.generator !== undefined;
|
|
2706
|
+
return this.stacklessGenerators && fn.generator !== undefined && fn.async !== true;
|
|
2707
|
+
}
|
|
2450
2708
|
/** Per-generator-function machinery — the async scaffolding's lazy
|
|
2451
2709
|
* sibling (async.ts's emitGenScaffolding): the same argument pack,
|
|
2452
2710
|
* a fiber trampoline whose epilogue stores the COMPLETION value (or
|
|
@@ -2467,14 +2725,15 @@ export class LlEmitter {
|
|
|
2467
2725
|
this.declare(`declare void @scr_gen_ret_to_out(ptr)`);
|
|
2468
2726
|
this.declare(`declare ptr @scr_gen_new(ptr, ptr, ptr)`);
|
|
2469
2727
|
this.needOom();
|
|
2470
|
-
|
|
2728
|
+
const coroBody = this.coroLowered(fn);
|
|
2729
|
+
if (lifted && !coroBody) {
|
|
2471
2730
|
this.declare(`declare void @scr_closure_release(ptr)`);
|
|
2472
2731
|
}
|
|
2473
2732
|
// Normal completion stores the (typed) return value; void completes
|
|
2474
2733
|
// with the NONE slot — JS's undefined done-value. A GENRET unwind
|
|
2475
2734
|
// consumes the sentinel and promotes the parked .return value; a
|
|
2476
2735
|
// real exception stays pending (the consumer-side resume moves it).
|
|
2477
|
-
if (
|
|
2736
|
+
if (coroBody) {
|
|
2478
2737
|
tr.push(` ret void`, `}`, ``);
|
|
2479
2738
|
}
|
|
2480
2739
|
else {
|
|
@@ -2559,8 +2818,11 @@ export class LlEmitter {
|
|
|
2559
2818
|
`entry:`,
|
|
2560
2819
|
...argPackLines,
|
|
2561
2820
|
];
|
|
2821
|
+
const genNew = coroBody && !this.wasi ? "scr_gen_new_stackless" : "scr_gen_new";
|
|
2822
|
+
if (genNew !== "scr_gen_new")
|
|
2823
|
+
this.declare(`declare ptr @${genNew}(ptr, ptr, ptr)`);
|
|
2562
2824
|
sp.push(settleAsync === null
|
|
2563
|
-
? ` %gg = call ptr
|
|
2825
|
+
? ` %gg = call ptr @${genNew}(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)})`
|
|
2564
2826
|
: ` %gg = call ptr @scr_async_gen_new(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)}, ptr @${settleAsync})`, ` ret ptr %gg`, `}`, ``);
|
|
2565
2827
|
for (const line of sp)
|
|
2566
2828
|
out.push(line);
|
|
@@ -2598,6 +2860,41 @@ export class LlEmitter {
|
|
|
2598
2860
|
this.immortalValues.add(name);
|
|
2599
2861
|
return name;
|
|
2600
2862
|
}
|
|
2863
|
+
/** The inline equality test against one string literal (an
|
|
2864
|
+
* `i1 (ptr)` helper LLVM inlines at each site), by literal symbol. */
|
|
2865
|
+
literalEquals(text) {
|
|
2866
|
+
const literal = this.internLiteral(text);
|
|
2867
|
+
const name = `@sc_streq_${literal.slice("@sc_lit_".length)}`;
|
|
2868
|
+
if (!this.literalEqualities.has(text)) {
|
|
2869
|
+
this.literalEqualities.set(text, name);
|
|
2870
|
+
if (Buffer.byteLength(text, "utf8") > INLINE_LITERAL_BYTES) {
|
|
2871
|
+
this.declare(`declare zeroext i1 @scr_str_eq(ptr, ptr)`);
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
return name;
|
|
2875
|
+
}
|
|
2876
|
+
/** `@`-ref of an immortal ScrBigInt holding `value`, or null when the
|
|
2877
|
+
* text is not a valid StringToBigInt input (the runtime parse then
|
|
2878
|
+
* throws as usual). BigInt here is the same StringToBigInt as the
|
|
2879
|
+
* program's, so every accepted spelling folds to the value Node gives. */
|
|
2880
|
+
internBigIntLiteral(text) {
|
|
2881
|
+
let value;
|
|
2882
|
+
try {
|
|
2883
|
+
value = BigInt(text);
|
|
2884
|
+
}
|
|
2885
|
+
catch {
|
|
2886
|
+
return null;
|
|
2887
|
+
}
|
|
2888
|
+
const key = value.toString(16);
|
|
2889
|
+
let lit = this.bigintLiterals.get(key);
|
|
2890
|
+
if (!lit) {
|
|
2891
|
+
lit = { sym: this.contentSymbol("sc_bigint_", key), value };
|
|
2892
|
+
this.bigintLiterals.set(key, lit);
|
|
2893
|
+
}
|
|
2894
|
+
const name = `@${lit.sym}`;
|
|
2895
|
+
this.immortalValues.add(name);
|
|
2896
|
+
return name;
|
|
2897
|
+
}
|
|
2601
2898
|
/** Interned NUL-terminated C-string constant (the scr_jb_puts /
|
|
2602
2899
|
* stringify-indent currency) — `@`-ref, first-use order. */
|
|
2603
2900
|
cstr(text) {
|
|
@@ -2636,6 +2933,16 @@ export class LlEmitter {
|
|
|
2636
2933
|
this.immortalValues.add(name);
|
|
2637
2934
|
return name;
|
|
2638
2935
|
}
|
|
2936
|
+
/** The i8 flag that a checked-dynamic cast sets before caching one of
|
|
2937
|
+
* this tagged union's boxes (unionRetype rebuilds once it is set). */
|
|
2938
|
+
retypeGuardRef(unionId) {
|
|
2939
|
+
let sym = this.retypeGuards.get(unionId);
|
|
2940
|
+
if (!sym) {
|
|
2941
|
+
sym = `sc_retype_guard_${this.retypeGuards.size}`;
|
|
2942
|
+
this.retypeGuards.set(unionId, sym);
|
|
2943
|
+
}
|
|
2944
|
+
return `@${sym}`;
|
|
2945
|
+
}
|
|
2639
2946
|
/** The immortal ABSENT state of an undefined-armed record field slot
|
|
2640
2947
|
* (IR fieldAbsent). */
|
|
2641
2948
|
absentInstanceRef(unionId) {
|
|
@@ -2880,19 +3187,21 @@ export class LlEmitter {
|
|
|
2880
3187
|
target.used = true;
|
|
2881
3188
|
terminator = `br label %${target.label}`;
|
|
2882
3189
|
}
|
|
2883
|
-
else if (this.
|
|
2884
|
-
terminator = `br label %${this.
|
|
3190
|
+
else if (this.currentCoro !== null) {
|
|
3191
|
+
terminator = `br label %${this.currentCoro.finalLabel}`;
|
|
2885
3192
|
}
|
|
2886
3193
|
else {
|
|
2887
3194
|
const t = this.llType(this.currentReturnType);
|
|
2888
3195
|
terminator =
|
|
2889
3196
|
t === "void"
|
|
2890
3197
|
? "ret void"
|
|
2891
|
-
:
|
|
2892
|
-
?
|
|
2893
|
-
: t === "
|
|
2894
|
-
?
|
|
2895
|
-
:
|
|
3198
|
+
: this.currentInt32Result
|
|
3199
|
+
? "ret i32 0"
|
|
3200
|
+
: t === "double"
|
|
3201
|
+
? `ret double ${f64Lit(0)}`
|
|
3202
|
+
: t === "i1"
|
|
3203
|
+
? "ret i1 false"
|
|
3204
|
+
: "ret ptr null";
|
|
2896
3205
|
}
|
|
2897
3206
|
// Copy the entries now: later declarations extend these lexical scopes.
|
|
2898
3207
|
// Release order and the handler are part of the shared block's identity.
|
|
@@ -2905,7 +3214,10 @@ export class LlEmitter {
|
|
|
2905
3214
|
}
|
|
2906
3215
|
}
|
|
2907
3216
|
if (entries.length === 0) {
|
|
2908
|
-
if (passThrough !== undefined &&
|
|
3217
|
+
if (passThrough !== undefined &&
|
|
3218
|
+
!target &&
|
|
3219
|
+
this.currentCoro === null &&
|
|
3220
|
+
!this.currentInt32Result) {
|
|
2909
3221
|
const t = this.llType(this.currentReturnType);
|
|
2910
3222
|
if (t === "double" || t === "i1")
|
|
2911
3223
|
terminator = `ret ${t} ${passThrough}`;
|
|
@@ -3878,6 +4190,29 @@ export class LlEmitter {
|
|
|
3878
4190
|
this.boxSet(box, local.type, borrowed && isRefCounted(local.type) ? this.retainValue(value, local.type) : value);
|
|
3879
4191
|
}
|
|
3880
4192
|
// ── functions ───────────────────────────────────────────────────────────
|
|
4193
|
+
/** True when `fnName`'s borrowing body lends every reference parameter
|
|
4194
|
+
* and returns an owned result: a closure over it then carries that body
|
|
4195
|
+
* as its borrowing entry (ScrClosure.fn_borrowed), which callers that
|
|
4196
|
+
* keep their arguments alive across the call may enter instead of `fn`. */
|
|
4197
|
+
lendsEveryArgument(fnName) {
|
|
4198
|
+
const fn = this.fnByName.get(fnName);
|
|
4199
|
+
if (!fn || fn.async || fn.generator || this.borrowedReturns.has(fnName))
|
|
4200
|
+
return false;
|
|
4201
|
+
if (fn.classCaptures !== undefined || this.implSlotBorrowed(fnName).size > 0)
|
|
4202
|
+
return false;
|
|
4203
|
+
const borrowed = this.callLifetimes.borrowed.get(fnName);
|
|
4204
|
+
if (!borrowed)
|
|
4205
|
+
return false;
|
|
4206
|
+
let any = false;
|
|
4207
|
+
for (const [index, param] of fn.params.entries()) {
|
|
4208
|
+
if (!isRefCounted(param.type))
|
|
4209
|
+
continue;
|
|
4210
|
+
if (!borrowed.has(index))
|
|
4211
|
+
return false;
|
|
4212
|
+
any = true;
|
|
4213
|
+
}
|
|
4214
|
+
return any;
|
|
4215
|
+
}
|
|
3881
4216
|
/** The LLVM symbol a direct call or closure enters a function through:
|
|
3882
4217
|
* async bodies are entered via their emitted spawn wrapper (which runs
|
|
3883
4218
|
* the fiber eagerly to its first suspension and returns the promise);
|
|
@@ -3895,7 +4230,7 @@ export class LlEmitter {
|
|
|
3895
4230
|
* whether the promise waiter list or the ready FIFO owns the continuation;
|
|
3896
4231
|
* LLVM keeps all live locals in the coroutine frame. */
|
|
3897
4232
|
emitWasiSuspend(promise) {
|
|
3898
|
-
const coro = this.
|
|
4233
|
+
const coro = this.currentCoro;
|
|
3899
4234
|
if (coro === null)
|
|
3900
4235
|
throw new InternalCompilerError("llvm emitter bug: wasm suspension outside async body");
|
|
3901
4236
|
if (promise === null) {
|
|
@@ -3906,12 +4241,12 @@ export class LlEmitter {
|
|
|
3906
4241
|
this.declare(`declare void @scr_wasi_await_prepare(ptr, ptr)`);
|
|
3907
4242
|
this.B.line(`call void @scr_wasi_await_prepare(ptr ${coro.self}, ptr ${promise})`);
|
|
3908
4243
|
}
|
|
3909
|
-
this.
|
|
4244
|
+
this.emitCoroSuspendPrepared();
|
|
3910
4245
|
}
|
|
3911
4246
|
/** Suspend after a target-specific runtime helper already queued the
|
|
3912
4247
|
* continuation (used by module awaits, which skip the hop when settled). */
|
|
3913
|
-
|
|
3914
|
-
const coro = this.
|
|
4248
|
+
emitCoroSuspendPrepared() {
|
|
4249
|
+
const coro = this.currentCoro;
|
|
3915
4250
|
if (coro === null)
|
|
3916
4251
|
throw new InternalCompilerError("llvm emitter bug: wasm suspension outside async body");
|
|
3917
4252
|
const save = this.B.tmp();
|
|
@@ -3924,7 +4259,7 @@ export class LlEmitter {
|
|
|
3924
4259
|
}
|
|
3925
4260
|
/** Move a clean async return value into the current fiber's promise. */
|
|
3926
4261
|
emitWasiFulfill(v) {
|
|
3927
|
-
const coro = this.
|
|
4262
|
+
const coro = this.currentCoro;
|
|
3928
4263
|
if (coro === null)
|
|
3929
4264
|
throw new InternalCompilerError("llvm emitter bug: wasm fulfillment outside async body");
|
|
3930
4265
|
const ret = this.currentReturnType;
|
|
@@ -4038,6 +4373,8 @@ export class LlEmitter {
|
|
|
4038
4373
|
this.borrowedParameters.add(fn.params[index].localId);
|
|
4039
4374
|
}
|
|
4040
4375
|
this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
|
|
4376
|
+
this.stableCaptureIds =
|
|
4377
|
+
fn.captures !== undefined ? this.lazyCaptures.stableCaptures(fn.name) : new Set();
|
|
4041
4378
|
this.integerLoopBindings.clear();
|
|
4042
4379
|
this.countedLoopsEnabled = this.debug === null && !fn.async && !fn.generator;
|
|
4043
4380
|
this.finalFieldBorrows =
|
|
@@ -4065,17 +4402,28 @@ export class LlEmitter {
|
|
|
4065
4402
|
this.walkBorrows = this.walkBorrowsByFunction.get(fn.name) ?? new Set();
|
|
4066
4403
|
this.integerRanges = analyzeIntegerRanges(numericFn, this.int32Slots.facts(fn.name));
|
|
4067
4404
|
this.bytesBounds = findBytesBounds(numericFn, this.integerRanges);
|
|
4405
|
+
// Debug builds keep every number local a double for the debugger's
|
|
4406
|
+
// variable view. Lazily boxed captured parameters keep their double,
|
|
4407
|
+
// which the box is created from.
|
|
4408
|
+
const int32Locals = this.debug === null
|
|
4409
|
+
? findInt32Locals(fn, this.integerRanges, this.int32Slots.facts(fn.name), new Set(this.lazyCaptureBoxes.keys()))
|
|
4410
|
+
: null;
|
|
4411
|
+
this.int32Locals = int32Locals?.locals ?? new Map();
|
|
4412
|
+
this.int32LocalWrites = int32Locals?.values ?? new Set();
|
|
4068
4413
|
this.chainSlots.clear();
|
|
4069
4414
|
this.finallyStack = [];
|
|
4070
4415
|
this.tryStack = [];
|
|
4071
4416
|
this.loopDepth = 0;
|
|
4072
4417
|
this.currentReturnType = fn.returnType;
|
|
4418
|
+
const int32Abi = this.int32Abis.get(fn.name);
|
|
4419
|
+
this.currentInt32Result = int32Abi?.result === true;
|
|
4073
4420
|
this.currentGenerator = fn.generator ?? null;
|
|
4074
4421
|
this.currentBorrowedReturn = this.borrowedReturns.has(fn.name);
|
|
4075
4422
|
this.inConstructor = this.constructorFunctions.has(fn.name);
|
|
4076
|
-
this.
|
|
4423
|
+
this.currentCoro = null;
|
|
4077
4424
|
this.logArgSlots = 0;
|
|
4078
|
-
if (this.
|
|
4425
|
+
if (this.coroLowered(fn)) {
|
|
4426
|
+
this.usesCoroutines = true;
|
|
4079
4427
|
this.declare(`declare token @llvm.coro.id(i32, ptr, ptr, ptr)`);
|
|
4080
4428
|
this.declare(`declare ${this.sizeType} @llvm.coro.size.${this.sizeType}()`);
|
|
4081
4429
|
this.declare(`declare ptr @llvm.coro.begin(token, ptr)`);
|
|
@@ -4083,9 +4431,12 @@ export class LlEmitter {
|
|
|
4083
4431
|
this.declare(`declare i8 @llvm.coro.suspend(token, i1)`);
|
|
4084
4432
|
this.declare(`declare ptr @llvm.coro.free(token, ptr)`);
|
|
4085
4433
|
this.declare(`declare void @llvm.coro.end(ptr, i1, token)`);
|
|
4086
|
-
|
|
4087
|
-
this.
|
|
4088
|
-
this.declare(`declare
|
|
4434
|
+
// Native frames come from the runtime's small-block allocator.
|
|
4435
|
+
const frameAlloc = this.wasi ? "malloc" : "scr_rt_calloc";
|
|
4436
|
+
this.declare(`declare ptr @${frameAlloc}(${this.sizeType})`);
|
|
4437
|
+
this.declare(`declare void @${this.wasi ? "free" : "scr_rt_free"}(ptr)`);
|
|
4438
|
+
const started = this.wasi ? "scr_wasi_coro_started" : "scr_coro_started";
|
|
4439
|
+
this.declare(`declare void @${started}(ptr)`);
|
|
4089
4440
|
this.declare(`declare ptr @scr_fiber_self()`);
|
|
4090
4441
|
const id = B.tmp();
|
|
4091
4442
|
const size = B.tmp();
|
|
@@ -4094,11 +4445,11 @@ export class LlEmitter {
|
|
|
4094
4445
|
const self = B.tmp();
|
|
4095
4446
|
B.line(`${id} = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)`);
|
|
4096
4447
|
B.line(`${size} = call ${this.sizeType} @llvm.coro.size.${this.sizeType}()`);
|
|
4097
|
-
B.line(`${mem} = call ptr
|
|
4448
|
+
B.line(`${mem} = call ptr @${frameAlloc}(${this.sizeType} ${size})`);
|
|
4098
4449
|
B.line(`${handle} = call ptr @llvm.coro.begin(token ${id}, ptr ${mem})`);
|
|
4099
|
-
B.line(`call void
|
|
4450
|
+
B.line(`call void @${started}(ptr ${handle})`);
|
|
4100
4451
|
B.line(`${self} = call ptr @scr_fiber_self()`);
|
|
4101
|
-
this.
|
|
4452
|
+
this.currentCoro = {
|
|
4102
4453
|
kind: fn.generator !== undefined ? "generator" : "async",
|
|
4103
4454
|
id,
|
|
4104
4455
|
handle,
|
|
@@ -4111,6 +4462,9 @@ export class LlEmitter {
|
|
|
4111
4462
|
const paramIds = new Set(fn.params.map((p) => p.localId));
|
|
4112
4463
|
if (this.debug === null) {
|
|
4113
4464
|
for (const id of findIntegerViews(numericFn)) {
|
|
4465
|
+
// An i32 slot is its own view.
|
|
4466
|
+
if (this.int32Locals.has(id))
|
|
4467
|
+
continue;
|
|
4114
4468
|
const slot = B.slot();
|
|
4115
4469
|
B.entryAllocas.push(`${slot} = alloca i32 ; integer view ${this.numericLocals.get(id).name}`);
|
|
4116
4470
|
this.integerViews.set(id, slot);
|
|
@@ -4130,7 +4484,9 @@ export class LlEmitter {
|
|
|
4130
4484
|
// box the catch prologue takes (scr_exc_take).
|
|
4131
4485
|
const slotTy = local.boxed || this.captureIds.has(local.id) || local.type.kind === "caught"
|
|
4132
4486
|
? "ptr"
|
|
4133
|
-
: this.
|
|
4487
|
+
: this.int32Locals.has(local.id)
|
|
4488
|
+
? "i32"
|
|
4489
|
+
: this.llType(local.type);
|
|
4134
4490
|
B.entryAllocas.push(`%${mangleLocal(local.id)} = alloca ${slotTy} ; ${local.name}`);
|
|
4135
4491
|
this.emitDebugLocal(local, `%${mangleLocal(local.id)}`, fn.params.findIndex((p) => p.localId === local.id) + 1, this.captureIds.has(local.id));
|
|
4136
4492
|
// Refcounted/boxed locals start NULL (the C prologue's `= NULL`):
|
|
@@ -4204,8 +4560,16 @@ export class LlEmitter {
|
|
|
4204
4560
|
fnScope.push({ slot, type: p.type, boxed: true });
|
|
4205
4561
|
continue;
|
|
4206
4562
|
}
|
|
4563
|
+
const incoming = { name: `%p_${mangleLocal(p.localId)}`, type: p.type };
|
|
4564
|
+
if (int32Abi?.params[index]) {
|
|
4565
|
+
const bits = `%p_${mangleLocal(p.localId)}${INT32_ABI_SUFFIX}`;
|
|
4566
|
+
incoming.uint32 = bits;
|
|
4567
|
+
incoming.integer = { name: bits, type: "i32", signed: true, range: INT32_RANGE };
|
|
4568
|
+
}
|
|
4569
|
+
if (this.storeInt32Local(p.localId, incoming))
|
|
4570
|
+
continue;
|
|
4207
4571
|
B.line(`store ${this.llType(p.type)} %p_${mangleLocal(p.localId)}, ptr ${slot}`);
|
|
4208
|
-
this.storeIntegerView(p.localId,
|
|
4572
|
+
this.storeIntegerView(p.localId, incoming);
|
|
4209
4573
|
if (isRefCounted(p.type) && !borrowed?.has(index))
|
|
4210
4574
|
fnScope.push({ slot, type: p.type });
|
|
4211
4575
|
if (this.reboundByFunction.get(fn.name)?.has(p.localId) && borrowed?.has(index)) {
|
|
@@ -4219,8 +4583,9 @@ export class LlEmitter {
|
|
|
4219
4583
|
}
|
|
4220
4584
|
}
|
|
4221
4585
|
this.scopes.push(fnScope);
|
|
4222
|
-
// Stackful native fibers
|
|
4223
|
-
// active exception context owns the chain, so
|
|
4586
|
+
// Stackful native fibers and native coroutine frames retain these frames
|
|
4587
|
+
// while suspended. The active exception context owns the chain, so
|
|
4588
|
+
// concurrent fibers isolate it.
|
|
4224
4589
|
if (this.stackTraces && !this.wasi && fn.sourceName !== undefined) {
|
|
4225
4590
|
this.declare(`declare void @scr_stack_enter(ptr, ptr)`);
|
|
4226
4591
|
this.declare(`declare void @scr_stack_leave(ptr)`);
|
|
@@ -4239,25 +4604,32 @@ export class LlEmitter {
|
|
|
4239
4604
|
// whose unwind released everything down to depth 0).
|
|
4240
4605
|
if (fn.returnType.kind === "void" && !B.isTerminated()) {
|
|
4241
4606
|
this.releaseScope(this.scopes[0]);
|
|
4242
|
-
if (this.
|
|
4607
|
+
if (this.currentCoro !== null) {
|
|
4243
4608
|
this.emitWasiFulfill(null);
|
|
4244
|
-
B.terminate(`br label %${this.
|
|
4609
|
+
B.terminate(`br label %${this.currentCoro.finalLabel}`);
|
|
4245
4610
|
}
|
|
4246
4611
|
else {
|
|
4247
4612
|
B.terminate("ret void");
|
|
4248
4613
|
}
|
|
4249
4614
|
}
|
|
4250
4615
|
this.scopes.pop();
|
|
4251
|
-
const coro = this.
|
|
4616
|
+
const coro = this.currentCoro;
|
|
4252
4617
|
if (coro !== null) {
|
|
4253
4618
|
B.startBlock(coro.finalLabel);
|
|
4619
|
+
// Every exit of a coroutine body joins here: its source frame (part
|
|
4620
|
+
// of the coroutine frame) leaves the generator's own chain.
|
|
4621
|
+
const epilogue = B.returnEpilogue;
|
|
4622
|
+
B.returnEpilogue = null;
|
|
4623
|
+
if (epilogue !== null)
|
|
4624
|
+
B.line(epilogue);
|
|
4254
4625
|
if (fn.captures !== undefined) {
|
|
4255
4626
|
this.declare(`declare void @scr_closure_release(ptr)`);
|
|
4256
4627
|
B.line(`call void @scr_closure_release(ptr %sc_env)`);
|
|
4257
4628
|
}
|
|
4258
4629
|
if (coro.kind === "generator") {
|
|
4259
|
-
this.
|
|
4260
|
-
|
|
4630
|
+
const finish = this.wasi ? "scr_wasi_gen_finish" : "scr_gen_coro_finish";
|
|
4631
|
+
this.declare(`declare void @${finish}(ptr)`);
|
|
4632
|
+
B.line(`call void @${finish}(ptr ${coro.self})`);
|
|
4261
4633
|
}
|
|
4262
4634
|
else {
|
|
4263
4635
|
this.declare(`declare void @scr_wasi_async_finish(ptr)`);
|
|
@@ -4269,7 +4641,7 @@ export class LlEmitter {
|
|
|
4269
4641
|
B.startBlock(coro.cleanupLabel);
|
|
4270
4642
|
const frame = B.tmp();
|
|
4271
4643
|
B.line(`${frame} = call ptr @llvm.coro.free(token ${coro.id}, ptr ${coro.handle})`);
|
|
4272
|
-
B.line(`call void
|
|
4644
|
+
B.line(`call void @${this.wasi ? "free" : "scr_rt_free"}(ptr ${frame})`);
|
|
4273
4645
|
B.br(coro.suspendLabel);
|
|
4274
4646
|
B.startBlock(coro.suspendLabel);
|
|
4275
4647
|
B.line(`call void @llvm.coro.end(ptr ${coro.handle}, i1 false, token none)`);
|
|
@@ -4294,11 +4666,22 @@ export class LlEmitter {
|
|
|
4294
4666
|
if (this.logArgSlots > 0) {
|
|
4295
4667
|
B.entryAllocas.push(`%logargs = alloca [${this.logArgSlots} x %ScrLogArg]`);
|
|
4296
4668
|
}
|
|
4297
|
-
const params = fn.params.map((p) =>
|
|
4669
|
+
const params = fn.params.map((p, index) => int32Abi?.params[index]
|
|
4670
|
+
? `i32 %p_${mangleLocal(p.localId)}${INT32_ABI_SUFFIX}`
|
|
4671
|
+
: `${this.llType(p.type)} %p_${mangleLocal(p.localId)}`);
|
|
4672
|
+
// The i32 entry widens its i32 parameters once; the proof makes the
|
|
4673
|
+
// double exact, so readers of the incoming number see the same value.
|
|
4674
|
+
if (int32Abi)
|
|
4675
|
+
fn.params.forEach((p, index) => {
|
|
4676
|
+
if (!int32Abi.params[index])
|
|
4677
|
+
return;
|
|
4678
|
+
const name = `%p_${mangleLocal(p.localId)}`;
|
|
4679
|
+
B.entryAllocas.push(`${name} = sitofp i32 ${name}${INT32_ABI_SUFFIX} to double`);
|
|
4680
|
+
});
|
|
4298
4681
|
// Lifted functions receive their closure first (the callValue ABI).
|
|
4299
4682
|
if (fn.captures !== undefined)
|
|
4300
4683
|
params.unshift("ptr %sc_env");
|
|
4301
|
-
const ret = this.llType(fn.returnType);
|
|
4684
|
+
const ret = int32Abi?.result ? "i32" : this.llType(fn.returnType);
|
|
4302
4685
|
let attrs = coro !== null ? "#1" : FN_ATTRS;
|
|
4303
4686
|
// The shared indexed-read helper (lowerSafeIndexRead) is a dense bounds
|
|
4304
4687
|
// test, a state test and a load behind cold runtime fallbacks, called
|
|
@@ -4309,7 +4692,9 @@ export class LlEmitter {
|
|
|
4309
4692
|
attrs = `inlinehint ${attrs}`;
|
|
4310
4693
|
const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`;
|
|
4311
4694
|
const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
|
|
4312
|
-
|
|
4695
|
+
let body = `define internal ${ret} @${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
|
|
4696
|
+
if (int32Abi)
|
|
4697
|
+
body += "\n" + this.emitInt32AbiWrapper(fn, symbol, int32Abi);
|
|
4313
4698
|
if (!borrowed)
|
|
4314
4699
|
return body;
|
|
4315
4700
|
const slot = this.implSlotBorrowed(fn.name);
|
|
@@ -4318,9 +4703,62 @@ export class LlEmitter {
|
|
|
4318
4703
|
: "";
|
|
4319
4704
|
return body + "\n" + this.emitOwnedCallAdapter(fn, borrowed) + virtual;
|
|
4320
4705
|
}
|
|
4706
|
+
/** The double-ABI entry of a function with an i32 entry: every caller
|
|
4707
|
+
* that is not a direct call (closures, dispatch, adapters, the runtime)
|
|
4708
|
+
* enters here. A pending exception passes through to its caller. */
|
|
4709
|
+
emitInt32AbiWrapper(fn, symbol, abi) {
|
|
4710
|
+
const lines = [];
|
|
4711
|
+
const params = [];
|
|
4712
|
+
const args = [];
|
|
4713
|
+
fn.params.forEach((param, index) => {
|
|
4714
|
+
const type = this.llType(param.type);
|
|
4715
|
+
params.push(`${type} %p${index}`);
|
|
4716
|
+
if (abi.params[index]) {
|
|
4717
|
+
lines.push(` %a${index} = fptosi double %p${index} to i32`);
|
|
4718
|
+
args.push(`i32 %a${index}`);
|
|
4719
|
+
}
|
|
4720
|
+
else
|
|
4721
|
+
args.push(`${type} %p${index}`);
|
|
4722
|
+
});
|
|
4723
|
+
const ret = this.llType(fn.returnType);
|
|
4724
|
+
const call = `call ${abi.result ? "i32" : ret} @${symbol}${INT32_ABI_SUFFIX}(${args.join(", ")})`;
|
|
4725
|
+
if (ret === "void")
|
|
4726
|
+
lines.push(` ${call}`, " ret void");
|
|
4727
|
+
else if (abi.result)
|
|
4728
|
+
lines.push(` %r = ${call}`, ` %d = sitofp i32 %r to double`, ` ret double %d`);
|
|
4729
|
+
else
|
|
4730
|
+
lines.push(` %r = ${call}`, ` ret ${ret} %r`);
|
|
4731
|
+
return `define internal ${ret} @${symbol}(${params.join(", ")}) ${FN_ATTRS} {\nentry:\n${lines.join("\n")}\n}`;
|
|
4732
|
+
}
|
|
4733
|
+
/** The i32 bits of a number proven an int32 (a direct call's i32
|
|
4734
|
+
* argument or an i32 result): its exact integer when the value carries
|
|
4735
|
+
* one, else the conversion the proof makes exact. */
|
|
4736
|
+
int32Argument(value, expr) {
|
|
4737
|
+
const integer = exactInteger(this, value, expr);
|
|
4738
|
+
if (integer && /^-?\d+$/.test(integer.name))
|
|
4739
|
+
return String(Number(integer.name) | 0);
|
|
4740
|
+
if (integer)
|
|
4741
|
+
return narrowInteger(this, integer);
|
|
4742
|
+
const bits = this.B.tmp();
|
|
4743
|
+
this.B.line(`${bits} = fptosi double ${value.name} to i32`);
|
|
4744
|
+
return bits;
|
|
4745
|
+
}
|
|
4746
|
+
/** A direct call's i32 result as a number, with the i32 as its integer
|
|
4747
|
+
* and ToUint32 forms. */
|
|
4748
|
+
int32Result(bits, type) {
|
|
4749
|
+
const number = this.B.tmp();
|
|
4750
|
+
this.B.line(`${number} = sitofp i32 ${bits} to double`);
|
|
4751
|
+
return {
|
|
4752
|
+
name: number,
|
|
4753
|
+
type,
|
|
4754
|
+
uint32: bits,
|
|
4755
|
+
integer: { name: bits, type: "i32", signed: true, range: INT32_RANGE },
|
|
4756
|
+
};
|
|
4757
|
+
}
|
|
4321
4758
|
/** Keep the ordinary owned ABI for closures, virtual dispatch, generated
|
|
4322
|
-
* runtime adapters and library exports. Only direct IR calls
|
|
4323
|
-
*
|
|
4759
|
+
* runtime adapters and library exports. Only direct IR calls, calls of a
|
|
4760
|
+
* known closure body and borrowing closure entries (fn_borrowed) select
|
|
4761
|
+
* the borrowing body. Ownership of other parameters moves into that body;
|
|
4324
4762
|
* borrowed parameters are released here on normal and exceptional exits.
|
|
4325
4763
|
* The pending exception remains for the adapter's caller to handle. */
|
|
4326
4764
|
/** With `kept`, the virtual adapter of a borrowing slot instead: the
|
|
@@ -4330,6 +4768,9 @@ export class LlEmitter {
|
|
|
4330
4768
|
const B = new BlockBuilder();
|
|
4331
4769
|
this.B = B;
|
|
4332
4770
|
const params = fn.params.map((param, index) => `${this.llType(param.type)} %p${index}`);
|
|
4771
|
+
// Lifted bodies receive their closure first (the callValue ABI).
|
|
4772
|
+
if (fn.captures !== undefined)
|
|
4773
|
+
params.unshift("ptr %sc_env");
|
|
4333
4774
|
const ret = this.llType(fn.returnType);
|
|
4334
4775
|
const call = `call ${ret} @${mangleBorrowedFunction(fn.name)}(${params.join(", ")})`;
|
|
4335
4776
|
B.line(ret === "void" ? call : `%result = ${call}`);
|
|
@@ -4417,6 +4858,9 @@ export class LlEmitter {
|
|
|
4417
4858
|
// A module constant owns its value for the rest of the program.
|
|
4418
4859
|
if (binding.kind === "global")
|
|
4419
4860
|
return this.stableGlobals.has(value.localId);
|
|
4861
|
+
// So does the environment for a capture nothing ever rebinds.
|
|
4862
|
+
if (binding.kind === "boxed")
|
|
4863
|
+
return this.stableCaptureIds.has(value.localId) && this.canBorrowReceiver(value);
|
|
4420
4864
|
return (binding.kind === "local" &&
|
|
4421
4865
|
binding.local !== undefined &&
|
|
4422
4866
|
(!binding.local.mutable ||
|
|
@@ -4494,12 +4938,74 @@ export class LlEmitter {
|
|
|
4494
4938
|
return false;
|
|
4495
4939
|
return !rw.conflicts(["a", ...keys], writes);
|
|
4496
4940
|
}
|
|
4941
|
+
/** `return p` of a promise inside try regions of an async function. JS
|
|
4942
|
+
* completes the return first: p is evaluated, every crossed finally runs,
|
|
4943
|
+
* and only then does the function's promise adopt p's settlement (a then
|
|
4944
|
+
* job, then p's reaction). So the promise parks in a slot through the
|
|
4945
|
+
* finally copies, like any pending return value, and the adopting await
|
|
4946
|
+
* runs afterwards with no handler in scope: a rejection settles the
|
|
4947
|
+
* function's promise instead of reaching its own catch clauses. */
|
|
4948
|
+
emitAdoptingReturn(s, adopted) {
|
|
4949
|
+
const B = this.B;
|
|
4950
|
+
const promise = this.emitExpr(adopted.value);
|
|
4951
|
+
this.moveTemp(promise);
|
|
4952
|
+
const slot = B.slot();
|
|
4953
|
+
B.entryAllocas.push(`${slot} = alloca ptr ; adopted return promise`);
|
|
4954
|
+
B.line(`store ptr ${promise.name}, ptr ${slot}`);
|
|
4955
|
+
const parked = { slot, type: adopted.value.type };
|
|
4956
|
+
const savedFrames = this.frames;
|
|
4957
|
+
const savedScopes = this.scopes;
|
|
4958
|
+
const savedFinally = this.finallyStack;
|
|
4959
|
+
const savedTry = this.tryStack;
|
|
4960
|
+
for (let i = savedFinally.length - 1; i >= 0 && !B.isTerminated(); i--) {
|
|
4961
|
+
const fin = savedFinally[i];
|
|
4962
|
+
this.releaseForJump(fin.frameDepth, fin.scopeDepth);
|
|
4963
|
+
this.frames = this.frames.slice(0, fin.frameDepth);
|
|
4964
|
+
this.scopes = this.scopes.slice(0, fin.scopeDepth);
|
|
4965
|
+
this.finallyStack = savedFinally.slice(0, i);
|
|
4966
|
+
this.tryStack = savedTry.slice(0, fin.tryDepth);
|
|
4967
|
+
this.scopes.push([parked]);
|
|
4968
|
+
this.emitBlock(fin.body);
|
|
4969
|
+
this.scopes.pop();
|
|
4970
|
+
}
|
|
4971
|
+
if (!B.isTerminated()) {
|
|
4972
|
+
this.releaseForJump(0, 0);
|
|
4973
|
+
// Nothing of the body is live any more except the parked promise,
|
|
4974
|
+
// whose scope releases it on either exit of the await.
|
|
4975
|
+
this.frames = [[]];
|
|
4976
|
+
this.scopes = [[parked]];
|
|
4977
|
+
this.finallyStack = [];
|
|
4978
|
+
this.tryStack = [];
|
|
4979
|
+
const loaded = B.tmp();
|
|
4980
|
+
B.line(`${loaded} = load ptr, ptr ${slot}`);
|
|
4981
|
+
this.adoptedOperand = { name: loaded, type: adopted.value.type };
|
|
4982
|
+
const v = this.emitExpr(s.value);
|
|
4983
|
+
if (this.adoptedOperand !== null)
|
|
4984
|
+
throw new InternalCompilerError("llvm emitter bug: adopted return promise was not awaited");
|
|
4985
|
+
this.moveTemp(v);
|
|
4986
|
+
this.releaseForJump(0, 0);
|
|
4987
|
+
if (!B.isTerminated()) {
|
|
4988
|
+
if (this.currentCoro !== null) {
|
|
4989
|
+
this.emitWasiFulfill(v);
|
|
4990
|
+
B.terminate(`br label %${this.currentCoro.finalLabel}`);
|
|
4991
|
+
}
|
|
4992
|
+
else if (s.value.type.kind === "void")
|
|
4993
|
+
B.terminate("ret void");
|
|
4994
|
+
else
|
|
4995
|
+
B.terminate(`ret ${this.llType(s.value.type)} ${v.name}`);
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
this.frames = savedFrames;
|
|
4999
|
+
this.scopes = savedScopes;
|
|
5000
|
+
this.finallyStack = savedFinally;
|
|
5001
|
+
this.tryStack = savedTry;
|
|
5002
|
+
}
|
|
4497
5003
|
/** `return x` of a local whose slot is an owned scope entry of the same
|
|
4498
5004
|
* type: the return can take over the local's reference. */
|
|
4499
5005
|
movableReturn(value) {
|
|
4500
5006
|
if (value === null || value.kind !== "varRef" || !isRefCounted(value.type))
|
|
4501
5007
|
return null;
|
|
4502
|
-
if (this.currentBorrowedReturn || this.
|
|
5008
|
+
if (this.currentBorrowedReturn || this.currentCoro !== null)
|
|
4503
5009
|
return null;
|
|
4504
5010
|
const binding = this.binding(value.localId);
|
|
4505
5011
|
if (binding.kind !== "local" || binding.local === undefined || binding.local.boxed)
|
|
@@ -4669,6 +5175,10 @@ export class LlEmitter {
|
|
|
4669
5175
|
this.frames.push([]);
|
|
4670
5176
|
switch (s.kind) {
|
|
4671
5177
|
case "varDecl": {
|
|
5178
|
+
if (s.init && this.int32Locals.has(s.localId)) {
|
|
5179
|
+
this.storeInt32Local(s.localId, this.emitExpr(s.init), s.init);
|
|
5180
|
+
break;
|
|
5181
|
+
}
|
|
4672
5182
|
const b = this.binding(s.localId);
|
|
4673
5183
|
if (this.walkBorrows.has(s.localId) && b.kind === "local") {
|
|
4674
5184
|
// A borrowed walk pointer: no retain now, no release at scope exit.
|
|
@@ -4816,6 +5326,10 @@ export class LlEmitter {
|
|
|
4816
5326
|
break;
|
|
4817
5327
|
}
|
|
4818
5328
|
case "assign": {
|
|
5329
|
+
if (this.int32Locals.has(s.localId)) {
|
|
5330
|
+
this.storeInt32Local(s.localId, this.emitExpr(s.value), s.value);
|
|
5331
|
+
break;
|
|
5332
|
+
}
|
|
4819
5333
|
const owner = this.reboundOwnerSlots.get(s.localId);
|
|
4820
5334
|
if (owner !== undefined && !this.walkBorrows.has(s.localId)) {
|
|
4821
5335
|
// A rebound borrowed parameter: the new value moves into the owner
|
|
@@ -4904,8 +5418,10 @@ export class LlEmitter {
|
|
|
4904
5418
|
case "arraySet": {
|
|
4905
5419
|
// Evaluation order matches JS: array, index, then value. Ownership
|
|
4906
5420
|
// of a refcounted value moves into the array (the runtime releases
|
|
4907
|
-
// the replaced element itself).
|
|
4908
|
-
|
|
5421
|
+
// the replaced element itself). A local receiver that the index and
|
|
5422
|
+
// value cannot rebind is read in place, as for field stores; the
|
|
5423
|
+
// released element cannot reach that binding.
|
|
5424
|
+
const arr = this.emitStableReceiver(s.arr, [s.index, s.value]);
|
|
4909
5425
|
const idx = this.emitExpr(s.index);
|
|
4910
5426
|
const v = this.emitExpr(s.value);
|
|
4911
5427
|
if (s.arr.type.kind !== "array")
|
|
@@ -5083,7 +5599,6 @@ export class LlEmitter {
|
|
|
5083
5599
|
break;
|
|
5084
5600
|
}
|
|
5085
5601
|
const join = B.newLabel("rks.j");
|
|
5086
|
-
this.declare(`declare zeroext i1 @scr_str_eq(ptr, ptr)`);
|
|
5087
5602
|
if (iv.kind === "dyn" && shape.indexValue) {
|
|
5088
5603
|
// A dyn-valued shape (the C recordKeySetHelper's dyn arm,
|
|
5089
5604
|
// inline): declared keys VALIDATE the dyn value against the
|
|
@@ -5093,9 +5608,8 @@ export class LlEmitter {
|
|
|
5093
5608
|
// insert the dyn value into the overflow map as-is.
|
|
5094
5609
|
this.declare(`declare void @scr_dyn_release(ptr)`);
|
|
5095
5610
|
for (const f of shape.fields) {
|
|
5096
|
-
const lit = this.internLiteral(f.name);
|
|
5097
5611
|
const hit = B.tmp();
|
|
5098
|
-
B.line(`${hit} = call zeroext i1
|
|
5612
|
+
B.line(`${hit} = call zeroext i1 ${this.literalEquals(f.name)}(ptr ${key.name}) ; ${llvmCommentText(f.name)}`);
|
|
5099
5613
|
const lh = B.newLabel("rks.h");
|
|
5100
5614
|
const ln = B.newLabel("rks.n");
|
|
5101
5615
|
B.condBr(hit, lh, ln);
|
|
@@ -5149,9 +5663,8 @@ export class LlEmitter {
|
|
|
5149
5663
|
}
|
|
5150
5664
|
for (const f of shape.fields) {
|
|
5151
5665
|
// typeEquals(f.type, iv) — the frontend fences everything else.
|
|
5152
|
-
const lit = this.internLiteral(f.name);
|
|
5153
5666
|
const hit = B.tmp();
|
|
5154
|
-
B.line(`${hit} = call zeroext i1
|
|
5667
|
+
B.line(`${hit} = call zeroext i1 ${this.literalEquals(f.name)}(ptr ${key.name}) ; ${llvmCommentText(f.name)}`);
|
|
5155
5668
|
const lh = B.newLabel("rks.h");
|
|
5156
5669
|
const ln = B.newLabel("rks.n");
|
|
5157
5670
|
B.condBr(hit, lh, ln);
|
|
@@ -5678,6 +6191,13 @@ export class LlEmitter {
|
|
|
5678
6191
|
// try, past its own catch), then the function-level releases and
|
|
5679
6192
|
// the actual ret. The parked value owns a synthetic slot-backed
|
|
5680
6193
|
// scope entry during each copy so a throwing finally releases it.
|
|
6194
|
+
const adopted = this.finallyStack.length > 0 || this.tryStack.length > 0
|
|
6195
|
+
? adoptingAwaitOf(s.value)
|
|
6196
|
+
: null;
|
|
6197
|
+
if (adopted !== null) {
|
|
6198
|
+
this.emitAdoptingReturn(s, adopted);
|
|
6199
|
+
break;
|
|
6200
|
+
}
|
|
5681
6201
|
let v = null;
|
|
5682
6202
|
const moved = this.finallyStack.length === 0 ? this.movableReturn(s.value) : null;
|
|
5683
6203
|
if (moved !== null) {
|
|
@@ -5745,12 +6265,14 @@ export class LlEmitter {
|
|
|
5745
6265
|
this.releaseForJump(0, 0);
|
|
5746
6266
|
}
|
|
5747
6267
|
if (!B.isTerminated()) {
|
|
5748
|
-
if (this.
|
|
6268
|
+
if (this.currentCoro !== null) {
|
|
5749
6269
|
this.emitWasiFulfill(v);
|
|
5750
|
-
B.terminate(`br label %${this.
|
|
6270
|
+
B.terminate(`br label %${this.currentCoro.finalLabel}`);
|
|
5751
6271
|
}
|
|
5752
6272
|
else if (v === null)
|
|
5753
6273
|
B.terminate("ret void");
|
|
6274
|
+
else if (this.currentInt32Result)
|
|
6275
|
+
B.terminate(`ret i32 ${this.int32Argument(v, s.value)}`);
|
|
5754
6276
|
else
|
|
5755
6277
|
B.terminate(`ret ${this.llType(s.value.type)} ${v.name}`);
|
|
5756
6278
|
}
|
|
@@ -6008,7 +6530,10 @@ export class LlEmitter {
|
|
|
6008
6530
|
this.frames.push([]);
|
|
6009
6531
|
const t = this.emitExpr(c.test);
|
|
6010
6532
|
const hit = B.tmp();
|
|
6011
|
-
if (c.test.
|
|
6533
|
+
if (c.test.kind === "strLit") {
|
|
6534
|
+
B.line(`${hit} = call zeroext i1 ${this.literalEquals(c.test.value)}(ptr ${disc.name})`);
|
|
6535
|
+
}
|
|
6536
|
+
else if (c.test.type.kind === "string") {
|
|
6012
6537
|
this.declare(`declare zeroext i1 @scr_str_eq(ptr, ptr)`);
|
|
6013
6538
|
B.line(`${hit} = call zeroext i1 @sc_str_eq(ptr ${disc.name}, ptr ${t.name})`);
|
|
6014
6539
|
}
|
|
@@ -6125,6 +6650,12 @@ export class LlEmitter {
|
|
|
6125
6650
|
return this.canBorrowCallArgument(value);
|
|
6126
6651
|
}
|
|
6127
6652
|
emitDiscarded(e) {
|
|
6653
|
+
// A statement-position yield never reads the resumed value: it stays
|
|
6654
|
+
// in the IN slot until the next resume replaces it.
|
|
6655
|
+
if (e.kind === "yieldExpr" && e.captureCompletion === undefined && e.type.kind !== "void") {
|
|
6656
|
+
this.emitExpr({ ...e, type: VOID });
|
|
6657
|
+
return;
|
|
6658
|
+
}
|
|
6128
6659
|
if (e.kind === "seqExpr" &&
|
|
6129
6660
|
isRefCounted(e.result.type) &&
|
|
6130
6661
|
e.result.kind !== "strLit" &&
|
|
@@ -6242,6 +6773,11 @@ export class LlEmitter {
|
|
|
6242
6773
|
emitCallExpr(e) {
|
|
6243
6774
|
return emitCallExpr(this, e);
|
|
6244
6775
|
}
|
|
6776
|
+
/** A direct async call an enclosing `await` consumes whole: emitted
|
|
6777
|
+
* through the callee's await-call entry, yielding the awaited value. */
|
|
6778
|
+
emitAwaitedCall(e, adopt = false) {
|
|
6779
|
+
return emitCallExpr(this, e, adopt ? "adopt" : "await");
|
|
6780
|
+
}
|
|
6245
6781
|
emitRecordExpr(e) {
|
|
6246
6782
|
return emitRecordExpr(this, e);
|
|
6247
6783
|
}
|
|
@@ -7011,6 +7547,49 @@ export class LlEmitter {
|
|
|
7011
7547
|
emitToUint32(value, expr, uint32) {
|
|
7012
7548
|
return emitToUint32(this, value, expr, uint32);
|
|
7013
7549
|
}
|
|
7550
|
+
/** Stores a number into its int32-stored local, when it is one. The
|
|
7551
|
+
* value is proven to be in the local's range, so its low 32 bits are the
|
|
7552
|
+
* exact value. */
|
|
7553
|
+
storeInt32Local(localId, value, expr) {
|
|
7554
|
+
const local = this.int32Locals.get(localId);
|
|
7555
|
+
if (local === undefined)
|
|
7556
|
+
return false;
|
|
7557
|
+
if (expr !== undefined && !this.int32LocalWrites.has(expr))
|
|
7558
|
+
throw new InternalCompilerError(`llvm emitter bug: unproven write of int32 local ${localId}`);
|
|
7559
|
+
const B = this.B;
|
|
7560
|
+
const integer = exactInteger(this, value, expr);
|
|
7561
|
+
let bits;
|
|
7562
|
+
if (integer && /^-?\d+$/.test(integer.name))
|
|
7563
|
+
bits = String(Number(integer.name) | 0); // a literal's i32 bits
|
|
7564
|
+
else if (integer)
|
|
7565
|
+
bits = narrowInteger(this, integer);
|
|
7566
|
+
else if (value.uint32 !== undefined)
|
|
7567
|
+
bits = value.uint32;
|
|
7568
|
+
else {
|
|
7569
|
+
// An Int32 contract proved this write without a range at the value.
|
|
7570
|
+
bits = B.tmp();
|
|
7571
|
+
B.line(`${bits} = ${local.signed ? "fptosi" : "fptoui"} double ${value.name} to i32`);
|
|
7572
|
+
}
|
|
7573
|
+
B.line(`store i32 ${bits}, ptr %${mangleLocal(localId)}`);
|
|
7574
|
+
return true;
|
|
7575
|
+
}
|
|
7576
|
+
/** The number of an int32-stored local's i32, with the i32 as its
|
|
7577
|
+
* integer and ToUint32 forms. A parameter the body never writes reads
|
|
7578
|
+
* as its incoming double, which is the same number. */
|
|
7579
|
+
int32LocalValue(localId, bits, type) {
|
|
7580
|
+
const local = this.int32Locals.get(localId);
|
|
7581
|
+
let number = `%p_${mangleLocal(localId)}`;
|
|
7582
|
+
if (!local.incoming) {
|
|
7583
|
+
number = this.B.tmp();
|
|
7584
|
+
this.B.line(`${number} = ${local.signed ? "sitofp" : "uitofp"} i32 ${bits} to double`);
|
|
7585
|
+
}
|
|
7586
|
+
return {
|
|
7587
|
+
name: number,
|
|
7588
|
+
type,
|
|
7589
|
+
uint32: bits,
|
|
7590
|
+
integer: { name: bits, type: "i32", signed: local.signed, range: local.range },
|
|
7591
|
+
};
|
|
7592
|
+
}
|
|
7014
7593
|
storeIntegerView(localId, value, expr) {
|
|
7015
7594
|
const slot = this.integerViews.get(localId);
|
|
7016
7595
|
if (slot === undefined)
|
|
@@ -7100,4 +7679,24 @@ export class LlEmitter {
|
|
|
7100
7679
|
return emitLibCall(this, e);
|
|
7101
7680
|
}
|
|
7102
7681
|
}
|
|
7682
|
+
/** The adopting await a `return` value resolves through, if any: the value
|
|
7683
|
+
* itself or the operand of its conversion into the declared result. */
|
|
7684
|
+
function adoptingAwaitOf(value) {
|
|
7685
|
+
if (value === null)
|
|
7686
|
+
return null;
|
|
7687
|
+
let found = null;
|
|
7688
|
+
everyExpr(value, {
|
|
7689
|
+
expr: (node) => {
|
|
7690
|
+
if (found !== null)
|
|
7691
|
+
return false;
|
|
7692
|
+
if (node.kind === "awaitExpr" && node.adopt === true) {
|
|
7693
|
+
found = node;
|
|
7694
|
+
return false;
|
|
7695
|
+
}
|
|
7696
|
+
return true;
|
|
7697
|
+
},
|
|
7698
|
+
stmt: () => true,
|
|
7699
|
+
});
|
|
7700
|
+
return found;
|
|
7701
|
+
}
|
|
7103
7702
|
//# sourceMappingURL=emitter.js.map
|