@scriptc/compiler 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ambient/scriptc-node-fallback.d.ts +1 -0
- package/dist/backend/cycle-analysis.d.ts +56 -0
- package/dist/backend/cycle-analysis.js +222 -28
- package/dist/backend/cycle-analysis.js.map +1 -1
- package/dist/backend/cycle-analysis.test.js +232 -15
- package/dist/backend/cycle-analysis.test.js.map +1 -1
- package/dist/backend/cycle-mutability.d.ts +27 -6
- package/dist/backend/cycle-mutability.js +110 -17
- package/dist/backend/cycle-mutability.js.map +1 -1
- package/dist/backend/immutability.d.ts +57 -0
- package/dist/backend/immutability.js +178 -0
- package/dist/backend/immutability.js.map +1 -0
- package/dist/backend/immutability.test.d.ts +1 -0
- package/dist/backend/immutability.test.js +131 -0
- package/dist/backend/immutability.test.js.map +1 -0
- package/dist/backend/llvm/alloc.d.ts +13 -3
- package/dist/backend/llvm/alloc.js +24 -10
- package/dist/backend/llvm/alloc.js.map +1 -1
- package/dist/backend/llvm/alloc.test.js +5 -5
- package/dist/backend/llvm/alloc.test.js.map +1 -1
- package/dist/backend/llvm/borrowed-inputs.d.ts +9 -2
- package/dist/backend/llvm/borrowed-inputs.js +31 -3
- package/dist/backend/llvm/borrowed-inputs.js.map +1 -1
- package/dist/backend/llvm/byte-numbers.js +2 -3
- package/dist/backend/llvm/byte-numbers.js.map +1 -1
- package/dist/backend/llvm/classes.d.ts +28 -0
- package/dist/backend/llvm/classes.js +51 -10
- package/dist/backend/llvm/classes.js.map +1 -1
- package/dist/backend/llvm/classes.test.js +129 -1
- package/dist/backend/llvm/classes.test.js.map +1 -1
- package/dist/backend/llvm/common.d.ts +30 -0
- package/dist/backend/llvm/common.js +41 -0
- package/dist/backend/llvm/common.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.js +6 -5
- package/dist/backend/llvm/dense-array-access.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.test.js +10 -2
- package/dist/backend/llvm/dense-array-access.test.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +6 -4
- package/dist/backend/llvm/dyn.js +30 -14
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +109 -5
- package/dist/backend/llvm/emitter.js +716 -112
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.test.js +54 -3
- package/dist/backend/llvm/emitter.test.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.js +35 -28
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-calls.js +14 -6
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js +31 -8
- 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-control.js +46 -5
- package/dist/backend/llvm/expr-control.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +3 -0
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.js +30 -3
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.test.js +53 -0
- package/dist/backend/llvm/expr-primitives.test.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.d.ts +3 -0
- package/dist/backend/llvm/expr-stream-bridges.js +76 -14
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +18 -2
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +1 -0
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.d.ts +17 -6
- package/dist/backend/llvm/local-array-reads.js +157 -45
- package/dist/backend/llvm/local-array-reads.js.map +1 -1
- package/dist/backend/llvm/loop-array-borrows.d.ts +5 -2
- package/dist/backend/llvm/loop-array-borrows.js +27 -8
- package/dist/backend/llvm/loop-array-borrows.js.map +1 -1
- package/dist/backend/llvm/map-number-lookup.d.ts +7 -0
- package/dist/backend/llvm/map-number-lookup.js +18 -1
- package/dist/backend/llvm/map-number-lookup.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.d.ts +5 -0
- package/dist/backend/llvm/map-read-lifetimes.js +14 -4
- package/dist/backend/llvm/map-read-lifetimes.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.test.js +5 -0
- package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/reference-effects.js +34 -1
- package/dist/backend/llvm/reference-effects.js.map +1 -1
- package/dist/backend/llvm/reference-writes.d.ts +124 -0
- package/dist/backend/llvm/reference-writes.js +1090 -0
- package/dist/backend/llvm/reference-writes.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +22 -8
- package/dist/backend/llvm/shapes.js +79 -23
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/shapes.test.js +5 -5
- package/dist/backend/llvm/shapes.test.js.map +1 -1
- package/dist/backend/llvm/string-lifetimes.test.js +39 -10
- package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/string-reads.d.ts +3 -0
- package/dist/backend/llvm/string-reads.js +45 -11
- package/dist/backend/llvm/string-reads.js.map +1 -1
- package/dist/backend/llvm/string-slices.js +1 -1
- package/dist/backend/llvm/switch-dispatch.d.ts +2 -2
- package/dist/backend/llvm/switch-dispatch.js +35 -22
- package/dist/backend/llvm/switch-dispatch.js.map +1 -1
- package/dist/backend/llvm/switch-dispatch.test.js +28 -10
- package/dist/backend/llvm/switch-dispatch.test.js.map +1 -1
- package/dist/backend/llvm/trunc.d.ts +8 -0
- package/dist/backend/llvm/trunc.js +28 -0
- package/dist/backend/llvm/trunc.js.map +1 -0
- package/dist/backend/llvm/walk-borrows.d.ts +8 -1
- package/dist/backend/llvm/walk-borrows.js +15 -4
- package/dist/backend/llvm/walk-borrows.js.map +1 -1
- package/dist/backend/loop-polls.d.ts +14 -0
- package/dist/backend/loop-polls.js +100 -0
- package/dist/backend/loop-polls.js.map +1 -0
- package/dist/backend/loop-polls.test.d.ts +1 -0
- package/dist/backend/loop-polls.test.js +86 -0
- package/dist/backend/loop-polls.test.js.map +1 -0
- package/dist/backend/may-throw.d.ts +36 -2
- package/dist/backend/may-throw.js +328 -13
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/may-throw.test.js +169 -1
- package/dist/backend/may-throw.test.js.map +1 -1
- package/dist/backend/native-codegen.d.ts +1 -3
- package/dist/backend/native-codegen.js +4 -32
- package/dist/backend/native-codegen.js.map +1 -1
- package/dist/backend/publication.d.ts +5 -0
- package/dist/backend/publication.js +10 -0
- package/dist/backend/publication.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-abi.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.js +1 -1
- package/dist/backend/runtime-pack-targets.test.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +1 -1
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/cli/command.js +8 -1
- package/dist/cli/command.js.map +1 -1
- package/dist/cli/output-options.d.ts +1 -0
- package/dist/cli/output-options.js +3 -0
- package/dist/cli/output-options.js.map +1 -1
- package/dist/cli/usage.d.ts +5 -1
- package/dist/cli/usage.js +6 -0
- package/dist/cli/usage.js.map +1 -1
- package/dist/compile-types.d.ts +5 -0
- package/dist/contracts/check.d.ts +8 -0
- package/dist/contracts/check.js +1577 -0
- package/dist/contracts/check.js.map +1 -0
- package/dist/executable/executable-cache.d.ts +2 -0
- package/dist/executable/executable-cache.js +3 -0
- package/dist/executable/executable-cache.js.map +1 -1
- package/dist/executable/executable-cache.test.js +1 -0
- package/dist/executable/executable-cache.test.js.map +1 -1
- package/dist/executable/prepare.js +9 -0
- package/dist/executable/prepare.js.map +1 -1
- package/dist/frontend/contract-types.d.ts +50 -0
- package/dist/frontend/contract-types.js +232 -0
- package/dist/frontend/contract-types.js.map +1 -0
- package/dist/frontend/lowering/array-values.js +37 -1
- package/dist/frontend/lowering/array-values.js.map +1 -1
- package/dist/frontend/lowering/builtins/strings.js +4 -1
- package/dist/frontend/lowering/builtins/strings.js.map +1 -1
- package/dist/frontend/lowering/call-arguments.js +96 -4
- package/dist/frontend/lowering/call-arguments.js.map +1 -1
- package/dist/frontend/lowering/class-method-mutations.js +88 -0
- package/dist/frontend/lowering/class-method-mutations.js.map +1 -1
- package/dist/frontend/lowering/coercions/unions.d.ts +12 -0
- package/dist/frontend/lowering/coercions/unions.js +63 -0
- package/dist/frontend/lowering/coercions/unions.js.map +1 -1
- package/dist/frontend/lowering/containers/bytes.js +15 -4
- package/dist/frontend/lowering/containers/bytes.js.map +1 -1
- package/dist/frontend/lowering/expressions/optional-chains.js +4 -1
- package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -1
- package/dist/frontend/lowering/expressions/union-fields.d.ts +19 -0
- package/dist/frontend/lowering/expressions/union-fields.js +236 -0
- package/dist/frontend/lowering/expressions/union-fields.js.map +1 -0
- package/dist/frontend/lowering/lower-calls.js +21 -4
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +6 -0
- package/dist/frontend/lowering/lower-exprs.js +245 -4
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-island.js +5 -19
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +11 -1
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +14 -0
- package/dist/frontend/lowering/lowerer.js +221 -8
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/nan-coded-reads.d.ts +161 -0
- package/dist/frontend/lowering/nan-coded-reads.js +1342 -0
- package/dist/frontend/lowering/nan-coded-reads.js.map +1 -0
- package/dist/frontend/lowering/narrowing-stability.d.ts +100 -0
- package/dist/frontend/lowering/narrowing-stability.js +1174 -0
- package/dist/frontend/lowering/narrowing-stability.js.map +1 -0
- package/dist/frontend/lowering/narrowing-stability.test.d.ts +1 -0
- package/dist/frontend/lowering/narrowing-stability.test.js +150 -0
- package/dist/frontend/lowering/narrowing-stability.test.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-bounds.d.ts +14 -2
- package/dist/frontend/lowering/runtime-optional-bounds.js +17 -11
- package/dist/frontend/lowering/runtime-optional-bounds.js.map +1 -1
- package/dist/frontend/lowering/runtime-optional-bounds.test.js +28 -0
- package/dist/frontend/lowering/runtime-optional-bounds.test.js.map +1 -1
- package/dist/frontend/lowering/surfaces.js +8 -0
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/program.js +1 -1
- package/dist/frontend/type-mapper.js +43 -0
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +20 -8
- package/dist/index.js.map +1 -1
- package/dist/ir/builtin-signatures.d.ts +1 -1
- package/dist/ir/builtin-signatures.js +2 -0
- package/dist/ir/builtin-signatures.js.map +1 -1
- package/dist/ir/constant-globals.d.ts +14 -0
- package/dist/ir/constant-globals.js +81 -0
- package/dist/ir/constant-globals.js.map +1 -0
- package/dist/ir/constant-globals.test.d.ts +1 -0
- package/dist/ir/constant-globals.test.js +97 -0
- package/dist/ir/constant-globals.test.js.map +1 -0
- package/dist/ir/final-fields.d.ts +35 -0
- package/dist/ir/final-fields.js +187 -0
- package/dist/ir/final-fields.js.map +1 -0
- package/dist/ir/final-fields.test.d.ts +1 -0
- package/dist/ir/final-fields.test.js +106 -0
- package/dist/ir/final-fields.test.js.map +1 -0
- package/dist/ir/int32-slots.d.ts +35 -0
- package/dist/ir/int32-slots.js +217 -104
- package/dist/ir/int32-slots.js.map +1 -1
- package/dist/ir/int32-slots.test.js +27 -0
- package/dist/ir/int32-slots.test.js.map +1 -1
- package/dist/ir/integer-loops.d.ts +5 -0
- package/dist/ir/integer-loops.js +23 -4
- package/dist/ir/integer-loops.js.map +1 -1
- package/dist/ir/integer-loops.test.js +43 -1
- package/dist/ir/integer-loops.test.js.map +1 -1
- package/dist/ir/integer-ranges.d.ts +20 -0
- package/dist/ir/integer-ranges.js +178 -21
- package/dist/ir/integer-ranges.js.map +1 -1
- package/dist/ir/integer-ranges.test.js +148 -3
- package/dist/ir/integer-ranges.test.js.map +1 -1
- package/dist/ir/ir.d.ts +38 -1
- package/dist/ir/ir.js +4 -0
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/serialize.d.ts +17 -3
- package/dist/ir/serialize.js +198 -39
- package/dist/ir/serialize.js.map +1 -1
- package/dist/ir/serialize.test.js +65 -1
- package/dist/ir/serialize.test.js.map +1 -1
- package/dist/ir/unshare.d.ts +14 -0
- package/dist/ir/unshare.js +60 -0
- package/dist/ir/unshare.js.map +1 -0
- package/dist/ir/unshare.test.d.ts +1 -0
- package/dist/ir/unshare.test.js +115 -0
- package/dist/ir/unshare.test.js.map +1 -0
- package/dist/library/prepare.js +9 -0
- package/dist/library/prepare.js.map +1 -1
- package/dist/native/cache.d.ts +8 -1
- package/dist/native/cache.js +38 -1
- package/dist/native/cache.js.map +1 -1
- package/dist/native/cache.test.js +11 -0
- package/dist/native/cache.test.js.map +1 -1
- package/dist/native/compiler.js +4 -3
- package/dist/native/compiler.js.map +1 -1
- package/dist/native/prepare.d.ts +3 -1
- package/dist/native/prepare.js +63 -28
- package/dist/native/prepare.js.map +1 -1
- package/dist/native/prepare.test.js +23 -1
- package/dist/native/prepare.test.js.map +1 -1
- package/dist/native/text-file.d.ts +10 -0
- package/dist/native/text-file.js +41 -0
- package/dist/native/text-file.js.map +1 -0
- package/dist/text-parts.d.ts +5 -0
- package/dist/text-parts.js +36 -0
- package/dist/text-parts.js.map +1 -0
- package/package.json +20 -20
- package/surface-manifest.json +7 -1
- package/dist/frontend/lowering/expressions/union-field-write.d.ts +0 -8
- package/dist/frontend/lowering/expressions/union-field-write.js +0 -108
- package/dist/frontend/lowering/expressions/union-field-write.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { emitLlvmLayouts } from "./layouts.js";
|
|
2
|
-
import { inlineAllocSupported } from "./alloc.js";
|
|
2
|
+
import { FIRST_EMITTED_CYC_KIND, inlineAllocSupported } from "./alloc.js";
|
|
3
3
|
import { llvmBytes as llBytes } from "../literals.js";
|
|
4
4
|
import { InternalCompilerError } from "../../errors.js";
|
|
5
5
|
/** Lower typed IR to LLVM, using the runtime's C ABI.
|
|
@@ -23,6 +23,7 @@ import { createHash } from "node:crypto";
|
|
|
23
23
|
import { deflateRawSync } from "node:zlib";
|
|
24
24
|
import { endsWithJump, matchStringSelfConcat, streamTypedRefEligible, undefinedArmTag, } from "../../ir/analysis.js";
|
|
25
25
|
import { emitLibraryIdentityLines } from "../library-identity-markers.js";
|
|
26
|
+
import { loopTestsEveryIteration } from "../loop-polls.js";
|
|
26
27
|
import { CAUGHT, ffiCallbackType, isDynTypedRefType, isFfiContextParam, isRefCounted, isUnitType, POINTER_KINDS, RUNTIME_EMITTER_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, typeEquals, typeKey, STRING, VOID, } from "../../ir/ir.js";
|
|
27
28
|
import { moduleRuntimeFeatures, moduleEmbedsBuiltin, moduleEmbedsCompressedNpm, NPM_COMPRESS_MIN, } from "../../ir/runtime-features.js";
|
|
28
29
|
import { matchIntegerArrayForLoop, matchIntegerBytesForLoop, matchIntegerCountedForLoop, } from "../../ir/integer-loops.js";
|
|
@@ -35,7 +36,10 @@ import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
|
|
|
35
36
|
import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js";
|
|
36
37
|
import { everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
|
|
37
38
|
import { analyzeIntegerRanges, INT32_RANGE } from "../../ir/integer-ranges.js";
|
|
39
|
+
import { unshareNodes } from "../../ir/unshare.js";
|
|
38
40
|
import { analyzeInt32Slots } from "../../ir/int32-slots.js";
|
|
41
|
+
import { analyzeFinalFields, constructorClass } from "../../ir/final-fields.js";
|
|
42
|
+
import { foldConstantGlobals } from "../../ir/constant-globals.js";
|
|
39
43
|
import { findIntegerViews } from "./integer-views.js";
|
|
40
44
|
import { findInitializerBindings, withInitializerBindings } from "../../ir/initializer-bindings.js";
|
|
41
45
|
import { findConstantNumericTables } from "../../ir/constant-tables.js";
|
|
@@ -53,10 +57,11 @@ import { CheckedNarrows, emitCheckedNarrowReceiver } from "./checked-narrows.js"
|
|
|
53
57
|
import { emitDenseArrayGet, emitDenseArraySet } from "./dense-array-access.js";
|
|
54
58
|
import { emitStackMapRead, findMapReadLifetimes, matchMapRead, } from "./map-read-lifetimes.js";
|
|
55
59
|
import { emitBorrowedFieldSequence, guardedValue } from "./borrowed-receivers.js";
|
|
56
|
-
import { emitBorrowedInput } from "./borrowed-inputs.js";
|
|
60
|
+
import { borrowableInputs, emitBorrowedInput } from "./borrowed-inputs.js";
|
|
57
61
|
import { analyzeWalks, findWalkBorrows } from "./walk-borrows.js";
|
|
58
62
|
import { findReboundParameters } from "./rebound-parameters.js";
|
|
59
63
|
import { ReferenceEffects } from "./reference-effects.js";
|
|
64
|
+
import { ReferenceWrites } from "./reference-writes.js";
|
|
60
65
|
import { LlvmDebugInfo } from "./debug-info.js";
|
|
61
66
|
import { StackCallbacks } from "./stack-callbacks.js";
|
|
62
67
|
import { findLoopArrayBorrows } from "./loop-array-borrows.js";
|
|
@@ -64,7 +69,7 @@ import { AmbientReceiverReaders, findConstantCallbacks } from "./constant-callba
|
|
|
64
69
|
import { findScalarStringSlices, emitStringSliceSnapshot, } from "./string-slices.js";
|
|
65
70
|
import { LazyCaptures } from "./lazy-captures.js";
|
|
66
71
|
import { StackCaptures } from "./stack-captures.js";
|
|
67
|
-
import { ABSENT_FIELD_PAYLOAD, emitFieldAbsentTest, f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, } from "./common.js";
|
|
72
|
+
import { ABSENT_FIELD_PAYLOAD, emitFieldAbsentTest, exactIndexLines, f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, } from "./common.js";
|
|
68
73
|
import { ffiExtendsNarrowIntegers } from "../targets.js";
|
|
69
74
|
import { emitLiteralExpr, emitOperatorExpr, emitStringExpr, emitContainerExpr, emitRecordExpr, } from "./expr-primitives.js";
|
|
70
75
|
import { emitControlExpr } from "./expr-control.js";
|
|
@@ -72,6 +77,7 @@ import { emitBorrowedResultCall, emitCallExpr } from "./expr-calls.js";
|
|
|
72
77
|
import { emitDynamicExpr } from "./expr-dynamic.js";
|
|
73
78
|
import { emitIntrinsicExpr, emitSerializationExpr, emitAsyncExpr } from "./expr-async.js";
|
|
74
79
|
import { PublishWalkers } from "./publish.js";
|
|
80
|
+
import { computeImmutableFields, stableGlobals } from "../immutability.js";
|
|
75
81
|
import { computePublishedTypes } from "../publication.js";
|
|
76
82
|
import { emitJsInteropExpr, emitExpr } from "./expr-dispatch.js";
|
|
77
83
|
import { emitJsMarshal, emitJsOp, emitJsExit, islandAdapter, islandTypedAdapter, } from "./expr-island.js";
|
|
@@ -94,7 +100,7 @@ import { LlWalkers } from "./walkers.js";
|
|
|
94
100
|
import { isObjectArm, NULLABLE_ABSENT, NULLABLE_NULL, NullableUnions } from "./nullable-unions.js";
|
|
95
101
|
import { emitNullablePresent, emitUnionPeek, emitUnionTag } from "./union-repr.js";
|
|
96
102
|
import { literalCapWord, literalCapWord32 } from "./string-key-hash.js";
|
|
97
|
-
import { NUMBER_MAP_ENTRY_DECL } from "./map-number-lookup.js";
|
|
103
|
+
import { NUMBER_MAP_ENTRY_DECL, STRING_MAP_ENTRY_DECL } from "./map-number-lookup.js";
|
|
98
104
|
import { STRING_READ_HELPERS } from "./string-reads.js";
|
|
99
105
|
import { emitConcatInputs, emitMixedConcat, numberPart, stringParts, } from "./string-construction.js";
|
|
100
106
|
import { arrNewCall, boxAccess, boxNewCall, boxReleaseSym, computeTraced, elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, FN_ATTRS, inlineRcDecls, llFieldType, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
|
|
@@ -124,16 +130,20 @@ function ffiCallbackDummyLl(callback) {
|
|
|
124
130
|
return "i32 0";
|
|
125
131
|
}
|
|
126
132
|
}
|
|
127
|
-
export function emitLlvmModule(
|
|
133
|
+
export function emitLlvmModule(input, options = {}) {
|
|
134
|
+
// Facts are keyed by node identity: emit the same module whether it was
|
|
135
|
+
// lowered in memory or read back from its serialized form.
|
|
136
|
+
const mod = unshareNodes(input);
|
|
128
137
|
// Keep source storage intact for debugger inspection in dev builds.
|
|
129
138
|
return new LlEmitter(options.debugSources === undefined
|
|
130
|
-
? scalarizeNumericRecords(specializeNumericCalls(mod, mangleFunction))
|
|
139
|
+
? scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction))
|
|
131
140
|
: mod, options).emit();
|
|
132
141
|
}
|
|
133
142
|
/** Keep large translation units below the host's single-string limit. */
|
|
134
|
-
export function emitLlvmModuleSource(
|
|
143
|
+
export function emitLlvmModuleSource(input, options = {}) {
|
|
144
|
+
const mod = unshareNodes(input);
|
|
135
145
|
const parts = new LlEmitter(options.debugSources === undefined
|
|
136
|
-
? scalarizeNumericRecords(specializeNumericCalls(mod, mangleFunction))
|
|
146
|
+
? scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction))
|
|
137
147
|
: mod, options).emitParts();
|
|
138
148
|
const length = parts.reduce((total, part) => total + part.length, Math.max(0, parts.length - 1));
|
|
139
149
|
if (length <= 256 * 1024 * 1024)
|
|
@@ -224,12 +234,27 @@ export class LlEmitter {
|
|
|
224
234
|
debugScope = null;
|
|
225
235
|
sizeType;
|
|
226
236
|
ffiExtendNarrowIntegers;
|
|
227
|
-
|
|
237
|
+
cycleBufferedOffset;
|
|
238
|
+
/** Emitted ScrCycType descriptors so far (their collector statistics
|
|
239
|
+
* indices; see ShapeHost.nextCycKind). */
|
|
240
|
+
cycKinds = 0;
|
|
228
241
|
wasi;
|
|
242
|
+
/** The target lacks a rounding instruction at its baseline CPU (x86-64
|
|
243
|
+
* before SSE4.1), so `llvm.trunc` would lower to a libm call: emit
|
|
244
|
+
* truncation inline through an integer round trip instead. */
|
|
245
|
+
inlineTrunc;
|
|
229
246
|
/** ELF worker executables give thread-locals the executable TLS models. */
|
|
230
247
|
executableTls;
|
|
248
|
+
/** Source loops enclosing the statement being emitted in the current
|
|
249
|
+
* function body. */
|
|
250
|
+
loopDepth = 0;
|
|
251
|
+
/** Darwin worker executables read the stack guard from a pthread
|
|
252
|
+
* thread-specific-data slot off the thread pointer (scr_runtime.h's
|
|
253
|
+
* SCR_STACK_GUARD_TSD): the architecture names the access sequence. */
|
|
254
|
+
stackGuardTsd;
|
|
231
255
|
emitLibraryIdentity;
|
|
232
256
|
runtimeAbiMarker;
|
|
257
|
+
detachedExit;
|
|
233
258
|
/** Interned string literals: UTF-8 text → { symbol, byte length } —
|
|
234
259
|
* first-use order, the runtime ABI’s determinism discipline. */
|
|
235
260
|
literals = new Map();
|
|
@@ -284,6 +309,12 @@ export class LlEmitter {
|
|
|
284
309
|
* fiberless-async analysis scans it for suspension entry points. */
|
|
285
310
|
fnDefText = new Map();
|
|
286
311
|
referenceEffects;
|
|
312
|
+
/** Field-sensitive reference writes (null in debug builds). */
|
|
313
|
+
referenceWrites = null;
|
|
314
|
+
borrowedReturnKeyCache = new Map();
|
|
315
|
+
/** The statement list being emitted and the current index, for blocks
|
|
316
|
+
* whose bindings end with the list (emitBlock and function bodies). */
|
|
317
|
+
stmtCursor = null;
|
|
287
318
|
optionalArrayReads;
|
|
288
319
|
checkedNarrows;
|
|
289
320
|
callArrayReads = new Map();
|
|
@@ -327,6 +358,9 @@ export class LlEmitter {
|
|
|
327
358
|
ffiHasRetainedCallback;
|
|
328
359
|
ffiHasForeignCallback;
|
|
329
360
|
globalTypes = new Map();
|
|
361
|
+
/** Module constants that keep one value once initialized
|
|
362
|
+
* (immutability.ts stableGlobals). */
|
|
363
|
+
stableGlobals = new Set();
|
|
330
364
|
tdzGlobals = new Map();
|
|
331
365
|
constantNumericTables;
|
|
332
366
|
/** May-throw analysis (computeMayThrow): pending
|
|
@@ -342,6 +376,13 @@ export class LlEmitter {
|
|
|
342
376
|
/** Static types whose values can be published (@scriptc/threads): their
|
|
343
377
|
* stores carry the frozen-object guard. Null without publish calls. */
|
|
344
378
|
publishedTypes;
|
|
379
|
+
/** Construction-only class and record fields (immutability.ts); null
|
|
380
|
+
* where the facts are disabled. */
|
|
381
|
+
immutableFields;
|
|
382
|
+
/** Constructor function names: their bodies may still write their own
|
|
383
|
+
* `this`'s construction-only fields. */
|
|
384
|
+
constructorFunctions;
|
|
385
|
+
inConstructor = false;
|
|
345
386
|
/** Method names with at least one may-throw implementation — the
|
|
346
387
|
* virtualCall pending check's key. */
|
|
347
388
|
mayThrowMethods = new Set();
|
|
@@ -418,11 +459,22 @@ export class LlEmitter {
|
|
|
418
459
|
initializerBindings;
|
|
419
460
|
/** Whole-program int32 storage and parameter/result facts. */
|
|
420
461
|
int32Slots;
|
|
462
|
+
/** Immutable module constants (contract types) whose graphs become
|
|
463
|
+
* immortal once initialized: their reads need no reference counting. */
|
|
464
|
+
immortalGlobals;
|
|
465
|
+
/** ProgramLifetime hierarchy roots (contract types): their instances are
|
|
466
|
+
* immortal from construction (classes.ts programLifetimeClass). */
|
|
467
|
+
programLifetime;
|
|
421
468
|
numericLocals = new Map();
|
|
422
469
|
captureIds = new Set();
|
|
423
470
|
/** Active induction bindings retain their width independently of size_t. */
|
|
424
471
|
integerLoopBindings = new Map();
|
|
425
472
|
countedLoopsEnabled = false;
|
|
473
|
+
/** Whole-program final reference fields (ir/final-fields.ts). */
|
|
474
|
+
finalFields;
|
|
475
|
+
/** The current function may borrow final fields of borrowable objects:
|
|
476
|
+
* not a constructor (where they change), not async or a generator. */
|
|
477
|
+
finalFieldBorrows = false;
|
|
426
478
|
integerRanges = new Map();
|
|
427
479
|
byteWindowsEnabled = true;
|
|
428
480
|
byteWindowEntries = new Map();
|
|
@@ -472,22 +524,43 @@ export class LlEmitter {
|
|
|
472
524
|
this.constantNumericTables = findConstantNumericTables(mod);
|
|
473
525
|
this.initializerBindings = findInitializerBindings(mod);
|
|
474
526
|
this.int32Slots = analyzeInt32Slots(mod);
|
|
527
|
+
this.immortalGlobals = new Set(mod.contracts?.immortalGlobals ?? []);
|
|
528
|
+
this.programLifetime = new Set(mod.contracts?.programLifetime ?? []);
|
|
529
|
+
this.finalFields = analyzeFinalFields(mod);
|
|
475
530
|
this.sizeType = options.pointerBits === 32 ? "i32" : "i64";
|
|
476
531
|
this.ffiExtendNarrowIntegers =
|
|
477
532
|
options.wasi === true || ffiExtendsNarrowIntegers(options.targetTriple);
|
|
478
533
|
this.wasi = options.wasi === true;
|
|
534
|
+
// An empty triple targets the host, as for executableTls below: the
|
|
535
|
+
// Node-hosted CLI passes none, the native CLI passes its toolchain's, and
|
|
536
|
+
// both must emit the same module for the same target.
|
|
537
|
+
this.inlineTrunc =
|
|
538
|
+
!this.wasi &&
|
|
539
|
+
(options.targetTriple
|
|
540
|
+
? /^(x86_64|amd64)\b/i.test(options.targetTriple)
|
|
541
|
+
: process.arch === "x64");
|
|
479
542
|
this.executableTls =
|
|
480
543
|
mod.workers === true &&
|
|
481
544
|
mod.lib === undefined &&
|
|
482
545
|
!this.wasi &&
|
|
483
546
|
!/apple|darwin|windows|mingw|cygwin|win32/.test(options.targetTriple || process.platform);
|
|
547
|
+
const host = options.targetTriple || `${process.arch}-${process.platform}`;
|
|
548
|
+
this.stackGuardTsd =
|
|
549
|
+
mod.workers === true && mod.lib === undefined && !this.wasi && /apple|darwin/.test(host)
|
|
550
|
+
? /^(arm64|aarch64)/.test(host)
|
|
551
|
+
? "aarch64"
|
|
552
|
+
: /^(x86_64|amd64|x64)/.test(host)
|
|
553
|
+
? "x86_64"
|
|
554
|
+
: null
|
|
555
|
+
: null;
|
|
484
556
|
this.emitLibraryIdentity = options.emitLibraryIdentity !== false;
|
|
485
557
|
this.runtimeAbiMarker = options.runtimeAbiMarker === true;
|
|
558
|
+
this.detachedExit = options.detachedExit === true;
|
|
486
559
|
this.rcHelpers = options.inlineRc === true ? new Set() : null;
|
|
487
|
-
// ScrCycHdr.
|
|
488
|
-
//
|
|
489
|
-
//
|
|
490
|
-
this.
|
|
560
|
+
// ScrCycHdr.buffered sits 6 bytes behind every object. The wasm32
|
|
561
|
+
// header pads before its descriptor so its payload remains
|
|
562
|
+
// double-aligned without changing this ABI offset.
|
|
563
|
+
this.cycleBufferedOffset = 6;
|
|
491
564
|
this.ffiCallbackAdapters = allocateFfiCallbackAdapters(mod.ffiImports ?? []);
|
|
492
565
|
this.ffiHasRetainedCallback = hasRetainedFfiCallback(mod.ffiImports ?? []);
|
|
493
566
|
this.ffiHasForeignCallback = hasForeignFfiCallback(mod.ffiImports ?? []);
|
|
@@ -507,6 +580,8 @@ export class LlEmitter {
|
|
|
507
580
|
this.workerEntryPolls = mt.workerEntryPolls ?? null;
|
|
508
581
|
this.stackChecks = mt.stackChecks ?? null;
|
|
509
582
|
this.publishedTypes = computePublishedTypes(mod);
|
|
583
|
+
this.immutableFields = computeImmutableFields(mod);
|
|
584
|
+
this.constructorFunctions = new Set((mod.classes ?? []).map((c) => `%${c.name}.constructor`));
|
|
510
585
|
this.indirectMayThrow = mt.indirect || mod.workers === true;
|
|
511
586
|
for (const cls of mod.classes ?? []) {
|
|
512
587
|
for (const m of cls.methods ?? []) {
|
|
@@ -522,9 +597,12 @@ export class LlEmitter {
|
|
|
522
597
|
this.nullableFields = new NullableRefFields(mod.classes ?? [], this.boxedUnionsById);
|
|
523
598
|
// Optional array reads also cover nullable unions: emitLocalArrayRead
|
|
524
599
|
// then yields the (borrowed or owned) element pointer, not a stack box.
|
|
525
|
-
|
|
600
|
+
// Arrays of such a union are read the same way.
|
|
601
|
+
this.optionalArrayReads = new OptionalArrayReads(this.fnByName, this.unionsById, (id) => this.nullableUnions.has(id));
|
|
526
602
|
this.checkedNarrows = new CheckedNarrows(this.fnByName);
|
|
527
603
|
this.referenceEffects = new ReferenceEffects(this.fnByName, (call) => this.optionalArrayReads.get(call) !== null, mod.classes ?? []);
|
|
604
|
+
if (this.debug === null)
|
|
605
|
+
this.referenceWrites = new ReferenceWrites(this.fnByName, mod.classes ?? [], (call) => this.optionalArrayReads.get(call) !== null, new Set((mod.globals ?? []).map((g) => g.id)), { unions: mod.unions, records: mod.records }, this.immutableFields);
|
|
528
606
|
const walkParameters = new Map();
|
|
529
607
|
if (this.debug === null) {
|
|
530
608
|
const host = {
|
|
@@ -532,9 +610,10 @@ export class LlEmitter {
|
|
|
532
610
|
borrowsWithoutOwning: (e) => this.borrowsWithoutOwning(e),
|
|
533
611
|
};
|
|
534
612
|
for (const fn of this.fnByName.values()) {
|
|
535
|
-
|
|
613
|
+
const preserving = this.referenceEffects.functions.has(fn.name);
|
|
614
|
+
if (!preserving && this.referenceWrites === null)
|
|
536
615
|
continue;
|
|
537
|
-
const walks = findWalkBorrows(fn, host);
|
|
616
|
+
const walks = findWalkBorrows(fn, preserving ? host : this.unwrittenWalkHost(fn, host));
|
|
538
617
|
if (walks.size === 0)
|
|
539
618
|
continue;
|
|
540
619
|
this.walkBorrowsByFunction.set(fn.name, walks);
|
|
@@ -578,7 +657,7 @@ export class LlEmitter {
|
|
|
578
657
|
this.tracedUnions = traced.unions;
|
|
579
658
|
this.shapeHost = {
|
|
580
659
|
sizeType: this.sizeType,
|
|
581
|
-
|
|
660
|
+
cycleBufferedOffset: this.cycleBufferedOffset,
|
|
582
661
|
tracedShapes: this.tracedShapes,
|
|
583
662
|
tracedUnions: this.tracedUnions,
|
|
584
663
|
nullableUnions: this.nullableUnions,
|
|
@@ -595,6 +674,7 @@ export class LlEmitter {
|
|
|
595
674
|
// paths address its thread-local state.
|
|
596
675
|
threadLocalAlloc: mod.workers === true,
|
|
597
676
|
rcHelpers: this.rcHelpers,
|
|
677
|
+
nextCycKind: () => FIRST_EMITTED_CYC_KIND + this.cycKinds++,
|
|
598
678
|
unionsById: this.unionsById,
|
|
599
679
|
declare: (decl) => this.declare(decl),
|
|
600
680
|
needOom: () => this.needOom(),
|
|
@@ -617,6 +697,7 @@ export class LlEmitter {
|
|
|
617
697
|
},
|
|
618
698
|
pendingTestLines: (dest) => this.pendingTestLines(dest),
|
|
619
699
|
virtualEntry: (implFn) => this.virtualEntry(implFn),
|
|
700
|
+
programLifetime: this.programLifetime,
|
|
620
701
|
};
|
|
621
702
|
this.walkers = new LlWalkers(this.shapeHost);
|
|
622
703
|
this.dyn = new LlDyn(this.shapeHost);
|
|
@@ -638,6 +719,7 @@ export class LlEmitter {
|
|
|
638
719
|
if (g.tdz)
|
|
639
720
|
this.tdzGlobals.set(g.id, g.name);
|
|
640
721
|
}
|
|
722
|
+
this.stableGlobals = stableGlobals(mod);
|
|
641
723
|
// Runtime error, EventEmitter, and stream classes have known layouts.
|
|
642
724
|
// Subclasses embed the ScrEmitter/ScrStream prefixes from classes.ts.
|
|
643
725
|
const classes = mod.classes ?? [];
|
|
@@ -997,14 +1079,16 @@ export class LlEmitter {
|
|
|
997
1079
|
if (!this.executableTls)
|
|
998
1080
|
return parts;
|
|
999
1081
|
// A worker executable's thread-locals all live in the executable's own
|
|
1000
|
-
// TLS block
|
|
1001
|
-
//
|
|
1002
|
-
//
|
|
1003
|
-
//
|
|
1004
|
-
//
|
|
1082
|
+
// TLS block: the program's internal ones, and the runtime's external
|
|
1083
|
+
// ones, because the runtime is always linked statically into the
|
|
1084
|
+
// executable. So on ELF they can all use the local-exec model: LLVM's
|
|
1085
|
+
// default general-dynamic sequence (relaxed by the linker to `mov %fs:0`
|
|
1086
|
+
// plus an add), and even initial-exec (relaxed to `mov $offset` plus an
|
|
1087
|
+
// indexed access), become one %fs-relative operand LLVM can fold into
|
|
1088
|
+
// loads and compares. Darwin's thread-local variables have one model.
|
|
1005
1089
|
return parts.map((part) => part
|
|
1006
1090
|
.replace(/^(@[-$._A-Za-z0-9]+ = internal )thread_local global /gm, "$1thread_local(localexec) global ")
|
|
1007
|
-
.replace(/^(@[-$._A-Za-z0-9]+ = external )thread_local global /gm, "$1thread_local(
|
|
1091
|
+
.replace(/^(@[-$._A-Za-z0-9]+ = external )thread_local global /gm, "$1thread_local(localexec) global "));
|
|
1008
1092
|
}
|
|
1009
1093
|
emitModuleParts() {
|
|
1010
1094
|
// Function bodies first (the literal/unit/fn-value tables fill as they
|
|
@@ -1356,10 +1440,15 @@ export class LlEmitter {
|
|
|
1356
1440
|
`%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32, i32, ptr }`,
|
|
1357
1441
|
`%ScrFfiTable = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, i8, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`,
|
|
1358
1442
|
`%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr, ptr }`,
|
|
1359
|
-
// ScrArr mirrors scr_runtime.h field-for-field
|
|
1360
|
-
//
|
|
1361
|
-
//
|
|
1362
|
-
|
|
1443
|
+
// ScrArr mirrors scr_runtime.h field-for-field: { rc, len, cap, elem,
|
|
1444
|
+
// cyc, ops, data, present, ext } (field numbers in SCR_ARR_FIELD),
|
|
1445
|
+
// where ops points at the element type's constant RC descriptor
|
|
1446
|
+
// (%ScrArrOps, SCR_ELEM_REF only) and ext at the out-of-line sparse,
|
|
1447
|
+
// property and metadata storage (NULL until first used). Live dynamic
|
|
1448
|
+
// stream commits swap its dense, presence and out-of-line storage while
|
|
1449
|
+
// preserving the target object's identity.
|
|
1450
|
+
`%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i32, i32, ptr, ptr, ptr, ptr }`,
|
|
1451
|
+
`%ScrArrOps = type { ptr, ptr, ptr }`,
|
|
1363
1452
|
// The ScrMap prefix { rc, key_kind, val_kind, val_retain, val_release,
|
|
1364
1453
|
// val_trace, key_retain, key_release, key_trace } — the inline RC
|
|
1365
1454
|
// fast paths test the two trace slots (shapes.ts).
|
|
@@ -1372,9 +1461,13 @@ export class LlEmitter {
|
|
|
1372
1461
|
...(this.decls.has(NUMBER_MAP_ENTRY_DECL)
|
|
1373
1462
|
? [
|
|
1374
1463
|
`%ScrMapIx = type { ${this.sizeType}, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ${this.sizeType}, ptr, ${this.sizeType}, ptr, ptr, ptr, ${this.sizeType} }`,
|
|
1375
|
-
`%ScrMapEntry = type { i64, i64, i64 }`,
|
|
1376
1464
|
]
|
|
1377
1465
|
: []),
|
|
1466
|
+
// String-keyed reference reads load the value slot of the entry
|
|
1467
|
+
// scr_map_entry_str returns (map-number-lookup.ts).
|
|
1468
|
+
...(this.decls.has(NUMBER_MAP_ENTRY_DECL) || this.decls.has(STRING_MAP_ENTRY_DECL)
|
|
1469
|
+
? [`%ScrMapEntry = type { i64, i64, i64 }`]
|
|
1470
|
+
: []),
|
|
1378
1471
|
// The runtime error prefix { rc, vt, name, message, code, cause } and the
|
|
1379
1472
|
// class-object shape { rc, pre, post, ctor, name } — field reads on
|
|
1380
1473
|
// builtin errors and classval loads GEP through these.
|
|
@@ -1422,6 +1515,10 @@ export class LlEmitter {
|
|
|
1422
1515
|
const tl = this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : "";
|
|
1423
1516
|
out.push(``, `@scr_error_vts = external ${tl}global [${RUNTIME_ERROR_CLASSES.size} x %ScrVt]`, `declare void @scr_init()`, `declare void @scr_lib_init(i32, ptr)`, ...(this.mod.workers ? [`declare void @scr_runtime_workers_v8()`] : []), ...(this.stackChecks?.size ? [`declare void @scr_stack_guard_init()`] : []), ...(this.runtimeAbiMarker && this.mod.lib === undefined
|
|
1424
1517
|
? [`declare void @${RUNTIME_ABI_MARKER}()`]
|
|
1518
|
+
: []),
|
|
1519
|
+
// Overrides the runtime's weak default (scr_lib.c).
|
|
1520
|
+
...(this.detachedExit && this.mod.lib === undefined
|
|
1521
|
+
? [`@scr_detached_exit = constant i8 1`]
|
|
1425
1522
|
: []));
|
|
1426
1523
|
for (const d of this.decls)
|
|
1427
1524
|
out.push(d);
|
|
@@ -1429,10 +1526,9 @@ export class LlEmitter {
|
|
|
1429
1526
|
const flushedDecls = new Set(this.decls);
|
|
1430
1527
|
for (const table of this.constantNumericTables.values()) {
|
|
1431
1528
|
const n = table.values.length;
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
` %index = fptoui double %i to ${this.sizeType}`, ` %roundtrip = uitofp ${this.sizeType} %index to double`, ` %integer = fcmp oeq double %roundtrip, %i`, ` br i1 %integer, label %read, label %fallback`, `read:`, ` %slot = getelementptr inbounds [${n} x double], ptr @${table.symbol}, ${this.sizeType} 0, ${this.sizeType} %index`, ` %value = load double, ptr %slot`, ` ret double %value`, `fallback:`, ` %generic = call double @scr_arr_get_number(ptr %a, double %i)`, ` ret double %generic`, `}`, ``);
|
|
1529
|
+
let tableTmp = 0;
|
|
1530
|
+
const tableIndex = exactIndexLines("%i", String(n), "i64", () => `%ix${tableTmp++}`);
|
|
1531
|
+
out.push(`@${table.symbol} = private constant [${n} x double] [${table.values.map((v) => `double ${f64Lit(v)}`).join(", ")}]`, `define internal double @${table.symbol}_get(ptr %a, double %i) alwaysinline #0 {`, `entry:`, ` %initialized = icmp ne ptr %a, null`, ...tableIndex.lines.map((line) => ` ${line}`), ` %safe = and i1 %initialized, ${tableIndex.ok}`, ` br i1 %safe, label %read, label %fallback`, `read:`, ` %slot = getelementptr inbounds [${n} x double], ptr @${table.symbol}, i64 0, i64 ${tableIndex.wide}`, ` %value = load double, ptr %slot`, ` ret double %value`, `fallback:`, ` %generic = call double @scr_arr_get_number(ptr %a, double %i)`, ` ret double %generic`, `}`, ``);
|
|
1436
1532
|
}
|
|
1437
1533
|
for (const [text, lit] of this.literals) {
|
|
1438
1534
|
// Immortal interned ScrStr: { rc = SIZE_MAX, len, cap = len, bytes\0 } —
|
|
@@ -1499,7 +1595,7 @@ export class LlEmitter {
|
|
|
1499
1595
|
// strings — a no-op.
|
|
1500
1596
|
const n = inst.slots.length;
|
|
1501
1597
|
const present = n === 0 ? "zeroinitializer" : `[ ${inst.slots.map(() => "i8 1").join(", ")} ]`;
|
|
1502
|
-
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}, i32 2,
|
|
1598
|
+
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}, i32 2, i32 0, ptr null, ptr @${inst.sym}_data, ptr @${inst.sym}_present, ptr null }`);
|
|
1503
1599
|
}
|
|
1504
1600
|
if (this.templateStringsInstances.size > 0)
|
|
1505
1601
|
out.push(``);
|
|
@@ -2024,7 +2120,13 @@ export class LlEmitter {
|
|
|
2024
2120
|
// keeps the complete SameValueZero semantics.
|
|
2025
2121
|
if (this.decls.has(NUMBER_MAP_ENTRY_DECL)) {
|
|
2026
2122
|
const sz = this.sizeType;
|
|
2027
|
-
|
|
2123
|
+
let mapTmp = 0;
|
|
2124
|
+
const mapIndex = exactIndexLines("%k", "%nd", sz, () => `%ix${mapTmp++}`);
|
|
2125
|
+
defs.push(`define internal ptr @sc_map_entry_f64(ptr %m, double %k) ${FN_ATTRS} {`, `entry:`, ` %ndp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 19`, ` %nd = load ${sz}, ptr %ndp`,
|
|
2126
|
+
// A non-integral key inside the table is as absent as one outside it.
|
|
2127
|
+
...mapIndex.lines.map((line) => ` ${line}`), ` br i1 ${mapIndex.ok}, label %slot, label %outside`, `slot:`, sz === "i64"
|
|
2128
|
+
? ` %i = add i64 ${mapIndex.wide}, 0`
|
|
2129
|
+
: ` %i = trunc i64 ${mapIndex.wide} to ${sz}`, ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 18`, ` %dense = load ptr, ptr %dp`, ` %sp = getelementptr inbounds i32, ptr %dense, ${sz} %i`, ` %s = load i32, ptr %sp`, ` %empty = icmp eq i32 %s, 0`, ` br i1 %empty, label %miss, label %hit`, `hit:`, ` %e32 = sub i32 %s, 1`, sz === "i32" ? ` %e = add i32 %e32, 0` : ` %e = zext i32 %e32 to ${sz}`, ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 12`, ` %entries = load ptr, ptr %ep`, ` %found = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %e`, ` ret ptr %found`, `miss:`, ` ret ptr null`, `outside:`, ` %dense.any = icmp ne ${sz} %nd, 0`, ` br i1 %dense.any, label %miss, label %slow`, `slow:`, ` %r = call ptr @scr_map_entry_f64(ptr %m, double %k)`, ` ret ptr %r`, `}`, ``);
|
|
2028
2130
|
}
|
|
2029
2131
|
// String comparisons enter through small internal wrappers that LLVM
|
|
2030
2132
|
// inlines at each site: interned literals and shared values compare by
|
|
@@ -2833,13 +2935,28 @@ export class LlEmitter {
|
|
|
2833
2935
|
* return and stays eligible for tail-call and recursion elimination. */
|
|
2834
2936
|
emitUnwind(passThrough) {
|
|
2835
2937
|
if (this.mod.workers && this.tryStack.length > 0) {
|
|
2836
|
-
this.declare(`declare zeroext i1 @scr_context_stopping()`);
|
|
2837
|
-
const stopped = this.B.tmp();
|
|
2838
|
-
this.B.line(`${stopped} = call zeroext i1 @scr_context_stopping()`);
|
|
2839
2938
|
const stop = this.B.newLabel("context.stop");
|
|
2840
2939
|
const ordinary = this.B.newLabel("context.throw");
|
|
2841
|
-
this.
|
|
2842
|
-
|
|
2940
|
+
if (this.executableTls) {
|
|
2941
|
+
// A set stop bit unwinds while the active cell may still be clear:
|
|
2942
|
+
// install the termination sentinel before skipping the handlers.
|
|
2943
|
+
const word = this.loadExcFlag();
|
|
2944
|
+
const bit = this.B.tmp();
|
|
2945
|
+
const stopped = this.B.tmp();
|
|
2946
|
+
this.B.line(`${bit} = and i32 ${word}, 2`);
|
|
2947
|
+
this.B.line(`${stopped} = icmp ne i32 ${bit}, 0`);
|
|
2948
|
+
this.B.condBr(stopped, stop, ordinary);
|
|
2949
|
+
this.B.startBlock(stop);
|
|
2950
|
+
this.declare(`declare zeroext i1 @scr_context_checkpoint()`);
|
|
2951
|
+
this.B.line(`call zeroext i1 @scr_context_checkpoint()`);
|
|
2952
|
+
}
|
|
2953
|
+
else {
|
|
2954
|
+
this.declare(`declare zeroext i1 @scr_context_stopping()`);
|
|
2955
|
+
const stopped = this.B.tmp();
|
|
2956
|
+
this.B.line(`${stopped} = call zeroext i1 @scr_context_stopping()`);
|
|
2957
|
+
this.B.condBr(stopped, stop, ordinary);
|
|
2958
|
+
this.B.startBlock(stop);
|
|
2959
|
+
}
|
|
2843
2960
|
const handlers = this.tryStack;
|
|
2844
2961
|
this.tryStack = [];
|
|
2845
2962
|
this.emitUnwind();
|
|
@@ -2896,6 +3013,18 @@ export class LlEmitter {
|
|
|
2896
3013
|
}
|
|
2897
3014
|
this.B.br(cleanup.label);
|
|
2898
3015
|
}
|
|
3016
|
+
/** Loads the running thread's exact pending word (ELF worker
|
|
3017
|
+
* executables, scr_exception.c): bit 0 mirrors the active cell, bit 1
|
|
3018
|
+
* requests the context's stop. It lives in the executable's static TLS
|
|
3019
|
+
* block, so the access is one thread-pointer-relative load. Monotonic by
|
|
3020
|
+
* default: a loop must not hoist the load out and miss a cross-thread
|
|
3021
|
+
* stop (see emitPendingCheck for the unordered tests). */
|
|
3022
|
+
loadExcFlag(unordered = false) {
|
|
3023
|
+
this.declare(`@scr_exc_flag = external thread_local(localexec) global i32, align 4`);
|
|
3024
|
+
const word = this.B.tmp();
|
|
3025
|
+
this.B.line(`${word} = load atomic i32, ptr @scr_exc_flag ${unordered ? "unordered" : "monotonic"}, align 4`);
|
|
3026
|
+
return word;
|
|
3027
|
+
}
|
|
2899
3028
|
/** Instructions (two-space indented) that set the i1 `dest` to "an
|
|
2900
3029
|
* exception is pending in the active context" — scr_exc_pending()'s
|
|
2901
3030
|
* meaning — for the helper and adapter bodies that test it outside
|
|
@@ -2922,8 +3051,9 @@ export class LlEmitter {
|
|
|
2922
3051
|
* call's result temp must join its frame BEFORE this runs so the unwind
|
|
2923
3052
|
* releases the dummy (NULL for refcounted kinds) harmlessly. A scalar
|
|
2924
3053
|
* call result of the function's return type may be passed as the unwind's
|
|
2925
|
-
* dummy value (see emitUnwind).
|
|
2926
|
-
|
|
3054
|
+
* dummy value (see emitUnwind). `poll` marks a worker termination poll
|
|
3055
|
+
* (function entry or loop budget), whose test must stay ordered. */
|
|
3056
|
+
emitPendingCheck(passThrough, poll = false) {
|
|
2927
3057
|
const through = passThrough !== undefined &&
|
|
2928
3058
|
this.llType(passThrough.type) === this.llType(this.currentReturnType) &&
|
|
2929
3059
|
(passThrough.type.kind === "f64" || passThrough.type.kind === "bool")
|
|
@@ -2945,16 +3075,35 @@ export class LlEmitter {
|
|
|
2945
3075
|
return;
|
|
2946
3076
|
}
|
|
2947
3077
|
// Executables test the active exception cell's kind inline (its first
|
|
2948
|
-
// field).
|
|
2949
|
-
// is nonzero
|
|
2950
|
-
//
|
|
2951
|
-
//
|
|
2952
|
-
//
|
|
2953
|
-
//
|
|
3078
|
+
// field). ELF worker executables test the thread's exact pending word,
|
|
3079
|
+
// which is also nonzero once the context must stop, and unwind directly:
|
|
3080
|
+
// no site carries an out-of-line call, so LLVM's inliner sees the check
|
|
3081
|
+
// as two instructions. Other worker programs test the process-wide
|
|
3082
|
+
// alert word: it is nonzero while any script thread may have an
|
|
3083
|
+
// exception pending in its active cell or has been asked to stop, so
|
|
3084
|
+
// the common case is one ordinary global load (a thread-local access is
|
|
3085
|
+
// a call on Darwin) and the out-of-line scr_exc_pending answers for this
|
|
3086
|
+
// thread, observing cancellation and reinstalling the termination
|
|
3087
|
+
// sentinel.
|
|
2954
3088
|
this.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
2955
3089
|
let word;
|
|
2956
|
-
|
|
2957
|
-
|
|
3090
|
+
const exact = this.mod.workers !== true || this.executableTls;
|
|
3091
|
+
if (this.executableTls) {
|
|
3092
|
+
// Termination stays observable where the polls were dropped for it:
|
|
3093
|
+
// polls, tests inside source loops (exc's loop-poll rule relies on
|
|
3094
|
+
// them every iteration) and tail-position tests (a dead ordered load
|
|
3095
|
+
// keeps recursion from becoming a loop without stack growth) use an
|
|
3096
|
+
// ordered load, which LLVM never deletes, hoists or merges. Every
|
|
3097
|
+
// other test is an unordered load: it reads the same word, but LLVM's
|
|
3098
|
+
// memory analyses no longer treat it as a write, so loads around it
|
|
3099
|
+
// stay forwardable after inlining. Same-thread changes only happen in
|
|
3100
|
+
// runtime calls, which clobber memory.
|
|
3101
|
+
word = this.loadExcFlag(!poll && through === undefined && this.loopDepth === 0);
|
|
3102
|
+
}
|
|
3103
|
+
else if (this.mod.workers === true) {
|
|
3104
|
+
// dso_local: executables link the runtime statically, so the word is
|
|
3105
|
+
// addressed directly instead of through the GOT.
|
|
3106
|
+
this.declare(`@scr_exc_alert = external dso_local global i32, align 64`);
|
|
2958
3107
|
word = B.tmp();
|
|
2959
3108
|
B.line(`${word} = load atomic i32, ptr @scr_exc_alert monotonic, align 64`);
|
|
2960
3109
|
}
|
|
@@ -2984,7 +3133,7 @@ export class LlEmitter {
|
|
|
2984
3133
|
B.line(`${cold} = call i1 @llvm.expect.i1(i1 ${hit}, i1 false)`);
|
|
2985
3134
|
const lu = B.newLabel("exc.u");
|
|
2986
3135
|
const lk = B.newLabel("exc.k");
|
|
2987
|
-
if (
|
|
3136
|
+
if (!exact) {
|
|
2988
3137
|
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
2989
3138
|
const slow = B.newLabel("exc.s");
|
|
2990
3139
|
B.condBr(cold, slow, lk);
|
|
@@ -3016,30 +3165,98 @@ export class LlEmitter {
|
|
|
3016
3165
|
* throw Node's RangeError and unwind like any other pending exception. */
|
|
3017
3166
|
emitStackCheck() {
|
|
3018
3167
|
const B = this.B;
|
|
3019
|
-
const tl = this.mod.workers === true ? "thread_local " : "";
|
|
3020
|
-
this.declare(`@scr_stack_guard = external ${tl}global ptr`);
|
|
3021
3168
|
this.declare(`declare ptr @llvm.stacksave.p0()`);
|
|
3022
3169
|
this.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
3023
3170
|
this.declare(`declare void @scr_stack_overflow()`);
|
|
3024
|
-
const sp = B.tmp();
|
|
3025
|
-
const guard = B.tmp();
|
|
3026
|
-
const low = B.tmp();
|
|
3027
|
-
const cold = B.tmp();
|
|
3028
|
-
B.line(`${sp} = call ptr @llvm.stacksave.p0()`);
|
|
3029
|
-
B.line(`${guard} = load ptr, ptr @scr_stack_guard`);
|
|
3030
|
-
B.line(`${low} = icmp ult ptr ${sp}, ${guard}`);
|
|
3031
|
-
B.line(`${cold} = call i1 @llvm.expect.i1(i1 ${low}, i1 false)`);
|
|
3032
3171
|
const over = B.newLabel("stack.over");
|
|
3033
3172
|
const ok = B.newLabel("stack.ok");
|
|
3034
|
-
B.
|
|
3173
|
+
const sp = B.tmp();
|
|
3174
|
+
B.line(`${sp} = call ptr @llvm.stacksave.p0()`);
|
|
3175
|
+
const test = (guard) => {
|
|
3176
|
+
const low = B.tmp();
|
|
3177
|
+
const cold = B.tmp();
|
|
3178
|
+
B.line(`${low} = icmp ult ptr ${sp}, ${guard}`);
|
|
3179
|
+
B.line(`${cold} = call i1 @llvm.expect.i1(i1 ${low}, i1 false)`);
|
|
3180
|
+
B.condBr(cold, over, ok);
|
|
3181
|
+
};
|
|
3182
|
+
if (this.stackGuardTsd !== null) {
|
|
3183
|
+
// A Darwin thread-local access is a call to its TLV thunk: read the
|
|
3184
|
+
// guard from its thread-specific-data slot at the runtime-validated
|
|
3185
|
+
// offset instead, and call out only when the runtime found no slot.
|
|
3186
|
+
this.declare(`@scr_stack_guard_tsd = external dso_local global i64`);
|
|
3187
|
+
this.declare(`declare i64 @scr_stack_guard_get()`);
|
|
3188
|
+
const offset = B.tmp();
|
|
3189
|
+
const direct = B.tmp();
|
|
3190
|
+
const likely = B.tmp();
|
|
3191
|
+
B.line(`${offset} = load i64, ptr @scr_stack_guard_tsd`);
|
|
3192
|
+
B.line(`${direct} = icmp ne i64 ${offset}, 0`);
|
|
3193
|
+
B.line(`${likely} = call i1 @llvm.expect.i1(i1 ${direct}, i1 true)`);
|
|
3194
|
+
const fast = B.newLabel("stack.tsd");
|
|
3195
|
+
const slow = B.newLabel("stack.tlv");
|
|
3196
|
+
B.condBr(likely, fast, slow);
|
|
3197
|
+
B.startBlock(fast);
|
|
3198
|
+
const guard = B.tmp();
|
|
3199
|
+
if (this.stackGuardTsd === "aarch64") {
|
|
3200
|
+
this.declare(`declare i64 @llvm.read_register.i64(metadata)`);
|
|
3201
|
+
const thread = B.tmp();
|
|
3202
|
+
const base = B.tmp();
|
|
3203
|
+
const slot = B.tmp();
|
|
3204
|
+
const address = B.tmp();
|
|
3205
|
+
B.line(`${thread} = call i64 @llvm.read_register.i64(metadata !{!"tpidrro_el0"})`);
|
|
3206
|
+
B.line(`${base} = and i64 ${thread}, -8`);
|
|
3207
|
+
B.line(`${slot} = add i64 ${base}, ${offset}`);
|
|
3208
|
+
B.line(`${address} = inttoptr i64 ${slot} to ptr`);
|
|
3209
|
+
B.line(`${guard} = load ptr, ptr ${address}`);
|
|
3210
|
+
}
|
|
3211
|
+
else {
|
|
3212
|
+
const address = B.tmp();
|
|
3213
|
+
B.line(`${address} = inttoptr i64 ${offset} to ptr addrspace(256)`);
|
|
3214
|
+
B.line(`${guard} = load ptr, ptr addrspace(256) ${address}`);
|
|
3215
|
+
}
|
|
3216
|
+
test(guard);
|
|
3217
|
+
B.startBlock(slow);
|
|
3218
|
+
// uintptr_t scr_stack_guard_get(void): an i64 in the C ABI.
|
|
3219
|
+
const raw = B.tmp();
|
|
3220
|
+
const fallback = B.tmp();
|
|
3221
|
+
B.line(`${raw} = call i64 @scr_stack_guard_get()`);
|
|
3222
|
+
B.line(`${fallback} = inttoptr i64 ${raw} to ptr`);
|
|
3223
|
+
test(fallback);
|
|
3224
|
+
}
|
|
3225
|
+
else {
|
|
3226
|
+
const tl = this.mod.workers === true ? "thread_local " : "";
|
|
3227
|
+
this.declare(`@scr_stack_guard = external ${tl}global ptr`);
|
|
3228
|
+
const guard = B.tmp();
|
|
3229
|
+
B.line(`${guard} = load ptr, ptr @scr_stack_guard`);
|
|
3230
|
+
test(guard);
|
|
3231
|
+
}
|
|
3035
3232
|
B.startBlock(over);
|
|
3036
3233
|
B.line(`call void @scr_stack_overflow()`);
|
|
3037
3234
|
this.emitUnwind();
|
|
3038
3235
|
B.startBlock(ok);
|
|
3039
3236
|
}
|
|
3040
|
-
|
|
3237
|
+
/** Whether the emitter follows this call with a pending test (the
|
|
3238
|
+
* conditions in expr-calls.ts). */
|
|
3239
|
+
callTestsPending(e) {
|
|
3240
|
+
switch (e.kind) {
|
|
3241
|
+
case "call":
|
|
3242
|
+
return this.mayThrow.has(e.callee);
|
|
3243
|
+
case "new":
|
|
3244
|
+
return this.mayThrow.has(`%${e.className}.constructor`);
|
|
3245
|
+
case "virtualCall":
|
|
3246
|
+
return this.mayThrowMethods.has(e.method);
|
|
3247
|
+
case "callValue":
|
|
3248
|
+
return this.indirectMayThrow;
|
|
3249
|
+
default:
|
|
3250
|
+
return false;
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
workerLoopBudget(loop) {
|
|
3041
3254
|
if (!this.mod.workers)
|
|
3042
3255
|
return null;
|
|
3256
|
+
// A loop that runs a pending test on every iteration observes a
|
|
3257
|
+
// terminate request through it; a separate poll would only add work.
|
|
3258
|
+
if (loopTestsEveryIteration(loop, (e) => this.callTestsPending(e)))
|
|
3259
|
+
return null;
|
|
3043
3260
|
const slot = this.B.slot();
|
|
3044
3261
|
this.B.entryAllocas.push(`${slot} = alloca i32 ; worker cancellation budget`);
|
|
3045
3262
|
this.B.line(`store i32 0, ptr ${slot}`);
|
|
@@ -3061,7 +3278,7 @@ export class LlEmitter {
|
|
|
3061
3278
|
// Calls and throwing operations retain their own checks. Amortize the
|
|
3062
3279
|
// extra cancellation poll across bounded loop batches so scalar work
|
|
3063
3280
|
// does not cross the runtime ABI on every iteration.
|
|
3064
|
-
this.emitPendingCheck();
|
|
3281
|
+
this.emitPendingCheck(undefined, true);
|
|
3065
3282
|
B.br(next);
|
|
3066
3283
|
B.startBlock(next);
|
|
3067
3284
|
}
|
|
@@ -3665,18 +3882,38 @@ export class LlEmitter {
|
|
|
3665
3882
|
this.declare(`declare void @scr_box_set_ref(ptr, ptr)`);
|
|
3666
3883
|
B.line(`call void @scr_box_set_ref(ptr ${box}, ptr ${cell})`);
|
|
3667
3884
|
}
|
|
3885
|
+
/** The payload a TDZ box holds before its declaration runs, when that is
|
|
3886
|
+
* not NULL. A nullable union's value is its arm pointer with NULL as a
|
|
3887
|
+
* unit arm (nullable-unions.ts), so a stored `null`/`undefined` would
|
|
3888
|
+
* read back as the temporal dead zone; such boxes start at the module's
|
|
3889
|
+
* immortal NULLABLE_ABSENT sentinel instead, which no value of the
|
|
3890
|
+
* binding can hold and every RC entry point and the collector skip. */
|
|
3891
|
+
tdzEmptySentinel(t) {
|
|
3892
|
+
if (!this.nullableUnions.of(t))
|
|
3893
|
+
return null;
|
|
3894
|
+
this.needsNullableAbsent = true;
|
|
3895
|
+
this.immortalValues.add(NULLABLE_ABSENT);
|
|
3896
|
+
return NULLABLE_ABSENT;
|
|
3897
|
+
}
|
|
3668
3898
|
/** The TDZ-guarded read of a boxed binding: an empty payload slot is
|
|
3669
3899
|
* the temporal dead zone — throw Node's exact catchable ReferenceError
|
|
3670
3900
|
* (exprs.ts's varRef guard). Scalars then peek the one-element
|
|
3671
3901
|
* array cell; ref kinds read the box normally (+1). */
|
|
3672
|
-
checkTdz(box, name) {
|
|
3902
|
+
checkTdz(box, t, name) {
|
|
3673
3903
|
const B = this.B;
|
|
3674
3904
|
const slotp = B.tmp();
|
|
3675
3905
|
const slotv = B.tmp();
|
|
3676
3906
|
const empty = B.tmp();
|
|
3677
3907
|
B.line(`${slotp} = getelementptr inbounds %ScrBox, ptr ${box}, i64 0, i32 5`);
|
|
3678
|
-
|
|
3679
|
-
|
|
3908
|
+
const sentinel = this.tdzEmptySentinel(t);
|
|
3909
|
+
if (sentinel !== null) {
|
|
3910
|
+
B.line(`${slotv} = load ptr, ptr ${slotp}`);
|
|
3911
|
+
B.line(`${empty} = icmp eq ptr ${slotv}, ${sentinel}`);
|
|
3912
|
+
}
|
|
3913
|
+
else {
|
|
3914
|
+
B.line(`${slotv} = load i64, ptr ${slotp}`);
|
|
3915
|
+
B.line(`${empty} = icmp eq i64 ${slotv}, 0`);
|
|
3916
|
+
}
|
|
3680
3917
|
this.throwIfUninitialized(empty, name);
|
|
3681
3918
|
return slotv;
|
|
3682
3919
|
}
|
|
@@ -3713,7 +3950,7 @@ export class LlEmitter {
|
|
|
3713
3950
|
B.startBlock(lk);
|
|
3714
3951
|
}
|
|
3715
3952
|
tdzBoxRead(box, t, name) {
|
|
3716
|
-
const slotv = this.checkTdz(box, name);
|
|
3953
|
+
const slotv = this.checkTdz(box, t, name);
|
|
3717
3954
|
const B = this.B;
|
|
3718
3955
|
const acc = boxAccess(t);
|
|
3719
3956
|
if (acc === "ref")
|
|
@@ -3733,7 +3970,7 @@ export class LlEmitter {
|
|
|
3733
3970
|
writeBindingBox(box, local, value, initializes = false, borrowed = false) {
|
|
3734
3971
|
if (local.tdz) {
|
|
3735
3972
|
const first = initializes || !local.mutable;
|
|
3736
|
-
const slotv = first ? null : this.checkTdz(box, local.name);
|
|
3973
|
+
const slotv = first ? null : this.checkTdz(box, local.type, local.name);
|
|
3737
3974
|
const acc = boxAccess(local.type);
|
|
3738
3975
|
if (acc !== "ref") {
|
|
3739
3976
|
if (first)
|
|
@@ -3893,6 +4130,7 @@ export class LlEmitter {
|
|
|
3893
4130
|
const numericFn = withInitializerBindings(fn, initializerBindings);
|
|
3894
4131
|
this.numericLocals = new Map(numericFn.locals.map((l) => [l.id, l]));
|
|
3895
4132
|
this.borrowedParameters.clear();
|
|
4133
|
+
this.referenceWrites?.enter(fn);
|
|
3896
4134
|
this.reboundOwnerSlots.clear();
|
|
3897
4135
|
this.projectedParameters.clear();
|
|
3898
4136
|
const projectedParameterIndexes = this.callLifetimes.parameters.get(fn.name);
|
|
@@ -3912,6 +4150,8 @@ export class LlEmitter {
|
|
|
3912
4150
|
this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
|
|
3913
4151
|
this.integerLoopBindings.clear();
|
|
3914
4152
|
this.countedLoopsEnabled = this.debug === null && !fn.async && !fn.generator;
|
|
4153
|
+
this.finalFieldBorrows =
|
|
4154
|
+
!fn.async && !fn.generator && constructorClass(fn.name) === null && this.finalFields.size > 0;
|
|
3915
4155
|
this.byteWindowsEnabled = this.countedLoopsEnabled;
|
|
3916
4156
|
this.byteWindowEntries =
|
|
3917
4157
|
this.byteWindowsEnabled && numericFn.locals.some((l) => l.type.kind === "bytes")
|
|
@@ -3922,10 +4162,10 @@ export class LlEmitter {
|
|
|
3922
4162
|
this.integerArrayBindings.clear();
|
|
3923
4163
|
this.loopArrayBorrows =
|
|
3924
4164
|
this.debug === null
|
|
3925
|
-
? findLoopArrayBorrows(fn, this.callLifetimes, this.referenceEffects, this.optionalArrayReads)
|
|
4165
|
+
? findLoopArrayBorrows(fn, this.callLifetimes, this.referenceEffects, this.optionalArrayReads, this.referenceWrites)
|
|
3926
4166
|
: new Set();
|
|
3927
|
-
this.localArrayReads = findLocalArrayReads(fn, this.fnByName, this.unionsById, this.referenceEffects.functions, this.callLifetimes, this.optionalArrayReads, this.loopArrayBorrows);
|
|
3928
|
-
this.callArrayReads = findCallArrayReads(fn, this.optionalArrayReads, this.referenceEffects.functions, this.callLifetimes);
|
|
4167
|
+
this.localArrayReads = findLocalArrayReads(fn, this.fnByName, this.unionsById, this.referenceEffects.functions, this.callLifetimes, this.optionalArrayReads, this.loopArrayBorrows, this.debug === null ? this.referenceEffects : undefined);
|
|
4168
|
+
this.callArrayReads = findCallArrayReads(fn, this.optionalArrayReads, this.referenceEffects.functions, this.callLifetimes, this.debug === null ? this.referenceEffects : undefined);
|
|
3929
4169
|
this.scalarStringSlices =
|
|
3930
4170
|
this.debug === null ? findScalarStringSlices(fn, this.callLifetimes) : new Map();
|
|
3931
4171
|
this.mapReadLifetimes = findMapReadLifetimes(fn, this.boxedUnionsById, this.callLifetimes);
|
|
@@ -3938,9 +4178,11 @@ export class LlEmitter {
|
|
|
3938
4178
|
this.chainSlots.clear();
|
|
3939
4179
|
this.finallyStack = [];
|
|
3940
4180
|
this.tryStack = [];
|
|
4181
|
+
this.loopDepth = 0;
|
|
3941
4182
|
this.currentReturnType = fn.returnType;
|
|
3942
4183
|
this.currentGenerator = fn.generator ?? null;
|
|
3943
4184
|
this.currentBorrowedReturn = this.borrowedReturns.has(fn.name);
|
|
4185
|
+
this.inConstructor = this.constructorFunctions.has(fn.name);
|
|
3944
4186
|
this.currentWasiCoro = null;
|
|
3945
4187
|
this.logArgSlots = 0;
|
|
3946
4188
|
if (this.wasi && (fn.async === true || fn.generator !== undefined)) {
|
|
@@ -4100,8 +4342,8 @@ export class LlEmitter {
|
|
|
4100
4342
|
if (this.stackChecks?.has(fn.name))
|
|
4101
4343
|
this.emitStackCheck();
|
|
4102
4344
|
if (this.workerEntryPolls?.has(fn.name))
|
|
4103
|
-
this.emitPendingCheck();
|
|
4104
|
-
this.emitStmts(fn.body);
|
|
4345
|
+
this.emitPendingCheck(undefined, true);
|
|
4346
|
+
this.emitStmts(fn.body, true);
|
|
4105
4347
|
// Implicit exit of a void function: release the function scope unless
|
|
4106
4348
|
// the body already terminated its final block (return, or a throw
|
|
4107
4349
|
// whose unwind released everything down to depth 0).
|
|
@@ -4167,7 +4409,14 @@ export class LlEmitter {
|
|
|
4167
4409
|
if (fn.captures !== undefined)
|
|
4168
4410
|
params.unshift("ptr %sc_env");
|
|
4169
4411
|
const ret = this.llType(fn.returnType);
|
|
4170
|
-
|
|
4412
|
+
let attrs = coro !== null ? "#1" : FN_ATTRS;
|
|
4413
|
+
// The shared indexed-read helper (lowerSafeIndexRead) is a dense bounds
|
|
4414
|
+
// test, a state test and a load behind cold runtime fallbacks, called
|
|
4415
|
+
// from every `a[i]` of one element type. Without the hint LLVM keeps the
|
|
4416
|
+
// hot instances out of line once they have many callers: every read pays
|
|
4417
|
+
// a call, and an integer index converts to a double and back across it.
|
|
4418
|
+
if (fn.name.startsWith("%arr.idxOr."))
|
|
4419
|
+
attrs = `inlinehint ${attrs}`;
|
|
4171
4420
|
const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`;
|
|
4172
4421
|
const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
|
|
4173
4422
|
const body = `define internal ${ret} @${symbol}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
|
|
@@ -4250,14 +4499,34 @@ export class LlEmitter {
|
|
|
4250
4499
|
return this.canBorrowCallArgument(value.value);
|
|
4251
4500
|
if (value.kind === "libCall")
|
|
4252
4501
|
return value.fn === "error.nodeThrow";
|
|
4502
|
+
// A final field never changes outside constructors: while its object is
|
|
4503
|
+
// borrowable, so is the field's value. Nullable-pointer fields have no
|
|
4504
|
+
// box to lend and keep their owning path.
|
|
4505
|
+
if (value.kind === "fieldGet" &&
|
|
4506
|
+
this.finalFieldBorrows &&
|
|
4507
|
+
isRefCounted(value.type) &&
|
|
4508
|
+
this.finalFields.isFinal(value.className, value.field) &&
|
|
4509
|
+
!this.nullableFieldGet(value))
|
|
4510
|
+
return this.canBorrowReceiver(value.obj) && this.canBorrowCallArgument(value.obj);
|
|
4253
4511
|
if (value.kind === "ternary")
|
|
4254
4512
|
return (this.canBorrowReceiver(value.then) &&
|
|
4255
4513
|
this.canBorrowReceiver(value.else_) &&
|
|
4256
4514
|
this.canBorrowCallArgument(value.then) &&
|
|
4257
4515
|
this.canBorrowCallArgument(value.else_));
|
|
4516
|
+
// A construction-only field of a stable owner keeps its value until the
|
|
4517
|
+
// owner dies: no later operand or callee can replace and release it.
|
|
4518
|
+
if ((value.kind === "fieldGet" || value.kind === "recordGet") && this.immutableRead(value))
|
|
4519
|
+
return this.canBorrowCallArgument(value.obj);
|
|
4520
|
+
// An optional chain's bound receiver stays unchanged until its body
|
|
4521
|
+
// finishes (see emitReadReceiver).
|
|
4522
|
+
if (value.kind === "chainRecv")
|
|
4523
|
+
return this.chainSlots.has(value.id);
|
|
4258
4524
|
if (value.kind !== "varRef")
|
|
4259
4525
|
return false;
|
|
4260
4526
|
const binding = this.binding(value.localId);
|
|
4527
|
+
// A module constant owns its value for the rest of the program.
|
|
4528
|
+
if (binding.kind === "global")
|
|
4529
|
+
return this.stableGlobals.has(value.localId);
|
|
4261
4530
|
return (binding.kind === "local" &&
|
|
4262
4531
|
binding.local !== undefined &&
|
|
4263
4532
|
(!binding.local.mutable ||
|
|
@@ -4267,15 +4536,212 @@ export class LlEmitter {
|
|
|
4267
4536
|
!binding.local.boxed &&
|
|
4268
4537
|
!binding.local.tdz);
|
|
4269
4538
|
}
|
|
4539
|
+
/** A plain-pointer read of a construction-only field outside any
|
|
4540
|
+
* constructor (ImmutableFields). */
|
|
4541
|
+
immutableRead(e) {
|
|
4542
|
+
if (this.inConstructor || this.immutableFields === null)
|
|
4543
|
+
return false;
|
|
4544
|
+
const t = e.type.kind;
|
|
4545
|
+
if (t !== "object" && t !== "record" && t !== "array" && t !== "string")
|
|
4546
|
+
return false;
|
|
4547
|
+
return e.kind === "fieldGet"
|
|
4548
|
+
? this.immutableFields.classField(e.className, e.field)
|
|
4549
|
+
: this.immutableFields.recordField(e.shapeId, e.field);
|
|
4550
|
+
}
|
|
4551
|
+
/** A projection read whose locations nothing writes while a borrowed
|
|
4552
|
+
* read of it is in use (ReferenceWrites), given the writes of that
|
|
4553
|
+
* extent. The root binding itself must stay alive across it too, which
|
|
4554
|
+
* its own location (`l:`, `g:`, `bx`) covers. */
|
|
4555
|
+
unwrittenRead(e, writes) {
|
|
4556
|
+
const rw = this.referenceWrites;
|
|
4557
|
+
if (rw === null)
|
|
4558
|
+
return false;
|
|
4559
|
+
const keys = rw.readKeys(e, (call) => this.borrowedReturnKeys(call.callee));
|
|
4560
|
+
return keys !== null && !rw.conflicts(keys, writes);
|
|
4561
|
+
}
|
|
4562
|
+
/** A stable binding initialized by a projection can borrow it when the
|
|
4563
|
+
* rest of its block (its whole lifetime) writes none of the projection's
|
|
4564
|
+
* locations: the source edge then keeps the value alive, and uses that
|
|
4565
|
+
* escape still retain their own copies. */
|
|
4566
|
+
unwrittenAlias(s) {
|
|
4567
|
+
const cursor = this.stmtCursor;
|
|
4568
|
+
const rw = this.referenceWrites;
|
|
4569
|
+
if (rw === null || s.init === null || cursor === null || cursor.stmts[cursor.index] !== s)
|
|
4570
|
+
return false;
|
|
4571
|
+
if (!this.plainReference(s.init.type) ||
|
|
4572
|
+
!this.canBorrowReceiver(s.init) ||
|
|
4573
|
+
!this.unownedWalk(s.init))
|
|
4574
|
+
return false;
|
|
4575
|
+
const writes = rw.union([rw.expr(s.init), rw.stmts(cursor.stmts.slice(cursor.index + 1))]);
|
|
4576
|
+
return this.unwrittenRead(s.init, writes);
|
|
4577
|
+
}
|
|
4578
|
+
/** An optional element read into a projection-only local can borrow the
|
|
4579
|
+
* element when the rest of the block writes no array element and none of
|
|
4580
|
+
* the locations keeping the array alive (which must itself be read
|
|
4581
|
+
* without a frame owner). */
|
|
4582
|
+
unwrittenLocalArrayRead(s, read) {
|
|
4583
|
+
const cursor = this.stmtCursor;
|
|
4584
|
+
const rw = this.referenceWrites;
|
|
4585
|
+
if (rw === null || s.init === null || cursor === null || cursor.stmts[cursor.index] !== s)
|
|
4586
|
+
return false;
|
|
4587
|
+
if (!this.unownedWalk(read.array))
|
|
4588
|
+
return false;
|
|
4589
|
+
const keys = rw.readKeys(read.array);
|
|
4590
|
+
if (keys === null || rw.expr(read.index) !== null)
|
|
4591
|
+
return false;
|
|
4592
|
+
const writes = rw.union([rw.expr(s.init), rw.stmts(cursor.stmts.slice(cursor.index + 1))]);
|
|
4593
|
+
return !rw.conflicts(["a", ...keys], writes);
|
|
4594
|
+
}
|
|
4595
|
+
/** An optional element read passed to a projection-only parameter can
|
|
4596
|
+
* borrow the element when the callee and later arguments (`writes`)
|
|
4597
|
+
* write no array element and nothing keeping the array alive. */
|
|
4598
|
+
unwrittenCallArrayRead(read, writes) {
|
|
4599
|
+
const rw = this.referenceWrites;
|
|
4600
|
+
if (rw === null)
|
|
4601
|
+
return false;
|
|
4602
|
+
const keys = rw.readKeys(read.array);
|
|
4603
|
+
if (keys === null || rw.expr(read.index) !== null)
|
|
4604
|
+
return false;
|
|
4605
|
+
return !rw.conflicts(["a", ...keys], writes);
|
|
4606
|
+
}
|
|
4607
|
+
/** `return x` of a local whose slot is an owned scope entry of the same
|
|
4608
|
+
* type: the return can take over the local's reference. */
|
|
4609
|
+
movableReturn(value) {
|
|
4610
|
+
if (value === null || value.kind !== "varRef" || !isRefCounted(value.type))
|
|
4611
|
+
return null;
|
|
4612
|
+
if (this.currentBorrowedReturn || this.currentWasiCoro !== null)
|
|
4613
|
+
return null;
|
|
4614
|
+
const binding = this.binding(value.localId);
|
|
4615
|
+
if (binding.kind !== "local" || binding.local === undefined || binding.local.boxed)
|
|
4616
|
+
return null;
|
|
4617
|
+
if (!typeEquals(binding.type, value.type) || !typeEquals(this.currentReturnType, value.type))
|
|
4618
|
+
return null;
|
|
4619
|
+
for (const scope of this.scopes) {
|
|
4620
|
+
const index = scope.findIndex((entry) => entry.slot === binding.slot);
|
|
4621
|
+
if (index < 0)
|
|
4622
|
+
continue;
|
|
4623
|
+
const entry = scope[index];
|
|
4624
|
+
if (entry.boxed || entry.lazy || !typeEquals(entry.type, value.type))
|
|
4625
|
+
return null;
|
|
4626
|
+
return { scope, entry, index };
|
|
4627
|
+
}
|
|
4628
|
+
return null;
|
|
4629
|
+
}
|
|
4630
|
+
/** A walk host for a body that may write the heap: each walk must read
|
|
4631
|
+
* only locations the whole body (with its callees) never writes;
|
|
4632
|
+
* rebinding the walk locals themselves releases nothing. */
|
|
4633
|
+
unwrittenWalkHost(fn, host) {
|
|
4634
|
+
return {
|
|
4635
|
+
...host,
|
|
4636
|
+
unwritten: (value, candidates) => this.referenceWrites?.unwrittenWalk(fn, value, candidates, (call) => this.borrowedReturnKeys(call.callee)) ?? false,
|
|
4637
|
+
};
|
|
4638
|
+
}
|
|
4639
|
+
/** emitReadReceiver yields a plain pointer for `e` that no statement
|
|
4640
|
+
* frame owns, so it can outlive the statement (walk-borrows' walks, from
|
|
4641
|
+
* any unboxed plain-pointer binding, plus element reads). */
|
|
4642
|
+
unownedWalk(e) {
|
|
4643
|
+
switch (e.kind) {
|
|
4644
|
+
case "varRef": {
|
|
4645
|
+
const binding = this.binding(e.localId);
|
|
4646
|
+
if (binding.kind === "global")
|
|
4647
|
+
return this.plainReference(binding.type);
|
|
4648
|
+
return (binding.kind === "local" &&
|
|
4649
|
+
binding.local !== undefined &&
|
|
4650
|
+
!binding.local.boxed &&
|
|
4651
|
+
!binding.local.tdz &&
|
|
4652
|
+
this.plainReference(binding.local.type) &&
|
|
4653
|
+
!this.stringSlices.has(e.localId) &&
|
|
4654
|
+
!this.storedSplits.has(e.localId) &&
|
|
4655
|
+
!this.localUnionStorage.has(e.localId));
|
|
4656
|
+
}
|
|
4657
|
+
case "fieldGet":
|
|
4658
|
+
case "recordGet":
|
|
4659
|
+
return this.borrowsWithoutOwning(e) && this.unownedWalk(e.obj);
|
|
4660
|
+
case "unionNarrow":
|
|
4661
|
+
case "downcast":
|
|
4662
|
+
case "upcast":
|
|
4663
|
+
return this.borrowsWithoutOwning(e) && this.unownedWalk(e.value);
|
|
4664
|
+
case "unionWrap":
|
|
4665
|
+
if (!this.borrowsWithoutOwning(e))
|
|
4666
|
+
return false;
|
|
4667
|
+
return e.value.kind === "unitLit" || this.unownedWalk(e.value);
|
|
4668
|
+
case "ternary": {
|
|
4669
|
+
const arm = (v) => (v.kind === "libCall" && v.fn === "error.nodeThrow") || this.unownedWalk(v);
|
|
4670
|
+
return this.borrowsWithoutOwning(e) && arm(e.then) && arm(e.else_);
|
|
4671
|
+
}
|
|
4672
|
+
case "call":
|
|
4673
|
+
return (this.borrowsWithoutOwning(e) &&
|
|
4674
|
+
e.args.every((arg) => !isRefCounted(arg.type) || this.unownedWalk(arg)));
|
|
4675
|
+
case "arrayGet":
|
|
4676
|
+
return (!this.isStackUnionSource(e) &&
|
|
4677
|
+
this.optionalArrayReads.get(e) === null &&
|
|
4678
|
+
this.unownedWalk(e.arr));
|
|
4679
|
+
case "seqExpr": {
|
|
4680
|
+
const guarded = guardedValue(e);
|
|
4681
|
+
return guarded !== null && this.unownedWalk(guarded);
|
|
4682
|
+
}
|
|
4683
|
+
default:
|
|
4684
|
+
return false;
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
/** Every location a borrowed-return callee's walks can read (its body's
|
|
4688
|
+
* field, record and element reads, through nested borrowed returns). */
|
|
4689
|
+
borrowedReturnKeys(callee) {
|
|
4690
|
+
if (!this.borrowedReturns.has(callee))
|
|
4691
|
+
return null;
|
|
4692
|
+
const known = this.borrowedReturnKeyCache.get(callee);
|
|
4693
|
+
if (known)
|
|
4694
|
+
return known;
|
|
4695
|
+
const rw = this.referenceWrites;
|
|
4696
|
+
const keys = new Set();
|
|
4697
|
+
const seen = new Set([callee]);
|
|
4698
|
+
const pending = [callee];
|
|
4699
|
+
while (pending.length > 0) {
|
|
4700
|
+
const fn = this.fnByName.get(pending.pop());
|
|
4701
|
+
if (!fn)
|
|
4702
|
+
continue;
|
|
4703
|
+
everyStmtList(fn.body, {
|
|
4704
|
+
stmt: () => true,
|
|
4705
|
+
expr: (e) => {
|
|
4706
|
+
if (e.kind === "fieldGet")
|
|
4707
|
+
keys.add(rw.fieldKey(e.className, e.field));
|
|
4708
|
+
else if (e.kind === "recordGet") {
|
|
4709
|
+
keys.add(`r:${e.field}`);
|
|
4710
|
+
keys.add("r:*");
|
|
4711
|
+
}
|
|
4712
|
+
else if (e.kind === "arrayGet")
|
|
4713
|
+
keys.add("a");
|
|
4714
|
+
else if (e.kind === "call" && this.borrowedReturns.has(e.callee) && !seen.has(e.callee)) {
|
|
4715
|
+
seen.add(e.callee);
|
|
4716
|
+
pending.push(e.callee);
|
|
4717
|
+
}
|
|
4718
|
+
return true;
|
|
4719
|
+
},
|
|
4720
|
+
});
|
|
4721
|
+
}
|
|
4722
|
+
const result = [...keys];
|
|
4723
|
+
this.borrowedReturnKeyCache.set(callee, result);
|
|
4724
|
+
return result;
|
|
4725
|
+
}
|
|
4270
4726
|
// ── statements ──────────────────────────────────────────────────────────
|
|
4271
|
-
emitStmts(stmts) {
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
return
|
|
4278
|
-
|
|
4727
|
+
emitStmts(stmts, scoped = false) {
|
|
4728
|
+
const saved = this.stmtCursor;
|
|
4729
|
+
const cursor = scoped ? { stmts, index: 0 } : null;
|
|
4730
|
+
try {
|
|
4731
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
4732
|
+
// Statements after a terminator are unreachable (dead code after
|
|
4733
|
+
// return/break/continue). Skip them so no dropped SSA definition
|
|
4734
|
+
// can leak forward.
|
|
4735
|
+
if (this.B.isTerminated())
|
|
4736
|
+
return;
|
|
4737
|
+
if (cursor)
|
|
4738
|
+
cursor.index = i;
|
|
4739
|
+
this.stmtCursor = cursor;
|
|
4740
|
+
this.emitStmt(stmts[i]);
|
|
4741
|
+
}
|
|
4742
|
+
}
|
|
4743
|
+
finally {
|
|
4744
|
+
this.stmtCursor = saved;
|
|
4279
4745
|
}
|
|
4280
4746
|
}
|
|
4281
4747
|
/** Emits a block in its own lexical scope (refcounted locals released at
|
|
@@ -4287,7 +4753,7 @@ export class LlEmitter {
|
|
|
4287
4753
|
const scope = [];
|
|
4288
4754
|
this.scopes.push(scope);
|
|
4289
4755
|
setup?.(scope);
|
|
4290
|
-
this.emitStmts(stmts);
|
|
4756
|
+
this.emitStmts(stmts, true);
|
|
4291
4757
|
const ended = endsWithJump(stmts);
|
|
4292
4758
|
this.scopes.pop();
|
|
4293
4759
|
if (!ended)
|
|
@@ -4296,11 +4762,16 @@ export class LlEmitter {
|
|
|
4296
4762
|
emitStmt(s) {
|
|
4297
4763
|
const previous = this.B.debugLocation;
|
|
4298
4764
|
this.B.debugLocation = this.debug?.location(s.loc, this.debugScope) ?? null;
|
|
4765
|
+
const loop = s.kind === "while" || s.kind === "doWhile" || s.kind === "for" || s.kind === "forOf";
|
|
4766
|
+
if (loop)
|
|
4767
|
+
this.loopDepth++;
|
|
4299
4768
|
try {
|
|
4300
4769
|
this.emitStmtBody(s);
|
|
4301
4770
|
}
|
|
4302
4771
|
finally {
|
|
4303
4772
|
this.B.debugLocation = previous;
|
|
4773
|
+
if (loop)
|
|
4774
|
+
this.loopDepth--;
|
|
4304
4775
|
}
|
|
4305
4776
|
}
|
|
4306
4777
|
emitStmtBody(s) {
|
|
@@ -4344,7 +4815,9 @@ export class LlEmitter {
|
|
|
4344
4815
|
}
|
|
4345
4816
|
const localRead = this.localArrayReads.get(s.localId);
|
|
4346
4817
|
if (localRead) {
|
|
4347
|
-
const owner = emitLocalArrayRead(this, localRead,
|
|
4818
|
+
const owner = emitLocalArrayRead(this, !localRead.borrow && this.unwrittenLocalArrayRead(s, localRead)
|
|
4819
|
+
? { ...localRead, borrow: true }
|
|
4820
|
+
: localRead, b.slot);
|
|
4348
4821
|
if (owner)
|
|
4349
4822
|
this.scopes[this.scopes.length - 1].push(owner);
|
|
4350
4823
|
break;
|
|
@@ -4397,8 +4870,14 @@ export class LlEmitter {
|
|
|
4397
4870
|
B.line(`${box} = ${boxNew} ; let ${b.local.name} (boxed)`);
|
|
4398
4871
|
B.line(`store ptr ${box}, ptr ${b.slot}`);
|
|
4399
4872
|
this.scopes[this.scopes.length - 1].push({ slot: b.slot, type: b.type, boxed: true });
|
|
4400
|
-
if (s.init === null)
|
|
4873
|
+
if (s.init === null) {
|
|
4874
|
+
// An empty TDZ box whose payload can legitimately be NULL
|
|
4875
|
+
// starts at its distinct sentinel (tdzEmptySentinel).
|
|
4876
|
+
const sentinel = b.local.tdz === true ? this.tdzEmptySentinel(b.type) : null;
|
|
4877
|
+
if (sentinel !== null)
|
|
4878
|
+
B.line(`store ptr ${sentinel}, ptr ${this.boxSlot(box)}`);
|
|
4401
4879
|
break;
|
|
4880
|
+
}
|
|
4402
4881
|
const v = this.emitExpr(s.init);
|
|
4403
4882
|
if (isRefCounted(v.type))
|
|
4404
4883
|
this.moveTemp(v); // the box takes ownership
|
|
@@ -4432,7 +4911,7 @@ export class LlEmitter {
|
|
|
4432
4911
|
b.local &&
|
|
4433
4912
|
isRefCounted(b.type) &&
|
|
4434
4913
|
this.stableCallBindings.has(s.localId) &&
|
|
4435
|
-
this.canBorrowCallArgument(s.init)) {
|
|
4914
|
+
(this.canBorrowCallArgument(s.init) || this.unwrittenAlias(s))) {
|
|
4436
4915
|
const v = this.emitReadReceiver(s.init);
|
|
4437
4916
|
B.line(`store ptr ${v.name}, ptr ${b.slot}`);
|
|
4438
4917
|
break;
|
|
@@ -4499,6 +4978,32 @@ export class LlEmitter {
|
|
|
4499
4978
|
}
|
|
4500
4979
|
B.line(`store ${this.llType(b.type)} ${v.name}, ptr ${b.slot}`);
|
|
4501
4980
|
this.storeIntegerView(s.localId, v, s.value);
|
|
4981
|
+
if (b.kind === "global" && this.immortalGlobals.has(s.localId)) {
|
|
4982
|
+
// An Immutable module constant (contracts/check.ts proved it is
|
|
4983
|
+
// fresh, publishable and never written): its graph becomes
|
|
4984
|
+
// immortal, so reads skip reference counting and the collector
|
|
4985
|
+
// never visits it.
|
|
4986
|
+
const lines = this.publishWalkers.emitPublish(v.name, v.type, s.loc);
|
|
4987
|
+
for (const line of lines)
|
|
4988
|
+
B.line(line.trimStart());
|
|
4989
|
+
// Reads of this global skip retains from here on, which is only
|
|
4990
|
+
// sound once the graph really is immortal. Node.js never fails
|
|
4991
|
+
// here, so a refusal means the contract proof was wrong: stop
|
|
4992
|
+
// rather than keep running with a mortal graph read as immortal.
|
|
4993
|
+
if (lines.length > 0) {
|
|
4994
|
+
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
4995
|
+
this.declare(`declare void @scr_trap(ptr)`);
|
|
4996
|
+
const refused = B.tmp();
|
|
4997
|
+
B.line(`${refused} = call zeroext i1 @scr_exc_pending()`);
|
|
4998
|
+
const stop = B.newLabel("immutable.refused");
|
|
4999
|
+
const done = B.newLabel("immutable.published");
|
|
5000
|
+
B.condBr(refused, stop, done);
|
|
5001
|
+
B.startBlock(stop);
|
|
5002
|
+
B.line(`call void @scr_trap(ptr ${this.cstr("scriptc: internal error: an Immutable value could not be published\n")})`);
|
|
5003
|
+
B.terminate("unreachable");
|
|
5004
|
+
B.startBlock(done);
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
4502
5007
|
break;
|
|
4503
5008
|
}
|
|
4504
5009
|
case "exprStmt":
|
|
@@ -4545,7 +5050,7 @@ export class LlEmitter {
|
|
|
4545
5050
|
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
4546
5051
|
this.markMemoryPointer(lenPtr, "array:header");
|
|
4547
5052
|
B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
4548
|
-
B.line(`${lenNumber} =
|
|
5053
|
+
B.line(`${lenNumber} = sitofp ${this.sizeType} ${len} to double`);
|
|
4549
5054
|
B.line(`${same} = fcmp oeq double ${length.name}, ${lenNumber}`);
|
|
4550
5055
|
B.condBr(same, done, resize);
|
|
4551
5056
|
B.startBlock(resize);
|
|
@@ -4596,14 +5101,14 @@ export class LlEmitter {
|
|
|
4596
5101
|
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
4597
5102
|
// (The guard precedes the value here: the nullable store
|
|
4598
5103
|
// evaluates it itself.)
|
|
4599
|
-
this.
|
|
5104
|
+
this.emitStoreGuard(obj.name, s);
|
|
4600
5105
|
this.emitNullableFieldStore(obj.name, s.className, s.field, nullable, s.value);
|
|
4601
5106
|
break;
|
|
4602
5107
|
}
|
|
4603
5108
|
if (s.kind === "fieldSet" && this.int32Slots.isField(s.className, s.field)) {
|
|
4604
5109
|
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
4605
5110
|
const v = this.emitExpr(s.value);
|
|
4606
|
-
this.
|
|
5111
|
+
this.emitStoreGuard(obj.name, s);
|
|
4607
5112
|
const { ptr } = this.classFieldPtr(obj.name, s.className, s.field);
|
|
4608
5113
|
this.storeInt32Field(ptr, v, s.value);
|
|
4609
5114
|
break;
|
|
@@ -4611,7 +5116,7 @@ export class LlEmitter {
|
|
|
4611
5116
|
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
4612
5117
|
const v = this.emitExpr(s.value);
|
|
4613
5118
|
if (s.kind === "fieldSet")
|
|
4614
|
-
this.
|
|
5119
|
+
this.emitStoreGuard(obj.name, s);
|
|
4615
5120
|
else if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
|
|
4616
5121
|
this.emitPublishedFieldGuard(obj.name, null, s.field);
|
|
4617
5122
|
const { ptr, type } = s.kind === "fieldSet"
|
|
@@ -4833,7 +5338,7 @@ export class LlEmitter {
|
|
|
4833
5338
|
break;
|
|
4834
5339
|
}
|
|
4835
5340
|
case "while": {
|
|
4836
|
-
const workerBudget = this.workerLoopBudget();
|
|
5341
|
+
const workerBudget = this.workerLoopBudget(s);
|
|
4837
5342
|
const lc = B.newLabel("loop.c");
|
|
4838
5343
|
const lb = B.newLabel("loop.b");
|
|
4839
5344
|
const le = B.newLabel("loop.e");
|
|
@@ -4858,7 +5363,7 @@ export class LlEmitter {
|
|
|
4858
5363
|
break;
|
|
4859
5364
|
}
|
|
4860
5365
|
case "doWhile": {
|
|
4861
|
-
const workerBudget = this.workerLoopBudget();
|
|
5366
|
+
const workerBudget = this.workerLoopBudget(s);
|
|
4862
5367
|
// Body first (runs at least once); continue jumps to the CONDITION.
|
|
4863
5368
|
const lb = B.newLabel("loop.b");
|
|
4864
5369
|
const lc = B.newLabel("loop.c");
|
|
@@ -4923,12 +5428,39 @@ export class LlEmitter {
|
|
|
4923
5428
|
}
|
|
4924
5429
|
let countedJoin = null;
|
|
4925
5430
|
if (countedLoop?.guarded) {
|
|
4926
|
-
|
|
4927
|
-
|
|
5431
|
+
let safe = "true";
|
|
5432
|
+
if (countedLoop.limitGuarded) {
|
|
5433
|
+
const bound = this.emitExpr(countedLoop.limit);
|
|
5434
|
+
safe = B.tmp();
|
|
5435
|
+
B.line(`${safe} = fcmp ${countedLoop.step > 0 ? "ole" : "oge"} double ${bound.name}, ${f64Lit(countedLoop.guardLimit)}`);
|
|
5436
|
+
}
|
|
5437
|
+
if (countedLoop.startGuarded) {
|
|
5438
|
+
// An exact integer of at most 2^53 - 1 in magnitude, and not -0
|
|
5439
|
+
// (the bit patterns of the value and its round trip agree).
|
|
5440
|
+
// NaN and infinities fail the ordered range tests.
|
|
5441
|
+
const start = this.emitExpr(countedLoop.start);
|
|
5442
|
+
const low = B.tmp(), high = B.tmp(), raw = B.tmp(), wide = B.tmp(), back = B.tmp(), bits = B.tmp(), backBits = B.tmp(), exact = B.tmp(), inRange = B.tmp(), ok = B.tmp();
|
|
5443
|
+
B.line(`${low} = fcmp oge double ${start.name}, ${f64Lit(-Number.MAX_SAFE_INTEGER)}`);
|
|
5444
|
+
B.line(`${high} = fcmp ole double ${start.name}, ${f64Lit(Number.MAX_SAFE_INTEGER)}`);
|
|
5445
|
+
B.line(`${inRange} = and i1 ${low}, ${high}`);
|
|
5446
|
+
B.line(`${raw} = fptosi double ${start.name} to i64`);
|
|
5447
|
+
B.line(`${wide} = freeze i64 ${raw}`);
|
|
5448
|
+
B.line(`${back} = sitofp i64 ${wide} to double`);
|
|
5449
|
+
B.line(`${bits} = bitcast double ${start.name} to i64`);
|
|
5450
|
+
B.line(`${backBits} = bitcast double ${back} to i64`);
|
|
5451
|
+
B.line(`${exact} = icmp eq i64 ${bits}, ${backBits}`);
|
|
5452
|
+
B.line(`${ok} = and i1 ${inRange}, ${exact}`);
|
|
5453
|
+
if (safe === "true")
|
|
5454
|
+
safe = ok;
|
|
5455
|
+
else {
|
|
5456
|
+
const both = B.tmp();
|
|
5457
|
+
B.line(`${both} = and i1 ${safe}, ${ok}`);
|
|
5458
|
+
safe = both;
|
|
5459
|
+
}
|
|
5460
|
+
}
|
|
4928
5461
|
const fast = B.newLabel("counted.fast");
|
|
4929
5462
|
const slow = B.newLabel("counted.slow");
|
|
4930
5463
|
countedJoin = B.newLabel("counted.done");
|
|
4931
|
-
B.line(`${safe} = fcmp ${countedLoop.step > 0 ? "ole" : "oge"} double ${bound.name}, ${f64Lit(countedLoop.guardLimit)}`);
|
|
4932
5464
|
B.condBr(safe, fast, slow);
|
|
4933
5465
|
B.startBlock(slow);
|
|
4934
5466
|
this.countedLoopsEnabled = false;
|
|
@@ -4947,7 +5479,11 @@ export class LlEmitter {
|
|
|
4947
5479
|
if (integerArrayLoop && s.init?.kind === "varDecl" && s.init.init) {
|
|
4948
5480
|
const value = this.emitExpr(s.init.init);
|
|
4949
5481
|
start = B.tmp();
|
|
4950
|
-
|
|
5482
|
+
// The start is 0 or an enclosing array index plus one (at most
|
|
5483
|
+
// 2^32 - 1): on 64-bit targets the signed conversion is exact
|
|
5484
|
+
// and avoids x86-64's unsigned 64-bit conversion sequence.
|
|
5485
|
+
const conversion = this.sizeType === "i64" ? "fptosi" : "fptoui";
|
|
5486
|
+
B.line(`${start} = ${conversion} double ${value.name} to ${this.sizeType}`);
|
|
4951
5487
|
this.integerArrayBindings.add(integerLoop.localId);
|
|
4952
5488
|
}
|
|
4953
5489
|
B.line(`store ${this.sizeType} ${start}, ptr ${integerSlot}`);
|
|
@@ -5009,7 +5545,7 @@ export class LlEmitter {
|
|
|
5009
5545
|
};
|
|
5010
5546
|
const lu = s.update || capturedLets.length > 0 ? B.newLabel("loop.u") : lc;
|
|
5011
5547
|
freshenBindings();
|
|
5012
|
-
const workerBudget = this.workerLoopBudget();
|
|
5548
|
+
const workerBudget = this.workerLoopBudget(s);
|
|
5013
5549
|
B.br(lc);
|
|
5014
5550
|
B.startBlock(lc);
|
|
5015
5551
|
this.emitWorkerLoopCheck(workerBudget);
|
|
@@ -5117,7 +5653,7 @@ export class LlEmitter {
|
|
|
5117
5653
|
const lb = B.newLabel("fof.b");
|
|
5118
5654
|
const lu = B.newLabel("fof.u");
|
|
5119
5655
|
const le = B.newLabel("fof.e");
|
|
5120
|
-
const workerBudget = this.workerLoopBudget();
|
|
5656
|
+
const workerBudget = this.workerLoopBudget(s);
|
|
5121
5657
|
B.br(lc);
|
|
5122
5658
|
B.startBlock(lc);
|
|
5123
5659
|
this.emitWorkerLoopCheck(workerBudget);
|
|
@@ -5136,7 +5672,7 @@ export class LlEmitter {
|
|
|
5136
5672
|
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
5137
5673
|
this.markMemoryPointer(lenPtr, "array:header");
|
|
5138
5674
|
B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
5139
|
-
B.line(`${len} =
|
|
5675
|
+
B.line(`${len} = sitofp ${this.sizeType} ${rawLen} to double`);
|
|
5140
5676
|
B.line(`${inBounds} = fcmp olt double ${i}, ${len}`);
|
|
5141
5677
|
cur = i;
|
|
5142
5678
|
}
|
|
@@ -5253,10 +5789,32 @@ export class LlEmitter {
|
|
|
5253
5789
|
// the actual ret. The parked value owns a synthetic slot-backed
|
|
5254
5790
|
// scope entry during each copy so a throwing finally releases it.
|
|
5255
5791
|
let v = null;
|
|
5256
|
-
|
|
5792
|
+
const moved = this.finallyStack.length === 0 ? this.movableReturn(s.value) : null;
|
|
5793
|
+
if (moved !== null) {
|
|
5794
|
+
// Returning an owned local hands its reference to the caller: no
|
|
5795
|
+
// retain now and no release of the local below.
|
|
5796
|
+
const t = B.tmp();
|
|
5797
|
+
B.line(`${t} = load ptr, ptr ${moved.entry.slot}`);
|
|
5798
|
+
v = { name: t, type: s.value.type };
|
|
5799
|
+
moved.scope.splice(moved.scope.indexOf(moved.entry), 1);
|
|
5800
|
+
this.releaseForJump(0, 0);
|
|
5801
|
+
moved.scope.splice(moved.index, 0, moved.entry);
|
|
5802
|
+
}
|
|
5803
|
+
else if (s.value !== null && this.currentBorrowedReturn) {
|
|
5257
5804
|
// A borrowed-return body hands back its walk without a reference
|
|
5258
|
-
// (it has no try statements, so no finally can intervene).
|
|
5259
|
-
|
|
5805
|
+
// (it has no try statements, so no finally can intervene). An
|
|
5806
|
+
// optional element read lends the element its array keeps alive.
|
|
5807
|
+
const read = this.optionalArrayReads.get(s.value);
|
|
5808
|
+
if (read) {
|
|
5809
|
+
const slot = B.slot();
|
|
5810
|
+
B.entryAllocas.push(`${slot} = alloca ptr`);
|
|
5811
|
+
emitLocalArrayRead(this, { ...read, borrow: true }, slot);
|
|
5812
|
+
const t = B.tmp();
|
|
5813
|
+
B.line(`${t} = load ptr, ptr ${slot}`);
|
|
5814
|
+
v = { name: t, type: s.value.type };
|
|
5815
|
+
}
|
|
5816
|
+
else
|
|
5817
|
+
v = this.emitReadReceiver(s.value);
|
|
5260
5818
|
}
|
|
5261
5819
|
else if (s.value !== null) {
|
|
5262
5820
|
v = this.emitExpr(s.value);
|
|
@@ -5293,7 +5851,7 @@ export class LlEmitter {
|
|
|
5293
5851
|
this.finallyStack = savedFinally;
|
|
5294
5852
|
this.tryStack = savedTry;
|
|
5295
5853
|
}
|
|
5296
|
-
else {
|
|
5854
|
+
else if (moved === null) {
|
|
5297
5855
|
this.releaseForJump(0, 0);
|
|
5298
5856
|
}
|
|
5299
5857
|
if (!B.isTerminated()) {
|
|
@@ -5551,7 +6109,7 @@ export class LlEmitter {
|
|
|
5551
6109
|
const caseLabels = s.cases.map(() => B.newLabel("sw.c"));
|
|
5552
6110
|
const defaultIdx = s.cases.findIndex((c) => c.test === null);
|
|
5553
6111
|
const fallback = defaultIdx >= 0 ? caseLabels[defaultIdx] : end;
|
|
5554
|
-
if (!emitLiteralSwitch(this, disc, s.cases, caseLabels, fallback)) {
|
|
6112
|
+
if (!emitLiteralSwitch(this, disc, s.disc, s.cases, caseLabels, fallback)) {
|
|
5555
6113
|
s.cases.forEach((c, i) => {
|
|
5556
6114
|
if (c.test === null)
|
|
5557
6115
|
return;
|
|
@@ -5887,6 +6445,13 @@ export class LlEmitter {
|
|
|
5887
6445
|
``,
|
|
5888
6446
|
];
|
|
5889
6447
|
}
|
|
6448
|
+
/** The frozen-object guard of a class field store, unless the store
|
|
6449
|
+
* provably targets a still-private `this` (PublishedTypes.privateStores). */
|
|
6450
|
+
emitStoreGuard(obj, s) {
|
|
6451
|
+
if (this.publishedTypes?.privateStores.has(s) === true)
|
|
6452
|
+
return;
|
|
6453
|
+
this.emitPublishedFieldGuard(obj, s.className, s.field);
|
|
6454
|
+
}
|
|
5890
6455
|
/** The frozen-object guard of a class field or record field store:
|
|
5891
6456
|
* Node's "Cannot assign to read only property" TypeError when the target
|
|
5892
6457
|
* is published. `className` null means a record (`#<Object>`). */
|
|
@@ -5994,6 +6559,16 @@ export class LlEmitter {
|
|
|
5994
6559
|
this.B.line(`${value} = load ptr, ptr ${binding.slot}`);
|
|
5995
6560
|
return { name: value, type: e.type };
|
|
5996
6561
|
}
|
|
6562
|
+
if (e.kind === "chainRecv") {
|
|
6563
|
+
// The chain's bind slot holds its receiver unchanged until the body
|
|
6564
|
+
// finishes, owned by the slot itself or by the chain's receiver.
|
|
6565
|
+
const bound = this.chainSlots.get(e.id);
|
|
6566
|
+
if (!bound)
|
|
6567
|
+
throw new InternalCompilerError(`llvm emitter bug: chainRecv "${e.id}" outside its chain`);
|
|
6568
|
+
const value = this.B.tmp();
|
|
6569
|
+
this.B.line(`${value} = load ${this.llType(bound.type)}, ptr ${bound.name}`);
|
|
6570
|
+
return { name: value, type: e.type };
|
|
6571
|
+
}
|
|
5997
6572
|
if (e.kind === "arrayGet" && this.canBorrowReceiver(e)) {
|
|
5998
6573
|
// The array keeps the element alive until the consumer; a missing
|
|
5999
6574
|
// element still traps exactly like the owned read.
|
|
@@ -6016,6 +6591,10 @@ export class LlEmitter {
|
|
|
6016
6591
|
this.ownSlot(result.owner.slot, result.owner.type);
|
|
6017
6592
|
return result.value;
|
|
6018
6593
|
}
|
|
6594
|
+
// A borrowed-return helper (such as an optional element read) lends its
|
|
6595
|
+
// result like any borrowed-return call.
|
|
6596
|
+
if (e.kind === "call" && this.borrowedReturns.has(e.callee) && this.canBorrowReceiver(e))
|
|
6597
|
+
return emitBorrowedResultCall(this, e);
|
|
6019
6598
|
const arrayRead = this.optionalArrayReads.get(e);
|
|
6020
6599
|
if (arrayRead)
|
|
6021
6600
|
return emitProjectedArrayRead(this, arrayRead);
|
|
@@ -6229,6 +6808,8 @@ export class LlEmitter {
|
|
|
6229
6808
|
}
|
|
6230
6809
|
canBorrowReceiver(e) {
|
|
6231
6810
|
switch (e.kind) {
|
|
6811
|
+
case "chainRecv":
|
|
6812
|
+
return this.chainSlots.has(e.id);
|
|
6232
6813
|
case "varRef": {
|
|
6233
6814
|
const binding = this.binding(e.localId);
|
|
6234
6815
|
return (binding.kind !== "boxed" ||
|
|
@@ -6259,12 +6840,24 @@ export class LlEmitter {
|
|
|
6259
6840
|
const wrap = this.borrowableNullableWrap(e);
|
|
6260
6841
|
return wrap === "unit" || (wrap === "ref" && this.canBorrowReceiver(e.value));
|
|
6261
6842
|
}
|
|
6262
|
-
// A borrowed-return
|
|
6263
|
-
//
|
|
6264
|
-
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6843
|
+
// A borrowed-return result stays reachable from its borrowed
|
|
6844
|
+
// arguments until the consumer runs. Each argument is lent without a
|
|
6845
|
+
// snapshot, so neither the callee nor a later argument may replace
|
|
6846
|
+
// what it was read from: a callee that preserves every edge cannot,
|
|
6847
|
+
// and any other one must leave the argument's locations unwritten.
|
|
6848
|
+
case "call": {
|
|
6849
|
+
if (!this.borrowedReturns.has(e.callee))
|
|
6850
|
+
return false;
|
|
6851
|
+
if (!e.args.every((arg) => this.referenceEffects.preserves(arg)))
|
|
6852
|
+
return false;
|
|
6853
|
+
const preserving = this.referenceEffects.functions.has(e.callee);
|
|
6854
|
+
if (preserving)
|
|
6855
|
+
return e.args.every((arg) => !isRefCounted(arg.type) || this.canBorrowReceiver(arg));
|
|
6856
|
+
if (this.referenceWrites === null)
|
|
6857
|
+
return false;
|
|
6858
|
+
const lent = borrowableInputs(this, e.args, false, this.referenceWrites.summary(e.callee));
|
|
6859
|
+
return e.args.every((arg, i) => !isRefCounted(arg.type) || lent[i] === true);
|
|
6860
|
+
}
|
|
6268
6861
|
// A required element read: the (borrowed) array owns the element
|
|
6269
6862
|
// while an edge-preserving index computes.
|
|
6270
6863
|
case "arrayGet":
|
|
@@ -6310,14 +6903,17 @@ export class LlEmitter {
|
|
|
6310
6903
|
};
|
|
6311
6904
|
const hostFor = (fn) => {
|
|
6312
6905
|
const allowed = borrowedIds(fn);
|
|
6313
|
-
|
|
6906
|
+
const host = {
|
|
6314
6907
|
pointerLocal: (local) => pointer(local.type),
|
|
6315
6908
|
borrowsWithoutOwning: (e) => this.borrowsWithoutOwning(e),
|
|
6316
6909
|
borrowedReturn: (callee) => this.borrowedReturns.has(callee),
|
|
6317
6910
|
parameterAllowed: (id) => allowed.has(id),
|
|
6318
6911
|
};
|
|
6912
|
+
return this.referenceEffects.functions.has(fn.name) ? host : this.unwrittenWalkHost(fn, host);
|
|
6319
6913
|
};
|
|
6320
|
-
|
|
6914
|
+
// With ReferenceWrites, a body that writes none of its walks' locations
|
|
6915
|
+
// qualifies like one that preserves every edge.
|
|
6916
|
+
const preserving = [...this.fnByName.values()].filter((fn) => this.referenceEffects.functions.has(fn.name) || this.referenceWrites !== null);
|
|
6321
6917
|
const candidates = preserving.filter((fn) => {
|
|
6322
6918
|
if (!pointer(fn.returnType) || fn.async || fn.generator || fn.captures)
|
|
6323
6919
|
return false;
|
|
@@ -6338,6 +6934,8 @@ export class LlEmitter {
|
|
|
6338
6934
|
if (!analyzeWalks(fn, hostFor(fn)).returnsWalk)
|
|
6339
6935
|
continue;
|
|
6340
6936
|
this.borrowedReturns.add(fn.name);
|
|
6937
|
+
// Nested borrowed returns extend the locations a caller depends on.
|
|
6938
|
+
this.borrowedReturnKeyCache.clear();
|
|
6341
6939
|
changed = true;
|
|
6342
6940
|
}
|
|
6343
6941
|
}
|
|
@@ -6365,6 +6963,12 @@ export class LlEmitter {
|
|
|
6365
6963
|
return this.nullableFieldGet(e) === null;
|
|
6366
6964
|
case "recordGet":
|
|
6367
6965
|
return true;
|
|
6966
|
+
// emitReadReceiver lends a required element of a borrowed array when
|
|
6967
|
+
// computing the index removes no edge.
|
|
6968
|
+
case "arrayGet":
|
|
6969
|
+
return (e.arr.type.kind === "array" &&
|
|
6970
|
+
this.optionalArrayReads.get(e) === null &&
|
|
6971
|
+
this.referenceEffects.preserves(e.index));
|
|
6368
6972
|
case "unionNarrow":
|
|
6369
6973
|
return this.nullableUnions.has(e.unionId);
|
|
6370
6974
|
case "downcast":
|