@scriptc/compiler 0.3.4 → 0.3.6
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 +409 -42
- package/dist/backend/llvm/array-search.js +1 -1
- package/dist/backend/llvm/array-search.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.d.ts +6 -0
- package/dist/backend/llvm/call-lifetimes.js +19 -0
- package/dist/backend/llvm/call-lifetimes.js.map +1 -1
- package/dist/backend/llvm/constant-callbacks.d.ts +2 -21
- package/dist/backend/llvm/constant-callbacks.js +36 -219
- package/dist/backend/llvm/constant-callbacks.js.map +1 -1
- package/dist/backend/llvm/constant-callbacks.test.js +22 -0
- package/dist/backend/llvm/constant-callbacks.test.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.d.ts +9 -0
- package/dist/backend/llvm/dense-array-access.js +27 -1
- package/dist/backend/llvm/dense-array-access.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +6 -0
- package/dist/backend/llvm/dyn.js +71 -7
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +120 -2
- package/dist/backend/llvm/emitter.js +820 -142
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-async.js +17 -7
- package/dist/backend/llvm/expr-async.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.js +1 -1
- package/dist/backend/llvm/expr-callbacks.d.ts +11 -0
- package/dist/backend/llvm/expr-callbacks.js +53 -0
- package/dist/backend/llvm/expr-callbacks.js.map +1 -1
- package/dist/backend/llvm/expr-calls.js +88 -2
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js +50 -27
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-dispatch.js +1 -0
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +25 -1
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.d.ts +1 -1
- package/dist/backend/llvm/expr-primitives.js +20 -1
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +4 -0
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/integer-clones.d.ts +103 -0
- package/dist/backend/llvm/integer-clones.js +1336 -0
- package/dist/backend/llvm/integer-clones.js.map +1 -0
- package/dist/backend/llvm/integer-clones.test.d.ts +1 -0
- package/dist/backend/llvm/integer-clones.test.js +219 -0
- package/dist/backend/llvm/integer-clones.test.js.map +1 -0
- package/dist/backend/llvm/lib-datagram.js +14 -0
- package/dist/backend/llvm/lib-datagram.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +32 -1
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +61 -1
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-network.js +21 -3
- package/dist/backend/llvm/lib-network.js.map +1 -1
- package/dist/backend/llvm/lib-process.js +34 -0
- package/dist/backend/llvm/lib-process.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +77 -2
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/lib-test.js +16 -3
- package/dist/backend/llvm/lib-test.js.map +1 -1
- package/dist/backend/llvm/lib-tls.js +6 -0
- package/dist/backend/llvm/lib-tls.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.js +1 -1
- package/dist/backend/llvm/local-array-reads.js.map +1 -1
- package/dist/backend/llvm/map-entry-fusion.d.ts +28 -0
- package/dist/backend/llvm/map-entry-fusion.js +233 -0
- package/dist/backend/llvm/map-entry-fusion.js.map +1 -0
- package/dist/backend/llvm/map-entry-fusion.test.d.ts +1 -0
- package/dist/backend/llvm/map-entry-fusion.test.js +183 -0
- package/dist/backend/llvm/map-entry-fusion.test.js.map +1 -0
- package/dist/backend/llvm/map-number-lookup.d.ts +2 -0
- package/dist/backend/llvm/map-number-lookup.js +1 -1
- package/dist/backend/llvm/map-number-lookup.js.map +1 -1
- package/dist/backend/llvm/map-number-lookup.test.js +10 -11
- package/dist/backend/llvm/map-number-lookup.test.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.d.ts +2 -0
- package/dist/backend/llvm/map-read-lifetimes.js +3 -0
- package/dist/backend/llvm/map-read-lifetimes.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.test.js +2 -2
- package/dist/backend/llvm/nullable-unions.d.ts +6 -3
- package/dist/backend/llvm/nullable-unions.js +2 -0
- package/dist/backend/llvm/nullable-unions.js.map +1 -1
- package/dist/backend/llvm/publish.d.ts +16 -0
- package/dist/backend/llvm/publish.js +81 -10
- package/dist/backend/llvm/publish.js.map +1 -1
- package/dist/backend/llvm/publish.test.d.ts +1 -0
- package/dist/backend/llvm/publish.test.js +94 -0
- package/dist/backend/llvm/publish.test.js.map +1 -0
- package/dist/backend/llvm/record-results.d.ts +1 -1
- package/dist/backend/llvm/record-results.js +78 -4
- package/dist/backend/llvm/record-results.js.map +1 -1
- package/dist/backend/llvm/record-sources.d.ts +7 -0
- package/dist/backend/llvm/record-sources.js +14 -0
- package/dist/backend/llvm/record-sources.js.map +1 -0
- package/dist/backend/llvm/reference-writes.js +1 -0
- package/dist/backend/llvm/reference-writes.js.map +1 -1
- package/dist/backend/llvm/shapes.d.ts +12 -3
- package/dist/backend/llvm/shapes.js +23 -14
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/shapes.test.js +5 -3
- package/dist/backend/llvm/shapes.test.js.map +1 -1
- package/dist/backend/llvm/speculative-integers.d.ts +74 -0
- package/dist/backend/llvm/speculative-integers.js +571 -0
- package/dist/backend/llvm/speculative-integers.js.map +1 -0
- package/dist/backend/llvm/speculative-integers.test.d.ts +1 -0
- package/dist/backend/llvm/speculative-integers.test.js +148 -0
- package/dist/backend/llvm/speculative-integers.test.js.map +1 -0
- package/dist/backend/llvm/string-lifetimes.test.js +75 -1
- package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/walkers.d.ts +17 -0
- package/dist/backend/llvm/walkers.js +123 -0
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/backend/may-throw.js +2 -0
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/native/contracts.d.ts +2 -1
- package/dist/backend/native/executable.js +6 -1
- package/dist/backend/native/executable.js.map +1 -1
- package/dist/backend/native/runtime-inputs.d.ts +1 -1
- package/dist/backend/native/runtime-inputs.js +1 -0
- package/dist/backend/native/runtime-inputs.js.map +1 -1
- package/dist/backend/runtime-abi.d.ts +2 -2
- package/dist/backend/runtime-abi.js +2 -2
- package/dist/backend/runtime-pack-targets.test.js +1 -1
- package/dist/backend/runtime-pack.d.ts +7 -3
- package/dist/backend/runtime-pack.js +12 -6
- package/dist/backend/runtime-pack.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +16 -7
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/contracts/check.js +1 -0
- package/dist/contracts/check.js.map +1 -1
- package/dist/coverage/surface-manifest.js +3 -3
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +1 -1
- package/dist/diagnostics/diagnostic.js +6 -2
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lowering/builtin-calls.js +13 -0
- package/dist/frontend/lowering/builtin-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/callable-aliases.js +82 -8
- package/dist/frontend/lowering/builtins/callable-aliases.js.map +1 -1
- package/dist/frontend/lowering/builtins/child-process-async.d.ts +8 -6
- package/dist/frontend/lowering/builtins/child-process-async.js +45 -29
- package/dist/frontend/lowering/builtins/child-process-async.js.map +1 -1
- package/dist/frontend/lowering/builtins/compression.d.ts +9 -0
- package/dist/frontend/lowering/builtins/compression.js +145 -30
- package/dist/frontend/lowering/builtins/compression.js.map +1 -1
- package/dist/frontend/lowering/builtins/crypto.js +12 -1
- package/dist/frontend/lowering/builtins/crypto.js.map +1 -1
- package/dist/frontend/lowering/builtins/errors.js +5 -3
- package/dist/frontend/lowering/builtins/errors.js.map +1 -1
- package/dist/frontend/lowering/builtins/filesystem-calls.d.ts +6 -0
- package/dist/frontend/lowering/builtins/filesystem-calls.js +16 -0
- package/dist/frontend/lowering/builtins/filesystem-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/filesystem.d.ts +8 -0
- package/dist/frontend/lowering/builtins/filesystem.js +199 -2
- package/dist/frontend/lowering/builtins/filesystem.js.map +1 -1
- package/dist/frontend/lowering/builtins/fs-streams.d.ts +26 -0
- package/dist/frontend/lowering/builtins/fs-streams.js +131 -0
- package/dist/frontend/lowering/builtins/fs-streams.js.map +1 -0
- package/dist/frontend/lowering/builtins/hash-stream.d.ts +12 -0
- package/dist/frontend/lowering/builtins/hash-stream.js +80 -0
- package/dist/frontend/lowering/builtins/hash-stream.js.map +1 -0
- package/dist/frontend/lowering/builtins/module-calls.js +17 -3
- package/dist/frontend/lowering/builtins/module-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/process.js +9 -2
- package/dist/frontend/lowering/builtins/process.js.map +1 -1
- package/dist/frontend/lowering/builtins/readline.d.ts +45 -13
- package/dist/frontend/lowering/builtins/readline.js +812 -47
- package/dist/frontend/lowering/builtins/readline.js.map +1 -1
- package/dist/frontend/lowering/builtins/workers.d.ts +4 -0
- package/dist/frontend/lowering/builtins/workers.js +58 -0
- package/dist/frontend/lowering/builtins/workers.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.js +150 -8
- package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
- package/dist/frontend/lowering/class-protocols.d.ts +55 -0
- package/dist/frontend/lowering/class-protocols.js +735 -0
- package/dist/frontend/lowering/class-protocols.js.map +1 -1
- package/dist/frontend/lowering/containers/array-from-async.d.ts +18 -0
- package/dist/frontend/lowering/containers/array-from-async.js +282 -0
- package/dist/frontend/lowering/containers/array-from-async.js.map +1 -0
- package/dist/frontend/lowering/containers/bytes.js +33 -12
- package/dist/frontend/lowering/containers/bytes.js.map +1 -1
- package/dist/frontend/lowering/containers/collection-grouping.js +2 -1
- package/dist/frontend/lowering/containers/collection-grouping.js.map +1 -1
- package/dist/frontend/lowering/containers/collection-methods.js +27 -23
- package/dist/frontend/lowering/containers/collection-methods.js.map +1 -1
- package/dist/frontend/lowering/devirtualize.js +20 -7
- package/dist/frontend/lowering/devirtualize.js.map +1 -1
- package/dist/frontend/lowering/divergences.js +188 -15
- package/dist/frontend/lowering/divergences.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +129 -25
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +46 -1
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-dgram.js +55 -15
- package/dist/frontend/lowering/lower-dgram.js.map +1 -1
- package/dist/frontend/lowering/lower-event-emitter.d.ts +9 -1
- package/dist/frontend/lowering/lower-event-emitter.js +88 -1
- package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +4 -0
- package/dist/frontend/lowering/lower-exprs.js +239 -71
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +49 -3
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-mock.d.ts +11 -1
- package/dist/frontend/lowering/lower-mock.js +377 -61
- package/dist/frontend/lowering/lower-mock.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +1 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-server.d.ts +2 -1
- package/dist/frontend/lowering/lower-server.js +234 -21
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +60 -13
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.d.ts +1 -3
- package/dist/frontend/lowering/lower-stream.js +325 -20
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lower-test.js +147 -48
- package/dist/frontend/lowering/lower-test.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +13 -1
- package/dist/frontend/lowering/lowerer.js +10 -2
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/null-prototype.d.ts +13 -0
- package/dist/frontend/lowering/null-prototype.js +33 -0
- package/dist/frontend/lowering/null-prototype.js.map +1 -0
- package/dist/frontend/lowering/record-sources.d.ts +15 -0
- package/dist/frontend/lowering/record-sources.js +46 -0
- package/dist/frontend/lowering/record-sources.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +3 -3
- package/dist/frontend/lowering/surfaces.js +59 -5
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/mock-shapes.d.ts +5 -0
- package/dist/frontend/mock-shapes.js +27 -2
- package/dist/frontend/mock-shapes.js.map +1 -1
- package/dist/frontend/program.js +5 -4
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/type-mapper.d.ts +3 -0
- package/dist/frontend/type-mapper.js +148 -4
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.js +18 -8
- package/dist/index.js.map +1 -1
- package/dist/ir/analysis.d.ts +10 -0
- package/dist/ir/analysis.js +35 -0
- package/dist/ir/analysis.js.map +1 -1
- package/dist/ir/analysis.test.js +69 -1
- package/dist/ir/analysis.test.js.map +1 -1
- package/dist/ir/builtin-effects.js +72 -0
- package/dist/ir/builtin-effects.js.map +1 -1
- package/dist/ir/builtin-signatures.d.ts +1 -1
- package/dist/ir/builtin-signatures.js +258 -13
- package/dist/ir/builtin-signatures.js.map +1 -1
- package/dist/ir/capture-values.d.ts +107 -0
- package/dist/ir/capture-values.js +850 -0
- package/dist/ir/capture-values.js.map +1 -0
- package/dist/ir/capture-values.test.d.ts +1 -0
- package/dist/ir/capture-values.test.js +268 -0
- package/dist/ir/capture-values.test.js.map +1 -0
- package/dist/ir/field-values.d.ts +16 -0
- package/dist/ir/field-values.js +128 -0
- package/dist/ir/field-values.js.map +1 -0
- package/dist/ir/field-values.test.d.ts +1 -0
- package/dist/ir/field-values.test.js +160 -0
- package/dist/ir/field-values.test.js.map +1 -0
- package/dist/ir/final-fields.d.ts +4 -1
- package/dist/ir/final-fields.js +11 -3
- package/dist/ir/final-fields.js.map +1 -1
- package/dist/ir/function-references.test.js +2 -1
- package/dist/ir/function-references.test.js.map +1 -1
- package/dist/ir/init-only-globals.d.ts +16 -0
- package/dist/ir/init-only-globals.js +121 -0
- package/dist/ir/init-only-globals.js.map +1 -0
- package/dist/ir/init-only-globals.test.d.ts +1 -0
- package/dist/ir/init-only-globals.test.js +115 -0
- package/dist/ir/init-only-globals.test.js.map +1 -0
- package/dist/ir/initializer-bindings.d.ts +18 -1
- package/dist/ir/initializer-bindings.js +91 -1
- package/dist/ir/initializer-bindings.js.map +1 -1
- package/dist/ir/initializer-bindings.test.js +57 -2
- package/dist/ir/initializer-bindings.test.js.map +1 -1
- package/dist/ir/int32-slots.d.ts +22 -0
- package/dist/ir/int32-slots.js +398 -13
- package/dist/ir/int32-slots.js.map +1 -1
- package/dist/ir/int32-slots.test.js +119 -8
- package/dist/ir/int32-slots.test.js.map +1 -1
- package/dist/ir/integer-loop-facts.d.ts +27 -1
- package/dist/ir/integer-loop-facts.js +239 -7
- package/dist/ir/integer-loop-facts.js.map +1 -1
- package/dist/ir/integer-ranges.d.ts +8 -5
- package/dist/ir/integer-ranges.js +147 -59
- package/dist/ir/integer-ranges.js.map +1 -1
- package/dist/ir/integer-ranges.test.js +285 -7
- package/dist/ir/integer-ranges.test.js.map +1 -1
- package/dist/ir/ir.d.ts +22 -1
- package/dist/ir/ir.js +4 -0
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/property-bags.d.ts +14 -0
- package/dist/ir/property-bags.js +121 -0
- package/dist/ir/property-bags.js.map +1 -0
- package/dist/ir/receiver-readers.d.ts +21 -0
- package/dist/ir/receiver-readers.js +217 -0
- package/dist/ir/receiver-readers.js.map +1 -0
- package/dist/ir/runtime-features.d.ts +3 -1
- package/dist/ir/runtime-features.js +102 -7
- package/dist/ir/runtime-features.js.map +1 -1
- package/dist/ir/runtime-features.test.js +62 -0
- package/dist/ir/runtime-features.test.js.map +1 -1
- package/dist/ir/traverse.js +4 -0
- package/dist/ir/traverse.js.map +1 -1
- package/dist/ir/validate.js +71 -3
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.js +59 -0
- package/dist/ir/validate.test.js.map +1 -1
- package/dist/library/int-infer.js +3 -0
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/ir-policy.js +5 -0
- package/dist/library/ir-policy.js.map +1 -1
- package/package.json +20 -20
- package/surface-manifest.json +57 -19
|
@@ -6,13 +6,14 @@ import { CYC_BUF, CYC_HDR, FIRST_EMITTED_CYC_KIND, inlineAllocSupported } from "
|
|
|
6
6
|
import { hasLoneSurrogate, llvmBytes as llBytes, wtf8Bytes } from "../literals.js";
|
|
7
7
|
import { InternalCompilerError } from "../../errors.js";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
|
-
import { endsWithJump, matchStringSelfConcat, streamTypedRefEligible, undefinedArmTag, } from "../../ir/analysis.js";
|
|
9
|
+
import { endsWithJump, matchFieldSelfConcat, matchStringSelfConcat, readsArraySlot, streamTypedRefEligible, undefinedArmTag, } from "../../ir/analysis.js";
|
|
10
10
|
import { emitLibraryIdentityLines } from "../library-identity-markers.js";
|
|
11
11
|
import { loopTestsEveryIteration } from "../loop-polls.js";
|
|
12
12
|
import { CAUGHT, ffiCallbackType, isDynTypedRefType, isFfiContextParam, isRefCounted, isUnitType, POINTER_KINDS, RUNTIME_EMITTER_CLASS, RUNTIME_EVENT_CLASS, RUNTIME_EVENT_TARGET_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, typeEquals, typeKey, STRING, VOID, } from "../../ir/ir.js";
|
|
13
13
|
import { moduleRuntimeFeatures } from "../../ir/runtime-features.js";
|
|
14
14
|
import { matchIntegerArrayForLoop, matchIntegerBytesForLoop, matchIntegerCountedForLoop, } from "../../ir/integer-loops.js";
|
|
15
15
|
import { emitCountedLoopLimit } from "./counted-loops.js";
|
|
16
|
+
import { emitSafeIntegerCheck, emitSpeculativeInteger, planSpeculativeLoop, speculativeReads, speculativeWriteTarget, SPECULATIVE_RANGE, } from "./speculative-integers.js";
|
|
16
17
|
import { emitLiteralSwitch } from "./switch-dispatch.js";
|
|
17
18
|
import { findBytesBounds } from "./bytes-bounds.js";
|
|
18
19
|
import { emitByteWindowGuard, findInitializedByteLoopBindings, matchByteWindow, } from "./byte-windows.js";
|
|
@@ -21,16 +22,21 @@ import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
|
|
|
21
22
|
import { RESULT_ENTRY_PREFIX, addRecordResultEntries } from "./record-results.js";
|
|
22
23
|
import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js";
|
|
23
24
|
import { everyExpr, everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
|
|
24
|
-
import { analyzeIntegerRanges, INT32_RANGE } from "../../ir/integer-ranges.js";
|
|
25
|
+
import { analyzeIntegerRanges, INT32_RANGE, } from "../../ir/integer-ranges.js";
|
|
25
26
|
import { unshareNodes } from "../../ir/unshare.js";
|
|
26
|
-
import {
|
|
27
|
+
import { copyStableCaptures } from "../../ir/capture-values.js";
|
|
28
|
+
import { analyzeInt32Slots, dynamicFieldStores } from "../../ir/int32-slots.js";
|
|
27
29
|
import { analyzeFinalFields, constructorClass } from "../../ir/final-fields.js";
|
|
28
30
|
import { foldConstantGlobals } from "../../ir/constant-globals.js";
|
|
31
|
+
import { dropUnwrittenPropertyBags } from "../../ir/property-bags.js";
|
|
32
|
+
import { freezeInitOnlyGlobals } from "../../ir/init-only-globals.js";
|
|
33
|
+
import { hoistFinalFieldReads } from "../../ir/field-values.js";
|
|
29
34
|
import { findIntegerViews } from "./integer-views.js";
|
|
30
35
|
import { findInt32Abis, INT32_ABI_SUFFIX, SCALAR_UNION_DUMMY, SCALAR_UNION_RESULT, } from "./int32-abi.js";
|
|
31
36
|
import { findInt32Locals } from "./int32-locals.js";
|
|
37
|
+
import { emitIntegerClone, emitIntegerDispatcher, findIntegerClones, NO_INTEGER_CLONES, } from "./integer-clones.js";
|
|
32
38
|
import { exactInteger, narrowInteger } from "./integer-values.js";
|
|
33
|
-
import { findInitializerBindings, withInitializerBindings } from "../../ir/initializer-bindings.js";
|
|
39
|
+
import { conditionalLoopWrites, findInitializerBindings, initializerLocalCandidates, withInitializerBindings, withInitializerLocals, } from "../../ir/initializer-bindings.js";
|
|
34
40
|
import { findConstantNumericTables } from "../../ir/constant-tables.js";
|
|
35
41
|
import { allocateFfiCallbackAdapters, hasForeignFfiCallback, hasRetainedFfiCallback, } from "../ffi-callbacks.js";
|
|
36
42
|
import { RUNTIME_ABI_MARKER } from "../runtime-abi.js";
|
|
@@ -45,8 +51,9 @@ import { NullableRefFields } from "./nullable-fields.js";
|
|
|
45
51
|
import { InlineUnionFields, loadInlineUnion, presentBits, stackUnionBox, storeInlineUnion, unionBoxBits, } from "./inline-unions.js";
|
|
46
52
|
import { emitLocalArrayRead, emitProjectedArrayRead, emitBorrowedArrayRead, findLocalArrayReads, findCallArrayReads, OptionalArrayReads, } from "./local-array-reads.js";
|
|
47
53
|
import { CheckedNarrows, emitCheckedNarrowReceiver } from "./checked-narrows.js";
|
|
48
|
-
import { emitDenseArrayGet, emitDenseArraySet } from "./dense-array-access.js";
|
|
54
|
+
import { emitDenseArrayGet, emitDenseArraySet, emitDenseSlotUnlink } from "./dense-array-access.js";
|
|
49
55
|
import { emitStackMapRead, findMapReadLifetimes, findReadNumberMapWrites, matchMapRead, } from "./map-read-lifetimes.js";
|
|
56
|
+
import { findMapEntryFusions } from "./map-entry-fusion.js";
|
|
50
57
|
import { emitBorrowedFieldSequence, guardedValue } from "./borrowed-receivers.js";
|
|
51
58
|
import { borrowableInputs, emitBorrowedInput, emitBorrowedInputs } from "./borrowed-inputs.js";
|
|
52
59
|
import { analyzeWalks, findWalkBorrows } from "./walk-borrows.js";
|
|
@@ -72,7 +79,7 @@ import { PublishWalkers } from "./publish.js";
|
|
|
72
79
|
import { computeImmutableFields, stableGlobals } from "../immutability.js";
|
|
73
80
|
import { computePublishedTypes } from "../publication.js";
|
|
74
81
|
import { emitExpr } from "./expr-dispatch.js";
|
|
75
|
-
import { dynKind, raceAdapterFor, genResultThunkFor, childExitThunkFor, childExitSignalThunkFor, childDataThunkFor, execFileThunkFor, ipcMessageThunkFor, ipcSendThunkFor, emitterFixedAdapter, wrapEmitterListener, unwrapNullableClosure, closeBindThunkFor, closeOverrideWrapFor, } from "./expr-callbacks.js";
|
|
82
|
+
import { dynKind, raceAdapterFor, genResultThunkFor, childExitThunkFor, childExitSignalThunkFor, childDataThunkFor, execFileThunkFor, ipcMessageThunkFor, ipcSendThunkFor, emitterFixedAdapter, emitterOnceAdapter, wrapEmitterListener, unwrapNullableClosure, closeBindThunkFor, closeOverrideWrapFor, } from "./expr-callbacks.js";
|
|
76
83
|
import { streamDataAdapter, streamDoneFnFor, cryptoBytesThunkFor, cryptoIntThunkFor, fsRenameThunkFor, streamCbThunkFor, zlibBytesThunkFor, } from "./expr-stream-callbacks.js";
|
|
77
84
|
import { emitStableReceiver, resolveThunkFor, tagInSet, arrPush, emitArrayCopyLoop, emitStrIntrinsic, emitArrIntrinsic, emitDiscardedSplice, wrapNullable, emitMapNew, mapSet, emitMapLikeIntrinsic, emitSetNew, } from "./expr-containers.js";
|
|
78
85
|
import { emitIntegerLoopIndex, emitBytesIndex, emitBytesData, emitBytesLength, emitBytesGet, emitToUint32, emitBytesSet, emitBytesIntrinsic, } from "./expr-bytes.js";
|
|
@@ -95,7 +102,7 @@ import { literalCapWord, literalCapWord32 } from "./string-key-hash.js";
|
|
|
95
102
|
import { MAP_ITER_DECL, NUMBER_MAP_ENTRY_DECL, STRING_MAP_ENTRY_DECL, } from "./map-number-lookup.js";
|
|
96
103
|
import { STRING_READ_HELPERS, stringScanDecl } from "./string-reads.js";
|
|
97
104
|
import { emitConcatInputs, emitMixedConcat, numberPart, stringParts, } from "./string-construction.js";
|
|
98
|
-
import { arrNewCall, boxAccess, boxNewCall, boxReleaseSym, computeTraced, elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, FN_ATTRS, inlineRcDecls, limitInlineDynReleases, llFieldType, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
|
|
105
|
+
import { arrNewCall, boxAccess, boxNewCall, boxReleaseSym, computeTraced, elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, FN_ATTRS, inlineRcDecls, limitInlineDynReleases, llFieldType, mapFlagsOffset, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
|
|
99
106
|
import { INLINE_LITERAL_BYTES, literalEqualityDefinition } from "./string-literal-equality.js";
|
|
100
107
|
export { LlvmUnsupportedError } from "./unsupported.js";
|
|
101
108
|
function ffiCallbackDummyLl(callback) {
|
|
@@ -129,14 +136,14 @@ export function emitLlvmModule(input, options = {}) {
|
|
|
129
136
|
const mod = unshareNodes(input);
|
|
130
137
|
// Keep source storage intact for debugger inspection in dev builds.
|
|
131
138
|
return new LlEmitter(options.debugSources === undefined
|
|
132
|
-
? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction)))
|
|
139
|
+
? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(copyStableCaptures(hoistFinalFieldReads(freezeInitOnlyGlobals(dropUnwrittenPropertyBags(mod))))), mangleFunction)))
|
|
133
140
|
: mod, options).emit();
|
|
134
141
|
}
|
|
135
142
|
/** Keep large translation units below the host's single-string limit. */
|
|
136
143
|
export function emitLlvmModuleSource(input, options = {}) {
|
|
137
144
|
const mod = unshareNodes(input);
|
|
138
145
|
const parts = new LlEmitter(options.debugSources === undefined
|
|
139
|
-
? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction)))
|
|
146
|
+
? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(copyStableCaptures(hoistFinalFieldReads(freezeInitOnlyGlobals(dropUnwrittenPropertyBags(mod))))), mangleFunction)))
|
|
140
147
|
: mod, options).emitParts();
|
|
141
148
|
const length = parts.reduce((total, part) => total + part.length, Math.max(0, parts.length - 1));
|
|
142
149
|
if (length <= 256 * 1024 * 1024)
|
|
@@ -147,6 +154,9 @@ function llStrBytes(text) {
|
|
|
147
154
|
return llBytes(wtf8Bytes(text));
|
|
148
155
|
}
|
|
149
156
|
const NO_BORROWED = new Set();
|
|
157
|
+
/** ScrExcKind tags of a generator slot holding a number or a boolean. */
|
|
158
|
+
const SCR_EXC_F64 = 1;
|
|
159
|
+
const SCR_EXC_BOOL = 2;
|
|
150
160
|
/** Whether any node of `e` names the local (reads, writes, declarations,
|
|
151
161
|
* captures). */
|
|
152
162
|
function mentionsLocal(e, localId) {
|
|
@@ -226,6 +236,12 @@ export class LlEmitter {
|
|
|
226
236
|
}
|
|
227
237
|
return `, !tbaa !${tag}`;
|
|
228
238
|
}
|
|
239
|
+
/** The `!range` attachment of an array length load: a length is at most
|
|
240
|
+
* 2^32 - 1 (the runtime's SCR_ARR_MAX_LENGTH), so conversions to double
|
|
241
|
+
* and back fold. 32-bit targets load the full range anyway. */
|
|
242
|
+
get arrayLengthRange() {
|
|
243
|
+
return this.sizeType === "i64" ? ", !range !{i64 0, i64 4294967296}" : "";
|
|
244
|
+
}
|
|
229
245
|
fieldAliasAttachment(ptr) {
|
|
230
246
|
const tag = this.fieldPointerTags.get(ptr);
|
|
231
247
|
return tag === undefined ? "" : `, !tbaa !${tag}`;
|
|
@@ -312,6 +328,8 @@ export class LlEmitter {
|
|
|
312
328
|
/** Interned NUL-terminated C-string constants (scr_jb_puts labels, the
|
|
313
329
|
* stringify indent text): UTF-8 text → { symbol, byte length }. */
|
|
314
330
|
cstrs = new Map();
|
|
331
|
+
/** Interned ScrMapOps descriptors (scr_map.h): initializer → symbol. */
|
|
332
|
+
mapOpsConsts = new Map();
|
|
315
333
|
/** Type-directed walker functions (JSON serializers, the indent
|
|
316
334
|
* rewriter, union ToString/join) — interned per typeKey/unionId, defs
|
|
317
335
|
* flushed with the shape helpers. */
|
|
@@ -355,6 +373,8 @@ export class LlEmitter {
|
|
|
355
373
|
callArrayReads = new Map();
|
|
356
374
|
mapReadLifetimes = { locals: new Map(), arguments: new Map() };
|
|
357
375
|
readNumberMapWrites = new Set();
|
|
376
|
+
/** The current function's fused string-key Map get-then-set pairs (map-entry-fusion.ts). */
|
|
377
|
+
mapEntryFusion = null;
|
|
358
378
|
callLifetimes;
|
|
359
379
|
constantCallbacks;
|
|
360
380
|
currentConstantCallbacks = new Map();
|
|
@@ -402,6 +422,9 @@ export class LlEmitter {
|
|
|
402
422
|
stableGlobals = new Set();
|
|
403
423
|
tdzGlobals = new Map();
|
|
404
424
|
constantNumericTables;
|
|
425
|
+
/** Constant-key reads on checked-dynamic objects, each with its own
|
|
426
|
+
* zero-initialized ScrDynReadIc (scr_runtime.h). */
|
|
427
|
+
dynReadIcSites = 0;
|
|
405
428
|
/** May-throw analysis (computeMayThrow): pending
|
|
406
429
|
* checks are emitted only after calls that can actually raise. */
|
|
407
430
|
mayThrow;
|
|
@@ -518,8 +541,20 @@ export class LlEmitter {
|
|
|
518
541
|
initializerBindings;
|
|
519
542
|
/** Whole-program int32 storage and parameter/result facts. */
|
|
520
543
|
int32Slots;
|
|
544
|
+
/** The program has no runtime store into a checked-dynamic value
|
|
545
|
+
* (dynamicFieldStores), so live views of classes and records never
|
|
546
|
+
* commit back into their objects. Library builds keep their commits:
|
|
547
|
+
* their values cross a native boundary. */
|
|
548
|
+
liveViewsReadOnly;
|
|
521
549
|
/** Functions whose direct calls use i32 parameters or results. */
|
|
522
550
|
int32Abis;
|
|
551
|
+
/** Pure number functions with speculative integer clones. */
|
|
552
|
+
integerClones;
|
|
553
|
+
/** The IR function being emitted. */
|
|
554
|
+
currentFunctionName = "";
|
|
555
|
+
integerParamRange(fnName, localId) {
|
|
556
|
+
return this.int32Slots.facts(fnName).params?.get(localId) ?? null;
|
|
557
|
+
}
|
|
523
558
|
/** Immutable module constants (contract types) whose graphs become
|
|
524
559
|
* immortal once initialized: their reads need no reference counting. */
|
|
525
560
|
immortalGlobals;
|
|
@@ -531,6 +566,17 @@ export class LlEmitter {
|
|
|
531
566
|
/** Active induction bindings retain their width independently of size_t. */
|
|
532
567
|
integerLoopBindings = new Map();
|
|
533
568
|
countedLoopsEnabled = false;
|
|
569
|
+
/** Speculative integer loops (speculative-integers.ts) run in this function. */
|
|
570
|
+
speculativeLoopsEnabled = false;
|
|
571
|
+
/** The fast copy of a speculative integer loop being emitted: its
|
|
572
|
+
* integer-computed writes and where each resumes in the ordinary copy. */
|
|
573
|
+
speculation = null;
|
|
574
|
+
/** The ordinary copy being emitted: resume labels still to place. */
|
|
575
|
+
speculationResume = null;
|
|
576
|
+
/** The function being emitted, for speculative globals. */
|
|
577
|
+
speculativeFunction = "";
|
|
578
|
+
/** Every function that writes each number global (computed on demand). */
|
|
579
|
+
globalWriters = null;
|
|
534
580
|
/** Whole-program final reference fields (ir/final-fields.ts). */
|
|
535
581
|
finalFields;
|
|
536
582
|
/** The current function may borrow final fields of borrowable objects:
|
|
@@ -543,6 +589,9 @@ export class LlEmitter {
|
|
|
543
589
|
byteWindowEntries = new Map();
|
|
544
590
|
bytesBounds = new Set();
|
|
545
591
|
integerViews = new Map();
|
|
592
|
+
/** Module globals the current function owns outright (initializer
|
|
593
|
+
* bindings): their function-local slot, which stands in for the global. */
|
|
594
|
+
privateGlobalSlots = new Map();
|
|
546
595
|
/** Number locals of the current function stored only as an i32
|
|
547
596
|
* (int32-locals.ts); their slots are `alloca i32`. */
|
|
548
597
|
int32Locals = new Map();
|
|
@@ -576,6 +625,9 @@ export class LlEmitter {
|
|
|
576
625
|
* switched-coroutine intrinsics (wasm32 async bodies and generators,
|
|
577
626
|
* native synchronous generators; see coroLowered). */
|
|
578
627
|
currentCoro = null;
|
|
628
|
+
/** Inline generator slot accesses need the %ScrGenHead/%ScrGenSlot
|
|
629
|
+
* mirrors in the module header. */
|
|
630
|
+
usesGenSlots = false;
|
|
579
631
|
/** The generator channels of the function being emitted (null outside
|
|
580
632
|
* generator bodies): yieldExpr emission reads them, and emitTryCatch's
|
|
581
633
|
* catch prologue emits the GENRET sentinel re-unwind exactly here. */
|
|
@@ -600,6 +652,9 @@ export class LlEmitter {
|
|
|
600
652
|
this.constantNumericTables = findConstantNumericTables(mod);
|
|
601
653
|
this.initializerBindings = findInitializerBindings(mod);
|
|
602
654
|
this.int32Slots = analyzeInt32Slots(mod);
|
|
655
|
+
const dynamicStores = dynamicFieldStores(mod);
|
|
656
|
+
this.liveViewsReadOnly =
|
|
657
|
+
mod.lib === undefined && !dynamicStores.all && dynamicStores.names.size === 0;
|
|
603
658
|
this.int32Abis = this.debug === null ? findInt32Abis(mod, this.int32Slots) : new Map();
|
|
604
659
|
this.immortalGlobals = new Set(mod.contracts?.immortalGlobals ?? []);
|
|
605
660
|
this.programLifetime = new Set(mod.contracts?.programLifetime ?? []);
|
|
@@ -743,6 +798,20 @@ export class LlEmitter {
|
|
|
743
798
|
return shape !== undefined && !recordDynKeepsIdentity(shape);
|
|
744
799
|
});
|
|
745
800
|
this.constantCallbacks = findConstantCallbacks(mod, this.callLifetimes);
|
|
801
|
+
// Integer clones signal a stack overflow through their result, which
|
|
802
|
+
// needs the single-threaded guard; worker programs also poll for
|
|
803
|
+
// termination, and debug builds and stack traces keep every frame.
|
|
804
|
+
// Clones read number arrays their function borrows (64-bit layouts).
|
|
805
|
+
this.integerClones =
|
|
806
|
+
this.debug === null &&
|
|
807
|
+
this.stackChecks !== null &&
|
|
808
|
+
mod.workers !== true &&
|
|
809
|
+
mod.lib === undefined &&
|
|
810
|
+
!this.stackTraces
|
|
811
|
+
? findIntegerClones(mod, this.sizeType === "i64"
|
|
812
|
+
? (fn, index) => this.callLifetimes.borrowed.get(fn)?.has(index) === true
|
|
813
|
+
: undefined)
|
|
814
|
+
: NO_INTEGER_CLONES;
|
|
746
815
|
this.stackCallbacks = new StackCallbacks(this.fnByName);
|
|
747
816
|
this.receiverReaders = new AmbientReceiverReaders(this.fnByName);
|
|
748
817
|
this.stackCaptures = new StackCaptures(this.fnByName, this.callLifetimes, this.stackCallbacks);
|
|
@@ -780,6 +849,7 @@ export class LlEmitter {
|
|
|
780
849
|
needBadTag: () => this.needBadTag(),
|
|
781
850
|
internLiteral: (text) => this.internLiteral(text),
|
|
782
851
|
cstr: (text) => this.cstr(text),
|
|
852
|
+
mapOps: (operands) => this.mapOps(operands),
|
|
783
853
|
unitInstanceRef: (unionId, tag) => this.unitInstanceRef(unionId, tag),
|
|
784
854
|
retypeGuardRef: (unionId) => this.retypeGuardRef(unionId),
|
|
785
855
|
fieldAbsentTestIn: (B, value, unionId) => this.fieldAbsentTestIn(B, value, unionId),
|
|
@@ -880,6 +950,10 @@ export class LlEmitter {
|
|
|
880
950
|
virtualSlotBorrowed(rootName, slotIndex) {
|
|
881
951
|
return this.virtualBorrows.slot(rootName, slotIndex);
|
|
882
952
|
}
|
|
953
|
+
/** Optimized emission: no debug-info (dev) posture. */
|
|
954
|
+
get optimizing() {
|
|
955
|
+
return this.debug === null;
|
|
956
|
+
}
|
|
883
957
|
/** The vtable entry for one implementation (VirtualBorrows.entry). */
|
|
884
958
|
virtualEntry(implFn) {
|
|
885
959
|
return this.virtualBorrows.entry(implFn);
|
|
@@ -1376,7 +1450,25 @@ export class LlEmitter {
|
|
|
1376
1450
|
` call void @scr_run_exit_listeners(double %${prefix}hd)`,
|
|
1377
1451
|
];
|
|
1378
1452
|
};
|
|
1379
|
-
|
|
1453
|
+
// The entry's exits release the module globals only where the thread
|
|
1454
|
+
// frees its heap when its script ends (scr_exit_frees: workers, and
|
|
1455
|
+
// builds that check for leaks); the main thread of other builds leaves
|
|
1456
|
+
// it to the operating system.
|
|
1457
|
+
const exitReleaseLines = (prefix) => {
|
|
1458
|
+
const releases = globalReleaseLines(prefix);
|
|
1459
|
+
if (releases.length === 0)
|
|
1460
|
+
return releases;
|
|
1461
|
+
this.declare(`declare zeroext i1 @scr_exit_frees()`);
|
|
1462
|
+
return [
|
|
1463
|
+
` %${prefix}.frees = call zeroext i1 @scr_exit_frees()`,
|
|
1464
|
+
` br i1 %${prefix}.frees, label %${prefix}.release, label %${prefix}.kept`,
|
|
1465
|
+
`${prefix}.release:`,
|
|
1466
|
+
...releases,
|
|
1467
|
+
` br label %${prefix}.kept`,
|
|
1468
|
+
`${prefix}.kept:`,
|
|
1469
|
+
];
|
|
1470
|
+
};
|
|
1471
|
+
const globalReleases = exitReleaseLines("g");
|
|
1380
1472
|
const asyncEntry = this.fnByName.get(this.mod.entry)?.async === true;
|
|
1381
1473
|
const entryMayThrow = this.mayThrow.has(this.mod.entry);
|
|
1382
1474
|
// The event loop runs when timers appeared OR any async/generator
|
|
@@ -1388,12 +1480,12 @@ export class LlEmitter {
|
|
|
1388
1480
|
const runsLoop = this.usesTimers ||
|
|
1389
1481
|
this.ffiHasForeignCallback ||
|
|
1390
1482
|
this.mod.functions.some((f) => f.async === true || f.generator !== undefined);
|
|
1391
|
-
const uncaughtReleases = entryMayThrow && !asyncEntry ?
|
|
1392
|
-
const loopReleasesU = runsLoop ?
|
|
1393
|
-
const loopReleasesR = runsLoop ?
|
|
1394
|
-
const topRejectReleases = asyncEntry ?
|
|
1395
|
-
const topPendingReleases = asyncEntry ?
|
|
1396
|
-
const loopReportedReleases = runsLoop ?
|
|
1483
|
+
const uncaughtReleases = entryMayThrow && !asyncEntry ? exitReleaseLines("gu") : [];
|
|
1484
|
+
const loopReleasesU = runsLoop ? exitReleaseLines("gl") : [];
|
|
1485
|
+
const loopReleasesR = runsLoop ? exitReleaseLines("gr") : [];
|
|
1486
|
+
const topRejectReleases = asyncEntry ? exitReleaseLines("gt") : [];
|
|
1487
|
+
const topPendingReleases = asyncEntry ? exitReleaseLines("gp") : [];
|
|
1488
|
+
const loopReportedReleases = runsLoop ? exitReleaseLines("gq") : [];
|
|
1397
1489
|
// main's epilogues read the flag / the loop entry points — declared
|
|
1398
1490
|
// HERE, before the extern block flushes. They run once, so they keep
|
|
1399
1491
|
// the out-of-line scr_exc_pending call; per-call checks inline the
|
|
@@ -1519,22 +1611,20 @@ export class LlEmitter {
|
|
|
1519
1611
|
// preserving the target object's identity.
|
|
1520
1612
|
`%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i8, i8, i16, i32, ptr, ptr, ptr, ptr }`,
|
|
1521
1613
|
`%ScrArrOps = type { ptr, ptr, ptr }`,
|
|
1522
|
-
// The ScrMap prefix { rc, key_kind, val_kind,
|
|
1523
|
-
//
|
|
1524
|
-
|
|
1525
|
-
...(this.rcHelpers === null
|
|
1526
|
-
? []
|
|
1527
|
-
: [`%ScrMapRc = type { ${this.sizeType}, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr }`]),
|
|
1614
|
+
// The ScrMap prefix { rc, key_kind, val_kind, traced } — the inline
|
|
1615
|
+
// RC fast paths test the traced byte (shapes.ts).
|
|
1616
|
+
...(this.rcHelpers === null ? [] : [`%ScrMapRc = type { ${this.sizeType}, i8, i8, i8 }`]),
|
|
1528
1617
|
// The complete ScrMap and its { key, val, hash } entry, read by the
|
|
1529
1618
|
// inline number-key lookup (sc_map_entry_f64) and the inline
|
|
1530
|
-
// iteration steps (sc_map_iter_entry): field
|
|
1531
|
-
// field
|
|
1532
|
-
//
|
|
1619
|
+
// iteration steps (sc_map_iter_entry): field 8 is the i32 entry
|
|
1620
|
+
// count, field 10 the i64 absent-key filter, entries is field 11,
|
|
1621
|
+
// nbuckets and buckets fields 13 and 14, the direct index and its i32
|
|
1622
|
+
// length fields 15 and 16.
|
|
1533
1623
|
...(this.decls.has(NUMBER_MAP_ENTRY_DECL) ||
|
|
1534
1624
|
this.decls.has(STRING_MAP_ENTRY_DECL) ||
|
|
1535
1625
|
this.decls.has(MAP_ITER_DECL)
|
|
1536
1626
|
? [
|
|
1537
|
-
`%ScrMapIx = type { ${this.sizeType},
|
|
1627
|
+
`%ScrMapIx = type { ${this.sizeType}, i8, i8, i8, i8, i32, ptr, ptr, i32, i32, i64, ptr, i32, i32, ptr, ptr, i32, i32 }`,
|
|
1538
1628
|
]
|
|
1539
1629
|
: []),
|
|
1540
1630
|
// String-keyed reference reads load the value slot of the entry
|
|
@@ -1572,7 +1662,7 @@ export class LlEmitter {
|
|
|
1572
1662
|
// typed-array access GEPs through this directly: the IR type already
|
|
1573
1663
|
// fixes elem, so the hot path needs neither a runtime kind load nor
|
|
1574
1664
|
// the generic scr_bytes_get/set call.
|
|
1575
|
-
`%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, ptr }`,
|
|
1665
|
+
`%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, i8, ptr }`,
|
|
1576
1666
|
// The capture box { rc, kind, obj_retain, obj_release, obj_trace,
|
|
1577
1667
|
// slot } — TDZ reads peek the payload slot (offset 40) directly.
|
|
1578
1668
|
`%ScrBox = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
|
|
@@ -1583,6 +1673,15 @@ export class LlEmitter {
|
|
|
1583
1673
|
`%ScrDynPath = type { ptr, ptr, ${this.sizeType} }`,
|
|
1584
1674
|
`%ScrIslandModule = type { ptr, ptr, ${this.sizeType}, ${this.sizeType}, i32, ptr, ${this.sizeType}, ${this.sizeType} }`,
|
|
1585
1675
|
`%ScrIslandEdge = type { ptr, ptr, ptr, i32 }`,
|
|
1676
|
+
// A generator's prefix { rc, receiver, fiber, state, out } and its
|
|
1677
|
+
// value slot { kind, f64, b, payload, release_fn } (scr_async.c pins
|
|
1678
|
+
// both): number and boolean yields and drives address OUT directly.
|
|
1679
|
+
...(this.usesGenSlots
|
|
1680
|
+
? [
|
|
1681
|
+
`%ScrGenSlot = type { i32, double, i8, ptr, ptr }`,
|
|
1682
|
+
`%ScrGenHead = type { ${this.sizeType}, ptr, ptr, i32, %ScrGenSlot }`,
|
|
1683
|
+
]
|
|
1684
|
+
: []),
|
|
1586
1685
|
];
|
|
1587
1686
|
for (const line of shapes.typeDefs)
|
|
1588
1687
|
out.push(line);
|
|
@@ -1607,6 +1706,11 @@ export class LlEmitter {
|
|
|
1607
1706
|
out.push(d);
|
|
1608
1707
|
out.push(``);
|
|
1609
1708
|
const flushedDecls = new Set(this.decls);
|
|
1709
|
+
{
|
|
1710
|
+
const tl = this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : "";
|
|
1711
|
+
for (let site = 0; site < this.dynReadIcSites; site++)
|
|
1712
|
+
out.push(`@sc_dyn_read_ic_${site} = internal ${tl}global { ptr, ${this.sizeType}, ${this.sizeType}, ptr } zeroinitializer`);
|
|
1713
|
+
}
|
|
1610
1714
|
for (const table of this.constantNumericTables.values()) {
|
|
1611
1715
|
const n = table.values.length;
|
|
1612
1716
|
let tableTmp = 0;
|
|
@@ -1720,6 +1824,11 @@ export class LlEmitter {
|
|
|
1720
1824
|
}
|
|
1721
1825
|
if (this.cstrs.size > 0)
|
|
1722
1826
|
out.push(``);
|
|
1827
|
+
for (const [body, sym] of this.mapOpsConsts) {
|
|
1828
|
+
out.push(`@${sym} = internal constant { ptr, ptr, ptr, ptr, ptr, ptr, ptr } { ${body} }`);
|
|
1829
|
+
}
|
|
1830
|
+
if (this.mapOpsConsts.size > 0)
|
|
1831
|
+
out.push(``);
|
|
1723
1832
|
for (const line of ffiCallbacks.globals)
|
|
1724
1833
|
out.push(line);
|
|
1725
1834
|
if (ffiCallbacks.globals.length > 0)
|
|
@@ -2196,19 +2305,25 @@ export class LlEmitter {
|
|
|
2196
2305
|
const sz = this.sizeType;
|
|
2197
2306
|
let mapTmp = 0;
|
|
2198
2307
|
const mapIndex = exactIndexLines("%k", "%nd", sz, () => `%ix${mapTmp++}`);
|
|
2199
|
-
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
|
|
2308
|
+
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 16`, ...(sz === "i64"
|
|
2309
|
+
? [` %nd32 = load i32, ptr %ndp`, ` %nd = zext i32 %nd32 to i64`]
|
|
2310
|
+
: [` %nd = load i32, ptr %ndp`]),
|
|
2200
2311
|
// A non-integral key inside the table is as absent as one outside it.
|
|
2201
2312
|
...mapIndex.lines.map((line) => ` ${line}`), ` br i1 ${mapIndex.ok}, label %slot, label %outside`, `slot:`, sz === "i64"
|
|
2202
2313
|
? ` %i = add i64 ${mapIndex.wide}, 0`
|
|
2203
|
-
: ` %i = trunc i64 ${mapIndex.wide} to ${sz}`, ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32
|
|
2314
|
+
: ` %i = trunc i64 ${mapIndex.wide} to ${sz}`, ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 15`, ` %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 11`, ` %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:`, ` %hashed = call ptr @sc_map_entry_f64_hashed(ptr %m, double %k)`, ` ret ptr %hashed`, `}`, ``,
|
|
2204
2315
|
// Hashing (scr_map_find_f64): the SameValueZero key word, its
|
|
2205
2316
|
// scr_key_mix hash (zero remapped to one) and the absent-key filter.
|
|
2206
|
-
`define internal ptr @sc_map_entry_f64_hashed(ptr %m, double %k) noinline ${FN_ATTRS} {`, `entry:`, ` %nan = fcmp uno double %k, %k`, ` %zero = fcmp oeq double %k, 0.0`, ` %kz = select i1 %zero, double 0.0, double %k`, ` %kzb = bitcast double %kz to i64`, ` %kb = select i1 %nan, i64 9221120237041090560, i64 %kzb`, ` %x1s = lshr i64 %kb, 30`, ` %x1 = xor i64 %kb, %x1s`, ` %x2 = mul i64 %x1, -4658895280553007687`, ` %x3s = lshr i64 %x2, 27`, ` %x3 = xor i64 %x2, %x3s`, ` %x4 = mul i64 %x3, -7723592293110705685`, ` %x5s = lshr i64 %x4, 31`, ` %x5 = xor i64 %x4, %x5s`, ` %hz = icmp eq i64 %x5, 0`, ` %h = select i1 %hz, i64 1, i64 %x5`, ` %f1s = lshr i64 %h, 44`, ` %f1a = and i64 %f1s, 63`, ` %f1 = shl i64 1, %f1a`, ` %f2s = lshr i64 %h, 50`, ` %f2a = and i64 %f2s, 63`, ` %f2 = shl i64 1, %f2a`, ` %fb = or i64 %f1, %f2`, ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32
|
|
2317
|
+
`define internal ptr @sc_map_entry_f64_hashed(ptr %m, double %k) noinline ${FN_ATTRS} {`, `entry:`, ` %nan = fcmp uno double %k, %k`, ` %zero = fcmp oeq double %k, 0.0`, ` %kz = select i1 %zero, double 0.0, double %k`, ` %kzb = bitcast double %kz to i64`, ` %kb = select i1 %nan, i64 9221120237041090560, i64 %kzb`, ` %x1s = lshr i64 %kb, 30`, ` %x1 = xor i64 %kb, %x1s`, ` %x2 = mul i64 %x1, -4658895280553007687`, ` %x3s = lshr i64 %x2, 27`, ` %x3 = xor i64 %x2, %x3s`, ` %x4 = mul i64 %x3, -7723592293110705685`, ` %x5s = lshr i64 %x4, 31`, ` %x5 = xor i64 %x4, %x5s`, ` %hz = icmp eq i64 %x5, 0`, ` %h = select i1 %hz, i64 1, i64 %x5`, ` %f1s = lshr i64 %h, 44`, ` %f1a = and i64 %f1s, 63`, ` %f1 = shl i64 1, %f1a`, ` %f2s = lshr i64 %h, 50`, ` %f2a = and i64 %f2s, 63`, ` %f2 = shl i64 1, %f2a`, ` %fb = or i64 %f1, %f2`, ` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`, ` %filter = load i64, ptr %fp`, ` %fm = and i64 %filter, %fb`, ` %fin = icmp eq i64 %fm, %fb`, ` br i1 %fin, label %table, label %miss`, `table:`, ` %nbp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 13`, ...(sz === "i64"
|
|
2318
|
+
? [` %nb32 = load i32, ptr %nbp`, ` %nb = zext i32 %nb32 to i64`]
|
|
2319
|
+
: [` %nb = load i32, ptr %nbp`]), ` %eps = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, ` %ents = load ptr, ptr %eps`, ` %linear = icmp eq ${sz} %nb, 0`, ` br i1 %linear, label %scan, label %buckets`,
|
|
2207
2320
|
// Small maps keep no bucket table: scan the cached entry hashes.
|
|
2208
|
-
`scan:`, ` %nep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32
|
|
2321
|
+
`scan:`, ` %nep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, ...(sz === "i64"
|
|
2322
|
+
? [` %ne32 = load i32, ptr %nep`, ` %ne = zext i32 %ne32 to i64`]
|
|
2323
|
+
: [` %ne = load i32, ptr %nep`]), ` br label %scan.loop`, `scan.loop:`, ` %si = phi ${sz} [ 0, %scan ], [ %si.next, %scan.next ]`, ` %sdone = icmp eq ${sz} %si, %ne`, ` br i1 %sdone, label %miss, label %scan.entry`, `scan.entry:`, ` %se = getelementptr inbounds %ScrMapEntry, ptr %ents, ${sz} %si`, ` %seh = getelementptr inbounds %ScrMapEntry, ptr %se, i32 0, i32 2`, ` %sh = load i64, ptr %seh`, ` %sk = load i64, ptr %se`, ` %shq = icmp eq i64 %sh, %h`, ` %skq = icmp eq i64 %sk, %kb`, ` %sq = and i1 %shq, %skq`, ` br i1 %sq, label %scan.found, label %scan.next`, `scan.found:`, ` ret ptr %se`, `scan.next:`, ` %si.next = add ${sz} %si, 1`, ` br label %scan.loop`,
|
|
2209
2324
|
// Compact tables tag each 24-bit entry index (+1) with the hash's
|
|
2210
2325
|
// top byte; wider tables take the runtime lookup.
|
|
2211
|
-
`buckets:`, ` %wide = icmp ugt ${sz} %nb, 16777216`, ` br i1 %wide, label %runtime, label %probe`, `probe:`, ` %bp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32
|
|
2326
|
+
`buckets:`, ` %wide = icmp ugt ${sz} %nb, 16777216`, ` br i1 %wide, label %runtime, label %probe`, `probe:`, ` %bp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 14`, ` %bk = load ptr, ptr %bp`, ` %mask = sub ${sz} %nb, 1`, ` %ht = lshr i64 %h, 32`, ` %ht32 = trunc i64 %ht to i32`, ` %tag = and i32 %ht32, -16777216`, sz === "i64" ? ` %hi = add i64 %h, 0` : ` %hi = trunc i64 %h to ${sz}`, ` %i0 = and ${sz} %hi, %mask`, ` br label %probe.loop`, `probe.loop:`, ` %pi = phi ${sz} [ %i0, %probe ], [ %pi.next, %probe.next ]`, ` %bep = getelementptr inbounds i32, ptr %bk, ${sz} %pi`, ` %b = load i32, ptr %bep`, ` %bempty = icmp eq i32 %b, 0`, ` br i1 %bempty, label %miss, label %probe.tag`, `probe.tag:`, ` %bt = and i32 %b, -16777216`, ` %btq = icmp eq i32 %bt, %tag`, ` br i1 %btq, label %probe.entry, label %probe.next`, `probe.entry:`, ` %bix = and i32 %b, 16777215`, ` %bix1 = sub i32 %bix, 1`, sz === "i32" ? ` %bi = add i32 %bix1, 0` : ` %bi = zext i32 %bix1 to ${sz}`, ` %pe = getelementptr inbounds %ScrMapEntry, ptr %ents, ${sz} %bi`, ` %peh = getelementptr inbounds %ScrMapEntry, ptr %pe, i32 0, i32 2`, ` %ph = load i64, ptr %peh`, ` %pk = load i64, ptr %pe`, ` %phq = icmp eq i64 %ph, %h`, ` %pkq = icmp eq i64 %pk, %kb`, ` %pq = and i1 %phq, %pkq`, ` br i1 %pq, label %probe.found, label %probe.next`, `probe.found:`, ` ret ptr %pe`, `probe.next:`, ` %pi.inc = add ${sz} %pi, 1`, ` %pi.next = and ${sz} %pi.inc, %mask`, ` br label %probe.loop`, `miss:`, ` ret ptr null`, `runtime:`, ` %r = call ptr @scr_map_entry_f64(ptr %m, double %k) memory(read) nounwind willreturn`, ` ret ptr %r`, `}`, ``);
|
|
2212
2327
|
}
|
|
2213
2328
|
// Map/Set iteration steps read the entries array inline: an index is
|
|
2214
2329
|
// live when it is below the entry count and its entry's cached hash is
|
|
@@ -2217,7 +2332,7 @@ export class LlEmitter {
|
|
|
2217
2332
|
// the iteration are visited.
|
|
2218
2333
|
if (this.decls.has(MAP_ITER_DECL)) {
|
|
2219
2334
|
const sz = this.sizeType;
|
|
2220
|
-
defs.push(`define internal ptr @sc_map_iter_entry(ptr %m, double %i) ${FN_ATTRS} {`, `entry:`, ` %np = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32
|
|
2335
|
+
defs.push(`define internal ptr @sc_map_iter_entry(ptr %m, double %i) ${FN_ATTRS} {`, `entry:`, ` %np = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, ` %n = load i32, ptr %np`, ` %nf = uitofp i32 %n to double`, ` %lo = fcmp oge double %i, 0.0`, ` %hi = fcmp olt double %i, %nf`, ` %in = and i1 %lo, %hi`, ` br i1 %in, label %load, label %none`, `load:`, ` %ix = fptoui double %i to ${sz}`, ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 11`, ` %entries = load ptr, ptr %ep`, ` %e = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %ix`, ` %hp = getelementptr inbounds %ScrMapEntry, ptr %e, i32 0, i32 2`, ` %h = load i64, ptr %hp`, ` %live = icmp ne i64 %h, 0`, ` br i1 %live, label %yes, label %none`, `yes:`, ` ret ptr %e`, `none:`, ` ret ptr null`, `}`, ``, `define internal double @sc_map_iter_count(ptr %m) ${FN_ATTRS} {`, `entry:`, ` %np = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, ` %n = load i32, ptr %np`, ` %nf = uitofp i32 %n to double`, ` ret double %nf`, `}`, ``);
|
|
2221
2336
|
}
|
|
2222
2337
|
// String-key Map/Set lookups test the map's absent-key filter inline
|
|
2223
2338
|
// (scr_map.c scr_map_filter_bits): a key whose two filter bits are not
|
|
@@ -2230,7 +2345,7 @@ export class LlEmitter {
|
|
|
2230
2345
|
if (this.decls.has(STRING_MAP_ENTRY_DECL)) {
|
|
2231
2346
|
defs.push(`define internal ptr @sc_map_entry_str(ptr %m, ptr %k) ${FN_ATTRS} {`, `entry:`, ...(this.sizeType === "i64"
|
|
2232
2347
|
? [
|
|
2233
|
-
` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32
|
|
2348
|
+
` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`,
|
|
2234
2349
|
` %filter = load i64, ptr %fp`,
|
|
2235
2350
|
` %capp = getelementptr inbounds %ScrStr, ptr %k, i32 0, i32 2`,
|
|
2236
2351
|
` %cap = load i64, ptr %capp`,
|
|
@@ -3036,6 +3151,18 @@ export class LlEmitter {
|
|
|
3036
3151
|
needBadTag() {
|
|
3037
3152
|
this.needsBadTag = true;
|
|
3038
3153
|
}
|
|
3154
|
+
/** The ScrMapOps descriptor holding these operands (ShapeHost.mapOps). */
|
|
3155
|
+
mapOps(operands) {
|
|
3156
|
+
if (operands.every((op) => op === "null"))
|
|
3157
|
+
return "null";
|
|
3158
|
+
const body = operands.map((op) => `ptr ${op}`).join(", ");
|
|
3159
|
+
let sym = this.mapOpsConsts.get(body);
|
|
3160
|
+
if (!sym) {
|
|
3161
|
+
sym = this.contentSymbol("sc_map_ops_", body);
|
|
3162
|
+
this.mapOpsConsts.set(body, sym);
|
|
3163
|
+
}
|
|
3164
|
+
return `@${sym}`;
|
|
3165
|
+
}
|
|
3039
3166
|
/** The interned immortal instance for a UNIT arm of a union — asserts
|
|
3040
3167
|
* the arm really is payload-less (undefined/null). Public: class
|
|
3041
3168
|
* emission initializes undefined-admitting fields through it
|
|
@@ -3616,6 +3743,259 @@ export class LlEmitter {
|
|
|
3616
3743
|
this.B.line(`store i32 0, ptr ${slot}`);
|
|
3617
3744
|
return slot;
|
|
3618
3745
|
}
|
|
3746
|
+
/** Emit a loop's condition, body and update through `emitCopy`, twice
|
|
3747
|
+
* when speculative-integers.ts plans a fast integer copy. Both copies
|
|
3748
|
+
* share the loop's setup and exit; the ordinary copy comes first so the
|
|
3749
|
+
* fast copy is only emitted when every resume point exists. */
|
|
3750
|
+
emitLoopCopies(loop, allowed, exit, emitCopy) {
|
|
3751
|
+
const plan = allowed &&
|
|
3752
|
+
this.speculativeLoopsEnabled &&
|
|
3753
|
+
this.speculation === null &&
|
|
3754
|
+
this.speculationResume === null
|
|
3755
|
+
? planSpeculativeLoop(loop, (id) => this.speculativeLocal(id), (id) => this.integerLoopBindings.has(id) || this.int32Locals.has(id), (e) => {
|
|
3756
|
+
const range = this.integerRanges.get(e);
|
|
3757
|
+
return (range !== undefined &&
|
|
3758
|
+
range !== null &&
|
|
3759
|
+
range.min >= -Number.MAX_SAFE_INTEGER &&
|
|
3760
|
+
range.max <= Number.MAX_SAFE_INTEGER);
|
|
3761
|
+
}, (id) => this.speculativeLocal(id) || this.speculativeInvariantGlobal(id))
|
|
3762
|
+
: null;
|
|
3763
|
+
if (!plan) {
|
|
3764
|
+
emitCopy(exit);
|
|
3765
|
+
return;
|
|
3766
|
+
}
|
|
3767
|
+
const B = this.B;
|
|
3768
|
+
const entries = new Map();
|
|
3769
|
+
let ok = "true";
|
|
3770
|
+
for (const [id, live] of plan.locals) {
|
|
3771
|
+
if (!live)
|
|
3772
|
+
continue;
|
|
3773
|
+
const value = B.tmp();
|
|
3774
|
+
B.line(`${value} = load double, ptr ${this.binding(id).slot}`);
|
|
3775
|
+
const [integer, exact] = emitSafeIntegerCheck(this, value);
|
|
3776
|
+
entries.set(id, integer);
|
|
3777
|
+
if (ok === "true")
|
|
3778
|
+
ok = exact;
|
|
3779
|
+
else {
|
|
3780
|
+
const both = B.tmp();
|
|
3781
|
+
B.line(`${both} = and i1 ${ok}, ${exact}`);
|
|
3782
|
+
ok = both;
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
const fast = B.newLabel("spec.fast"), slow = B.newLabel("spec.slow");
|
|
3786
|
+
B.condBr(ok, fast, slow);
|
|
3787
|
+
const resume = new Map();
|
|
3788
|
+
for (const s of plan.writes)
|
|
3789
|
+
resume.set(s, { label: B.newLabel("spec.resume"), placed: false });
|
|
3790
|
+
B.startBlock(slow);
|
|
3791
|
+
this.speculationResume = resume;
|
|
3792
|
+
try {
|
|
3793
|
+
emitCopy(exit);
|
|
3794
|
+
}
|
|
3795
|
+
finally {
|
|
3796
|
+
this.speculationResume = null;
|
|
3797
|
+
}
|
|
3798
|
+
B.startBlock(fast);
|
|
3799
|
+
if (![...resume.values()].every((r) => r.placed)) {
|
|
3800
|
+
B.br(slow);
|
|
3801
|
+
return;
|
|
3802
|
+
}
|
|
3803
|
+
for (const id of plan.locals.keys()) {
|
|
3804
|
+
const shadow = B.slot();
|
|
3805
|
+
B.entryAllocas.push(`${shadow} = alloca i64 ; speculative integer ${this.currentLocals.get(id)?.name ?? id}`);
|
|
3806
|
+
const entry = entries.get(id);
|
|
3807
|
+
if (entry !== undefined) {
|
|
3808
|
+
// The same double, now visibly an integer conversion: the double
|
|
3809
|
+
// phis of the fast copy fold into integer phis.
|
|
3810
|
+
const exact = B.tmp();
|
|
3811
|
+
B.line(`store i64 ${entry}, ptr ${shadow}`);
|
|
3812
|
+
B.line(`${exact} = sitofp i64 ${entry} to double`);
|
|
3813
|
+
B.line(`store double ${exact}, ptr ${this.binding(id).slot}`);
|
|
3814
|
+
}
|
|
3815
|
+
else
|
|
3816
|
+
B.line(`store i64 0, ptr ${shadow}`);
|
|
3817
|
+
this.integerLoopBindings.set(id, {
|
|
3818
|
+
slot: shadow,
|
|
3819
|
+
type: "i64",
|
|
3820
|
+
signed: true,
|
|
3821
|
+
range: SPECULATIVE_RANGE,
|
|
3822
|
+
});
|
|
3823
|
+
}
|
|
3824
|
+
// A global's store stays in the loop (calls may read it). When nothing
|
|
3825
|
+
// in the loop can observe it, its double is written once on leaving the
|
|
3826
|
+
// copy instead, so the loop keeps it in a register as the double copy
|
|
3827
|
+
// would.
|
|
3828
|
+
const deferred = new Set();
|
|
3829
|
+
// Worker loops also leave through their cancellation poll.
|
|
3830
|
+
if (plan.observerFree && !this.mod.workers)
|
|
3831
|
+
for (const s of plan.writes) {
|
|
3832
|
+
const id = speculativeWriteTarget(s).localId;
|
|
3833
|
+
if (!this.currentLocals.has(id))
|
|
3834
|
+
deferred.add(id);
|
|
3835
|
+
}
|
|
3836
|
+
this.speculation = {
|
|
3837
|
+
writes: plan.writes,
|
|
3838
|
+
resume: new Map([...resume].map(([s, r]) => [s, r.label])),
|
|
3839
|
+
deferred,
|
|
3840
|
+
};
|
|
3841
|
+
try {
|
|
3842
|
+
if (deferred.size === 0)
|
|
3843
|
+
emitCopy(exit);
|
|
3844
|
+
else {
|
|
3845
|
+
const leave = B.newLabel("spec.leave");
|
|
3846
|
+
emitCopy(leave);
|
|
3847
|
+
B.startBlock(leave);
|
|
3848
|
+
this.writeBackSpeculativeGlobals(deferred);
|
|
3849
|
+
B.br(exit);
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
finally {
|
|
3853
|
+
this.speculation = null;
|
|
3854
|
+
for (const id of plan.locals.keys())
|
|
3855
|
+
this.integerLoopBindings.delete(id);
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
writeBackSpeculativeGlobals(deferred) {
|
|
3859
|
+
const B = this.B;
|
|
3860
|
+
for (const id of deferred) {
|
|
3861
|
+
const integer = B.tmp(), number = B.tmp();
|
|
3862
|
+
B.line(`${integer} = load i64, ptr ${this.integerLoopBindings.get(id).slot}`);
|
|
3863
|
+
B.line(`${number} = sitofp i64 ${integer} to double`);
|
|
3864
|
+
B.line(`store double ${number}, ptr ${this.binding(id).slot}`);
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
/** A number local whose only storage is its own double slot, so an i64
|
|
3868
|
+
* shadow beside it can stand for it within one loop. */
|
|
3869
|
+
speculativeLocal(id) {
|
|
3870
|
+
const local = this.currentLocals.get(id);
|
|
3871
|
+
if (!local)
|
|
3872
|
+
return this.speculativeGlobal(id);
|
|
3873
|
+
if (local.type.kind !== "f64" || local.boxed || local.tdz)
|
|
3874
|
+
return false;
|
|
3875
|
+
return !(this.captureIds.has(id) ||
|
|
3876
|
+
this.int32Locals.has(id) ||
|
|
3877
|
+
this.integerLoopBindings.has(id) ||
|
|
3878
|
+
this.integerViews.has(id) ||
|
|
3879
|
+
this.localUnionStorage.has(id) ||
|
|
3880
|
+
this.reboundOwnerSlots.has(id) ||
|
|
3881
|
+
this.lazyCaptureBoxes.has(id) ||
|
|
3882
|
+
this.privateSplitLocals.has(id));
|
|
3883
|
+
}
|
|
3884
|
+
/** A module-level number written only by the file's top-level code that
|
|
3885
|
+
* is emitting this loop. Calls may still read it: the fast copy keeps its
|
|
3886
|
+
* double current. An init function runs once and is never re-entered, so
|
|
3887
|
+
* no call from the loop can write the global behind the shadow. */
|
|
3888
|
+
speculativeGlobal(id) {
|
|
3889
|
+
if (!this.speculativeFunction.startsWith("%init."))
|
|
3890
|
+
return false;
|
|
3891
|
+
const writers = this.numberGlobalWriters(id);
|
|
3892
|
+
return (writers !== null &&
|
|
3893
|
+
(writers.size === 0 || (writers.size === 1 && writers.has(this.speculativeFunction))));
|
|
3894
|
+
}
|
|
3895
|
+
/** A module-level number a loop of any function only reads, written by
|
|
3896
|
+
* nothing but top-level code: an init function never runs during a loop
|
|
3897
|
+
* of another function, so the entry value stays its value. */
|
|
3898
|
+
speculativeInvariantGlobal(id) {
|
|
3899
|
+
const writers = this.numberGlobalWriters(id);
|
|
3900
|
+
return writers !== null && [...writers].every((name) => name.startsWith("%init."));
|
|
3901
|
+
}
|
|
3902
|
+
/** The functions writing a number global a shadow may stand for, or null
|
|
3903
|
+
* when the global is not such a number. */
|
|
3904
|
+
numberGlobalWriters(id) {
|
|
3905
|
+
if (this.globalTypes.get(id)?.kind !== "f64")
|
|
3906
|
+
return null;
|
|
3907
|
+
if (this.tdzGlobals.has(id) || this.integerViews.has(id))
|
|
3908
|
+
return null;
|
|
3909
|
+
if (!this.globalWriters) {
|
|
3910
|
+
const writers = new Map();
|
|
3911
|
+
for (const fn of this.mod.functions) {
|
|
3912
|
+
const note = (localId) => {
|
|
3913
|
+
if (!localId.startsWith("%g."))
|
|
3914
|
+
return;
|
|
3915
|
+
const set = writers.get(localId) ?? new Set();
|
|
3916
|
+
set.add(fn.name);
|
|
3917
|
+
writers.set(localId, set);
|
|
3918
|
+
};
|
|
3919
|
+
everyStmtList(fn.body, {
|
|
3920
|
+
stmt: (s) => {
|
|
3921
|
+
if (s.kind === "assign" || s.kind === "varDecl")
|
|
3922
|
+
note(s.localId);
|
|
3923
|
+
return true;
|
|
3924
|
+
},
|
|
3925
|
+
expr: (e) => {
|
|
3926
|
+
if (e.kind === "assignExpr" || e.kind === "incDec")
|
|
3927
|
+
note(e.localId);
|
|
3928
|
+
return true;
|
|
3929
|
+
},
|
|
3930
|
+
});
|
|
3931
|
+
}
|
|
3932
|
+
this.globalWriters = writers;
|
|
3933
|
+
}
|
|
3934
|
+
return this.globalWriters.get(id) ?? new Set();
|
|
3935
|
+
}
|
|
3936
|
+
/** A speculated write in the fast copy: compute the value as a safe
|
|
3937
|
+
* integer, store it to the shadow and its exact double to the local.
|
|
3938
|
+
* Any failed check re-executes the statement in the ordinary copy. */
|
|
3939
|
+
emitSpeculativeWrite(s, resume) {
|
|
3940
|
+
const B = this.B;
|
|
3941
|
+
const target = speculativeWriteTarget(s);
|
|
3942
|
+
const binding = this.integerLoopBindings.get(target.localId);
|
|
3943
|
+
const failed = B.newLabel("spec.bail");
|
|
3944
|
+
let bails = false;
|
|
3945
|
+
this.declare("declare i1 @llvm.expect.i1(i1, i1)");
|
|
3946
|
+
const bail = (condition) => {
|
|
3947
|
+
if (condition === "false")
|
|
3948
|
+
return;
|
|
3949
|
+
bails = true;
|
|
3950
|
+
const expected = B.tmp();
|
|
3951
|
+
B.line(`${expected} = call i1 @llvm.expect.i1(i1 ${condition}, i1 false)`);
|
|
3952
|
+
const next = B.newLabel("spec.ok");
|
|
3953
|
+
B.condBr(expected, failed, next);
|
|
3954
|
+
B.startBlock(next);
|
|
3955
|
+
};
|
|
3956
|
+
// Heap reads come first, before any check, and release their temporaries
|
|
3957
|
+
// at once: a bailing path then owns nothing, and reads have no effect
|
|
3958
|
+
// whose reordering before the arithmetic could be observed.
|
|
3959
|
+
const reads = new Map();
|
|
3960
|
+
if (target.value !== null) {
|
|
3961
|
+
this.frames.push([]);
|
|
3962
|
+
for (const read of speculativeReads(target.value))
|
|
3963
|
+
reads.set(read, this.emitExpr(read));
|
|
3964
|
+
this.releaseFrame(this.frames.pop());
|
|
3965
|
+
}
|
|
3966
|
+
this.frames.push([]);
|
|
3967
|
+
let result;
|
|
3968
|
+
if (target.value === null) {
|
|
3969
|
+
const old = B.tmp();
|
|
3970
|
+
result = B.tmp();
|
|
3971
|
+
B.line(`${old} = load i64, ptr ${binding.slot}`);
|
|
3972
|
+
B.line(`${result} = ${target.op === "+" ? "add" : "sub"} nsw i64 ${old}, 1`);
|
|
3973
|
+
const biased = B.tmp(), outside = B.tmp();
|
|
3974
|
+
B.line(`${biased} = add i64 ${result}, ${Number.MAX_SAFE_INTEGER}`);
|
|
3975
|
+
B.line(`${outside} = icmp ugt i64 ${biased}, ${2 * Number.MAX_SAFE_INTEGER}`);
|
|
3976
|
+
bail(outside);
|
|
3977
|
+
}
|
|
3978
|
+
else
|
|
3979
|
+
result = emitSpeculativeInteger(this, target.value, bail, reads);
|
|
3980
|
+
// Speculable values read only numbers past this point.
|
|
3981
|
+
if (this.frames.pop().length > 0)
|
|
3982
|
+
throw new InternalCompilerError("llvm emitter bug: speculative write owns a temporary");
|
|
3983
|
+
B.line(`store i64 ${result}, ptr ${binding.slot}`);
|
|
3984
|
+
const deferred = this.speculation.deferred;
|
|
3985
|
+
if (!deferred.has(target.localId)) {
|
|
3986
|
+
const number = B.tmp();
|
|
3987
|
+
B.line(`${number} = sitofp i64 ${result} to double`);
|
|
3988
|
+
B.line(`store double ${number}, ptr ${this.binding(target.localId).slot}`);
|
|
3989
|
+
}
|
|
3990
|
+
if (!bails)
|
|
3991
|
+
return;
|
|
3992
|
+
const done = B.newLabel("spec.done");
|
|
3993
|
+
B.br(done);
|
|
3994
|
+
B.startBlock(failed);
|
|
3995
|
+
this.writeBackSpeculativeGlobals(deferred);
|
|
3996
|
+
B.br(resume);
|
|
3997
|
+
B.startBlock(done);
|
|
3998
|
+
}
|
|
3619
3999
|
/** The amortized cancellation poll, emitted on the loop's back edge (the
|
|
3620
4000
|
* continue target), after the body. A poll in the header would be a loop
|
|
3621
4001
|
* exit ahead of the body; LLVM then cannot rotate the loop, so loads that
|
|
@@ -4054,6 +4434,14 @@ export class LlEmitter {
|
|
|
4054
4434
|
this.B.line(`${v} = load ptr, ptr ${p}`);
|
|
4055
4435
|
return v;
|
|
4056
4436
|
}
|
|
4437
|
+
/** The pointer to ScrMap.flags of an index-signature record's overflow
|
|
4438
|
+
* map (byte offset sizeof(size_t) + 3; SCR_MAP_NULL_PROTO is bit 0). */
|
|
4439
|
+
recordMapFlagsPtr(objName, shapeId) {
|
|
4440
|
+
const ovf = this.recordOvfPtr(objName, shapeId);
|
|
4441
|
+
const p = this.B.tmp();
|
|
4442
|
+
this.B.line(`${p} = getelementptr inbounds i8, ptr ${ovf}, i64 ${mapFlagsOffset(this.sizeType)}`);
|
|
4443
|
+
return p;
|
|
4444
|
+
}
|
|
4057
4445
|
/** Loads a record field (i8-stored bools trunc to i1). An inline union
|
|
4058
4446
|
* field answers a private stack box with the slot's own bits, ABSENT
|
|
4059
4447
|
* included: it may only be borrowed. */
|
|
@@ -4172,7 +4560,11 @@ export class LlEmitter {
|
|
|
4172
4560
|
const g = this.globalTypes.get(id);
|
|
4173
4561
|
if (!g)
|
|
4174
4562
|
throw new InternalCompilerError(`llvm emitter bug: unknown binding ${id}`);
|
|
4175
|
-
return {
|
|
4563
|
+
return {
|
|
4564
|
+
kind: "global",
|
|
4565
|
+
slot: this.privateGlobalSlots.get(id) ?? `@${mangleGlobal(id)}`,
|
|
4566
|
+
type: g,
|
|
4567
|
+
};
|
|
4176
4568
|
}
|
|
4177
4569
|
emitDebugLocal(local, slot, arg = 0, captured = false) {
|
|
4178
4570
|
const debug = this.debug?.local(local, this.debugScope, arg, captured);
|
|
@@ -4435,6 +4827,55 @@ export class LlEmitter {
|
|
|
4435
4827
|
}
|
|
4436
4828
|
this.emitCoroSuspendPrepared();
|
|
4437
4829
|
}
|
|
4830
|
+
/** A native generator's OUT slot (scr_async.c's ScrGen.out, mirrored by
|
|
4831
|
+
* %ScrGenHead and %ScrGenSlot { kind, f64, b, payload, release_fn }). */
|
|
4832
|
+
genOutSlot(gen) {
|
|
4833
|
+
this.usesGenSlots = true;
|
|
4834
|
+
const slot = this.B.tmp();
|
|
4835
|
+
this.B.line(`${slot} = getelementptr inbounds %ScrGenHead, ptr ${gen}, i32 0, i32 4`);
|
|
4836
|
+
return slot;
|
|
4837
|
+
}
|
|
4838
|
+
/** A yield on a number or boolean channel: scr_gen_slot_f64/bool inline.
|
|
4839
|
+
* Such a body's OUT never holds a reference at a yield (only its final
|
|
4840
|
+
* completion can park one), so the slot needs no release first. */
|
|
4841
|
+
emitGenSlotStore(slot, kind, value) {
|
|
4842
|
+
const B = this.B;
|
|
4843
|
+
const field = B.tmp();
|
|
4844
|
+
if (kind === "f64") {
|
|
4845
|
+
B.line(`store i32 ${SCR_EXC_F64}, ptr ${slot}`);
|
|
4846
|
+
B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 1`);
|
|
4847
|
+
B.line(`store double ${value}, ptr ${field}`);
|
|
4848
|
+
}
|
|
4849
|
+
else {
|
|
4850
|
+
const byte = B.tmp();
|
|
4851
|
+
B.line(`store i32 ${SCR_EXC_BOOL}, ptr ${slot}`);
|
|
4852
|
+
B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 2`);
|
|
4853
|
+
B.line(`${byte} = zext i1 ${value} to i8`);
|
|
4854
|
+
B.line(`store i8 ${byte}, ptr ${field}`);
|
|
4855
|
+
}
|
|
4856
|
+
}
|
|
4857
|
+
/** scr_gen_take_out_f64/bool inline: the slot's value when it holds that
|
|
4858
|
+
* kind (otherwise 0 or false), leaving the slot empty. */
|
|
4859
|
+
emitGenSlotTake(slot, kind) {
|
|
4860
|
+
const B = this.B;
|
|
4861
|
+
const tag = B.tmp(), matches = B.tmp(), field = B.tmp(), raw = B.tmp(), out = B.tmp();
|
|
4862
|
+
B.line(`${tag} = load i32, ptr ${slot}`);
|
|
4863
|
+
B.line(`${matches} = icmp eq i32 ${tag}, ${kind === "f64" ? SCR_EXC_F64 : SCR_EXC_BOOL}`);
|
|
4864
|
+
if (kind === "f64") {
|
|
4865
|
+
B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 1`);
|
|
4866
|
+
B.line(`${raw} = load double, ptr ${field}`);
|
|
4867
|
+
B.line(`${out} = select i1 ${matches}, double ${raw}, double 0.0`);
|
|
4868
|
+
}
|
|
4869
|
+
else {
|
|
4870
|
+
const bit = B.tmp();
|
|
4871
|
+
B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 2`);
|
|
4872
|
+
B.line(`${raw} = load i8, ptr ${field}`);
|
|
4873
|
+
B.line(`${bit} = trunc i8 ${raw} to i1`);
|
|
4874
|
+
B.line(`${out} = select i1 ${matches}, i1 ${bit}, i1 false`);
|
|
4875
|
+
}
|
|
4876
|
+
B.line(`store i32 0, ptr ${slot}`);
|
|
4877
|
+
return out;
|
|
4878
|
+
}
|
|
4438
4879
|
/** Suspend after a target-specific runtime helper already queued the
|
|
4439
4880
|
* continuation (used by module awaits, which skip the hop when settled). */
|
|
4440
4881
|
emitCoroSuspendPrepared() {
|
|
@@ -4509,9 +4950,25 @@ export class LlEmitter {
|
|
|
4509
4950
|
}
|
|
4510
4951
|
}
|
|
4511
4952
|
}
|
|
4953
|
+
/** Number locals declared as a copy of a capture or of a lazily boxed
|
|
4954
|
+
* parameter, at the top of the body. */
|
|
4955
|
+
doubleCopies(fn) {
|
|
4956
|
+
const copies = [];
|
|
4957
|
+
for (const s of fn.body) {
|
|
4958
|
+
if (s.kind !== "varDecl")
|
|
4959
|
+
break;
|
|
4960
|
+
if (s.init?.kind !== "varRef" || s.init.type.kind !== "f64")
|
|
4961
|
+
continue;
|
|
4962
|
+
const source = s.init.localId;
|
|
4963
|
+
if (this.captureIds.has(source) || this.lazyCaptureBoxes.has(source))
|
|
4964
|
+
copies.push(s.localId);
|
|
4965
|
+
}
|
|
4966
|
+
return copies;
|
|
4967
|
+
}
|
|
4512
4968
|
emitFunction(fn) {
|
|
4513
4969
|
const B = new BlockBuilder();
|
|
4514
4970
|
this.B = B;
|
|
4971
|
+
this.currentFunctionName = fn.name;
|
|
4515
4972
|
// Fiber switches restore the active exception cell before control
|
|
4516
4973
|
// returns to a synchronous frame (emitPendingCheck).
|
|
4517
4974
|
B.excCellInvariant = fn.async !== true && fn.generator === undefined;
|
|
@@ -4564,11 +5021,20 @@ export class LlEmitter {
|
|
|
4564
5021
|
!this.reboundByFunction.get(fn.name)?.has(fn.params[index].localId))
|
|
4565
5022
|
this.borrowedParameters.add(fn.params[index].localId);
|
|
4566
5023
|
}
|
|
5024
|
+
// A generator frame owns its unchanged parameters across suspensions.
|
|
5025
|
+
const generatorParameters = this.callLifetimes.generatorParameters.get(fn.name);
|
|
5026
|
+
if (generatorParameters !== undefined)
|
|
5027
|
+
for (const id of generatorParameters)
|
|
5028
|
+
this.borrowedParameters.add(id);
|
|
4567
5029
|
this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
|
|
4568
5030
|
this.stableCaptureIds =
|
|
4569
5031
|
fn.captures !== undefined ? this.lazyCaptures.stableCaptures(fn.name) : new Set();
|
|
4570
5032
|
this.integerLoopBindings.clear();
|
|
4571
5033
|
this.countedLoopsEnabled = this.debug === null && !fn.async && !fn.generator;
|
|
5034
|
+
this.speculativeLoopsEnabled = this.countedLoopsEnabled;
|
|
5035
|
+
this.speculativeFunction = fn.name;
|
|
5036
|
+
this.speculation = null;
|
|
5037
|
+
this.speculationResume = null;
|
|
4572
5038
|
this.finalFieldBorrows =
|
|
4573
5039
|
!fn.async && !fn.generator && constructorClass(fn.name) === null && this.finalFields.size > 0;
|
|
4574
5040
|
this.byteWindowsEnabled = this.countedLoopsEnabled;
|
|
@@ -4589,6 +5055,7 @@ export class LlEmitter {
|
|
|
4589
5055
|
this.debug === null ? findScalarStringSlices(fn, this.callLifetimes) : new Map();
|
|
4590
5056
|
this.mapReadLifetimes = findMapReadLifetimes(fn, this.boxedUnionsById, this.callLifetimes);
|
|
4591
5057
|
this.readNumberMapWrites = findReadNumberMapWrites(fn);
|
|
5058
|
+
this.mapEntryFusion = this.debug === null ? findMapEntryFusions(fn) : null;
|
|
4592
5059
|
this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.boxedUnionsById);
|
|
4593
5060
|
this.localStackObjects = this.stackObjects
|
|
4594
5061
|
? findLocalStackObjects(this, fn, this.callLifetimes)
|
|
@@ -4604,12 +5071,30 @@ export class LlEmitter {
|
|
|
4604
5071
|
this.bytesBounds = findBytesBounds(numericFn, this.integerRanges);
|
|
4605
5072
|
// Debug builds keep every number local a double for the debugger's
|
|
4606
5073
|
// variable view. Lazily boxed captured parameters keep their double,
|
|
4607
|
-
// which the box is created from
|
|
5074
|
+
// which the box is created from, and so do plain copies of a capture or
|
|
5075
|
+
// such a parameter (copyStableCaptures): the source already holds the
|
|
5076
|
+
// double, which an i32 copy would convert and widen again on every call.
|
|
5077
|
+
// Initializer bindings proven int32 move into an i32 local of their
|
|
5078
|
+
// initializer: no other code observes the global, so the loop-carried
|
|
5079
|
+
// value never round-trips through the double slot.
|
|
5080
|
+
const initializerLocals = initializerLocalCandidates(initializerBindings);
|
|
4608
5081
|
const int32Locals = this.debug === null
|
|
4609
|
-
? findInt32Locals(fn, this.integerRanges, this.int32Slots.facts(fn.name), new Set(this.lazyCaptureBoxes.keys()))
|
|
5082
|
+
? findInt32Locals(withInitializerLocals(fn, initializerLocals), this.integerRanges, this.int32Slots.facts(fn.name), new Set([...this.lazyCaptureBoxes.keys(), ...this.doubleCopies(fn)]))
|
|
4610
5083
|
: null;
|
|
4611
5084
|
this.int32Locals = int32Locals?.locals ?? new Map();
|
|
4612
5085
|
this.int32LocalWrites = int32Locals?.values ?? new Set();
|
|
5086
|
+
const localizedGlobals = new Set();
|
|
5087
|
+
for (const global of initializerLocals) {
|
|
5088
|
+
if (!this.int32Locals.has(global.id))
|
|
5089
|
+
continue;
|
|
5090
|
+
localizedGlobals.add(global.id);
|
|
5091
|
+
this.currentLocals.set(global.id, {
|
|
5092
|
+
id: global.id,
|
|
5093
|
+
name: global.name,
|
|
5094
|
+
type: global.type,
|
|
5095
|
+
mutable: global.mutable,
|
|
5096
|
+
});
|
|
5097
|
+
}
|
|
4613
5098
|
this.chainSlots.clear();
|
|
4614
5099
|
this.finallyStack = [];
|
|
4615
5100
|
this.tryStack = [];
|
|
@@ -4651,6 +5136,18 @@ export class LlEmitter {
|
|
|
4651
5136
|
B.line(`${handle} = call ptr @llvm.coro.begin(token ${id}, ptr ${mem})`);
|
|
4652
5137
|
B.line(`call void @${started}(ptr ${handle})`);
|
|
4653
5138
|
B.line(`${self} = call ptr @scr_fiber_self()`);
|
|
5139
|
+
// A native synchronous body owns its generator for the whole run (the
|
|
5140
|
+
// ScrGen owns the frame), so its OUT slot address is fixed.
|
|
5141
|
+
let out = null;
|
|
5142
|
+
const yieldKind = fn.generator?.yieldT.kind;
|
|
5143
|
+
if (!this.wasi &&
|
|
5144
|
+
fn.async !== true &&
|
|
5145
|
+
(yieldKind === "f64" || yieldKind === "date" || yieldKind === "bool")) {
|
|
5146
|
+
this.declare(`declare ptr @scr_gen_of_fiber(ptr)`);
|
|
5147
|
+
const gen = B.tmp();
|
|
5148
|
+
B.line(`${gen} = call ptr @scr_gen_of_fiber(ptr ${self})`);
|
|
5149
|
+
out = this.genOutSlot(gen);
|
|
5150
|
+
}
|
|
4654
5151
|
this.currentCoro = {
|
|
4655
5152
|
kind: fn.generator !== undefined ? "generator" : "async",
|
|
4656
5153
|
id,
|
|
@@ -4659,6 +5156,7 @@ export class LlEmitter {
|
|
|
4659
5156
|
finalLabel: B.newLabel("coro.final"),
|
|
4660
5157
|
cleanupLabel: B.newLabel("coro.cleanup"),
|
|
4661
5158
|
suspendLabel: B.newLabel("coro.suspend"),
|
|
5159
|
+
out,
|
|
4662
5160
|
};
|
|
4663
5161
|
}
|
|
4664
5162
|
const paramIds = new Set(fn.params.map((p) => p.localId));
|
|
@@ -4679,6 +5177,32 @@ export class LlEmitter {
|
|
|
4679
5177
|
B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`);
|
|
4680
5178
|
this.storeIntegerView(global.id, { name: value, type: global.type });
|
|
4681
5179
|
}
|
|
5180
|
+
// A number global only its guarded, synchronous initializer reads or
|
|
5181
|
+
// writes (findInitializerBindings) is invisible to every other function,
|
|
5182
|
+
// and reentry returns before reaching it, so a local slot can stand in
|
|
5183
|
+
// for it: LLVM keeps it in a register across calls and loops that would
|
|
5184
|
+
// otherwise force a store and reload per iteration. The global's
|
|
5185
|
+
// storage is never read again, so nothing writes the slot back. Bindings
|
|
5186
|
+
// proven int32 already live in an i32 local (localizedGlobals above);
|
|
5187
|
+
// this covers the remaining doubles.
|
|
5188
|
+
this.privateGlobalSlots.clear();
|
|
5189
|
+
if (this.debug === null && this.currentCoro === null && !fn.async && !fn.generator) {
|
|
5190
|
+
const conditional = initializerLocals.length ? conditionalLoopWrites(fn.body) : null;
|
|
5191
|
+
for (const global of initializerLocals) {
|
|
5192
|
+
if (localizedGlobals.has(global.id))
|
|
5193
|
+
continue;
|
|
5194
|
+
// A conditional update in a loop stays a memory update (see
|
|
5195
|
+
// conditionalLoopWrites).
|
|
5196
|
+
if (conditional?.has(global.id))
|
|
5197
|
+
continue;
|
|
5198
|
+
const slot = B.slot();
|
|
5199
|
+
const value = B.tmp();
|
|
5200
|
+
B.entryAllocas.push(`${slot} = alloca double ; module binding ${global.name}`);
|
|
5201
|
+
B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`);
|
|
5202
|
+
B.line(`store double ${value}, ptr ${slot}`);
|
|
5203
|
+
this.privateGlobalSlots.set(global.id, slot);
|
|
5204
|
+
}
|
|
5205
|
+
}
|
|
4682
5206
|
for (const local of this.currentLocals.values()) {
|
|
4683
5207
|
// Boxed locals' slots hold their capture BOX (a ptr); captured
|
|
4684
5208
|
// (env-borrowed) locals bind the incoming box below. A caught-typed
|
|
@@ -4691,6 +5215,9 @@ export class LlEmitter {
|
|
|
4691
5215
|
: this.llType(local.type);
|
|
4692
5216
|
B.entryAllocas.push(`%${mangleLocal(local.id)} = alloca ${slotTy} ; ${local.name}`);
|
|
4693
5217
|
this.emitDebugLocal(local, `%${mangleLocal(local.id)}`, fn.params.findIndex((p) => p.localId === local.id) + 1, this.captureIds.has(local.id));
|
|
5218
|
+
// A localized initializer binding starts at its global's zero value.
|
|
5219
|
+
if (localizedGlobals.has(local.id))
|
|
5220
|
+
B.line(`store i32 0, ptr %${mangleLocal(local.id)}`);
|
|
4694
5221
|
// Refcounted/boxed locals start NULL (the C prologue's `= NULL`):
|
|
4695
5222
|
// scope-exit releases run whether or not an assign ever did.
|
|
4696
5223
|
if (paramIds.has(local.id) || this.captureIds.has(local.id))
|
|
@@ -4910,9 +5437,17 @@ export class LlEmitter {
|
|
|
4910
5437
|
attrs = `inlinehint ${attrs}`;
|
|
4911
5438
|
const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`;
|
|
4912
5439
|
const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
|
|
5440
|
+
this.privateGlobalSlots.clear();
|
|
4913
5441
|
let body = `define internal ${ret} @${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
|
|
4914
5442
|
if (int32Abi)
|
|
4915
5443
|
body += "\n" + this.emitInt32AbiWrapper(fn, symbol, int32Abi);
|
|
5444
|
+
if (this.integerClones.members.has(fn.name)) {
|
|
5445
|
+
body += "\n" + emitIntegerClone(fn, this, this.stackChecks?.has(fn.name) === true);
|
|
5446
|
+
if (this.integerClones.dispatched.has(fn.name))
|
|
5447
|
+
body +=
|
|
5448
|
+
"\n" +
|
|
5449
|
+
emitIntegerDispatcher(fn, this, `${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}`, int32Abi?.params ?? [], int32Abi?.result === true);
|
|
5450
|
+
}
|
|
4916
5451
|
if (!borrowed)
|
|
4917
5452
|
return body;
|
|
4918
5453
|
const slot = this.implSlotBorrowed(fn.name);
|
|
@@ -5477,6 +6012,17 @@ export class LlEmitter {
|
|
|
5477
6012
|
this.releaseScope(scope);
|
|
5478
6013
|
}
|
|
5479
6014
|
emitStmt(s) {
|
|
6015
|
+
const resume = this.speculationResume?.get(s);
|
|
6016
|
+
if (resume && !resume.placed && !this.B.isTerminated()) {
|
|
6017
|
+
// The fast copy's failed checks re-execute this statement here.
|
|
6018
|
+
resume.placed = true;
|
|
6019
|
+
this.B.br(resume.label);
|
|
6020
|
+
this.B.startBlock(resume.label);
|
|
6021
|
+
}
|
|
6022
|
+
if (this.speculation?.writes.has(s)) {
|
|
6023
|
+
this.emitSpeculativeWrite(s, this.speculation.resume.get(s));
|
|
6024
|
+
return;
|
|
6025
|
+
}
|
|
5480
6026
|
const previous = this.B.debugLocation;
|
|
5481
6027
|
this.B.debugLocation = this.debug?.location(s.loc, this.debugScope) ?? null;
|
|
5482
6028
|
const loop = s.kind === "while" || s.kind === "doWhile" || s.kind === "for" || s.kind === "forOf";
|
|
@@ -5779,6 +6325,8 @@ export class LlEmitter {
|
|
|
5779
6325
|
// the replaced element itself). A local receiver that the index and
|
|
5780
6326
|
// value cannot rebind is read in place, as for field stores; the
|
|
5781
6327
|
// released element cannot reach that binding.
|
|
6328
|
+
if (this.emitArraySelfConcat(s))
|
|
6329
|
+
break;
|
|
5782
6330
|
const arr = this.emitStableReceiver(s.arr, [s.index, s.value]);
|
|
5783
6331
|
const idx = this.emitExpr(s.index);
|
|
5784
6332
|
const v = this.emitExpr(s.value);
|
|
@@ -5813,7 +6361,7 @@ export class LlEmitter {
|
|
|
5813
6361
|
const resize = B.newLabel("arr.len.set"), done = B.newLabel("arr.len.done");
|
|
5814
6362
|
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
5815
6363
|
this.markMemoryPointer(lenPtr, "array:header");
|
|
5816
|
-
B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
6364
|
+
B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}${this.arrayLengthRange}`);
|
|
5817
6365
|
B.line(`${lenNumber} = ${sizeToDouble(this.sizeType)} ${this.sizeType} ${len} to double`);
|
|
5818
6366
|
B.line(`${same} = fcmp oeq double ${length.name}, ${lenNumber}`);
|
|
5819
6367
|
B.condBr(same, done, resize);
|
|
@@ -5877,6 +6425,8 @@ export class LlEmitter {
|
|
|
5877
6425
|
this.storeInt32Field(ptr, v, s.value);
|
|
5878
6426
|
break;
|
|
5879
6427
|
}
|
|
6428
|
+
if (this.emitFieldSelfConcat(s))
|
|
6429
|
+
break;
|
|
5880
6430
|
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
5881
6431
|
const v = this.emitFieldValue(s.value, s.kind === "recordSet"
|
|
5882
6432
|
? this.recordFieldInline(s.shapeId, s.field)
|
|
@@ -6103,57 +6653,61 @@ export class LlEmitter {
|
|
|
6103
6653
|
}
|
|
6104
6654
|
case "while": {
|
|
6105
6655
|
const workerBudget = this.workerLoopBudget(s);
|
|
6106
|
-
const lc = B.newLabel("loop.c");
|
|
6107
|
-
const lb = B.newLabel("loop.b");
|
|
6108
6656
|
const le = B.newLabel("loop.e");
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
B.startBlock(lb);
|
|
6114
|
-
this.jumpTargets.push({
|
|
6115
|
-
kind: "loop",
|
|
6116
|
-
brkLabel: le,
|
|
6117
|
-
contLabel: lp,
|
|
6118
|
-
labels: s.labels,
|
|
6119
|
-
frameDepth: this.frames.length,
|
|
6120
|
-
scopeDepth: this.scopes.length,
|
|
6121
|
-
finallyDepth: this.finallyStack.length,
|
|
6122
|
-
});
|
|
6123
|
-
this.emitBlock(s.body);
|
|
6124
|
-
this.jumpTargets.pop();
|
|
6125
|
-
B.br(lp);
|
|
6126
|
-
if (lp !== lc) {
|
|
6127
|
-
B.startBlock(lp);
|
|
6128
|
-
this.emitWorkerLoopCheck(workerBudget);
|
|
6657
|
+
this.emitLoopCopies(s, true, le, (exit) => {
|
|
6658
|
+
const lc = B.newLabel("loop.c");
|
|
6659
|
+
const lb = B.newLabel("loop.b");
|
|
6660
|
+
const lp = workerBudget !== null ? B.newLabel("loop.p") : lc;
|
|
6129
6661
|
B.br(lc);
|
|
6130
|
-
|
|
6662
|
+
B.startBlock(lc);
|
|
6663
|
+
B.condBr(this.emitCondition(s.cond), lb, exit);
|
|
6664
|
+
B.startBlock(lb);
|
|
6665
|
+
this.jumpTargets.push({
|
|
6666
|
+
kind: "loop",
|
|
6667
|
+
brkLabel: exit,
|
|
6668
|
+
contLabel: lp,
|
|
6669
|
+
labels: s.labels,
|
|
6670
|
+
frameDepth: this.frames.length,
|
|
6671
|
+
scopeDepth: this.scopes.length,
|
|
6672
|
+
finallyDepth: this.finallyStack.length,
|
|
6673
|
+
});
|
|
6674
|
+
this.emitBlock(s.body);
|
|
6675
|
+
this.jumpTargets.pop();
|
|
6676
|
+
B.br(lp);
|
|
6677
|
+
if (lp !== lc) {
|
|
6678
|
+
B.startBlock(lp);
|
|
6679
|
+
this.emitWorkerLoopCheck(workerBudget);
|
|
6680
|
+
B.br(lc);
|
|
6681
|
+
}
|
|
6682
|
+
});
|
|
6131
6683
|
B.startBlock(le);
|
|
6132
6684
|
break;
|
|
6133
6685
|
}
|
|
6134
6686
|
case "doWhile": {
|
|
6135
6687
|
const workerBudget = this.workerLoopBudget(s);
|
|
6136
6688
|
// Body first (runs at least once); continue jumps to the CONDITION.
|
|
6137
|
-
const lb = B.newLabel("loop.b");
|
|
6138
|
-
const lc = B.newLabel("loop.c");
|
|
6139
6689
|
const le = B.newLabel("loop.e");
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6690
|
+
this.emitLoopCopies(s, true, le, (exit) => {
|
|
6691
|
+
const lb = B.newLabel("loop.b");
|
|
6692
|
+
const lc = B.newLabel("loop.c");
|
|
6693
|
+
B.br(lb);
|
|
6694
|
+
B.startBlock(lb);
|
|
6695
|
+
this.jumpTargets.push({
|
|
6696
|
+
kind: "loop",
|
|
6697
|
+
brkLabel: exit,
|
|
6698
|
+
contLabel: lc,
|
|
6699
|
+
labels: s.labels,
|
|
6700
|
+
frameDepth: this.frames.length,
|
|
6701
|
+
scopeDepth: this.scopes.length,
|
|
6702
|
+
finallyDepth: this.finallyStack.length,
|
|
6703
|
+
});
|
|
6704
|
+
this.emitBlock(s.body);
|
|
6705
|
+
this.jumpTargets.pop();
|
|
6706
|
+
B.br(lc);
|
|
6707
|
+
B.startBlock(lc);
|
|
6708
|
+
this.emitWorkerLoopCheck(workerBudget);
|
|
6709
|
+
B.condBr(this.emitCondition(s.cond), lb, exit);
|
|
6150
6710
|
});
|
|
6151
|
-
this.emitBlock(s.body);
|
|
6152
|
-
this.jumpTargets.pop();
|
|
6153
|
-
B.br(lc);
|
|
6154
|
-
B.startBlock(lc);
|
|
6155
|
-
this.emitWorkerLoopCheck(workerBudget);
|
|
6156
|
-
B.condBr(this.emitCondition(s.cond), lb, le);
|
|
6157
6711
|
B.startBlock(le);
|
|
6158
6712
|
break;
|
|
6159
6713
|
}
|
|
@@ -6285,8 +6839,6 @@ export class LlEmitter {
|
|
|
6285
6839
|
for (const initializer of initializers)
|
|
6286
6840
|
this.emitStmt(initializer);
|
|
6287
6841
|
}
|
|
6288
|
-
const lc = B.newLabel("loop.c");
|
|
6289
|
-
const lb = B.newLabel("loop.b");
|
|
6290
6842
|
const le = B.newLabel("loop.e");
|
|
6291
6843
|
// JS `for (let i ...)`: each iteration gets a FRESH binding holding
|
|
6292
6844
|
// a copy of the previous one (closures made in iteration k keep
|
|
@@ -6313,70 +6865,76 @@ export class LlEmitter {
|
|
|
6313
6865
|
}
|
|
6314
6866
|
};
|
|
6315
6867
|
const workerBudget = this.workerLoopBudget(s);
|
|
6316
|
-
const lu = s.update || capturedLets.length > 0 || workerBudget !== null ? B.newLabel("loop.u") : lc;
|
|
6317
6868
|
freshenBindings();
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
const
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
B.line(`${lenPtr} = getelementptr inbounds %${integerArrayLoop ? "ScrArr" : "ScrBytes"}, ptr ${receiver.name}, i64 0, i32 1`);
|
|
6327
|
-
if (integerArrayLoop)
|
|
6328
|
-
this.markMemoryPointer(lenPtr, "array:header");
|
|
6329
|
-
B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
6330
|
-
B.line(`${index} = load ${this.sizeType}, ptr ${integerSlot}`);
|
|
6331
|
-
B.line(`${inBounds} = icmp ult ${this.sizeType} ${index}, ${len}`);
|
|
6332
|
-
B.condBr(inBounds, lb, le);
|
|
6333
|
-
}
|
|
6334
|
-
else if (countedLoop && integerSlot && countedLimit) {
|
|
6335
|
-
const index = B.tmp();
|
|
6336
|
-
const inBounds = B.tmp();
|
|
6337
|
-
B.line(`${index} = load i64, ptr ${integerSlot}`);
|
|
6338
|
-
B.line(`${inBounds} = icmp ${countedLoop.step > 0 ? "slt" : "sgt"} i64 ${index}, ${countedLimit}`);
|
|
6339
|
-
B.condBr(inBounds, lb, le);
|
|
6340
|
-
}
|
|
6341
|
-
else if (s.cond)
|
|
6342
|
-
B.condBr(this.emitCondition(s.cond), lb, le);
|
|
6343
|
-
else
|
|
6344
|
-
B.br(lb);
|
|
6345
|
-
B.startBlock(lb);
|
|
6346
|
-
this.jumpTargets.push({
|
|
6347
|
-
kind: "loop",
|
|
6348
|
-
brkLabel: le,
|
|
6349
|
-
contLabel: lu,
|
|
6350
|
-
labels: s.labels,
|
|
6351
|
-
frameDepth: this.frames.length,
|
|
6352
|
-
scopeDepth: this.scopes.length,
|
|
6353
|
-
finallyDepth: this.finallyStack.length,
|
|
6354
|
-
});
|
|
6355
|
-
this.emitBlock(s.body);
|
|
6356
|
-
this.jumpTargets.pop();
|
|
6357
|
-
B.br(lu);
|
|
6358
|
-
if (lu !== lc) {
|
|
6359
|
-
B.startBlock(lu);
|
|
6360
|
-
this.emitWorkerLoopCheck(workerBudget);
|
|
6361
|
-
freshenBindings();
|
|
6869
|
+
this.emitLoopCopies(s, capturedLets.length === 0, le, (exit) => {
|
|
6870
|
+
const lc = B.newLabel("loop.c");
|
|
6871
|
+
const lb = B.newLabel("loop.b");
|
|
6872
|
+
const lu = s.update || capturedLets.length > 0 || workerBudget !== null
|
|
6873
|
+
? B.newLabel("loop.u")
|
|
6874
|
+
: lc;
|
|
6875
|
+
B.br(lc);
|
|
6876
|
+
B.startBlock(lc);
|
|
6362
6877
|
if (integerLoop && integerSlot) {
|
|
6363
|
-
const
|
|
6364
|
-
const
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
B.
|
|
6878
|
+
const receiver = this.emitStableReceiver(integerLoop.limitReceiver, []);
|
|
6879
|
+
const lenPtr = B.tmp();
|
|
6880
|
+
const len = B.tmp();
|
|
6881
|
+
const index = B.tmp();
|
|
6882
|
+
const inBounds = B.tmp();
|
|
6883
|
+
B.line(`${lenPtr} = getelementptr inbounds %${integerArrayLoop ? "ScrArr" : "ScrBytes"}, ptr ${receiver.name}, i64 0, i32 1`);
|
|
6884
|
+
if (integerArrayLoop)
|
|
6885
|
+
this.markMemoryPointer(lenPtr, "array:header");
|
|
6886
|
+
B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
6887
|
+
B.line(`${index} = load ${this.sizeType}, ptr ${integerSlot}`);
|
|
6888
|
+
B.line(`${inBounds} = icmp ult ${this.sizeType} ${index}, ${len}`);
|
|
6889
|
+
B.condBr(inBounds, lb, exit);
|
|
6368
6890
|
}
|
|
6369
|
-
else if (countedLoop && integerSlot) {
|
|
6370
|
-
const
|
|
6371
|
-
const
|
|
6372
|
-
B.line(`${
|
|
6373
|
-
B.line(`${
|
|
6374
|
-
B.
|
|
6891
|
+
else if (countedLoop && integerSlot && countedLimit) {
|
|
6892
|
+
const index = B.tmp();
|
|
6893
|
+
const inBounds = B.tmp();
|
|
6894
|
+
B.line(`${index} = load i64, ptr ${integerSlot}`);
|
|
6895
|
+
B.line(`${inBounds} = icmp ${countedLoop.step > 0 ? "slt" : "sgt"} i64 ${index}, ${countedLimit}`);
|
|
6896
|
+
B.condBr(inBounds, lb, exit);
|
|
6375
6897
|
}
|
|
6376
|
-
else if (s.
|
|
6377
|
-
this.
|
|
6378
|
-
|
|
6379
|
-
|
|
6898
|
+
else if (s.cond)
|
|
6899
|
+
B.condBr(this.emitCondition(s.cond), lb, exit);
|
|
6900
|
+
else
|
|
6901
|
+
B.br(lb);
|
|
6902
|
+
B.startBlock(lb);
|
|
6903
|
+
this.jumpTargets.push({
|
|
6904
|
+
kind: "loop",
|
|
6905
|
+
brkLabel: exit,
|
|
6906
|
+
contLabel: lu,
|
|
6907
|
+
labels: s.labels,
|
|
6908
|
+
frameDepth: this.frames.length,
|
|
6909
|
+
scopeDepth: this.scopes.length,
|
|
6910
|
+
finallyDepth: this.finallyStack.length,
|
|
6911
|
+
});
|
|
6912
|
+
this.emitBlock(s.body);
|
|
6913
|
+
this.jumpTargets.pop();
|
|
6914
|
+
B.br(lu);
|
|
6915
|
+
if (lu !== lc) {
|
|
6916
|
+
B.startBlock(lu);
|
|
6917
|
+
this.emitWorkerLoopCheck(workerBudget);
|
|
6918
|
+
freshenBindings();
|
|
6919
|
+
if (integerLoop && integerSlot) {
|
|
6920
|
+
const old = B.tmp();
|
|
6921
|
+
const next = B.tmp();
|
|
6922
|
+
B.line(`${old} = load ${this.sizeType}, ptr ${integerSlot}`);
|
|
6923
|
+
B.line(`${next} = add nuw ${this.sizeType} ${old}, 1`);
|
|
6924
|
+
B.line(`store ${this.sizeType} ${next}, ptr ${integerSlot}`);
|
|
6925
|
+
}
|
|
6926
|
+
else if (countedLoop && integerSlot) {
|
|
6927
|
+
const old = B.tmp();
|
|
6928
|
+
const next = B.tmp();
|
|
6929
|
+
B.line(`${old} = load i64, ptr ${integerSlot}`);
|
|
6930
|
+
B.line(`${next} = add nsw i64 ${old}, ${countedLoop.step}`);
|
|
6931
|
+
B.line(`store i64 ${next}, ptr ${integerSlot}`);
|
|
6932
|
+
}
|
|
6933
|
+
else if (s.update)
|
|
6934
|
+
this.emitStmt(s.update);
|
|
6935
|
+
B.br(lc);
|
|
6936
|
+
}
|
|
6937
|
+
});
|
|
6380
6938
|
B.startBlock(le);
|
|
6381
6939
|
this.releaseScope(this.scopes.pop());
|
|
6382
6940
|
if (integerLoop)
|
|
@@ -6439,7 +6997,7 @@ export class LlEmitter {
|
|
|
6439
6997
|
// The inline form of scr_arr_len, like the `length` intrinsic.
|
|
6440
6998
|
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
6441
6999
|
this.markMemoryPointer(lenPtr, "array:header");
|
|
6442
|
-
B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
7000
|
+
B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}${this.arrayLengthRange}`);
|
|
6443
7001
|
B.line(`${len} = ${sizeToDouble(this.sizeType)} ${this.sizeType} ${rawLen} to double`);
|
|
6444
7002
|
B.line(`${inBounds} = fcmp olt double ${i}, ${len}`);
|
|
6445
7003
|
cur = i;
|
|
@@ -7146,6 +7704,118 @@ export class LlEmitter {
|
|
|
7146
7704
|
return null;
|
|
7147
7705
|
return this.emitExpr(packed.elems[0]);
|
|
7148
7706
|
}
|
|
7707
|
+
/**
|
|
7708
|
+
* Lower `recv.f = recv.f + suffix` on a plain string field like
|
|
7709
|
+
* emitStringSelfConcatAssign does for locals. The field's old value is
|
|
7710
|
+
* snapshotted (+1) and the suffix evaluated in JavaScript order; then the
|
|
7711
|
+
* field unlinks and releases its CURRENT value, so the snapshot is unique
|
|
7712
|
+
* unless a real alias exists and the concat appends in place instead of
|
|
7713
|
+
* copying the whole string on every append. Returns false (emitting
|
|
7714
|
+
* nothing) for every other store.
|
|
7715
|
+
*/
|
|
7716
|
+
emitFieldSelfConcat(s) {
|
|
7717
|
+
const suffix = matchFieldSelfConcat(s);
|
|
7718
|
+
if (!suffix || s.value.kind !== "strConcat")
|
|
7719
|
+
return false;
|
|
7720
|
+
let fieldType;
|
|
7721
|
+
if (s.kind === "fieldSet") {
|
|
7722
|
+
if (this.inlineUnions.classField(s.className, s.field) ||
|
|
7723
|
+
this.classMeta.get(s.className)?.root.def.name === "%Error")
|
|
7724
|
+
return false;
|
|
7725
|
+
fieldType = classFieldIndex(this.classMetaOf(s.className), s.field).type;
|
|
7726
|
+
}
|
|
7727
|
+
else {
|
|
7728
|
+
fieldType = this.recordShape(s.shapeId).fields.find((f) => f.name === s.field)?.type;
|
|
7729
|
+
}
|
|
7730
|
+
if (fieldType?.kind !== "string")
|
|
7731
|
+
return false;
|
|
7732
|
+
const B = this.B;
|
|
7733
|
+
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
7734
|
+
const snapshot = this.emitExpr(s.value.left);
|
|
7735
|
+
const parts = stringParts(suffix);
|
|
7736
|
+
const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
|
|
7737
|
+
const inputs = mixed ? emitConcatInputs(this, parts) : [];
|
|
7738
|
+
const right = mixed ? null : this.emitExpr(suffix);
|
|
7739
|
+
if (s.kind === "fieldSet")
|
|
7740
|
+
this.emitStoreGuard(obj.name, s);
|
|
7741
|
+
else if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
|
|
7742
|
+
this.emitPublishedFieldGuard(obj.name, null, s.field);
|
|
7743
|
+
const { ptr } = s.kind === "fieldSet"
|
|
7744
|
+
? this.classFieldPtr(obj.name, s.className, s.field)
|
|
7745
|
+
: this.recordFieldPtr(obj.name, s.shapeId, s.field);
|
|
7746
|
+
// Unlink, then release: nothing between here and the store below can
|
|
7747
|
+
// run user code or observe the field (the concat only traps on OOM).
|
|
7748
|
+
// Only an owned snapshot may survive the field's release.
|
|
7749
|
+
const owned = this.ownsSnapshot(snapshot.name);
|
|
7750
|
+
if (owned) {
|
|
7751
|
+
const old = this.loadField(ptr, fieldType, false);
|
|
7752
|
+
this.storeField(ptr, fieldType, "null", false);
|
|
7753
|
+
this.releaseValue(old, fieldType);
|
|
7754
|
+
}
|
|
7755
|
+
let raw;
|
|
7756
|
+
if (right === null) {
|
|
7757
|
+
raw = emitMixedConcat(this, snapshot.name, inputs);
|
|
7758
|
+
}
|
|
7759
|
+
else {
|
|
7760
|
+
this.declare(`declare ptr @scr_str_concat(ptr, ptr)`);
|
|
7761
|
+
raw = B.tmp();
|
|
7762
|
+
B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`);
|
|
7763
|
+
}
|
|
7764
|
+
// The +1 result moves straight into the (now empty) field.
|
|
7765
|
+
if (owned)
|
|
7766
|
+
this.storeField(ptr, fieldType, raw, false);
|
|
7767
|
+
else
|
|
7768
|
+
this.replaceField(ptr, fieldType, false, this.own({ name: raw, type: fieldType }));
|
|
7769
|
+
return true;
|
|
7770
|
+
}
|
|
7771
|
+
/** A snapshot the current statement frames own (+1), or an immortal
|
|
7772
|
+
* value: it stays valid after its container releases the same string. */
|
|
7773
|
+
ownsSnapshot(name) {
|
|
7774
|
+
return (this.isImmortal(name) || this.frames.some((f) => f.some((v) => v.name === name && !v.slot)));
|
|
7775
|
+
}
|
|
7776
|
+
/**
|
|
7777
|
+
* `a[i] = a[i] + suffix` on a string array (the canonical statement form
|
|
7778
|
+
* of `a[i] += suffix`): the old element is snapshotted (+1) and the
|
|
7779
|
+
* suffix evaluated in JavaScript order; then, when the slot still holds
|
|
7780
|
+
* the snapshot, the array lets go of it (emitDenseSlotUnlink) so the
|
|
7781
|
+
* concat can append in place, and the result is stored as usual.
|
|
7782
|
+
*/
|
|
7783
|
+
emitArraySelfConcat(s) {
|
|
7784
|
+
const value = s.value;
|
|
7785
|
+
if (value.kind !== "strConcat" ||
|
|
7786
|
+
value.type.kind !== "string" ||
|
|
7787
|
+
value.left.type.kind !== "string" ||
|
|
7788
|
+
value.right.type.kind !== "string" ||
|
|
7789
|
+
s.arr.type.kind !== "array" ||
|
|
7790
|
+
s.arr.type.elem.kind !== "string" ||
|
|
7791
|
+
s.arr.kind !== "varRef" ||
|
|
7792
|
+
s.index.kind !== "varRef" ||
|
|
7793
|
+
!readsArraySlot(value.left, s.arr.localId, s.index.localId))
|
|
7794
|
+
return false;
|
|
7795
|
+
const elem = s.arr.type.elem;
|
|
7796
|
+
const B = this.B;
|
|
7797
|
+
const arr = this.emitStableReceiver(s.arr, [s.index, s.value]);
|
|
7798
|
+
const idx = this.emitExpr(s.index);
|
|
7799
|
+
const snapshot = this.emitExpr(value.left);
|
|
7800
|
+
const parts = stringParts(value.right);
|
|
7801
|
+
const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
|
|
7802
|
+
const inputs = mixed ? emitConcatInputs(this, parts) : [];
|
|
7803
|
+
const right = mixed ? null : this.emitExpr(value.right);
|
|
7804
|
+
this.emitPublishedArrayGuard(arr.name, s.arr.type, 0, idx.name);
|
|
7805
|
+
if (this.ownsSnapshot(snapshot.name))
|
|
7806
|
+
emitDenseSlotUnlink(this, arr.name, idx, s.index, snapshot.name, this.internLiteral(""), elem);
|
|
7807
|
+
let raw;
|
|
7808
|
+
if (right === null) {
|
|
7809
|
+
raw = emitMixedConcat(this, snapshot.name, inputs);
|
|
7810
|
+
}
|
|
7811
|
+
else {
|
|
7812
|
+
this.declare(`declare ptr @scr_str_concat(ptr, ptr)`);
|
|
7813
|
+
raw = B.tmp();
|
|
7814
|
+
B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`);
|
|
7815
|
+
}
|
|
7816
|
+
emitDenseArraySet(this, arr.name, idx, s.index, "ref", elem, raw);
|
|
7817
|
+
return true;
|
|
7818
|
+
}
|
|
7149
7819
|
/** `local = local + suffix` when the suffix cannot read or write the
|
|
7150
7820
|
* local (an unboxed binding is only reachable through this function's own
|
|
7151
7821
|
* expressions): reading the binding after the suffix gives the value the
|
|
@@ -7373,6 +8043,7 @@ export class LlEmitter {
|
|
|
7373
8043
|
declare: (decl) => this.declare(decl),
|
|
7374
8044
|
cstr: (text) => this.cstr(text),
|
|
7375
8045
|
sizeType: this.sizeType,
|
|
8046
|
+
rcAudit: this.shapeHost.objectAudit !== false,
|
|
7376
8047
|
tracedShapes: this.tracedShapes,
|
|
7377
8048
|
unionsById: this.unionsById,
|
|
7378
8049
|
recordsById: this.recordsById,
|
|
@@ -8057,6 +8728,10 @@ export class LlEmitter {
|
|
|
8057
8728
|
emitExpr(e) {
|
|
8058
8729
|
return emitExpr(this, e);
|
|
8059
8730
|
}
|
|
8731
|
+
/** A fresh read-site cache for one constant-key dyn read. */
|
|
8732
|
+
dynReadIcSite() {
|
|
8733
|
+
return `@sc_dyn_read_ic_${this.dynReadIcSites++}`;
|
|
8734
|
+
}
|
|
8060
8735
|
dynKind(d) {
|
|
8061
8736
|
return dynKind(this, d);
|
|
8062
8737
|
}
|
|
@@ -8087,6 +8762,9 @@ export class LlEmitter {
|
|
|
8087
8762
|
emitterFixedAdapter(cbT) {
|
|
8088
8763
|
return emitterFixedAdapter(this, cbT);
|
|
8089
8764
|
}
|
|
8765
|
+
emitterOnceAdapter(boxerT) {
|
|
8766
|
+
return emitterOnceAdapter(this, boxerT);
|
|
8767
|
+
}
|
|
8090
8768
|
wrapEmitterListener(target, adapterFn) {
|
|
8091
8769
|
return wrapEmitterListener(this, target, adapterFn);
|
|
8092
8770
|
}
|