@scriptc/compiler 0.3.3 → 0.3.5
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 +844 -36
- package/dist/backend/cycle-mutability.d.ts +5 -0
- package/dist/backend/cycle-mutability.js +62 -3
- package/dist/backend/cycle-mutability.js.map +1 -1
- package/dist/backend/executable-features.js +1 -0
- package/dist/backend/executable-features.js.map +1 -1
- package/dist/backend/library-pack.js +1 -0
- package/dist/backend/library-pack.js.map +1 -1
- 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 +11 -1
- package/dist/backend/llvm/call-lifetimes.js +30 -3
- package/dist/backend/llvm/call-lifetimes.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.test.js +31 -1
- package/dist/backend/llvm/call-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/common.d.ts +5 -0
- package/dist/backend/llvm/common.js +7 -0
- package/dist/backend/llvm/common.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 +29 -3
- package/dist/backend/llvm/dense-array-access.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +46 -1
- package/dist/backend/llvm/dyn.js +218 -33
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +87 -1
- package/dist/backend/llvm/emitter.js +568 -61
- 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.d.ts +9 -0
- package/dist/backend/llvm/expr-bytes.js +159 -64
- package/dist/backend/llvm/expr-bytes.js.map +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.d.ts +3 -1
- package/dist/backend/llvm/expr-calls.js +137 -3
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js +63 -29
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.js +9 -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/intern-tables.test.d.ts +1 -0
- package/dist/backend/llvm/intern-tables.test.js +77 -0
- package/dist/backend/llvm/intern-tables.test.js.map +1 -0
- package/dist/backend/llvm/lib-datagram.js +24 -0
- package/dist/backend/llvm/lib-datagram.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +96 -1
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +51 -0
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-network.js +16 -0
- package/dist/backend/llvm/lib-network.js.map +1 -1
- package/dist/backend/llvm/lib-process.js +24 -0
- package/dist/backend/llvm/lib-process.js.map +1 -1
- package/dist/backend/llvm/lib-shared.d.ts +5 -0
- package/dist/backend/llvm/lib-shared.js +114 -3
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/lib-stream.js +87 -0
- package/dist/backend/llvm/lib-stream.js.map +1 -1
- package/dist/backend/llvm/lib-test.js +9 -0
- 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 +6 -0
- package/dist/backend/llvm/map-number-lookup.js +23 -1
- package/dist/backend/llvm/map-number-lookup.js.map +1 -1
- package/dist/backend/llvm/map-number-lookup.test.js +60 -5
- package/dist/backend/llvm/map-number-lookup.test.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.d.ts +11 -0
- package/dist/backend/llvm/map-read-lifetimes.js +92 -1
- package/dist/backend/llvm/map-read-lifetimes.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.test.js +131 -3
- package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/publish.d.ts +21 -0
- package/dist/backend/llvm/publish.js +101 -11
- 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.js +76 -2
- 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 +5 -2
- package/dist/backend/llvm/reference-writes.js.map +1 -1
- package/dist/backend/llvm/regions.d.ts +4 -1
- package/dist/backend/llvm/regions.js +12 -3
- package/dist/backend/llvm/regions.js.map +1 -1
- package/dist/backend/llvm/shapes.d.ts +9 -3
- package/dist/backend/llvm/shapes.js +18 -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/string-lifetimes.test.js +75 -1
- package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/string-reads.js +2 -2
- package/dist/backend/llvm/string-reads.js.map +1 -1
- package/dist/backend/llvm/string-slices.js +2 -2
- package/dist/backend/llvm/string-slices.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 +4 -0
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/native/contracts.d.ts +5 -1
- package/dist/backend/native/executable.js +16 -6
- 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/native-codegen-core.d.ts +4 -4
- package/dist/backend/native-codegen-core.js +4 -6
- package/dist/backend/native-codegen-core.js.map +1 -1
- package/dist/backend/native-codegen.test.js +7 -8
- package/dist/backend/native-codegen.test.js.map +1 -1
- package/dist/backend/native-link-info-core.d.ts +3 -0
- package/dist/backend/native-link-info-core.js.map +1 -1
- package/dist/backend/publication.js +3 -1
- package/dist/backend/publication.js.map +1 -1
- package/dist/backend/runtime-abi.d.ts +2 -2
- package/dist/backend/runtime-abi.js +2 -2
- package/dist/backend/runtime-pack-targets.test.js +2 -1
- package/dist/backend/runtime-pack-targets.test.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +2 -1
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/backend/target-diagnostics.js +7 -0
- package/dist/backend/target-diagnostics.js.map +1 -1
- package/dist/backend/warm-cache.js +1 -0
- package/dist/backend/warm-cache.js.map +1 -1
- package/dist/executable/executable-cache.d.ts +1 -0
- package/dist/executable/executable-cache.js +1 -0
- package/dist/executable/executable-cache.js.map +1 -1
- package/dist/executable/executable-cache.test.js +1 -0
- package/dist/executable/executable-cache.test.js.map +1 -1
- package/dist/frontend/experimental-import.d.ts +5 -1
- package/dist/frontend/experimental-import.js +28 -3
- package/dist/frontend/experimental-import.js.map +1 -1
- package/dist/frontend/lowering/assert-instances.d.ts +38 -0
- package/dist/frontend/lowering/assert-instances.js +269 -0
- package/dist/frontend/lowering/assert-instances.js.map +1 -0
- package/dist/frontend/lowering/builtin-calls.js +3 -0
- package/dist/frontend/lowering/builtin-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/callable-aliases.js +54 -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 +18 -0
- package/dist/frontend/lowering/builtins/compression.js +175 -2
- package/dist/frontend/lowering/builtins/compression.js.map +1 -1
- package/dist/frontend/lowering/builtins/constants.js +7 -2
- package/dist/frontend/lowering/builtins/constants.js.map +1 -1
- package/dist/frontend/lowering/builtins/crypto.d.ts +6 -0
- package/dist/frontend/lowering/builtins/crypto.js +111 -1
- package/dist/frontend/lowering/builtins/crypto.js.map +1 -1
- package/dist/frontend/lowering/builtins/errors.js +23 -0
- package/dist/frontend/lowering/builtins/errors.js.map +1 -1
- package/dist/frontend/lowering/builtins/filesystem-calls.d.ts +2 -0
- package/dist/frontend/lowering/builtins/filesystem-calls.js +5 -0
- package/dist/frontend/lowering/builtins/filesystem-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/filesystem.js +60 -0
- 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/module-calls.js +5 -1
- package/dist/frontend/lowering/builtins/module-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/timers.d.ts +3 -0
- package/dist/frontend/lowering/builtins/timers.js +109 -23
- package/dist/frontend/lowering/builtins/timers.js.map +1 -1
- package/dist/frontend/lowering/call-signatures.js +6 -0
- package/dist/frontend/lowering/call-signatures.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.js +216 -20
- package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
- package/dist/frontend/lowering/containers/bytes.js +33 -12
- package/dist/frontend/lowering/containers/bytes.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/intern-tables.d.ts +13 -0
- package/dist/frontend/lowering/intern-tables.js +536 -0
- package/dist/frontend/lowering/intern-tables.js.map +1 -0
- package/dist/frontend/lowering/lower-assert.js +700 -61
- package/dist/frontend/lowering/lower-assert.js.map +1 -1
- package/dist/frontend/lowering/lower-async-iteration.js +11 -2
- package/dist/frontend/lowering/lower-async-iteration.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +75 -6
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +3 -0
- package/dist/frontend/lowering/lower-classes.js +43 -0
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-dgram.js +132 -67
- 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 +78 -0
- 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 +144 -58
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.d.ts +6 -0
- package/dist/frontend/lowering/lower-inspect.js +138 -39
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-mock.d.ts +11 -0
- package/dist/frontend/lowering/lower-mock.js +504 -0
- package/dist/frontend/lowering/lower-mock.js.map +1 -0
- package/dist/frontend/lowering/lower-modules.js +7 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-server.d.ts +4 -0
- package/dist/frontend/lowering/lower-server.js +448 -30
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +35 -8
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.d.ts +10 -6
- package/dist/frontend/lowering/lower-stream.js +794 -27
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lower-test.js +86 -8
- package/dist/frontend/lowering/lower-test.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +25 -1
- package/dist/frontend/lowering/lowerer.js +74 -9
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/pairs-snapshot.d.ts +6 -0
- package/dist/frontend/lowering/pairs-snapshot.js +49 -0
- package/dist/frontend/lowering/pairs-snapshot.js.map +1 -1
- 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 +4 -0
- package/dist/frontend/lowering/surfaces.js +94 -16
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/lowering/zlib-constants.d.ts +1 -0
- package/dist/frontend/lowering/zlib-constants.js +181 -0
- package/dist/frontend/lowering/zlib-constants.js.map +1 -0
- package/dist/frontend/mock-shapes.d.ts +10 -0
- package/dist/frontend/mock-shapes.js +76 -0
- package/dist/frontend/mock-shapes.js.map +1 -0
- package/dist/frontend/pipeline.js +5 -3
- package/dist/frontend/pipeline.js.map +1 -1
- package/dist/frontend/program.js +6 -1
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/resolve.d.ts +4 -0
- package/dist/frontend/resolve.js +14 -0
- package/dist/frontend/resolve.js.map +1 -1
- package/dist/frontend/tsc-codes.js +1 -0
- package/dist/frontend/tsc-codes.js.map +1 -1
- package/dist/frontend/type-mapper.d.ts +8 -0
- package/dist/frontend/type-mapper.js +155 -9
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.js +2 -1
- 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 +238 -15
- 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 +1 -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 +376 -13
- package/dist/ir/int32-slots.js.map +1 -1
- package/dist/ir/int32-slots.test.js +91 -8
- package/dist/ir/int32-slots.test.js.map +1 -1
- package/dist/ir/integer-loop-facts.d.ts +19 -1
- package/dist/ir/integer-loop-facts.js +155 -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 -55
- package/dist/ir/integer-ranges.js.map +1 -1
- package/dist/ir/integer-ranges.test.js +225 -7
- package/dist/ir/integer-ranges.test.js.map +1 -1
- package/dist/ir/ir.d.ts +29 -2
- package/dist/ir/ir.js +17 -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 +1 -0
- package/dist/ir/runtime-features.js +34 -2
- package/dist/ir/runtime-features.js.map +1 -1
- package/dist/ir/validate.js +221 -5
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.js +21 -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 +1 -0
- package/dist/library/ir-policy.js.map +1 -1
- package/dist/native/cli.js +1 -0
- package/dist/native/cli.js.map +1 -1
- package/dist/native/compiler.js +1 -3
- package/dist/native/compiler.js.map +1 -1
- package/dist/native/prepare.js +1 -0
- package/dist/native/prepare.js.map +1 -1
- package/dist/native/sanitizer.test.js +1 -0
- package/dist/native/sanitizer.test.js.map +1 -1
- package/package.json +20 -20
- package/surface-manifest.json +896 -49
|
@@ -6,7 +6,7 @@ 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";
|
|
@@ -21,16 +21,21 @@ import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
|
|
|
21
21
|
import { RESULT_ENTRY_PREFIX, addRecordResultEntries } from "./record-results.js";
|
|
22
22
|
import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js";
|
|
23
23
|
import { everyExpr, everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
|
|
24
|
-
import { analyzeIntegerRanges, INT32_RANGE } from "../../ir/integer-ranges.js";
|
|
24
|
+
import { analyzeIntegerRanges, INT32_RANGE, } from "../../ir/integer-ranges.js";
|
|
25
25
|
import { unshareNodes } from "../../ir/unshare.js";
|
|
26
|
-
import {
|
|
26
|
+
import { copyStableCaptures } from "../../ir/capture-values.js";
|
|
27
|
+
import { analyzeInt32Slots, dynamicFieldStores } from "../../ir/int32-slots.js";
|
|
27
28
|
import { analyzeFinalFields, constructorClass } from "../../ir/final-fields.js";
|
|
28
29
|
import { foldConstantGlobals } from "../../ir/constant-globals.js";
|
|
30
|
+
import { dropUnwrittenPropertyBags } from "../../ir/property-bags.js";
|
|
31
|
+
import { freezeInitOnlyGlobals } from "../../ir/init-only-globals.js";
|
|
32
|
+
import { hoistFinalFieldReads } from "../../ir/field-values.js";
|
|
29
33
|
import { findIntegerViews } from "./integer-views.js";
|
|
30
34
|
import { findInt32Abis, INT32_ABI_SUFFIX, SCALAR_UNION_DUMMY, SCALAR_UNION_RESULT, } from "./int32-abi.js";
|
|
31
35
|
import { findInt32Locals } from "./int32-locals.js";
|
|
36
|
+
import { emitIntegerClone, emitIntegerDispatcher, findIntegerClones, NO_INTEGER_CLONES, } from "./integer-clones.js";
|
|
32
37
|
import { exactInteger, narrowInteger } from "./integer-values.js";
|
|
33
|
-
import { findInitializerBindings, withInitializerBindings } from "../../ir/initializer-bindings.js";
|
|
38
|
+
import { conditionalLoopWrites, findInitializerBindings, initializerLocalCandidates, withInitializerBindings, withInitializerLocals, } from "../../ir/initializer-bindings.js";
|
|
34
39
|
import { findConstantNumericTables } from "../../ir/constant-tables.js";
|
|
35
40
|
import { allocateFfiCallbackAdapters, hasForeignFfiCallback, hasRetainedFfiCallback, } from "../ffi-callbacks.js";
|
|
36
41
|
import { RUNTIME_ABI_MARKER } from "../runtime-abi.js";
|
|
@@ -45,10 +50,11 @@ import { NullableRefFields } from "./nullable-fields.js";
|
|
|
45
50
|
import { InlineUnionFields, loadInlineUnion, presentBits, stackUnionBox, storeInlineUnion, unionBoxBits, } from "./inline-unions.js";
|
|
46
51
|
import { emitLocalArrayRead, emitProjectedArrayRead, emitBorrowedArrayRead, findLocalArrayReads, findCallArrayReads, OptionalArrayReads, } from "./local-array-reads.js";
|
|
47
52
|
import { CheckedNarrows, emitCheckedNarrowReceiver } from "./checked-narrows.js";
|
|
48
|
-
import { emitDenseArrayGet, emitDenseArraySet } from "./dense-array-access.js";
|
|
49
|
-
import { emitStackMapRead, findMapReadLifetimes, matchMapRead, } from "./map-read-lifetimes.js";
|
|
53
|
+
import { emitDenseArrayGet, emitDenseArraySet, emitDenseSlotUnlink } from "./dense-array-access.js";
|
|
54
|
+
import { emitStackMapRead, findMapReadLifetimes, findReadNumberMapWrites, matchMapRead, } from "./map-read-lifetimes.js";
|
|
55
|
+
import { findMapEntryFusions } from "./map-entry-fusion.js";
|
|
50
56
|
import { emitBorrowedFieldSequence, guardedValue } from "./borrowed-receivers.js";
|
|
51
|
-
import { borrowableInputs, emitBorrowedInput } from "./borrowed-inputs.js";
|
|
57
|
+
import { borrowableInputs, emitBorrowedInput, emitBorrowedInputs } from "./borrowed-inputs.js";
|
|
52
58
|
import { analyzeWalks, findWalkBorrows } from "./walk-borrows.js";
|
|
53
59
|
import { findReboundParameters } from "./rebound-parameters.js";
|
|
54
60
|
import { ReferenceEffects } from "./reference-effects.js";
|
|
@@ -61,7 +67,7 @@ import { AmbientReceiverReaders, findConstantCallbacks } from "./constant-callba
|
|
|
61
67
|
import { findScalarStringSlices, emitStringSliceSnapshot, } from "./string-slices.js";
|
|
62
68
|
import { LazyCaptures } from "./lazy-captures.js";
|
|
63
69
|
import { StackCaptures } from "./stack-captures.js";
|
|
64
|
-
import { ABSENT_FIELD_PAYLOAD, emitFieldAbsentTest, exactIndexLines, f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, } from "./common.js";
|
|
70
|
+
import { ABSENT_FIELD_PAYLOAD, emitFieldAbsentTest, exactIndexLines, f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, sizeToDouble, } from "./common.js";
|
|
65
71
|
import { ffiExtendsNarrowIntegers } from "../targets.js";
|
|
66
72
|
import { emitLiteralExpr, emitOperatorExpr, emitStringExpr, emitContainerExpr, emitRecordExpr, } from "./expr-primitives.js";
|
|
67
73
|
import { emitControlExpr } from "./expr-control.js";
|
|
@@ -72,7 +78,7 @@ import { PublishWalkers } from "./publish.js";
|
|
|
72
78
|
import { computeImmutableFields, stableGlobals } from "../immutability.js";
|
|
73
79
|
import { computePublishedTypes } from "../publication.js";
|
|
74
80
|
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";
|
|
81
|
+
import { dynKind, raceAdapterFor, genResultThunkFor, childExitThunkFor, childExitSignalThunkFor, childDataThunkFor, execFileThunkFor, ipcMessageThunkFor, ipcSendThunkFor, emitterFixedAdapter, emitterOnceAdapter, wrapEmitterListener, unwrapNullableClosure, closeBindThunkFor, closeOverrideWrapFor, } from "./expr-callbacks.js";
|
|
76
82
|
import { streamDataAdapter, streamDoneFnFor, cryptoBytesThunkFor, cryptoIntThunkFor, fsRenameThunkFor, streamCbThunkFor, zlibBytesThunkFor, } from "./expr-stream-callbacks.js";
|
|
77
83
|
import { emitStableReceiver, resolveThunkFor, tagInSet, arrPush, emitArrayCopyLoop, emitStrIntrinsic, emitArrIntrinsic, emitDiscardedSplice, wrapNullable, emitMapNew, mapSet, emitMapLikeIntrinsic, emitSetNew, } from "./expr-containers.js";
|
|
78
84
|
import { emitIntegerLoopIndex, emitBytesIndex, emitBytesData, emitBytesLength, emitBytesGet, emitToUint32, emitBytesSet, emitBytesIntrinsic, } from "./expr-bytes.js";
|
|
@@ -85,7 +91,7 @@ import { emitNetworkHttpLibCall } from "./lib-network.js";
|
|
|
85
91
|
import { emitAssertInspectLibCall, emitIoLibCall, emitGenericLibCall, emitLibCall, } from "./lib-dispatch.js";
|
|
86
92
|
import { buildClassGraph, classMembershipIntervals, emitClassMembershipHelper, classEnvironmentIndex, classFieldIndex, classStructSym, programLifetimeClass, } from "./classes.js";
|
|
87
93
|
import { LifetimeRegions, REGION_TYPES } from "./regions.js";
|
|
88
|
-
import { LlDyn } from "./dyn.js";
|
|
94
|
+
import { LlDyn, recordDynKeepsIdentity } from "./dyn.js";
|
|
89
95
|
import { VirtualBorrows } from "./virtual-borrows.js";
|
|
90
96
|
import { LlvmUnsupportedError } from "./unsupported.js";
|
|
91
97
|
import { LlWalkers } from "./walkers.js";
|
|
@@ -129,14 +135,14 @@ export function emitLlvmModule(input, options = {}) {
|
|
|
129
135
|
const mod = unshareNodes(input);
|
|
130
136
|
// Keep source storage intact for debugger inspection in dev builds.
|
|
131
137
|
return new LlEmitter(options.debugSources === undefined
|
|
132
|
-
? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction)))
|
|
138
|
+
? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(copyStableCaptures(hoistFinalFieldReads(freezeInitOnlyGlobals(dropUnwrittenPropertyBags(mod))))), mangleFunction)))
|
|
133
139
|
: mod, options).emit();
|
|
134
140
|
}
|
|
135
141
|
/** Keep large translation units below the host's single-string limit. */
|
|
136
142
|
export function emitLlvmModuleSource(input, options = {}) {
|
|
137
143
|
const mod = unshareNodes(input);
|
|
138
144
|
const parts = new LlEmitter(options.debugSources === undefined
|
|
139
|
-
? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction)))
|
|
145
|
+
? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(copyStableCaptures(hoistFinalFieldReads(freezeInitOnlyGlobals(dropUnwrittenPropertyBags(mod))))), mangleFunction)))
|
|
140
146
|
: mod, options).emitParts();
|
|
141
147
|
const length = parts.reduce((total, part) => total + part.length, Math.max(0, parts.length - 1));
|
|
142
148
|
if (length <= 256 * 1024 * 1024)
|
|
@@ -147,6 +153,9 @@ function llStrBytes(text) {
|
|
|
147
153
|
return llBytes(wtf8Bytes(text));
|
|
148
154
|
}
|
|
149
155
|
const NO_BORROWED = new Set();
|
|
156
|
+
/** ScrExcKind tags of a generator slot holding a number or a boolean. */
|
|
157
|
+
const SCR_EXC_F64 = 1;
|
|
158
|
+
const SCR_EXC_BOOL = 2;
|
|
150
159
|
/** Whether any node of `e` names the local (reads, writes, declarations,
|
|
151
160
|
* captures). */
|
|
152
161
|
function mentionsLocal(e, localId) {
|
|
@@ -226,6 +235,12 @@ export class LlEmitter {
|
|
|
226
235
|
}
|
|
227
236
|
return `, !tbaa !${tag}`;
|
|
228
237
|
}
|
|
238
|
+
/** The `!range` attachment of an array length load: a length is at most
|
|
239
|
+
* 2^32 - 1 (the runtime's SCR_ARR_MAX_LENGTH), so conversions to double
|
|
240
|
+
* and back fold. 32-bit targets load the full range anyway. */
|
|
241
|
+
get arrayLengthRange() {
|
|
242
|
+
return this.sizeType === "i64" ? ", !range !{i64 0, i64 4294967296}" : "";
|
|
243
|
+
}
|
|
229
244
|
fieldAliasAttachment(ptr) {
|
|
230
245
|
const tag = this.fieldPointerTags.get(ptr);
|
|
231
246
|
return tag === undefined ? "" : `, !tbaa !${tag}`;
|
|
@@ -312,6 +327,8 @@ export class LlEmitter {
|
|
|
312
327
|
/** Interned NUL-terminated C-string constants (scr_jb_puts labels, the
|
|
313
328
|
* stringify indent text): UTF-8 text → { symbol, byte length }. */
|
|
314
329
|
cstrs = new Map();
|
|
330
|
+
/** Interned ScrMapOps descriptors (scr_map.h): initializer → symbol. */
|
|
331
|
+
mapOpsConsts = new Map();
|
|
315
332
|
/** Type-directed walker functions (JSON serializers, the indent
|
|
316
333
|
* rewriter, union ToString/join) — interned per typeKey/unionId, defs
|
|
317
334
|
* flushed with the shape helpers. */
|
|
@@ -354,6 +371,9 @@ export class LlEmitter {
|
|
|
354
371
|
checkedNarrows;
|
|
355
372
|
callArrayReads = new Map();
|
|
356
373
|
mapReadLifetimes = { locals: new Map(), arguments: new Map() };
|
|
374
|
+
readNumberMapWrites = new Set();
|
|
375
|
+
/** The current function's fused string-key Map get-then-set pairs (map-entry-fusion.ts). */
|
|
376
|
+
mapEntryFusion = null;
|
|
357
377
|
callLifetimes;
|
|
358
378
|
constantCallbacks;
|
|
359
379
|
currentConstantCallbacks = new Map();
|
|
@@ -517,8 +537,20 @@ export class LlEmitter {
|
|
|
517
537
|
initializerBindings;
|
|
518
538
|
/** Whole-program int32 storage and parameter/result facts. */
|
|
519
539
|
int32Slots;
|
|
540
|
+
/** The program has no runtime store into a checked-dynamic value
|
|
541
|
+
* (dynamicFieldStores), so live views of classes and records never
|
|
542
|
+
* commit back into their objects. Library builds keep their commits:
|
|
543
|
+
* their values cross a native boundary. */
|
|
544
|
+
liveViewsReadOnly;
|
|
520
545
|
/** Functions whose direct calls use i32 parameters or results. */
|
|
521
546
|
int32Abis;
|
|
547
|
+
/** Pure number functions with speculative integer clones. */
|
|
548
|
+
integerClones;
|
|
549
|
+
/** The IR function being emitted. */
|
|
550
|
+
currentFunctionName = "";
|
|
551
|
+
integerParamRange(fnName, localId) {
|
|
552
|
+
return this.int32Slots.facts(fnName).params?.get(localId) ?? null;
|
|
553
|
+
}
|
|
522
554
|
/** Immutable module constants (contract types) whose graphs become
|
|
523
555
|
* immortal once initialized: their reads need no reference counting. */
|
|
524
556
|
immortalGlobals;
|
|
@@ -542,6 +574,9 @@ export class LlEmitter {
|
|
|
542
574
|
byteWindowEntries = new Map();
|
|
543
575
|
bytesBounds = new Set();
|
|
544
576
|
integerViews = new Map();
|
|
577
|
+
/** Module globals the current function owns outright (initializer
|
|
578
|
+
* bindings): their function-local slot, which stands in for the global. */
|
|
579
|
+
privateGlobalSlots = new Map();
|
|
545
580
|
/** Number locals of the current function stored only as an i32
|
|
546
581
|
* (int32-locals.ts); their slots are `alloca i32`. */
|
|
547
582
|
int32Locals = new Map();
|
|
@@ -575,6 +610,9 @@ export class LlEmitter {
|
|
|
575
610
|
* switched-coroutine intrinsics (wasm32 async bodies and generators,
|
|
576
611
|
* native synchronous generators; see coroLowered). */
|
|
577
612
|
currentCoro = null;
|
|
613
|
+
/** Inline generator slot accesses need the %ScrGenHead/%ScrGenSlot
|
|
614
|
+
* mirrors in the module header. */
|
|
615
|
+
usesGenSlots = false;
|
|
578
616
|
/** The generator channels of the function being emitted (null outside
|
|
579
617
|
* generator bodies): yieldExpr emission reads them, and emitTryCatch's
|
|
580
618
|
* catch prologue emits the GENRET sentinel re-unwind exactly here. */
|
|
@@ -599,6 +637,9 @@ export class LlEmitter {
|
|
|
599
637
|
this.constantNumericTables = findConstantNumericTables(mod);
|
|
600
638
|
this.initializerBindings = findInitializerBindings(mod);
|
|
601
639
|
this.int32Slots = analyzeInt32Slots(mod);
|
|
640
|
+
const dynamicStores = dynamicFieldStores(mod);
|
|
641
|
+
this.liveViewsReadOnly =
|
|
642
|
+
mod.lib === undefined && !dynamicStores.all && dynamicStores.names.size === 0;
|
|
602
643
|
this.int32Abis = this.debug === null ? findInt32Abis(mod, this.int32Slots) : new Map();
|
|
603
644
|
this.immortalGlobals = new Set(mod.contracts?.immortalGlobals ?? []);
|
|
604
645
|
this.programLifetime = new Set(mod.contracts?.programLifetime ?? []);
|
|
@@ -737,8 +778,25 @@ export class LlEmitter {
|
|
|
737
778
|
this.callLifetimes = analyzeCallLifetimes(this.fnByName, (className, field) => this.nullableFields.get(className, field) !== null, walkParameters, new Set([...this.int32Abis].filter(([, abi]) => abi.union).map(([name]) => name)), {
|
|
738
779
|
record: (shapeId, field) => this.recordFieldInline(shapeId, field),
|
|
739
780
|
classField: (className, field) => this.inlineUnions.classField(className, field),
|
|
781
|
+
}, (shapeId) => {
|
|
782
|
+
const shape = this.recordsById.get(shapeId);
|
|
783
|
+
return shape !== undefined && !recordDynKeepsIdentity(shape);
|
|
740
784
|
});
|
|
741
785
|
this.constantCallbacks = findConstantCallbacks(mod, this.callLifetimes);
|
|
786
|
+
// Integer clones signal a stack overflow through their result, which
|
|
787
|
+
// needs the single-threaded guard; worker programs also poll for
|
|
788
|
+
// termination, and debug builds and stack traces keep every frame.
|
|
789
|
+
// Clones read number arrays their function borrows (64-bit layouts).
|
|
790
|
+
this.integerClones =
|
|
791
|
+
this.debug === null &&
|
|
792
|
+
this.stackChecks !== null &&
|
|
793
|
+
mod.workers !== true &&
|
|
794
|
+
mod.lib === undefined &&
|
|
795
|
+
!this.stackTraces
|
|
796
|
+
? findIntegerClones(mod, this.sizeType === "i64"
|
|
797
|
+
? (fn, index) => this.callLifetimes.borrowed.get(fn)?.has(index) === true
|
|
798
|
+
: undefined)
|
|
799
|
+
: NO_INTEGER_CLONES;
|
|
742
800
|
this.stackCallbacks = new StackCallbacks(this.fnByName);
|
|
743
801
|
this.receiverReaders = new AmbientReceiverReaders(this.fnByName);
|
|
744
802
|
this.stackCaptures = new StackCaptures(this.fnByName, this.callLifetimes, this.stackCallbacks);
|
|
@@ -776,6 +834,7 @@ export class LlEmitter {
|
|
|
776
834
|
needBadTag: () => this.needBadTag(),
|
|
777
835
|
internLiteral: (text) => this.internLiteral(text),
|
|
778
836
|
cstr: (text) => this.cstr(text),
|
|
837
|
+
mapOps: (operands) => this.mapOps(operands),
|
|
779
838
|
unitInstanceRef: (unionId, tag) => this.unitInstanceRef(unionId, tag),
|
|
780
839
|
retypeGuardRef: (unionId) => this.retypeGuardRef(unionId),
|
|
781
840
|
fieldAbsentTestIn: (B, value, unionId) => this.fieldAbsentTestIn(B, value, unionId),
|
|
@@ -876,6 +935,10 @@ export class LlEmitter {
|
|
|
876
935
|
virtualSlotBorrowed(rootName, slotIndex) {
|
|
877
936
|
return this.virtualBorrows.slot(rootName, slotIndex);
|
|
878
937
|
}
|
|
938
|
+
/** Optimized emission: no debug-info (dev) posture. */
|
|
939
|
+
get optimizing() {
|
|
940
|
+
return this.debug === null;
|
|
941
|
+
}
|
|
879
942
|
/** The vtable entry for one implementation (VirtualBorrows.entry). */
|
|
880
943
|
virtualEntry(implFn) {
|
|
881
944
|
return this.virtualBorrows.entry(implFn);
|
|
@@ -1372,7 +1435,25 @@ export class LlEmitter {
|
|
|
1372
1435
|
` call void @scr_run_exit_listeners(double %${prefix}hd)`,
|
|
1373
1436
|
];
|
|
1374
1437
|
};
|
|
1375
|
-
|
|
1438
|
+
// The entry's exits release the module globals only where the thread
|
|
1439
|
+
// frees its heap when its script ends (scr_exit_frees: workers, and
|
|
1440
|
+
// builds that check for leaks); the main thread of other builds leaves
|
|
1441
|
+
// it to the operating system.
|
|
1442
|
+
const exitReleaseLines = (prefix) => {
|
|
1443
|
+
const releases = globalReleaseLines(prefix);
|
|
1444
|
+
if (releases.length === 0)
|
|
1445
|
+
return releases;
|
|
1446
|
+
this.declare(`declare zeroext i1 @scr_exit_frees()`);
|
|
1447
|
+
return [
|
|
1448
|
+
` %${prefix}.frees = call zeroext i1 @scr_exit_frees()`,
|
|
1449
|
+
` br i1 %${prefix}.frees, label %${prefix}.release, label %${prefix}.kept`,
|
|
1450
|
+
`${prefix}.release:`,
|
|
1451
|
+
...releases,
|
|
1452
|
+
` br label %${prefix}.kept`,
|
|
1453
|
+
`${prefix}.kept:`,
|
|
1454
|
+
];
|
|
1455
|
+
};
|
|
1456
|
+
const globalReleases = exitReleaseLines("g");
|
|
1376
1457
|
const asyncEntry = this.fnByName.get(this.mod.entry)?.async === true;
|
|
1377
1458
|
const entryMayThrow = this.mayThrow.has(this.mod.entry);
|
|
1378
1459
|
// The event loop runs when timers appeared OR any async/generator
|
|
@@ -1384,12 +1465,12 @@ export class LlEmitter {
|
|
|
1384
1465
|
const runsLoop = this.usesTimers ||
|
|
1385
1466
|
this.ffiHasForeignCallback ||
|
|
1386
1467
|
this.mod.functions.some((f) => f.async === true || f.generator !== undefined);
|
|
1387
|
-
const uncaughtReleases = entryMayThrow && !asyncEntry ?
|
|
1388
|
-
const loopReleasesU = runsLoop ?
|
|
1389
|
-
const loopReleasesR = runsLoop ?
|
|
1390
|
-
const topRejectReleases = asyncEntry ?
|
|
1391
|
-
const topPendingReleases = asyncEntry ?
|
|
1392
|
-
const loopReportedReleases = runsLoop ?
|
|
1468
|
+
const uncaughtReleases = entryMayThrow && !asyncEntry ? exitReleaseLines("gu") : [];
|
|
1469
|
+
const loopReleasesU = runsLoop ? exitReleaseLines("gl") : [];
|
|
1470
|
+
const loopReleasesR = runsLoop ? exitReleaseLines("gr") : [];
|
|
1471
|
+
const topRejectReleases = asyncEntry ? exitReleaseLines("gt") : [];
|
|
1472
|
+
const topPendingReleases = asyncEntry ? exitReleaseLines("gp") : [];
|
|
1473
|
+
const loopReportedReleases = runsLoop ? exitReleaseLines("gq") : [];
|
|
1393
1474
|
// main's epilogues read the flag / the loop entry points — declared
|
|
1394
1475
|
// HERE, before the extern block flushes. They run once, so they keep
|
|
1395
1476
|
// the out-of-line scr_exc_pending call; per-call checks inline the
|
|
@@ -1413,21 +1494,17 @@ export class LlEmitter {
|
|
|
1413
1494
|
if (!asyncEntry)
|
|
1414
1495
|
return [];
|
|
1415
1496
|
const lines = [];
|
|
1497
|
+
// finish_top_level noted 13 unless the program set process.exitCode.
|
|
1498
|
+
// A failed node:test run outranks either before exit listeners run.
|
|
1416
1499
|
if (usesNodeTest) {
|
|
1417
|
-
lines.push(` %tla_program_exit = call i32 @scr_test_exit_code()`);
|
|
1418
|
-
}
|
|
1419
|
-
if (usesNodeTest) {
|
|
1420
|
-
lines.push(` %tla_program_exit_zero = icmp eq i32 %tla_program_exit, 0`, ` %tla_exit_status = select i1 %tla_program_exit_zero, i32 %tla_status, i32 %tla_program_exit`);
|
|
1500
|
+
lines.push(` %tla_program_exit = call i32 @scr_test_exit_code()`, ` %tla_program_exit_zero = icmp eq i32 %tla_program_exit, 0`, ` %tla_hint = call i32 @scr_exit_code_hint_get()`, ` %tla_exit_status = select i1 %tla_program_exit_zero, i32 %tla_hint, i32 %tla_program_exit`, ` call void @scr_exit_code_note(i32 %tla_exit_status)`);
|
|
1421
1501
|
}
|
|
1422
|
-
const exitStatus = usesNodeTest ? "%tla_exit_status" : "%tla_status";
|
|
1423
|
-
// finish_top_level initially notes 13. Replace that hint before exit
|
|
1424
|
-
// listeners run when a higher-priority verdict was already selected.
|
|
1425
|
-
lines.push(` call void @scr_exit_code_note(i32 ${exitStatus})`);
|
|
1426
1502
|
for (const line of exitListenerLines("xp"))
|
|
1427
1503
|
lines.push(line);
|
|
1428
1504
|
for (const line of topPendingReleases)
|
|
1429
1505
|
lines.push(line);
|
|
1430
|
-
|
|
1506
|
+
// An exit listener may set process.exitCode again, as in Node.
|
|
1507
|
+
lines.push(` %tla_exit_code = call i32 @scr_exit_code_hint_get()`, ` ret i32 %tla_exit_code`);
|
|
1431
1508
|
return lines;
|
|
1432
1509
|
};
|
|
1433
1510
|
// LIBRARY mode: the runtime entry points the generated library
|
|
@@ -1519,22 +1596,20 @@ export class LlEmitter {
|
|
|
1519
1596
|
// preserving the target object's identity.
|
|
1520
1597
|
`%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i8, i8, i16, i32, ptr, ptr, ptr, ptr }`,
|
|
1521
1598
|
`%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 }`]),
|
|
1599
|
+
// The ScrMap prefix { rc, key_kind, val_kind, traced } — the inline
|
|
1600
|
+
// RC fast paths test the traced byte (shapes.ts).
|
|
1601
|
+
...(this.rcHelpers === null ? [] : [`%ScrMapRc = type { ${this.sizeType}, i8, i8, i8 }`]),
|
|
1528
1602
|
// The complete ScrMap and its { key, val, hash } entry, read by the
|
|
1529
1603
|
// inline number-key lookup (sc_map_entry_f64) and the inline
|
|
1530
|
-
// iteration steps (sc_map_iter_entry): field
|
|
1531
|
-
// field
|
|
1532
|
-
//
|
|
1604
|
+
// iteration steps (sc_map_iter_entry): field 8 is the i32 entry
|
|
1605
|
+
// count, field 10 the i64 absent-key filter, entries is field 11,
|
|
1606
|
+
// nbuckets and buckets fields 13 and 14, the direct index and its i32
|
|
1607
|
+
// length fields 15 and 16.
|
|
1533
1608
|
...(this.decls.has(NUMBER_MAP_ENTRY_DECL) ||
|
|
1534
1609
|
this.decls.has(STRING_MAP_ENTRY_DECL) ||
|
|
1535
1610
|
this.decls.has(MAP_ITER_DECL)
|
|
1536
1611
|
? [
|
|
1537
|
-
`%ScrMapIx = type { ${this.sizeType},
|
|
1612
|
+
`%ScrMapIx = type { ${this.sizeType}, i8, i8, i8, i8, i32, ptr, ptr, i32, i32, i64, ptr, i32, i32, ptr, ptr, i32, i32 }`,
|
|
1538
1613
|
]
|
|
1539
1614
|
: []),
|
|
1540
1615
|
// String-keyed reference reads load the value slot of the entry
|
|
@@ -1572,7 +1647,7 @@ export class LlEmitter {
|
|
|
1572
1647
|
// typed-array access GEPs through this directly: the IR type already
|
|
1573
1648
|
// fixes elem, so the hot path needs neither a runtime kind load nor
|
|
1574
1649
|
// the generic scr_bytes_get/set call.
|
|
1575
|
-
`%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, ptr }`,
|
|
1650
|
+
`%ScrBytes = type { ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, i8, i8, i8, i8, ptr }`,
|
|
1576
1651
|
// The capture box { rc, kind, obj_retain, obj_release, obj_trace,
|
|
1577
1652
|
// slot } — TDZ reads peek the payload slot (offset 40) directly.
|
|
1578
1653
|
`%ScrBox = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
|
|
@@ -1583,6 +1658,15 @@ export class LlEmitter {
|
|
|
1583
1658
|
`%ScrDynPath = type { ptr, ptr, ${this.sizeType} }`,
|
|
1584
1659
|
`%ScrIslandModule = type { ptr, ptr, ${this.sizeType}, ${this.sizeType}, i32, ptr, ${this.sizeType}, ${this.sizeType} }`,
|
|
1585
1660
|
`%ScrIslandEdge = type { ptr, ptr, ptr, i32 }`,
|
|
1661
|
+
// A generator's prefix { rc, receiver, fiber, state, out } and its
|
|
1662
|
+
// value slot { kind, f64, b, payload, release_fn } (scr_async.c pins
|
|
1663
|
+
// both): number and boolean yields and drives address OUT directly.
|
|
1664
|
+
...(this.usesGenSlots
|
|
1665
|
+
? [
|
|
1666
|
+
`%ScrGenSlot = type { i32, double, i8, ptr, ptr }`,
|
|
1667
|
+
`%ScrGenHead = type { ${this.sizeType}, ptr, ptr, i32, %ScrGenSlot }`,
|
|
1668
|
+
]
|
|
1669
|
+
: []),
|
|
1586
1670
|
];
|
|
1587
1671
|
for (const line of shapes.typeDefs)
|
|
1588
1672
|
out.push(line);
|
|
@@ -1720,6 +1804,11 @@ export class LlEmitter {
|
|
|
1720
1804
|
}
|
|
1721
1805
|
if (this.cstrs.size > 0)
|
|
1722
1806
|
out.push(``);
|
|
1807
|
+
for (const [body, sym] of this.mapOpsConsts) {
|
|
1808
|
+
out.push(`@${sym} = internal constant { ptr, ptr, ptr, ptr, ptr, ptr, ptr } { ${body} }`);
|
|
1809
|
+
}
|
|
1810
|
+
if (this.mapOpsConsts.size > 0)
|
|
1811
|
+
out.push(``);
|
|
1723
1812
|
for (const line of ffiCallbacks.globals)
|
|
1724
1813
|
out.push(line);
|
|
1725
1814
|
if (ffiCallbacks.globals.length > 0)
|
|
@@ -2185,18 +2274,36 @@ export class LlEmitter {
|
|
|
2185
2274
|
// Number-key Map/Set lookups probe the runtime's direct index inline:
|
|
2186
2275
|
// while ndense is nonzero every live key is an integer below it, so an
|
|
2187
2276
|
// in-range integral key reads its slot (entry index + 1, 0 = absent) and
|
|
2188
|
-
// any other key is absent. Maps without the index (ndense 0)
|
|
2189
|
-
//
|
|
2190
|
-
//
|
|
2277
|
+
// any other key is absent. Maps without the index (ndense 0) hash the
|
|
2278
|
+
// key in sc_map_entry_f64_hashed, which scans the entries of a small map
|
|
2279
|
+
// or probes a compact bucket table as scr_map_find_f64 does, and calls
|
|
2280
|
+
// the runtime lookup for a wide table. It stays out of line so that the
|
|
2281
|
+
// direct-index path stays small at every call site. Both only read
|
|
2282
|
+
// memory, so LLVM merges lookups of one key when nothing is stored in
|
|
2283
|
+
// between (a has() followed by get(), for example).
|
|
2191
2284
|
if (this.decls.has(NUMBER_MAP_ENTRY_DECL)) {
|
|
2192
2285
|
const sz = this.sizeType;
|
|
2193
2286
|
let mapTmp = 0;
|
|
2194
2287
|
const mapIndex = exactIndexLines("%k", "%nd", sz, () => `%ix${mapTmp++}`);
|
|
2195
|
-
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
|
|
2288
|
+
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"
|
|
2289
|
+
? [` %nd32 = load i32, ptr %ndp`, ` %nd = zext i32 %nd32 to i64`]
|
|
2290
|
+
: [` %nd = load i32, ptr %ndp`]),
|
|
2196
2291
|
// A non-integral key inside the table is as absent as one outside it.
|
|
2197
2292
|
...mapIndex.lines.map((line) => ` ${line}`), ` br i1 ${mapIndex.ok}, label %slot, label %outside`, `slot:`, sz === "i64"
|
|
2198
2293
|
? ` %i = add i64 ${mapIndex.wide}, 0`
|
|
2199
|
-
: ` %i = trunc i64 ${mapIndex.wide} to ${sz}`, ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32
|
|
2294
|
+
: ` %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`, `}`, ``,
|
|
2295
|
+
// Hashing (scr_map_find_f64): the SameValueZero key word, its
|
|
2296
|
+
// scr_key_mix hash (zero remapped to one) and the absent-key filter.
|
|
2297
|
+
`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"
|
|
2298
|
+
? [` %nb32 = load i32, ptr %nbp`, ` %nb = zext i32 %nb32 to i64`]
|
|
2299
|
+
: [` %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`,
|
|
2300
|
+
// Small maps keep no bucket table: scan the cached entry hashes.
|
|
2301
|
+
`scan:`, ` %nep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 8`, ...(sz === "i64"
|
|
2302
|
+
? [` %ne32 = load i32, ptr %nep`, ` %ne = zext i32 %ne32 to i64`]
|
|
2303
|
+
: [` %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`,
|
|
2304
|
+
// Compact tables tag each 24-bit entry index (+1) with the hash's
|
|
2305
|
+
// top byte; wider tables take the runtime lookup.
|
|
2306
|
+
`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`, `}`, ``);
|
|
2200
2307
|
}
|
|
2201
2308
|
// Map/Set iteration steps read the entries array inline: an index is
|
|
2202
2309
|
// live when it is below the entry count and its entry's cached hash is
|
|
@@ -2205,7 +2312,7 @@ export class LlEmitter {
|
|
|
2205
2312
|
// the iteration are visited.
|
|
2206
2313
|
if (this.decls.has(MAP_ITER_DECL)) {
|
|
2207
2314
|
const sz = this.sizeType;
|
|
2208
|
-
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
|
|
2315
|
+
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`, `}`, ``);
|
|
2209
2316
|
}
|
|
2210
2317
|
// String-key Map/Set lookups test the map's absent-key filter inline
|
|
2211
2318
|
// (scr_map.c scr_map_filter_bits): a key whose two filter bits are not
|
|
@@ -2218,7 +2325,7 @@ export class LlEmitter {
|
|
|
2218
2325
|
if (this.decls.has(STRING_MAP_ENTRY_DECL)) {
|
|
2219
2326
|
defs.push(`define internal ptr @sc_map_entry_str(ptr %m, ptr %k) ${FN_ATTRS} {`, `entry:`, ...(this.sizeType === "i64"
|
|
2220
2327
|
? [
|
|
2221
|
-
` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32
|
|
2328
|
+
` %fp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 10`,
|
|
2222
2329
|
` %filter = load i64, ptr %fp`,
|
|
2223
2330
|
` %capp = getelementptr inbounds %ScrStr, ptr %k, i32 0, i32 2`,
|
|
2224
2331
|
` %cap = load i64, ptr %capp`,
|
|
@@ -3024,6 +3131,18 @@ export class LlEmitter {
|
|
|
3024
3131
|
needBadTag() {
|
|
3025
3132
|
this.needsBadTag = true;
|
|
3026
3133
|
}
|
|
3134
|
+
/** The ScrMapOps descriptor holding these operands (ShapeHost.mapOps). */
|
|
3135
|
+
mapOps(operands) {
|
|
3136
|
+
if (operands.every((op) => op === "null"))
|
|
3137
|
+
return "null";
|
|
3138
|
+
const body = operands.map((op) => `ptr ${op}`).join(", ");
|
|
3139
|
+
let sym = this.mapOpsConsts.get(body);
|
|
3140
|
+
if (!sym) {
|
|
3141
|
+
sym = this.contentSymbol("sc_map_ops_", body);
|
|
3142
|
+
this.mapOpsConsts.set(body, sym);
|
|
3143
|
+
}
|
|
3144
|
+
return `@${sym}`;
|
|
3145
|
+
}
|
|
3027
3146
|
/** The interned immortal instance for a UNIT arm of a union — asserts
|
|
3028
3147
|
* the arm really is payload-less (undefined/null). Public: class
|
|
3029
3148
|
* emission initializes undefined-admitting fields through it
|
|
@@ -3604,6 +3723,11 @@ export class LlEmitter {
|
|
|
3604
3723
|
this.B.line(`store i32 0, ptr ${slot}`);
|
|
3605
3724
|
return slot;
|
|
3606
3725
|
}
|
|
3726
|
+
/** The amortized cancellation poll, emitted on the loop's back edge (the
|
|
3727
|
+
* continue target), after the body. A poll in the header would be a loop
|
|
3728
|
+
* exit ahead of the body; LLVM then cannot rotate the loop, so loads that
|
|
3729
|
+
* the body repeats on every iteration (a typed array's length, for one)
|
|
3730
|
+
* stay in the loop. */
|
|
3607
3731
|
emitWorkerLoopCheck(slot) {
|
|
3608
3732
|
if (slot === null)
|
|
3609
3733
|
return;
|
|
@@ -4155,7 +4279,11 @@ export class LlEmitter {
|
|
|
4155
4279
|
const g = this.globalTypes.get(id);
|
|
4156
4280
|
if (!g)
|
|
4157
4281
|
throw new InternalCompilerError(`llvm emitter bug: unknown binding ${id}`);
|
|
4158
|
-
return {
|
|
4282
|
+
return {
|
|
4283
|
+
kind: "global",
|
|
4284
|
+
slot: this.privateGlobalSlots.get(id) ?? `@${mangleGlobal(id)}`,
|
|
4285
|
+
type: g,
|
|
4286
|
+
};
|
|
4159
4287
|
}
|
|
4160
4288
|
emitDebugLocal(local, slot, arg = 0, captured = false) {
|
|
4161
4289
|
const debug = this.debug?.local(local, this.debugScope, arg, captured);
|
|
@@ -4418,6 +4546,55 @@ export class LlEmitter {
|
|
|
4418
4546
|
}
|
|
4419
4547
|
this.emitCoroSuspendPrepared();
|
|
4420
4548
|
}
|
|
4549
|
+
/** A native generator's OUT slot (scr_async.c's ScrGen.out, mirrored by
|
|
4550
|
+
* %ScrGenHead and %ScrGenSlot { kind, f64, b, payload, release_fn }). */
|
|
4551
|
+
genOutSlot(gen) {
|
|
4552
|
+
this.usesGenSlots = true;
|
|
4553
|
+
const slot = this.B.tmp();
|
|
4554
|
+
this.B.line(`${slot} = getelementptr inbounds %ScrGenHead, ptr ${gen}, i32 0, i32 4`);
|
|
4555
|
+
return slot;
|
|
4556
|
+
}
|
|
4557
|
+
/** A yield on a number or boolean channel: scr_gen_slot_f64/bool inline.
|
|
4558
|
+
* Such a body's OUT never holds a reference at a yield (only its final
|
|
4559
|
+
* completion can park one), so the slot needs no release first. */
|
|
4560
|
+
emitGenSlotStore(slot, kind, value) {
|
|
4561
|
+
const B = this.B;
|
|
4562
|
+
const field = B.tmp();
|
|
4563
|
+
if (kind === "f64") {
|
|
4564
|
+
B.line(`store i32 ${SCR_EXC_F64}, ptr ${slot}`);
|
|
4565
|
+
B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 1`);
|
|
4566
|
+
B.line(`store double ${value}, ptr ${field}`);
|
|
4567
|
+
}
|
|
4568
|
+
else {
|
|
4569
|
+
const byte = B.tmp();
|
|
4570
|
+
B.line(`store i32 ${SCR_EXC_BOOL}, ptr ${slot}`);
|
|
4571
|
+
B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 2`);
|
|
4572
|
+
B.line(`${byte} = zext i1 ${value} to i8`);
|
|
4573
|
+
B.line(`store i8 ${byte}, ptr ${field}`);
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
/** scr_gen_take_out_f64/bool inline: the slot's value when it holds that
|
|
4577
|
+
* kind (otherwise 0 or false), leaving the slot empty. */
|
|
4578
|
+
emitGenSlotTake(slot, kind) {
|
|
4579
|
+
const B = this.B;
|
|
4580
|
+
const tag = B.tmp(), matches = B.tmp(), field = B.tmp(), raw = B.tmp(), out = B.tmp();
|
|
4581
|
+
B.line(`${tag} = load i32, ptr ${slot}`);
|
|
4582
|
+
B.line(`${matches} = icmp eq i32 ${tag}, ${kind === "f64" ? SCR_EXC_F64 : SCR_EXC_BOOL}`);
|
|
4583
|
+
if (kind === "f64") {
|
|
4584
|
+
B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 1`);
|
|
4585
|
+
B.line(`${raw} = load double, ptr ${field}`);
|
|
4586
|
+
B.line(`${out} = select i1 ${matches}, double ${raw}, double 0.0`);
|
|
4587
|
+
}
|
|
4588
|
+
else {
|
|
4589
|
+
const bit = B.tmp();
|
|
4590
|
+
B.line(`${field} = getelementptr inbounds %ScrGenSlot, ptr ${slot}, i32 0, i32 2`);
|
|
4591
|
+
B.line(`${raw} = load i8, ptr ${field}`);
|
|
4592
|
+
B.line(`${bit} = trunc i8 ${raw} to i1`);
|
|
4593
|
+
B.line(`${out} = select i1 ${matches}, i1 ${bit}, i1 false`);
|
|
4594
|
+
}
|
|
4595
|
+
B.line(`store i32 0, ptr ${slot}`);
|
|
4596
|
+
return out;
|
|
4597
|
+
}
|
|
4421
4598
|
/** Suspend after a target-specific runtime helper already queued the
|
|
4422
4599
|
* continuation (used by module awaits, which skip the hop when settled). */
|
|
4423
4600
|
emitCoroSuspendPrepared() {
|
|
@@ -4492,9 +4669,25 @@ export class LlEmitter {
|
|
|
4492
4669
|
}
|
|
4493
4670
|
}
|
|
4494
4671
|
}
|
|
4672
|
+
/** Number locals declared as a copy of a capture or of a lazily boxed
|
|
4673
|
+
* parameter, at the top of the body. */
|
|
4674
|
+
doubleCopies(fn) {
|
|
4675
|
+
const copies = [];
|
|
4676
|
+
for (const s of fn.body) {
|
|
4677
|
+
if (s.kind !== "varDecl")
|
|
4678
|
+
break;
|
|
4679
|
+
if (s.init?.kind !== "varRef" || s.init.type.kind !== "f64")
|
|
4680
|
+
continue;
|
|
4681
|
+
const source = s.init.localId;
|
|
4682
|
+
if (this.captureIds.has(source) || this.lazyCaptureBoxes.has(source))
|
|
4683
|
+
copies.push(s.localId);
|
|
4684
|
+
}
|
|
4685
|
+
return copies;
|
|
4686
|
+
}
|
|
4495
4687
|
emitFunction(fn) {
|
|
4496
4688
|
const B = new BlockBuilder();
|
|
4497
4689
|
this.B = B;
|
|
4690
|
+
this.currentFunctionName = fn.name;
|
|
4498
4691
|
// Fiber switches restore the active exception cell before control
|
|
4499
4692
|
// returns to a synchronous frame (emitPendingCheck).
|
|
4500
4693
|
B.excCellInvariant = fn.async !== true && fn.generator === undefined;
|
|
@@ -4547,6 +4740,11 @@ export class LlEmitter {
|
|
|
4547
4740
|
!this.reboundByFunction.get(fn.name)?.has(fn.params[index].localId))
|
|
4548
4741
|
this.borrowedParameters.add(fn.params[index].localId);
|
|
4549
4742
|
}
|
|
4743
|
+
// A generator frame owns its unchanged parameters across suspensions.
|
|
4744
|
+
const generatorParameters = this.callLifetimes.generatorParameters.get(fn.name);
|
|
4745
|
+
if (generatorParameters !== undefined)
|
|
4746
|
+
for (const id of generatorParameters)
|
|
4747
|
+
this.borrowedParameters.add(id);
|
|
4550
4748
|
this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
|
|
4551
4749
|
this.stableCaptureIds =
|
|
4552
4750
|
fn.captures !== undefined ? this.lazyCaptures.stableCaptures(fn.name) : new Set();
|
|
@@ -4571,6 +4769,8 @@ export class LlEmitter {
|
|
|
4571
4769
|
this.scalarStringSlices =
|
|
4572
4770
|
this.debug === null ? findScalarStringSlices(fn, this.callLifetimes) : new Map();
|
|
4573
4771
|
this.mapReadLifetimes = findMapReadLifetimes(fn, this.boxedUnionsById, this.callLifetimes);
|
|
4772
|
+
this.readNumberMapWrites = findReadNumberMapWrites(fn);
|
|
4773
|
+
this.mapEntryFusion = this.debug === null ? findMapEntryFusions(fn) : null;
|
|
4574
4774
|
this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.boxedUnionsById);
|
|
4575
4775
|
this.localStackObjects = this.stackObjects
|
|
4576
4776
|
? findLocalStackObjects(this, fn, this.callLifetimes)
|
|
@@ -4586,12 +4786,30 @@ export class LlEmitter {
|
|
|
4586
4786
|
this.bytesBounds = findBytesBounds(numericFn, this.integerRanges);
|
|
4587
4787
|
// Debug builds keep every number local a double for the debugger's
|
|
4588
4788
|
// variable view. Lazily boxed captured parameters keep their double,
|
|
4589
|
-
// which the box is created from
|
|
4789
|
+
// which the box is created from, and so do plain copies of a capture or
|
|
4790
|
+
// such a parameter (copyStableCaptures): the source already holds the
|
|
4791
|
+
// double, which an i32 copy would convert and widen again on every call.
|
|
4792
|
+
// Initializer bindings proven int32 move into an i32 local of their
|
|
4793
|
+
// initializer: no other code observes the global, so the loop-carried
|
|
4794
|
+
// value never round-trips through the double slot.
|
|
4795
|
+
const initializerLocals = initializerLocalCandidates(initializerBindings);
|
|
4590
4796
|
const int32Locals = this.debug === null
|
|
4591
|
-
? findInt32Locals(fn, this.integerRanges, this.int32Slots.facts(fn.name), new Set(this.lazyCaptureBoxes.keys()))
|
|
4797
|
+
? findInt32Locals(withInitializerLocals(fn, initializerLocals), this.integerRanges, this.int32Slots.facts(fn.name), new Set([...this.lazyCaptureBoxes.keys(), ...this.doubleCopies(fn)]))
|
|
4592
4798
|
: null;
|
|
4593
4799
|
this.int32Locals = int32Locals?.locals ?? new Map();
|
|
4594
4800
|
this.int32LocalWrites = int32Locals?.values ?? new Set();
|
|
4801
|
+
const localizedGlobals = new Set();
|
|
4802
|
+
for (const global of initializerLocals) {
|
|
4803
|
+
if (!this.int32Locals.has(global.id))
|
|
4804
|
+
continue;
|
|
4805
|
+
localizedGlobals.add(global.id);
|
|
4806
|
+
this.currentLocals.set(global.id, {
|
|
4807
|
+
id: global.id,
|
|
4808
|
+
name: global.name,
|
|
4809
|
+
type: global.type,
|
|
4810
|
+
mutable: global.mutable,
|
|
4811
|
+
});
|
|
4812
|
+
}
|
|
4595
4813
|
this.chainSlots.clear();
|
|
4596
4814
|
this.finallyStack = [];
|
|
4597
4815
|
this.tryStack = [];
|
|
@@ -4633,6 +4851,18 @@ export class LlEmitter {
|
|
|
4633
4851
|
B.line(`${handle} = call ptr @llvm.coro.begin(token ${id}, ptr ${mem})`);
|
|
4634
4852
|
B.line(`call void @${started}(ptr ${handle})`);
|
|
4635
4853
|
B.line(`${self} = call ptr @scr_fiber_self()`);
|
|
4854
|
+
// A native synchronous body owns its generator for the whole run (the
|
|
4855
|
+
// ScrGen owns the frame), so its OUT slot address is fixed.
|
|
4856
|
+
let out = null;
|
|
4857
|
+
const yieldKind = fn.generator?.yieldT.kind;
|
|
4858
|
+
if (!this.wasi &&
|
|
4859
|
+
fn.async !== true &&
|
|
4860
|
+
(yieldKind === "f64" || yieldKind === "date" || yieldKind === "bool")) {
|
|
4861
|
+
this.declare(`declare ptr @scr_gen_of_fiber(ptr)`);
|
|
4862
|
+
const gen = B.tmp();
|
|
4863
|
+
B.line(`${gen} = call ptr @scr_gen_of_fiber(ptr ${self})`);
|
|
4864
|
+
out = this.genOutSlot(gen);
|
|
4865
|
+
}
|
|
4636
4866
|
this.currentCoro = {
|
|
4637
4867
|
kind: fn.generator !== undefined ? "generator" : "async",
|
|
4638
4868
|
id,
|
|
@@ -4641,6 +4871,7 @@ export class LlEmitter {
|
|
|
4641
4871
|
finalLabel: B.newLabel("coro.final"),
|
|
4642
4872
|
cleanupLabel: B.newLabel("coro.cleanup"),
|
|
4643
4873
|
suspendLabel: B.newLabel("coro.suspend"),
|
|
4874
|
+
out,
|
|
4644
4875
|
};
|
|
4645
4876
|
}
|
|
4646
4877
|
const paramIds = new Set(fn.params.map((p) => p.localId));
|
|
@@ -4661,6 +4892,32 @@ export class LlEmitter {
|
|
|
4661
4892
|
B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`);
|
|
4662
4893
|
this.storeIntegerView(global.id, { name: value, type: global.type });
|
|
4663
4894
|
}
|
|
4895
|
+
// A number global only its guarded, synchronous initializer reads or
|
|
4896
|
+
// writes (findInitializerBindings) is invisible to every other function,
|
|
4897
|
+
// and reentry returns before reaching it, so a local slot can stand in
|
|
4898
|
+
// for it: LLVM keeps it in a register across calls and loops that would
|
|
4899
|
+
// otherwise force a store and reload per iteration. The global's
|
|
4900
|
+
// storage is never read again, so nothing writes the slot back. Bindings
|
|
4901
|
+
// proven int32 already live in an i32 local (localizedGlobals above);
|
|
4902
|
+
// this covers the remaining doubles.
|
|
4903
|
+
this.privateGlobalSlots.clear();
|
|
4904
|
+
if (this.debug === null && this.currentCoro === null && !fn.async && !fn.generator) {
|
|
4905
|
+
const conditional = initializerLocals.length ? conditionalLoopWrites(fn.body) : null;
|
|
4906
|
+
for (const global of initializerLocals) {
|
|
4907
|
+
if (localizedGlobals.has(global.id))
|
|
4908
|
+
continue;
|
|
4909
|
+
// A conditional update in a loop stays a memory update (see
|
|
4910
|
+
// conditionalLoopWrites).
|
|
4911
|
+
if (conditional?.has(global.id))
|
|
4912
|
+
continue;
|
|
4913
|
+
const slot = B.slot();
|
|
4914
|
+
const value = B.tmp();
|
|
4915
|
+
B.entryAllocas.push(`${slot} = alloca double ; module binding ${global.name}`);
|
|
4916
|
+
B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`);
|
|
4917
|
+
B.line(`store double ${value}, ptr ${slot}`);
|
|
4918
|
+
this.privateGlobalSlots.set(global.id, slot);
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
4664
4921
|
for (const local of this.currentLocals.values()) {
|
|
4665
4922
|
// Boxed locals' slots hold their capture BOX (a ptr); captured
|
|
4666
4923
|
// (env-borrowed) locals bind the incoming box below. A caught-typed
|
|
@@ -4673,6 +4930,9 @@ export class LlEmitter {
|
|
|
4673
4930
|
: this.llType(local.type);
|
|
4674
4931
|
B.entryAllocas.push(`%${mangleLocal(local.id)} = alloca ${slotTy} ; ${local.name}`);
|
|
4675
4932
|
this.emitDebugLocal(local, `%${mangleLocal(local.id)}`, fn.params.findIndex((p) => p.localId === local.id) + 1, this.captureIds.has(local.id));
|
|
4933
|
+
// A localized initializer binding starts at its global's zero value.
|
|
4934
|
+
if (localizedGlobals.has(local.id))
|
|
4935
|
+
B.line(`store i32 0, ptr %${mangleLocal(local.id)}`);
|
|
4676
4936
|
// Refcounted/boxed locals start NULL (the C prologue's `= NULL`):
|
|
4677
4937
|
// scope-exit releases run whether or not an assign ever did.
|
|
4678
4938
|
if (paramIds.has(local.id) || this.captureIds.has(local.id))
|
|
@@ -4892,9 +5152,17 @@ export class LlEmitter {
|
|
|
4892
5152
|
attrs = `inlinehint ${attrs}`;
|
|
4893
5153
|
const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`;
|
|
4894
5154
|
const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
|
|
5155
|
+
this.privateGlobalSlots.clear();
|
|
4895
5156
|
let body = `define internal ${ret} @${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
|
|
4896
5157
|
if (int32Abi)
|
|
4897
5158
|
body += "\n" + this.emitInt32AbiWrapper(fn, symbol, int32Abi);
|
|
5159
|
+
if (this.integerClones.members.has(fn.name)) {
|
|
5160
|
+
body += "\n" + emitIntegerClone(fn, this, this.stackChecks?.has(fn.name) === true);
|
|
5161
|
+
if (this.integerClones.dispatched.has(fn.name))
|
|
5162
|
+
body +=
|
|
5163
|
+
"\n" +
|
|
5164
|
+
emitIntegerDispatcher(fn, this, `${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}`, int32Abi?.params ?? [], int32Abi?.result === true);
|
|
5165
|
+
}
|
|
4898
5166
|
if (!borrowed)
|
|
4899
5167
|
return body;
|
|
4900
5168
|
const slot = this.implSlotBorrowed(fn.name);
|
|
@@ -5761,6 +6029,8 @@ export class LlEmitter {
|
|
|
5761
6029
|
// the replaced element itself). A local receiver that the index and
|
|
5762
6030
|
// value cannot rebind is read in place, as for field stores; the
|
|
5763
6031
|
// released element cannot reach that binding.
|
|
6032
|
+
if (this.emitArraySelfConcat(s))
|
|
6033
|
+
break;
|
|
5764
6034
|
const arr = this.emitStableReceiver(s.arr, [s.index, s.value]);
|
|
5765
6035
|
const idx = this.emitExpr(s.index);
|
|
5766
6036
|
const v = this.emitExpr(s.value);
|
|
@@ -5795,8 +6065,8 @@ export class LlEmitter {
|
|
|
5795
6065
|
const resize = B.newLabel("arr.len.set"), done = B.newLabel("arr.len.done");
|
|
5796
6066
|
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
5797
6067
|
this.markMemoryPointer(lenPtr, "array:header");
|
|
5798
|
-
B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
5799
|
-
B.line(`${lenNumber} =
|
|
6068
|
+
B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}${this.arrayLengthRange}`);
|
|
6069
|
+
B.line(`${lenNumber} = ${sizeToDouble(this.sizeType)} ${this.sizeType} ${len} to double`);
|
|
5800
6070
|
B.line(`${same} = fcmp oeq double ${length.name}, ${lenNumber}`);
|
|
5801
6071
|
B.condBr(same, done, resize);
|
|
5802
6072
|
B.startBlock(resize);
|
|
@@ -5859,6 +6129,8 @@ export class LlEmitter {
|
|
|
5859
6129
|
this.storeInt32Field(ptr, v, s.value);
|
|
5860
6130
|
break;
|
|
5861
6131
|
}
|
|
6132
|
+
if (this.emitFieldSelfConcat(s))
|
|
6133
|
+
break;
|
|
5862
6134
|
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
5863
6135
|
const v = this.emitFieldValue(s.value, s.kind === "recordSet"
|
|
5864
6136
|
? this.recordFieldInline(s.shapeId, s.field)
|
|
@@ -6088,15 +6360,15 @@ export class LlEmitter {
|
|
|
6088
6360
|
const lc = B.newLabel("loop.c");
|
|
6089
6361
|
const lb = B.newLabel("loop.b");
|
|
6090
6362
|
const le = B.newLabel("loop.e");
|
|
6363
|
+
const lp = workerBudget !== null ? B.newLabel("loop.p") : lc;
|
|
6091
6364
|
B.br(lc);
|
|
6092
6365
|
B.startBlock(lc);
|
|
6093
|
-
this.emitWorkerLoopCheck(workerBudget);
|
|
6094
6366
|
B.condBr(this.emitCondition(s.cond), lb, le);
|
|
6095
6367
|
B.startBlock(lb);
|
|
6096
6368
|
this.jumpTargets.push({
|
|
6097
6369
|
kind: "loop",
|
|
6098
6370
|
brkLabel: le,
|
|
6099
|
-
contLabel:
|
|
6371
|
+
contLabel: lp,
|
|
6100
6372
|
labels: s.labels,
|
|
6101
6373
|
frameDepth: this.frames.length,
|
|
6102
6374
|
scopeDepth: this.scopes.length,
|
|
@@ -6104,7 +6376,12 @@ export class LlEmitter {
|
|
|
6104
6376
|
});
|
|
6105
6377
|
this.emitBlock(s.body);
|
|
6106
6378
|
this.jumpTargets.pop();
|
|
6107
|
-
B.br(
|
|
6379
|
+
B.br(lp);
|
|
6380
|
+
if (lp !== lc) {
|
|
6381
|
+
B.startBlock(lp);
|
|
6382
|
+
this.emitWorkerLoopCheck(workerBudget);
|
|
6383
|
+
B.br(lc);
|
|
6384
|
+
}
|
|
6108
6385
|
B.startBlock(le);
|
|
6109
6386
|
break;
|
|
6110
6387
|
}
|
|
@@ -6116,7 +6393,6 @@ export class LlEmitter {
|
|
|
6116
6393
|
const le = B.newLabel("loop.e");
|
|
6117
6394
|
B.br(lb);
|
|
6118
6395
|
B.startBlock(lb);
|
|
6119
|
-
this.emitWorkerLoopCheck(workerBudget);
|
|
6120
6396
|
this.jumpTargets.push({
|
|
6121
6397
|
kind: "loop",
|
|
6122
6398
|
brkLabel: le,
|
|
@@ -6130,6 +6406,7 @@ export class LlEmitter {
|
|
|
6130
6406
|
this.jumpTargets.pop();
|
|
6131
6407
|
B.br(lc);
|
|
6132
6408
|
B.startBlock(lc);
|
|
6409
|
+
this.emitWorkerLoopCheck(workerBudget);
|
|
6133
6410
|
B.condBr(this.emitCondition(s.cond), lb, le);
|
|
6134
6411
|
B.startBlock(le);
|
|
6135
6412
|
break;
|
|
@@ -6289,12 +6566,11 @@ export class LlEmitter {
|
|
|
6289
6566
|
B.line(`store ptr ${fresh}, ptr ${slot}`);
|
|
6290
6567
|
}
|
|
6291
6568
|
};
|
|
6292
|
-
const lu = s.update || capturedLets.length > 0 ? B.newLabel("loop.u") : lc;
|
|
6293
|
-
freshenBindings();
|
|
6294
6569
|
const workerBudget = this.workerLoopBudget(s);
|
|
6570
|
+
const lu = s.update || capturedLets.length > 0 || workerBudget !== null ? B.newLabel("loop.u") : lc;
|
|
6571
|
+
freshenBindings();
|
|
6295
6572
|
B.br(lc);
|
|
6296
6573
|
B.startBlock(lc);
|
|
6297
|
-
this.emitWorkerLoopCheck(workerBudget);
|
|
6298
6574
|
if (integerLoop && integerSlot) {
|
|
6299
6575
|
const receiver = this.emitStableReceiver(integerLoop.limitReceiver, []);
|
|
6300
6576
|
const lenPtr = B.tmp();
|
|
@@ -6335,6 +6611,7 @@ export class LlEmitter {
|
|
|
6335
6611
|
B.br(lu);
|
|
6336
6612
|
if (lu !== lc) {
|
|
6337
6613
|
B.startBlock(lu);
|
|
6614
|
+
this.emitWorkerLoopCheck(workerBudget);
|
|
6338
6615
|
freshenBindings();
|
|
6339
6616
|
if (integerLoop && integerSlot) {
|
|
6340
6617
|
const old = B.tmp();
|
|
@@ -6402,7 +6679,6 @@ export class LlEmitter {
|
|
|
6402
6679
|
const workerBudget = this.workerLoopBudget(s);
|
|
6403
6680
|
B.br(lc);
|
|
6404
6681
|
B.startBlock(lc);
|
|
6405
|
-
this.emitWorkerLoopCheck(workerBudget);
|
|
6406
6682
|
const inBounds = B.tmp();
|
|
6407
6683
|
let cur;
|
|
6408
6684
|
if (snapshot && cursor && scratch) {
|
|
@@ -6417,8 +6693,8 @@ export class LlEmitter {
|
|
|
6417
6693
|
// The inline form of scr_arr_len, like the `length` intrinsic.
|
|
6418
6694
|
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
6419
6695
|
this.markMemoryPointer(lenPtr, "array:header");
|
|
6420
|
-
B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
6421
|
-
B.line(`${len} =
|
|
6696
|
+
B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}${this.arrayLengthRange}`);
|
|
6697
|
+
B.line(`${len} = ${sizeToDouble(this.sizeType)} ${this.sizeType} ${rawLen} to double`);
|
|
6422
6698
|
B.line(`${inBounds} = fcmp olt double ${i}, ${len}`);
|
|
6423
6699
|
cur = i;
|
|
6424
6700
|
}
|
|
@@ -6476,6 +6752,7 @@ export class LlEmitter {
|
|
|
6476
6752
|
this.jumpTargets.pop();
|
|
6477
6753
|
B.br(lu);
|
|
6478
6754
|
B.startBlock(lu);
|
|
6755
|
+
this.emitWorkerLoopCheck(workerBudget);
|
|
6479
6756
|
if (idxSlot) {
|
|
6480
6757
|
const i2 = B.tmp(), i3 = B.tmp();
|
|
6481
6758
|
B.line(`${i2} = load double, ptr ${idxSlot}`);
|
|
@@ -7123,6 +7400,118 @@ export class LlEmitter {
|
|
|
7123
7400
|
return null;
|
|
7124
7401
|
return this.emitExpr(packed.elems[0]);
|
|
7125
7402
|
}
|
|
7403
|
+
/**
|
|
7404
|
+
* Lower `recv.f = recv.f + suffix` on a plain string field like
|
|
7405
|
+
* emitStringSelfConcatAssign does for locals. The field's old value is
|
|
7406
|
+
* snapshotted (+1) and the suffix evaluated in JavaScript order; then the
|
|
7407
|
+
* field unlinks and releases its CURRENT value, so the snapshot is unique
|
|
7408
|
+
* unless a real alias exists and the concat appends in place instead of
|
|
7409
|
+
* copying the whole string on every append. Returns false (emitting
|
|
7410
|
+
* nothing) for every other store.
|
|
7411
|
+
*/
|
|
7412
|
+
emitFieldSelfConcat(s) {
|
|
7413
|
+
const suffix = matchFieldSelfConcat(s);
|
|
7414
|
+
if (!suffix || s.value.kind !== "strConcat")
|
|
7415
|
+
return false;
|
|
7416
|
+
let fieldType;
|
|
7417
|
+
if (s.kind === "fieldSet") {
|
|
7418
|
+
if (this.inlineUnions.classField(s.className, s.field) ||
|
|
7419
|
+
this.classMeta.get(s.className)?.root.def.name === "%Error")
|
|
7420
|
+
return false;
|
|
7421
|
+
fieldType = classFieldIndex(this.classMetaOf(s.className), s.field).type;
|
|
7422
|
+
}
|
|
7423
|
+
else {
|
|
7424
|
+
fieldType = this.recordShape(s.shapeId).fields.find((f) => f.name === s.field)?.type;
|
|
7425
|
+
}
|
|
7426
|
+
if (fieldType?.kind !== "string")
|
|
7427
|
+
return false;
|
|
7428
|
+
const B = this.B;
|
|
7429
|
+
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
7430
|
+
const snapshot = this.emitExpr(s.value.left);
|
|
7431
|
+
const parts = stringParts(suffix);
|
|
7432
|
+
const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
|
|
7433
|
+
const inputs = mixed ? emitConcatInputs(this, parts) : [];
|
|
7434
|
+
const right = mixed ? null : this.emitExpr(suffix);
|
|
7435
|
+
if (s.kind === "fieldSet")
|
|
7436
|
+
this.emitStoreGuard(obj.name, s);
|
|
7437
|
+
else if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
|
|
7438
|
+
this.emitPublishedFieldGuard(obj.name, null, s.field);
|
|
7439
|
+
const { ptr } = s.kind === "fieldSet"
|
|
7440
|
+
? this.classFieldPtr(obj.name, s.className, s.field)
|
|
7441
|
+
: this.recordFieldPtr(obj.name, s.shapeId, s.field);
|
|
7442
|
+
// Unlink, then release: nothing between here and the store below can
|
|
7443
|
+
// run user code or observe the field (the concat only traps on OOM).
|
|
7444
|
+
// Only an owned snapshot may survive the field's release.
|
|
7445
|
+
const owned = this.ownsSnapshot(snapshot.name);
|
|
7446
|
+
if (owned) {
|
|
7447
|
+
const old = this.loadField(ptr, fieldType, false);
|
|
7448
|
+
this.storeField(ptr, fieldType, "null", false);
|
|
7449
|
+
this.releaseValue(old, fieldType);
|
|
7450
|
+
}
|
|
7451
|
+
let raw;
|
|
7452
|
+
if (right === null) {
|
|
7453
|
+
raw = emitMixedConcat(this, snapshot.name, inputs);
|
|
7454
|
+
}
|
|
7455
|
+
else {
|
|
7456
|
+
this.declare(`declare ptr @scr_str_concat(ptr, ptr)`);
|
|
7457
|
+
raw = B.tmp();
|
|
7458
|
+
B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`);
|
|
7459
|
+
}
|
|
7460
|
+
// The +1 result moves straight into the (now empty) field.
|
|
7461
|
+
if (owned)
|
|
7462
|
+
this.storeField(ptr, fieldType, raw, false);
|
|
7463
|
+
else
|
|
7464
|
+
this.replaceField(ptr, fieldType, false, this.own({ name: raw, type: fieldType }));
|
|
7465
|
+
return true;
|
|
7466
|
+
}
|
|
7467
|
+
/** A snapshot the current statement frames own (+1), or an immortal
|
|
7468
|
+
* value: it stays valid after its container releases the same string. */
|
|
7469
|
+
ownsSnapshot(name) {
|
|
7470
|
+
return (this.isImmortal(name) || this.frames.some((f) => f.some((v) => v.name === name && !v.slot)));
|
|
7471
|
+
}
|
|
7472
|
+
/**
|
|
7473
|
+
* `a[i] = a[i] + suffix` on a string array (the canonical statement form
|
|
7474
|
+
* of `a[i] += suffix`): the old element is snapshotted (+1) and the
|
|
7475
|
+
* suffix evaluated in JavaScript order; then, when the slot still holds
|
|
7476
|
+
* the snapshot, the array lets go of it (emitDenseSlotUnlink) so the
|
|
7477
|
+
* concat can append in place, and the result is stored as usual.
|
|
7478
|
+
*/
|
|
7479
|
+
emitArraySelfConcat(s) {
|
|
7480
|
+
const value = s.value;
|
|
7481
|
+
if (value.kind !== "strConcat" ||
|
|
7482
|
+
value.type.kind !== "string" ||
|
|
7483
|
+
value.left.type.kind !== "string" ||
|
|
7484
|
+
value.right.type.kind !== "string" ||
|
|
7485
|
+
s.arr.type.kind !== "array" ||
|
|
7486
|
+
s.arr.type.elem.kind !== "string" ||
|
|
7487
|
+
s.arr.kind !== "varRef" ||
|
|
7488
|
+
s.index.kind !== "varRef" ||
|
|
7489
|
+
!readsArraySlot(value.left, s.arr.localId, s.index.localId))
|
|
7490
|
+
return false;
|
|
7491
|
+
const elem = s.arr.type.elem;
|
|
7492
|
+
const B = this.B;
|
|
7493
|
+
const arr = this.emitStableReceiver(s.arr, [s.index, s.value]);
|
|
7494
|
+
const idx = this.emitExpr(s.index);
|
|
7495
|
+
const snapshot = this.emitExpr(value.left);
|
|
7496
|
+
const parts = stringParts(value.right);
|
|
7497
|
+
const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
|
|
7498
|
+
const inputs = mixed ? emitConcatInputs(this, parts) : [];
|
|
7499
|
+
const right = mixed ? null : this.emitExpr(value.right);
|
|
7500
|
+
this.emitPublishedArrayGuard(arr.name, s.arr.type, 0, idx.name);
|
|
7501
|
+
if (this.ownsSnapshot(snapshot.name))
|
|
7502
|
+
emitDenseSlotUnlink(this, arr.name, idx, s.index, snapshot.name, this.internLiteral(""), elem);
|
|
7503
|
+
let raw;
|
|
7504
|
+
if (right === null) {
|
|
7505
|
+
raw = emitMixedConcat(this, snapshot.name, inputs);
|
|
7506
|
+
}
|
|
7507
|
+
else {
|
|
7508
|
+
this.declare(`declare ptr @scr_str_concat(ptr, ptr)`);
|
|
7509
|
+
raw = B.tmp();
|
|
7510
|
+
B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`);
|
|
7511
|
+
}
|
|
7512
|
+
emitDenseArraySet(this, arr.name, idx, s.index, "ref", elem, raw);
|
|
7513
|
+
return true;
|
|
7514
|
+
}
|
|
7126
7515
|
/** `local = local + suffix` when the suffix cannot read or write the
|
|
7127
7516
|
* local (an unboxed binding is only reachable through this function's own
|
|
7128
7517
|
* expressions): reading the binding after the suffix gives the value the
|
|
@@ -7192,8 +7581,122 @@ export class LlEmitter {
|
|
|
7192
7581
|
e.name === "region.leave" ||
|
|
7193
7582
|
e.name === "region.release")
|
|
7194
7583
|
return this.emitRegionIntrinsic(e);
|
|
7584
|
+
if (e.name.startsWith("itable."))
|
|
7585
|
+
return this.emitInternTableIntrinsic(e);
|
|
7195
7586
|
return emitIntrinsicExpr(this, e);
|
|
7196
7587
|
}
|
|
7588
|
+
/** InternTable's runtime calls (scr_itable.c). Table and entry handles
|
|
7589
|
+
* are f64 values carrying pointer bits; entry values are 64-bit slots
|
|
7590
|
+
* (double bits, a bool, or a pointer to an immortal value) that the
|
|
7591
|
+
* table neither retains nor releases. Lookups and inserts of array keys
|
|
7592
|
+
* throw a TypeError for keys longer than a table accepts. */
|
|
7593
|
+
emitInternTableIntrinsic(e) {
|
|
7594
|
+
const B = this.B;
|
|
7595
|
+
// The runtime copies keys and stores published values without counts,
|
|
7596
|
+
// so every operand is borrowed.
|
|
7597
|
+
const args = emitBorrowedInputs(this, e.args);
|
|
7598
|
+
if (B.isTerminated())
|
|
7599
|
+
return { name: "", type: e.type };
|
|
7600
|
+
const toPtr = (handle) => {
|
|
7601
|
+
const bits = B.tmp(), ptr = B.tmp();
|
|
7602
|
+
B.line(`${bits} = bitcast double ${handle} to i64`);
|
|
7603
|
+
B.line(`${ptr} = inttoptr i64 ${bits} to ptr`);
|
|
7604
|
+
return ptr;
|
|
7605
|
+
};
|
|
7606
|
+
const toHandle = (ptr) => {
|
|
7607
|
+
const bits = B.tmp(), handle = B.tmp();
|
|
7608
|
+
B.line(`${bits} = ptrtoint ptr ${ptr} to i64`);
|
|
7609
|
+
B.line(`${handle} = bitcast i64 ${bits} to double`);
|
|
7610
|
+
return { name: handle, type: e.type };
|
|
7611
|
+
};
|
|
7612
|
+
const keyCall = (op) => {
|
|
7613
|
+
const kind = e.args[1].type.kind;
|
|
7614
|
+
const key = kind === "string" ? "str" : kind === "array" ? "arr" : "f64";
|
|
7615
|
+
const keyTy = key === "f64" ? "double" : "ptr";
|
|
7616
|
+
const value = op === "insert" ? ", i64" : "";
|
|
7617
|
+
this.declare(`declare ptr @scr_itable_${op}_${key}(ptr, ${keyTy}${value})`);
|
|
7618
|
+
return `@scr_itable_${op}_${key}(ptr ${toPtr(args[0].name)}, ${keyTy} ${args[1].name}`;
|
|
7619
|
+
};
|
|
7620
|
+
switch (e.name) {
|
|
7621
|
+
case "itable.open": {
|
|
7622
|
+
this.declare(`declare ptr @scr_itable_open(ptr, i32)`);
|
|
7623
|
+
const kind = B.tmp(), table = B.tmp();
|
|
7624
|
+
B.line(`${kind} = fptosi double ${args[1].name} to i32`);
|
|
7625
|
+
B.line(`${table} = call ptr @scr_itable_open(ptr ${args[0].name}, i32 ${kind})`);
|
|
7626
|
+
return toHandle(table);
|
|
7627
|
+
}
|
|
7628
|
+
case "itable.find": {
|
|
7629
|
+
const call = keyCall("find");
|
|
7630
|
+
const entry = B.tmp();
|
|
7631
|
+
B.line(`${entry} = call ptr ${call})`);
|
|
7632
|
+
// Array keys longer than a table accepts throw a TypeError.
|
|
7633
|
+
if (e.args[1].type.kind === "array")
|
|
7634
|
+
this.emitPendingCheck();
|
|
7635
|
+
return toHandle(entry);
|
|
7636
|
+
}
|
|
7637
|
+
case "itable.insert": {
|
|
7638
|
+
const value = args[2];
|
|
7639
|
+
const ty = this.llType(value.type);
|
|
7640
|
+
let slot = value.name;
|
|
7641
|
+
if (ty === "double") {
|
|
7642
|
+
slot = B.tmp();
|
|
7643
|
+
B.line(`${slot} = bitcast double ${value.name} to i64`);
|
|
7644
|
+
}
|
|
7645
|
+
else if (ty === "i1") {
|
|
7646
|
+
slot = B.tmp();
|
|
7647
|
+
B.line(`${slot} = zext i1 ${value.name} to i64`);
|
|
7648
|
+
}
|
|
7649
|
+
else if (ty === "ptr") {
|
|
7650
|
+
slot = B.tmp();
|
|
7651
|
+
B.line(`${slot} = ptrtoint ptr ${value.name} to i64`);
|
|
7652
|
+
}
|
|
7653
|
+
else
|
|
7654
|
+
throw new InternalCompilerError(`llvm emitter bug: itable value of type ${ty}`);
|
|
7655
|
+
const call = keyCall("insert");
|
|
7656
|
+
const entry = B.tmp();
|
|
7657
|
+
B.line(`${entry} = call ptr ${call}, i64 ${slot})`);
|
|
7658
|
+
if (e.args[1].type.kind === "array")
|
|
7659
|
+
this.emitPendingCheck();
|
|
7660
|
+
return toHandle(entry);
|
|
7661
|
+
}
|
|
7662
|
+
case "itable.value": {
|
|
7663
|
+
// The handle's bits reach the address through memory: at -O0, x86
|
|
7664
|
+
// FastISel folds inttoptr(bitcast double) into the load's address
|
|
7665
|
+
// without moving the value out of its XMM register. Optimized
|
|
7666
|
+
// builds turn the stack round trip back into one register move.
|
|
7667
|
+
const carrier = B.slot();
|
|
7668
|
+
B.entryAllocas.push(`${carrier} = alloca double`);
|
|
7669
|
+
B.line(`store double ${args[0].name}, ptr ${carrier}`);
|
|
7670
|
+
const bits = B.tmp(), entry = B.tmp();
|
|
7671
|
+
B.line(`${bits} = load i64, ptr ${carrier}`);
|
|
7672
|
+
B.line(`${entry} = inttoptr i64 ${bits} to ptr`);
|
|
7673
|
+
const slot = B.tmp();
|
|
7674
|
+
B.line(`${slot} = load i64, ptr ${entry}, align 8`);
|
|
7675
|
+
const ty = this.llType(e.type);
|
|
7676
|
+
const result = B.tmp();
|
|
7677
|
+
if (ty === "double")
|
|
7678
|
+
B.line(`${result} = bitcast i64 ${slot} to double`);
|
|
7679
|
+
else if (ty === "i1")
|
|
7680
|
+
B.line(`${result} = trunc i64 ${slot} to i1`);
|
|
7681
|
+
else if (ty === "ptr")
|
|
7682
|
+
B.line(`${result} = inttoptr i64 ${slot} to ptr`);
|
|
7683
|
+
else
|
|
7684
|
+
throw new InternalCompilerError(`llvm emitter bug: itable value of type ${ty}`);
|
|
7685
|
+
// Stored values are immortal: owning one adds no count.
|
|
7686
|
+
return isRefCounted(e.type)
|
|
7687
|
+
? this.own({ name: result, type: e.type })
|
|
7688
|
+
: { name: result, type: e.type };
|
|
7689
|
+
}
|
|
7690
|
+
case "itable.size": {
|
|
7691
|
+
this.declare(`declare double @scr_itable_size(ptr)`);
|
|
7692
|
+
const size = B.tmp();
|
|
7693
|
+
B.line(`${size} = call double @scr_itable_size(ptr ${toPtr(args[0].name)})`);
|
|
7694
|
+
return { name: size, type: e.type };
|
|
7695
|
+
}
|
|
7696
|
+
default:
|
|
7697
|
+
throw new InternalCompilerError(`llvm emitter bug: unknown intrinsic ${e.name}`);
|
|
7698
|
+
}
|
|
7699
|
+
}
|
|
7197
7700
|
/** Lifetime regions in modules with lifetimeRegions (llvm/regions.ts).
|
|
7198
7701
|
* Created with the class graph; null elsewhere. */
|
|
7199
7702
|
regions = null;
|
|
@@ -7236,6 +7739,7 @@ export class LlEmitter {
|
|
|
7236
7739
|
declare: (decl) => this.declare(decl),
|
|
7237
7740
|
cstr: (text) => this.cstr(text),
|
|
7238
7741
|
sizeType: this.sizeType,
|
|
7742
|
+
rcAudit: this.shapeHost.objectAudit !== false,
|
|
7239
7743
|
tracedShapes: this.tracedShapes,
|
|
7240
7744
|
unionsById: this.unionsById,
|
|
7241
7745
|
recordsById: this.recordsById,
|
|
@@ -7950,6 +8454,9 @@ export class LlEmitter {
|
|
|
7950
8454
|
emitterFixedAdapter(cbT) {
|
|
7951
8455
|
return emitterFixedAdapter(this, cbT);
|
|
7952
8456
|
}
|
|
8457
|
+
emitterOnceAdapter(boxerT) {
|
|
8458
|
+
return emitterOnceAdapter(this, boxerT);
|
|
8459
|
+
}
|
|
7953
8460
|
wrapEmitterListener(target, adapterFn) {
|
|
7954
8461
|
return wrapEmitterListener(this, target, adapterFn);
|
|
7955
8462
|
}
|