@scriptc/compiler 0.3.0 → 0.3.2
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 +346 -17
- package/dist/backend/executable-features.js +1 -0
- package/dist/backend/executable-features.js.map +1 -1
- package/dist/backend/literals.d.ts +7 -0
- package/dist/backend/literals.js +32 -0
- package/dist/backend/literals.js.map +1 -1
- package/dist/backend/literals.test.js +17 -1
- package/dist/backend/literals.test.js.map +1 -1
- package/dist/backend/llvm/alloc.d.ts +15 -4
- package/dist/backend/llvm/alloc.js +39 -15
- package/dist/backend/llvm/alloc.js.map +1 -1
- package/dist/backend/llvm/alloc.test.js +15 -1
- package/dist/backend/llvm/alloc.test.js.map +1 -1
- package/dist/backend/llvm/appended-array-literals.d.ts +2 -0
- package/dist/backend/llvm/appended-array-literals.js +109 -0
- package/dist/backend/llvm/appended-array-literals.js.map +1 -0
- package/dist/backend/llvm/appended-array-literals.test.d.ts +1 -0
- package/dist/backend/llvm/appended-array-literals.test.js +90 -0
- package/dist/backend/llvm/appended-array-literals.test.js.map +1 -0
- package/dist/backend/llvm/bigint-wrap.d.ts +4 -0
- package/dist/backend/llvm/bigint-wrap.js +590 -0
- package/dist/backend/llvm/bigint-wrap.js.map +1 -0
- package/dist/backend/llvm/blocks.d.ts +2 -0
- package/dist/backend/llvm/blocks.js +4 -0
- package/dist/backend/llvm/blocks.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.d.ts +4 -1
- package/dist/backend/llvm/call-lifetimes.js +66 -7
- package/dist/backend/llvm/call-lifetimes.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.test.js +14 -3
- package/dist/backend/llvm/call-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/checked-narrows.test.js +1 -1
- package/dist/backend/llvm/checked-narrows.test.js.map +1 -1
- package/dist/backend/llvm/classes.js +67 -14
- package/dist/backend/llvm/classes.js.map +1 -1
- package/dist/backend/llvm/classes.test.js +6 -6
- package/dist/backend/llvm/classes.test.js.map +1 -1
- package/dist/backend/llvm/common.d.ts +7 -5
- package/dist/backend/llvm/common.js +7 -5
- package/dist/backend/llvm/common.js.map +1 -1
- package/dist/backend/llvm/constant-callbacks.js +10 -28
- package/dist/backend/llvm/constant-callbacks.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.js +146 -53
- package/dist/backend/llvm/dense-array-access.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.test.js +68 -3
- package/dist/backend/llvm/dense-array-access.test.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +4 -0
- package/dist/backend/llvm/dyn.js +15 -2
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +163 -8
- package/dist/backend/llvm/emitter.js +973 -100
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.test.js +14 -7
- package/dist/backend/llvm/emitter.test.js.map +1 -1
- package/dist/backend/llvm/expr-async.js +242 -49
- package/dist/backend/llvm/expr-async.js.map +1 -1
- package/dist/backend/llvm/expr-async.test.js +1 -1
- package/dist/backend/llvm/expr-async.test.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.js +51 -36
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-callbacks.d.ts +8 -0
- package/dist/backend/llvm/expr-callbacks.js +153 -4
- package/dist/backend/llvm/expr-callbacks.js.map +1 -1
- package/dist/backend/llvm/expr-calls.d.ts +12 -1
- package/dist/backend/llvm/expr-calls.js +279 -78
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-calls.test.d.ts +1 -0
- package/dist/backend/llvm/expr-calls.test.js +132 -0
- package/dist/backend/llvm/expr-calls.test.js.map +1 -0
- package/dist/backend/llvm/expr-containers.d.ts +7 -0
- package/dist/backend/llvm/expr-containers.js +49 -5
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-context.d.ts +4 -0
- package/dist/backend/llvm/expr-control.d.ts +8 -0
- package/dist/backend/llvm/expr-control.js +103 -69
- package/dist/backend/llvm/expr-control.js.map +1 -1
- package/dist/backend/llvm/expr-dispatch.js +3 -0
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +28 -6
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.d.ts +14 -0
- package/dist/backend/llvm/expr-primitives.js +110 -48
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.test.js +95 -5
- package/dist/backend/llvm/expr-primitives.test.js.map +1 -1
- package/dist/backend/llvm/expr-records.d.ts +9 -0
- package/dist/backend/llvm/expr-records.js +144 -16
- package/dist/backend/llvm/expr-records.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.d.ts +9 -1
- package/dist/backend/llvm/expr-stream-bridges.js +152 -4
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.test.js +98 -1
- package/dist/backend/llvm/expr-stream-bridges.test.js.map +1 -1
- package/dist/backend/llvm/int32-abi.d.ts +47 -0
- package/dist/backend/llvm/int32-abi.js +83 -0
- package/dist/backend/llvm/int32-abi.js.map +1 -0
- package/dist/backend/llvm/int32-abi.test.d.ts +1 -0
- package/dist/backend/llvm/int32-abi.test.js +181 -0
- package/dist/backend/llvm/int32-abi.test.js.map +1 -0
- package/dist/backend/llvm/int32-locals.d.ts +30 -0
- package/dist/backend/llvm/int32-locals.js +95 -0
- package/dist/backend/llvm/int32-locals.js.map +1 -0
- package/dist/backend/llvm/integer-values.d.ts +4 -0
- package/dist/backend/llvm/integer-values.js +30 -1
- package/dist/backend/llvm/integer-values.js.map +1 -1
- package/dist/backend/llvm/integer-views.d.ts +8 -1
- package/dist/backend/llvm/integer-views.js +47 -2
- package/dist/backend/llvm/integer-views.js.map +1 -1
- package/dist/backend/llvm/lazy-captures.d.ts +16 -0
- package/dist/backend/llvm/lazy-captures.js +88 -2
- package/dist/backend/llvm/lazy-captures.js.map +1 -1
- package/dist/backend/llvm/lazy-captures.test.js +48 -0
- package/dist/backend/llvm/lazy-captures.test.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +21 -0
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +61 -0
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +83 -9
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.js +26 -5
- package/dist/backend/llvm/local-array-reads.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.test.js +3 -2
- package/dist/backend/llvm/local-array-reads.test.js.map +1 -1
- package/dist/backend/llvm/local-union-storage.d.ts +24 -2
- package/dist/backend/llvm/local-union-storage.js +127 -12
- package/dist/backend/llvm/local-union-storage.js.map +1 -1
- package/dist/backend/llvm/local-union-storage.test.js +108 -3
- package/dist/backend/llvm/local-union-storage.test.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.test.js +2 -2
- package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/record-results.d.ts +26 -0
- package/dist/backend/llvm/record-results.js +252 -0
- package/dist/backend/llvm/record-results.js.map +1 -0
- package/dist/backend/llvm/reference-effects.js +4 -0
- package/dist/backend/llvm/reference-effects.js.map +1 -1
- package/dist/backend/llvm/reference-writes.d.ts +3 -0
- package/dist/backend/llvm/reference-writes.js +25 -11
- package/dist/backend/llvm/reference-writes.js.map +1 -1
- package/dist/backend/llvm/regex-matchers.d.ts +39 -0
- package/dist/backend/llvm/regex-matchers.js +1649 -0
- package/dist/backend/llvm/regex-matchers.js.map +1 -0
- package/dist/backend/llvm/regex-matchers.test.d.ts +1 -0
- package/dist/backend/llvm/regex-matchers.test.js +96 -0
- package/dist/backend/llvm/regex-matchers.test.js.map +1 -0
- package/dist/backend/llvm/regions.d.ts +67 -0
- package/dist/backend/llvm/regions.js +309 -0
- package/dist/backend/llvm/regions.js.map +1 -0
- package/dist/backend/llvm/regions.test.d.ts +1 -0
- package/dist/backend/llvm/regions.test.js +173 -0
- package/dist/backend/llvm/regions.test.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +70 -8
- package/dist/backend/llvm/shapes.js +157 -29
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/shapes.test.js +7 -6
- package/dist/backend/llvm/shapes.test.js.map +1 -1
- package/dist/backend/llvm/split-loops.js +1 -1
- package/dist/backend/llvm/split-loops.js.map +1 -1
- package/dist/backend/llvm/stack-objects.d.ts +29 -0
- package/dist/backend/llvm/stack-objects.js +130 -0
- package/dist/backend/llvm/stack-objects.js.map +1 -0
- package/dist/backend/llvm/stack-objects.test.d.ts +1 -0
- package/dist/backend/llvm/stack-objects.test.js +252 -0
- package/dist/backend/llvm/stack-objects.test.js.map +1 -0
- package/dist/backend/llvm/stack-unions.test.js +1 -1
- package/dist/backend/llvm/stack-unions.test.js.map +1 -1
- package/dist/backend/llvm/string-construction.test.js +2 -2
- package/dist/backend/llvm/string-construction.test.js.map +1 -1
- package/dist/backend/llvm/string-lifetimes.test.js +5 -5
- package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/string-literal-equality.d.ts +22 -0
- package/dist/backend/llvm/string-literal-equality.js +78 -0
- package/dist/backend/llvm/string-literal-equality.js.map +1 -0
- package/dist/backend/llvm/string-reads.d.ts +4 -1
- package/dist/backend/llvm/string-reads.js +89 -9
- package/dist/backend/llvm/string-reads.js.map +1 -1
- package/dist/backend/llvm/switch-dispatch.js +60 -13
- package/dist/backend/llvm/switch-dispatch.js.map +1 -1
- package/dist/backend/llvm/switch-dispatch.test.js +71 -6
- package/dist/backend/llvm/switch-dispatch.test.js.map +1 -1
- package/dist/backend/llvm/union-retype.test.d.ts +1 -0
- package/dist/backend/llvm/union-retype.test.js +229 -0
- package/dist/backend/llvm/union-retype.test.js.map +1 -0
- package/dist/backend/llvm/walkers.d.ts +2 -0
- package/dist/backend/llvm/walkers.js +4 -2
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/backend/llvm/wrapping-sums.d.ts +17 -0
- package/dist/backend/llvm/wrapping-sums.js +113 -0
- package/dist/backend/llvm/wrapping-sums.js.map +1 -0
- package/dist/backend/loop-polls.js +1 -0
- package/dist/backend/loop-polls.js.map +1 -1
- package/dist/backend/mangle.d.ts +9 -0
- package/dist/backend/mangle.js +15 -0
- package/dist/backend/mangle.js.map +1 -1
- package/dist/backend/may-throw.js +7 -3
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/native/contracts.d.ts +5 -0
- package/dist/backend/native/executable.js +16 -1
- package/dist/backend/native/executable.js.map +1 -1
- package/dist/backend/native/library.js +1 -1
- package/dist/backend/native/library.js.map +1 -1
- package/dist/backend/native/runtime-inputs.d.ts +1 -1
- package/dist/backend/native/runtime-inputs.js +2 -0
- package/dist/backend/native/runtime-inputs.js.map +1 -1
- package/dist/backend/native-link-info-core.d.ts +1 -0
- package/dist/backend/native-link-info-core.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-core.d.ts +1 -0
- package/dist/backend/runtime-pack-core.js +1 -0
- package/dist/backend/runtime-pack-core.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.js +1 -1
- package/dist/backend/runtime-pack.test.js +1 -1
- package/dist/backend/target-diagnostics.js +14 -0
- package/dist/backend/target-diagnostics.js.map +1 -1
- package/dist/backend/target-diagnostics.test.js +13 -0
- package/dist/backend/target-diagnostics.test.js.map +1 -1
- package/dist/contracts/check.js +4 -0
- package/dist/contracts/check.js.map +1 -1
- package/dist/executable/executable-cache.d.ts +1 -0
- package/dist/executable/executable-cache.js.map +1 -1
- package/dist/frontend/experimental-import.d.ts +12 -0
- package/dist/frontend/experimental-import.js +63 -0
- package/dist/frontend/experimental-import.js.map +1 -0
- package/dist/frontend/lowering/array-values.d.ts +14 -0
- package/dist/frontend/lowering/array-values.js +29 -0
- package/dist/frontend/lowering/array-values.js.map +1 -1
- package/dist/frontend/lowering/builtin-calls.d.ts +6 -1
- package/dist/frontend/lowering/builtin-calls.js +36 -0
- package/dist/frontend/lowering/builtin-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/console.d.ts +16 -6
- package/dist/frontend/lowering/builtins/console.js +33 -9
- package/dist/frontend/lowering/builtins/console.js.map +1 -1
- package/dist/frontend/lowering/builtins/errors.js +20 -0
- package/dist/frontend/lowering/builtins/errors.js.map +1 -1
- package/dist/frontend/lowering/builtins/json.d.ts +2 -2
- package/dist/frontend/lowering/builtins/json.js +13 -4
- package/dist/frontend/lowering/builtins/json.js.map +1 -1
- package/dist/frontend/lowering/builtins/module-calls.js +16 -1
- package/dist/frontend/lowering/builtins/module-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/numbers.d.ts +1 -0
- package/dist/frontend/lowering/builtins/numbers.js +17 -0
- package/dist/frontend/lowering/builtins/numbers.js.map +1 -1
- package/dist/frontend/lowering/builtins/operating-system.d.ts +18 -0
- package/dist/frontend/lowering/builtins/operating-system.js +361 -1
- package/dist/frontend/lowering/builtins/operating-system.js.map +1 -1
- package/dist/frontend/lowering/builtins/process.js +15 -3
- package/dist/frontend/lowering/builtins/process.js.map +1 -1
- package/dist/frontend/lowering/builtins/promises.js +109 -2
- package/dist/frontend/lowering/builtins/promises.js.map +1 -1
- package/dist/frontend/lowering/builtins/text-codecs.d.ts +3 -4
- package/dist/frontend/lowering/builtins/text-codecs.js +3 -4
- package/dist/frontend/lowering/builtins/text-codecs.js.map +1 -1
- package/dist/frontend/lowering/checked-promise-all.d.ts +10 -3
- package/dist/frontend/lowering/checked-promise-all.js +52 -6
- package/dist/frontend/lowering/checked-promise-all.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.d.ts +6 -3
- package/dist/frontend/lowering/class-dynamic-dispatch.js +54 -31
- package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
- package/dist/frontend/lowering/containers/array-callbacks.js +74 -4
- package/dist/frontend/lowering/containers/array-callbacks.js.map +1 -1
- package/dist/frontend/lowering/containers/array-construction.js +8 -4
- package/dist/frontend/lowering/containers/array-construction.js.map +1 -1
- package/dist/frontend/lowering/containers/array-pipelines.d.ts +54 -0
- package/dist/frontend/lowering/containers/array-pipelines.js +1827 -0
- package/dist/frontend/lowering/containers/array-pipelines.js.map +1 -0
- package/dist/frontend/lowering/containers/exec-result-uses.d.ts +10 -0
- package/dist/frontend/lowering/containers/exec-result-uses.js +88 -0
- package/dist/frontend/lowering/containers/exec-result-uses.js.map +1 -0
- package/dist/frontend/lowering/containers/string-and-regexp.js +106 -7
- package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
- package/dist/frontend/lowering/containers/string-replacement.d.ts +2 -1
- package/dist/frontend/lowering/containers/string-replacement.js +32 -136
- package/dist/frontend/lowering/containers/string-replacement.js.map +1 -1
- package/dist/frontend/lowering/cycle-initialization.d.ts +18 -2
- package/dist/frontend/lowering/cycle-initialization.js +166 -12
- package/dist/frontend/lowering/cycle-initialization.js.map +1 -1
- package/dist/frontend/lowering/deferred-module-initializers.js +7 -2
- package/dist/frontend/lowering/deferred-module-initializers.js.map +1 -1
- package/dist/frontend/lowering/deferred-module-initializers.test.js +28 -0
- package/dist/frontend/lowering/deferred-module-initializers.test.js.map +1 -1
- package/dist/frontend/lowering/devirtualize.d.ts +33 -0
- package/dist/frontend/lowering/devirtualize.js +199 -0
- package/dist/frontend/lowering/devirtualize.js.map +1 -0
- package/dist/frontend/lowering/expressions/nullish-assignment.js +55 -7
- package/dist/frontend/lowering/expressions/nullish-assignment.js.map +1 -1
- package/dist/frontend/lowering/expressions/object-literals.js +5 -56
- package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
- package/dist/frontend/lowering/generator-delegation.d.ts +8 -0
- package/dist/frontend/lowering/generator-delegation.js +231 -10
- package/dist/frontend/lowering/generator-delegation.js.map +1 -1
- package/dist/frontend/lowering/identifier-uses.d.ts +7 -0
- package/dist/frontend/lowering/identifier-uses.js +23 -0
- package/dist/frontend/lowering/identifier-uses.js.map +1 -0
- package/dist/frontend/lowering/iterator-adapters.js +51 -3
- package/dist/frontend/lowering/iterator-adapters.js.map +1 -1
- package/dist/frontend/lowering/lifetime-regions.d.ts +11 -0
- package/dist/frontend/lowering/lifetime-regions.js +216 -0
- package/dist/frontend/lowering/lifetime-regions.js.map +1 -0
- package/dist/frontend/lowering/lower-array-sort.d.ts +28 -2
- package/dist/frontend/lowering/lower-array-sort.js +112 -2
- package/dist/frontend/lowering/lower-array-sort.js.map +1 -1
- package/dist/frontend/lowering/lower-array-sort.test.js +99 -2
- package/dist/frontend/lowering/lower-array-sort.test.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +135 -131
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +4 -3
- package/dist/frontend/lowering/lower-classes.js +117 -13
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-console.d.ts +27 -0
- package/dist/frontend/lowering/lower-console.js +651 -0
- package/dist/frontend/lowering/lower-console.js.map +1 -0
- package/dist/frontend/lowering/lower-event-emitter.d.ts +6 -0
- package/dist/frontend/lowering/lower-event-emitter.js +124 -38
- package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.js +272 -25
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-functions.js +7 -3
- package/dist/frontend/lowering/lower-functions.js.map +1 -1
- package/dist/frontend/lowering/lower-generators.d.ts +25 -7
- package/dist/frontend/lowering/lower-generators.js +264 -113
- package/dist/frontend/lowering/lower-generators.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.d.ts +8 -1
- package/dist/frontend/lowering/lower-inspect.js +119 -18
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +24 -14
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-stdlib.d.ts +4 -0
- package/dist/frontend/lowering/lower-stdlib.js +35 -1
- package/dist/frontend/lowering/lower-stdlib.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +66 -11
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +13 -10
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +47 -5
- package/dist/frontend/lowering/lowerer.js +235 -54
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/nan-coded-reads.d.ts +16 -0
- package/dist/frontend/lowering/nan-coded-reads.js +108 -1
- package/dist/frontend/lowering/nan-coded-reads.js.map +1 -1
- package/dist/frontend/lowering/regex-span-reads.d.ts +12 -0
- package/dist/frontend/lowering/regex-span-reads.js +164 -0
- package/dist/frontend/lowering/regex-span-reads.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-bounds.d.ts +4 -0
- package/dist/frontend/lowering/runtime-optional-bounds.js +2 -0
- package/dist/frontend/lowering/runtime-optional-bounds.js.map +1 -1
- package/dist/frontend/lowering/runtime-optional-bounds.test.js +27 -0
- package/dist/frontend/lowering/runtime-optional-bounds.test.js.map +1 -1
- package/dist/frontend/lowering/split-consumers.js +2 -18
- package/dist/frontend/lowering/split-consumers.js.map +1 -1
- package/dist/frontend/lowering/surfaces.js +115 -18
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/lowering/union-retag.d.ts +6 -1
- package/dist/frontend/lowering/union-retag.js +64 -1
- package/dist/frontend/lowering/union-retag.js.map +1 -1
- package/dist/frontend/lowering/union-retag.test.js +46 -0
- package/dist/frontend/lowering/union-retag.test.js.map +1 -1
- package/dist/frontend/lowering/var-hoisting.d.ts +3 -0
- package/dist/frontend/lowering/var-hoisting.js +261 -0
- package/dist/frontend/lowering/var-hoisting.js.map +1 -0
- package/dist/frontend/pipeline.js +5 -3
- package/dist/frontend/pipeline.js.map +1 -1
- package/dist/frontend/program.d.ts +3 -2
- package/dist/frontend/program.js +23 -5
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/program.test.js +13 -0
- package/dist/frontend/program.test.js.map +1 -1
- package/dist/frontend/type-mapper.d.ts +13 -1
- package/dist/frontend/type-mapper.js +160 -14
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/ir/builtin-effects.js +31 -2
- package/dist/ir/builtin-effects.js.map +1 -1
- package/dist/ir/builtin-signatures.d.ts +1 -1
- package/dist/ir/builtin-signatures.js +157 -16
- package/dist/ir/builtin-signatures.js.map +1 -1
- package/dist/ir/function-references.d.ts +12 -0
- package/dist/ir/function-references.js +29 -0
- package/dist/ir/function-references.js.map +1 -0
- package/dist/ir/function-references.test.d.ts +1 -0
- package/dist/ir/function-references.test.js +277 -0
- package/dist/ir/function-references.test.js.map +1 -0
- package/dist/ir/int32-slots.d.ts +7 -4
- package/dist/ir/int32-slots.js +215 -49
- package/dist/ir/int32-slots.js.map +1 -1
- package/dist/ir/int32-slots.test.js +38 -1
- package/dist/ir/int32-slots.test.js.map +1 -1
- package/dist/ir/integer-ranges.d.ts +4 -4
- package/dist/ir/integer-ranges.js +89 -64
- package/dist/ir/integer-ranges.js.map +1 -1
- package/dist/ir/integer-ranges.test.js +71 -9
- package/dist/ir/integer-ranges.test.js.map +1 -1
- package/dist/ir/intrinsic-signatures.js +3 -0
- package/dist/ir/intrinsic-signatures.js.map +1 -1
- package/dist/ir/ir.d.ts +111 -20
- package/dist/ir/ir.js +6 -0
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/runtime-features.d.ts +1 -0
- package/dist/ir/runtime-features.js +38 -3
- package/dist/ir/runtime-features.js.map +1 -1
- package/dist/ir/scalar-records.js +1 -2
- package/dist/ir/scalar-records.js.map +1 -1
- package/dist/ir/traverse.js +4 -0
- package/dist/ir/traverse.js.map +1 -1
- package/dist/ir/validate.js +201 -25
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.js +90 -7
- package/dist/ir/validate.test.js.map +1 -1
- package/dist/library/ir-policy.js +1 -0
- package/dist/library/ir-policy.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 +159 -17
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { emitLlvmLayouts } from "./layouts.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { compileRegexMatcher } from "./regex-matchers.js";
|
|
3
|
+
import { CYC_BUF, CYC_HDR, FIRST_EMITTED_CYC_KIND, inlineAllocSupported } from "./alloc.js";
|
|
4
|
+
import { hasLoneSurrogate, llvmBytes as llBytes, wtf8Bytes } from "../literals.js";
|
|
4
5
|
import { InternalCompilerError } from "../../errors.js";
|
|
5
6
|
import { createHash } from "node:crypto";
|
|
6
7
|
import { endsWithJump, matchStringSelfConcat, streamTypedRefEligible, undefinedArmTag, } from "../../ir/analysis.js";
|
|
@@ -15,23 +16,28 @@ import { findBytesBounds } from "./bytes-bounds.js";
|
|
|
15
16
|
import { emitByteWindowGuard, findInitializedByteLoopBindings, matchByteWindow, } from "./byte-windows.js";
|
|
16
17
|
import { canDeferSplitPiece, emitSplitSpanNext, materializeSplitPiece, emitSplitCursor, emitSplitNext, emitSplitScratch, emitSplitSnapshot, findPrivateSplitLocals, loadSplitSnapshot, storeSplitSnapshot, stringSplit, } from "./split-loops.js";
|
|
17
18
|
import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
|
|
19
|
+
import { addRecordResultEntries } from "./record-results.js";
|
|
18
20
|
import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js";
|
|
19
|
-
import { everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
|
|
21
|
+
import { everyExpr, everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
|
|
20
22
|
import { analyzeIntegerRanges, INT32_RANGE } from "../../ir/integer-ranges.js";
|
|
21
23
|
import { unshareNodes } from "../../ir/unshare.js";
|
|
22
24
|
import { analyzeInt32Slots } from "../../ir/int32-slots.js";
|
|
23
25
|
import { analyzeFinalFields, constructorClass } from "../../ir/final-fields.js";
|
|
24
26
|
import { foldConstantGlobals } from "../../ir/constant-globals.js";
|
|
25
27
|
import { findIntegerViews } from "./integer-views.js";
|
|
28
|
+
import { findInt32Abis, INT32_ABI_SUFFIX, SCALAR_UNION_DUMMY, SCALAR_UNION_RESULT, } from "./int32-abi.js";
|
|
29
|
+
import { findInt32Locals } from "./int32-locals.js";
|
|
30
|
+
import { exactInteger, narrowInteger } from "./integer-values.js";
|
|
26
31
|
import { findInitializerBindings, withInitializerBindings } from "../../ir/initializer-bindings.js";
|
|
27
32
|
import { findConstantNumericTables } from "../../ir/constant-tables.js";
|
|
28
33
|
import { allocateFfiCallbackAdapters, hasForeignFfiCallback, hasRetainedFfiCallback, } from "../ffi-callbacks.js";
|
|
29
34
|
import { RUNTIME_ABI_MARKER } from "../runtime-abi.js";
|
|
30
35
|
import { computeMayThrow } from "../may-throw.js";
|
|
31
|
-
import { mangleArgPack, mangleAsyncSpawn, mangleBorrowedFunction, mangleClassObj, mangleClassStruct, mangleFnClosure, mangleFunction, mangleGenDrop, mangleGenSpawn, mangleGlobal, mangleLocal, mangleRecordStruct, mangleTrampoline, mangleWrapper, mangleVtInstance, } from "../mangle.js";
|
|
36
|
+
import { mangleArgPack, mangleAsyncAdoptCall, mangleAsyncAwaitCall, mangleAsyncSpawn, mangleBorrowedFunction, mangleClassObj, mangleClassStruct, mangleFnClosure, mangleFunction, mangleGenDrop, mangleGenSpawn, mangleGlobal, mangleLocal, mangleRecordStruct, mangleTrampoline, mangleWrapper, mangleBorrowingWrapper, mangleVtInstance, } from "../mangle.js";
|
|
32
37
|
import { analyzeCallLifetimes } from "./call-lifetimes.js";
|
|
33
38
|
import { canStackUnion, emitStackUnion, findLocalStackUnions } from "./stack-unions.js";
|
|
34
|
-
import {
|
|
39
|
+
import { emitStackObject, findLocalStackObjects, findReassignedStackObjects, stackObjectStruct, } from "./stack-objects.js";
|
|
40
|
+
import { allocateLocalUnion, emitScalarOptional, findLocalUnionStorage, isScalarOptionalSource, storeLocalUnion, } from "./local-union-storage.js";
|
|
35
41
|
import { BlockBuilder } from "./blocks.js";
|
|
36
42
|
import { NullableRefFields } from "./nullable-fields.js";
|
|
37
43
|
import { emitLocalArrayRead, emitProjectedArrayRead, emitBorrowedArrayRead, findLocalArrayReads, findCallArrayReads, OptionalArrayReads, } from "./local-array-reads.js";
|
|
@@ -47,6 +53,7 @@ import { ReferenceWrites } from "./reference-writes.js";
|
|
|
47
53
|
import { LlvmDebugInfo } from "./debug-info.js";
|
|
48
54
|
import { StackCallbacks } from "./stack-callbacks.js";
|
|
49
55
|
import { findLoopArrayBorrows } from "./loop-array-borrows.js";
|
|
56
|
+
import { findAppendedArrayLiterals } from "./appended-array-literals.js";
|
|
50
57
|
import { AmbientReceiverReaders, findConstantCallbacks } from "./constant-callbacks.js";
|
|
51
58
|
import { findScalarStringSlices, emitStringSliceSnapshot, } from "./string-slices.js";
|
|
52
59
|
import { LazyCaptures } from "./lazy-captures.js";
|
|
@@ -73,7 +80,8 @@ import { emitChildProcessLibCall, emitAsyncContextLibCall, emitProcessLibCall, e
|
|
|
73
80
|
import { emitStreamLibCall } from "./lib-stream.js";
|
|
74
81
|
import { emitNetworkHttpLibCall } from "./lib-network.js";
|
|
75
82
|
import { emitAssertInspectLibCall, emitIoLibCall, emitGenericLibCall, emitLibCall, } from "./lib-dispatch.js";
|
|
76
|
-
import { buildClassGraph, classMembershipIntervals, emitClassMembershipHelper, classEnvironmentIndex, classFieldIndex, classStructSym, } from "./classes.js";
|
|
83
|
+
import { buildClassGraph, classMembershipIntervals, emitClassMembershipHelper, classEnvironmentIndex, classFieldIndex, classStructSym, programLifetimeClass, } from "./classes.js";
|
|
84
|
+
import { LifetimeRegions, REGION_TYPES } from "./regions.js";
|
|
77
85
|
import { LlDyn } from "./dyn.js";
|
|
78
86
|
import { VirtualBorrows } from "./virtual-borrows.js";
|
|
79
87
|
import { LlvmUnsupportedError } from "./unsupported.js";
|
|
@@ -82,9 +90,10 @@ import { isObjectArm, NULLABLE_ABSENT, NULLABLE_NULL, NullableUnions } from "./n
|
|
|
82
90
|
import { emitNullablePresent, emitUnionPeek, emitUnionTag } from "./union-repr.js";
|
|
83
91
|
import { literalCapWord, literalCapWord32 } from "./string-key-hash.js";
|
|
84
92
|
import { NUMBER_MAP_ENTRY_DECL, STRING_MAP_ENTRY_DECL } from "./map-number-lookup.js";
|
|
85
|
-
import { STRING_READ_HELPERS } from "./string-reads.js";
|
|
93
|
+
import { STRING_READ_HELPERS, stringScanDecl } from "./string-reads.js";
|
|
86
94
|
import { emitConcatInputs, emitMixedConcat, numberPart, stringParts, } from "./string-construction.js";
|
|
87
95
|
import { arrNewCall, boxAccess, boxNewCall, boxReleaseSym, computeTraced, elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, FN_ATTRS, inlineRcDecls, llFieldType, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
|
|
96
|
+
import { INLINE_LITERAL_BYTES, literalEqualityDefinition } from "./string-literal-equality.js";
|
|
88
97
|
export { LlvmUnsupportedError } from "./unsupported.js";
|
|
89
98
|
function ffiCallbackDummyLl(callback) {
|
|
90
99
|
switch (callback.returns) {
|
|
@@ -117,14 +126,14 @@ export function emitLlvmModule(input, options = {}) {
|
|
|
117
126
|
const mod = unshareNodes(input);
|
|
118
127
|
// Keep source storage intact for debugger inspection in dev builds.
|
|
119
128
|
return new LlEmitter(options.debugSources === undefined
|
|
120
|
-
? scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction))
|
|
129
|
+
? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction)))
|
|
121
130
|
: mod, options).emit();
|
|
122
131
|
}
|
|
123
132
|
/** Keep large translation units below the host's single-string limit. */
|
|
124
133
|
export function emitLlvmModuleSource(input, options = {}) {
|
|
125
134
|
const mod = unshareNodes(input);
|
|
126
135
|
const parts = new LlEmitter(options.debugSources === undefined
|
|
127
|
-
? scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction))
|
|
136
|
+
? addRecordResultEntries(scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction)))
|
|
128
137
|
: mod, options).emitParts();
|
|
129
138
|
const length = parts.reduce((total, part) => total + part.length, Math.max(0, parts.length - 1));
|
|
130
139
|
if (length <= 256 * 1024 * 1024)
|
|
@@ -132,7 +141,7 @@ export function emitLlvmModuleSource(input, options = {}) {
|
|
|
132
141
|
return parts.flatMap((part, index) => (index === 0 ? [part] : ["\n", part]));
|
|
133
142
|
}
|
|
134
143
|
function llStrBytes(text) {
|
|
135
|
-
return llBytes(
|
|
144
|
+
return llBytes(wtf8Bytes(text));
|
|
136
145
|
}
|
|
137
146
|
const NO_BORROWED = new Set();
|
|
138
147
|
/** Whether any node of `e` names the local (reads, writes, declarations,
|
|
@@ -176,6 +185,10 @@ export class LlEmitter {
|
|
|
176
185
|
scalarStringSlices = new Map();
|
|
177
186
|
stringSlices = new Map();
|
|
178
187
|
localStackUnions = new Map();
|
|
188
|
+
localStackObjects = new Map();
|
|
189
|
+
reassignedStackObjects = new Set();
|
|
190
|
+
/** The binding slot of each reassigned stack-object local. */
|
|
191
|
+
stackObjectSlots = new Map();
|
|
179
192
|
localUnionStorageProofs = new Map();
|
|
180
193
|
localUnionStorage = new Map();
|
|
181
194
|
integerArrayBindings = new Set();
|
|
@@ -198,6 +211,18 @@ export class LlEmitter {
|
|
|
198
211
|
}
|
|
199
212
|
this.fieldPointerTags.set(ptr, tag);
|
|
200
213
|
}
|
|
214
|
+
/** The alias tag of a runtime word that emitted code reads and writes
|
|
215
|
+
* outside every field (the lifetime-region count, owner and cursors). */
|
|
216
|
+
memoryAliasAttachment(category) {
|
|
217
|
+
if (this.debug !== null)
|
|
218
|
+
return "";
|
|
219
|
+
let tag = this.fieldAliasTags.get(category);
|
|
220
|
+
if (tag === undefined) {
|
|
221
|
+
tag = 2 + this.fieldAliasTags.size * 2;
|
|
222
|
+
this.fieldAliasTags.set(category, tag);
|
|
223
|
+
}
|
|
224
|
+
return `, !tbaa !${tag}`;
|
|
225
|
+
}
|
|
201
226
|
fieldAliasAttachment(ptr) {
|
|
202
227
|
const tag = this.fieldPointerTags.get(ptr);
|
|
203
228
|
return tag === undefined ? "" : `, !tbaa !${tag}`;
|
|
@@ -212,14 +237,24 @@ export class LlEmitter {
|
|
|
212
237
|
return lines;
|
|
213
238
|
}
|
|
214
239
|
debug;
|
|
240
|
+
/** Projection-only scalar objects may live in frame storage (stack-objects.ts). */
|
|
241
|
+
stackObjects;
|
|
215
242
|
debugScope = null;
|
|
216
243
|
sizeType;
|
|
217
244
|
ffiExtendNarrowIntegers;
|
|
218
|
-
|
|
245
|
+
cycleBufOffset;
|
|
219
246
|
/** Emitted ScrCycType descriptors so far (their collector statistics
|
|
220
247
|
* indices; see ShapeHost.nextCycKind). */
|
|
221
248
|
cycKinds = 0;
|
|
222
249
|
wasi;
|
|
250
|
+
/** Synchronous generator bodies compile to LLVM switched coroutines (a
|
|
251
|
+
* heap frame resumed by an ordinary call) instead of running on a fiber
|
|
252
|
+
* stack. Worker programs keep fibers (context shutdown unwinds suspended
|
|
253
|
+
* generator stacks) and so do debug builds (the debugger reads locals
|
|
254
|
+
* from the native frame). */
|
|
255
|
+
stacklessGenerators = false;
|
|
256
|
+
/** Some function in the module is lowered as a coroutine. */
|
|
257
|
+
usesCoroutines = false;
|
|
223
258
|
/** The target lacks a rounding instruction at its baseline CPU (x86-64
|
|
224
259
|
* before SSE4.1), so `llvm.trunc` would lower to a libm call: emit
|
|
225
260
|
* truncation inline through an integer round trip instead. */
|
|
@@ -239,6 +274,10 @@ export class LlEmitter {
|
|
|
239
274
|
/** Interned string literals: UTF-8 text → { symbol, byte length } —
|
|
240
275
|
* first-use order, the runtime ABI’s determinism discipline. */
|
|
241
276
|
literals = new Map();
|
|
277
|
+
/** Literal equality helpers: literal text → helper symbol (literalEquals). */
|
|
278
|
+
literalEqualities = new Map();
|
|
279
|
+
/** Interned bigint literals: hex magnitude with sign → { symbol, value }. */
|
|
280
|
+
bigintLiterals = new Map();
|
|
242
281
|
/** Symbols of interned constant data, named by content (contentSymbol). */
|
|
243
282
|
contentSymbols = new Set();
|
|
244
283
|
/** Interned unit-armed union instances: "unionId:tag" → symbol — one
|
|
@@ -247,6 +286,10 @@ export class LlEmitter {
|
|
|
247
286
|
unitInstances = new Map();
|
|
248
287
|
/** unionId → symbol of the union's immortal ABSENT field-slot instance. */
|
|
249
288
|
absentInstances = new Map();
|
|
289
|
+
/** unionId → symbol of a flag set once a checked-dynamic cast caches one
|
|
290
|
+
* of the union's boxes. A cached box is refreshed in place, possibly with
|
|
291
|
+
* a tag that another union numbers apart, so it is never shared. */
|
|
292
|
+
retypeGuards = new Map();
|
|
250
293
|
/** The nullable-union ABSENT sentinel was referenced (absentInstanceRef). */
|
|
251
294
|
needsNullableAbsent = false;
|
|
252
295
|
immortalValues = new Set();
|
|
@@ -286,6 +329,8 @@ export class LlEmitter {
|
|
|
286
329
|
* when inline RC is off. */
|
|
287
330
|
rcHelpers;
|
|
288
331
|
fnByName = new Map();
|
|
332
|
+
/** Empty literals that construct with inline slots (appended-array-literals.ts). */
|
|
333
|
+
appendedArrayLiterals = new Set();
|
|
289
334
|
/** Emitted LLVM text of every IR function body, by function name: the
|
|
290
335
|
* fiberless-async analysis scans it for suspension entry points. */
|
|
291
336
|
fnDefText = new Map();
|
|
@@ -297,6 +342,8 @@ export class LlEmitter {
|
|
|
297
342
|
* whose bindings end with the list (emitBlock and function bodies). */
|
|
298
343
|
stmtCursor = null;
|
|
299
344
|
optionalArrayReads;
|
|
345
|
+
/** Union sources a binding or projection can hold in a stack box. */
|
|
346
|
+
unionSources;
|
|
300
347
|
checkedNarrows;
|
|
301
348
|
callArrayReads = new Map();
|
|
302
349
|
mapReadLifetimes = { locals: new Map(), arguments: new Map() };
|
|
@@ -312,6 +359,9 @@ export class LlEmitter {
|
|
|
312
359
|
lazyCaptureBoxes = new Map();
|
|
313
360
|
lazyStackBoxes = new Map();
|
|
314
361
|
borrowedParameters = new Set();
|
|
362
|
+
/** Captures of the current closure body that its environment holds
|
|
363
|
+
* unchanged for the closure's whole life (LazyCaptures.stableCaptures). */
|
|
364
|
+
stableCaptureIds = new Set();
|
|
315
365
|
/** Parameters proven projection-only: callers may pass stack boxes. */
|
|
316
366
|
projectedParameters = new Set();
|
|
317
367
|
stableCallBindings = new Set();
|
|
@@ -367,6 +417,21 @@ export class LlEmitter {
|
|
|
367
417
|
/** Method names with at least one may-throw implementation — the
|
|
368
418
|
* virtualCall pending check's key. */
|
|
369
419
|
mayThrowMethods = new Set();
|
|
420
|
+
/** Async functions awaited directly at some `await f(...)` site; each
|
|
421
|
+
* gets an await-call entry (emitAsyncAwaitCall). */
|
|
422
|
+
awaitCallees = new Set();
|
|
423
|
+
/** Async functions returned directly at some `return f(...)` site of an
|
|
424
|
+
* async function; each gets an adopting entry (emitAsyncAwaitCall). */
|
|
425
|
+
adoptCallees = new Set();
|
|
426
|
+
/** The promise a `return` emitted past its finally regions already
|
|
427
|
+
* produced for its adopting await (borrowed from the return's parked
|
|
428
|
+
* slot); that await takes it instead of evaluating its operand. */
|
|
429
|
+
adoptedOperand = null;
|
|
430
|
+
takeAdoptedOperand() {
|
|
431
|
+
const value = this.adoptedOperand;
|
|
432
|
+
this.adoptedOperand = null;
|
|
433
|
+
return value;
|
|
434
|
+
}
|
|
370
435
|
/** setTimeout and friends appeared somewhere: main must run the event
|
|
371
436
|
* loop even in programs with no async functions. */
|
|
372
437
|
usesTimers = false;
|
|
@@ -374,6 +439,8 @@ export class LlEmitter {
|
|
|
374
439
|
* typeKey → thunk symbol. */
|
|
375
440
|
resolveThunks = new Map();
|
|
376
441
|
resolveThunkDefs = [];
|
|
442
|
+
/** Typed-ref recipe constants by type key (lazy receivers). */
|
|
443
|
+
typedRefRecipes = new Map();
|
|
377
444
|
/** ReadableStream.from adapters keep typed arrays by reference and box
|
|
378
445
|
* one current element per pull. */
|
|
379
446
|
streamFromArrayAdapters = new Map();
|
|
@@ -440,6 +507,8 @@ export class LlEmitter {
|
|
|
440
507
|
initializerBindings;
|
|
441
508
|
/** Whole-program int32 storage and parameter/result facts. */
|
|
442
509
|
int32Slots;
|
|
510
|
+
/** Functions whose direct calls use i32 parameters or results. */
|
|
511
|
+
int32Abis;
|
|
443
512
|
/** Immutable module constants (contract types) whose graphs become
|
|
444
513
|
* immortal once initialized: their reads need no reference counting. */
|
|
445
514
|
immortalGlobals;
|
|
@@ -457,10 +526,17 @@ export class LlEmitter {
|
|
|
457
526
|
* not a constructor (where they change), not async or a generator. */
|
|
458
527
|
finalFieldBorrows = false;
|
|
459
528
|
integerRanges = new Map();
|
|
529
|
+
/** Sums whose result only feeds 32-bit conversions (wrapping-sums.ts). */
|
|
530
|
+
wrappingSums = new Set();
|
|
460
531
|
byteWindowsEnabled = true;
|
|
461
532
|
byteWindowEntries = new Map();
|
|
462
533
|
bytesBounds = new Set();
|
|
463
534
|
integerViews = new Map();
|
|
535
|
+
/** Number locals of the current function stored only as an i32
|
|
536
|
+
* (int32-locals.ts); their slots are `alloca i32`. */
|
|
537
|
+
int32Locals = new Map();
|
|
538
|
+
/** The written values (and `++`/`--` nodes) the int32 proof covers. */
|
|
539
|
+
int32LocalWrites = new Set();
|
|
464
540
|
/** Enclosing try-with-FINALLY regions, innermost last: a `return`
|
|
465
541
|
* inside one runs every crossed finally (innermost first) before the
|
|
466
542
|
* actual ret — the runtime ABI’s pending-return path, with the finally
|
|
@@ -480,13 +556,21 @@ export class LlEmitter {
|
|
|
480
556
|
/** Return type of the function being emitted — the unwind path returns
|
|
481
557
|
* a dummy of this type (never read: callers check the flag first). */
|
|
482
558
|
currentReturnType = VOID;
|
|
483
|
-
/**
|
|
484
|
-
|
|
485
|
-
|
|
559
|
+
/** The current body returns its number result as i32. */
|
|
560
|
+
currentInt32Result = false;
|
|
561
|
+
/** The function's i32 entry returns its scalar union result as tag and
|
|
562
|
+
* payload bits (Int32Abi.union). */
|
|
563
|
+
currentUnionResult = false;
|
|
564
|
+
/** Active only while emitting a body lowered with LLVM's
|
|
565
|
+
* switched-coroutine intrinsics (wasm32 async bodies and generators,
|
|
566
|
+
* native synchronous generators; see coroLowered). */
|
|
567
|
+
currentCoro = null;
|
|
486
568
|
/** The generator channels of the function being emitted (null outside
|
|
487
569
|
* generator bodies): yieldExpr emission reads them, and emitTryCatch's
|
|
488
570
|
* catch prologue emits the GENRET sentinel re-unwind exactly here. */
|
|
489
571
|
currentGenerator = null;
|
|
572
|
+
/** The function being emitted is an async generator body. */
|
|
573
|
+
currentAsyncGenerator = false;
|
|
490
574
|
/** Active optional-chain bind slots, by chain id (chainRecv reads). */
|
|
491
575
|
chainSlots = new Map();
|
|
492
576
|
logArgSlots = 0;
|
|
@@ -505,6 +589,7 @@ export class LlEmitter {
|
|
|
505
589
|
this.constantNumericTables = findConstantNumericTables(mod);
|
|
506
590
|
this.initializerBindings = findInitializerBindings(mod);
|
|
507
591
|
this.int32Slots = analyzeInt32Slots(mod);
|
|
592
|
+
this.int32Abis = this.debug === null ? findInt32Abis(mod, this.int32Slots) : new Map();
|
|
508
593
|
this.immortalGlobals = new Set(mod.contracts?.immortalGlobals ?? []);
|
|
509
594
|
this.programLifetime = new Set(mod.contracts?.programLifetime ?? []);
|
|
510
595
|
this.finalFields = analyzeFinalFields(mod);
|
|
@@ -512,6 +597,8 @@ export class LlEmitter {
|
|
|
512
597
|
this.ffiExtendNarrowIntegers =
|
|
513
598
|
options.wasi === true || ffiExtendsNarrowIntegers(options.targetTriple);
|
|
514
599
|
this.wasi = options.wasi === true;
|
|
600
|
+
this.stacklessGenerators = !this.wasi && mod.workers !== true && this.debug === null;
|
|
601
|
+
this.stackObjects = this.debug === null;
|
|
515
602
|
// An empty triple targets the host, as for executableTls below: the
|
|
516
603
|
// Node-hosted CLI passes none, the native CLI passes its toolchain's, and
|
|
517
604
|
// both must emit the same module for the same target.
|
|
@@ -538,15 +625,14 @@ export class LlEmitter {
|
|
|
538
625
|
this.runtimeAbiMarker = options.runtimeAbiMarker === true;
|
|
539
626
|
this.detachedExit = options.detachedExit === true;
|
|
540
627
|
this.rcHelpers = options.inlineRc === true ? new Set() : null;
|
|
541
|
-
// ScrCycHdr.
|
|
542
|
-
|
|
543
|
-
// double-aligned without changing this ABI offset.
|
|
544
|
-
this.cycleBufferedOffset = 6;
|
|
628
|
+
// ScrCycHdr.buf sits 4 bytes behind every object on every target.
|
|
629
|
+
this.cycleBufOffset = CYC_HDR - CYC_BUF;
|
|
545
630
|
this.ffiCallbackAdapters = allocateFfiCallbackAdapters(mod.ffiImports ?? []);
|
|
546
631
|
this.ffiHasRetainedCallback = hasRetainedFfiCallback(mod.ffiImports ?? []);
|
|
547
632
|
this.ffiHasForeignCallback = hasForeignFfiCallback(mod.ffiImports ?? []);
|
|
548
633
|
for (const fn of mod.functions)
|
|
549
634
|
this.fnByName.set(fn.name, fn);
|
|
635
|
+
this.appendedArrayLiterals = findAppendedArrayLiterals(mod);
|
|
550
636
|
for (const entry of mod.ffiImports ?? []) {
|
|
551
637
|
this.ffiByName.set(entry.name, entry);
|
|
552
638
|
}
|
|
@@ -580,6 +666,13 @@ export class LlEmitter {
|
|
|
580
666
|
// then yields the (borrowed or owned) element pointer, not a stack box.
|
|
581
667
|
// Arrays of such a union are read the same way.
|
|
582
668
|
this.optionalArrayReads = new OptionalArrayReads(this.fnByName, this.unionsById, (id) => this.nullableUnions.has(id));
|
|
669
|
+
this.unionSources = {
|
|
670
|
+
unions: this.boxedUnionsById,
|
|
671
|
+
receiverUnions: this.unionsById,
|
|
672
|
+
nullableField: (e) => this.nullableFieldGet(e) !== null,
|
|
673
|
+
arrayRead: (e) => this.optionalArrayReads.get(e) !== null,
|
|
674
|
+
scalarCall: (e) => this.scalarResultCall(e),
|
|
675
|
+
};
|
|
583
676
|
this.checkedNarrows = new CheckedNarrows(this.fnByName);
|
|
584
677
|
this.referenceEffects = new ReferenceEffects(this.fnByName, (call) => this.optionalArrayReads.get(call) !== null, mod.classes ?? []);
|
|
585
678
|
if (this.debug === null)
|
|
@@ -625,7 +718,7 @@ export class LlEmitter {
|
|
|
625
718
|
walkParameters.set(fn.name, indexes);
|
|
626
719
|
}
|
|
627
720
|
}
|
|
628
|
-
this.callLifetimes = analyzeCallLifetimes(this.fnByName, (className, field) => this.nullableFields.get(className, field) !== null, walkParameters);
|
|
721
|
+
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)));
|
|
629
722
|
this.constantCallbacks = findConstantCallbacks(mod, this.callLifetimes);
|
|
630
723
|
this.stackCallbacks = new StackCallbacks(this.fnByName);
|
|
631
724
|
this.receiverReaders = new AmbientReceiverReaders(this.fnByName);
|
|
@@ -638,7 +731,7 @@ export class LlEmitter {
|
|
|
638
731
|
this.tracedUnions = traced.unions;
|
|
639
732
|
this.shapeHost = {
|
|
640
733
|
sizeType: this.sizeType,
|
|
641
|
-
|
|
734
|
+
cycleBufOffset: this.cycleBufOffset,
|
|
642
735
|
tracedShapes: this.tracedShapes,
|
|
643
736
|
tracedUnions: this.tracedUnions,
|
|
644
737
|
nullableUnions: this.nullableUnions,
|
|
@@ -663,6 +756,7 @@ export class LlEmitter {
|
|
|
663
756
|
internLiteral: (text) => this.internLiteral(text),
|
|
664
757
|
cstr: (text) => this.cstr(text),
|
|
665
758
|
unitInstanceRef: (unionId, tag) => this.unitInstanceRef(unionId, tag),
|
|
759
|
+
retypeGuardRef: (unionId) => this.retypeGuardRef(unionId),
|
|
666
760
|
fieldAbsentTestIn: (B, value, unionId) => this.fieldAbsentTestIn(B, value, unionId),
|
|
667
761
|
absentInstanceRef: (unionId) => this.absentInstanceRef(unionId),
|
|
668
762
|
liveDynRefAdapter: (type) => this.liveDynRefAdapter(type),
|
|
@@ -679,6 +773,7 @@ export class LlEmitter {
|
|
|
679
773
|
pendingTestLines: (dest) => this.pendingTestLines(dest),
|
|
680
774
|
virtualEntry: (implFn) => this.virtualEntry(implFn),
|
|
681
775
|
programLifetime: this.programLifetime,
|
|
776
|
+
regionAliasTag: (word) => this.memoryAliasAttachment(`region.${word}`),
|
|
682
777
|
};
|
|
683
778
|
this.walkers = new LlWalkers(this.shapeHost);
|
|
684
779
|
this.dyn = new LlDyn(this.shapeHost);
|
|
@@ -716,6 +811,17 @@ export class LlEmitter {
|
|
|
716
811
|
// whole-program preorder numbering for instanceof over runtime and
|
|
717
812
|
// compiled error objects, and the per-hierarchy virtual slot lists.
|
|
718
813
|
this.classMeta = buildClassGraph(mod, this.fnByName);
|
|
814
|
+
if (mod.lifetimeRegions === true) {
|
|
815
|
+
this.regions = new LifetimeRegions(this.shapeHost, mod, this.classMeta, (meta) => programLifetimeClass(this.shapeHost, meta), this.nullableFields);
|
|
816
|
+
const regions = this.regions;
|
|
817
|
+
// Member names differ from LifetimeRegions' own, which keeps the
|
|
818
|
+
// record from reading as that class's protocol.
|
|
819
|
+
this.shapeHost.regions = {
|
|
820
|
+
rcBody: (fnName, ret, comment) => regions.countBody(fnName, ret, comment),
|
|
821
|
+
newAlloc: (className, sizeOf) => regions.allocLines(className, sizeOf),
|
|
822
|
+
classDefinitions: (cls, sizeOf) => regions.classDefs(cls, sizeOf),
|
|
823
|
+
};
|
|
824
|
+
}
|
|
719
825
|
for (const [name, rec] of RUNTIME_ERROR_CLASSES) {
|
|
720
826
|
const meta = this.classMeta.get(name);
|
|
721
827
|
if (!meta)
|
|
@@ -1348,9 +1454,20 @@ export class LlEmitter {
|
|
|
1348
1454
|
...emitClassMembershipHelper(this.classMeta, this.sizeType),
|
|
1349
1455
|
...(this.publishWalkersCache?.defs() ?? []),
|
|
1350
1456
|
...this.publishedClassNamesDefs(),
|
|
1457
|
+
...(this.regions?.moduleDefs() ?? []),
|
|
1351
1458
|
];
|
|
1352
1459
|
if (this.constantNumericTables.size > 0)
|
|
1353
1460
|
this.declare(`declare double @scr_arr_get_number(ptr, double)`);
|
|
1461
|
+
// Native matchers of the regex literals inside the compiled subset.
|
|
1462
|
+
const regexMatchers = new Map();
|
|
1463
|
+
for (const [key, re] of this.regexInstances) {
|
|
1464
|
+
const code = compileRegexMatcher(`${re.sym}_match`, re.pattern, re.flags, this.sizeType, !this.wasi);
|
|
1465
|
+
if (code === null)
|
|
1466
|
+
continue;
|
|
1467
|
+
regexMatchers.set(key, code);
|
|
1468
|
+
for (const decl of code.decls)
|
|
1469
|
+
this.declare(decl);
|
|
1470
|
+
}
|
|
1354
1471
|
const out = [
|
|
1355
1472
|
`; Generated by scriptc (LLVM backend) from ${this.mod.sourceFile}. Do not edit.`,
|
|
1356
1473
|
``,
|
|
@@ -1364,17 +1481,17 @@ export class LlEmitter {
|
|
|
1364
1481
|
`%ScrLogArg = type { i32, i64 }`,
|
|
1365
1482
|
`%ScrVt = type { ${this.sizeType}, ${this.sizeType}, ptr }`,
|
|
1366
1483
|
`%ScrUnion = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
|
|
1367
|
-
`%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32, i32, ptr }`,
|
|
1484
|
+
`%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32, i32, ptr, ptr }`,
|
|
1368
1485
|
`%ScrFfiTable = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, i8, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`,
|
|
1369
|
-
`%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr, ptr }`,
|
|
1486
|
+
`%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr, ptr, ptr }`,
|
|
1370
1487
|
// ScrArr mirrors scr_runtime.h field-for-field: { rc, len, cap, elem,
|
|
1371
|
-
// cyc, ops, data, present, ext } (field numbers in
|
|
1372
|
-
// where ops points at the element type's constant RC descriptor
|
|
1488
|
+
// cyc, spare, filled, ops, data, present, ext } (field numbers in
|
|
1489
|
+
// SCR_ARR_FIELD), where filled bounds the hole-free prefix, ops points at the element type's constant RC descriptor
|
|
1373
1490
|
// (%ScrArrOps, SCR_ELEM_REF only) and ext at the out-of-line sparse,
|
|
1374
1491
|
// property and metadata storage (NULL until first used). Live dynamic
|
|
1375
1492
|
// stream commits swap its dense, presence and out-of-line storage while
|
|
1376
1493
|
// preserving the target object's identity.
|
|
1377
|
-
`%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType},
|
|
1494
|
+
`%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i8, i8, i16, i32, ptr, ptr, ptr, ptr }`,
|
|
1378
1495
|
`%ScrArrOps = type { ptr, ptr, ptr }`,
|
|
1379
1496
|
// The ScrMap prefix { rc, key_kind, val_kind, val_retain, val_release,
|
|
1380
1497
|
// val_trace, key_retain, key_release, key_trace } — the inline RC
|
|
@@ -1465,13 +1582,33 @@ export class LlEmitter {
|
|
|
1465
1582
|
// half carries the precomputed Map key hash (SCR_STR_HASH_CACHE).
|
|
1466
1583
|
// Bit 31 marks all-ASCII literals (SCR_STR_ASCII_BIT) on every target.
|
|
1467
1584
|
const capWord = this.sizeType === "i64"
|
|
1468
|
-
? literalCapWord(
|
|
1469
|
-
: literalCapWord32(
|
|
1585
|
+
? literalCapWord(wtf8Bytes(text))
|
|
1586
|
+
: literalCapWord32(wtf8Bytes(text));
|
|
1470
1587
|
out.push(`@${lit.sym} = internal global { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, [${lit.len + 1} x i8] } ` +
|
|
1471
1588
|
`{ ${this.sizeType} -1, ${this.sizeType} ${lit.len}, ${this.sizeType} ${capWord}, [${lit.len + 1} x i8] c"${llStrBytes(text)}" }`);
|
|
1472
1589
|
}
|
|
1473
1590
|
if (this.literals.size > 0)
|
|
1474
1591
|
out.push(``);
|
|
1592
|
+
for (const lit of this.bigintLiterals.values()) {
|
|
1593
|
+
// Immortal ScrBigInt: { rc = SIZE_MAX, len, sign, cap, limbs } with
|
|
1594
|
+
// pointer-sized little-endian limbs (scr_bigint.c); zero keeps one
|
|
1595
|
+
// zero limb so limb 0 is always readable.
|
|
1596
|
+
const size = this.sizeType;
|
|
1597
|
+
const limbBits = size === "i64" ? 64n : 32n;
|
|
1598
|
+
const mask = (1n << limbBits) - 1n;
|
|
1599
|
+
const limbs = [];
|
|
1600
|
+
for (let m = lit.value < 0n ? -lit.value : lit.value; m > 0n; m = m >> limbBits) {
|
|
1601
|
+
limbs.push(`${size} ${m & mask}`);
|
|
1602
|
+
}
|
|
1603
|
+
const len = limbs.length;
|
|
1604
|
+
if (len === 0)
|
|
1605
|
+
limbs.push(`${size} 0`);
|
|
1606
|
+
const sign = lit.value < 0n ? -1 : lit.value > 0n ? 1 : 0;
|
|
1607
|
+
out.push(`@${lit.sym} = internal global { ${size}, ${size}, i32, i32, [${limbs.length} x ${size}] } ` +
|
|
1608
|
+
`{ ${size} -1, ${size} ${len}, i32 ${sign}, i32 ${limbs.length}, [${limbs.length} x ${size}] [${limbs.join(", ")}] }`);
|
|
1609
|
+
}
|
|
1610
|
+
if (this.bigintLiterals.size > 0)
|
|
1611
|
+
out.push(``);
|
|
1475
1612
|
for (const [key, sym] of this.unitInstances) {
|
|
1476
1613
|
// One immortal instance per unit-armed (union, tag): tag set, payload
|
|
1477
1614
|
// slot and RC entry points zero — retain/release/collector all skip
|
|
@@ -1481,6 +1618,13 @@ export class LlEmitter {
|
|
|
1481
1618
|
}
|
|
1482
1619
|
if (this.unitInstances.size > 0)
|
|
1483
1620
|
out.push(``);
|
|
1621
|
+
for (const [unionId, sym] of this.retypeGuards) {
|
|
1622
|
+
// Thread-local like module globals: a thread's cast caches hold only
|
|
1623
|
+
// its own boxes. Never written, the flag folds to false.
|
|
1624
|
+
out.push(`@${sym} = internal ${tl}global i8 0 ; ${unionId} boxes in a cast cache`);
|
|
1625
|
+
}
|
|
1626
|
+
if (this.retypeGuards.size > 0)
|
|
1627
|
+
out.push(``);
|
|
1484
1628
|
for (const [unionId, sym] of this.absentInstances) {
|
|
1485
1629
|
// One immortal ABSENT field-slot instance per undefined-armed union:
|
|
1486
1630
|
// the undefined arm's tag with payload 1 (ordinary unit instances
|
|
@@ -1508,22 +1652,25 @@ export class LlEmitter {
|
|
|
1508
1652
|
}
|
|
1509
1653
|
for (const [key, re] of this.regexInstances) {
|
|
1510
1654
|
// One immortal ScrRegex per (pattern, flags) literal, pointing at
|
|
1511
|
-
// the interned source/flags strings. The bc and
|
|
1512
|
-
// start null (lazy compile, cached by the runtime) — a mutable
|
|
1513
|
-
// global, not constant.
|
|
1514
|
-
|
|
1655
|
+
// the interned source/flags strings. The bc and translated-program
|
|
1656
|
+
// slots start null (lazy compile, cached by the runtime) — a mutable
|
|
1657
|
+
// global, not constant. The last slot is the build-time matcher.
|
|
1658
|
+
const matcher = regexMatchers.has(key) ? `@${re.sym}_match` : "null";
|
|
1659
|
+
out.push(`@${re.sym} = internal ${tl}global %ScrRegex { ${this.sizeType} -1, ptr ${re.src}, ptr ${re.fl}, ptr null, double 0.0, ptr null, ptr null, ptr ${matcher} } ; ${key.replace(/\n/g, "\\n")}`);
|
|
1515
1660
|
}
|
|
1516
1661
|
if (this.regexInstances.size > 0)
|
|
1517
1662
|
out.push(``);
|
|
1663
|
+
for (const code of regexMatchers.values())
|
|
1664
|
+
out.push(...code.lines, ``);
|
|
1518
1665
|
for (const [, inst] of this.templateStringsInstances) {
|
|
1519
1666
|
// One immortal ScrArr per tagged-template site: a [N x ptr] data
|
|
1520
1667
|
// global of interned cooked-string literals, and the ScrArr header
|
|
1521
|
-
// over it (rc == SIZE_MAX, len == cap, SCR_ELEM_STR = 2,
|
|
1522
|
-
// entry points). Every dense slot is present; reads retain
|
|
1523
|
-
// strings — a no-op.
|
|
1668
|
+
// over it (rc == SIZE_MAX, len == cap == filled, SCR_ELEM_STR = 2,
|
|
1669
|
+
// no REF entry points). Every dense slot is present; reads retain
|
|
1670
|
+
// immortal strings — a no-op.
|
|
1524
1671
|
const n = inst.slots.length;
|
|
1525
1672
|
const present = n === 0 ? "zeroinitializer" : `[ ${inst.slots.map(() => "i8 1").join(", ")} ]`;
|
|
1526
|
-
out.push(`@${inst.sym}_data = internal constant [${n} x ptr] [ ${inst.slots.map((s) => `ptr ${s}`).join(", ")} ]`, `@${inst.sym}_present = internal constant [${n} x i8] ${present}`, `@${inst.sym} = internal global %ScrArr { ${this.sizeType} -1, ${this.sizeType} ${n}, ${this.sizeType} ${n},
|
|
1673
|
+
out.push(`@${inst.sym}_data = internal constant [${n} x ptr] [ ${inst.slots.map((s) => `ptr ${s}`).join(", ")} ]`, `@${inst.sym}_present = internal constant [${n} x i8] ${present}`, `@${inst.sym} = internal global %ScrArr { ${this.sizeType} -1, ${this.sizeType} ${n}, ${this.sizeType} ${n}, i8 2, i8 0, i16 0, i32 ${n}, ptr null, ptr @${inst.sym}_data, ptr @${inst.sym}_present, ptr null }`);
|
|
1527
1674
|
}
|
|
1528
1675
|
if (this.templateStringsInstances.size > 0)
|
|
1529
1676
|
out.push(``);
|
|
@@ -1599,6 +1746,12 @@ export class LlEmitter {
|
|
|
1599
1746
|
}
|
|
1600
1747
|
if (this.streamIntervals.length > 0)
|
|
1601
1748
|
out.push(``);
|
|
1749
|
+
if ([...this.literals.keys(), ...this.cstrs.keys()].some(hasLoneSurrogate)) {
|
|
1750
|
+
// The runtime skips surrogate work until a lone surrogate may exist
|
|
1751
|
+
// (scr_wtf8_any); its own producers note one, and literals do here.
|
|
1752
|
+
out.push(`declare void @scr_wtf8_note()`, ``);
|
|
1753
|
+
stamps.push(` call void @scr_wtf8_note()`);
|
|
1754
|
+
}
|
|
1602
1755
|
if (this.errorIntervals.length > 0 && this.tracedShapes.has("object:%Error")) {
|
|
1603
1756
|
// The cycle fixpoint marked the Error hierarchy (a user subclass
|
|
1604
1757
|
// holds cycle-capable fields — capability is hierarchy-uniform), so
|
|
@@ -1622,7 +1775,7 @@ export class LlEmitter {
|
|
|
1622
1775
|
out.push(line);
|
|
1623
1776
|
out.push(...this.inlineRcTail(flushedDecls));
|
|
1624
1777
|
out.push(`attributes #0 = { sanitize_address }`);
|
|
1625
|
-
if (this.
|
|
1778
|
+
if (this.usesCoroutines)
|
|
1626
1779
|
out.push(`attributes #1 = { sanitize_address presplitcoroutine }`);
|
|
1627
1780
|
if (hasNoInlineRecordClone)
|
|
1628
1781
|
out.push(`attributes #2 = { noinline sanitize_address }`);
|
|
@@ -1737,7 +1890,7 @@ export class LlEmitter {
|
|
|
1737
1890
|
// sanitize_address is inert under the plain pipeline; the sanitized
|
|
1738
1891
|
// lane's -fsanitize=address link activates instrumentation over the
|
|
1739
1892
|
// emitted functions too (the runtime TUs get theirs from clang).
|
|
1740
|
-
`attributes #0 = { sanitize_address }`, ...(this.
|
|
1893
|
+
`attributes #0 = { sanitize_address }`, ...(this.usesCoroutines ? [`attributes #1 = { sanitize_address presplitcoroutine }`] : []), ...(hasNoInlineRecordClone ? [`attributes #2 = { noinline sanitize_address }`] : []), ``);
|
|
1741
1894
|
if (this.mod.workers) {
|
|
1742
1895
|
out.push(`declare void @scr_context_cleanup()`, `declare i32 @scr_worker_argc()`, `declare ptr @scr_worker_argv()`, `define i32 @sc_worker_entry(i32 %root) ${FN_ATTRS} {`, `entry:`, ` %argc = call i32 @scr_worker_argc()`, ` %argv = call ptr @scr_worker_argv()`, ` %result = call i32 @sc_context_entry(i32 %argc, ptr %argv)`, ` ret i32 %result`, `}`, `define i32 @main(i32 %argc, ptr %argv) ${FN_ATTRS} {`, `entry:`, ` %result = call i32 @sc_context_entry(i32 %argc, ptr %argv)`, ` call void @scr_context_cleanup()`, ` ret i32 %result`, `}`);
|
|
1743
1896
|
}
|
|
@@ -2045,14 +2198,25 @@ export class LlEmitter {
|
|
|
2045
2198
|
if (this.decls.has(`declare zeroext i1 @scr_str_eq(ptr, ptr)`)) {
|
|
2046
2199
|
defs.push(`define internal zeroext i1 @sc_str_eq(ptr %a, ptr %b) ${FN_ATTRS} {`, `entry:`, ` %same = icmp eq ptr %a, %b`, ` br i1 %same, label %yes, label %lengths`, `lengths:`, ` %alen.p = getelementptr inbounds %ScrStr, ptr %a, i32 0, i32 1`, ` %blen.p = getelementptr inbounds %ScrStr, ptr %b, i32 0, i32 1`, ` %alen = load ${this.sizeType}, ptr %alen.p`, ` %blen = load ${this.sizeType}, ptr %blen.p`, ` %samelen = icmp eq ${this.sizeType} %alen, %blen`, ` br i1 %samelen, label %bytes, label %no`, `bytes:`, ` %eq = call zeroext i1 @scr_str_eq(ptr %a, ptr %b)`, ` ret i1 %eq`, `yes:`, ` ret i1 true`, `no:`, ` ret i1 false`, `}`, ``);
|
|
2047
2200
|
}
|
|
2201
|
+
for (const [text, name] of this.literalEqualities) {
|
|
2202
|
+
defs.push(...literalEqualityDefinition(name, wtf8Bytes(text), this.internLiteral(text), this.sizeType, FN_ATTRS), ``);
|
|
2203
|
+
}
|
|
2048
2204
|
// String length and charCodeAt read proven-ASCII strings inline: bit 31
|
|
2049
2205
|
// of the capacity word (SCR_STR_ASCII_BIT, the low half's sign bit on
|
|
2050
2206
|
// little-endian targets) makes UTF-16 units bytes. Unproven strings and
|
|
2051
2207
|
// anything but an in-range index take the runtime's UTF-16 mapping.
|
|
2208
|
+
// A scan's receiver reads inline too where runtime state is ordinary
|
|
2209
|
+
// global storage (ScrStrScan; SCR_TL makes it thread-local otherwise).
|
|
2210
|
+
const scanInline = this.mod.workers !== true && this.mod.lib?.threadInstances !== true;
|
|
2211
|
+
let scanDeclared = false;
|
|
2052
2212
|
for (const helper of STRING_READ_HELPERS) {
|
|
2053
2213
|
if (!this.decls.has(helper.decl))
|
|
2054
2214
|
continue;
|
|
2055
|
-
|
|
2215
|
+
if (scanInline && !scanDeclared) {
|
|
2216
|
+
defs.push(stringScanDecl(this.sizeType), ``);
|
|
2217
|
+
scanDeclared = true;
|
|
2218
|
+
}
|
|
2219
|
+
defs.push(...helper.define(this.sizeType, FN_ATTRS, scanInline), ``);
|
|
2056
2220
|
}
|
|
2057
2221
|
// Ordering also decides inline when the first bytes differ: byte order
|
|
2058
2222
|
// is the plain comparison's order, and the UTF-16 order whenever both
|
|
@@ -2128,7 +2292,12 @@ export class LlEmitter {
|
|
|
2128
2292
|
// never the inner return type.
|
|
2129
2293
|
const ret = fn.async === true || fn.generator !== undefined ? "ptr" : this.llType(fn.returnType);
|
|
2130
2294
|
const call = `call ${ret} @${this.callTarget(name)}(${args})`;
|
|
2131
|
-
|
|
2295
|
+
const lending = this.lendsEveryArgument(name);
|
|
2296
|
+
out.push(`define internal ${ret} @${mangleWrapper(name)}(ptr %env${params.length ? ", " + params.join(", ") : ""}) ${FN_ATTRS} { ; ${name} as a value`, `entry:`, ret === "void" ? ` ${call}` : ` %r = ${call}`, ret === "void" ? ` ret void` : ` ret ${ret} %r`, `}`, `@${mangleFnClosure(name)} = internal ${this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : ""}global %ScrClosure { ${this.sizeType} -1, ptr @${mangleWrapper(name)}, ${this.sizeType} 0, ptr null, i32 ${(fn.generator ? 1 : 0) + (fn.async ? 2 : 0) + (fn.ownsPrototype ? 4 : 0)}, i32 0, ptr null, ptr ${lending ? `@${mangleBorrowingWrapper(name)}` : "null"} }`, ``);
|
|
2297
|
+
if (lending)
|
|
2298
|
+
out.push(`define internal ${ret} @${mangleBorrowingWrapper(name)}(ptr %env${params.length ? ", " + params.join(", ") : ""}) ${FN_ATTRS} { ; ${name} as a value, lending its arguments`, `entry:`, ret === "void"
|
|
2299
|
+
? ` call void @${mangleBorrowedFunction(name)}(${args})`
|
|
2300
|
+
: ` %r = call ${ret} @${mangleBorrowedFunction(name)}(${args})`, ret === "void" ? ` ret void` : ` ret ${ret} %r`, `}`, ``);
|
|
2132
2301
|
}
|
|
2133
2302
|
return out;
|
|
2134
2303
|
}
|
|
@@ -2141,8 +2310,14 @@ export class LlEmitter {
|
|
|
2141
2310
|
const fieldTys = [...(lifted ? ["ptr"] : []), ...fn.params.map((p) => this.llType(p.type))];
|
|
2142
2311
|
const definitions = [`%${pack} = type { ${fieldTys.join(", ") || "i8"} } ; ${fn.name} args`];
|
|
2143
2312
|
const sizeOf = `ptrtoint (ptr getelementptr (%${pack}, ptr null, i32 1) to ${this.sizeType})`;
|
|
2144
|
-
|
|
2145
|
-
|
|
2313
|
+
// Async argument packs live from the spawn to the trampoline's first
|
|
2314
|
+
// instructions, so they take the runtime's small-block allocator.
|
|
2315
|
+
// Generator packs keep malloc: the runtime frees never-started ones.
|
|
2316
|
+
const smallPack = fn.generator === undefined;
|
|
2317
|
+
const packAlloc = smallPack ? "scr_rt_calloc" : "malloc";
|
|
2318
|
+
const packFree = smallPack ? "scr_rt_free" : "free";
|
|
2319
|
+
this.declare(`declare void @${packFree}(ptr)`);
|
|
2320
|
+
this.declare(`declare ptr @${packAlloc}(${this.sizeType})`);
|
|
2146
2321
|
const tr = [
|
|
2147
2322
|
`define internal void @${mangleTrampoline(fn.name)}(ptr %self, ptr %ap) ${FN_ATTRS} {`,
|
|
2148
2323
|
`entry:`,
|
|
@@ -2152,17 +2327,17 @@ export class LlEmitter {
|
|
|
2152
2327
|
tr.push(` %fp${i} = getelementptr inbounds %${pack}, ptr %ap, i64 0, i32 ${i}`, ` %a${i} = load ${ty}, ptr %fp${i}`);
|
|
2153
2328
|
loads.push(`${ty} %a${i}`);
|
|
2154
2329
|
});
|
|
2155
|
-
tr.push(` call void
|
|
2330
|
+
tr.push(` call void @${packFree}(ptr %ap)`);
|
|
2156
2331
|
const ret = fn.returnType;
|
|
2157
2332
|
const retTy = this.llType(ret);
|
|
2158
2333
|
const bodyCall = `call ${retTy} @${mangleFunction(fn.name)}(${loads.join(", ")})`;
|
|
2159
2334
|
tr.push(retTy === "void" ? ` ${bodyCall}` : ` %r = ${bodyCall}`);
|
|
2160
|
-
if (lifted && !this.
|
|
2335
|
+
if (lifted && !this.coroLowered(fn)) {
|
|
2161
2336
|
tr.push(` call void @scr_closure_release(ptr %a0)`);
|
|
2162
2337
|
}
|
|
2163
2338
|
const spawnParams = fieldTys.map((ty, i) => `${ty} %a${i}`);
|
|
2164
2339
|
const argPackLines = [
|
|
2165
|
-
` %ap = call ptr
|
|
2340
|
+
` %ap = call ptr @${packAlloc}(${this.sizeType} ${sizeOf})`,
|
|
2166
2341
|
` %isnull = icmp eq ptr %ap, null`,
|
|
2167
2342
|
` br i1 %isnull, label %oom, label %ok`,
|
|
2168
2343
|
`oom:`,
|
|
@@ -2229,7 +2404,7 @@ export class LlEmitter {
|
|
|
2229
2404
|
const eligible = new Set();
|
|
2230
2405
|
if (this.wasi || this.mod.workers === true)
|
|
2231
2406
|
return eligible;
|
|
2232
|
-
const suspends = /@scr_(?:await_|module_await\b|promise_await_settled\b|test_sub\b|gen_yield_|async_gen_|fiber_)/;
|
|
2407
|
+
const suspends = /@(?:scr_(?:await_|module_await\b|promise_await_settled\b|test_sub\b|gen_yield_|async_gen_|fiber_)|sc_a[wd]_)/;
|
|
2233
2408
|
for (const decl of this.decls)
|
|
2234
2409
|
if (/@scr_test_sub\b/.test(decl))
|
|
2235
2410
|
return eligible;
|
|
@@ -2325,6 +2500,133 @@ export class LlEmitter {
|
|
|
2325
2500
|
out.push(` call void @scr_promise_reject_pending(ptr %p)`, ` ret ptr %p`, `}`, ``);
|
|
2326
2501
|
return out;
|
|
2327
2502
|
}
|
|
2503
|
+
/** Whether `await f(...)` may enter f through its await-call entry. The
|
|
2504
|
+
* entry's result is the awaited value of f's own body type; direct calls
|
|
2505
|
+
* never carry a lifted environment. */
|
|
2506
|
+
awaitCallEligible(fnName) {
|
|
2507
|
+
if (this.wasi || this.mod.workers === true)
|
|
2508
|
+
return false;
|
|
2509
|
+
const fn = this.fnByName.get(fnName);
|
|
2510
|
+
if (fn === undefined || fn.async !== true || fn.generator !== undefined)
|
|
2511
|
+
return false;
|
|
2512
|
+
if (fn.captures !== undefined || fn.returnType.kind === "dyn")
|
|
2513
|
+
return false;
|
|
2514
|
+
return fn.asyncCacheGlobal === undefined && fn.asyncCycleCacheGlobal === undefined;
|
|
2515
|
+
}
|
|
2516
|
+
/** The await-call entry of an async function (`sc_aw_*`): one direct
|
|
2517
|
+
* `await f(...)` as a call returning the awaited value, so the call site
|
|
2518
|
+
* needs no promise of its own. A fiberless body runs on the caller's
|
|
2519
|
+
* stack as in its spawn wrapper; a clean result then takes the await's
|
|
2520
|
+
* one microtask turn (scr_await_hop) and is returned without ever
|
|
2521
|
+
* materializing the promise, which nothing else could observe. A throw
|
|
2522
|
+
* builds the rejected promise exactly as the spawn wrapper would and
|
|
2523
|
+
* awaits it, so the rejection reaches the awaiter on the same turn. Other
|
|
2524
|
+
* bodies pack their arguments as the spawn wrapper does and enter
|
|
2525
|
+
* scr_async_spawn_await_*, which parks the caller on the promise as soon
|
|
2526
|
+
* as the body first suspends. The adopting entry (`sc_ad_*`, for
|
|
2527
|
+
* `return f(...)` in an async function) adds the turn of the then job
|
|
2528
|
+
* that adopts the returned promise, before the await proper. */
|
|
2529
|
+
emitAsyncAwaitCall(fn, argPackLines, adopt = false) {
|
|
2530
|
+
const ret = fn.returnType;
|
|
2531
|
+
const retTy = this.llType(ret);
|
|
2532
|
+
const params = fn.params.map((p, i) => `${this.llType(p.type)} %a${i}`);
|
|
2533
|
+
const awaitLine = (dest, promise) => {
|
|
2534
|
+
switch (ret.kind) {
|
|
2535
|
+
case "void":
|
|
2536
|
+
this.declare(`declare void @scr_await_void(ptr)`);
|
|
2537
|
+
return ` call void @scr_await_void(ptr ${promise})`;
|
|
2538
|
+
case "f64":
|
|
2539
|
+
case "date":
|
|
2540
|
+
this.declare(`declare double @scr_await_f64(ptr)`);
|
|
2541
|
+
return ` ${dest} = call double @scr_await_f64(ptr ${promise})`;
|
|
2542
|
+
case "bool":
|
|
2543
|
+
this.declare(`declare zeroext i1 @scr_await_bool(ptr)`);
|
|
2544
|
+
return ` ${dest} = call zeroext i1 @scr_await_bool(ptr ${promise})`;
|
|
2545
|
+
case "string":
|
|
2546
|
+
this.declare(`declare ptr @scr_await_str(ptr)`);
|
|
2547
|
+
return ` ${dest} = call ptr @scr_await_str(ptr ${promise})`;
|
|
2548
|
+
default:
|
|
2549
|
+
this.declare(`declare ptr @scr_await_ref(ptr)`);
|
|
2550
|
+
return ` ${dest} = call ptr @scr_await_ref(ptr ${promise})`;
|
|
2551
|
+
}
|
|
2552
|
+
};
|
|
2553
|
+
const retLine = (v) => retTy === "void" ? ` ret void` : ` ret ${retTy} ${v}`;
|
|
2554
|
+
const head = adopt
|
|
2555
|
+
? `define internal ${retTy} @${mangleAsyncAdoptCall(fn.name)}(${params.join(", ")}) ${FN_ATTRS} { ; adopt ${fn.name}`
|
|
2556
|
+
: `define internal ${retTy} @${mangleAsyncAwaitCall(fn.name)}(${params.join(", ")}) ${FN_ATTRS} { ; await ${fn.name}`;
|
|
2557
|
+
const args = params.join(", ");
|
|
2558
|
+
if (adopt && argPackLines !== null) {
|
|
2559
|
+
// A body that may suspend settles later than the adoption's then job
|
|
2560
|
+
// runs: materialize its promise, take that turn, then await it.
|
|
2561
|
+
this.declare(`declare void @scr_await_hop()`);
|
|
2562
|
+
this.declare(`declare void @scr_promise_release(ptr)`);
|
|
2563
|
+
return [
|
|
2564
|
+
head,
|
|
2565
|
+
`entry:`,
|
|
2566
|
+
` %p = call ptr @${mangleAsyncSpawn(fn.name)}(${args})`,
|
|
2567
|
+
` call void @scr_await_hop()`,
|
|
2568
|
+
awaitLine("%v", "%p"),
|
|
2569
|
+
` call void @scr_promise_release(ptr %p)`,
|
|
2570
|
+
retLine("%v"),
|
|
2571
|
+
`}`,
|
|
2572
|
+
``,
|
|
2573
|
+
];
|
|
2574
|
+
}
|
|
2575
|
+
if (argPackLines !== null) {
|
|
2576
|
+
const kind = ret.kind === "void"
|
|
2577
|
+
? "void"
|
|
2578
|
+
: ret.kind === "f64" || ret.kind === "date"
|
|
2579
|
+
? "f64"
|
|
2580
|
+
: ret.kind === "bool"
|
|
2581
|
+
? "bool"
|
|
2582
|
+
: ret.kind === "string"
|
|
2583
|
+
? "str"
|
|
2584
|
+
: "ref";
|
|
2585
|
+
const cret = kind === "bool" ? "zeroext i1" : retTy;
|
|
2586
|
+
this.declare(`declare ${cret} @scr_async_spawn_await_${kind}(ptr, ptr)`);
|
|
2587
|
+
this.needOom();
|
|
2588
|
+
const call = `call ${cret} @scr_async_spawn_await_${kind}(ptr @${mangleTrampoline(fn.name)}, ptr %ap)`;
|
|
2589
|
+
return [
|
|
2590
|
+
head,
|
|
2591
|
+
`entry:`,
|
|
2592
|
+
...argPackLines,
|
|
2593
|
+
kind === "void" ? ` ${call}` : ` %v = ${call}`,
|
|
2594
|
+
retLine("%v"),
|
|
2595
|
+
`}`,
|
|
2596
|
+
``,
|
|
2597
|
+
];
|
|
2598
|
+
}
|
|
2599
|
+
this.declare(`declare void @scr_async_inline_enter(ptr)`);
|
|
2600
|
+
this.declare(`declare void @scr_async_inline_exit(ptr)`);
|
|
2601
|
+
this.declare(`declare void @scr_await_hop()`);
|
|
2602
|
+
this.declare(`declare ptr @scr_promise_new()`);
|
|
2603
|
+
this.declare(`declare void @scr_promise_reject_pending(ptr)`);
|
|
2604
|
+
this.declare(`declare void @scr_promise_release(ptr)`);
|
|
2605
|
+
const bodyCall = `call ${retTy} @${mangleFunction(fn.name)}(${args})`;
|
|
2606
|
+
const out = [
|
|
2607
|
+
head,
|
|
2608
|
+
`entry:`,
|
|
2609
|
+
` %frame = alloca { ptr, ptr }`,
|
|
2610
|
+
` call void @scr_async_inline_enter(ptr %frame)`,
|
|
2611
|
+
retTy === "void" ? ` ${bodyCall}` : ` %r = ${bodyCall}`,
|
|
2612
|
+
...this.pendingTestLines("%pend"),
|
|
2613
|
+
` call void @scr_async_inline_exit(ptr %frame)`,
|
|
2614
|
+
// Adopting: the then job's turn comes before the settled promise's
|
|
2615
|
+
// own reaction, whichever way the body completed.
|
|
2616
|
+
...(adopt ? [` call void @scr_await_hop()`] : []),
|
|
2617
|
+
` br i1 %pend, label %thrown, label %clean`,
|
|
2618
|
+
`clean:`,
|
|
2619
|
+
` call void @scr_await_hop()`,
|
|
2620
|
+
retLine("%r"),
|
|
2621
|
+
`thrown:`,
|
|
2622
|
+
];
|
|
2623
|
+
if (ret.kind !== "void" && isRefCounted(ret)) {
|
|
2624
|
+
// An escaping throw means %r is the never-read dummy (NULL).
|
|
2625
|
+
out.push(` call void ${releaseSym(this.shapeHost, ret)}(ptr %r)`);
|
|
2626
|
+
}
|
|
2627
|
+
out.push(` %p = call ptr @scr_promise_new()`, ` call void @scr_promise_reject_pending(ptr %p)`, awaitLine("%v", "%p"), ` call void @scr_promise_release(ptr %p)`, retLine("%v"), `}`, ``);
|
|
2628
|
+
return out;
|
|
2629
|
+
}
|
|
2328
2630
|
/** Per-async-function machinery — async.ts's scaffolding, .ll
|
|
2329
2631
|
* flavored: an argument-pack struct type, a fiber trampoline (unpacks,
|
|
2330
2632
|
* frees the pack, runs the ordinary compiled body, settles the
|
|
@@ -2343,6 +2645,14 @@ export class LlEmitter {
|
|
|
2343
2645
|
for (const fn of this.mod.functions) {
|
|
2344
2646
|
if (fn.async !== true || fn.generator !== undefined)
|
|
2345
2647
|
continue;
|
|
2648
|
+
if (fiberless.has(fn.name) && this.awaitCallees.has(fn.name)) {
|
|
2649
|
+
for (const line of this.emitAsyncAwaitCall(fn, null))
|
|
2650
|
+
out.push(line);
|
|
2651
|
+
}
|
|
2652
|
+
if (fiberless.has(fn.name) && this.adoptCallees.has(fn.name)) {
|
|
2653
|
+
for (const line of this.emitAsyncAwaitCall(fn, null, true))
|
|
2654
|
+
out.push(line);
|
|
2655
|
+
}
|
|
2346
2656
|
if (fiberless.has(fn.name)) {
|
|
2347
2657
|
const fieldTys = [
|
|
2348
2658
|
...(fn.captures !== undefined ? ["ptr"] : []),
|
|
@@ -2356,6 +2666,14 @@ export class LlEmitter {
|
|
|
2356
2666
|
const { definitions, ret, tr, spawnParams, argPackLines } = this.emitArgPackAndTrampolinePrologue(fn);
|
|
2357
2667
|
for (const line of definitions)
|
|
2358
2668
|
out.push(line);
|
|
2669
|
+
if (this.awaitCallees.has(fn.name)) {
|
|
2670
|
+
for (const line of this.emitAsyncAwaitCall(fn, argPackLines))
|
|
2671
|
+
out.push(line);
|
|
2672
|
+
}
|
|
2673
|
+
if (this.adoptCallees.has(fn.name)) {
|
|
2674
|
+
for (const line of this.emitAsyncAwaitCall(fn, argPackLines, true))
|
|
2675
|
+
out.push(line);
|
|
2676
|
+
}
|
|
2359
2677
|
this.declare(`declare ptr @scr_fiber_promise(ptr)`);
|
|
2360
2678
|
this.declare(`declare ptr @scr_async_spawn(ptr, ptr)`);
|
|
2361
2679
|
this.needOom();
|
|
@@ -2447,6 +2765,13 @@ export class LlEmitter {
|
|
|
2447
2765
|
out.push(line);
|
|
2448
2766
|
return out;
|
|
2449
2767
|
}
|
|
2768
|
+
/** Whether `fn`'s body is emitted as an LLVM switched coroutine: every
|
|
2769
|
+
* wasm32 async or generator body, and native synchronous generators. */
|
|
2770
|
+
coroLowered(fn) {
|
|
2771
|
+
if (this.wasi)
|
|
2772
|
+
return fn.async === true || fn.generator !== undefined;
|
|
2773
|
+
return this.stacklessGenerators && fn.generator !== undefined && fn.async !== true;
|
|
2774
|
+
}
|
|
2450
2775
|
/** Per-generator-function machinery — the async scaffolding's lazy
|
|
2451
2776
|
* sibling (async.ts's emitGenScaffolding): the same argument pack,
|
|
2452
2777
|
* a fiber trampoline whose epilogue stores the COMPLETION value (or
|
|
@@ -2467,14 +2792,15 @@ export class LlEmitter {
|
|
|
2467
2792
|
this.declare(`declare void @scr_gen_ret_to_out(ptr)`);
|
|
2468
2793
|
this.declare(`declare ptr @scr_gen_new(ptr, ptr, ptr)`);
|
|
2469
2794
|
this.needOom();
|
|
2470
|
-
|
|
2795
|
+
const coroBody = this.coroLowered(fn);
|
|
2796
|
+
if (lifted && !coroBody) {
|
|
2471
2797
|
this.declare(`declare void @scr_closure_release(ptr)`);
|
|
2472
2798
|
}
|
|
2473
2799
|
// Normal completion stores the (typed) return value; void completes
|
|
2474
2800
|
// with the NONE slot — JS's undefined done-value. A GENRET unwind
|
|
2475
2801
|
// consumes the sentinel and promotes the parked .return value; a
|
|
2476
2802
|
// real exception stays pending (the consumer-side resume moves it).
|
|
2477
|
-
if (
|
|
2803
|
+
if (coroBody) {
|
|
2478
2804
|
tr.push(` ret void`, `}`, ``);
|
|
2479
2805
|
}
|
|
2480
2806
|
else {
|
|
@@ -2559,8 +2885,11 @@ export class LlEmitter {
|
|
|
2559
2885
|
`entry:`,
|
|
2560
2886
|
...argPackLines,
|
|
2561
2887
|
];
|
|
2888
|
+
const genNew = coroBody && !this.wasi ? "scr_gen_new_stackless" : "scr_gen_new";
|
|
2889
|
+
if (genNew !== "scr_gen_new")
|
|
2890
|
+
this.declare(`declare ptr @${genNew}(ptr, ptr, ptr)`);
|
|
2562
2891
|
sp.push(settleAsync === null
|
|
2563
|
-
? ` %gg = call ptr
|
|
2892
|
+
? ` %gg = call ptr @${genNew}(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)})`
|
|
2564
2893
|
: ` %gg = call ptr @scr_async_gen_new(ptr @${mangleTrampoline(fn.name)}, ptr %ap, ptr @${mangleGenDrop(fn.name)}, ptr @${settleAsync})`, ` ret ptr %gg`, `}`, ``);
|
|
2565
2894
|
for (const line of sp)
|
|
2566
2895
|
out.push(line);
|
|
@@ -2598,6 +2927,41 @@ export class LlEmitter {
|
|
|
2598
2927
|
this.immortalValues.add(name);
|
|
2599
2928
|
return name;
|
|
2600
2929
|
}
|
|
2930
|
+
/** The inline equality test against one string literal (an
|
|
2931
|
+
* `i1 (ptr)` helper LLVM inlines at each site), by literal symbol. */
|
|
2932
|
+
literalEquals(text) {
|
|
2933
|
+
const literal = this.internLiteral(text);
|
|
2934
|
+
const name = `@sc_streq_${literal.slice("@sc_lit_".length)}`;
|
|
2935
|
+
if (!this.literalEqualities.has(text)) {
|
|
2936
|
+
this.literalEqualities.set(text, name);
|
|
2937
|
+
if (Buffer.byteLength(text, "utf8") > INLINE_LITERAL_BYTES) {
|
|
2938
|
+
this.declare(`declare zeroext i1 @scr_str_eq(ptr, ptr)`);
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
return name;
|
|
2942
|
+
}
|
|
2943
|
+
/** `@`-ref of an immortal ScrBigInt holding `value`, or null when the
|
|
2944
|
+
* text is not a valid StringToBigInt input (the runtime parse then
|
|
2945
|
+
* throws as usual). BigInt here is the same StringToBigInt as the
|
|
2946
|
+
* program's, so every accepted spelling folds to the value Node gives. */
|
|
2947
|
+
internBigIntLiteral(text) {
|
|
2948
|
+
let value;
|
|
2949
|
+
try {
|
|
2950
|
+
value = BigInt(text);
|
|
2951
|
+
}
|
|
2952
|
+
catch {
|
|
2953
|
+
return null;
|
|
2954
|
+
}
|
|
2955
|
+
const key = value.toString(16);
|
|
2956
|
+
let lit = this.bigintLiterals.get(key);
|
|
2957
|
+
if (!lit) {
|
|
2958
|
+
lit = { sym: this.contentSymbol("sc_bigint_", key), value };
|
|
2959
|
+
this.bigintLiterals.set(key, lit);
|
|
2960
|
+
}
|
|
2961
|
+
const name = `@${lit.sym}`;
|
|
2962
|
+
this.immortalValues.add(name);
|
|
2963
|
+
return name;
|
|
2964
|
+
}
|
|
2601
2965
|
/** Interned NUL-terminated C-string constant (the scr_jb_puts /
|
|
2602
2966
|
* stringify-indent currency) — `@`-ref, first-use order. */
|
|
2603
2967
|
cstr(text) {
|
|
@@ -2636,6 +3000,16 @@ export class LlEmitter {
|
|
|
2636
3000
|
this.immortalValues.add(name);
|
|
2637
3001
|
return name;
|
|
2638
3002
|
}
|
|
3003
|
+
/** The i8 flag that a checked-dynamic cast sets before caching one of
|
|
3004
|
+
* this tagged union's boxes (unionRetype rebuilds once it is set). */
|
|
3005
|
+
retypeGuardRef(unionId) {
|
|
3006
|
+
let sym = this.retypeGuards.get(unionId);
|
|
3007
|
+
if (!sym) {
|
|
3008
|
+
sym = `sc_retype_guard_${this.retypeGuards.size}`;
|
|
3009
|
+
this.retypeGuards.set(unionId, sym);
|
|
3010
|
+
}
|
|
3011
|
+
return `@${sym}`;
|
|
3012
|
+
}
|
|
2639
3013
|
/** The immortal ABSENT state of an undefined-armed record field slot
|
|
2640
3014
|
* (IR fieldAbsent). */
|
|
2641
3015
|
absentInstanceRef(unionId) {
|
|
@@ -2880,19 +3254,23 @@ export class LlEmitter {
|
|
|
2880
3254
|
target.used = true;
|
|
2881
3255
|
terminator = `br label %${target.label}`;
|
|
2882
3256
|
}
|
|
2883
|
-
else if (this.
|
|
2884
|
-
terminator = `br label %${this.
|
|
3257
|
+
else if (this.currentCoro !== null) {
|
|
3258
|
+
terminator = `br label %${this.currentCoro.finalLabel}`;
|
|
2885
3259
|
}
|
|
2886
3260
|
else {
|
|
2887
3261
|
const t = this.llType(this.currentReturnType);
|
|
2888
3262
|
terminator =
|
|
2889
3263
|
t === "void"
|
|
2890
3264
|
? "ret void"
|
|
2891
|
-
:
|
|
2892
|
-
?
|
|
2893
|
-
:
|
|
2894
|
-
?
|
|
2895
|
-
:
|
|
3265
|
+
: this.currentInt32Result
|
|
3266
|
+
? "ret i32 0"
|
|
3267
|
+
: this.currentUnionResult
|
|
3268
|
+
? `ret ${SCALAR_UNION_DUMMY}`
|
|
3269
|
+
: t === "double"
|
|
3270
|
+
? `ret double ${f64Lit(0)}`
|
|
3271
|
+
: t === "i1"
|
|
3272
|
+
? "ret i1 false"
|
|
3273
|
+
: "ret ptr null";
|
|
2896
3274
|
}
|
|
2897
3275
|
// Copy the entries now: later declarations extend these lexical scopes.
|
|
2898
3276
|
// Release order and the handler are part of the shared block's identity.
|
|
@@ -2905,7 +3283,10 @@ export class LlEmitter {
|
|
|
2905
3283
|
}
|
|
2906
3284
|
}
|
|
2907
3285
|
if (entries.length === 0) {
|
|
2908
|
-
if (passThrough !== undefined &&
|
|
3286
|
+
if (passThrough !== undefined &&
|
|
3287
|
+
!target &&
|
|
3288
|
+
this.currentCoro === null &&
|
|
3289
|
+
!this.currentInt32Result) {
|
|
2909
3290
|
const t = this.llType(this.currentReturnType);
|
|
2910
3291
|
if (t === "double" || t === "i1")
|
|
2911
3292
|
terminator = `ret ${t} ${passThrough}`;
|
|
@@ -3878,6 +4259,29 @@ export class LlEmitter {
|
|
|
3878
4259
|
this.boxSet(box, local.type, borrowed && isRefCounted(local.type) ? this.retainValue(value, local.type) : value);
|
|
3879
4260
|
}
|
|
3880
4261
|
// ── functions ───────────────────────────────────────────────────────────
|
|
4262
|
+
/** True when `fnName`'s borrowing body lends every reference parameter
|
|
4263
|
+
* and returns an owned result: a closure over it then carries that body
|
|
4264
|
+
* as its borrowing entry (ScrClosure.fn_borrowed), which callers that
|
|
4265
|
+
* keep their arguments alive across the call may enter instead of `fn`. */
|
|
4266
|
+
lendsEveryArgument(fnName) {
|
|
4267
|
+
const fn = this.fnByName.get(fnName);
|
|
4268
|
+
if (!fn || fn.async || fn.generator || this.borrowedReturns.has(fnName))
|
|
4269
|
+
return false;
|
|
4270
|
+
if (fn.classCaptures !== undefined || this.implSlotBorrowed(fnName).size > 0)
|
|
4271
|
+
return false;
|
|
4272
|
+
const borrowed = this.callLifetimes.borrowed.get(fnName);
|
|
4273
|
+
if (!borrowed)
|
|
4274
|
+
return false;
|
|
4275
|
+
let any = false;
|
|
4276
|
+
for (const [index, param] of fn.params.entries()) {
|
|
4277
|
+
if (!isRefCounted(param.type))
|
|
4278
|
+
continue;
|
|
4279
|
+
if (!borrowed.has(index))
|
|
4280
|
+
return false;
|
|
4281
|
+
any = true;
|
|
4282
|
+
}
|
|
4283
|
+
return any;
|
|
4284
|
+
}
|
|
3881
4285
|
/** The LLVM symbol a direct call or closure enters a function through:
|
|
3882
4286
|
* async bodies are entered via their emitted spawn wrapper (which runs
|
|
3883
4287
|
* the fiber eagerly to its first suspension and returns the promise);
|
|
@@ -3895,7 +4299,7 @@ export class LlEmitter {
|
|
|
3895
4299
|
* whether the promise waiter list or the ready FIFO owns the continuation;
|
|
3896
4300
|
* LLVM keeps all live locals in the coroutine frame. */
|
|
3897
4301
|
emitWasiSuspend(promise) {
|
|
3898
|
-
const coro = this.
|
|
4302
|
+
const coro = this.currentCoro;
|
|
3899
4303
|
if (coro === null)
|
|
3900
4304
|
throw new InternalCompilerError("llvm emitter bug: wasm suspension outside async body");
|
|
3901
4305
|
if (promise === null) {
|
|
@@ -3906,12 +4310,12 @@ export class LlEmitter {
|
|
|
3906
4310
|
this.declare(`declare void @scr_wasi_await_prepare(ptr, ptr)`);
|
|
3907
4311
|
this.B.line(`call void @scr_wasi_await_prepare(ptr ${coro.self}, ptr ${promise})`);
|
|
3908
4312
|
}
|
|
3909
|
-
this.
|
|
4313
|
+
this.emitCoroSuspendPrepared();
|
|
3910
4314
|
}
|
|
3911
4315
|
/** Suspend after a target-specific runtime helper already queued the
|
|
3912
4316
|
* continuation (used by module awaits, which skip the hop when settled). */
|
|
3913
|
-
|
|
3914
|
-
const coro = this.
|
|
4317
|
+
emitCoroSuspendPrepared() {
|
|
4318
|
+
const coro = this.currentCoro;
|
|
3915
4319
|
if (coro === null)
|
|
3916
4320
|
throw new InternalCompilerError("llvm emitter bug: wasm suspension outside async body");
|
|
3917
4321
|
const save = this.B.tmp();
|
|
@@ -3924,7 +4328,7 @@ export class LlEmitter {
|
|
|
3924
4328
|
}
|
|
3925
4329
|
/** Move a clean async return value into the current fiber's promise. */
|
|
3926
4330
|
emitWasiFulfill(v) {
|
|
3927
|
-
const coro = this.
|
|
4331
|
+
const coro = this.currentCoro;
|
|
3928
4332
|
if (coro === null)
|
|
3929
4333
|
throw new InternalCompilerError("llvm emitter bug: wasm fulfillment outside async body");
|
|
3930
4334
|
const ret = this.currentReturnType;
|
|
@@ -4038,6 +4442,8 @@ export class LlEmitter {
|
|
|
4038
4442
|
this.borrowedParameters.add(fn.params[index].localId);
|
|
4039
4443
|
}
|
|
4040
4444
|
this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
|
|
4445
|
+
this.stableCaptureIds =
|
|
4446
|
+
fn.captures !== undefined ? this.lazyCaptures.stableCaptures(fn.name) : new Set();
|
|
4041
4447
|
this.integerLoopBindings.clear();
|
|
4042
4448
|
this.countedLoopsEnabled = this.debug === null && !fn.async && !fn.generator;
|
|
4043
4449
|
this.finalFieldBorrows =
|
|
@@ -4060,22 +4466,42 @@ export class LlEmitter {
|
|
|
4060
4466
|
this.debug === null ? findScalarStringSlices(fn, this.callLifetimes) : new Map();
|
|
4061
4467
|
this.mapReadLifetimes = findMapReadLifetimes(fn, this.boxedUnionsById, this.callLifetimes);
|
|
4062
4468
|
this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.boxedUnionsById);
|
|
4063
|
-
this.
|
|
4469
|
+
this.localStackObjects = this.stackObjects
|
|
4470
|
+
? findLocalStackObjects(this, fn, this.callLifetimes)
|
|
4471
|
+
: new Map();
|
|
4472
|
+
this.reassignedStackObjects = this.stackObjects
|
|
4473
|
+
? findReassignedStackObjects(this, fn, this.callLifetimes)
|
|
4474
|
+
: new Set();
|
|
4475
|
+
this.stackObjectSlots = new Map();
|
|
4476
|
+
this.localUnionStorageProofs = findLocalUnionStorage(fn, this.callLifetimes, this.unionSources);
|
|
4064
4477
|
this.localUnionStorage = new Map();
|
|
4065
4478
|
this.walkBorrows = this.walkBorrowsByFunction.get(fn.name) ?? new Set();
|
|
4066
4479
|
this.integerRanges = analyzeIntegerRanges(numericFn, this.int32Slots.facts(fn.name));
|
|
4067
4480
|
this.bytesBounds = findBytesBounds(numericFn, this.integerRanges);
|
|
4481
|
+
// Debug builds keep every number local a double for the debugger's
|
|
4482
|
+
// variable view. Lazily boxed captured parameters keep their double,
|
|
4483
|
+
// which the box is created from.
|
|
4484
|
+
const int32Locals = this.debug === null
|
|
4485
|
+
? findInt32Locals(fn, this.integerRanges, this.int32Slots.facts(fn.name), new Set(this.lazyCaptureBoxes.keys()))
|
|
4486
|
+
: null;
|
|
4487
|
+
this.int32Locals = int32Locals?.locals ?? new Map();
|
|
4488
|
+
this.int32LocalWrites = int32Locals?.values ?? new Set();
|
|
4068
4489
|
this.chainSlots.clear();
|
|
4069
4490
|
this.finallyStack = [];
|
|
4070
4491
|
this.tryStack = [];
|
|
4071
4492
|
this.loopDepth = 0;
|
|
4072
4493
|
this.currentReturnType = fn.returnType;
|
|
4494
|
+
const int32Abi = this.int32Abis.get(fn.name);
|
|
4495
|
+
this.currentInt32Result = int32Abi?.result === true;
|
|
4496
|
+
this.currentUnionResult = int32Abi?.union === true;
|
|
4073
4497
|
this.currentGenerator = fn.generator ?? null;
|
|
4498
|
+
this.currentAsyncGenerator = fn.async === true && fn.generator !== undefined;
|
|
4074
4499
|
this.currentBorrowedReturn = this.borrowedReturns.has(fn.name);
|
|
4075
4500
|
this.inConstructor = this.constructorFunctions.has(fn.name);
|
|
4076
|
-
this.
|
|
4501
|
+
this.currentCoro = null;
|
|
4077
4502
|
this.logArgSlots = 0;
|
|
4078
|
-
if (this.
|
|
4503
|
+
if (this.coroLowered(fn)) {
|
|
4504
|
+
this.usesCoroutines = true;
|
|
4079
4505
|
this.declare(`declare token @llvm.coro.id(i32, ptr, ptr, ptr)`);
|
|
4080
4506
|
this.declare(`declare ${this.sizeType} @llvm.coro.size.${this.sizeType}()`);
|
|
4081
4507
|
this.declare(`declare ptr @llvm.coro.begin(token, ptr)`);
|
|
@@ -4083,9 +4509,12 @@ export class LlEmitter {
|
|
|
4083
4509
|
this.declare(`declare i8 @llvm.coro.suspend(token, i1)`);
|
|
4084
4510
|
this.declare(`declare ptr @llvm.coro.free(token, ptr)`);
|
|
4085
4511
|
this.declare(`declare void @llvm.coro.end(ptr, i1, token)`);
|
|
4086
|
-
|
|
4087
|
-
this.
|
|
4088
|
-
this.declare(`declare
|
|
4512
|
+
// Native frames come from the runtime's small-block allocator.
|
|
4513
|
+
const frameAlloc = this.wasi ? "malloc" : "scr_rt_calloc";
|
|
4514
|
+
this.declare(`declare ptr @${frameAlloc}(${this.sizeType})`);
|
|
4515
|
+
this.declare(`declare void @${this.wasi ? "free" : "scr_rt_free"}(ptr)`);
|
|
4516
|
+
const started = this.wasi ? "scr_wasi_coro_started" : "scr_coro_started";
|
|
4517
|
+
this.declare(`declare void @${started}(ptr)`);
|
|
4089
4518
|
this.declare(`declare ptr @scr_fiber_self()`);
|
|
4090
4519
|
const id = B.tmp();
|
|
4091
4520
|
const size = B.tmp();
|
|
@@ -4094,11 +4523,11 @@ export class LlEmitter {
|
|
|
4094
4523
|
const self = B.tmp();
|
|
4095
4524
|
B.line(`${id} = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)`);
|
|
4096
4525
|
B.line(`${size} = call ${this.sizeType} @llvm.coro.size.${this.sizeType}()`);
|
|
4097
|
-
B.line(`${mem} = call ptr
|
|
4526
|
+
B.line(`${mem} = call ptr @${frameAlloc}(${this.sizeType} ${size})`);
|
|
4098
4527
|
B.line(`${handle} = call ptr @llvm.coro.begin(token ${id}, ptr ${mem})`);
|
|
4099
|
-
B.line(`call void
|
|
4528
|
+
B.line(`call void @${started}(ptr ${handle})`);
|
|
4100
4529
|
B.line(`${self} = call ptr @scr_fiber_self()`);
|
|
4101
|
-
this.
|
|
4530
|
+
this.currentCoro = {
|
|
4102
4531
|
kind: fn.generator !== undefined ? "generator" : "async",
|
|
4103
4532
|
id,
|
|
4104
4533
|
handle,
|
|
@@ -4111,6 +4540,9 @@ export class LlEmitter {
|
|
|
4111
4540
|
const paramIds = new Set(fn.params.map((p) => p.localId));
|
|
4112
4541
|
if (this.debug === null) {
|
|
4113
4542
|
for (const id of findIntegerViews(numericFn)) {
|
|
4543
|
+
// An i32 slot is its own view.
|
|
4544
|
+
if (this.int32Locals.has(id))
|
|
4545
|
+
continue;
|
|
4114
4546
|
const slot = B.slot();
|
|
4115
4547
|
B.entryAllocas.push(`${slot} = alloca i32 ; integer view ${this.numericLocals.get(id).name}`);
|
|
4116
4548
|
this.integerViews.set(id, slot);
|
|
@@ -4130,7 +4562,9 @@ export class LlEmitter {
|
|
|
4130
4562
|
// box the catch prologue takes (scr_exc_take).
|
|
4131
4563
|
const slotTy = local.boxed || this.captureIds.has(local.id) || local.type.kind === "caught"
|
|
4132
4564
|
? "ptr"
|
|
4133
|
-
: this.
|
|
4565
|
+
: this.int32Locals.has(local.id)
|
|
4566
|
+
? "i32"
|
|
4567
|
+
: this.llType(local.type);
|
|
4134
4568
|
B.entryAllocas.push(`%${mangleLocal(local.id)} = alloca ${slotTy} ; ${local.name}`);
|
|
4135
4569
|
this.emitDebugLocal(local, `%${mangleLocal(local.id)}`, fn.params.findIndex((p) => p.localId === local.id) + 1, this.captureIds.has(local.id));
|
|
4136
4570
|
// Refcounted/boxed locals start NULL (the C prologue's `= NULL`):
|
|
@@ -4204,8 +4638,16 @@ export class LlEmitter {
|
|
|
4204
4638
|
fnScope.push({ slot, type: p.type, boxed: true });
|
|
4205
4639
|
continue;
|
|
4206
4640
|
}
|
|
4641
|
+
const incoming = { name: `%p_${mangleLocal(p.localId)}`, type: p.type };
|
|
4642
|
+
if (int32Abi?.params[index]) {
|
|
4643
|
+
const bits = `%p_${mangleLocal(p.localId)}${INT32_ABI_SUFFIX}`;
|
|
4644
|
+
incoming.uint32 = bits;
|
|
4645
|
+
incoming.integer = { name: bits, type: "i32", signed: true, range: INT32_RANGE };
|
|
4646
|
+
}
|
|
4647
|
+
if (this.storeInt32Local(p.localId, incoming))
|
|
4648
|
+
continue;
|
|
4207
4649
|
B.line(`store ${this.llType(p.type)} %p_${mangleLocal(p.localId)}, ptr ${slot}`);
|
|
4208
|
-
this.storeIntegerView(p.localId,
|
|
4650
|
+
this.storeIntegerView(p.localId, incoming);
|
|
4209
4651
|
if (isRefCounted(p.type) && !borrowed?.has(index))
|
|
4210
4652
|
fnScope.push({ slot, type: p.type });
|
|
4211
4653
|
if (this.reboundByFunction.get(fn.name)?.has(p.localId) && borrowed?.has(index)) {
|
|
@@ -4219,8 +4661,9 @@ export class LlEmitter {
|
|
|
4219
4661
|
}
|
|
4220
4662
|
}
|
|
4221
4663
|
this.scopes.push(fnScope);
|
|
4222
|
-
// Stackful native fibers
|
|
4223
|
-
// active exception context owns the chain, so
|
|
4664
|
+
// Stackful native fibers and native coroutine frames retain these frames
|
|
4665
|
+
// while suspended. The active exception context owns the chain, so
|
|
4666
|
+
// concurrent fibers isolate it.
|
|
4224
4667
|
if (this.stackTraces && !this.wasi && fn.sourceName !== undefined) {
|
|
4225
4668
|
this.declare(`declare void @scr_stack_enter(ptr, ptr)`);
|
|
4226
4669
|
this.declare(`declare void @scr_stack_leave(ptr)`);
|
|
@@ -4239,25 +4682,35 @@ export class LlEmitter {
|
|
|
4239
4682
|
// whose unwind released everything down to depth 0).
|
|
4240
4683
|
if (fn.returnType.kind === "void" && !B.isTerminated()) {
|
|
4241
4684
|
this.releaseScope(this.scopes[0]);
|
|
4242
|
-
|
|
4685
|
+
// Completing by falling off the end has nothing to await.
|
|
4686
|
+
if (this.currentAsyncGenerator)
|
|
4687
|
+
this.emitAsyncGenSettled();
|
|
4688
|
+
if (this.currentCoro !== null) {
|
|
4243
4689
|
this.emitWasiFulfill(null);
|
|
4244
|
-
B.terminate(`br label %${this.
|
|
4690
|
+
B.terminate(`br label %${this.currentCoro.finalLabel}`);
|
|
4245
4691
|
}
|
|
4246
4692
|
else {
|
|
4247
4693
|
B.terminate("ret void");
|
|
4248
4694
|
}
|
|
4249
4695
|
}
|
|
4250
4696
|
this.scopes.pop();
|
|
4251
|
-
const coro = this.
|
|
4697
|
+
const coro = this.currentCoro;
|
|
4252
4698
|
if (coro !== null) {
|
|
4253
4699
|
B.startBlock(coro.finalLabel);
|
|
4700
|
+
// Every exit of a coroutine body joins here: its source frame (part
|
|
4701
|
+
// of the coroutine frame) leaves the generator's own chain.
|
|
4702
|
+
const epilogue = B.returnEpilogue;
|
|
4703
|
+
B.returnEpilogue = null;
|
|
4704
|
+
if (epilogue !== null)
|
|
4705
|
+
B.line(epilogue);
|
|
4254
4706
|
if (fn.captures !== undefined) {
|
|
4255
4707
|
this.declare(`declare void @scr_closure_release(ptr)`);
|
|
4256
4708
|
B.line(`call void @scr_closure_release(ptr %sc_env)`);
|
|
4257
4709
|
}
|
|
4258
4710
|
if (coro.kind === "generator") {
|
|
4259
|
-
this.
|
|
4260
|
-
|
|
4711
|
+
const finish = this.wasi ? "scr_wasi_gen_finish" : "scr_gen_coro_finish";
|
|
4712
|
+
this.declare(`declare void @${finish}(ptr)`);
|
|
4713
|
+
B.line(`call void @${finish}(ptr ${coro.self})`);
|
|
4261
4714
|
}
|
|
4262
4715
|
else {
|
|
4263
4716
|
this.declare(`declare void @scr_wasi_async_finish(ptr)`);
|
|
@@ -4269,7 +4722,7 @@ export class LlEmitter {
|
|
|
4269
4722
|
B.startBlock(coro.cleanupLabel);
|
|
4270
4723
|
const frame = B.tmp();
|
|
4271
4724
|
B.line(`${frame} = call ptr @llvm.coro.free(token ${coro.id}, ptr ${coro.handle})`);
|
|
4272
|
-
B.line(`call void
|
|
4725
|
+
B.line(`call void @${this.wasi ? "free" : "scr_rt_free"}(ptr ${frame})`);
|
|
4273
4726
|
B.br(coro.suspendLabel);
|
|
4274
4727
|
B.startBlock(coro.suspendLabel);
|
|
4275
4728
|
B.line(`call void @llvm.coro.end(ptr ${coro.handle}, i1 false, token none)`);
|
|
@@ -4294,11 +4747,26 @@ export class LlEmitter {
|
|
|
4294
4747
|
if (this.logArgSlots > 0) {
|
|
4295
4748
|
B.entryAllocas.push(`%logargs = alloca [${this.logArgSlots} x %ScrLogArg]`);
|
|
4296
4749
|
}
|
|
4297
|
-
const params = fn.params.map((p) =>
|
|
4750
|
+
const params = fn.params.map((p, index) => int32Abi?.params[index]
|
|
4751
|
+
? `i32 %p_${mangleLocal(p.localId)}${INT32_ABI_SUFFIX}`
|
|
4752
|
+
: `${this.llType(p.type)} %p_${mangleLocal(p.localId)}`);
|
|
4753
|
+
// The i32 entry widens its i32 parameters once; the proof makes the
|
|
4754
|
+
// double exact, so readers of the incoming number see the same value.
|
|
4755
|
+
if (int32Abi)
|
|
4756
|
+
fn.params.forEach((p, index) => {
|
|
4757
|
+
if (!int32Abi.params[index])
|
|
4758
|
+
return;
|
|
4759
|
+
const name = `%p_${mangleLocal(p.localId)}`;
|
|
4760
|
+
B.entryAllocas.push(`${name} = sitofp i32 ${name}${INT32_ABI_SUFFIX} to double`);
|
|
4761
|
+
});
|
|
4298
4762
|
// Lifted functions receive their closure first (the callValue ABI).
|
|
4299
4763
|
if (fn.captures !== undefined)
|
|
4300
4764
|
params.unshift("ptr %sc_env");
|
|
4301
|
-
const ret =
|
|
4765
|
+
const ret = int32Abi?.result
|
|
4766
|
+
? "i32"
|
|
4767
|
+
: int32Abi?.union
|
|
4768
|
+
? SCALAR_UNION_RESULT
|
|
4769
|
+
: this.llType(fn.returnType);
|
|
4302
4770
|
let attrs = coro !== null ? "#1" : FN_ATTRS;
|
|
4303
4771
|
// The shared indexed-read helper (lowerSafeIndexRead) is a dense bounds
|
|
4304
4772
|
// test, a state test and a load behind cold runtime fallbacks, called
|
|
@@ -4309,7 +4777,9 @@ export class LlEmitter {
|
|
|
4309
4777
|
attrs = `inlinehint ${attrs}`;
|
|
4310
4778
|
const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`;
|
|
4311
4779
|
const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
|
|
4312
|
-
|
|
4780
|
+
let body = `define internal ${ret} @${symbol}${int32Abi ? INT32_ABI_SUFFIX : ""}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
|
|
4781
|
+
if (int32Abi)
|
|
4782
|
+
body += "\n" + this.emitInt32AbiWrapper(fn, symbol, int32Abi);
|
|
4313
4783
|
if (!borrowed)
|
|
4314
4784
|
return body;
|
|
4315
4785
|
const slot = this.implSlotBorrowed(fn.name);
|
|
@@ -4318,9 +4788,141 @@ export class LlEmitter {
|
|
|
4318
4788
|
: "";
|
|
4319
4789
|
return body + "\n" + this.emitOwnedCallAdapter(fn, borrowed) + virtual;
|
|
4320
4790
|
}
|
|
4791
|
+
/** The double-ABI entry of a function with an i32 entry: every caller
|
|
4792
|
+
* that is not a direct call (closures, dispatch, adapters, the runtime)
|
|
4793
|
+
* enters here. A pending exception passes through to its caller. */
|
|
4794
|
+
emitInt32AbiWrapper(fn, symbol, abi) {
|
|
4795
|
+
const lines = [];
|
|
4796
|
+
const params = [];
|
|
4797
|
+
const args = [];
|
|
4798
|
+
fn.params.forEach((param, index) => {
|
|
4799
|
+
const type = this.llType(param.type);
|
|
4800
|
+
params.push(`${type} %p${index}`);
|
|
4801
|
+
if (abi.params[index]) {
|
|
4802
|
+
lines.push(` %a${index} = fptosi double %p${index} to i32`);
|
|
4803
|
+
args.push(`i32 %a${index}`);
|
|
4804
|
+
}
|
|
4805
|
+
else
|
|
4806
|
+
args.push(`${type} %p${index}`);
|
|
4807
|
+
});
|
|
4808
|
+
const ret = this.llType(fn.returnType);
|
|
4809
|
+
const entry = `@${symbol}${INT32_ABI_SUFFIX}(${args.join(", ")})`;
|
|
4810
|
+
if (abi.union) {
|
|
4811
|
+
// Box the register pair for every caller of the ordinary ABI.
|
|
4812
|
+
const B = new BlockBuilder();
|
|
4813
|
+
this.B = B;
|
|
4814
|
+
for (const line of lines)
|
|
4815
|
+
B.line(line.trim());
|
|
4816
|
+
const pair = B.tmp();
|
|
4817
|
+
B.line(`${pair} = call ${SCALAR_UNION_RESULT} ${entry}`);
|
|
4818
|
+
B.terminate(`ret ptr ${this.boxScalarUnion(pair, fn.returnType)}`);
|
|
4819
|
+
return `define internal ptr @${symbol}(${params.join(", ")}) ${FN_ATTRS} {\n${B.render()}\n}`;
|
|
4820
|
+
}
|
|
4821
|
+
const call = `call ${abi.result ? "i32" : ret} ${entry}`;
|
|
4822
|
+
if (ret === "void")
|
|
4823
|
+
lines.push(` ${call}`, " ret void");
|
|
4824
|
+
else if (abi.result)
|
|
4825
|
+
lines.push(` %r = ${call}`, ` %d = sitofp i32 %r to double`, ` ret double %d`);
|
|
4826
|
+
else
|
|
4827
|
+
lines.push(` %r = ${call}`, ` ret ${ret} %r`);
|
|
4828
|
+
return `define internal ${ret} @${symbol}(${params.join(", ")}) ${FN_ATTRS} {\nentry:\n${lines.join("\n")}\n}`;
|
|
4829
|
+
}
|
|
4830
|
+
/** The SCALAR_UNION_RESULT pair of a `%ScrUnion` box of `type`. */
|
|
4831
|
+
scalarUnionPair(box, type) {
|
|
4832
|
+
const B = this.B;
|
|
4833
|
+
const unionId = type.kind === "union" ? type.unionId : "";
|
|
4834
|
+
const tag = this.unionTag(box, unionId);
|
|
4835
|
+
const payload = B.tmp(), bits = B.tmp(), partial = B.tmp(), pair = B.tmp();
|
|
4836
|
+
B.line(`${payload} = getelementptr inbounds %ScrUnion, ptr ${box}, i32 0, i32 5`);
|
|
4837
|
+
B.line(`${bits} = load i64, ptr ${payload}`);
|
|
4838
|
+
B.line(`${partial} = insertvalue ${SCALAR_UNION_RESULT} poison, i32 ${tag}, 0`);
|
|
4839
|
+
B.line(`${pair} = insertvalue ${SCALAR_UNION_RESULT} ${partial}, i64 ${bits}, 1`);
|
|
4840
|
+
return pair;
|
|
4841
|
+
}
|
|
4842
|
+
/** A private stack box holding a SCALAR_UNION_RESULT pair of `type`. */
|
|
4843
|
+
stackScalarUnion(pair, type) {
|
|
4844
|
+
const B = this.B;
|
|
4845
|
+
const box = B.slot(), tagSlot = B.slot(), payloadSlot = B.slot();
|
|
4846
|
+
B.entryAllocas.push(`${box} = alloca %ScrUnion`);
|
|
4847
|
+
B.entryAllocas.push(`${tagSlot} = getelementptr inbounds %ScrUnion, ptr ${box}, i32 0, i32 1`);
|
|
4848
|
+
B.entryAllocas.push(`${payloadSlot} = getelementptr inbounds %ScrUnion, ptr ${box}, i32 0, i32 5`);
|
|
4849
|
+
const tag = B.tmp(), bits = B.tmp();
|
|
4850
|
+
B.line(`${tag} = extractvalue ${SCALAR_UNION_RESULT} ${pair}, 0`);
|
|
4851
|
+
B.line(`${bits} = extractvalue ${SCALAR_UNION_RESULT} ${pair}, 1`);
|
|
4852
|
+
B.line(`store i32 ${tag}, ptr ${tagSlot}`);
|
|
4853
|
+
B.line(`store i64 ${bits}, ptr ${payloadSlot}`);
|
|
4854
|
+
return { name: box, type };
|
|
4855
|
+
}
|
|
4856
|
+
/** A direct call whose i32 entry returns its scalar union in registers. */
|
|
4857
|
+
scalarResultCall(e) {
|
|
4858
|
+
return e.kind === "call" && this.int32Abis.get(e.callee)?.union === true;
|
|
4859
|
+
}
|
|
4860
|
+
/** A fresh heap box (+1) for a SCALAR_UNION_RESULT pair of `type`; the
|
|
4861
|
+
* exception dummy (tag -1) answers NULL. */
|
|
4862
|
+
boxScalarUnion(pair, type) {
|
|
4863
|
+
const B = this.B;
|
|
4864
|
+
const unionId = type.kind === "union" ? type.unionId : "";
|
|
4865
|
+
const arms = this.unionsById.get(unionId).arms;
|
|
4866
|
+
const tag = B.tmp(), bits = B.tmp();
|
|
4867
|
+
B.line(`${tag} = extractvalue ${SCALAR_UNION_RESULT} ${pair}, 0`);
|
|
4868
|
+
B.line(`${bits} = extractvalue ${SCALAR_UNION_RESULT} ${pair}, 1`);
|
|
4869
|
+
const join = B.newLabel("union.box.join"), none = B.newLabel("union.box.none");
|
|
4870
|
+
const labels = arms.map(() => B.newLabel("union.box.arm"));
|
|
4871
|
+
B.terminate(`switch i32 ${tag}, label %${none} [ ${labels.map((label, armTag) => `i32 ${armTag}, label %${label}`).join(" ")} ]`);
|
|
4872
|
+
const incoming = [];
|
|
4873
|
+
arms.forEach((arm, armTag) => {
|
|
4874
|
+
B.startBlock(labels[armTag]);
|
|
4875
|
+
let value;
|
|
4876
|
+
if (isUnitType(arm))
|
|
4877
|
+
value = this.unitInstanceRef(unionId, armTag);
|
|
4878
|
+
else {
|
|
4879
|
+
const payload = B.tmp();
|
|
4880
|
+
if (arm.kind === "bool")
|
|
4881
|
+
B.line(`${payload} = trunc i64 ${bits} to i1`);
|
|
4882
|
+
else
|
|
4883
|
+
B.line(`${payload} = bitcast i64 ${bits} to double`);
|
|
4884
|
+
value = this.unionNewOwned(unionId, armTag, { name: payload, type: arm });
|
|
4885
|
+
}
|
|
4886
|
+
incoming.push(`[ ${value}, %${B.currentLabel()} ]`);
|
|
4887
|
+
B.br(join);
|
|
4888
|
+
});
|
|
4889
|
+
B.startBlock(none);
|
|
4890
|
+
incoming.push(`[ null, %${none} ]`);
|
|
4891
|
+
B.br(join);
|
|
4892
|
+
B.startBlock(join);
|
|
4893
|
+
const box = B.tmp();
|
|
4894
|
+
B.line(`${box} = phi ptr ${incoming.join(", ")}`);
|
|
4895
|
+
return box;
|
|
4896
|
+
}
|
|
4897
|
+
/** The i32 bits of a number proven an int32 (a direct call's i32
|
|
4898
|
+
* argument or an i32 result): its exact integer when the value carries
|
|
4899
|
+
* one, else the conversion the proof makes exact. */
|
|
4900
|
+
int32Argument(value, expr) {
|
|
4901
|
+
const integer = exactInteger(this, value, expr);
|
|
4902
|
+
if (integer && /^-?\d+$/.test(integer.name))
|
|
4903
|
+
return String(Number(integer.name) | 0);
|
|
4904
|
+
if (integer)
|
|
4905
|
+
return narrowInteger(this, integer);
|
|
4906
|
+
const bits = this.B.tmp();
|
|
4907
|
+
this.B.line(`${bits} = fptosi double ${value.name} to i32`);
|
|
4908
|
+
return bits;
|
|
4909
|
+
}
|
|
4910
|
+
/** A direct call's i32 result as a number, with the i32 as its integer
|
|
4911
|
+
* and ToUint32 forms. */
|
|
4912
|
+
int32Result(bits, type) {
|
|
4913
|
+
const number = this.B.tmp();
|
|
4914
|
+
this.B.line(`${number} = sitofp i32 ${bits} to double`);
|
|
4915
|
+
return {
|
|
4916
|
+
name: number,
|
|
4917
|
+
type,
|
|
4918
|
+
uint32: bits,
|
|
4919
|
+
integer: { name: bits, type: "i32", signed: true, range: INT32_RANGE },
|
|
4920
|
+
};
|
|
4921
|
+
}
|
|
4321
4922
|
/** Keep the ordinary owned ABI for closures, virtual dispatch, generated
|
|
4322
|
-
* runtime adapters and library exports. Only direct IR calls
|
|
4323
|
-
*
|
|
4923
|
+
* runtime adapters and library exports. Only direct IR calls, calls of a
|
|
4924
|
+
* known closure body and borrowing closure entries (fn_borrowed) select
|
|
4925
|
+
* the borrowing body. Ownership of other parameters moves into that body;
|
|
4324
4926
|
* borrowed parameters are released here on normal and exceptional exits.
|
|
4325
4927
|
* The pending exception remains for the adapter's caller to handle. */
|
|
4326
4928
|
/** With `kept`, the virtual adapter of a borrowing slot instead: the
|
|
@@ -4330,6 +4932,9 @@ export class LlEmitter {
|
|
|
4330
4932
|
const B = new BlockBuilder();
|
|
4331
4933
|
this.B = B;
|
|
4332
4934
|
const params = fn.params.map((param, index) => `${this.llType(param.type)} %p${index}`);
|
|
4935
|
+
// Lifted bodies receive their closure first (the callValue ABI).
|
|
4936
|
+
if (fn.captures !== undefined)
|
|
4937
|
+
params.unshift("ptr %sc_env");
|
|
4333
4938
|
const ret = this.llType(fn.returnType);
|
|
4334
4939
|
const call = `call ${ret} @${mangleBorrowedFunction(fn.name)}(${params.join(", ")})`;
|
|
4335
4940
|
B.line(ret === "void" ? call : `%result = ${call}`);
|
|
@@ -4417,6 +5022,9 @@ export class LlEmitter {
|
|
|
4417
5022
|
// A module constant owns its value for the rest of the program.
|
|
4418
5023
|
if (binding.kind === "global")
|
|
4419
5024
|
return this.stableGlobals.has(value.localId);
|
|
5025
|
+
// So does the environment for a capture nothing ever rebinds.
|
|
5026
|
+
if (binding.kind === "boxed")
|
|
5027
|
+
return this.stableCaptureIds.has(value.localId) && this.canBorrowReceiver(value);
|
|
4420
5028
|
return (binding.kind === "local" &&
|
|
4421
5029
|
binding.local !== undefined &&
|
|
4422
5030
|
(!binding.local.mutable ||
|
|
@@ -4494,12 +5102,80 @@ export class LlEmitter {
|
|
|
4494
5102
|
return false;
|
|
4495
5103
|
return !rw.conflicts(["a", ...keys], writes);
|
|
4496
5104
|
}
|
|
5105
|
+
/** `return p` of a promise inside try regions of an async function. JS
|
|
5106
|
+
* completes the return first: p is evaluated, every crossed finally runs,
|
|
5107
|
+
* and only then does the function's promise adopt p's settlement (a then
|
|
5108
|
+
* job, then p's reaction). So the promise parks in a slot through the
|
|
5109
|
+
* finally copies, like any pending return value, and the adopting await
|
|
5110
|
+
* runs afterwards with no handler in scope: a rejection settles the
|
|
5111
|
+
* function's promise instead of reaching its own catch clauses. */
|
|
5112
|
+
emitAdoptingReturn(s, adopted) {
|
|
5113
|
+
const B = this.B;
|
|
5114
|
+
const promise = this.emitExpr(adopted.value);
|
|
5115
|
+
this.moveTemp(promise);
|
|
5116
|
+
const slot = B.slot();
|
|
5117
|
+
B.entryAllocas.push(`${slot} = alloca ptr ; adopted return promise`);
|
|
5118
|
+
B.line(`store ptr ${promise.name}, ptr ${slot}`);
|
|
5119
|
+
const parked = { slot, type: adopted.value.type };
|
|
5120
|
+
const savedFrames = this.frames;
|
|
5121
|
+
const savedScopes = this.scopes;
|
|
5122
|
+
const savedFinally = this.finallyStack;
|
|
5123
|
+
const savedTry = this.tryStack;
|
|
5124
|
+
for (let i = savedFinally.length - 1; i >= 0 && !B.isTerminated(); i--) {
|
|
5125
|
+
const fin = savedFinally[i];
|
|
5126
|
+
this.releaseForJump(fin.frameDepth, fin.scopeDepth);
|
|
5127
|
+
this.frames = this.frames.slice(0, fin.frameDepth);
|
|
5128
|
+
this.scopes = this.scopes.slice(0, fin.scopeDepth);
|
|
5129
|
+
this.finallyStack = savedFinally.slice(0, i);
|
|
5130
|
+
this.tryStack = savedTry.slice(0, fin.tryDepth);
|
|
5131
|
+
this.scopes.push([parked]);
|
|
5132
|
+
this.emitBlock(fin.body);
|
|
5133
|
+
this.scopes.pop();
|
|
5134
|
+
}
|
|
5135
|
+
if (!B.isTerminated()) {
|
|
5136
|
+
this.releaseForJump(0, 0);
|
|
5137
|
+
// Nothing of the body is live any more except the parked promise,
|
|
5138
|
+
// whose scope releases it on either exit of the await.
|
|
5139
|
+
this.frames = [[]];
|
|
5140
|
+
this.scopes = [[parked]];
|
|
5141
|
+
this.finallyStack = [];
|
|
5142
|
+
this.tryStack = [];
|
|
5143
|
+
const loaded = B.tmp();
|
|
5144
|
+
B.line(`${loaded} = load ptr, ptr ${slot}`);
|
|
5145
|
+
this.adoptedOperand = { name: loaded, type: adopted.value.type };
|
|
5146
|
+
const v = this.emitExpr(s.value);
|
|
5147
|
+
if (this.adoptedOperand !== null)
|
|
5148
|
+
throw new InternalCompilerError("llvm emitter bug: adopted return promise was not awaited");
|
|
5149
|
+
this.moveTemp(v);
|
|
5150
|
+
this.releaseForJump(0, 0);
|
|
5151
|
+
if (!B.isTerminated()) {
|
|
5152
|
+
if (this.currentCoro !== null) {
|
|
5153
|
+
this.emitWasiFulfill(v);
|
|
5154
|
+
B.terminate(`br label %${this.currentCoro.finalLabel}`);
|
|
5155
|
+
}
|
|
5156
|
+
else if (s.value.type.kind === "void")
|
|
5157
|
+
B.terminate("ret void");
|
|
5158
|
+
else
|
|
5159
|
+
B.terminate(`ret ${this.llType(s.value.type)} ${v.name}`);
|
|
5160
|
+
}
|
|
5161
|
+
}
|
|
5162
|
+
this.frames = savedFrames;
|
|
5163
|
+
this.scopes = savedScopes;
|
|
5164
|
+
this.finallyStack = savedFinally;
|
|
5165
|
+
this.tryStack = savedTry;
|
|
5166
|
+
}
|
|
5167
|
+
/** An async generator completing without a further turn: its request
|
|
5168
|
+
* settles as the body returns (scr_async_gen_hop_done). */
|
|
5169
|
+
emitAsyncGenSettled() {
|
|
5170
|
+
this.declare(`declare void @scr_async_gen_hop_done()`);
|
|
5171
|
+
this.B.line(`call void @scr_async_gen_hop_done()`);
|
|
5172
|
+
}
|
|
4497
5173
|
/** `return x` of a local whose slot is an owned scope entry of the same
|
|
4498
5174
|
* type: the return can take over the local's reference. */
|
|
4499
5175
|
movableReturn(value) {
|
|
4500
5176
|
if (value === null || value.kind !== "varRef" || !isRefCounted(value.type))
|
|
4501
5177
|
return null;
|
|
4502
|
-
if (this.currentBorrowedReturn || this.
|
|
5178
|
+
if (this.currentBorrowedReturn || this.currentCoro !== null)
|
|
4503
5179
|
return null;
|
|
4504
5180
|
const binding = this.binding(value.localId);
|
|
4505
5181
|
if (binding.kind !== "local" || binding.local === undefined || binding.local.boxed)
|
|
@@ -4669,6 +5345,10 @@ export class LlEmitter {
|
|
|
4669
5345
|
this.frames.push([]);
|
|
4670
5346
|
switch (s.kind) {
|
|
4671
5347
|
case "varDecl": {
|
|
5348
|
+
if (s.init && this.int32Locals.has(s.localId)) {
|
|
5349
|
+
this.storeInt32Local(s.localId, this.emitExpr(s.init), s.init);
|
|
5350
|
+
break;
|
|
5351
|
+
}
|
|
4672
5352
|
const b = this.binding(s.localId);
|
|
4673
5353
|
if (this.walkBorrows.has(s.localId) && b.kind === "local") {
|
|
4674
5354
|
// A borrowed walk pointer: no retain now, no release at scope exit.
|
|
@@ -4690,6 +5370,17 @@ export class LlEmitter {
|
|
|
4690
5370
|
this.scopes[this.scopes.length - 1].push({ slot: snapshot.sourceSlot, type: STRING }, { slot: snapshot.separatorSlot, type: STRING });
|
|
4691
5371
|
break;
|
|
4692
5372
|
}
|
|
5373
|
+
const stackObject = this.localStackObjects.get(s.localId);
|
|
5374
|
+
if (stackObject) {
|
|
5375
|
+
B.line(`store ptr ${emitStackObject(this, stackObject).name}, ptr ${b.slot}`);
|
|
5376
|
+
break;
|
|
5377
|
+
}
|
|
5378
|
+
if (s.init && this.reassignedStackObjects.has(s.localId)) {
|
|
5379
|
+
const slot = this.stackObjectSlot(s.localId, s.init.type);
|
|
5380
|
+
emitStackObject(this, s.init, slot);
|
|
5381
|
+
B.line(`store ptr ${slot}, ptr ${b.slot}`);
|
|
5382
|
+
break;
|
|
5383
|
+
}
|
|
4693
5384
|
const localUnion = this.localStackUnions.get(s.localId);
|
|
4694
5385
|
if (localUnion) {
|
|
4695
5386
|
const union = emitStackUnion(this, localUnion);
|
|
@@ -4816,6 +5507,10 @@ export class LlEmitter {
|
|
|
4816
5507
|
break;
|
|
4817
5508
|
}
|
|
4818
5509
|
case "assign": {
|
|
5510
|
+
if (this.int32Locals.has(s.localId)) {
|
|
5511
|
+
this.storeInt32Local(s.localId, this.emitExpr(s.value), s.value);
|
|
5512
|
+
break;
|
|
5513
|
+
}
|
|
4819
5514
|
const owner = this.reboundOwnerSlots.get(s.localId);
|
|
4820
5515
|
if (owner !== undefined && !this.walkBorrows.has(s.localId)) {
|
|
4821
5516
|
// A rebound borrowed parameter: the new value moves into the owner
|
|
@@ -4833,6 +5528,15 @@ export class LlEmitter {
|
|
|
4833
5528
|
B.line(`store ptr ${v.name}, ptr ${owner}`);
|
|
4834
5529
|
break;
|
|
4835
5530
|
}
|
|
5531
|
+
if (this.reassignedStackObjects.has(s.localId)) {
|
|
5532
|
+
// The new value may read the old one: build it apart, then copy.
|
|
5533
|
+
const struct = stackObjectStruct(s.value.type);
|
|
5534
|
+
const value = emitStackObject(this, s.value);
|
|
5535
|
+
const bits = B.tmp();
|
|
5536
|
+
B.line(`${bits} = load ${struct}, ptr ${value.name}`);
|
|
5537
|
+
B.line(`store ${struct} ${bits}, ptr ${this.stackObjectSlot(s.localId, s.value.type)}`);
|
|
5538
|
+
break;
|
|
5539
|
+
}
|
|
4836
5540
|
const localStorage = this.localUnionStorage.get(s.localId);
|
|
4837
5541
|
if (localStorage) {
|
|
4838
5542
|
storeLocalUnion(this, localStorage, s.value);
|
|
@@ -4904,8 +5608,10 @@ export class LlEmitter {
|
|
|
4904
5608
|
case "arraySet": {
|
|
4905
5609
|
// Evaluation order matches JS: array, index, then value. Ownership
|
|
4906
5610
|
// of a refcounted value moves into the array (the runtime releases
|
|
4907
|
-
// the replaced element itself).
|
|
4908
|
-
|
|
5611
|
+
// the replaced element itself). A local receiver that the index and
|
|
5612
|
+
// value cannot rebind is read in place, as for field stores; the
|
|
5613
|
+
// released element cannot reach that binding.
|
|
5614
|
+
const arr = this.emitStableReceiver(s.arr, [s.index, s.value]);
|
|
4909
5615
|
const idx = this.emitExpr(s.index);
|
|
4910
5616
|
const v = this.emitExpr(s.value);
|
|
4911
5617
|
if (s.arr.type.kind !== "array")
|
|
@@ -5083,7 +5789,6 @@ export class LlEmitter {
|
|
|
5083
5789
|
break;
|
|
5084
5790
|
}
|
|
5085
5791
|
const join = B.newLabel("rks.j");
|
|
5086
|
-
this.declare(`declare zeroext i1 @scr_str_eq(ptr, ptr)`);
|
|
5087
5792
|
if (iv.kind === "dyn" && shape.indexValue) {
|
|
5088
5793
|
// A dyn-valued shape (the C recordKeySetHelper's dyn arm,
|
|
5089
5794
|
// inline): declared keys VALIDATE the dyn value against the
|
|
@@ -5093,9 +5798,8 @@ export class LlEmitter {
|
|
|
5093
5798
|
// insert the dyn value into the overflow map as-is.
|
|
5094
5799
|
this.declare(`declare void @scr_dyn_release(ptr)`);
|
|
5095
5800
|
for (const f of shape.fields) {
|
|
5096
|
-
const lit = this.internLiteral(f.name);
|
|
5097
5801
|
const hit = B.tmp();
|
|
5098
|
-
B.line(`${hit} = call zeroext i1
|
|
5802
|
+
B.line(`${hit} = call zeroext i1 ${this.literalEquals(f.name)}(ptr ${key.name}) ; ${llvmCommentText(f.name)}`);
|
|
5099
5803
|
const lh = B.newLabel("rks.h");
|
|
5100
5804
|
const ln = B.newLabel("rks.n");
|
|
5101
5805
|
B.condBr(hit, lh, ln);
|
|
@@ -5149,9 +5853,8 @@ export class LlEmitter {
|
|
|
5149
5853
|
}
|
|
5150
5854
|
for (const f of shape.fields) {
|
|
5151
5855
|
// typeEquals(f.type, iv) — the frontend fences everything else.
|
|
5152
|
-
const lit = this.internLiteral(f.name);
|
|
5153
5856
|
const hit = B.tmp();
|
|
5154
|
-
B.line(`${hit} = call zeroext i1
|
|
5857
|
+
B.line(`${hit} = call zeroext i1 ${this.literalEquals(f.name)}(ptr ${key.name}) ; ${llvmCommentText(f.name)}`);
|
|
5155
5858
|
const lh = B.newLabel("rks.h");
|
|
5156
5859
|
const ln = B.newLabel("rks.n");
|
|
5157
5860
|
B.condBr(hit, lh, ln);
|
|
@@ -5678,9 +6381,24 @@ export class LlEmitter {
|
|
|
5678
6381
|
// try, past its own catch), then the function-level releases and
|
|
5679
6382
|
// the actual ret. The parked value owns a synthetic slot-backed
|
|
5680
6383
|
// scope entry during each copy so a throwing finally releases it.
|
|
6384
|
+
const adopted = this.finallyStack.length > 0 || this.tryStack.length > 0
|
|
6385
|
+
? adoptingAwaitOf(s.value)
|
|
6386
|
+
: null;
|
|
6387
|
+
if (adopted !== null) {
|
|
6388
|
+
this.emitAdoptingReturn(s, adopted);
|
|
6389
|
+
break;
|
|
6390
|
+
}
|
|
5681
6391
|
let v = null;
|
|
5682
|
-
const moved = this.finallyStack.length === 0
|
|
5683
|
-
|
|
6392
|
+
const moved = this.finallyStack.length === 0 && !this.currentUnionResult
|
|
6393
|
+
? this.movableReturn(s.value)
|
|
6394
|
+
: null;
|
|
6395
|
+
if (s.value !== null && this.currentUnionResult) {
|
|
6396
|
+
// The tag and payload bits leave in registers; a heap box the
|
|
6397
|
+
// value needed is released with its frame below.
|
|
6398
|
+
const box = this.emitUnionProjection(s.value);
|
|
6399
|
+
v = { name: this.scalarUnionPair(box.name, s.value.type), type: s.value.type };
|
|
6400
|
+
}
|
|
6401
|
+
else if (moved !== null) {
|
|
5684
6402
|
// Returning an owned local hands its reference to the caller: no
|
|
5685
6403
|
// retain now and no release of the local below.
|
|
5686
6404
|
const t = B.tmp();
|
|
@@ -5710,9 +6428,22 @@ export class LlEmitter {
|
|
|
5710
6428
|
v = this.emitExpr(s.value);
|
|
5711
6429
|
this.moveTemp(v);
|
|
5712
6430
|
}
|
|
6431
|
+
// An async generator's `return v` awaits v before any crossed
|
|
6432
|
+
// finally runs. Past finally regions that turn is taken here and
|
|
6433
|
+
// the request settles at completion; otherwise the completion's
|
|
6434
|
+
// own turn is the await (nothing runs in between).
|
|
6435
|
+
const genSettles = this.currentAsyncGenerator && (s.awaited === true || this.finallyStack.length > 0);
|
|
6436
|
+
if (this.currentAsyncGenerator && s.awaited !== true && this.finallyStack.length > 0) {
|
|
6437
|
+
if (this.wasi)
|
|
6438
|
+
this.emitWasiSuspend(null);
|
|
6439
|
+
else {
|
|
6440
|
+
this.declare(`declare void @scr_await_hop()`);
|
|
6441
|
+
B.line(`call void @scr_await_hop()`);
|
|
6442
|
+
}
|
|
6443
|
+
}
|
|
5713
6444
|
if (this.finallyStack.length > 0) {
|
|
5714
6445
|
let pretSlot = null;
|
|
5715
|
-
if (v !== null && isRefCounted(v.type)) {
|
|
6446
|
+
if (v !== null && isRefCounted(v.type) && !this.currentUnionResult) {
|
|
5716
6447
|
pretSlot = B.slot();
|
|
5717
6448
|
B.entryAllocas.push(`${pretSlot} = alloca ptr ; pending return (through finally)`);
|
|
5718
6449
|
B.line(`store ptr ${v.name}, ptr ${pretSlot}`);
|
|
@@ -5745,12 +6476,18 @@ export class LlEmitter {
|
|
|
5745
6476
|
this.releaseForJump(0, 0);
|
|
5746
6477
|
}
|
|
5747
6478
|
if (!B.isTerminated()) {
|
|
5748
|
-
if (
|
|
6479
|
+
if (genSettles)
|
|
6480
|
+
this.emitAsyncGenSettled();
|
|
6481
|
+
if (this.currentCoro !== null) {
|
|
5749
6482
|
this.emitWasiFulfill(v);
|
|
5750
|
-
B.terminate(`br label %${this.
|
|
6483
|
+
B.terminate(`br label %${this.currentCoro.finalLabel}`);
|
|
5751
6484
|
}
|
|
5752
6485
|
else if (v === null)
|
|
5753
6486
|
B.terminate("ret void");
|
|
6487
|
+
else if (this.currentInt32Result)
|
|
6488
|
+
B.terminate(`ret i32 ${this.int32Argument(v, s.value)}`);
|
|
6489
|
+
else if (this.currentUnionResult)
|
|
6490
|
+
B.terminate(`ret ${SCALAR_UNION_RESULT} ${v.name}`);
|
|
5754
6491
|
else
|
|
5755
6492
|
B.terminate(`ret ${this.llType(s.value.type)} ${v.name}`);
|
|
5756
6493
|
}
|
|
@@ -6008,7 +6745,10 @@ export class LlEmitter {
|
|
|
6008
6745
|
this.frames.push([]);
|
|
6009
6746
|
const t = this.emitExpr(c.test);
|
|
6010
6747
|
const hit = B.tmp();
|
|
6011
|
-
if (c.test.
|
|
6748
|
+
if (c.test.kind === "strLit") {
|
|
6749
|
+
B.line(`${hit} = call zeroext i1 ${this.literalEquals(c.test.value)}(ptr ${disc.name})`);
|
|
6750
|
+
}
|
|
6751
|
+
else if (c.test.type.kind === "string") {
|
|
6012
6752
|
this.declare(`declare zeroext i1 @scr_str_eq(ptr, ptr)`);
|
|
6013
6753
|
B.line(`${hit} = call zeroext i1 @sc_str_eq(ptr ${disc.name}, ptr ${t.name})`);
|
|
6014
6754
|
}
|
|
@@ -6125,6 +6865,12 @@ export class LlEmitter {
|
|
|
6125
6865
|
return this.canBorrowCallArgument(value);
|
|
6126
6866
|
}
|
|
6127
6867
|
emitDiscarded(e) {
|
|
6868
|
+
// A statement-position yield never reads the resumed value: it stays
|
|
6869
|
+
// in the IN slot until the next resume replaces it.
|
|
6870
|
+
if (e.kind === "yieldExpr" && e.captureCompletion === undefined && e.type.kind !== "void") {
|
|
6871
|
+
this.emitExpr({ ...e, type: VOID });
|
|
6872
|
+
return;
|
|
6873
|
+
}
|
|
6128
6874
|
if (e.kind === "seqExpr" &&
|
|
6129
6875
|
isRefCounted(e.result.type) &&
|
|
6130
6876
|
e.result.kind !== "strLit" &&
|
|
@@ -6242,6 +6988,11 @@ export class LlEmitter {
|
|
|
6242
6988
|
emitCallExpr(e) {
|
|
6243
6989
|
return emitCallExpr(this, e);
|
|
6244
6990
|
}
|
|
6991
|
+
/** A direct async call an enclosing `await` consumes whole: emitted
|
|
6992
|
+
* through the callee's await-call entry, yielding the awaited value. */
|
|
6993
|
+
emitAwaitedCall(e, adopt = false) {
|
|
6994
|
+
return emitCallExpr(this, e, adopt ? "adopt" : "await");
|
|
6995
|
+
}
|
|
6245
6996
|
emitRecordExpr(e) {
|
|
6246
6997
|
return emitRecordExpr(this, e);
|
|
6247
6998
|
}
|
|
@@ -6251,8 +7002,45 @@ export class LlEmitter {
|
|
|
6251
7002
|
emitIntrinsicExpr(e) {
|
|
6252
7003
|
if (e.name === "threads.publish")
|
|
6253
7004
|
return this.emitPublish(e);
|
|
7005
|
+
if (e.name === "region.create" ||
|
|
7006
|
+
e.name === "region.enter" ||
|
|
7007
|
+
e.name === "region.leave" ||
|
|
7008
|
+
e.name === "region.release")
|
|
7009
|
+
return this.emitRegionIntrinsic(e);
|
|
6254
7010
|
return emitIntrinsicExpr(this, e);
|
|
6255
7011
|
}
|
|
7012
|
+
/** Lifetime regions in modules with lifetimeRegions (llvm/regions.ts).
|
|
7013
|
+
* Created with the class graph; null elsewhere. */
|
|
7014
|
+
regions = null;
|
|
7015
|
+
/** LifetimeRegion's runtime calls: create answers the region id, enter
|
|
7016
|
+
* and leave bracket run(), release condemns. */
|
|
7017
|
+
emitRegionIntrinsic(e) {
|
|
7018
|
+
if (this.regions === null)
|
|
7019
|
+
throw new InternalCompilerError(`llvm emitter bug: ${e.name} without lifetimeRegions`);
|
|
7020
|
+
const args = e.args.map((a) => this.emitExpr(a));
|
|
7021
|
+
if (this.B.isTerminated())
|
|
7022
|
+
return { name: "", type: e.type };
|
|
7023
|
+
switch (e.name) {
|
|
7024
|
+
case "region.create": {
|
|
7025
|
+
this.declare(`declare double @scr_region_create(ptr)`);
|
|
7026
|
+
const id = this.B.tmp();
|
|
7027
|
+
this.B.line(`${id} = call double @scr_region_create(ptr ${REGION_TYPES})`);
|
|
7028
|
+
return { name: id, type: e.type };
|
|
7029
|
+
}
|
|
7030
|
+
case "region.enter":
|
|
7031
|
+
this.declare(`declare void @scr_region_enter(double)`);
|
|
7032
|
+
this.B.line(`call void @scr_region_enter(double ${args[0].name})`);
|
|
7033
|
+
break;
|
|
7034
|
+
case "region.leave":
|
|
7035
|
+
this.declare(`declare void @scr_region_leave()`);
|
|
7036
|
+
this.B.line(`call void @scr_region_leave()`);
|
|
7037
|
+
break;
|
|
7038
|
+
default:
|
|
7039
|
+
this.declare(`declare void @scr_region_release(double)`);
|
|
7040
|
+
this.B.line(`call void @scr_region_release(double ${args[0].name})`);
|
|
7041
|
+
}
|
|
7042
|
+
return { name: "", type: e.type };
|
|
7043
|
+
}
|
|
6256
7044
|
publishWalkersCache = null;
|
|
6257
7045
|
/** Per-type publication walkers, created on the first publish. */
|
|
6258
7046
|
get publishWalkers() {
|
|
@@ -6539,10 +7327,30 @@ export class LlEmitter {
|
|
|
6539
7327
|
B.line(`${value} = load ptr, ptr ${slot}`);
|
|
6540
7328
|
return { name: value, type: e.type };
|
|
6541
7329
|
}
|
|
7330
|
+
// Scalar optional values and register results answer a private stack
|
|
7331
|
+
// box. A ternary of borrowable arms above keeps lending its arm.
|
|
7332
|
+
if ((e.kind === "optChain" ||
|
|
7333
|
+
e.kind === "nullish" ||
|
|
7334
|
+
e.kind === "ternary" ||
|
|
7335
|
+
e.kind === "arrIntrinsic") &&
|
|
7336
|
+
isScalarOptionalSource(e, this.unionSources))
|
|
7337
|
+
return emitScalarOptional(this, e);
|
|
7338
|
+
if (e.kind === "call" && this.scalarResultCall(e))
|
|
7339
|
+
return emitCallExpr(this, e, false, true);
|
|
6542
7340
|
if (e.kind === "call" && this.canBorrowReceiver(e))
|
|
6543
7341
|
return emitBorrowedResultCall(this, e);
|
|
6544
7342
|
return this.emitExpr(e);
|
|
6545
7343
|
}
|
|
7344
|
+
/** The frame slot a reassigned stack-object local keeps for its value. */
|
|
7345
|
+
stackObjectSlot(localId, type) {
|
|
7346
|
+
let slot = this.stackObjectSlots.get(localId);
|
|
7347
|
+
if (slot === undefined) {
|
|
7348
|
+
slot = this.B.slot();
|
|
7349
|
+
this.B.entryAllocas.push(`${slot} = alloca ${stackObjectStruct(type)}, align 8`);
|
|
7350
|
+
this.stackObjectSlots.set(localId, slot);
|
|
7351
|
+
}
|
|
7352
|
+
return slot;
|
|
7353
|
+
}
|
|
6546
7354
|
/** A private stack union source with an independently owned payload. */
|
|
6547
7355
|
canStackReceiver(e) {
|
|
6548
7356
|
return this.isStackUnionSource(e);
|
|
@@ -6554,7 +7362,9 @@ export class LlEmitter {
|
|
|
6554
7362
|
return false;
|
|
6555
7363
|
return (canStackUnion(e, this.boxedUnionsById) ||
|
|
6556
7364
|
matchMapRead(e, this.boxedUnionsById) !== null ||
|
|
6557
|
-
this.optionalArrayReads.get(e) !== null
|
|
7365
|
+
this.optionalArrayReads.get(e) !== null ||
|
|
7366
|
+
isScalarOptionalSource(e, this.unionSources) ||
|
|
7367
|
+
this.scalarResultCall(e));
|
|
6558
7368
|
}
|
|
6559
7369
|
/** The nullable-pointer representation behind a union-typed class field
|
|
6560
7370
|
* read, or null for every other expression. */
|
|
@@ -7011,6 +7821,49 @@ export class LlEmitter {
|
|
|
7011
7821
|
emitToUint32(value, expr, uint32) {
|
|
7012
7822
|
return emitToUint32(this, value, expr, uint32);
|
|
7013
7823
|
}
|
|
7824
|
+
/** Stores a number into its int32-stored local, when it is one. The
|
|
7825
|
+
* value is proven to be in the local's range, so its low 32 bits are the
|
|
7826
|
+
* exact value. */
|
|
7827
|
+
storeInt32Local(localId, value, expr) {
|
|
7828
|
+
const local = this.int32Locals.get(localId);
|
|
7829
|
+
if (local === undefined)
|
|
7830
|
+
return false;
|
|
7831
|
+
if (expr !== undefined && !this.int32LocalWrites.has(expr))
|
|
7832
|
+
throw new InternalCompilerError(`llvm emitter bug: unproven write of int32 local ${localId}`);
|
|
7833
|
+
const B = this.B;
|
|
7834
|
+
const integer = exactInteger(this, value, expr);
|
|
7835
|
+
let bits;
|
|
7836
|
+
if (integer && /^-?\d+$/.test(integer.name))
|
|
7837
|
+
bits = String(Number(integer.name) | 0); // a literal's i32 bits
|
|
7838
|
+
else if (integer)
|
|
7839
|
+
bits = narrowInteger(this, integer);
|
|
7840
|
+
else if (value.uint32 !== undefined)
|
|
7841
|
+
bits = value.uint32;
|
|
7842
|
+
else {
|
|
7843
|
+
// An Int32 contract proved this write without a range at the value.
|
|
7844
|
+
bits = B.tmp();
|
|
7845
|
+
B.line(`${bits} = ${local.signed ? "fptosi" : "fptoui"} double ${value.name} to i32`);
|
|
7846
|
+
}
|
|
7847
|
+
B.line(`store i32 ${bits}, ptr %${mangleLocal(localId)}`);
|
|
7848
|
+
return true;
|
|
7849
|
+
}
|
|
7850
|
+
/** The number of an int32-stored local's i32, with the i32 as its
|
|
7851
|
+
* integer and ToUint32 forms. A parameter the body never writes reads
|
|
7852
|
+
* as its incoming double, which is the same number. */
|
|
7853
|
+
int32LocalValue(localId, bits, type) {
|
|
7854
|
+
const local = this.int32Locals.get(localId);
|
|
7855
|
+
let number = `%p_${mangleLocal(localId)}`;
|
|
7856
|
+
if (!local.incoming) {
|
|
7857
|
+
number = this.B.tmp();
|
|
7858
|
+
this.B.line(`${number} = ${local.signed ? "sitofp" : "uitofp"} i32 ${bits} to double`);
|
|
7859
|
+
}
|
|
7860
|
+
return {
|
|
7861
|
+
name: number,
|
|
7862
|
+
type,
|
|
7863
|
+
uint32: bits,
|
|
7864
|
+
integer: { name: bits, type: "i32", signed: local.signed, range: local.range },
|
|
7865
|
+
};
|
|
7866
|
+
}
|
|
7014
7867
|
storeIntegerView(localId, value, expr) {
|
|
7015
7868
|
const slot = this.integerViews.get(localId);
|
|
7016
7869
|
if (slot === undefined)
|
|
@@ -7100,4 +7953,24 @@ export class LlEmitter {
|
|
|
7100
7953
|
return emitLibCall(this, e);
|
|
7101
7954
|
}
|
|
7102
7955
|
}
|
|
7956
|
+
/** The adopting await a `return` value resolves through, if any: the value
|
|
7957
|
+
* itself or the operand of its conversion into the declared result. */
|
|
7958
|
+
function adoptingAwaitOf(value) {
|
|
7959
|
+
if (value === null)
|
|
7960
|
+
return null;
|
|
7961
|
+
let found = null;
|
|
7962
|
+
everyExpr(value, {
|
|
7963
|
+
expr: (node) => {
|
|
7964
|
+
if (found !== null)
|
|
7965
|
+
return false;
|
|
7966
|
+
if (node.kind === "awaitExpr" && node.adopt === true) {
|
|
7967
|
+
found = node;
|
|
7968
|
+
return false;
|
|
7969
|
+
}
|
|
7970
|
+
return true;
|
|
7971
|
+
},
|
|
7972
|
+
stmt: () => true,
|
|
7973
|
+
});
|
|
7974
|
+
return found;
|
|
7975
|
+
}
|
|
7103
7976
|
//# sourceMappingURL=emitter.js.map
|