@scriptc/compiler 0.2.6 → 0.2.8
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 +7 -18
- package/dist/backend/cycle-analysis.d.ts +21 -9
- package/dist/backend/cycle-analysis.js +139 -67
- package/dist/backend/cycle-analysis.js.map +1 -1
- package/dist/backend/cycle-analysis.test.js +283 -3
- package/dist/backend/cycle-analysis.test.js.map +1 -1
- package/dist/backend/cycle-mutability.d.ts +39 -0
- package/dist/backend/cycle-mutability.js +205 -0
- package/dist/backend/cycle-mutability.js.map +1 -0
- package/dist/backend/link-plan-core.d.ts +2 -1
- package/dist/backend/link-plan-core.js +15 -3
- package/dist/backend/link-plan-core.js.map +1 -1
- package/dist/backend/link-plan.d.ts +2 -1
- package/dist/backend/link-plan.js.map +1 -1
- package/dist/backend/llvm/alloc.d.ts +43 -0
- package/dist/backend/llvm/alloc.js +246 -0
- package/dist/backend/llvm/alloc.js.map +1 -0
- package/dist/backend/llvm/alloc.test.d.ts +1 -0
- package/dist/backend/llvm/alloc.test.js +102 -0
- package/dist/backend/llvm/alloc.test.js.map +1 -0
- package/dist/backend/llvm/blocks.d.ts +9 -0
- package/dist/backend/llvm/blocks.js +9 -0
- package/dist/backend/llvm/blocks.js.map +1 -1
- package/dist/backend/llvm/borrowed-receivers.d.ts +7 -0
- package/dist/backend/llvm/borrowed-receivers.js +39 -0
- package/dist/backend/llvm/borrowed-receivers.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.d.ts +18 -3
- package/dist/backend/llvm/call-lifetimes.js +54 -19
- package/dist/backend/llvm/call-lifetimes.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.test.js +178 -3
- package/dist/backend/llvm/call-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/checked-narrows.d.ts +24 -0
- package/dist/backend/llvm/checked-narrows.js +108 -0
- package/dist/backend/llvm/checked-narrows.js.map +1 -0
- package/dist/backend/llvm/checked-narrows.test.d.ts +1 -0
- package/dist/backend/llvm/checked-narrows.test.js +208 -0
- package/dist/backend/llvm/checked-narrows.test.js.map +1 -0
- package/dist/backend/llvm/classes.d.ts +16 -1
- package/dist/backend/llvm/classes.js +60 -49
- package/dist/backend/llvm/classes.js.map +1 -1
- package/dist/backend/llvm/classes.test.js +68 -6
- package/dist/backend/llvm/classes.test.js.map +1 -1
- package/dist/backend/llvm/common.d.ts +13 -0
- package/dist/backend/llvm/common.js +30 -0
- package/dist/backend/llvm/common.js.map +1 -1
- package/dist/backend/llvm/constant-callbacks.d.ts +19 -3
- package/dist/backend/llvm/constant-callbacks.js +289 -52
- package/dist/backend/llvm/constant-callbacks.js.map +1 -1
- package/dist/backend/llvm/constant-callbacks.test.js +140 -3
- package/dist/backend/llvm/constant-callbacks.test.js.map +1 -1
- package/dist/backend/llvm/debug-info.d.ts +6 -2
- package/dist/backend/llvm/debug-info.js +11 -1
- package/dist/backend/llvm/debug-info.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.d.ts +29 -0
- package/dist/backend/llvm/dense-array-access.js +335 -0
- package/dist/backend/llvm/dense-array-access.js.map +1 -0
- package/dist/backend/llvm/dense-array-access.test.d.ts +1 -0
- package/dist/backend/llvm/dense-array-access.test.js +238 -0
- package/dist/backend/llvm/dense-array-access.test.js.map +1 -0
- package/dist/backend/llvm/dyn.d.ts +38 -0
- package/dist/backend/llvm/dyn.js +213 -14
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +308 -15
- package/dist/backend/llvm/emitter.js +1788 -206
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.test.js +89 -7
- package/dist/backend/llvm/emitter.test.js.map +1 -1
- package/dist/backend/llvm/expr-async.js +62 -8
- package/dist/backend/llvm/expr-async.js.map +1 -1
- package/dist/backend/llvm/expr-async.test.d.ts +1 -0
- package/dist/backend/llvm/expr-async.test.js +62 -0
- package/dist/backend/llvm/expr-async.test.js.map +1 -0
- package/dist/backend/llvm/expr-bytes.d.ts +1 -1
- package/dist/backend/llvm/expr-bytes.js +85 -8
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-callbacks.js +29 -21
- package/dist/backend/llvm/expr-callbacks.js.map +1 -1
- package/dist/backend/llvm/expr-calls.d.ts +5 -0
- package/dist/backend/llvm/expr-calls.js +118 -15
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-containers.d.ts +7 -1
- package/dist/backend/llvm/expr-containers.js +246 -32
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-control.js +113 -8
- package/dist/backend/llvm/expr-control.js.map +1 -1
- package/dist/backend/llvm/expr-dispatch.js +2 -0
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +244 -44
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-island.js +3 -8
- package/dist/backend/llvm/expr-island.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.d.ts +1 -1
- package/dist/backend/llvm/expr-primitives.js +93 -36
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.test.js +51 -3
- package/dist/backend/llvm/expr-primitives.test.js.map +1 -1
- package/dist/backend/llvm/expr-records.js +31 -17
- package/dist/backend/llvm/expr-records.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +96 -29
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.test.js +1 -1
- package/dist/backend/llvm/expr-stream-callbacks.js +6 -3
- package/dist/backend/llvm/expr-stream-callbacks.js.map +1 -1
- package/dist/backend/llvm/integer-remainder.d.ts +11 -0
- package/dist/backend/llvm/integer-remainder.js +91 -0
- package/dist/backend/llvm/integer-remainder.js.map +1 -1
- package/dist/backend/llvm/layouts.d.ts +3 -1
- package/dist/backend/llvm/layouts.js +3 -2
- package/dist/backend/llvm/layouts.js.map +1 -1
- package/dist/backend/llvm/lazy-captures.d.ts +16 -0
- package/dist/backend/llvm/lazy-captures.js +96 -0
- package/dist/backend/llvm/lazy-captures.js.map +1 -0
- package/dist/backend/llvm/lazy-captures.test.d.ts +1 -0
- package/dist/backend/llvm/lazy-captures.test.js +78 -0
- package/dist/backend/llvm/lazy-captures.test.js.map +1 -0
- package/dist/backend/llvm/lib-abi.js +10 -0
- package/dist/backend/llvm/lib-abi.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +10 -15
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-process.js +8 -8
- package/dist/backend/llvm/lib-process.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +1 -0
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.d.ts +5 -1
- package/dist/backend/llvm/local-array-reads.js +147 -26
- package/dist/backend/llvm/local-array-reads.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.test.js +85 -4
- package/dist/backend/llvm/local-array-reads.test.js.map +1 -1
- package/dist/backend/llvm/local-union-storage.d.ts +5 -1
- package/dist/backend/llvm/local-union-storage.js +54 -8
- package/dist/backend/llvm/local-union-storage.js.map +1 -1
- package/dist/backend/llvm/local-union-storage.test.js +94 -6
- package/dist/backend/llvm/local-union-storage.test.js.map +1 -1
- package/dist/backend/llvm/map-number-lookup.d.ts +18 -0
- package/dist/backend/llvm/map-number-lookup.js +62 -0
- package/dist/backend/llvm/map-number-lookup.js.map +1 -0
- package/dist/backend/llvm/map-read-lifetimes.d.ts +6 -0
- package/dist/backend/llvm/map-read-lifetimes.js +24 -3
- package/dist/backend/llvm/map-read-lifetimes.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.test.js +39 -20
- package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/nullable-fields.d.ts +28 -0
- package/dist/backend/llvm/nullable-fields.js +45 -0
- package/dist/backend/llvm/nullable-fields.js.map +1 -0
- package/dist/backend/llvm/nullable-fields.test.d.ts +1 -0
- package/dist/backend/llvm/nullable-fields.test.js +48 -0
- package/dist/backend/llvm/nullable-fields.test.js.map +1 -0
- package/dist/backend/llvm/nullable-unions.d.ts +68 -0
- package/dist/backend/llvm/nullable-unions.js +132 -0
- package/dist/backend/llvm/nullable-unions.js.map +1 -0
- package/dist/backend/llvm/publish.d.ts +58 -0
- package/dist/backend/llvm/publish.js +286 -0
- package/dist/backend/llvm/publish.js.map +1 -0
- package/dist/backend/llvm/rebound-parameters.d.ts +12 -0
- package/dist/backend/llvm/rebound-parameters.js +72 -0
- package/dist/backend/llvm/rebound-parameters.js.map +1 -0
- package/dist/backend/llvm/rebound-parameters.test.d.ts +1 -0
- package/dist/backend/llvm/rebound-parameters.test.js +67 -0
- package/dist/backend/llvm/rebound-parameters.test.js.map +1 -0
- package/dist/backend/llvm/reference-effects.d.ts +18 -2
- package/dist/backend/llvm/reference-effects.js +106 -16
- package/dist/backend/llvm/reference-effects.js.map +1 -1
- package/dist/backend/llvm/reference-effects.test.js +52 -1
- package/dist/backend/llvm/reference-effects.test.js.map +1 -1
- package/dist/backend/llvm/serialized-ir-emission.test.d.ts +1 -0
- package/dist/backend/llvm/serialized-ir-emission.test.js +52 -0
- package/dist/backend/llvm/serialized-ir-emission.test.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +104 -9
- package/dist/backend/llvm/shapes.js +356 -46
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/shapes.test.js +139 -5
- package/dist/backend/llvm/shapes.test.js.map +1 -1
- package/dist/backend/llvm/stack-callbacks.js +2 -3
- package/dist/backend/llvm/stack-callbacks.js.map +1 -1
- package/dist/backend/llvm/stack-captures.d.ts +3 -1
- package/dist/backend/llvm/stack-captures.js +5 -3
- package/dist/backend/llvm/stack-captures.js.map +1 -1
- package/dist/backend/llvm/stack-unions.test.js +43 -3
- package/dist/backend/llvm/stack-unions.test.js.map +1 -1
- package/dist/backend/llvm/string-construction.d.ts +10 -0
- package/dist/backend/llvm/string-construction.js +42 -0
- package/dist/backend/llvm/string-construction.js.map +1 -1
- package/dist/backend/llvm/string-construction.test.js +1 -1
- package/dist/backend/llvm/string-key-hash.d.ts +15 -0
- package/dist/backend/llvm/string-key-hash.js +76 -0
- package/dist/backend/llvm/string-key-hash.js.map +1 -0
- package/dist/backend/llvm/string-key-hash.test.d.ts +1 -0
- package/dist/backend/llvm/string-key-hash.test.js +45 -0
- package/dist/backend/llvm/string-key-hash.test.js.map +1 -0
- package/dist/backend/llvm/string-lifetimes.test.js +65 -15
- package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/string-reads.d.ts +7 -0
- package/dist/backend/llvm/string-reads.js +76 -0
- package/dist/backend/llvm/string-reads.js.map +1 -0
- package/dist/backend/llvm/switch-dispatch.js +1 -1
- package/dist/backend/llvm/switch-dispatch.js.map +1 -1
- package/dist/backend/llvm/switch-dispatch.test.js +3 -3
- package/dist/backend/llvm/switch-dispatch.test.js.map +1 -1
- package/dist/backend/llvm/test-boxed-unions.d.ts +5 -0
- package/dist/backend/llvm/test-boxed-unions.js +13 -0
- package/dist/backend/llvm/test-boxed-unions.js.map +1 -0
- package/dist/backend/llvm/union-repr.d.ts +15 -0
- package/dist/backend/llvm/union-repr.js +89 -0
- package/dist/backend/llvm/union-repr.js.map +1 -0
- package/dist/backend/llvm/virtual-borrows.d.ts +31 -0
- package/dist/backend/llvm/virtual-borrows.js +82 -0
- package/dist/backend/llvm/virtual-borrows.js.map +1 -0
- package/dist/backend/llvm/walk-borrows.d.ts +43 -0
- package/dist/backend/llvm/walk-borrows.js +150 -0
- package/dist/backend/llvm/walk-borrows.js.map +1 -0
- package/dist/backend/llvm/walk-borrows.test.d.ts +1 -0
- package/dist/backend/llvm/walk-borrows.test.js +119 -0
- package/dist/backend/llvm/walk-borrows.test.js.map +1 -0
- package/dist/backend/llvm/walkers.d.ts +5 -1
- package/dist/backend/llvm/walkers.js +24 -21
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/backend/may-throw.d.ts +29 -1
- package/dist/backend/may-throw.js +349 -3
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/may-throw.test.js +114 -2
- package/dist/backend/may-throw.test.js.map +1 -1
- package/dist/backend/native/cache-warm.d.ts +2 -1
- package/dist/backend/native/cache-warm.js.map +1 -1
- package/dist/backend/native/contracts.d.ts +3 -2
- package/dist/backend/native/contracts.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-codegen-core.d.ts +7 -4
- package/dist/backend/native-codegen-core.js +14 -9
- package/dist/backend/native-codegen-core.js.map +1 -1
- package/dist/backend/native-codegen.d.ts +9 -0
- package/dist/backend/native-codegen.js +53 -2
- package/dist/backend/native-codegen.js.map +1 -1
- package/dist/backend/native-codegen.test.js +64 -10
- package/dist/backend/native-codegen.test.js.map +1 -1
- package/dist/backend/native-link-info-core.d.ts +3 -2
- package/dist/backend/native-link-info-core.js.map +1 -1
- package/dist/backend/native-link-info.d.ts +2 -1
- package/dist/backend/native-link-info.js.map +1 -1
- package/dist/backend/native-link-info.test.js +2 -2
- package/dist/backend/native-link-info.test.js.map +1 -1
- package/dist/backend/native-tools.d.ts +5 -1
- package/dist/backend/native-tools.js +3 -1
- package/dist/backend/native-tools.js.map +1 -1
- package/dist/backend/optimization.d.ts +25 -0
- package/dist/backend/optimization.js +21 -0
- package/dist/backend/optimization.js.map +1 -0
- package/dist/backend/optimization.test.d.ts +1 -0
- package/dist/backend/optimization.test.js +24 -0
- package/dist/backend/optimization.test.js.map +1 -0
- package/dist/backend/publication.d.ts +28 -0
- package/dist/backend/publication.js +155 -0
- package/dist/backend/publication.js.map +1 -0
- package/dist/backend/publication.test.d.ts +1 -0
- package/dist/backend/publication.test.js +72 -0
- package/dist/backend/publication.test.js.map +1 -0
- 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 +15 -4
- package/dist/backend/runtime-pack-core.js +23 -12
- package/dist/backend/runtime-pack-core.js.map +1 -1
- package/dist/backend/runtime-pack-native.d.ts +7 -3
- package/dist/backend/runtime-pack-native.js +0 -3
- package/dist/backend/runtime-pack-native.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.js +1 -1
- package/dist/backend/runtime-pack.d.ts +20 -2
- package/dist/backend/runtime-pack.js +23 -0
- package/dist/backend/runtime-pack.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +115 -2
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/backend/target-diagnostics.test.js +1 -1
- package/dist/backend/targets.d.ts +2 -1
- package/dist/backend/targets.js +1 -1
- package/dist/backend/targets.js.map +1 -1
- package/dist/cli/command.js +75 -11
- package/dist/cli/command.js.map +1 -1
- package/dist/cli/host.d.ts +2 -1
- package/dist/cli/usage.d.ts +4 -1
- package/dist/cli/usage.js +13 -3
- package/dist/cli/usage.js.map +1 -1
- package/dist/compile-types.d.ts +17 -5
- package/dist/coverage/report.d.ts +25 -2
- package/dist/coverage/report.js +161 -33
- package/dist/coverage/report.js.map +1 -1
- package/dist/coverage/verdict-cache.d.ts +63 -0
- package/dist/coverage/verdict-cache.js +193 -0
- package/dist/coverage/verdict-cache.js.map +1 -0
- package/dist/coverage/verdict-cache.test.d.ts +1 -0
- package/dist/coverage/verdict-cache.test.js +52 -0
- package/dist/coverage/verdict-cache.test.js.map +1 -0
- package/dist/coverage/verdict-semantic.d.ts +2 -0
- package/dist/coverage/verdict-semantic.js +24 -0
- package/dist/coverage/verdict-semantic.js.map +1 -0
- package/dist/coverage/verdict-startup.d.ts +15 -0
- package/dist/coverage/verdict-startup.js +66 -0
- package/dist/coverage/verdict-startup.js.map +1 -0
- package/dist/diagnostics/diagnostic.d.ts +29 -1
- package/dist/diagnostics/diagnostic.js +84 -12
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/diagnostics/envelope.d.ts +130 -0
- package/dist/diagnostics/envelope.js +257 -0
- package/dist/diagnostics/envelope.js.map +1 -0
- package/dist/diagnostics/envelope.test.d.ts +1 -0
- package/dist/diagnostics/envelope.test.js +91 -0
- package/dist/diagnostics/envelope.test.js.map +1 -0
- package/dist/diagnostics/render.d.ts +3 -0
- package/dist/diagnostics/render.js +13 -2
- package/dist/diagnostics/render.js.map +1 -1
- package/dist/executable/executable-cache.d.ts +2 -2
- package/dist/executable/executable-cache.js +7 -4
- package/dist/executable/executable-cache.js.map +1 -1
- package/dist/executable/executable-cache.test.js +20 -0
- package/dist/executable/executable-cache.test.js.map +1 -1
- package/dist/executable/prepare.d.ts +4 -0
- package/dist/executable/prepare.js +7 -1
- package/dist/executable/prepare.js.map +1 -1
- package/dist/frontend/analysis.js +1 -0
- package/dist/frontend/analysis.js.map +1 -1
- package/dist/frontend/lowering/abstract-equality.js +5 -49
- package/dist/frontend/lowering/abstract-equality.js.map +1 -1
- package/dist/frontend/lowering/array-values.d.ts +30 -0
- package/dist/frontend/lowering/array-values.js +246 -1
- package/dist/frontend/lowering/array-values.js.map +1 -1
- package/dist/frontend/lowering/builtins/errors.js +17 -2
- package/dist/frontend/lowering/builtins/errors.js.map +1 -1
- package/dist/frontend/lowering/builtins/numbers.js +29 -1
- package/dist/frontend/lowering/builtins/numbers.js.map +1 -1
- package/dist/frontend/lowering/builtins/process.d.ts +10 -17
- package/dist/frontend/lowering/builtins/process.js +66 -29
- package/dist/frontend/lowering/builtins/process.js.map +1 -1
- package/dist/frontend/lowering/builtins/workers.js +4 -2
- package/dist/frontend/lowering/builtins/workers.js.map +1 -1
- package/dist/frontend/lowering/caught-value-dispatch.d.ts +11 -0
- package/dist/frontend/lowering/caught-value-dispatch.js +87 -0
- package/dist/frontend/lowering/caught-value-dispatch.js.map +1 -0
- package/dist/frontend/lowering/class-assertions.d.ts +12 -0
- package/dist/frontend/lowering/class-assertions.js +229 -5
- package/dist/frontend/lowering/class-assertions.js.map +1 -1
- package/dist/frontend/lowering/class-callbacks.d.ts +3 -3
- package/dist/frontend/lowering/class-callbacks.js +18 -5
- package/dist/frontend/lowering/class-callbacks.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.d.ts +1 -0
- package/dist/frontend/lowering/class-dynamic-dispatch.js +12 -6
- package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
- package/dist/frontend/lowering/class-method-mutations.d.ts +5 -0
- package/dist/frontend/lowering/class-method-mutations.js +472 -0
- package/dist/frontend/lowering/class-method-mutations.js.map +1 -0
- package/dist/frontend/lowering/class-method-values.js +25 -5
- package/dist/frontend/lowering/class-method-values.js.map +1 -1
- package/dist/frontend/lowering/class-prototypes.js +8 -2
- package/dist/frontend/lowering/class-prototypes.js.map +1 -1
- package/dist/frontend/lowering/class-unions.d.ts +16 -0
- package/dist/frontend/lowering/class-unions.js +127 -0
- package/dist/frontend/lowering/class-unions.js.map +1 -0
- package/dist/frontend/lowering/coercions/builders.js +8 -1
- package/dist/frontend/lowering/coercions/builders.js.map +1 -1
- package/dist/frontend/lowering/coercions/function-adapters.js +2 -0
- package/dist/frontend/lowering/coercions/function-adapters.js.map +1 -1
- package/dist/frontend/lowering/coercions/identity.d.ts +7 -0
- package/dist/frontend/lowering/coercions/identity.js +26 -0
- package/dist/frontend/lowering/coercions/identity.js.map +1 -0
- package/dist/frontend/lowering/coercions/state.d.ts +6 -0
- package/dist/frontend/lowering/coercions/state.js +2 -0
- package/dist/frontend/lowering/coercions/state.js.map +1 -1
- package/dist/frontend/lowering/coercions/structural.js +9 -6
- package/dist/frontend/lowering/coercions/structural.js.map +1 -1
- package/dist/frontend/lowering/coercions/unions.d.ts +8 -0
- package/dist/frontend/lowering/coercions/unions.js +67 -2
- package/dist/frontend/lowering/coercions/unions.js.map +1 -1
- package/dist/frontend/lowering/collation-repertoire.d.ts +1 -0
- package/dist/frontend/lowering/collation-repertoire.js +19 -0
- package/dist/frontend/lowering/collation-repertoire.js.map +1 -0
- package/dist/frontend/lowering/collection-narrowing.d.ts +6 -0
- package/dist/frontend/lowering/collection-narrowing.js +31 -0
- package/dist/frontend/lowering/collection-narrowing.js.map +1 -0
- package/dist/frontend/lowering/construction-escapes.d.ts +38 -0
- package/dist/frontend/lowering/construction-escapes.js +146 -0
- package/dist/frontend/lowering/construction-escapes.js.map +1 -0
- package/dist/frontend/lowering/construction-escapes.test.d.ts +1 -0
- package/dist/frontend/lowering/construction-escapes.test.js +150 -0
- package/dist/frontend/lowering/construction-escapes.test.js.map +1 -0
- package/dist/frontend/lowering/containers/array-callback-call.d.ts +7 -0
- package/dist/frontend/lowering/containers/array-callback-call.js +52 -0
- package/dist/frontend/lowering/containers/array-callback-call.js.map +1 -0
- package/dist/frontend/lowering/containers/array-callbacks.d.ts +1 -1
- package/dist/frontend/lowering/containers/array-callbacks.js +416 -65
- package/dist/frontend/lowering/containers/array-callbacks.js.map +1 -1
- package/dist/frontend/lowering/containers/array-concat.d.ts +7 -0
- package/dist/frontend/lowering/containers/array-concat.js +130 -0
- package/dist/frontend/lowering/containers/array-concat.js.map +1 -0
- package/dist/frontend/lowering/containers/array-construction.d.ts +2 -0
- package/dist/frontend/lowering/containers/array-construction.js +110 -5
- package/dist/frontend/lowering/containers/array-construction.js.map +1 -1
- package/dist/frontend/lowering/containers/array-methods.d.ts +1 -11
- package/dist/frontend/lowering/containers/array-methods.js +274 -144
- package/dist/frontend/lowering/containers/array-methods.js.map +1 -1
- package/dist/frontend/lowering/containers/callback-arguments.js +43 -1
- package/dist/frontend/lowering/containers/callback-arguments.js.map +1 -1
- package/dist/frontend/lowering/containers/collection-methods.js +10 -1
- package/dist/frontend/lowering/containers/collection-methods.js.map +1 -1
- package/dist/frontend/lowering/containers/indexed-objects.js +56 -70
- package/dist/frontend/lowering/containers/indexed-objects.js.map +1 -1
- package/dist/frontend/lowering/containers/string-and-regexp.js +7 -63
- package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
- package/dist/frontend/lowering/cycle-initialization.d.ts +46 -0
- package/dist/frontend/lowering/cycle-initialization.js +303 -0
- package/dist/frontend/lowering/cycle-initialization.js.map +1 -0
- package/dist/frontend/lowering/divergences.d.ts +9 -0
- package/dist/frontend/lowering/divergences.js +630 -0
- package/dist/frontend/lowering/divergences.js.map +1 -0
- package/dist/frontend/lowering/expressions/evaluation-safety.js +4 -0
- package/dist/frontend/lowering/expressions/evaluation-safety.js.map +1 -1
- package/dist/frontend/lowering/expressions/object-literals.js +178 -34
- package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
- package/dist/frontend/lowering/expressions/union-field-write.d.ts +4 -4
- package/dist/frontend/lowering/expressions/union-field-write.js +54 -44
- package/dist/frontend/lowering/expressions/union-field-write.js.map +1 -1
- package/dist/frontend/lowering/iterator-adapters.d.ts +20 -0
- package/dist/frontend/lowering/iterator-adapters.js +605 -0
- package/dist/frontend/lowering/iterator-adapters.js.map +1 -0
- package/dist/frontend/lowering/lower-array-sort.d.ts +1 -1
- package/dist/frontend/lowering/lower-array-sort.js +44 -5
- package/dist/frontend/lowering/lower-array-sort.js.map +1 -1
- package/dist/frontend/lowering/lower-array-sort.test.d.ts +1 -0
- package/dist/frontend/lowering/lower-array-sort.test.js +31 -0
- package/dist/frontend/lowering/lower-array-sort.test.js.map +1 -0
- package/dist/frontend/lowering/lower-assert.js +7 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -1
- package/dist/frontend/lowering/lower-builtin-values.js +1 -0
- package/dist/frontend/lowering/lower-builtin-values.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +8 -3
- package/dist/frontend/lowering/lower-calls.js +388 -278
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +6 -0
- package/dist/frontend/lowering/lower-classes.js +117 -65
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +22 -2
- package/dist/frontend/lowering/lower-exprs.js +811 -254
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-functions.js +32 -19
- package/dist/frontend/lowering/lower-functions.js.map +1 -1
- package/dist/frontend/lowering/lower-generators.d.ts +19 -13
- package/dist/frontend/lowering/lower-generators.js +67 -44
- package/dist/frontend/lowering/lower-generators.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +29 -4
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.d.ts +1 -1
- package/dist/frontend/lowering/lower-island.js +24 -7
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +100 -3
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +82 -64
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +115 -2
- package/dist/frontend/lowering/lowerer.js +1266 -88
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/native-ffi.js +3 -1
- package/dist/frontend/lowering/native-ffi.js.map +1 -1
- package/dist/frontend/lowering/runtime-optional-bounds.d.ts +19 -0
- package/dist/frontend/lowering/runtime-optional-bounds.js +354 -0
- package/dist/frontend/lowering/runtime-optional-bounds.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-bounds.test.d.ts +1 -0
- package/dist/frontend/lowering/runtime-optional-bounds.test.js +66 -0
- package/dist/frontend/lowering/runtime-optional-bounds.test.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-elements.d.ts +117 -0
- package/dist/frontend/lowering/runtime-optional-elements.js +738 -0
- package/dist/frontend/lowering/runtime-optional-elements.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-guards.d.ts +36 -0
- package/dist/frontend/lowering/runtime-optional-guards.js +375 -0
- package/dist/frontend/lowering/runtime-optional-guards.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-guards.test.d.ts +1 -0
- package/dist/frontend/lowering/runtime-optional-guards.test.js +92 -0
- package/dist/frontend/lowering/runtime-optional-guards.test.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-ownership.d.ts +63 -0
- package/dist/frontend/lowering/runtime-optional-ownership.js +382 -0
- package/dist/frontend/lowering/runtime-optional-ownership.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-provenance.d.ts +16 -0
- package/dist/frontend/lowering/runtime-optional-provenance.js +41 -0
- package/dist/frontend/lowering/runtime-optional-provenance.js.map +1 -0
- package/dist/frontend/lowering/sanitize-class-types.test.js +2 -2
- package/dist/frontend/lowering/strict-equality.d.ts +9 -0
- package/dist/frontend/lowering/strict-equality.js +116 -0
- package/dist/frontend/lowering/strict-equality.js.map +1 -0
- package/dist/frontend/lowering/string-index-bounds.d.ts +36 -0
- package/dist/frontend/lowering/string-index-bounds.js +537 -0
- package/dist/frontend/lowering/string-index-bounds.js.map +1 -0
- package/dist/frontend/lowering/string-index-bounds.test.d.ts +1 -0
- package/dist/frontend/lowering/string-index-bounds.test.js +223 -0
- package/dist/frontend/lowering/string-index-bounds.test.js.map +1 -0
- package/dist/frontend/lowering/surfaces.js +3 -0
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/lowering/union-comparison.d.ts +5 -0
- package/dist/frontend/lowering/union-comparison.js +47 -0
- package/dist/frontend/lowering/union-comparison.js.map +1 -0
- package/dist/frontend/lowering/union-narrow.d.ts +2 -0
- package/dist/frontend/lowering/union-narrow.js +10 -6
- package/dist/frontend/lowering/union-narrow.js.map +1 -1
- package/dist/frontend/program-analysis.d.ts +10 -0
- package/dist/frontend/program-analysis.js +7 -0
- package/dist/frontend/program-analysis.js.map +1 -1
- package/dist/frontend/program.d.ts +63 -2
- package/dist/frontend/program.js +1038 -61
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/program.test.js +122 -6
- package/dist/frontend/program.test.js.map +1 -1
- package/dist/frontend/resolve.d.ts +6 -0
- package/dist/frontend/resolve.js +14 -0
- package/dist/frontend/resolve.js.map +1 -1
- package/dist/frontend/threads-import.d.ts +5 -0
- package/dist/frontend/threads-import.js +21 -0
- package/dist/frontend/threads-import.js.map +1 -0
- package/dist/frontend/type-mapper.d.ts +4 -1
- package/dist/frontend/type-mapper.js +161 -20
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/frontend/type-mapper.test.js +15 -3
- package/dist/frontend/type-mapper.test.js.map +1 -1
- package/dist/frontend/worker-target.d.ts +4 -0
- package/dist/frontend/worker-target.js +8 -1
- package/dist/frontend/worker-target.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +70 -16
- package/dist/index.js.map +1 -1
- package/dist/ir/analysis.d.ts +3 -1
- package/dist/ir/analysis.js +4 -0
- package/dist/ir/analysis.js.map +1 -1
- package/dist/ir/build.d.ts +5 -0
- package/dist/ir/build.js +7 -0
- package/dist/ir/build.js.map +1 -1
- package/dist/ir/builtin-signatures.d.ts +1 -1
- package/dist/ir/builtin-signatures.js +5 -3
- package/dist/ir/builtin-signatures.js.map +1 -1
- package/dist/ir/constant-tables.test.js +1 -1
- package/dist/ir/initializer-bindings.test.js +1 -1
- package/dist/ir/int32-slots.d.ts +72 -0
- package/dist/ir/int32-slots.js +837 -0
- package/dist/ir/int32-slots.js.map +1 -0
- package/dist/ir/int32-slots.test.d.ts +1 -0
- package/dist/ir/int32-slots.test.js +222 -0
- package/dist/ir/int32-slots.test.js.map +1 -0
- package/dist/ir/integer-ranges.d.ts +19 -3
- package/dist/ir/integer-ranges.js +168 -23
- package/dist/ir/integer-ranges.js.map +1 -1
- package/dist/ir/integer-ranges.test.js +74 -2
- package/dist/ir/integer-ranges.test.js.map +1 -1
- package/dist/ir/ir.d.ts +101 -20
- package/dist/ir/ir.js +20 -6
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/scalar-records.test.js +1 -1
- package/dist/ir/serialize.d.ts +1 -1
- package/dist/ir/serialize.js +3 -3
- package/dist/ir/serialize.js.map +1 -1
- package/dist/ir/traverse.js +8 -0
- package/dist/ir/traverse.js.map +1 -1
- package/dist/ir/validate.js +139 -22
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.js +308 -8
- package/dist/ir/validate.test.js.map +1 -1
- package/dist/library/fence-eval.test.js +1 -1
- package/dist/library/int-infer.test.js +4 -4
- package/dist/library/library-cache.test.js +5 -5
- package/dist/library/library-profile.d.ts +2 -1
- package/dist/library/library-profile.js +4 -3
- package/dist/library/library-profile.js.map +1 -1
- package/dist/native/cache.d.ts +3 -0
- package/dist/native/cache.js +49 -1
- package/dist/native/cache.js.map +1 -1
- package/dist/native/cache.test.js +21 -1
- package/dist/native/cache.test.js.map +1 -1
- package/dist/native/cli.js +1 -1
- package/dist/native/cli.js.map +1 -1
- package/dist/native/compiler.d.ts +5 -0
- package/dist/native/compiler.js +51 -4
- package/dist/native/compiler.js.map +1 -1
- package/dist/native/compiler.test.js +2 -2
- package/dist/native/library.d.ts +2 -1
- package/dist/native/library.js.map +1 -1
- package/dist/native/prepare.d.ts +4 -0
- package/dist/native/prepare.js +28 -3
- package/dist/native/prepare.js.map +1 -1
- package/dist/native/sanitizer.d.ts +3 -2
- package/dist/native/sanitizer.js +3 -13
- package/dist/native/sanitizer.js.map +1 -1
- package/dist/startup-cache.d.ts +2 -0
- package/dist/startup-cache.js +2 -0
- package/dist/startup-cache.js.map +1 -1
- package/package.json +20 -20
- package/surface-manifest.json +8 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { emitLlvmLayouts } from "./layouts.js";
|
|
2
|
+
import { inlineAllocSupported } from "./alloc.js";
|
|
2
3
|
import { llvmBytes as llBytes } from "../literals.js";
|
|
3
4
|
import { InternalCompilerError } from "../../errors.js";
|
|
4
5
|
/** Lower typed IR to LLVM, using the runtime's C ABI.
|
|
@@ -18,10 +19,11 @@ import { InternalCompilerError } from "../../errors.js";
|
|
|
18
19
|
* Dynamic values and islands share the runtime ABI; embedded npm modules use
|
|
19
20
|
* compressed source tables that the runtime inflates on demand.
|
|
20
21
|
*/
|
|
22
|
+
import { createHash } from "node:crypto";
|
|
21
23
|
import { deflateRawSync } from "node:zlib";
|
|
22
|
-
import { endsWithJump, matchStringSelfConcat, streamTypedRefEligible } from "../../ir/analysis.js";
|
|
24
|
+
import { endsWithJump, matchStringSelfConcat, streamTypedRefEligible, undefinedArmTag, } from "../../ir/analysis.js";
|
|
23
25
|
import { emitLibraryIdentityLines } from "../library-identity-markers.js";
|
|
24
|
-
import { CAUGHT, ffiCallbackType, isDynTypedRefType, isFfiContextParam, isRefCounted, isUnitType, POINTER_KINDS, RUNTIME_EMITTER_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, typeKey, STRING, VOID, } from "../../ir/ir.js";
|
|
26
|
+
import { CAUGHT, ffiCallbackType, isDynTypedRefType, isFfiContextParam, isRefCounted, isUnitType, POINTER_KINDS, RUNTIME_EMITTER_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, typeEquals, typeKey, STRING, VOID, } from "../../ir/ir.js";
|
|
25
27
|
import { moduleRuntimeFeatures, moduleEmbedsBuiltin, moduleEmbedsCompressedNpm, NPM_COMPRESS_MIN, } from "../../ir/runtime-features.js";
|
|
26
28
|
import { matchIntegerArrayForLoop, matchIntegerBytesForLoop, matchIntegerCountedForLoop, } from "../../ir/integer-loops.js";
|
|
27
29
|
import { emitCountedLoopLimit } from "./counted-loops.js";
|
|
@@ -31,8 +33,9 @@ import { emitByteWindowGuard, findInitializedByteLoopBindings, matchByteWindow,
|
|
|
31
33
|
import { canDeferSplitPiece, emitSplitSpanNext, materializeSplitPiece, emitSplitCursor, emitSplitNext, emitSplitScratch, emitSplitSnapshot, findPrivateSplitLocals, loadSplitSnapshot, storeSplitSnapshot, stringSplit, } from "./split-loops.js";
|
|
32
34
|
import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
|
|
33
35
|
import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js";
|
|
34
|
-
import { everyStmtList } from "../../ir/traverse.js";
|
|
35
|
-
import { analyzeIntegerRanges } from "../../ir/integer-ranges.js";
|
|
36
|
+
import { everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
|
|
37
|
+
import { analyzeIntegerRanges, INT32_RANGE } from "../../ir/integer-ranges.js";
|
|
38
|
+
import { analyzeInt32Slots } from "../../ir/int32-slots.js";
|
|
36
39
|
import { findIntegerViews } from "./integer-views.js";
|
|
37
40
|
import { findInitializerBindings, withInitializerBindings } from "../../ir/initializer-bindings.js";
|
|
38
41
|
import { findConstantNumericTables } from "../../ir/constant-tables.js";
|
|
@@ -44,29 +47,37 @@ import { analyzeCallLifetimes } from "./call-lifetimes.js";
|
|
|
44
47
|
import { canStackUnion, emitStackUnion, findLocalStackUnions } from "./stack-unions.js";
|
|
45
48
|
import { allocateLocalUnion, findLocalUnionStorage, storeLocalUnion, } from "./local-union-storage.js";
|
|
46
49
|
import { BlockBuilder } from "./blocks.js";
|
|
47
|
-
import {
|
|
50
|
+
import { NullableRefFields } from "./nullable-fields.js";
|
|
51
|
+
import { emitLocalArrayRead, emitProjectedArrayRead, emitBorrowedArrayRead, findLocalArrayReads, findCallArrayReads, OptionalArrayReads, } from "./local-array-reads.js";
|
|
52
|
+
import { CheckedNarrows, emitCheckedNarrowReceiver } from "./checked-narrows.js";
|
|
53
|
+
import { emitDenseArrayGet, emitDenseArraySet } from "./dense-array-access.js";
|
|
48
54
|
import { emitStackMapRead, findMapReadLifetimes, matchMapRead, } from "./map-read-lifetimes.js";
|
|
49
|
-
import { emitBorrowedFieldSequence } from "./borrowed-receivers.js";
|
|
55
|
+
import { emitBorrowedFieldSequence, guardedValue } from "./borrowed-receivers.js";
|
|
50
56
|
import { emitBorrowedInput } from "./borrowed-inputs.js";
|
|
57
|
+
import { analyzeWalks, findWalkBorrows } from "./walk-borrows.js";
|
|
58
|
+
import { findReboundParameters } from "./rebound-parameters.js";
|
|
51
59
|
import { ReferenceEffects } from "./reference-effects.js";
|
|
52
60
|
import { LlvmDebugInfo } from "./debug-info.js";
|
|
53
61
|
import { StackCallbacks } from "./stack-callbacks.js";
|
|
54
62
|
import { findLoopArrayBorrows } from "./loop-array-borrows.js";
|
|
55
|
-
import { findConstantCallbacks } from "./constant-callbacks.js";
|
|
63
|
+
import { AmbientReceiverReaders, findConstantCallbacks } from "./constant-callbacks.js";
|
|
56
64
|
import { findScalarStringSlices, emitStringSliceSnapshot, } from "./string-slices.js";
|
|
65
|
+
import { LazyCaptures } from "./lazy-captures.js";
|
|
57
66
|
import { StackCaptures } from "./stack-captures.js";
|
|
58
|
-
import { f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, } from "./common.js";
|
|
67
|
+
import { ABSENT_FIELD_PAYLOAD, emitFieldAbsentTest, f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, } from "./common.js";
|
|
59
68
|
import { ffiExtendsNarrowIntegers } from "../targets.js";
|
|
60
69
|
import { emitLiteralExpr, emitOperatorExpr, emitStringExpr, emitContainerExpr, emitRecordExpr, } from "./expr-primitives.js";
|
|
61
70
|
import { emitControlExpr } from "./expr-control.js";
|
|
62
|
-
import { emitCallExpr } from "./expr-calls.js";
|
|
71
|
+
import { emitBorrowedResultCall, emitCallExpr } from "./expr-calls.js";
|
|
63
72
|
import { emitDynamicExpr } from "./expr-dynamic.js";
|
|
64
73
|
import { emitIntrinsicExpr, emitSerializationExpr, emitAsyncExpr } from "./expr-async.js";
|
|
74
|
+
import { PublishWalkers } from "./publish.js";
|
|
75
|
+
import { computePublishedTypes } from "../publication.js";
|
|
65
76
|
import { emitJsInteropExpr, emitExpr } from "./expr-dispatch.js";
|
|
66
77
|
import { emitJsMarshal, emitJsOp, emitJsExit, islandAdapter, islandTypedAdapter, } from "./expr-island.js";
|
|
67
78
|
import { dynKind, raceAdapterFor, genResultThunkFor, childExitThunkFor, childExitSignalThunkFor, childDataThunkFor, execFileThunkFor, ipcMessageThunkFor, ipcSendThunkFor, emitterFixedAdapter, wrapEmitterListener, unwrapNullableClosure, closeBindThunkFor, closeOverrideWrapFor, } from "./expr-callbacks.js";
|
|
68
79
|
import { streamDataAdapter, streamDoneFnFor, cryptoBytesThunkFor, fsRenameThunkFor, streamCbThunkFor, zlibBytesThunkFor, } from "./expr-stream-callbacks.js";
|
|
69
|
-
import { emitStableReceiver, resolveThunkFor, tagInSet, arrPush, emitArrayCopyLoop, emitStrIntrinsic, emitArrIntrinsic, wrapNullable, emitMapNew, mapSet, emitMapLikeIntrinsic, emitSetNew, } from "./expr-containers.js";
|
|
80
|
+
import { emitStableReceiver, resolveThunkFor, tagInSet, arrPush, emitArrayCopyLoop, emitStrIntrinsic, emitArrIntrinsic, emitDiscardedSplice, wrapNullable, emitMapNew, mapSet, emitMapLikeIntrinsic, emitSetNew, } from "./expr-containers.js";
|
|
70
81
|
import { emitIntegerLoopIndex, emitBytesIndex, emitBytesData, emitBytesLength, emitBytesGet, emitToUint32, emitBytesSet, emitBytesIntrinsic, } from "./expr-bytes.js";
|
|
71
82
|
import { emitRegexIntrinsic, emitRecordKeyGet, keyedRecordReadInto } from "./expr-records.js";
|
|
72
83
|
import { dynPromiseAdapter, streamTypedRefCommitAdapter, liveDynUnionRefAdapter, streamTypedRefBoxValue, streamTypedRefMaterializeAdapter, streamFromArrayAdapter, } from "./expr-stream-bridges.js";
|
|
@@ -77,9 +88,16 @@ import { emitNetworkHttpLibCall } from "./lib-network.js";
|
|
|
77
88
|
import { emitAssertInspectLibCall, emitIoLibCall, emitGenericLibCall, emitLibCall, } from "./lib-dispatch.js";
|
|
78
89
|
import { buildClassGraph, classMembershipIntervals, emitClassMembershipHelper, classEnvironmentIndex, classFieldIndex, classStructSym, } from "./classes.js";
|
|
79
90
|
import { LlDyn } from "./dyn.js";
|
|
91
|
+
import { VirtualBorrows } from "./virtual-borrows.js";
|
|
80
92
|
import { LlvmUnsupportedError } from "./unsupported.js";
|
|
81
93
|
import { LlWalkers } from "./walkers.js";
|
|
82
|
-
import {
|
|
94
|
+
import { isObjectArm, NULLABLE_ABSENT, NULLABLE_NULL, NullableUnions } from "./nullable-unions.js";
|
|
95
|
+
import { emitNullablePresent, emitUnionPeek, emitUnionTag } from "./union-repr.js";
|
|
96
|
+
import { literalCapWord, literalCapWord32 } from "./string-key-hash.js";
|
|
97
|
+
import { NUMBER_MAP_ENTRY_DECL } from "./map-number-lookup.js";
|
|
98
|
+
import { STRING_READ_HELPERS } from "./string-reads.js";
|
|
99
|
+
import { emitConcatInputs, emitMixedConcat, numberPart, stringParts, } from "./string-construction.js";
|
|
100
|
+
import { arrNewCall, boxAccess, boxNewCall, boxReleaseSym, computeTraced, elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, FN_ATTRS, inlineRcDecls, llFieldType, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
|
|
83
101
|
export { LlvmUnsupportedError } from "./unsupported.js";
|
|
84
102
|
function ffiCallbackDummyLl(callback) {
|
|
85
103
|
switch (callback.returns) {
|
|
@@ -125,6 +143,41 @@ export function emitLlvmModuleSource(mod, options = {}) {
|
|
|
125
143
|
function llStrBytes(text) {
|
|
126
144
|
return llBytes(Buffer.from(text, "utf8"));
|
|
127
145
|
}
|
|
146
|
+
const NO_BORROWED = new Set();
|
|
147
|
+
/** Whether any node of `e` names the local (reads, writes, declarations,
|
|
148
|
+
* captures). */
|
|
149
|
+
function mentionsLocal(e, localId) {
|
|
150
|
+
const exprNames = (node) => {
|
|
151
|
+
if (node.kind === "varRef" || node.kind === "incDec" || node.kind === "assignExpr")
|
|
152
|
+
return node.localId === localId;
|
|
153
|
+
if (node.kind === "closure")
|
|
154
|
+
return node.captures.includes(localId);
|
|
155
|
+
if (node.kind === "classRef")
|
|
156
|
+
return node.captures?.includes(localId) === true;
|
|
157
|
+
return false;
|
|
158
|
+
};
|
|
159
|
+
const stmtNames = (node) => {
|
|
160
|
+
if (node.kind === "varDecl" ||
|
|
161
|
+
node.kind === "assign" ||
|
|
162
|
+
node.kind === "forOf" ||
|
|
163
|
+
node.kind === "rethrow")
|
|
164
|
+
return node.localId === localId;
|
|
165
|
+
return false;
|
|
166
|
+
};
|
|
167
|
+
let found = false;
|
|
168
|
+
const expr = (node) => {
|
|
169
|
+
if (found || exprNames(node))
|
|
170
|
+
return !(found = true);
|
|
171
|
+
return everyExprChild(node, expr, stmt);
|
|
172
|
+
};
|
|
173
|
+
const stmt = (node) => {
|
|
174
|
+
if (found || stmtNames(node))
|
|
175
|
+
return !(found = true);
|
|
176
|
+
return everyStmtChild(node, expr, stmt);
|
|
177
|
+
};
|
|
178
|
+
expr(e);
|
|
179
|
+
return found;
|
|
180
|
+
}
|
|
128
181
|
export class LlEmitter {
|
|
129
182
|
mod;
|
|
130
183
|
localArrayReads = new Map();
|
|
@@ -173,15 +226,23 @@ export class LlEmitter {
|
|
|
173
226
|
ffiExtendNarrowIntegers;
|
|
174
227
|
cycleColorOffset;
|
|
175
228
|
wasi;
|
|
229
|
+
/** ELF worker executables give thread-locals the executable TLS models. */
|
|
230
|
+
executableTls;
|
|
176
231
|
emitLibraryIdentity;
|
|
177
232
|
runtimeAbiMarker;
|
|
178
233
|
/** Interned string literals: UTF-8 text → { symbol, byte length } —
|
|
179
234
|
* first-use order, the runtime ABI’s determinism discipline. */
|
|
180
235
|
literals = new Map();
|
|
236
|
+
/** Symbols of interned constant data, named by content (contentSymbol). */
|
|
237
|
+
contentSymbols = new Set();
|
|
181
238
|
/** Interned unit-armed union instances: "unionId:tag" → symbol — one
|
|
182
239
|
* immortal (rc == SIZE_MAX) static per (union, unit tag).
|
|
183
240
|
* RC entry points and the collector skip immortals. */
|
|
184
241
|
unitInstances = new Map();
|
|
242
|
+
/** unionId → symbol of the union's immortal ABSENT field-slot instance. */
|
|
243
|
+
absentInstances = new Map();
|
|
244
|
+
/** The nullable-union ABSENT sentinel was referenced (absentInstanceRef). */
|
|
245
|
+
needsNullableAbsent = false;
|
|
185
246
|
immortalValues = new Set();
|
|
186
247
|
/** Regex literal templates: "<flags>/<pattern>" → { symbol, interned
|
|
187
248
|
* source/flags literal refs } — one immortal ScrRegex template per distinct
|
|
@@ -215,18 +276,45 @@ export class LlEmitter {
|
|
|
215
276
|
needsBadTag = false;
|
|
216
277
|
needsBadKey = false;
|
|
217
278
|
needsRetainBox = false;
|
|
279
|
+
/** Inline RC fast-path helpers requested by call sites (shapes.ts); null
|
|
280
|
+
* when inline RC is off. */
|
|
281
|
+
rcHelpers;
|
|
218
282
|
fnByName = new Map();
|
|
283
|
+
/** Emitted LLVM text of every IR function body, by function name: the
|
|
284
|
+
* fiberless-async analysis scans it for suspension entry points. */
|
|
285
|
+
fnDefText = new Map();
|
|
219
286
|
referenceEffects;
|
|
220
287
|
optionalArrayReads;
|
|
288
|
+
checkedNarrows;
|
|
221
289
|
callArrayReads = new Map();
|
|
222
290
|
mapReadLifetimes = { locals: new Map(), arguments: new Map() };
|
|
223
291
|
callLifetimes;
|
|
224
292
|
constantCallbacks;
|
|
225
293
|
currentConstantCallbacks = new Map();
|
|
226
294
|
stackCallbacks;
|
|
295
|
+
receiverReaders;
|
|
227
296
|
stackCaptures;
|
|
297
|
+
lazyCaptures;
|
|
298
|
+
/** Unchanged captured parameters of the current function: each maps to
|
|
299
|
+
* the slot caching its box, NULL until the first closure needs it. */
|
|
300
|
+
lazyCaptureBoxes = new Map();
|
|
301
|
+
lazyStackBoxes = new Map();
|
|
228
302
|
borrowedParameters = new Set();
|
|
303
|
+
/** Parameters proven projection-only: callers may pass stack boxes. */
|
|
304
|
+
projectedParameters = new Set();
|
|
229
305
|
stableCallBindings = new Set();
|
|
306
|
+
/** Locals and rebound parameters holding borrowed pointers for their
|
|
307
|
+
* whole lifetime (walk-borrows.ts), per function and for the current one. */
|
|
308
|
+
walkBorrowsByFunction = new Map();
|
|
309
|
+
walkBorrows = new Set();
|
|
310
|
+
/** Functions whose borrowing body returns its result without a reference
|
|
311
|
+
* (analyzeBorrowedReturns), and whether the current body is one. */
|
|
312
|
+
borrowedReturns = new Set();
|
|
313
|
+
/** Rebound parameters that keep the borrowed convention with an owner
|
|
314
|
+
* slot (rebound-parameters.ts), per function, and the current owner slots. */
|
|
315
|
+
reboundByFunction = new Map();
|
|
316
|
+
reboundOwnerSlots = new Map();
|
|
317
|
+
currentBorrowedReturn = false;
|
|
230
318
|
/** Manifest-bound native imports, used by ffiCall emission. */
|
|
231
319
|
ffiByName = new Map();
|
|
232
320
|
/** C-ABI callback trampolines and (for raw/no-userdata callbacks) their
|
|
@@ -245,6 +333,15 @@ export class LlEmitter {
|
|
|
245
333
|
* checks are emitted only after calls that can actually raise. */
|
|
246
334
|
mayThrow;
|
|
247
335
|
indirectMayThrow;
|
|
336
|
+
/** Worker programs: the functions that poll for termination on entry
|
|
337
|
+
* (workerTerminationPolls); null outside worker programs. */
|
|
338
|
+
workerEntryPolls;
|
|
339
|
+
/** Functions that check the native stack guard on entry
|
|
340
|
+
* (stackCheckedFunctions); null where the runtime has no guard. */
|
|
341
|
+
stackChecks;
|
|
342
|
+
/** Static types whose values can be published (@scriptc/threads): their
|
|
343
|
+
* stores carry the frozen-object guard. Null without publish calls. */
|
|
344
|
+
publishedTypes;
|
|
248
345
|
/** Method names with at least one may-throw implementation — the
|
|
249
346
|
* virtualCall pending check's key. */
|
|
250
347
|
mayThrowMethods = new Set();
|
|
@@ -265,6 +362,11 @@ export class LlEmitter {
|
|
|
265
362
|
liveDynUnionRefAdapters = new Map();
|
|
266
363
|
dynPromiseAdapters = new Map();
|
|
267
364
|
unionsById = new Map();
|
|
365
|
+
nullableFields;
|
|
366
|
+
/** Unions whose values are nullable arm pointers (nullable-unions.ts). */
|
|
367
|
+
nullableUnions;
|
|
368
|
+
/** The union table without nullable unions: the stack-box analyses. */
|
|
369
|
+
boxedUnionsById;
|
|
268
370
|
recordsById = new Map();
|
|
269
371
|
recordCloneShapes = new Set();
|
|
270
372
|
tracedShapes;
|
|
@@ -272,6 +374,8 @@ export class LlEmitter {
|
|
|
272
374
|
/** The class graph (buildClassGraph): preorder numbering, hierarchy
|
|
273
375
|
* membership, virtual slot lists. */
|
|
274
376
|
classMeta;
|
|
377
|
+
/** Which vtable slots borrow which parameters (virtual-borrows.ts). */
|
|
378
|
+
virtualBorrows;
|
|
275
379
|
/** Class objects (classes as first-class values): className → the
|
|
276
380
|
* interned .name literal ref — registered during body emission, the
|
|
277
381
|
* statics and construct thunks assemble around the bodies. */
|
|
@@ -312,6 +416,8 @@ export class LlEmitter {
|
|
|
312
416
|
storedSplits = new Map();
|
|
313
417
|
streamingSplitsEnabled = false;
|
|
314
418
|
initializerBindings;
|
|
419
|
+
/** Whole-program int32 storage and parameter/result facts. */
|
|
420
|
+
int32Slots;
|
|
315
421
|
numericLocals = new Map();
|
|
316
422
|
captureIds = new Set();
|
|
317
423
|
/** Active induction bindings retain their width independently of size_t. */
|
|
@@ -358,18 +464,26 @@ export class LlEmitter {
|
|
|
358
464
|
stmt: () => true,
|
|
359
465
|
expr: (expr) => !(expr.kind === "libCall" && expr.fn === "error.stack"),
|
|
360
466
|
}));
|
|
467
|
+
this.nullableUnions = new NullableUnions(mod);
|
|
361
468
|
this.debug =
|
|
362
469
|
options.debugSources === undefined
|
|
363
470
|
? null
|
|
364
|
-
: new LlvmDebugInfo(mod.sourceFile, options.debugSources, options.pointerBits, mod.unions);
|
|
471
|
+
: new LlvmDebugInfo(mod.sourceFile, options.debugSources, options.pointerBits, mod.unions, this.nullableUnions.armTypes());
|
|
365
472
|
this.constantNumericTables = findConstantNumericTables(mod);
|
|
366
473
|
this.initializerBindings = findInitializerBindings(mod);
|
|
474
|
+
this.int32Slots = analyzeInt32Slots(mod);
|
|
367
475
|
this.sizeType = options.pointerBits === 32 ? "i32" : "i64";
|
|
368
476
|
this.ffiExtendNarrowIntegers =
|
|
369
477
|
options.wasi === true || ffiExtendsNarrowIntegers(options.targetTriple);
|
|
370
478
|
this.wasi = options.wasi === true;
|
|
479
|
+
this.executableTls =
|
|
480
|
+
mod.workers === true &&
|
|
481
|
+
mod.lib === undefined &&
|
|
482
|
+
!this.wasi &&
|
|
483
|
+
!/apple|darwin|windows|mingw|cygwin|win32/.test(options.targetTriple || process.platform);
|
|
371
484
|
this.emitLibraryIdentity = options.emitLibraryIdentity !== false;
|
|
372
485
|
this.runtimeAbiMarker = options.runtimeAbiMarker === true;
|
|
486
|
+
this.rcHelpers = options.inlineRc === true ? new Set() : null;
|
|
373
487
|
// ScrCycHdr.color stays 12 bytes behind a wasm32 object and 16 bytes
|
|
374
488
|
// behind a 64-bit object. The wasm32 header pads before color so its
|
|
375
489
|
// payload remains double-aligned without changing this ABI offset.
|
|
@@ -382,11 +496,17 @@ export class LlEmitter {
|
|
|
382
496
|
for (const entry of mod.ffiImports ?? []) {
|
|
383
497
|
this.ffiByName.set(entry.name, entry);
|
|
384
498
|
}
|
|
385
|
-
|
|
499
|
+
// Executables on Linux and macOS install a native stack guard per thread
|
|
500
|
+
// and per async fiber; WASI, Windows and library builds have none.
|
|
501
|
+
const triple = (options.targetTriple ?? "").toLowerCase();
|
|
502
|
+
const windows = triple === "" ? process.platform === "win32" : triple.includes("windows");
|
|
503
|
+
const mt = computeMayThrow(mod, {
|
|
504
|
+
stackChecks: !this.wasi && mod.lib === undefined && !windows,
|
|
505
|
+
});
|
|
386
506
|
this.mayThrow = mt.fns;
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
507
|
+
this.workerEntryPolls = mt.workerEntryPolls ?? null;
|
|
508
|
+
this.stackChecks = mt.stackChecks ?? null;
|
|
509
|
+
this.publishedTypes = computePublishedTypes(mod);
|
|
390
510
|
this.indirectMayThrow = mt.indirect || mod.workers === true;
|
|
391
511
|
for (const cls of mod.classes ?? []) {
|
|
392
512
|
for (const m of cls.methods ?? []) {
|
|
@@ -396,12 +516,61 @@ export class LlEmitter {
|
|
|
396
516
|
}
|
|
397
517
|
for (const u of mod.unions ?? [])
|
|
398
518
|
this.unionsById.set(u.id, u);
|
|
519
|
+
this.boxedUnionsById = this.nullableUnions.boxedUnions(this.unionsById);
|
|
520
|
+
this.immortalValues.add("null");
|
|
521
|
+
this.immortalValues.add(NULLABLE_NULL);
|
|
522
|
+
this.nullableFields = new NullableRefFields(mod.classes ?? [], this.boxedUnionsById);
|
|
523
|
+
// Optional array reads also cover nullable unions: emitLocalArrayRead
|
|
524
|
+
// then yields the (borrowed or owned) element pointer, not a stack box.
|
|
399
525
|
this.optionalArrayReads = new OptionalArrayReads(this.fnByName, this.unionsById);
|
|
400
|
-
this.
|
|
401
|
-
this.
|
|
526
|
+
this.checkedNarrows = new CheckedNarrows(this.fnByName);
|
|
527
|
+
this.referenceEffects = new ReferenceEffects(this.fnByName, (call) => this.optionalArrayReads.get(call) !== null, mod.classes ?? []);
|
|
528
|
+
const walkParameters = new Map();
|
|
529
|
+
if (this.debug === null) {
|
|
530
|
+
const host = {
|
|
531
|
+
pointerLocal: (local) => this.plainReference(local.type),
|
|
532
|
+
borrowsWithoutOwning: (e) => this.borrowsWithoutOwning(e),
|
|
533
|
+
};
|
|
534
|
+
for (const fn of this.fnByName.values()) {
|
|
535
|
+
if (!this.referenceEffects.functions.has(fn.name))
|
|
536
|
+
continue;
|
|
537
|
+
const walks = findWalkBorrows(fn, host);
|
|
538
|
+
if (walks.size === 0)
|
|
539
|
+
continue;
|
|
540
|
+
this.walkBorrowsByFunction.set(fn.name, walks);
|
|
541
|
+
const indexes = new Set();
|
|
542
|
+
fn.params.forEach((param, index) => {
|
|
543
|
+
if (walks.has(param.localId))
|
|
544
|
+
indexes.add(index);
|
|
545
|
+
});
|
|
546
|
+
if (indexes.size > 0)
|
|
547
|
+
walkParameters.set(fn.name, indexes);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (this.debug === null) {
|
|
551
|
+
for (const fn of this.fnByName.values()) {
|
|
552
|
+
// Strings keep the owned parameter: their in-place append
|
|
553
|
+
// (`s = s + x`) needs a uniquely owned binding.
|
|
554
|
+
const rebound = findReboundParameters(fn, (type) => this.plainReference(type) &&
|
|
555
|
+
type.kind !== "string" &&
|
|
556
|
+
(type.kind !== "union" || this.nullableUnions.get(type.unionId)?.arm.kind !== "string"), this.walkBorrowsByFunction.get(fn.name) ?? new Set());
|
|
557
|
+
if (rebound.size === 0)
|
|
558
|
+
continue;
|
|
559
|
+
this.reboundByFunction.set(fn.name, rebound);
|
|
560
|
+
const indexes = walkParameters.get(fn.name) ?? new Set();
|
|
561
|
+
fn.params.forEach((param, index) => {
|
|
562
|
+
if (rebound.has(param.localId))
|
|
563
|
+
indexes.add(index);
|
|
564
|
+
});
|
|
565
|
+
walkParameters.set(fn.name, indexes);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
this.callLifetimes = analyzeCallLifetimes(this.fnByName, (className, field) => this.nullableFields.get(className, field) !== null, walkParameters);
|
|
402
569
|
this.constantCallbacks = findConstantCallbacks(mod, this.callLifetimes);
|
|
403
570
|
this.stackCallbacks = new StackCallbacks(this.fnByName);
|
|
571
|
+
this.receiverReaders = new AmbientReceiverReaders(this.fnByName);
|
|
404
572
|
this.stackCaptures = new StackCaptures(this.fnByName, this.callLifetimes, this.stackCallbacks);
|
|
573
|
+
this.lazyCaptures = new LazyCaptures(this.fnByName);
|
|
405
574
|
for (const r of mod.records ?? [])
|
|
406
575
|
this.recordsById.set(r.id, r);
|
|
407
576
|
const traced = computeTraced(mod);
|
|
@@ -412,8 +581,20 @@ export class LlEmitter {
|
|
|
412
581
|
cycleColorOffset: this.cycleColorOffset,
|
|
413
582
|
tracedShapes: this.tracedShapes,
|
|
414
583
|
tracedUnions: this.tracedUnions,
|
|
584
|
+
nullableUnions: this.nullableUnions,
|
|
415
585
|
recordsById: this.recordsById,
|
|
416
586
|
recordCloneShapes: this.recordCloneShapes,
|
|
587
|
+
objectAudit: options.objectAudit !== false,
|
|
588
|
+
inlineAlloc: inlineAllocSupported({
|
|
589
|
+
pointerBits: options.pointerBits,
|
|
590
|
+
wasi: options.wasi,
|
|
591
|
+
targetTriple: options.targetTriple,
|
|
592
|
+
threadInstances: mod.lib?.threadInstances === true,
|
|
593
|
+
}),
|
|
594
|
+
// Worker programs keep one allocator per script thread: the inline
|
|
595
|
+
// paths address its thread-local state.
|
|
596
|
+
threadLocalAlloc: mod.workers === true,
|
|
597
|
+
rcHelpers: this.rcHelpers,
|
|
417
598
|
unionsById: this.unionsById,
|
|
418
599
|
declare: (decl) => this.declare(decl),
|
|
419
600
|
needOom: () => this.needOom(),
|
|
@@ -421,6 +602,8 @@ export class LlEmitter {
|
|
|
421
602
|
internLiteral: (text) => this.internLiteral(text),
|
|
422
603
|
cstr: (text) => this.cstr(text),
|
|
423
604
|
unitInstanceRef: (unionId, tag) => this.unitInstanceRef(unionId, tag),
|
|
605
|
+
fieldAbsentTestIn: (B, value, unionId) => this.fieldAbsentTestIn(B, value, unionId),
|
|
606
|
+
absentInstanceRef: (unionId) => this.absentInstanceRef(unionId),
|
|
424
607
|
liveDynRefAdapter: (type) => this.liveDynRefAdapter(type),
|
|
425
608
|
liveDynUnionRefAdapter: (type) => this.liveDynUnionRefAdapter(type),
|
|
426
609
|
dynPromiseAdapter: (type) => this.dynPromiseAdapter(type),
|
|
@@ -432,6 +615,8 @@ export class LlEmitter {
|
|
|
432
615
|
.filter((meta) => intervals.some((range) => range.pre <= meta.pre && meta.pre <= range.post))
|
|
433
616
|
.map((meta) => meta.def.name);
|
|
434
617
|
},
|
|
618
|
+
pendingTestLines: (dest) => this.pendingTestLines(dest),
|
|
619
|
+
virtualEntry: (implFn) => this.virtualEntry(implFn),
|
|
435
620
|
};
|
|
436
621
|
this.walkers = new LlWalkers(this.shapeHost);
|
|
437
622
|
this.dyn = new LlDyn(this.shapeHost);
|
|
@@ -488,6 +673,21 @@ export class LlEmitter {
|
|
|
488
673
|
lib: rec.lib,
|
|
489
674
|
});
|
|
490
675
|
}
|
|
676
|
+
this.virtualBorrows = new VirtualBorrows(mod, this.classMeta, this.fnByName, this.callLifetimes.borrowed);
|
|
677
|
+
// After the vtable slots: borrowing slots exclude their implementations.
|
|
678
|
+
if (this.debug === null)
|
|
679
|
+
this.analyzeBorrowedReturns();
|
|
680
|
+
}
|
|
681
|
+
/** The borrowed parameters of a hierarchy's vtable slot (empty: owned). */
|
|
682
|
+
virtualSlotBorrowed(rootName, slotIndex) {
|
|
683
|
+
return this.virtualBorrows.slot(rootName, slotIndex);
|
|
684
|
+
}
|
|
685
|
+
/** The vtable entry for one implementation (VirtualBorrows.entry). */
|
|
686
|
+
virtualEntry(implFn) {
|
|
687
|
+
return this.virtualBorrows.entry(implFn);
|
|
688
|
+
}
|
|
689
|
+
implSlotBorrowed(implFn) {
|
|
690
|
+
return this.virtualBorrows.implSlotBorrowed(implFn);
|
|
491
691
|
}
|
|
492
692
|
abiOffset(native64, wasm32) {
|
|
493
693
|
return this.sizeType === "i32" ? wasm32 : native64;
|
|
@@ -528,7 +728,6 @@ export class LlEmitter {
|
|
|
528
728
|
const defs = [];
|
|
529
729
|
if (this.ffiCallbackAdapters.size === 0)
|
|
530
730
|
return { globals, defs };
|
|
531
|
-
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
532
731
|
this.declare(`declare void @scr_trap(ptr)`);
|
|
533
732
|
const expired = this.cstr("scriptc: native callback invoked outside its call-scoped lifetime\n");
|
|
534
733
|
const released = this.cstr("scriptc: native callback invoked outside its retained lifetime\n");
|
|
@@ -646,7 +845,7 @@ export class LlEmitter {
|
|
|
646
845
|
? ` %cb = load ptr, ptr @${adapter.tls}`
|
|
647
846
|
: adapter.global !== null
|
|
648
847
|
? ` %cb = load ptr, ptr @${adapter.global}`
|
|
649
|
-
: ` %cb = getelementptr inbounds i8, ptr %ctx, i64 0`, ` %missing = icmp eq ptr %cb, null`, ` br i1 %missing, label %expired, label %ready`, `expired:`, ` call void @scr_trap(ptr ${adapter.callback.lifetime === "call" ? expired : released})`, ` unreachable`, `ready:`,
|
|
848
|
+
: ` %cb = getelementptr inbounds i8, ptr %ctx, i64 0`, ` %missing = icmp eq ptr %cb, null`, ` br i1 %missing, label %expired, label %ready`, `expired:`, ` call void @scr_trap(ptr ${adapter.callback.lifetime === "call" ? expired : released})`, ` unreachable`, `ready:`, ...this.pendingTestLines("%pending"), ` br i1 %pending, label %skip, label %invoke`, `skip:`, ` ret ${ffiCallbackDummyLl(cb)}`, `invoke:`);
|
|
650
849
|
// Validate every native pointer before allocating any copy-in value.
|
|
651
850
|
// A later bad slot therefore cannot leak an earlier materialization.
|
|
652
851
|
for (let i = 0; i < cb.params.length; i++) {
|
|
@@ -794,11 +993,28 @@ export class LlEmitter {
|
|
|
794
993
|
return this.emitParts().join("\n");
|
|
795
994
|
}
|
|
796
995
|
emitParts() {
|
|
996
|
+
const parts = this.emitModuleParts();
|
|
997
|
+
if (!this.executableTls)
|
|
998
|
+
return parts;
|
|
999
|
+
// A worker executable's thread-locals all live in the executable's own
|
|
1000
|
+
// TLS block (the program's internal ones, the runtime's external ones),
|
|
1001
|
+
// so on ELF they can use the executable models: LLVM's default
|
|
1002
|
+
// general-dynamic sequence (relaxed by the linker to `mov %fs:0` plus
|
|
1003
|
+
// an add) becomes one %fs-relative access LLVM can fold into loads.
|
|
1004
|
+
// Darwin's thread-local variables have a single model.
|
|
1005
|
+
return parts.map((part) => part
|
|
1006
|
+
.replace(/^(@[-$._A-Za-z0-9]+ = internal )thread_local global /gm, "$1thread_local(localexec) global ")
|
|
1007
|
+
.replace(/^(@[-$._A-Za-z0-9]+ = external )thread_local global /gm, "$1thread_local(initialexec) global "));
|
|
1008
|
+
}
|
|
1009
|
+
emitModuleParts() {
|
|
797
1010
|
// Function bodies first (the literal/unit/fn-value tables fill as they
|
|
798
1011
|
// emit), then the file assembles around them — the runtime ABI’s order.
|
|
799
1012
|
const fnDefs = [];
|
|
800
|
-
for (const fn of this.mod.functions)
|
|
801
|
-
|
|
1013
|
+
for (const fn of this.mod.functions) {
|
|
1014
|
+
const def = this.emitFunction(fn);
|
|
1015
|
+
fnDefs.push(def);
|
|
1016
|
+
this.fnDefText.set(fn.name, def);
|
|
1017
|
+
}
|
|
802
1018
|
const errorViews = [];
|
|
803
1019
|
for (const property of ["name", "message"])
|
|
804
1020
|
if (this.mod.functions.some((fn) => fn.name === `%error.${property}.read`)) {
|
|
@@ -817,7 +1033,7 @@ export class LlEmitter {
|
|
|
817
1033
|
this.declare(`declare void @scr_error_register_dyn(ptr, ptr)`);
|
|
818
1034
|
errorViews.push(` call void @scr_error_register_dyn(ptr @${mangleVtInstance(meta.def.name)}, ptr @${name})`);
|
|
819
1035
|
}
|
|
820
|
-
const layouts = emitLlvmLayouts(this.shapeHost, this.mod, this.classMeta, this.classObjs, this.fnByName, (t) => this.llType(t));
|
|
1036
|
+
const layouts = emitLlvmLayouts(this.shapeHost, this.mod, this.classMeta, this.classObjs, this.fnByName, (t) => this.llType(t), this.nullableFields, this.int32Slots);
|
|
821
1037
|
const shapes = layouts.records;
|
|
822
1038
|
const classShapes = layouts.classes;
|
|
823
1039
|
const classObjDefs = layouts.classObjects;
|
|
@@ -1020,12 +1236,15 @@ export class LlEmitter {
|
|
|
1020
1236
|
const topPendingReleases = asyncEntry ? globalReleaseLines("gp") : [];
|
|
1021
1237
|
const loopReportedReleases = runsLoop ? globalReleaseLines("gq") : [];
|
|
1022
1238
|
// main's epilogues read the flag / the loop entry points — declared
|
|
1023
|
-
// HERE, before the extern block flushes
|
|
1024
|
-
//
|
|
1239
|
+
// HERE, before the extern block flushes. They run once, so they keep
|
|
1240
|
+
// the out-of-line scr_exc_pending call; per-call checks inline the
|
|
1241
|
+
// test (pendingTestLines).
|
|
1025
1242
|
if (entryMayThrow || runsLoop)
|
|
1026
1243
|
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
1027
1244
|
if (runsLoop) {
|
|
1028
1245
|
this.declare(`declare zeroext i1 @scr_loop_run(ptr)`);
|
|
1246
|
+
if (this.mod.commonJsEntry === true)
|
|
1247
|
+
this.declare(`declare void @scr_loop_commonjs_entry()`);
|
|
1029
1248
|
this.declare(`declare zeroext i1 @scr_report_unhandled_rejections()`);
|
|
1030
1249
|
this.declare(`declare void @scr_discard_unhandled_rejections()`);
|
|
1031
1250
|
}
|
|
@@ -1116,6 +1335,8 @@ export class LlEmitter {
|
|
|
1116
1335
|
const helpers = [
|
|
1117
1336
|
...this.helperDefs(),
|
|
1118
1337
|
...emitClassMembershipHelper(this.classMeta, this.sizeType),
|
|
1338
|
+
...(this.publishWalkersCache?.defs() ?? []),
|
|
1339
|
+
...this.publishedClassNamesDefs(),
|
|
1119
1340
|
];
|
|
1120
1341
|
if (this.constantNumericTables.size > 0)
|
|
1121
1342
|
this.declare(`declare double @scr_arr_get_number(ptr, double)`);
|
|
@@ -1132,13 +1353,28 @@ export class LlEmitter {
|
|
|
1132
1353
|
`%ScrLogArg = type { i32, i64 }`,
|
|
1133
1354
|
`%ScrVt = type { ${this.sizeType}, ${this.sizeType}, ptr }`,
|
|
1134
1355
|
`%ScrUnion = type { ${this.sizeType}, i32, ptr, ptr, ptr, i64 }`,
|
|
1135
|
-
`%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32 }`,
|
|
1356
|
+
`%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32, i32, ptr }`,
|
|
1136
1357
|
`%ScrFfiTable = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, i8, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`,
|
|
1137
|
-
`%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr }`,
|
|
1358
|
+
`%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr, ptr }`,
|
|
1138
1359
|
// ScrArr mirrors scr_runtime.h field-for-field. Live dynamic stream
|
|
1139
1360
|
// commits swap its mutable dense, sparse, presence, and property
|
|
1140
1361
|
// storage while preserving the target object's identity.
|
|
1141
1362
|
`%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, ptr, ptr, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ptr, ${this.sizeType}, ${this.sizeType}, ptr }`,
|
|
1363
|
+
// The ScrMap prefix { rc, key_kind, val_kind, val_retain, val_release,
|
|
1364
|
+
// val_trace, key_retain, key_release, key_trace } — the inline RC
|
|
1365
|
+
// fast paths test the two trace slots (shapes.ts).
|
|
1366
|
+
...(this.rcHelpers === null
|
|
1367
|
+
? []
|
|
1368
|
+
: [`%ScrMapRc = type { ${this.sizeType}, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr }`]),
|
|
1369
|
+
// The complete ScrMap and its { key, val, hash } entry, read by the
|
|
1370
|
+
// inline number-key lookup (sc_map_entry_f64): entries is field 12,
|
|
1371
|
+
// the direct index and its length fields 18 and 19.
|
|
1372
|
+
...(this.decls.has(NUMBER_MAP_ENTRY_DECL)
|
|
1373
|
+
? [
|
|
1374
|
+
`%ScrMapIx = type { ${this.sizeType}, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ${this.sizeType}, ptr, ${this.sizeType}, ptr, ptr, ptr, ${this.sizeType} }`,
|
|
1375
|
+
`%ScrMapEntry = type { i64, i64, i64 }`,
|
|
1376
|
+
]
|
|
1377
|
+
: []),
|
|
1142
1378
|
// The runtime error prefix { rc, vt, name, message, code, cause } and the
|
|
1143
1379
|
// class-object shape { rc, pre, post, ctor, name } — field reads on
|
|
1144
1380
|
// builtin errors and classval loads GEP through these.
|
|
@@ -1184,12 +1420,13 @@ export class LlEmitter {
|
|
|
1184
1420
|
// data (string literals, unit arms, template arrays, vtables) stays
|
|
1185
1421
|
// shared.
|
|
1186
1422
|
const tl = this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : "";
|
|
1187
|
-
out.push(``, `@scr_error_vts = external ${tl}global [${RUNTIME_ERROR_CLASSES.size} x %ScrVt]`, `declare void @scr_init()`, `declare void @scr_lib_init(i32, ptr)`, ...(this.mod.workers ? [`declare void @
|
|
1423
|
+
out.push(``, `@scr_error_vts = external ${tl}global [${RUNTIME_ERROR_CLASSES.size} x %ScrVt]`, `declare void @scr_init()`, `declare void @scr_lib_init(i32, ptr)`, ...(this.mod.workers ? [`declare void @scr_runtime_workers_v8()`] : []), ...(this.stackChecks?.size ? [`declare void @scr_stack_guard_init()`] : []), ...(this.runtimeAbiMarker && this.mod.lib === undefined
|
|
1188
1424
|
? [`declare void @${RUNTIME_ABI_MARKER}()`]
|
|
1189
1425
|
: []));
|
|
1190
1426
|
for (const d of this.decls)
|
|
1191
1427
|
out.push(d);
|
|
1192
1428
|
out.push(``);
|
|
1429
|
+
const flushedDecls = new Set(this.decls);
|
|
1193
1430
|
for (const table of this.constantNumericTables.values()) {
|
|
1194
1431
|
const n = table.values.length;
|
|
1195
1432
|
out.push(`@${table.symbol} = private constant [${n} x double] [${table.values.map((v) => `double ${f64Lit(v)}`).join(", ")}]`, `define internal double @${table.symbol}_get(ptr %a, double %i) alwaysinline #0 {`, `entry:`, ` %initialized = icmp ne ptr %a, null`, ` %nonnegative = fcmp oge double %i, ${f64Lit(0)}`, ` %below = fcmp olt double %i, ${f64Lit(n)}`, ` %range = and i1 %nonnegative, %below`, ` %safe = and i1 %initialized, %range`, ` br i1 %safe, label %convert, label %fallback`, `convert:`,
|
|
@@ -1200,8 +1437,14 @@ export class LlEmitter {
|
|
|
1200
1437
|
for (const [text, lit] of this.literals) {
|
|
1201
1438
|
// Immortal interned ScrStr: { rc = SIZE_MAX, len, cap = len, bytes\0 } —
|
|
1202
1439
|
// the runtime ABI’s static table, retain/release skip rc == SIZE_MAX.
|
|
1440
|
+
// 64-bit (little-endian) targets split the capacity word: the high
|
|
1441
|
+
// half carries the precomputed Map key hash (SCR_STR_HASH_CACHE).
|
|
1442
|
+
// Bit 31 marks all-ASCII literals (SCR_STR_ASCII_BIT) on every target.
|
|
1443
|
+
const capWord = this.sizeType === "i64"
|
|
1444
|
+
? literalCapWord(Buffer.from(text, "utf8"))
|
|
1445
|
+
: literalCapWord32(Buffer.from(text, "utf8"));
|
|
1203
1446
|
out.push(`@${lit.sym} = internal global { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, [${lit.len + 1} x i8] } ` +
|
|
1204
|
-
`{ ${this.sizeType} -1, ${this.sizeType} ${lit.len}, ${this.sizeType} ${
|
|
1447
|
+
`{ ${this.sizeType} -1, ${this.sizeType} ${lit.len}, ${this.sizeType} ${capWord}, [${lit.len + 1} x i8] c"${llStrBytes(text)}" }`);
|
|
1205
1448
|
}
|
|
1206
1449
|
if (this.literals.size > 0)
|
|
1207
1450
|
out.push(``);
|
|
@@ -1214,11 +1457,37 @@ export class LlEmitter {
|
|
|
1214
1457
|
}
|
|
1215
1458
|
if (this.unitInstances.size > 0)
|
|
1216
1459
|
out.push(``);
|
|
1460
|
+
for (const [unionId, sym] of this.absentInstances) {
|
|
1461
|
+
// One immortal ABSENT field-slot instance per undefined-armed union:
|
|
1462
|
+
// the undefined arm's tag with payload 1 (ordinary unit instances
|
|
1463
|
+
// carry 0). Every tag reader sees undefined; recordHas and the
|
|
1464
|
+
// field reads compare tag and payload, never the address, so the
|
|
1465
|
+
// per-object copies of separately compiled units agree.
|
|
1466
|
+
const tag = undefinedArmTag({ kind: "union", unionId }, this.unionsById);
|
|
1467
|
+
out.push(`@${sym} = internal global %ScrUnion { ${this.sizeType} -1, i32 ${tag}, ptr null, ptr null, ptr null, i64 ${ABSENT_FIELD_PAYLOAD} } ; ${unionId} absent field`);
|
|
1468
|
+
}
|
|
1469
|
+
if (this.absentInstances.size > 0)
|
|
1470
|
+
out.push(``);
|
|
1471
|
+
if (this.nullableUnions.usesNullSentinel) {
|
|
1472
|
+
// `null` in every `C | null | undefined` union (NULL is undefined):
|
|
1473
|
+
// one module-scope immortal object like the ABSENT sentinel.
|
|
1474
|
+
const S = this.sizeType;
|
|
1475
|
+
out.push(`${NULLABLE_NULL} = internal global { ${S}, ${S}, ${S}, ${S} } { ${S} -1, ${S} 0, ${S} 0, ${S} 0 } ; nullable null arm`, ``);
|
|
1476
|
+
}
|
|
1477
|
+
if (this.needsNullableAbsent) {
|
|
1478
|
+
// The ABSENT field-slot state of every nullable-pointer union: one
|
|
1479
|
+
// module-scope immortal object (rc == SIZE_MAX), distinct from NULL
|
|
1480
|
+
// (undefined) and from every instance. RC entry points and the
|
|
1481
|
+
// collector skip it like any immortal; field reads compare its address.
|
|
1482
|
+
const S = this.sizeType;
|
|
1483
|
+
out.push(`${NULLABLE_ABSENT} = internal global { ${S}, ${S}, ${S}, ${S} } { ${S} -1, ${S} 0, ${S} 0, ${S} 0 } ; nullable absent field`, ``);
|
|
1484
|
+
}
|
|
1217
1485
|
for (const [key, re] of this.regexInstances) {
|
|
1218
1486
|
// One immortal ScrRegex per (pattern, flags) literal, pointing at
|
|
1219
|
-
// the interned source/flags strings. The bc
|
|
1220
|
-
// compile, cached by the runtime) — a mutable
|
|
1221
|
-
|
|
1487
|
+
// the interned source/flags strings. The bc and native-matcher slots
|
|
1488
|
+
// start null (lazy compile, cached by the runtime) — a mutable
|
|
1489
|
+
// global, not constant.
|
|
1490
|
+
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 } ; ${key.replace(/\n/g, "\\n")}`);
|
|
1222
1491
|
}
|
|
1223
1492
|
if (this.regexInstances.size > 0)
|
|
1224
1493
|
out.push(``);
|
|
@@ -1367,6 +1636,7 @@ export class LlEmitter {
|
|
|
1367
1636
|
// symbols specified by the library IR instead.
|
|
1368
1637
|
for (const line of this.emitLibDefs(globals, globalReleaseLines, stamps))
|
|
1369
1638
|
out.push(line);
|
|
1639
|
+
out.push(...this.inlineRcTail(flushedDecls));
|
|
1370
1640
|
out.push(`attributes #0 = { sanitize_address }`);
|
|
1371
1641
|
if (this.wasi)
|
|
1372
1642
|
out.push(`attributes #1 = { sanitize_address presplitcoroutine }`);
|
|
@@ -1378,7 +1648,10 @@ export class LlEmitter {
|
|
|
1378
1648
|
out.push(this.debug.render());
|
|
1379
1649
|
return out;
|
|
1380
1650
|
}
|
|
1381
|
-
out.push(`define i32 @${this.mod.workers ? "sc_context_entry" : this.wasi ? "__main_argc_argv" : "main"}(i32 %argc, ptr %argv) ${FN_ATTRS} {`, `entry:`, ...(this.runtimeAbiMarker ? [` call void @${RUNTIME_ABI_MARKER}()`] : []), ...(this.mod.workers ? [` call void @
|
|
1651
|
+
out.push(`define i32 @${this.mod.workers ? "sc_context_entry" : this.wasi ? "__main_argc_argv" : "main"}(i32 %argc, ptr %argv) ${FN_ATTRS} {`, `entry:`, ...(this.runtimeAbiMarker ? [` call void @${RUNTIME_ABI_MARKER}()`] : []), ...(this.mod.workers ? [` call void @scr_runtime_workers_v8()`] : []), ` call void @scr_init()`,
|
|
1652
|
+
// Each context (the main thread, every worker) installs its stack
|
|
1653
|
+
// guard before running compiled code that checks it.
|
|
1654
|
+
...(this.stackChecks?.size ? [` call void @scr_stack_guard_init()`] : []), ...stamps,
|
|
1382
1655
|
// Event-surface programs (signal/exit listeners) fill the loop's
|
|
1383
1656
|
// nullable event hooks before %main — scr_events.c links only when
|
|
1384
1657
|
// this line is emitted (native/executable.ts gates on the same predicate).
|
|
@@ -1423,6 +1696,7 @@ export class LlEmitter {
|
|
|
1423
1696
|
// both exit 1, like Node — the C main's loop block exactly.
|
|
1424
1697
|
...(runsLoop
|
|
1425
1698
|
? [
|
|
1699
|
+
...(this.mod.commonJsEntry === true ? [` call void @scr_loop_commonjs_entry()`] : []),
|
|
1426
1700
|
` %loop_rejection = call zeroext i1 @scr_loop_run(ptr ${asyncEntry ? "%top" : "null"})`,
|
|
1427
1701
|
` %lexc = call zeroext i1 @scr_exc_pending()`,
|
|
1428
1702
|
` br i1 %lexc, label %luncaught, label %lok`,
|
|
@@ -1486,7 +1760,7 @@ export class LlEmitter {
|
|
|
1486
1760
|
? [` %test_exit = call i32 @scr_test_exit_code()`, ` ret i32 %test_exit`]
|
|
1487
1761
|
: usesIsland
|
|
1488
1762
|
? [` %island_exit = call i32 @scr_island_exit_code()`, ` ret i32 %island_exit`]
|
|
1489
|
-
: [` %native_exit = call i32 @scr_exit_code_hint_get()`, ` ret i32 %native_exit`]), `}`, ``,
|
|
1763
|
+
: [` %native_exit = call i32 @scr_exit_code_hint_get()`, ` ret i32 %native_exit`]), `}`, ``, ...this.inlineRcTail(flushedDecls),
|
|
1490
1764
|
// sanitize_address is inert under the plain pipeline; the sanitized
|
|
1491
1765
|
// lane's -fsanitize=address link activates instrumentation over the
|
|
1492
1766
|
// emitted functions too (the runtime TUs get theirs from clang).
|
|
@@ -1738,15 +2012,96 @@ export class LlEmitter {
|
|
|
1738
2012
|
msgHelper("sc_bad_key", "sc_bad_key_msg", "scriptc: TypeError: record has no key (typed slot — no undefined is representable)\n");
|
|
1739
2013
|
}
|
|
1740
2014
|
if (this.needsRetainBox) {
|
|
1741
|
-
// scr_box_retain is a static inline (increment-unless-immortal
|
|
1742
|
-
// mark the cycle header live — every box is collector-headered);
|
|
2015
|
+
// scr_box_retain is a static inline (increment-unless-immortal);
|
|
1743
2016
|
// emitted once with internal linkage, like the record retains.
|
|
1744
|
-
defs.push(`define internal ptr @sc_retain_box(ptr %b) ${FN_ATTRS} {`, `entry:`, ` %rc = load ${this.sizeType}, ptr %b`, ` %imm = icmp eq ${this.sizeType} %rc, -1`, ` br i1 %imm, label %done, label %inc`, `inc:`, ` %n = add ${this.sizeType} %rc, 1`, ` store ${this.sizeType} %n, ptr %b`, `
|
|
2017
|
+
defs.push(`define internal ptr @sc_retain_box(ptr %b) ${FN_ATTRS} {`, `entry:`, ` %rc = load ${this.sizeType}, ptr %b`, ` %imm = icmp eq ${this.sizeType} %rc, -1`, ` br i1 %imm, label %done, label %inc`, `inc:`, ` %n = add ${this.sizeType} %rc, 1`, ` store ${this.sizeType} %n, ptr %b`, ` br label %done`, `done:`, ` ret ptr %b`, `}`, ``);
|
|
2018
|
+
}
|
|
2019
|
+
// Number-key Map/Set lookups probe the runtime's direct index inline:
|
|
2020
|
+
// while ndense is nonzero every live key is an integer below it, so an
|
|
2021
|
+
// in-range integral key reads its slot (entry index + 1, 0 = absent) and
|
|
2022
|
+
// any other key is absent. Maps without the index (ndense 0) and every
|
|
2023
|
+
// non-integral probe outside the table take the runtime lookup, which
|
|
2024
|
+
// keeps the complete SameValueZero semantics.
|
|
2025
|
+
if (this.decls.has(NUMBER_MAP_ENTRY_DECL)) {
|
|
2026
|
+
const sz = this.sizeType;
|
|
2027
|
+
defs.push(`define internal ptr @sc_map_entry_f64(ptr %m, double %k) ${FN_ATTRS} {`, `entry:`, ` %ndp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 19`, ` %nd = load ${sz}, ptr %ndp`, ` %ndf = uitofp ${sz} %nd to double`, ` %lo = fcmp oge double %k, 0.0`, ` %hi = fcmp olt double %k, %ndf`, ` %in = and i1 %lo, %hi`, ` br i1 %in, label %direct, label %outside`, `direct:`, ` %i = fptoui double %k to ${sz}`, ` %back = uitofp ${sz} %i to double`, ` %int = fcmp oeq double %back, %k`, ` br i1 %int, label %slot, label %miss`, `slot:`, ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 18`, ` %dense = load ptr, ptr %dp`, ` %sp = getelementptr inbounds i32, ptr %dense, ${sz} %i`, ` %s = load i32, ptr %sp`, ` %empty = icmp eq i32 %s, 0`, ` br i1 %empty, label %miss, label %hit`, `hit:`, ` %e32 = sub i32 %s, 1`, sz === "i32" ? ` %e = add i32 %e32, 0` : ` %e = zext i32 %e32 to ${sz}`, ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 12`, ` %entries = load ptr, ptr %ep`, ` %found = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %e`, ` ret ptr %found`, `miss:`, ` ret ptr null`, `outside:`, ` %dense.any = icmp ne ${sz} %nd, 0`, ` br i1 %dense.any, label %miss, label %slow`, `slow:`, ` %r = call ptr @scr_map_entry_f64(ptr %m, double %k)`, ` ret ptr %r`, `}`, ``);
|
|
2028
|
+
}
|
|
2029
|
+
// String comparisons enter through small internal wrappers that LLVM
|
|
2030
|
+
// inlines at each site: interned literals and shared values compare by
|
|
2031
|
+
// identity, and equality rejects different byte lengths, before any
|
|
2032
|
+
// runtime call (scr_str_eq/scr_str_cmp keep the complete semantics).
|
|
2033
|
+
if (this.decls.has(`declare zeroext i1 @scr_str_eq(ptr, ptr)`)) {
|
|
2034
|
+
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`, `}`, ``);
|
|
2035
|
+
}
|
|
2036
|
+
// String length and charCodeAt read proven-ASCII strings inline: bit 31
|
|
2037
|
+
// of the capacity word (SCR_STR_ASCII_BIT, the low half's sign bit on
|
|
2038
|
+
// little-endian targets) makes UTF-16 units bytes. Unproven strings and
|
|
2039
|
+
// anything but an in-range index take the runtime's UTF-16 mapping.
|
|
2040
|
+
for (const helper of STRING_READ_HELPERS) {
|
|
2041
|
+
if (!this.decls.has(helper.decl))
|
|
2042
|
+
continue;
|
|
2043
|
+
defs.push(...helper.define(this.sizeType, FN_ATTRS), ``);
|
|
2044
|
+
}
|
|
2045
|
+
// Ordering also decides inline when the first bytes differ: byte order
|
|
2046
|
+
// is the plain comparison's order, and the UTF-16 order whenever both
|
|
2047
|
+
// bytes are ASCII. Callers only test the sign of the result.
|
|
2048
|
+
for (const fn of ["scr_str_cmp", "scr_str_cmp_u16"]) {
|
|
2049
|
+
if (!this.decls.has(`declare i32 @${fn}(ptr, ptr)`))
|
|
2050
|
+
continue;
|
|
2051
|
+
this.declare("declare i64 @llvm.bswap.i64(i64)");
|
|
2052
|
+
const utf16 = fn === "scr_str_cmp_u16";
|
|
2053
|
+
defs.push(`define internal i32 @${fn.replace(/^scr_/, "sc_")}(ptr %a, ptr %b) ${FN_ATTRS} {`, `entry:`, ` %same = icmp eq ptr %a, %b`, ` br i1 %same, label %equal, 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`, ` %aempty = icmp eq ${this.sizeType} %alen, 0`, ` %bempty = icmp eq ${this.sizeType} %blen, 0`, ` %empty = or i1 %aempty, %bempty`, ` br i1 %empty, label %order, label %words`,
|
|
2054
|
+
// Both at least 8 bytes: the first differing byte of one word pair.
|
|
2055
|
+
// Byte-swapped little-endian words compare in byte order.
|
|
2056
|
+
`words:`, ` %along = icmp uge ${this.sizeType} %alen, 8`, ` %blong = icmp uge ${this.sizeType} %blen, 8`, ` %long = and i1 %along, %blong`, ` br i1 %long, label %word, label %first`, `word:`, ` %awp = getelementptr inbounds %ScrStr, ptr %a, i32 1`, ` %bwp = getelementptr inbounds %ScrStr, ptr %b, i32 1`, ` %aw = load i64, ptr %awp, align 1`, ` %bw = load i64, ptr %bwp, align 1`, ` %wdiffer = icmp ne i64 %aw, %bw`, ` br i1 %wdiffer, label %wdiff, label %wsame`, `wdiff:`, ...(utf16
|
|
2057
|
+
? [
|
|
2058
|
+
` %wbits = or i64 %aw, %bw`,
|
|
2059
|
+
` %whigh = and i64 %wbits, -9187201950435737472`,
|
|
2060
|
+
` %wascii = icmp eq i64 %whigh, 0`,
|
|
2061
|
+
` br i1 %wascii, label %wordorder, label %order`,
|
|
2062
|
+
]
|
|
2063
|
+
: [` br label %wordorder`]), `wordorder:`, ` %asw = call i64 @llvm.bswap.i64(i64 %aw)`, ` %bsw = call i64 @llvm.bswap.i64(i64 %bw)`, ` %wbelow = icmp ult i64 %asw, %bsw`, ` %wsign = select i1 %wbelow, i32 -1, i32 1`, ` ret i32 %wsign`,
|
|
2064
|
+
// Equal first words and one length of at most 16 bytes: the word
|
|
2065
|
+
// ending at that length covers the rest, and any difference lies
|
|
2066
|
+
// past the equal first word.
|
|
2067
|
+
`wsame:`, ` %samelen = icmp eq ${this.sizeType} %alen, %blen`, ` %short = icmp ule ${this.sizeType} %alen, 16`, ` %tailable = and i1 %samelen, %short`, ` br i1 %tailable, label %tail, label %order`, `tail:`, ` %toff = sub ${this.sizeType} %alen, 8`, ` %atp = getelementptr inbounds i8, ptr %awp, ${this.sizeType} %toff`, ` %btp = getelementptr inbounds i8, ptr %bwp, ${this.sizeType} %toff`, ` %at = load i64, ptr %atp, align 1`, ` %bt = load i64, ptr %btp, align 1`, ` %tdiffer = icmp ne i64 %at, %bt`, ` br i1 %tdiffer, label %tdiff, label %equal`, `tdiff:`, ...(utf16
|
|
2068
|
+
? [
|
|
2069
|
+
` %tbits = or i64 %at, %bt`,
|
|
2070
|
+
` %thigh = and i64 %tbits, -9187201950435737472`,
|
|
2071
|
+
` %tascii = icmp eq i64 %thigh, 0`,
|
|
2072
|
+
` br i1 %tascii, label %tailorder, label %order`,
|
|
2073
|
+
]
|
|
2074
|
+
: [` br label %tailorder`]), `tailorder:`, ` %ats = call i64 @llvm.bswap.i64(i64 %at)`, ` %bts = call i64 @llvm.bswap.i64(i64 %bt)`, ` %tbelow = icmp ult i64 %ats, %bts`, ` %tsign = select i1 %tbelow, i32 -1, i32 1`, ` ret i32 %tsign`, `first:`, ` %adata = getelementptr inbounds %ScrStr, ptr %a, i32 1`, ` %bdata = getelementptr inbounds %ScrStr, ptr %b, i32 1`, ` %x = load i8, ptr %adata`, ` %y = load i8, ptr %bdata`, ` %differ = icmp ne i8 %x, %y`, ...(utf16
|
|
2075
|
+
? [
|
|
2076
|
+
` %bits = or i8 %x, %y`,
|
|
2077
|
+
` %ascii = icmp sge i8 %bits, 0`,
|
|
2078
|
+
` %decide = and i1 %differ, %ascii`,
|
|
2079
|
+
]
|
|
2080
|
+
: [` %decide = and i1 %differ, true`]), ` br i1 %decide, label %byte, label %order`, `byte:`, ` %below = icmp ult i8 %x, %y`, ` %sign = select i1 %below, i32 -1, i32 1`, ` ret i32 %sign`, `order:`, ` %c = call i32 @${fn}(ptr %a, ptr %b)`, ` ret i32 %c`, `equal:`, ` ret i32 0`, `}`, ``);
|
|
1745
2081
|
}
|
|
1746
2082
|
// The declarations these helpers added must land in the extern block,
|
|
1747
2083
|
// which already flushed — append here instead (LLVM is order-free).
|
|
1748
2084
|
return defs.length > 0 ? [...defs] : defs;
|
|
1749
2085
|
}
|
|
2086
|
+
/** The inline RC helpers every call site requested, emitted last so
|
|
2087
|
+
* requests from main/library epilogues are included. Slow-path
|
|
2088
|
+
* declarations a late request added after the extern block flushed land
|
|
2089
|
+
* here too. */
|
|
2090
|
+
inlineRcTail(flushedDecls) {
|
|
2091
|
+
const late = inlineRcDecls(this.shapeHost).filter((d) => !flushedDecls.has(d));
|
|
2092
|
+
// NULL-skipping retain wrappers of nullable unions; a runtime inner
|
|
2093
|
+
// retain requested after the extern block flushed is declared here.
|
|
2094
|
+
for (const inner of this.nullableUnions.retainWrappers.values()) {
|
|
2095
|
+
const decl = `declare ptr ${inner}(ptr)`;
|
|
2096
|
+
if (inner.startsWith("@scr_") && !flushedDecls.has(decl) && !late.includes(decl))
|
|
2097
|
+
late.push(decl);
|
|
2098
|
+
}
|
|
2099
|
+
return [
|
|
2100
|
+
...late,
|
|
2101
|
+
...emitNullableRetainWrappers(this.shapeHost),
|
|
2102
|
+
...emitInlineRcHelpers(this.shapeHost),
|
|
2103
|
+
];
|
|
2104
|
+
}
|
|
1750
2105
|
/** Env-signature wrappers + interned immortal closures for declared
|
|
1751
2106
|
* functions used as values (the runtime ABI’s sc_w_/sc_fc_ pair): every
|
|
1752
2107
|
* mention of `f` yields the same pointer, so `f === f` holds. */
|
|
@@ -1761,7 +2116,7 @@ export class LlEmitter {
|
|
|
1761
2116
|
// never the inner return type.
|
|
1762
2117
|
const ret = fn.async === true || fn.generator !== undefined ? "ptr" : this.llType(fn.returnType);
|
|
1763
2118
|
const call = `call ${ret} @${this.callTarget(name)}(${args})`;
|
|
1764
|
-
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)} }`, ``);
|
|
2119
|
+
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 }`, ``);
|
|
1765
2120
|
}
|
|
1766
2121
|
return out;
|
|
1767
2122
|
}
|
|
@@ -1776,7 +2131,6 @@ export class LlEmitter {
|
|
|
1776
2131
|
const sizeOf = `ptrtoint (ptr getelementptr (%${pack}, ptr null, i32 1) to ${this.sizeType})`;
|
|
1777
2132
|
this.declare(`declare void @free(ptr)`);
|
|
1778
2133
|
this.declare(`declare ptr @malloc(${this.sizeType})`);
|
|
1779
|
-
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
1780
2134
|
const tr = [
|
|
1781
2135
|
`define internal void @${mangleTrampoline(fn.name)}(ptr %self, ptr %ap) ${FN_ATTRS} {`,
|
|
1782
2136
|
`entry:`,
|
|
@@ -1815,6 +2169,153 @@ export class LlEmitter {
|
|
|
1815
2169
|
});
|
|
1816
2170
|
return { definitions, pack, lifted, fieldTys, ret, tr, spawnParams, argPackLines };
|
|
1817
2171
|
}
|
|
2172
|
+
/** Settles `promise` from a clean async body result `%r` (moved in). */
|
|
2173
|
+
asyncFulfillLines(ret, promise) {
|
|
2174
|
+
switch (ret.kind) {
|
|
2175
|
+
case "void":
|
|
2176
|
+
this.declare(`declare void @scr_promise_fulfill_void(ptr)`);
|
|
2177
|
+
return [` call void @scr_promise_fulfill_void(ptr ${promise})`];
|
|
2178
|
+
case "f64":
|
|
2179
|
+
case "date":
|
|
2180
|
+
this.declare(`declare void @scr_promise_fulfill_f64(ptr, double)`);
|
|
2181
|
+
return [` call void @scr_promise_fulfill_f64(ptr ${promise}, double %r)`];
|
|
2182
|
+
case "bool":
|
|
2183
|
+
this.declare(`declare void @scr_promise_fulfill_bool(ptr, i1 zeroext)`);
|
|
2184
|
+
return [` call void @scr_promise_fulfill_bool(ptr ${promise}, i1 %r)`];
|
|
2185
|
+
case "string":
|
|
2186
|
+
this.declare(`declare void @scr_promise_fulfill_str(ptr, ptr)`);
|
|
2187
|
+
return [` call void @scr_promise_fulfill_str(ptr ${promise}, ptr %r) ; moves in`];
|
|
2188
|
+
case "dyn":
|
|
2189
|
+
this.declare(`declare void @scr_promise_resolve_dyn(ptr, ptr)`);
|
|
2190
|
+
return [` call void @scr_promise_resolve_dyn(ptr ${promise}, ptr %r) ; moves in`];
|
|
2191
|
+
default: {
|
|
2192
|
+
const v = vAdapters(this.shapeHost, ret);
|
|
2193
|
+
this.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`);
|
|
2194
|
+
return [
|
|
2195
|
+
` call void @scr_promise_fulfill_ref(ptr ${promise}, ptr %r, ptr ${v.retain}, ptr ${v.release}, ptr ${traceArg(this.shapeHost, ret)})`,
|
|
2196
|
+
];
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
/** Async functions whose calls can skip the fiber (scr_async_inline_*).
|
|
2201
|
+
* A body qualifies only when nothing it runs can park its execution
|
|
2202
|
+
* context: its emitted text, and the text of every module function it
|
|
2203
|
+
* mentions (transitively), contains no suspension entry point. Such a
|
|
2204
|
+
* body always completes before the call returns, which is exactly the
|
|
2205
|
+
* behavior of a fiber that never suspends. Excluded conservatively:
|
|
2206
|
+
* wasm32 (its coroutine lowering owns async bodies), programs embedding
|
|
2207
|
+
* the dynamic engine, module-evaluation bodies (their promise caches),
|
|
2208
|
+
* `dyn` results (resolution may adopt a thenable), and every program
|
|
2209
|
+
* that uses node:test subtests — `t.test()` runs a subtest inline and
|
|
2210
|
+
* awaits it on the CURRENT fiber even from synchronous code. Worker
|
|
2211
|
+
* programs keep fibers too: their fiber epilogue owns context
|
|
2212
|
+
* termination (a stopping worker rejects and observes the promise), which
|
|
2213
|
+
* the fiberless wrapper does not reproduce. A missed suspension still
|
|
2214
|
+
* cannot reorder silently: the runtime traps when a context parks while a
|
|
2215
|
+
* fiberless body is on its stack. */
|
|
2216
|
+
fiberlessAsyncFunctions() {
|
|
2217
|
+
const eligible = new Set();
|
|
2218
|
+
if (this.wasi || this.mod.workers === true)
|
|
2219
|
+
return eligible;
|
|
2220
|
+
const embedded = this.mod.embedded;
|
|
2221
|
+
if (embedded !== undefined && embedded.modules.length > 0)
|
|
2222
|
+
return eligible;
|
|
2223
|
+
const suspends = /@scr_(?:await_|module_await\b|promise_await_settled\b|test_sub\b|gen_yield_|async_gen_|fiber_)/;
|
|
2224
|
+
for (const decl of this.decls)
|
|
2225
|
+
if (/@scr_test_sub\b/.test(decl))
|
|
2226
|
+
return eligible;
|
|
2227
|
+
for (const text of this.fnDefText.values())
|
|
2228
|
+
if (/@scr_test_sub\b/.test(text))
|
|
2229
|
+
return eligible;
|
|
2230
|
+
const byMangled = new Map();
|
|
2231
|
+
for (const fn of this.mod.functions) {
|
|
2232
|
+
byMangled.set(mangleFunction(fn.name), fn.name);
|
|
2233
|
+
byMangled.set(mangleBorrowedFunction(fn.name), fn.name);
|
|
2234
|
+
}
|
|
2235
|
+
// Reverse mention edges, then propagate "may park" to every mentioner.
|
|
2236
|
+
const mentionedBy = new Map();
|
|
2237
|
+
const parks = new Set();
|
|
2238
|
+
const work = [];
|
|
2239
|
+
for (const [name, text] of this.fnDefText) {
|
|
2240
|
+
if (suspends.test(text)) {
|
|
2241
|
+
parks.add(name);
|
|
2242
|
+
work.push(name);
|
|
2243
|
+
}
|
|
2244
|
+
for (const match of text.matchAll(/@(sc_b?f_[A-Za-z0-9_]+)/g)) {
|
|
2245
|
+
const callee = byMangled.get(match[1]);
|
|
2246
|
+
if (callee === undefined || callee === name)
|
|
2247
|
+
continue;
|
|
2248
|
+
let callers = mentionedBy.get(callee);
|
|
2249
|
+
if (callers === undefined)
|
|
2250
|
+
mentionedBy.set(callee, (callers = new Set()));
|
|
2251
|
+
callers.add(name);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
while (work.length > 0) {
|
|
2255
|
+
const callers = mentionedBy.get(work.pop());
|
|
2256
|
+
if (callers === undefined)
|
|
2257
|
+
continue;
|
|
2258
|
+
for (const caller of callers) {
|
|
2259
|
+
if (parks.has(caller))
|
|
2260
|
+
continue;
|
|
2261
|
+
parks.add(caller);
|
|
2262
|
+
work.push(caller);
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
for (const fn of this.mod.functions) {
|
|
2266
|
+
if (fn.async !== true || fn.generator !== undefined)
|
|
2267
|
+
continue;
|
|
2268
|
+
if (fn.asyncCacheGlobal !== undefined || fn.asyncCycleCacheGlobal !== undefined)
|
|
2269
|
+
continue;
|
|
2270
|
+
if (fn.returnType.kind === "dyn" || parks.has(fn.name))
|
|
2271
|
+
continue;
|
|
2272
|
+
if (!this.fnDefText.has(fn.name))
|
|
2273
|
+
continue;
|
|
2274
|
+
eligible.add(fn.name);
|
|
2275
|
+
}
|
|
2276
|
+
return eligible;
|
|
2277
|
+
}
|
|
2278
|
+
/** The spawn wrapper of a fiberless async function: runs the body on the
|
|
2279
|
+
* caller's stack between scr_async_inline_enter/leave and settles the
|
|
2280
|
+
* fresh promise from its outcome — the fiber trampoline's epilogue,
|
|
2281
|
+
* without the fiber. */
|
|
2282
|
+
emitFiberlessAsyncSpawn(fn, spawnParams) {
|
|
2283
|
+
const ret = fn.returnType;
|
|
2284
|
+
const retTy = this.llType(ret);
|
|
2285
|
+
const lifted = fn.captures !== undefined;
|
|
2286
|
+
this.declare(`declare void @scr_async_inline_enter(ptr)`);
|
|
2287
|
+
this.declare(`declare ptr @scr_async_inline_leave(ptr)`);
|
|
2288
|
+
this.declare(`declare void @scr_promise_reject_pending(ptr)`);
|
|
2289
|
+
if (lifted) {
|
|
2290
|
+
this.declare(`declare ptr @scr_closure_retain_v(ptr)`);
|
|
2291
|
+
this.declare(`declare void @scr_closure_release(ptr)`);
|
|
2292
|
+
}
|
|
2293
|
+
const bodyCall = `call ${retTy} @${mangleFunction(fn.name)}(${spawnParams.join(", ")})`;
|
|
2294
|
+
const out = [
|
|
2295
|
+
`define internal ptr @${mangleAsyncSpawn(fn.name)}(${spawnParams.join(", ")}) ${FN_ATTRS} { ; fiberless ${fn.name}`,
|
|
2296
|
+
`entry:`,
|
|
2297
|
+
` %frame = alloca { ptr, ptr }`,
|
|
2298
|
+
// The fiber path holds +1 on a lifted environment for the body's
|
|
2299
|
+
// duration (packed, released by the trampoline); keep that lifetime.
|
|
2300
|
+
...(lifted ? [` %env = call ptr @scr_closure_retain_v(ptr %a0)`] : []),
|
|
2301
|
+
` call void @scr_async_inline_enter(ptr %frame)`,
|
|
2302
|
+
retTy === "void" ? ` ${bodyCall}` : ` %r = ${bodyCall}`,
|
|
2303
|
+
...(lifted ? [` call void @scr_closure_release(ptr %a0)`] : []),
|
|
2304
|
+
...this.pendingTestLines("%pend"),
|
|
2305
|
+
` %p = call ptr @scr_async_inline_leave(ptr %frame)`,
|
|
2306
|
+
` br i1 %pend, label %thrown, label %clean`,
|
|
2307
|
+
`clean:`,
|
|
2308
|
+
...this.asyncFulfillLines(ret, "%p"),
|
|
2309
|
+
` ret ptr %p`,
|
|
2310
|
+
`thrown:`,
|
|
2311
|
+
];
|
|
2312
|
+
if (ret.kind !== "void" && isRefCounted(ret)) {
|
|
2313
|
+
// An escaping throw means %r is the never-read dummy (NULL).
|
|
2314
|
+
out.push(` call void ${releaseSym(this.shapeHost, ret)}(ptr %r)`);
|
|
2315
|
+
}
|
|
2316
|
+
out.push(` call void @scr_promise_reject_pending(ptr %p)`, ` ret ptr %p`, `}`, ``);
|
|
2317
|
+
return out;
|
|
2318
|
+
}
|
|
1818
2319
|
/** Per-async-function machinery — async.ts's scaffolding, .ll
|
|
1819
2320
|
* flavored: an argument-pack struct type, a fiber trampoline (unpacks,
|
|
1820
2321
|
* frees the pack, runs the ordinary compiled body, settles the
|
|
@@ -1829,9 +2330,20 @@ export class LlEmitter {
|
|
|
1829
2330
|
this.declare(`declare void @llvm.coro.destroy(ptr)`);
|
|
1830
2331
|
out.push(`define void @scr_wasi_coro_resume(ptr %handle) ${FN_ATTRS} {`, `entry:`, ` call void @llvm.coro.resume(ptr %handle)`, ` ret void`, `}`, `define void @scr_wasi_coro_destroy(ptr %handle) ${FN_ATTRS} {`, `entry:`, ` call void @llvm.coro.destroy(ptr %handle)`, ` ret void`, `}`, ``);
|
|
1831
2332
|
}
|
|
2333
|
+
const fiberless = this.fiberlessAsyncFunctions();
|
|
1832
2334
|
for (const fn of this.mod.functions) {
|
|
1833
2335
|
if (fn.async !== true || fn.generator !== undefined)
|
|
1834
2336
|
continue;
|
|
2337
|
+
if (fiberless.has(fn.name)) {
|
|
2338
|
+
const fieldTys = [
|
|
2339
|
+
...(fn.captures !== undefined ? ["ptr"] : []),
|
|
2340
|
+
...fn.params.map((p) => this.llType(p.type)),
|
|
2341
|
+
];
|
|
2342
|
+
const params = fieldTys.map((ty, i) => `${ty} %a${i}`);
|
|
2343
|
+
for (const line of this.emitFiberlessAsyncSpawn(fn, params))
|
|
2344
|
+
out.push(line);
|
|
2345
|
+
continue;
|
|
2346
|
+
}
|
|
1835
2347
|
const { definitions, ret, tr, spawnParams, argPackLines } = this.emitArgPackAndTrampolinePrologue(fn);
|
|
1836
2348
|
for (const line of definitions)
|
|
1837
2349
|
out.push(line);
|
|
@@ -1848,35 +2360,9 @@ export class LlEmitter {
|
|
|
1848
2360
|
if (fn.captures !== undefined) {
|
|
1849
2361
|
this.declare(`declare void @scr_closure_release(ptr)`);
|
|
1850
2362
|
}
|
|
1851
|
-
tr.push(
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
this.declare(`declare void @scr_promise_fulfill_void(ptr)`);
|
|
1855
|
-
tr.push(` call void @scr_promise_fulfill_void(ptr %pr)`);
|
|
1856
|
-
break;
|
|
1857
|
-
case "f64":
|
|
1858
|
-
case "date":
|
|
1859
|
-
this.declare(`declare void @scr_promise_fulfill_f64(ptr, double)`);
|
|
1860
|
-
tr.push(` call void @scr_promise_fulfill_f64(ptr %pr, double %r)`);
|
|
1861
|
-
break;
|
|
1862
|
-
case "bool":
|
|
1863
|
-
this.declare(`declare void @scr_promise_fulfill_bool(ptr, i1 zeroext)`);
|
|
1864
|
-
tr.push(` call void @scr_promise_fulfill_bool(ptr %pr, i1 %r)`);
|
|
1865
|
-
break;
|
|
1866
|
-
case "string":
|
|
1867
|
-
this.declare(`declare void @scr_promise_fulfill_str(ptr, ptr)`);
|
|
1868
|
-
tr.push(` call void @scr_promise_fulfill_str(ptr %pr, ptr %r) ; moves in`);
|
|
1869
|
-
break;
|
|
1870
|
-
case "dyn":
|
|
1871
|
-
this.declare(`declare void @scr_promise_resolve_dyn(ptr, ptr)`);
|
|
1872
|
-
tr.push(` call void @scr_promise_resolve_dyn(ptr %pr, ptr %r) ; moves in`);
|
|
1873
|
-
break;
|
|
1874
|
-
default: {
|
|
1875
|
-
const v = vAdapters(this.shapeHost, ret);
|
|
1876
|
-
this.declare(`declare void @scr_promise_fulfill_ref(ptr, ptr, ptr, ptr, ptr)`);
|
|
1877
|
-
tr.push(` call void @scr_promise_fulfill_ref(ptr %pr, ptr %r, ptr ${v.retain}, ptr ${v.release}, ptr ${traceArg(this.shapeHost, ret)})`);
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
2363
|
+
tr.push(...this.pendingTestLines("%pend"), ` br i1 %pend, label %thrown, label %clean`, `clean:`, ` %pr = call ptr @scr_fiber_promise(ptr %self)`);
|
|
2364
|
+
for (const line of this.asyncFulfillLines(ret, "%pr"))
|
|
2365
|
+
tr.push(line);
|
|
1880
2366
|
tr.push(` ret void`, `thrown:`);
|
|
1881
2367
|
if (ret.kind !== "void" && isRefCounted(ret)) {
|
|
1882
2368
|
// An escaping throw means %r is the never-read dummy (NULL).
|
|
@@ -1983,7 +2469,7 @@ export class LlEmitter {
|
|
|
1983
2469
|
tr.push(` ret void`, `}`, ``);
|
|
1984
2470
|
}
|
|
1985
2471
|
else {
|
|
1986
|
-
tr.push(` %g = call ptr @scr_gen_of_fiber(ptr %self)`,
|
|
2472
|
+
tr.push(` %g = call ptr @scr_gen_of_fiber(ptr %self)`, ...this.pendingTestLines("%pend"), ` br i1 %pend, label %thrown, label %clean`, `clean:`);
|
|
1987
2473
|
switch (ret.kind) {
|
|
1988
2474
|
case "void":
|
|
1989
2475
|
tr.push(` br label %done ; void body: the done value is undefined (NONE)`);
|
|
@@ -2073,10 +2559,30 @@ export class LlEmitter {
|
|
|
2073
2559
|
return out;
|
|
2074
2560
|
}
|
|
2075
2561
|
// ── plumbing (frame and scope ownership) ──────
|
|
2562
|
+
/** A symbol derived from the constant's text rather than its first-use
|
|
2563
|
+
* index, so an edit that interns one more string leaves every other
|
|
2564
|
+
* constant's symbol, and the code referencing it, unchanged. This lets
|
|
2565
|
+
* native builds reuse the compiled partitions an edit does not touch. A
|
|
2566
|
+
* collision retries with a counter, still in first-use order. */
|
|
2567
|
+
contentSymbol(prefix, text) {
|
|
2568
|
+
for (let attempt = 0;; attempt++) {
|
|
2569
|
+
const digest = createHash("sha256")
|
|
2570
|
+
.update(attempt === 0 ? text : `${attempt}\0${text}`)
|
|
2571
|
+
.digest("hex");
|
|
2572
|
+
const symbol = `${prefix}${parseInt(digest.slice(0, 13), 16)}`;
|
|
2573
|
+
if (!this.contentSymbols.has(symbol)) {
|
|
2574
|
+
this.contentSymbols.add(symbol);
|
|
2575
|
+
return symbol;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2076
2579
|
internLiteral(text) {
|
|
2077
2580
|
let lit = this.literals.get(text);
|
|
2078
2581
|
if (!lit) {
|
|
2079
|
-
lit = {
|
|
2582
|
+
lit = {
|
|
2583
|
+
sym: this.contentSymbol("sc_lit_", text),
|
|
2584
|
+
len: Buffer.byteLength(text, "utf8"),
|
|
2585
|
+
};
|
|
2080
2586
|
this.literals.set(text, lit);
|
|
2081
2587
|
}
|
|
2082
2588
|
const name = `@${lit.sym}`;
|
|
@@ -2088,7 +2594,7 @@ export class LlEmitter {
|
|
|
2088
2594
|
cstr(text) {
|
|
2089
2595
|
let c = this.cstrs.get(text);
|
|
2090
2596
|
if (!c) {
|
|
2091
|
-
c = { sym:
|
|
2597
|
+
c = { sym: this.contentSymbol("sc_cs_", text), len: Buffer.byteLength(text, "utf8") };
|
|
2092
2598
|
this.cstrs.set(text, c);
|
|
2093
2599
|
}
|
|
2094
2600
|
return `@${c.sym}`;
|
|
@@ -2105,6 +2611,12 @@ export class LlEmitter {
|
|
|
2105
2611
|
if (!arm || !isUnitType(arm)) {
|
|
2106
2612
|
throw new InternalCompilerError(`llvm emitter bug: unit instance for non-unit arm ${tag} of ${unionId}`);
|
|
2107
2613
|
}
|
|
2614
|
+
const nullable = this.nullableUnions.get(unionId);
|
|
2615
|
+
if (nullable) {
|
|
2616
|
+
// NULL is the unit arm of a nullable union (`undefined` when it has
|
|
2617
|
+
// both); the `null` sentinel is immortal. Neither owns anything.
|
|
2618
|
+
return tag === nullable.nullTag ? NULLABLE_NULL : "null";
|
|
2619
|
+
}
|
|
2108
2620
|
const key = `${unionId}:${tag}`;
|
|
2109
2621
|
let sym = this.unitInstances.get(key);
|
|
2110
2622
|
if (!sym) {
|
|
@@ -2115,6 +2627,56 @@ export class LlEmitter {
|
|
|
2115
2627
|
this.immortalValues.add(name);
|
|
2116
2628
|
return name;
|
|
2117
2629
|
}
|
|
2630
|
+
/** The immortal ABSENT state of an undefined-armed record field slot
|
|
2631
|
+
* (IR fieldAbsent). */
|
|
2632
|
+
absentInstanceRef(unionId) {
|
|
2633
|
+
if (undefinedArmTag({ kind: "union", unionId }, this.unionsById) < 0) {
|
|
2634
|
+
throw new InternalCompilerError(`llvm emitter bug: absent state of ${unionId} without undefined`);
|
|
2635
|
+
}
|
|
2636
|
+
if (this.nullableUnions.has(unionId)) {
|
|
2637
|
+
this.needsNullableAbsent = true;
|
|
2638
|
+
this.immortalValues.add(NULLABLE_ABSENT);
|
|
2639
|
+
return NULLABLE_ABSENT;
|
|
2640
|
+
}
|
|
2641
|
+
let sym = this.absentInstances.get(unionId);
|
|
2642
|
+
if (!sym) {
|
|
2643
|
+
sym = `sc_absent_${this.absentInstances.size}`;
|
|
2644
|
+
this.absentInstances.set(unionId, sym);
|
|
2645
|
+
}
|
|
2646
|
+
const name = `@${sym}`;
|
|
2647
|
+
this.immortalValues.add(name);
|
|
2648
|
+
return name;
|
|
2649
|
+
}
|
|
2650
|
+
/** i1: a union value loaded from a field slot is the ABSENT state — the
|
|
2651
|
+
* undefined arm's tag with the absent payload marker. */
|
|
2652
|
+
fieldAbsentTest(value, unionId) {
|
|
2653
|
+
return this.fieldAbsentTestIn(this.B, value, unionId);
|
|
2654
|
+
}
|
|
2655
|
+
/** fieldAbsentTest in another block builder (helper generators). A
|
|
2656
|
+
* nullable union's ABSENT state is the module's immortal sentinel. */
|
|
2657
|
+
fieldAbsentTestIn(B, value, unionId) {
|
|
2658
|
+
if (this.nullableUnions.has(unionId)) {
|
|
2659
|
+
this.needsNullableAbsent = true;
|
|
2660
|
+
const t = B.tmp();
|
|
2661
|
+
B.line(`${t} = icmp eq ptr ${value}, ${NULLABLE_ABSENT}`);
|
|
2662
|
+
return t;
|
|
2663
|
+
}
|
|
2664
|
+
return emitFieldAbsentTest(B, value, undefinedArmTag({ kind: "union", unionId }, this.unionsById));
|
|
2665
|
+
}
|
|
2666
|
+
/** Loads a record field slot as a VALUE: an undefined-armed union slot
|
|
2667
|
+
* holding the ABSENT state surfaces as the ordinary undefined arm, so
|
|
2668
|
+
* absence never travels beyond its slot (a later `{ f: r.f }` is an
|
|
2669
|
+
* explicit, present property). */
|
|
2670
|
+
loadRecordField(ptr, t) {
|
|
2671
|
+
const v = this.loadField(ptr, t);
|
|
2672
|
+
if (t.kind !== "union" || undefinedArmTag(t, this.unionsById) < 0)
|
|
2673
|
+
return v;
|
|
2674
|
+
const absent = this.fieldAbsentTest(v, t.unionId);
|
|
2675
|
+
const unit = this.unitInstanceRef(t.unionId, undefinedArmTag(t, this.unionsById));
|
|
2676
|
+
const out = this.B.tmp();
|
|
2677
|
+
this.B.line(`${out} = select i1 ${absent}, ptr ${unit}, ptr ${v}`);
|
|
2678
|
+
return out;
|
|
2679
|
+
}
|
|
2118
2680
|
declare(decl) {
|
|
2119
2681
|
this.decls.add(decl);
|
|
2120
2682
|
}
|
|
@@ -2127,9 +2689,20 @@ export class LlEmitter {
|
|
|
2127
2689
|
throw new InternalCompilerError("llvm emitter bug: no active statement frame");
|
|
2128
2690
|
return frame;
|
|
2129
2691
|
}
|
|
2692
|
+
/** Whether a value is immortal: an interned constant, or a temp marked by
|
|
2693
|
+
* markImmortal. */
|
|
2694
|
+
isImmortal(name) {
|
|
2695
|
+
return this.immortalValues.has(name) || this.B.immortalTemps.has(name);
|
|
2696
|
+
}
|
|
2697
|
+
/** Record that a temp holds an immortal value (a choice between interned
|
|
2698
|
+
* literals): nothing owns it, and retains, releases and moves skip it. */
|
|
2699
|
+
markImmortal(v) {
|
|
2700
|
+
this.B.immortalTemps.add(v.name);
|
|
2701
|
+
return v;
|
|
2702
|
+
}
|
|
2130
2703
|
/** Registers an owned refcounted value on the current statement frame. */
|
|
2131
2704
|
own(v) {
|
|
2132
|
-
if (isRefCounted(v.type) && !this.
|
|
2705
|
+
if (isRefCounted(v.type) && !this.isImmortal(v.name))
|
|
2133
2706
|
this.currentFrame().push(v);
|
|
2134
2707
|
return v;
|
|
2135
2708
|
}
|
|
@@ -2141,7 +2714,7 @@ export class LlEmitter {
|
|
|
2141
2714
|
}
|
|
2142
2715
|
/** Strike a refcounted temp from its frame: ownership is being moved. */
|
|
2143
2716
|
moveTemp(v) {
|
|
2144
|
-
if (!isRefCounted(v.type) || this.
|
|
2717
|
+
if (!isRefCounted(v.type) || this.isImmortal(v.name))
|
|
2145
2718
|
return;
|
|
2146
2719
|
for (let i = this.frames.length - 1; i >= 0; i--) {
|
|
2147
2720
|
const idx = this.frames[i].findIndex((e) => e.name === v.name);
|
|
@@ -2155,7 +2728,7 @@ export class LlEmitter {
|
|
|
2155
2728
|
/** The retained (+1) read of a refcounted value — type-directed through
|
|
2156
2729
|
* the `_v` table (immortals skip, exactly the C retain calls). */
|
|
2157
2730
|
retainValue(name, type) {
|
|
2158
|
-
if (this.
|
|
2731
|
+
if (this.isImmortal(name))
|
|
2159
2732
|
return name;
|
|
2160
2733
|
const t = this.B.tmp();
|
|
2161
2734
|
this.B.line(`${t} = call ptr ${retainSym(this.shapeHost, type)}(ptr ${name})`);
|
|
@@ -2164,7 +2737,7 @@ export class LlEmitter {
|
|
|
2164
2737
|
/** The release call for one owned refcounted value — type-directed like
|
|
2165
2738
|
* releaseCallC (all runtime releases are NULL-tolerant). */
|
|
2166
2739
|
releaseValue(name, type) {
|
|
2167
|
-
if (this.
|
|
2740
|
+
if (this.isImmortal(name))
|
|
2168
2741
|
return;
|
|
2169
2742
|
this.B.line(`call void ${releaseSym(this.shapeHost, type)}(ptr ${name})`);
|
|
2170
2743
|
}
|
|
@@ -2174,8 +2747,7 @@ export class LlEmitter {
|
|
|
2174
2747
|
const t = this.B.tmp();
|
|
2175
2748
|
this.B.line(`${t} = load ptr, ptr ${v.name}`);
|
|
2176
2749
|
if (v.boxed) {
|
|
2177
|
-
this.
|
|
2178
|
-
this.B.line(`call void @scr_box_release(ptr ${t})`);
|
|
2750
|
+
this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
|
|
2179
2751
|
}
|
|
2180
2752
|
else {
|
|
2181
2753
|
this.releaseValue(t, v.type);
|
|
@@ -2190,9 +2762,22 @@ export class LlEmitter {
|
|
|
2190
2762
|
for (const e of scope) {
|
|
2191
2763
|
const t = this.B.tmp();
|
|
2192
2764
|
this.B.line(`${t} = load ptr, ptr ${e.slot}`);
|
|
2193
|
-
if (e.boxed) {
|
|
2194
|
-
|
|
2195
|
-
|
|
2765
|
+
if (e.boxed && e.lazy) {
|
|
2766
|
+
// Unreachable code after a terminator emits nothing; a new block
|
|
2767
|
+
// here would revive it.
|
|
2768
|
+
if (this.B.isTerminated())
|
|
2769
|
+
continue;
|
|
2770
|
+
const created = this.B.tmp();
|
|
2771
|
+
this.B.line(`${created} = icmp ne ptr ${t}, null`);
|
|
2772
|
+
const release = this.B.newLabel("lazy.release"), join = this.B.newLabel("lazy.release.j");
|
|
2773
|
+
this.B.condBr(created, release, join);
|
|
2774
|
+
this.B.startBlock(release);
|
|
2775
|
+
this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
|
|
2776
|
+
this.B.br(join);
|
|
2777
|
+
this.B.startBlock(join);
|
|
2778
|
+
}
|
|
2779
|
+
else if (e.boxed) {
|
|
2780
|
+
this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
|
|
2196
2781
|
}
|
|
2197
2782
|
else {
|
|
2198
2783
|
this.releaseValue(t, e.type); // runtime releases are NULL-tolerant
|
|
@@ -2242,8 +2827,11 @@ export class LlEmitter {
|
|
|
2242
2827
|
* function — and branch to the handler / return a dummy value (never
|
|
2243
2828
|
* read: callers of a may-throw function test the pending flag before
|
|
2244
2829
|
* using the result). Callers own the surrounding pending branch; a
|
|
2245
|
-
* `throw` unwinds unconditionally.
|
|
2246
|
-
|
|
2830
|
+
* `throw` unwinds unconditionally. `passThrough` names a scalar call
|
|
2831
|
+
* result of the function's own return type: with nothing to release, the
|
|
2832
|
+
* unwind returns it as the dummy, so a call in tail position keeps a single
|
|
2833
|
+
* return and stays eligible for tail-call and recursion elimination. */
|
|
2834
|
+
emitUnwind(passThrough) {
|
|
2247
2835
|
if (this.mod.workers && this.tryStack.length > 0) {
|
|
2248
2836
|
this.declare(`declare zeroext i1 @scr_context_stopping()`);
|
|
2249
2837
|
const stopped = this.B.tmp();
|
|
@@ -2272,13 +2860,13 @@ export class LlEmitter {
|
|
|
2272
2860
|
terminator = `br label %${this.currentWasiCoro.finalLabel}`;
|
|
2273
2861
|
}
|
|
2274
2862
|
else {
|
|
2275
|
-
const t = this.currentReturnType;
|
|
2863
|
+
const t = this.llType(this.currentReturnType);
|
|
2276
2864
|
terminator =
|
|
2277
|
-
t
|
|
2865
|
+
t === "void"
|
|
2278
2866
|
? "ret void"
|
|
2279
|
-
: t
|
|
2867
|
+
: t === "double"
|
|
2280
2868
|
? `ret double ${f64Lit(0)}`
|
|
2281
|
-
: t
|
|
2869
|
+
: t === "i1"
|
|
2282
2870
|
? "ret i1 false"
|
|
2283
2871
|
: "ret ptr null";
|
|
2284
2872
|
}
|
|
@@ -2293,6 +2881,11 @@ export class LlEmitter {
|
|
|
2293
2881
|
}
|
|
2294
2882
|
}
|
|
2295
2883
|
if (entries.length === 0) {
|
|
2884
|
+
if (passThrough !== undefined && !target && this.currentWasiCoro === null) {
|
|
2885
|
+
const t = this.llType(this.currentReturnType);
|
|
2886
|
+
if (t === "double" || t === "i1")
|
|
2887
|
+
terminator = `ret ${t} ${passThrough}`;
|
|
2888
|
+
}
|
|
2296
2889
|
this.B.terminate(terminator);
|
|
2297
2890
|
return;
|
|
2298
2891
|
}
|
|
@@ -2303,24 +2896,147 @@ export class LlEmitter {
|
|
|
2303
2896
|
}
|
|
2304
2897
|
this.B.br(cleanup.label);
|
|
2305
2898
|
}
|
|
2899
|
+
/** Instructions (two-space indented) that set the i1 `dest` to "an
|
|
2900
|
+
* exception is pending in the active context" — scr_exc_pending()'s
|
|
2901
|
+
* meaning — for the helper and adapter bodies that test it outside
|
|
2902
|
+
* emitPendingCheck. Executables read the runtime's exported active-cell
|
|
2903
|
+
* pointer and compare its i32 kind (offset 0) against SCR_EXC_NONE inline,
|
|
2904
|
+
* as emitPendingCheck does; the out-of-line call would be opaque to LLVM
|
|
2905
|
+
* because the runtime links as prebuilt objects. Libraries and worker
|
|
2906
|
+
* programs keep the runtime call, which is emitPendingCheck's path there
|
|
2907
|
+
* too (workers also observe cross-thread termination in it). */
|
|
2908
|
+
pendingTestLines(dest) {
|
|
2909
|
+
if (this.mod.lib !== undefined || this.mod.workers === true) {
|
|
2910
|
+
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
2911
|
+
return [` ${dest} = call zeroext i1 @scr_exc_pending()`];
|
|
2912
|
+
}
|
|
2913
|
+
this.declare(`@scr_exc_active = external global ptr`);
|
|
2914
|
+
return [
|
|
2915
|
+
` ${dest}.cell = load ptr, ptr @scr_exc_active`,
|
|
2916
|
+
` ${dest}.kind = load i32, ptr ${dest}.cell`,
|
|
2917
|
+
` ${dest} = icmp ne i32 ${dest}.kind, 0`,
|
|
2918
|
+
];
|
|
2919
|
+
}
|
|
2306
2920
|
/** The emitter contract for exceptions: after EVERY call that can throw
|
|
2307
2921
|
* (per the may-throw analysis), test the pending flag and unwind. The
|
|
2308
2922
|
* call's result temp must join its frame BEFORE this runs so the unwind
|
|
2309
|
-
* releases the dummy (NULL for refcounted kinds) harmlessly.
|
|
2310
|
-
|
|
2923
|
+
* releases the dummy (NULL for refcounted kinds) harmlessly. A scalar
|
|
2924
|
+
* call result of the function's return type may be passed as the unwind's
|
|
2925
|
+
* dummy value (see emitUnwind). */
|
|
2926
|
+
emitPendingCheck(passThrough) {
|
|
2927
|
+
const through = passThrough !== undefined &&
|
|
2928
|
+
this.llType(passThrough.type) === this.llType(this.currentReturnType) &&
|
|
2929
|
+
(passThrough.type.kind === "f64" || passThrough.type.kind === "bool")
|
|
2930
|
+
? passThrough.name
|
|
2931
|
+
: undefined;
|
|
2311
2932
|
const B = this.B;
|
|
2312
2933
|
if (B.isTerminated())
|
|
2313
2934
|
return;
|
|
2314
|
-
this.
|
|
2315
|
-
|
|
2316
|
-
|
|
2935
|
+
if (this.mod.lib !== undefined) {
|
|
2936
|
+
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
2937
|
+
const p = B.tmp();
|
|
2938
|
+
B.line(`${p} = call zeroext i1 @scr_exc_pending()`);
|
|
2939
|
+
const lu = B.newLabel("exc.u");
|
|
2940
|
+
const lk = B.newLabel("exc.k");
|
|
2941
|
+
B.condBr(p, lu, lk);
|
|
2942
|
+
B.startBlock(lu);
|
|
2943
|
+
this.emitUnwind(through);
|
|
2944
|
+
B.startBlock(lk);
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
// Executables test the active exception cell's kind inline (its first
|
|
2948
|
+
// field). Worker programs test the process-wide alert word instead: it
|
|
2949
|
+
// is nonzero while any script thread may have an exception pending in
|
|
2950
|
+
// its active cell or has been asked to stop, so the common case is one
|
|
2951
|
+
// ordinary global load (a thread-local access is a call on Darwin) and
|
|
2952
|
+
// the out-of-line scr_exc_pending answers for this thread, observing
|
|
2953
|
+
// cancellation and reinstalling the termination sentinel.
|
|
2954
|
+
this.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
2955
|
+
let word;
|
|
2956
|
+
if (this.mod.workers === true) {
|
|
2957
|
+
this.declare(`@scr_exc_alert = external global i32, align 64`);
|
|
2958
|
+
word = B.tmp();
|
|
2959
|
+
B.line(`${word} = load atomic i32, ptr @scr_exc_alert monotonic, align 64`);
|
|
2960
|
+
}
|
|
2961
|
+
else {
|
|
2962
|
+
this.declare(`@scr_exc_active = external global ptr`);
|
|
2963
|
+
let cell;
|
|
2964
|
+
if (B.excCellInvariant) {
|
|
2965
|
+
// Fiber switches restore the active cell before control returns to a
|
|
2966
|
+
// synchronous frame, so its pointer is one value for the whole call:
|
|
2967
|
+
// load it once in the entry block and test only the kind here.
|
|
2968
|
+
if (B.excCell === null) {
|
|
2969
|
+
B.excCell = "%exc.cell";
|
|
2970
|
+
B.entryAllocas.push(`${B.excCell} = load ptr, ptr @scr_exc_active`);
|
|
2971
|
+
}
|
|
2972
|
+
cell = B.excCell;
|
|
2973
|
+
}
|
|
2974
|
+
else {
|
|
2975
|
+
cell = B.tmp();
|
|
2976
|
+
B.line(`${cell} = load ptr, ptr @scr_exc_active`);
|
|
2977
|
+
}
|
|
2978
|
+
word = B.tmp();
|
|
2979
|
+
B.line(`${word} = load i32, ptr ${cell}`);
|
|
2980
|
+
}
|
|
2981
|
+
const hit = B.tmp();
|
|
2982
|
+
const cold = B.tmp();
|
|
2983
|
+
B.line(`${hit} = icmp ne i32 ${word}, 0`);
|
|
2984
|
+
B.line(`${cold} = call i1 @llvm.expect.i1(i1 ${hit}, i1 false)`);
|
|
2317
2985
|
const lu = B.newLabel("exc.u");
|
|
2318
2986
|
const lk = B.newLabel("exc.k");
|
|
2319
|
-
|
|
2987
|
+
if (this.mod.workers === true) {
|
|
2988
|
+
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
2989
|
+
const slow = B.newLabel("exc.s");
|
|
2990
|
+
B.condBr(cold, slow, lk);
|
|
2991
|
+
B.startBlock(slow);
|
|
2992
|
+
const p = B.tmp();
|
|
2993
|
+
B.line(`${p} = call zeroext i1 @scr_exc_pending()`);
|
|
2994
|
+
B.condBr(p, lu, lk);
|
|
2995
|
+
}
|
|
2996
|
+
else {
|
|
2997
|
+
B.condBr(cold, lu, lk);
|
|
2998
|
+
}
|
|
2320
2999
|
B.startBlock(lu);
|
|
2321
|
-
this.emitUnwind();
|
|
3000
|
+
this.emitUnwind(through);
|
|
2322
3001
|
B.startBlock(lk);
|
|
2323
3002
|
}
|
|
3003
|
+
/** After a runtime call that ALWAYS leaves an exception pending (the
|
|
3004
|
+
* compiler-resolved Node throws): the pending test would always answer
|
|
3005
|
+
* true, so unwind unconditionally. Emission continues in a block with no
|
|
3006
|
+
* predecessors so the caller's remaining instructions stay well-formed;
|
|
3007
|
+
* LLVM deletes it. */
|
|
3008
|
+
emitAlwaysPendingUnwind() {
|
|
3009
|
+
const B = this.B;
|
|
3010
|
+
if (B.isTerminated())
|
|
3011
|
+
return;
|
|
3012
|
+
this.emitUnwind();
|
|
3013
|
+
B.startBlock(B.newLabel("exc.dead"));
|
|
3014
|
+
}
|
|
3015
|
+
/** A recursive function's entry check: below the running stack's guard,
|
|
3016
|
+
* throw Node's RangeError and unwind like any other pending exception. */
|
|
3017
|
+
emitStackCheck() {
|
|
3018
|
+
const B = this.B;
|
|
3019
|
+
const tl = this.mod.workers === true ? "thread_local " : "";
|
|
3020
|
+
this.declare(`@scr_stack_guard = external ${tl}global ptr`);
|
|
3021
|
+
this.declare(`declare ptr @llvm.stacksave.p0()`);
|
|
3022
|
+
this.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
3023
|
+
this.declare(`declare void @scr_stack_overflow()`);
|
|
3024
|
+
const sp = B.tmp();
|
|
3025
|
+
const guard = B.tmp();
|
|
3026
|
+
const low = B.tmp();
|
|
3027
|
+
const cold = B.tmp();
|
|
3028
|
+
B.line(`${sp} = call ptr @llvm.stacksave.p0()`);
|
|
3029
|
+
B.line(`${guard} = load ptr, ptr @scr_stack_guard`);
|
|
3030
|
+
B.line(`${low} = icmp ult ptr ${sp}, ${guard}`);
|
|
3031
|
+
B.line(`${cold} = call i1 @llvm.expect.i1(i1 ${low}, i1 false)`);
|
|
3032
|
+
const over = B.newLabel("stack.over");
|
|
3033
|
+
const ok = B.newLabel("stack.ok");
|
|
3034
|
+
B.condBr(cold, over, ok);
|
|
3035
|
+
B.startBlock(over);
|
|
3036
|
+
B.line(`call void @scr_stack_overflow()`);
|
|
3037
|
+
this.emitUnwind();
|
|
3038
|
+
B.startBlock(ok);
|
|
3039
|
+
}
|
|
2324
3040
|
workerLoopBudget() {
|
|
2325
3041
|
if (!this.mod.workers)
|
|
2326
3042
|
return null;
|
|
@@ -2418,26 +3134,27 @@ export class LlEmitter {
|
|
|
2418
3134
|
const def = this.unionsById.get(v.type.unionId);
|
|
2419
3135
|
if (!def)
|
|
2420
3136
|
throw new InternalCompilerError(`llvm emitter bug: truthiness of unknown union ${v.type.unionId}`);
|
|
3137
|
+
const nullable = this.nullableUnions.get(def.id);
|
|
3138
|
+
// Object arms are always truthy: present iff neither unit encoding.
|
|
3139
|
+
if (nullable && isObjectArm(nullable))
|
|
3140
|
+
return emitNullablePresent(B, nullable, v.name);
|
|
3141
|
+
const unionId = def.id;
|
|
2421
3142
|
const slot = B.slot();
|
|
2422
3143
|
B.entryAllocas.push(`${slot} = alloca i1`);
|
|
2423
3144
|
const join = B.newLabel("ut.j");
|
|
2424
3145
|
this.unionTagSwitch(v.name, def, (arm) => {
|
|
2425
3146
|
let valueName = "false";
|
|
2426
3147
|
if (arm.kind === "f64" || arm.kind === "procStream") {
|
|
2427
|
-
valueName =
|
|
2428
|
-
this.declare(`declare double @scr_union_get_f64(ptr)`);
|
|
2429
|
-
B.line(`${valueName} = call double @scr_union_get_f64(ptr ${v.name})`);
|
|
3148
|
+
valueName = this.unionGetF64(v.name);
|
|
2430
3149
|
}
|
|
2431
3150
|
else if (arm.kind === "bool") {
|
|
2432
|
-
valueName =
|
|
2433
|
-
this.declare(`declare zeroext i1 @scr_union_get_bool(ptr)`);
|
|
2434
|
-
B.line(`${valueName} = call zeroext i1 @scr_union_get_bool(ptr ${v.name})`);
|
|
3151
|
+
valueName = this.unionGetBool(v.name);
|
|
2435
3152
|
}
|
|
2436
3153
|
else if (arm.kind === "string") {
|
|
2437
|
-
valueName = this.unionPeek(v.name);
|
|
3154
|
+
valueName = this.unionPeek(v.name, unionId);
|
|
2438
3155
|
}
|
|
2439
3156
|
else if (arm.kind === "bigint" || arm.kind === "dyn" || arm.kind === "jsval") {
|
|
2440
|
-
valueName = this.unionPeek(v.name);
|
|
3157
|
+
valueName = this.unionPeek(v.name, unionId);
|
|
2441
3158
|
}
|
|
2442
3159
|
const truthy = this.truthyOf(arm.kind, valueName, true);
|
|
2443
3160
|
B.line(`store i1 ${truthy}, ptr ${slot}`);
|
|
@@ -2543,22 +3260,22 @@ export class LlEmitter {
|
|
|
2543
3260
|
}
|
|
2544
3261
|
}
|
|
2545
3262
|
// ── union plumbing ──────────────────────────────────────────────────────
|
|
2546
|
-
/**
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
3263
|
+
/** A union value's tag (i32): the box's tag word, or the NULL test of a
|
|
3264
|
+
* nullable union's pointer. */
|
|
3265
|
+
unionTag(uName, unionId) {
|
|
3266
|
+
return emitUnionTag(this.B, this.nullableUnions.get(unionId), uName);
|
|
3267
|
+
}
|
|
3268
|
+
/** Guard for helpers that build or read a union's `%ScrUnion` box
|
|
3269
|
+
* directly (library-shaped unions with fixed arms): a nullable union
|
|
3270
|
+
* reaching one is an emitter bug, never a silent miscompile. */
|
|
3271
|
+
requireBoxedUnion(unionId) {
|
|
3272
|
+
if (this.nullableUnions.has(unionId))
|
|
3273
|
+
throw new InternalCompilerError(`llvm emitter bug: nullable union ${unionId} reached a tagged-box helper`);
|
|
2553
3274
|
}
|
|
2554
3275
|
/** The BORROWED payload pointer of a ref arm (scr_union_peek inlined —
|
|
2555
|
-
* the runtime's is a static inline). */
|
|
2556
|
-
unionPeek(uName) {
|
|
2557
|
-
|
|
2558
|
-
const t = this.B.tmp();
|
|
2559
|
-
this.B.line(`${p} = getelementptr inbounds %ScrUnion, ptr ${uName}, i64 0, i32 5`);
|
|
2560
|
-
this.B.line(`${t} = load ptr, ptr ${p}`);
|
|
2561
|
-
return t;
|
|
3276
|
+
* the runtime's is a static inline); a nullable union is its payload. */
|
|
3277
|
+
unionPeek(uName, unionId) {
|
|
3278
|
+
return emitUnionPeek(this.B, this.nullableUnions.get(unionId), uName);
|
|
2562
3279
|
}
|
|
2563
3280
|
/** Emits `switch` over a union's tag with one block per arm; each arm
|
|
2564
3281
|
* body must TERMINATE its block (the callers branch to a join). The
|
|
@@ -2566,7 +3283,7 @@ export class LlEmitter {
|
|
|
2566
3283
|
* reads can group equivalent storage prefixes and emit one representative. */
|
|
2567
3284
|
unionTagSwitch(uName, def, arm, fieldGroups) {
|
|
2568
3285
|
const B = this.B;
|
|
2569
|
-
const tag = this.unionTag(uName);
|
|
3286
|
+
const tag = this.unionTag(uName, def.id);
|
|
2570
3287
|
const bad = B.newLabel("u.bad");
|
|
2571
3288
|
const labels = [];
|
|
2572
3289
|
if (fieldGroups) {
|
|
@@ -2610,29 +3327,46 @@ export class LlEmitter {
|
|
|
2610
3327
|
B.terminate(`unreachable`);
|
|
2611
3328
|
}
|
|
2612
3329
|
/** The +1 extraction of a union's single narrowed arm (unionNarrow /
|
|
2613
|
-
* the nullish-family reads): scalars via
|
|
3330
|
+
* the nullish-family reads): scalars via inline payload loads, ref arms
|
|
2614
3331
|
* a retained peek. */
|
|
2615
|
-
unionExtract(uName, arm) {
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
this.
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
}
|
|
2629
|
-
return
|
|
3332
|
+
unionExtract(uName, unionId, arm) {
|
|
3333
|
+
if (arm.kind === "f64" || arm.kind === "procStream")
|
|
3334
|
+
return this.unionGetF64(uName);
|
|
3335
|
+
if (arm.kind === "bool")
|
|
3336
|
+
return this.unionGetBool(uName);
|
|
3337
|
+
return this.retainValue(this.unionPeek(uName, unionId), arm);
|
|
3338
|
+
}
|
|
3339
|
+
/** A scalar f64 arm's payload (scr_union_get_f64 inlined). Inline loads
|
|
3340
|
+
* keep stack boxes free of escaping calls, so LLVM can scalarize them. */
|
|
3341
|
+
unionGetF64(uName) {
|
|
3342
|
+
const p = this.B.tmp();
|
|
3343
|
+
const t = this.B.tmp();
|
|
3344
|
+
this.B.line(`${p} = getelementptr inbounds %ScrUnion, ptr ${uName}, i64 0, i32 5`);
|
|
3345
|
+
this.B.line(`${t} = load double, ptr ${p}`);
|
|
3346
|
+
return t;
|
|
3347
|
+
}
|
|
3348
|
+
/** A bool arm's payload (scr_union_get_bool inlined: nonzero slot). */
|
|
3349
|
+
unionGetBool(uName) {
|
|
3350
|
+
const p = this.B.tmp();
|
|
3351
|
+
const bits = this.B.tmp();
|
|
3352
|
+
const t = this.B.tmp();
|
|
3353
|
+
this.B.line(`${p} = getelementptr inbounds %ScrUnion, ptr ${uName}, i64 0, i32 5`);
|
|
3354
|
+
this.B.line(`${bits} = load i64, ptr ${p}`);
|
|
3355
|
+
this.B.line(`${t} = icmp ne i64 ${bits}, 0`);
|
|
3356
|
+
return t;
|
|
2630
3357
|
}
|
|
2631
3358
|
/** Constructs a union box around an OWNED (+1, already moved) value —
|
|
2632
3359
|
* the scr_union_new_* dispatch of unionWrap and the wrap-into-join
|
|
2633
3360
|
* sites (shift). */
|
|
2634
|
-
unionNewOwned(tag, v) {
|
|
3361
|
+
unionNewOwned(unionId, tag, v) {
|
|
2635
3362
|
const B = this.B;
|
|
3363
|
+
const nullable = this.nullableUnions.get(unionId);
|
|
3364
|
+
if (nullable) {
|
|
3365
|
+
// The owned payload IS the nullable union's value.
|
|
3366
|
+
if (tag !== nullable.refTag)
|
|
3367
|
+
throw new InternalCompilerError(`llvm emitter bug: payload wrap into unit arm ${tag} of ${unionId}`);
|
|
3368
|
+
return v.name;
|
|
3369
|
+
}
|
|
2636
3370
|
const t = B.tmp();
|
|
2637
3371
|
if (v.type.kind === "f64" || v.type.kind === "procStream") {
|
|
2638
3372
|
this.declare(`declare ptr @scr_union_new_f64(i32, double)`);
|
|
@@ -2775,6 +3509,42 @@ export class LlEmitter {
|
|
|
2775
3509
|
B.line(`${z} = zext i1 ${value} to i8`);
|
|
2776
3510
|
B.line(`store i8 ${z}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
|
|
2777
3511
|
}
|
|
3512
|
+
/** Reads an int32-specialized class field: the i32 slot is the value,
|
|
3513
|
+
* and its widening is the exact double every program observation sees. */
|
|
3514
|
+
loadInt32Field(ptr, type) {
|
|
3515
|
+
const B = this.B;
|
|
3516
|
+
const raw = B.tmp();
|
|
3517
|
+
const number = B.tmp();
|
|
3518
|
+
B.line(`${raw} = load i32, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
|
|
3519
|
+
B.line(`${number} = sitofp i32 ${raw} to double`);
|
|
3520
|
+
return {
|
|
3521
|
+
name: number,
|
|
3522
|
+
type,
|
|
3523
|
+
uint32: raw,
|
|
3524
|
+
integer: { name: raw, type: "i32", signed: true, range: INT32_RANGE },
|
|
3525
|
+
};
|
|
3526
|
+
}
|
|
3527
|
+
/** Stores a value the whole-program analysis proved to be an int32 (never
|
|
3528
|
+
* -0, NaN or fractional) into an int32-specialized class field. */
|
|
3529
|
+
storeInt32Field(ptr, value, expr) {
|
|
3530
|
+
if (!this.int32Slots.provenWrites.has(expr))
|
|
3531
|
+
throw new InternalCompilerError("llvm emitter bug: unproven int32 field write");
|
|
3532
|
+
const B = this.B;
|
|
3533
|
+
let bits;
|
|
3534
|
+
if (value.integer?.type === "i32")
|
|
3535
|
+
bits = value.integer.name;
|
|
3536
|
+
else if (value.integer) {
|
|
3537
|
+
bits = B.tmp();
|
|
3538
|
+
B.line(`${bits} = trunc i64 ${value.integer.name} to i32`);
|
|
3539
|
+
}
|
|
3540
|
+
else if (value.uint32 !== undefined)
|
|
3541
|
+
bits = value.uint32;
|
|
3542
|
+
else {
|
|
3543
|
+
bits = B.tmp();
|
|
3544
|
+
B.line(`${bits} = fptosi double ${value.name} to i32`);
|
|
3545
|
+
}
|
|
3546
|
+
B.line(`store i32 ${bits}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
|
|
3547
|
+
}
|
|
2778
3548
|
// ── bindings ────────────────────────────────────────────────────────────
|
|
2779
3549
|
/** A binding's storage: a module global, a plain local slot, or a boxed
|
|
2780
3550
|
* local (the slot holds the capture BOX; access goes through it). */
|
|
@@ -2806,17 +3576,26 @@ export class LlEmitter {
|
|
|
2806
3576
|
this.B.line(`${b} = load ptr, ptr ${slot}`);
|
|
2807
3577
|
return b;
|
|
2808
3578
|
}
|
|
3579
|
+
/** The payload slot of a capture box (ScrBox.slot, field 5). */
|
|
3580
|
+
boxSlot(box) {
|
|
3581
|
+
const slot = this.B.tmp();
|
|
3582
|
+
this.B.line(`${slot} = getelementptr inbounds %ScrBox, ptr ${box}, i32 0, i32 5`);
|
|
3583
|
+
return slot;
|
|
3584
|
+
}
|
|
2809
3585
|
boxGet(box, t) {
|
|
2810
3586
|
const B = this.B;
|
|
2811
3587
|
const acc = boxAccess(t);
|
|
2812
3588
|
const r = B.tmp();
|
|
3589
|
+
// Scalar payloads are read in place (scr_box_get_f64/bool are a
|
|
3590
|
+
// memcpy and a nonzero test of the same slot), so LLVM can keep a
|
|
3591
|
+
// captured number in a register across a loop of calls.
|
|
2813
3592
|
if (acc === "f64") {
|
|
2814
|
-
|
|
2815
|
-
B.line(`${r} = call double @scr_box_get_f64(ptr ${box})`);
|
|
3593
|
+
B.line(`${r} = load double, ptr ${this.boxSlot(box)}`);
|
|
2816
3594
|
}
|
|
2817
3595
|
else if (acc === "bool") {
|
|
2818
|
-
|
|
2819
|
-
B.line(`${
|
|
3596
|
+
const raw = B.tmp();
|
|
3597
|
+
B.line(`${raw} = load i64, ptr ${this.boxSlot(box)}`);
|
|
3598
|
+
B.line(`${r} = icmp ne i64 ${raw}, 0`);
|
|
2820
3599
|
}
|
|
2821
3600
|
else {
|
|
2822
3601
|
this.declare(`declare ptr @scr_box_get_ref(ptr)`);
|
|
@@ -2829,18 +3608,45 @@ export class LlEmitter {
|
|
|
2829
3608
|
const B = this.B;
|
|
2830
3609
|
const acc = boxAccess(t);
|
|
2831
3610
|
if (acc === "f64") {
|
|
2832
|
-
|
|
2833
|
-
B.line(`call void @scr_box_set_f64(ptr ${box}, double ${value})`);
|
|
3611
|
+
B.line(`store double ${value}, ptr ${this.boxSlot(box)}`);
|
|
2834
3612
|
}
|
|
2835
3613
|
else if (acc === "bool") {
|
|
2836
|
-
|
|
2837
|
-
B.line(
|
|
3614
|
+
const raw = B.tmp();
|
|
3615
|
+
B.line(`${raw} = zext i1 ${value} to i64`);
|
|
3616
|
+
B.line(`store i64 ${raw}, ptr ${this.boxSlot(box)}`);
|
|
2838
3617
|
}
|
|
2839
3618
|
else {
|
|
2840
3619
|
this.declare(`declare void @scr_box_set_ref(ptr, ptr)`);
|
|
2841
3620
|
B.line(`call void @scr_box_set_ref(ptr ${box}, ptr ${value})`);
|
|
2842
3621
|
}
|
|
2843
3622
|
}
|
|
3623
|
+
/** The box a new environment captures for `localId` (borrowed). An
|
|
3624
|
+
* unchanged parameter gets one box on first capture, holding its own
|
|
3625
|
+
* reference to the value; the function scope releases the cached box. */
|
|
3626
|
+
captureBox(localId) {
|
|
3627
|
+
const cache = this.lazyCaptureBoxes.get(localId);
|
|
3628
|
+
if (cache === undefined)
|
|
3629
|
+
return this.loadBox(`%${mangleLocal(localId)}`);
|
|
3630
|
+
const B = this.B;
|
|
3631
|
+
const type = this.currentLocals.get(localId).type;
|
|
3632
|
+
const current = this.loadBox(cache);
|
|
3633
|
+
const missing = B.tmp();
|
|
3634
|
+
B.line(`${missing} = icmp eq ptr ${current}, null`);
|
|
3635
|
+
const create = B.newLabel("lazy.box"), join = B.newLabel("lazy.box.j");
|
|
3636
|
+
B.condBr(missing, create, join);
|
|
3637
|
+
B.startBlock(create);
|
|
3638
|
+
const stack = this.lazyStackBoxes.get(localId);
|
|
3639
|
+
const box = stack ?? B.tmp();
|
|
3640
|
+
if (!stack)
|
|
3641
|
+
B.line(`${box} = ${boxNewCall(this.shapeHost, type)}`);
|
|
3642
|
+
const value = B.tmp();
|
|
3643
|
+
B.line(`${value} = load ${this.llType(type)}, ptr %${mangleLocal(localId)}`);
|
|
3644
|
+
this.boxSet(box, type, isRefCounted(type) ? this.retainValue(value, type) : value);
|
|
3645
|
+
B.line(`store ptr ${box}, ptr ${cache}`);
|
|
3646
|
+
B.br(join);
|
|
3647
|
+
B.startBlock(join);
|
|
3648
|
+
return this.loadBox(cache);
|
|
3649
|
+
}
|
|
2844
3650
|
retainBox(box) {
|
|
2845
3651
|
this.needsRetainBox = true;
|
|
2846
3652
|
const t = this.B.tmp();
|
|
@@ -2885,6 +3691,12 @@ export class LlEmitter {
|
|
|
2885
3691
|
B.line(`${empty} = icmp eq ptr ${value}, null`);
|
|
2886
3692
|
this.throwIfUninitialized(empty, name);
|
|
2887
3693
|
}
|
|
3694
|
+
/** Throws the TDZ ReferenceError for `name` unless the i1 `flag` is set. */
|
|
3695
|
+
checkInitializedFlag(flag, name) {
|
|
3696
|
+
const empty = this.B.tmp();
|
|
3697
|
+
this.B.line(`${empty} = xor i1 ${flag}, true`);
|
|
3698
|
+
this.throwIfUninitialized(empty, name);
|
|
3699
|
+
}
|
|
2888
3700
|
throwIfUninitialized(empty, name) {
|
|
2889
3701
|
const B = this.B;
|
|
2890
3702
|
const lt = B.newLabel("tdz.t");
|
|
@@ -3046,6 +3858,9 @@ export class LlEmitter {
|
|
|
3046
3858
|
emitFunction(fn) {
|
|
3047
3859
|
const B = new BlockBuilder();
|
|
3048
3860
|
this.B = B;
|
|
3861
|
+
// Fiber switches restore the active exception cell before control
|
|
3862
|
+
// returns to a synchronous frame (emitPendingCheck).
|
|
3863
|
+
B.excCellInvariant = fn.async !== true && fn.generator === undefined;
|
|
3049
3864
|
this.debugScope = this.debug?.function(fn) ?? null;
|
|
3050
3865
|
B.debugLocation = this.debug?.location(fn.loc, this.debugScope) ?? null;
|
|
3051
3866
|
this.frames = [];
|
|
@@ -3053,13 +3868,23 @@ export class LlEmitter {
|
|
|
3053
3868
|
this.unwindCleanups.clear();
|
|
3054
3869
|
this.jumpTargets = [];
|
|
3055
3870
|
this.currentLocals = new Map(fn.locals.map((l) => [l.id, l]));
|
|
3871
|
+
this.lazyCaptureBoxes.clear();
|
|
3872
|
+
this.lazyStackBoxes.clear();
|
|
3873
|
+
// An unchanged captured parameter keeps its value in a plain slot; the
|
|
3874
|
+
// binding is boxed only for closures (captureBox).
|
|
3875
|
+
const lazyParameters = this.lazyCaptures.parameters(fn);
|
|
3876
|
+
for (const id of lazyParameters) {
|
|
3877
|
+
const { boxed: _boxed, ...plain } = this.currentLocals.get(id);
|
|
3878
|
+
this.currentLocals.set(id, plain);
|
|
3879
|
+
this.lazyCaptureBoxes.set(id, "");
|
|
3880
|
+
}
|
|
3056
3881
|
this.currentConstantCallbacks =
|
|
3057
3882
|
this.debug === null ? (this.constantCallbacks.get(fn.name) ?? new Map()) : new Map();
|
|
3058
3883
|
// Preserve concrete source bindings for debugger inspection. Suspended
|
|
3059
3884
|
// functions keep the established array lifetime across continuations.
|
|
3060
3885
|
this.streamingSplitsEnabled = this.debug === null && !fn.async && !fn.generator;
|
|
3061
3886
|
this.stackCallbacks.reset(this.streamingSplitsEnabled);
|
|
3062
|
-
this.stackCaptures.reset(fn, this.streamingSplitsEnabled);
|
|
3887
|
+
this.stackCaptures.reset(fn, this.streamingSplitsEnabled, lazyParameters);
|
|
3063
3888
|
this.privateSplitLocals = this.streamingSplitsEnabled ? findPrivateSplitLocals(fn) : new Map();
|
|
3064
3889
|
this.storedSplits.clear();
|
|
3065
3890
|
this.splitSpans.clear();
|
|
@@ -3068,11 +3893,21 @@ export class LlEmitter {
|
|
|
3068
3893
|
const numericFn = withInitializerBindings(fn, initializerBindings);
|
|
3069
3894
|
this.numericLocals = new Map(numericFn.locals.map((l) => [l.id, l]));
|
|
3070
3895
|
this.borrowedParameters.clear();
|
|
3896
|
+
this.reboundOwnerSlots.clear();
|
|
3897
|
+
this.projectedParameters.clear();
|
|
3898
|
+
const projectedParameterIndexes = this.callLifetimes.parameters.get(fn.name);
|
|
3899
|
+
if (projectedParameterIndexes)
|
|
3900
|
+
for (const index of projectedParameterIndexes)
|
|
3901
|
+
this.projectedParameters.add(fn.params[index].localId);
|
|
3071
3902
|
this.stableCallBindings = this.callLifetimes.bindings.get(fn.name) ?? new Set();
|
|
3072
3903
|
const borrowedParameterIndexes = this.callLifetimes.borrowed.get(fn.name);
|
|
3073
3904
|
if (borrowedParameterIndexes) {
|
|
3905
|
+
// Rebound walk parameters borrow too, but only unchanged ones may
|
|
3906
|
+
// serve as stable owners for call arguments and aliases.
|
|
3074
3907
|
for (const index of borrowedParameterIndexes)
|
|
3075
|
-
this.
|
|
3908
|
+
if (!this.walkBorrowsByFunction.get(fn.name)?.has(fn.params[index].localId) &&
|
|
3909
|
+
!this.reboundByFunction.get(fn.name)?.has(fn.params[index].localId))
|
|
3910
|
+
this.borrowedParameters.add(fn.params[index].localId);
|
|
3076
3911
|
}
|
|
3077
3912
|
this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
|
|
3078
3913
|
this.integerLoopBindings.clear();
|
|
@@ -3093,17 +3928,19 @@ export class LlEmitter {
|
|
|
3093
3928
|
this.callArrayReads = findCallArrayReads(fn, this.optionalArrayReads, this.referenceEffects.functions, this.callLifetimes);
|
|
3094
3929
|
this.scalarStringSlices =
|
|
3095
3930
|
this.debug === null ? findScalarStringSlices(fn, this.callLifetimes) : new Map();
|
|
3096
|
-
this.mapReadLifetimes = findMapReadLifetimes(fn, this.
|
|
3097
|
-
this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.
|
|
3098
|
-
this.localUnionStorageProofs = findLocalUnionStorage(fn, this.callLifetimes, this.
|
|
3931
|
+
this.mapReadLifetimes = findMapReadLifetimes(fn, this.boxedUnionsById, this.callLifetimes);
|
|
3932
|
+
this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.boxedUnionsById);
|
|
3933
|
+
this.localUnionStorageProofs = findLocalUnionStorage(fn, this.callLifetimes, this.boxedUnionsById, (e) => this.nullableFieldGet(e) !== null);
|
|
3099
3934
|
this.localUnionStorage = new Map();
|
|
3100
|
-
this.
|
|
3935
|
+
this.walkBorrows = this.walkBorrowsByFunction.get(fn.name) ?? new Set();
|
|
3936
|
+
this.integerRanges = analyzeIntegerRanges(numericFn, this.int32Slots.facts(fn.name));
|
|
3101
3937
|
this.bytesBounds = findBytesBounds(numericFn, this.integerRanges);
|
|
3102
3938
|
this.chainSlots.clear();
|
|
3103
3939
|
this.finallyStack = [];
|
|
3104
3940
|
this.tryStack = [];
|
|
3105
3941
|
this.currentReturnType = fn.returnType;
|
|
3106
3942
|
this.currentGenerator = fn.generator ?? null;
|
|
3943
|
+
this.currentBorrowedReturn = this.borrowedReturns.has(fn.name);
|
|
3107
3944
|
this.currentWasiCoro = null;
|
|
3108
3945
|
this.logArgSlots = 0;
|
|
3109
3946
|
if (this.wasi && (fn.async === true || fn.generator !== undefined)) {
|
|
@@ -3154,7 +3991,7 @@ export class LlEmitter {
|
|
|
3154
3991
|
B.line(`${value} = load double, ptr @${mangleGlobal(global.id)}`);
|
|
3155
3992
|
this.storeIntegerView(global.id, { name: value, type: global.type });
|
|
3156
3993
|
}
|
|
3157
|
-
for (const local of
|
|
3994
|
+
for (const local of this.currentLocals.values()) {
|
|
3158
3995
|
// Boxed locals' slots hold their capture BOX (a ptr); captured
|
|
3159
3996
|
// (env-borrowed) locals bind the incoming box below. A caught-typed
|
|
3160
3997
|
// local is a catch binding: its slot holds the ScrCaught snapshot
|
|
@@ -3203,6 +4040,26 @@ export class LlEmitter {
|
|
|
3203
4040
|
// params (callees own their params — callers passed +1). Boxed params
|
|
3204
4041
|
// allocate the shared binding and move the raw value in.
|
|
3205
4042
|
const fnScope = [];
|
|
4043
|
+
for (const id of this.lazyCaptureBoxes.keys()) {
|
|
4044
|
+
const cache = B.slot();
|
|
4045
|
+
B.entryAllocas.push(`${cache} = alloca ptr ; lazy box ${this.currentLocals.get(id).name}`);
|
|
4046
|
+
B.line(`store ptr null, ptr ${cache}`);
|
|
4047
|
+
fnScope.push({
|
|
4048
|
+
slot: cache,
|
|
4049
|
+
type: this.currentLocals.get(id).type,
|
|
4050
|
+
boxed: true,
|
|
4051
|
+
lazy: true,
|
|
4052
|
+
});
|
|
4053
|
+
this.lazyCaptureBoxes.set(id, cache);
|
|
4054
|
+
// Environments that finish during the call can share an immortal
|
|
4055
|
+
// frame box. Its payload owner is released with the function scope.
|
|
4056
|
+
const stack = this.stackCaptures.emit(this, id, this.currentLocals.get(id).type);
|
|
4057
|
+
if (stack) {
|
|
4058
|
+
this.lazyStackBoxes.set(id, stack.box);
|
|
4059
|
+
if (stack.owner)
|
|
4060
|
+
fnScope.push(stack.owner);
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
3206
4063
|
const borrowed = this.callLifetimes.borrowed.get(fn.name);
|
|
3207
4064
|
for (const [index, p] of fn.params.entries()) {
|
|
3208
4065
|
const local = this.currentLocals.get(p.localId);
|
|
@@ -3219,6 +4076,15 @@ export class LlEmitter {
|
|
|
3219
4076
|
this.storeIntegerView(p.localId, { name: `%p_${mangleLocal(p.localId)}`, type: p.type });
|
|
3220
4077
|
if (isRefCounted(p.type) && !borrowed?.has(index))
|
|
3221
4078
|
fnScope.push({ slot, type: p.type });
|
|
4079
|
+
if (this.reboundByFunction.get(fn.name)?.has(p.localId) && borrowed?.has(index)) {
|
|
4080
|
+
// The caller's borrow stays in the parameter slot; values the body
|
|
4081
|
+
// assigns are owned here until rebinding or exit releases them.
|
|
4082
|
+
const owner = `${slot}.owner`;
|
|
4083
|
+
B.entryAllocas.push(`${owner} = alloca ptr`);
|
|
4084
|
+
B.line(`store ptr null, ptr ${owner}`);
|
|
4085
|
+
fnScope.push({ slot: owner, type: p.type });
|
|
4086
|
+
this.reboundOwnerSlots.set(p.localId, owner);
|
|
4087
|
+
}
|
|
3222
4088
|
}
|
|
3223
4089
|
this.scopes.push(fnScope);
|
|
3224
4090
|
// Stackful native fibers retain these frames while suspended. The
|
|
@@ -3231,7 +4097,9 @@ export class LlEmitter {
|
|
|
3231
4097
|
B.line(`call void @scr_stack_enter(ptr %source_frame, ptr ${this.cstr(frame)})`);
|
|
3232
4098
|
B.returnEpilogue = `call void @scr_stack_leave(ptr %source_frame)`;
|
|
3233
4099
|
}
|
|
3234
|
-
if (this.
|
|
4100
|
+
if (this.stackChecks?.has(fn.name))
|
|
4101
|
+
this.emitStackCheck();
|
|
4102
|
+
if (this.workerEntryPolls?.has(fn.name))
|
|
3235
4103
|
this.emitPendingCheck();
|
|
3236
4104
|
this.emitStmts(fn.body);
|
|
3237
4105
|
// Implicit exit of a void function: release the function scope unless
|
|
@@ -3303,24 +4171,55 @@ export class LlEmitter {
|
|
|
3303
4171
|
const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`;
|
|
3304
4172
|
const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
|
|
3305
4173
|
const body = `define internal ${ret} @${symbol}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
|
|
3306
|
-
|
|
4174
|
+
if (!borrowed)
|
|
4175
|
+
return body;
|
|
4176
|
+
const slot = this.implSlotBorrowed(fn.name);
|
|
4177
|
+
const virtual = slot.size > 0 && slot.size !== borrowed.size
|
|
4178
|
+
? "\n" + this.emitOwnedCallAdapter(fn, borrowed, slot)
|
|
4179
|
+
: "";
|
|
4180
|
+
return body + "\n" + this.emitOwnedCallAdapter(fn, borrowed) + virtual;
|
|
3307
4181
|
}
|
|
3308
4182
|
/** Keep the ordinary owned ABI for closures, virtual dispatch, generated
|
|
3309
4183
|
* runtime adapters and library exports. Only direct IR calls select the
|
|
3310
4184
|
* borrowing body. Ownership of other parameters moves into that body;
|
|
3311
4185
|
* borrowed parameters are released here on normal and exceptional exits.
|
|
3312
4186
|
* The pending exception remains for the adapter's caller to handle. */
|
|
3313
|
-
|
|
4187
|
+
/** With `kept`, the virtual adapter of a borrowing slot instead: the
|
|
4188
|
+
* slot's parameters stay borrowed, the body's other borrowed ones are
|
|
4189
|
+
* released here. */
|
|
4190
|
+
emitOwnedCallAdapter(fn, borrowed, kept = NO_BORROWED) {
|
|
3314
4191
|
const B = new BlockBuilder();
|
|
3315
4192
|
this.B = B;
|
|
3316
4193
|
const params = fn.params.map((param, index) => `${this.llType(param.type)} %p${index}`);
|
|
3317
4194
|
const ret = this.llType(fn.returnType);
|
|
3318
4195
|
const call = `call ${ret} @${mangleBorrowedFunction(fn.name)}(${params.join(", ")})`;
|
|
3319
4196
|
B.line(ret === "void" ? call : `%result = ${call}`);
|
|
4197
|
+
// A borrowed result is reachable from the parameters: own it first. A
|
|
4198
|
+
// throwing body returns a null dummy, which needs no owner (and string
|
|
4199
|
+
// or array retains do not accept NULL).
|
|
4200
|
+
let result = "%result";
|
|
4201
|
+
if (this.borrowedReturns.has(fn.name)) {
|
|
4202
|
+
const slot = B.slot();
|
|
4203
|
+
B.entryAllocas.push(`${slot} = alloca ptr`);
|
|
4204
|
+
B.line(`store ptr null, ptr ${slot}`);
|
|
4205
|
+
const present = B.tmp();
|
|
4206
|
+
const own = B.newLabel("result.own");
|
|
4207
|
+
const done = B.newLabel("result.done");
|
|
4208
|
+
B.line(`${present} = icmp ne ptr %result, null`);
|
|
4209
|
+
B.condBr(present, own, done);
|
|
4210
|
+
B.startBlock(own);
|
|
4211
|
+
B.line(`store ptr ${this.retainValue("%result", fn.returnType)}, ptr ${slot}`);
|
|
4212
|
+
B.br(done);
|
|
4213
|
+
B.startBlock(done);
|
|
4214
|
+
result = B.tmp();
|
|
4215
|
+
B.line(`${result} = load ptr, ptr ${slot}`);
|
|
4216
|
+
}
|
|
3320
4217
|
for (const index of borrowed)
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
4218
|
+
if (!kept.has(index))
|
|
4219
|
+
this.releaseValue(`%p${index}`, fn.params[index].type);
|
|
4220
|
+
B.terminate(ret === "void" ? "ret void" : `ret ${ret} ${result}`);
|
|
4221
|
+
const symbol = kept.size > 0 ? `${mangleBorrowedFunction(fn.name)}.virtual` : mangleFunction(fn.name);
|
|
4222
|
+
return `define internal ${ret} @${symbol}(${params.join(", ")}) ${FN_ATTRS} {\n${B.render()}\n}`;
|
|
3324
4223
|
}
|
|
3325
4224
|
/** A local stable binding keeps its value alive while later arguments
|
|
3326
4225
|
* and the callee execute. A proven local union box cannot escape or be
|
|
@@ -3329,6 +4228,33 @@ export class LlEmitter {
|
|
|
3329
4228
|
canBorrowCallArgument(value) {
|
|
3330
4229
|
if (value.kind === "strLit")
|
|
3331
4230
|
return true;
|
|
4231
|
+
// Class casts reinterpret the same pointer (prefix layouts).
|
|
4232
|
+
if ((value.kind === "upcast" || value.kind === "downcast") &&
|
|
4233
|
+
value.value.type.kind === "object")
|
|
4234
|
+
return this.canBorrowCallArgument(value.value);
|
|
4235
|
+
if (value.kind === "seqExpr") {
|
|
4236
|
+
const guarded = guardedValue(value);
|
|
4237
|
+
return guarded !== null && this.canBorrowCallArgument(guarded);
|
|
4238
|
+
}
|
|
4239
|
+
// A nullable-union wrap reinterprets its payload pointer; an immortal
|
|
4240
|
+
// unit constant needs no owner at all.
|
|
4241
|
+
const wrap = this.borrowableNullableWrap(value);
|
|
4242
|
+
if (wrap === "unit")
|
|
4243
|
+
return true;
|
|
4244
|
+
if (wrap === "ref" && value.kind === "unionWrap")
|
|
4245
|
+
return this.canBorrowCallArgument(value.value);
|
|
4246
|
+
// Checked casts (`x as C`, `x!` on a nullable) lower to a ternary whose
|
|
4247
|
+
// failing arm always throws; the successful arm projects the same
|
|
4248
|
+
// stable owner. The throwing arm's typed dummy owns nothing.
|
|
4249
|
+
if (value.kind === "unionNarrow")
|
|
4250
|
+
return this.canBorrowCallArgument(value.value);
|
|
4251
|
+
if (value.kind === "libCall")
|
|
4252
|
+
return value.fn === "error.nodeThrow";
|
|
4253
|
+
if (value.kind === "ternary")
|
|
4254
|
+
return (this.canBorrowReceiver(value.then) &&
|
|
4255
|
+
this.canBorrowReceiver(value.else_) &&
|
|
4256
|
+
this.canBorrowCallArgument(value.then) &&
|
|
4257
|
+
this.canBorrowCallArgument(value.else_));
|
|
3332
4258
|
if (value.kind !== "varRef")
|
|
3333
4259
|
return false;
|
|
3334
4260
|
const binding = this.binding(value.localId);
|
|
@@ -3383,6 +4309,12 @@ export class LlEmitter {
|
|
|
3383
4309
|
switch (s.kind) {
|
|
3384
4310
|
case "varDecl": {
|
|
3385
4311
|
const b = this.binding(s.localId);
|
|
4312
|
+
if (this.walkBorrows.has(s.localId) && b.kind === "local") {
|
|
4313
|
+
// A borrowed walk pointer: no retain now, no release at scope exit.
|
|
4314
|
+
const v = s.init === null ? "null" : this.emitReadReceiver(s.init).name;
|
|
4315
|
+
B.line(`store ptr ${v}, ptr ${b.slot}`);
|
|
4316
|
+
break;
|
|
4317
|
+
}
|
|
3386
4318
|
const slice = this.scalarStringSlices.get(s.localId);
|
|
3387
4319
|
if (slice) {
|
|
3388
4320
|
const snapshot = emitStringSliceSnapshot(this, slice);
|
|
@@ -3515,6 +4447,23 @@ export class LlEmitter {
|
|
|
3515
4447
|
break;
|
|
3516
4448
|
}
|
|
3517
4449
|
case "assign": {
|
|
4450
|
+
const owner = this.reboundOwnerSlots.get(s.localId);
|
|
4451
|
+
if (owner !== undefined && !this.walkBorrows.has(s.localId)) {
|
|
4452
|
+
// A rebound borrowed parameter: the new value moves into the owner
|
|
4453
|
+
// slot, replacing (and releasing) the previous owned value. This
|
|
4454
|
+
// precedes the in-place string append, which would release the
|
|
4455
|
+
// caller's borrow.
|
|
4456
|
+
const param = this.binding(s.localId);
|
|
4457
|
+
const slot = param.slot;
|
|
4458
|
+
const v = this.emitExpr(s.value);
|
|
4459
|
+
this.moveTemp(v);
|
|
4460
|
+
const old = B.tmp();
|
|
4461
|
+
B.line(`${old} = load ptr, ptr ${owner}`);
|
|
4462
|
+
this.releaseValue(old, param.type);
|
|
4463
|
+
B.line(`store ptr ${v.name}, ptr ${slot}`);
|
|
4464
|
+
B.line(`store ptr ${v.name}, ptr ${owner}`);
|
|
4465
|
+
break;
|
|
4466
|
+
}
|
|
3518
4467
|
const localStorage = this.localUnionStorage.get(s.localId);
|
|
3519
4468
|
if (localStorage) {
|
|
3520
4469
|
storeLocalUnion(this, localStorage, s.value);
|
|
@@ -3527,6 +4476,11 @@ export class LlEmitter {
|
|
|
3527
4476
|
break;
|
|
3528
4477
|
}
|
|
3529
4478
|
const b = this.binding(s.localId);
|
|
4479
|
+
if (this.walkBorrows.has(s.localId) && b.kind === "local") {
|
|
4480
|
+
// Rebinding a borrowed walk pointer releases nothing.
|
|
4481
|
+
B.line(`store ptr ${this.emitReadReceiver(s.value).name}, ptr ${b.slot}`);
|
|
4482
|
+
break;
|
|
4483
|
+
}
|
|
3530
4484
|
const v = this.emitExpr(s.value);
|
|
3531
4485
|
if (b.kind === "global" && !s.initializes)
|
|
3532
4486
|
this.checkGlobalTdz(s.localId);
|
|
@@ -3548,7 +4502,9 @@ export class LlEmitter {
|
|
|
3548
4502
|
break;
|
|
3549
4503
|
}
|
|
3550
4504
|
case "exprStmt":
|
|
3551
|
-
|
|
4505
|
+
// A statement-position splice never observes its removed elements.
|
|
4506
|
+
if (!emitDiscardedSplice(this, s.expr))
|
|
4507
|
+
this.emitDiscarded(s.expr);
|
|
3552
4508
|
break;
|
|
3553
4509
|
case "arraySet": {
|
|
3554
4510
|
// Evaluation order matches JS: array, index, then value. Ownership
|
|
@@ -3560,21 +4516,43 @@ export class LlEmitter {
|
|
|
3560
4516
|
if (s.arr.type.kind !== "array")
|
|
3561
4517
|
throw new InternalCompilerError("llvm emitter bug: arraySet on non-array");
|
|
3562
4518
|
const acc = elemAccess(s.arr.type.elem);
|
|
4519
|
+
this.emitPublishedArrayGuard(arr.name, s.arr.type, 0, idx.name);
|
|
3563
4520
|
if (acc === "ref")
|
|
3564
4521
|
this.moveTemp(v);
|
|
3565
|
-
|
|
3566
|
-
this.declare(`declare void @scr_arr_set_${acc}(ptr, double, ${argTy === "i1" ? "i1 zeroext" : argTy})`);
|
|
3567
|
-
B.line(`call void @scr_arr_set_${acc}(ptr ${arr.name}, double ${idx.name}, ${argTy} ${v.name})`);
|
|
4522
|
+
emitDenseArraySet(this, arr.name, idx, s.index, acc, s.arr.type.elem, v.name);
|
|
3568
4523
|
break;
|
|
3569
4524
|
}
|
|
3570
4525
|
case "arraySetLength": {
|
|
4526
|
+
// Pop's `length = 0` store on an empty array only matters when the
|
|
4527
|
+
// array can be frozen (published); its operand is a local.
|
|
4528
|
+
if (s.pop === true &&
|
|
4529
|
+
s.length.kind === "numLit" &&
|
|
4530
|
+
s.length.value === 0 &&
|
|
4531
|
+
this.publishedTypes?.containerGuarded(s.arr.type) !== true)
|
|
4532
|
+
break;
|
|
3571
4533
|
const arr = emitBorrowedInput(this, s.arr);
|
|
3572
4534
|
const length = this.emitExpr(s.length);
|
|
3573
4535
|
if (s.arr.type.kind !== "array")
|
|
3574
4536
|
throw new InternalCompilerError("llvm emitter bug: arraySetLength on non-array");
|
|
4537
|
+
this.emitPublishedArrayGuard(arr.name, s.arr.type, s.pop === true ? 2 : 3);
|
|
3575
4538
|
this.declare(`declare void @scr_arr_set_len(ptr, double)`);
|
|
4539
|
+
// Assigning the current length (a reused work array reset to where
|
|
4540
|
+
// it already is) changes nothing: the runtime's validation accepts
|
|
4541
|
+
// exactly the value equal to the length (-0 included, NaN never),
|
|
4542
|
+
// and no element is released. Everything else keeps the call.
|
|
4543
|
+
const lenPtr = B.tmp(), len = B.tmp(), lenNumber = B.tmp(), same = B.tmp();
|
|
4544
|
+
const resize = B.newLabel("arr.len.set"), done = B.newLabel("arr.len.done");
|
|
4545
|
+
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
4546
|
+
this.markMemoryPointer(lenPtr, "array:header");
|
|
4547
|
+
B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
4548
|
+
B.line(`${lenNumber} = uitofp ${this.sizeType} ${len} to double`);
|
|
4549
|
+
B.line(`${same} = fcmp oeq double ${length.name}, ${lenNumber}`);
|
|
4550
|
+
B.condBr(same, done, resize);
|
|
4551
|
+
B.startBlock(resize);
|
|
3576
4552
|
B.line(`call void @scr_arr_set_len(ptr ${arr.name}, double ${length.name})`);
|
|
3577
4553
|
this.emitPendingCheck();
|
|
4554
|
+
B.br(done);
|
|
4555
|
+
B.startBlock(done);
|
|
3578
4556
|
break;
|
|
3579
4557
|
}
|
|
3580
4558
|
case "arraySetUndefined":
|
|
@@ -3583,6 +4561,7 @@ export class LlEmitter {
|
|
|
3583
4561
|
const idx = this.emitExpr(s.index);
|
|
3584
4562
|
if (s.arr.type.kind !== "array")
|
|
3585
4563
|
throw new InternalCompilerError(`llvm emitter bug: ${s.kind} on non-array`);
|
|
4564
|
+
this.emitPublishedArrayGuard(arr.name, s.arr.type, s.kind === "arrayDelete" ? 5 : 0, idx.name);
|
|
3586
4565
|
const fn = s.kind === "arraySetUndefined" ? "scr_arr_set_undefined" : "scr_arr_delete";
|
|
3587
4566
|
this.declare(`declare ${s.kind === "arrayDelete" ? "zeroext i1" : "void"} @${fn}(ptr, double)`);
|
|
3588
4567
|
if (s.kind === "arrayDelete")
|
|
@@ -3612,10 +4591,29 @@ export class LlEmitter {
|
|
|
3612
4591
|
// release — a release can trigger a cycle collection, which must
|
|
3613
4592
|
// never see a heap edge whose count was already given up).
|
|
3614
4593
|
// Classes and records share the struct layout, so one emission.
|
|
3615
|
-
const
|
|
3616
|
-
|
|
3617
|
-
|
|
4594
|
+
const nullable = s.kind === "fieldSet" ? this.nullableFields.get(s.className, s.field) : null;
|
|
4595
|
+
if (nullable && s.kind === "fieldSet") {
|
|
4596
|
+
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
4597
|
+
// (The guard precedes the value here: the nullable store
|
|
4598
|
+
// evaluates it itself.)
|
|
4599
|
+
this.emitPublishedFieldGuard(obj.name, s.className, s.field);
|
|
4600
|
+
this.emitNullableFieldStore(obj.name, s.className, s.field, nullable, s.value);
|
|
4601
|
+
break;
|
|
4602
|
+
}
|
|
4603
|
+
if (s.kind === "fieldSet" && this.int32Slots.isField(s.className, s.field)) {
|
|
4604
|
+
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
4605
|
+
const v = this.emitExpr(s.value);
|
|
4606
|
+
this.emitPublishedFieldGuard(obj.name, s.className, s.field);
|
|
4607
|
+
const { ptr } = this.classFieldPtr(obj.name, s.className, s.field);
|
|
4608
|
+
this.storeInt32Field(ptr, v, s.value);
|
|
4609
|
+
break;
|
|
4610
|
+
}
|
|
4611
|
+
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
3618
4612
|
const v = this.emitExpr(s.value);
|
|
4613
|
+
if (s.kind === "fieldSet")
|
|
4614
|
+
this.emitPublishedFieldGuard(obj.name, s.className, s.field);
|
|
4615
|
+
else if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
|
|
4616
|
+
this.emitPublishedFieldGuard(obj.name, null, s.field);
|
|
3619
4617
|
const { ptr, type } = s.kind === "fieldSet"
|
|
3620
4618
|
? this.classFieldPtr(obj.name, s.className, s.field)
|
|
3621
4619
|
: this.recordFieldPtr(obj.name, s.shapeId, s.field);
|
|
@@ -3649,6 +4647,11 @@ export class LlEmitter {
|
|
|
3649
4647
|
// the overflow (key and value released; absent keys no-op).
|
|
3650
4648
|
const obj = this.emitExpr(s.obj);
|
|
3651
4649
|
const key = this.emitExpr(s.key);
|
|
4650
|
+
if (this.publishedTypes?.recordGuarded(s.shapeId) === true) {
|
|
4651
|
+
// Node: "Cannot delete property 'k' of #<Object>" (key known at
|
|
4652
|
+
// run time only; the guard reports the frozen record generically).
|
|
4653
|
+
this.emitPublishedFieldGuard(obj.name, null, "[key]");
|
|
4654
|
+
}
|
|
3652
4655
|
const ovf = this.recordOvfPtr(obj.name, s.shapeId);
|
|
3653
4656
|
this.declare(`declare zeroext i1 @scr_map_delete_str(ptr, ptr)`);
|
|
3654
4657
|
const t = B.tmp();
|
|
@@ -3664,6 +4667,8 @@ export class LlEmitter {
|
|
|
3664
4667
|
const obj = this.emitExpr(s.obj);
|
|
3665
4668
|
const key = this.emitExpr(s.key);
|
|
3666
4669
|
const v = this.emitExpr(s.value);
|
|
4670
|
+
if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
|
|
4671
|
+
this.emitPublishedFieldGuard(obj.name, null, "[key]");
|
|
3667
4672
|
if (isRefCounted(v.type))
|
|
3668
4673
|
this.moveTemp(v);
|
|
3669
4674
|
const shape = this.recordShape(s.shapeId);
|
|
@@ -3695,7 +4700,7 @@ export class LlEmitter {
|
|
|
3695
4700
|
for (const f of shape.fields) {
|
|
3696
4701
|
const lit = this.internLiteral(f.name);
|
|
3697
4702
|
const hit = B.tmp();
|
|
3698
|
-
B.line(`${hit} = call zeroext i1 @
|
|
4703
|
+
B.line(`${hit} = call zeroext i1 @sc_str_eq(ptr ${key.name}, ptr ${lit}) ; ${llvmCommentText(f.name)}`);
|
|
3699
4704
|
const lh = B.newLabel("rks.h");
|
|
3700
4705
|
const ln = B.newLabel("rks.n");
|
|
3701
4706
|
B.condBr(hit, lh, ln);
|
|
@@ -3718,9 +4723,9 @@ export class LlEmitter {
|
|
|
3718
4723
|
B.line(`call void @scr_dyn_release(ptr ${v.name})`);
|
|
3719
4724
|
// Mismatched write: TypeError pending, field untouched — the
|
|
3720
4725
|
// statement-level check below unwinds.
|
|
3721
|
-
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
3722
4726
|
const pend = B.tmp();
|
|
3723
|
-
|
|
4727
|
+
for (const line of this.pendingTestLines(pend))
|
|
4728
|
+
B.line(line.trimStart());
|
|
3724
4729
|
const lw = B.newLabel("rks.w");
|
|
3725
4730
|
B.condBr(pend, join, lw);
|
|
3726
4731
|
B.startBlock(lw);
|
|
@@ -3751,7 +4756,7 @@ export class LlEmitter {
|
|
|
3751
4756
|
// typeEquals(f.type, iv) — the frontend fences everything else.
|
|
3752
4757
|
const lit = this.internLiteral(f.name);
|
|
3753
4758
|
const hit = B.tmp();
|
|
3754
|
-
B.line(`${hit} = call zeroext i1 @
|
|
4759
|
+
B.line(`${hit} = call zeroext i1 @sc_str_eq(ptr ${key.name}, ptr ${lit}) ; ${llvmCommentText(f.name)}`);
|
|
3755
4760
|
const lh = B.newLabel("rks.h");
|
|
3756
4761
|
const ln = B.newLabel("rks.n");
|
|
3757
4762
|
B.condBr(hit, lh, ln);
|
|
@@ -4125,10 +5130,13 @@ export class LlEmitter {
|
|
|
4125
5130
|
B.line(`${inBounds} = icmp ne ptr ${cur}, null`);
|
|
4126
5131
|
}
|
|
4127
5132
|
else {
|
|
4128
|
-
const i = B.tmp(), len = B.tmp();
|
|
4129
|
-
this.declare(`declare double @scr_arr_len(ptr)`);
|
|
5133
|
+
const i = B.tmp(), lenPtr = B.tmp(), rawLen = B.tmp(), len = B.tmp();
|
|
4130
5134
|
B.line(`${i} = load double, ptr ${idxSlot}`);
|
|
4131
|
-
|
|
5135
|
+
// The inline form of scr_arr_len, like the `length` intrinsic.
|
|
5136
|
+
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
5137
|
+
this.markMemoryPointer(lenPtr, "array:header");
|
|
5138
|
+
B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
5139
|
+
B.line(`${len} = uitofp ${this.sizeType} ${rawLen} to double`);
|
|
4132
5140
|
B.line(`${inBounds} = fcmp olt double ${i}, ${len}`);
|
|
4133
5141
|
cur = i;
|
|
4134
5142
|
}
|
|
@@ -4152,12 +5160,8 @@ export class LlEmitter {
|
|
|
4152
5160
|
const borrowedElement = !snapshot && this.loopArrayBorrows.has(s);
|
|
4153
5161
|
if (!snapshot) {
|
|
4154
5162
|
const acc = elemAccess(elem);
|
|
4155
|
-
const accTy = acc === "f64" ? "double" : acc === "bool" ? "i1" : "ptr";
|
|
4156
5163
|
const getter = borrowedElement ? "scr_arr_borrow_ref" : `scr_arr_get_${acc}`;
|
|
4157
|
-
this.
|
|
4158
|
-
const value = B.tmp();
|
|
4159
|
-
B.line(`${value} = call ${accTy} @${getter}(ptr ${arr.name}, double ${cur})`);
|
|
4160
|
-
cur = value;
|
|
5164
|
+
cur = emitDenseArrayGet(this, arr.name, { name: cur, type: { kind: "f64" } }, undefined, acc, elem, getter, !borrowedElement);
|
|
4161
5165
|
}
|
|
4162
5166
|
if (spanLength) {
|
|
4163
5167
|
const length = B.tmp();
|
|
@@ -4249,7 +5253,12 @@ export class LlEmitter {
|
|
|
4249
5253
|
// the actual ret. The parked value owns a synthetic slot-backed
|
|
4250
5254
|
// scope entry during each copy so a throwing finally releases it.
|
|
4251
5255
|
let v = null;
|
|
4252
|
-
if (s.value !== null) {
|
|
5256
|
+
if (s.value !== null && this.currentBorrowedReturn) {
|
|
5257
|
+
// A borrowed-return body hands back its walk without a reference
|
|
5258
|
+
// (it has no try statements, so no finally can intervene).
|
|
5259
|
+
v = this.emitReadReceiver(s.value);
|
|
5260
|
+
}
|
|
5261
|
+
else if (s.value !== null) {
|
|
4253
5262
|
v = this.emitExpr(s.value);
|
|
4254
5263
|
this.moveTemp(v);
|
|
4255
5264
|
}
|
|
@@ -4553,7 +5562,7 @@ export class LlEmitter {
|
|
|
4553
5562
|
const hit = B.tmp();
|
|
4554
5563
|
if (c.test.type.kind === "string") {
|
|
4555
5564
|
this.declare(`declare zeroext i1 @scr_str_eq(ptr, ptr)`);
|
|
4556
|
-
B.line(`${hit} = call zeroext i1 @
|
|
5565
|
+
B.line(`${hit} = call zeroext i1 @sc_str_eq(ptr ${disc.name}, ptr ${t.name})`);
|
|
4557
5566
|
}
|
|
4558
5567
|
else if (c.test.type.kind === "bool") {
|
|
4559
5568
|
B.line(`${hit} = icmp eq i1 ${disc.name}, ${t.name}`);
|
|
@@ -4624,24 +5633,62 @@ export class LlEmitter {
|
|
|
4624
5633
|
return emitLiteralExpr(this, e);
|
|
4625
5634
|
}
|
|
4626
5635
|
emitOperatorExpr(e) {
|
|
4627
|
-
if (e.kind === "seqExpr")
|
|
4628
|
-
|
|
4629
|
-
// to this expression's frame so they survive those reads and still
|
|
4630
|
-
// die on the path that created them, including inside lazy branches.
|
|
4631
|
-
this.scopes.push([]);
|
|
4632
|
-
const result = emitBorrowedFieldSequence(this, e) ?? emitOperatorExpr(this, e);
|
|
4633
|
-
for (const local of this.scopes.pop()) {
|
|
4634
|
-
this.currentFrame().push({
|
|
4635
|
-
name: local.slot,
|
|
4636
|
-
type: local.type,
|
|
4637
|
-
slot: true,
|
|
4638
|
-
...(local.boxed ? { boxed: true } : {}),
|
|
4639
|
-
});
|
|
4640
|
-
}
|
|
4641
|
-
return result;
|
|
4642
|
-
}
|
|
5636
|
+
if (e.kind === "seqExpr")
|
|
5637
|
+
return this.emitSequence(() => emitBorrowedFieldSequence(this, e) ?? emitOperatorExpr(this, e));
|
|
4643
5638
|
return emitOperatorExpr(this, e);
|
|
4644
5639
|
}
|
|
5640
|
+
/** Saved operands can feed later call arguments. Transfer ownership of a
|
|
5641
|
+
* sequence's locals to this expression's frame so they survive those
|
|
5642
|
+
* reads and still die on the path that created them, including inside
|
|
5643
|
+
* lazy branches. */
|
|
5644
|
+
emitSequence(body) {
|
|
5645
|
+
this.scopes.push([]);
|
|
5646
|
+
const result = body();
|
|
5647
|
+
for (const local of this.scopes.pop()) {
|
|
5648
|
+
this.currentFrame().push({
|
|
5649
|
+
name: local.slot,
|
|
5650
|
+
type: local.type,
|
|
5651
|
+
slot: true,
|
|
5652
|
+
...(local.boxed ? { boxed: true } : {}),
|
|
5653
|
+
});
|
|
5654
|
+
}
|
|
5655
|
+
return result;
|
|
5656
|
+
}
|
|
5657
|
+
/** A statement-position expression whose value is dropped. When a
|
|
5658
|
+
* sequence's result only reads an unchanged local (a derived
|
|
5659
|
+
* constructor's `super()` evaluates to `this`), its statements still run
|
|
5660
|
+
* in place, but the result is neither retained nor released. */
|
|
5661
|
+
/** Whether a discarded value can be skipped entirely: a borrowable read
|
|
5662
|
+
* with no effect of its own. canBorrowCallArgument also accepts checked
|
|
5663
|
+
* projections (a ternary or narrow whose failing arm throws, and the
|
|
5664
|
+
* throwing call itself) because a borrowed use still evaluates them;
|
|
5665
|
+
* skipping those would drop the throw. */
|
|
5666
|
+
discardableRead(value) {
|
|
5667
|
+
if (value.kind === "libCall" || value.kind === "ternary" || value.kind === "unionNarrow")
|
|
5668
|
+
return false;
|
|
5669
|
+
if ((value.kind === "upcast" || value.kind === "downcast" || value.kind === "unionWrap") &&
|
|
5670
|
+
!this.discardableRead(value.value))
|
|
5671
|
+
return false;
|
|
5672
|
+
if (value.kind === "seqExpr") {
|
|
5673
|
+
const guarded = guardedValue(value);
|
|
5674
|
+
if (guarded === null || !this.discardableRead(guarded))
|
|
5675
|
+
return false;
|
|
5676
|
+
}
|
|
5677
|
+
return this.canBorrowCallArgument(value);
|
|
5678
|
+
}
|
|
5679
|
+
emitDiscarded(e) {
|
|
5680
|
+
if (e.kind === "seqExpr" &&
|
|
5681
|
+
isRefCounted(e.result.type) &&
|
|
5682
|
+
e.result.kind !== "strLit" &&
|
|
5683
|
+
this.discardableRead(e.result)) {
|
|
5684
|
+
this.emitSequence(() => {
|
|
5685
|
+
for (const s of e.stmts)
|
|
5686
|
+
this.emitStmt(s);
|
|
5687
|
+
});
|
|
5688
|
+
return;
|
|
5689
|
+
}
|
|
5690
|
+
this.emitExpr(e);
|
|
5691
|
+
}
|
|
4645
5692
|
emitControlExpr(e) {
|
|
4646
5693
|
return emitControlExpr(this, e);
|
|
4647
5694
|
}
|
|
@@ -4655,8 +5702,17 @@ export class LlEmitter {
|
|
|
4655
5702
|
* value, making the snapshot unique unless a real observable alias exists.
|
|
4656
5703
|
*/
|
|
4657
5704
|
emitStringSelfConcatAssign(localId, left, suffix, retainForYield) {
|
|
5705
|
+
const moved = this.emitMovedSelfConcat(localId, left, suffix, retainForYield);
|
|
5706
|
+
if (moved)
|
|
5707
|
+
return moved;
|
|
4658
5708
|
const snapshot = this.emitExpr(left);
|
|
4659
|
-
|
|
5709
|
+
// A suffix of several parts, or a number, appends each operand to the
|
|
5710
|
+
// snapshot directly (in place once the binding lets go of it) instead
|
|
5711
|
+
// of first building the suffix as a separate string.
|
|
5712
|
+
const parts = stringParts(suffix);
|
|
5713
|
+
const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
|
|
5714
|
+
const inputs = mixed ? emitConcatInputs(this, parts) : [];
|
|
5715
|
+
const right = mixed ? null : this.emitExpr(suffix);
|
|
4660
5716
|
const b = this.binding(localId);
|
|
4661
5717
|
const B = this.B;
|
|
4662
5718
|
if (b.kind === "boxed") {
|
|
@@ -4669,9 +5725,15 @@ export class LlEmitter {
|
|
|
4669
5725
|
B.line(`store ptr null, ptr ${b.slot}`);
|
|
4670
5726
|
this.releaseValue(old, b.type);
|
|
4671
5727
|
}
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
5728
|
+
let raw;
|
|
5729
|
+
if (right === null) {
|
|
5730
|
+
raw = emitMixedConcat(this, snapshot.name, inputs);
|
|
5731
|
+
}
|
|
5732
|
+
else {
|
|
5733
|
+
this.declare(`declare ptr @scr_str_concat(ptr, ptr)`);
|
|
5734
|
+
raw = B.tmp();
|
|
5735
|
+
B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`);
|
|
5736
|
+
}
|
|
4675
5737
|
const result = this.own({ name: raw, type: left.type });
|
|
4676
5738
|
if (retainForYield) {
|
|
4677
5739
|
const stored = this.retainValue(result.name, result.type);
|
|
@@ -4689,6 +5751,43 @@ export class LlEmitter {
|
|
|
4689
5751
|
}
|
|
4690
5752
|
return result;
|
|
4691
5753
|
}
|
|
5754
|
+
/** `local = local + suffix` when the suffix cannot read or write the
|
|
5755
|
+
* local (an unboxed binding is only reachable through this function's own
|
|
5756
|
+
* expressions): reading the binding after the suffix gives the value the
|
|
5757
|
+
* left side had, so it moves into a consuming concat, and the result takes
|
|
5758
|
+
* its place. Null when the general snapshot path is needed. */
|
|
5759
|
+
emitMovedSelfConcat(localId, left, suffix, retainForYield) {
|
|
5760
|
+
const b = this.binding(localId);
|
|
5761
|
+
if (retainForYield ||
|
|
5762
|
+
b.kind !== "local" ||
|
|
5763
|
+
b.local === undefined ||
|
|
5764
|
+
b.local.boxed ||
|
|
5765
|
+
b.local.tdz ||
|
|
5766
|
+
b.type.kind !== "string" ||
|
|
5767
|
+
left.kind !== "varRef" ||
|
|
5768
|
+
left.localId !== localId ||
|
|
5769
|
+
mentionsLocal(suffix, localId))
|
|
5770
|
+
return null;
|
|
5771
|
+
const parts = stringParts(suffix);
|
|
5772
|
+
const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
|
|
5773
|
+
const inputs = mixed ? emitConcatInputs(this, parts) : [];
|
|
5774
|
+
const right = mixed ? null : this.emitExpr(suffix);
|
|
5775
|
+
this.materializeSplitLocal(localId);
|
|
5776
|
+
const B = this.B;
|
|
5777
|
+
const head = B.tmp();
|
|
5778
|
+
B.line(`${head} = load ptr, ptr ${b.slot}`);
|
|
5779
|
+
let raw;
|
|
5780
|
+
if (right === null) {
|
|
5781
|
+
raw = emitMixedConcat(this, head, inputs, true);
|
|
5782
|
+
}
|
|
5783
|
+
else {
|
|
5784
|
+
this.declare(`declare ptr @scr_str_concat_move(ptr, ptr)`);
|
|
5785
|
+
raw = B.tmp();
|
|
5786
|
+
B.line(`${raw} = call ptr @scr_str_concat_move(ptr ${head}, ptr ${right.name})`);
|
|
5787
|
+
}
|
|
5788
|
+
B.line(`store ptr ${raw}, ptr ${b.slot}`);
|
|
5789
|
+
return { name: raw, type: left.type }; // moved into the binding
|
|
5790
|
+
}
|
|
4692
5791
|
emitContainerExpr(e) {
|
|
4693
5792
|
return emitContainerExpr(this, e);
|
|
4694
5793
|
}
|
|
@@ -4702,8 +5801,152 @@ export class LlEmitter {
|
|
|
4702
5801
|
return emitDynamicExpr(this, e);
|
|
4703
5802
|
}
|
|
4704
5803
|
emitIntrinsicExpr(e) {
|
|
5804
|
+
if (e.name === "threads.publish")
|
|
5805
|
+
return this.emitPublish(e);
|
|
4705
5806
|
return emitIntrinsicExpr(this, e);
|
|
4706
5807
|
}
|
|
5808
|
+
publishWalkersCache = null;
|
|
5809
|
+
/** Per-type publication walkers, created on the first publish. */
|
|
5810
|
+
get publishWalkers() {
|
|
5811
|
+
if (this.publishWalkersCache !== null)
|
|
5812
|
+
return this.publishWalkersCache;
|
|
5813
|
+
const byPre = [...this.classMeta.values()].sort((a, b) => a.pre - b.pre);
|
|
5814
|
+
this.publishWalkersCache = new PublishWalkers({
|
|
5815
|
+
declare: (decl) => this.declare(decl),
|
|
5816
|
+
cstr: (text) => this.cstr(text),
|
|
5817
|
+
sizeType: this.sizeType,
|
|
5818
|
+
tracedShapes: this.tracedShapes,
|
|
5819
|
+
unionsById: this.unionsById,
|
|
5820
|
+
recordsById: this.recordsById,
|
|
5821
|
+
nullableArm: (t) => this.nullableUnions.of(t)?.arm ?? null,
|
|
5822
|
+
classInfo: (className) => {
|
|
5823
|
+
const meta = this.classMetaOf(className);
|
|
5824
|
+
return {
|
|
5825
|
+
def: meta.def,
|
|
5826
|
+
hierarchy: meta.hierarchy,
|
|
5827
|
+
pre: meta.pre,
|
|
5828
|
+
post: meta.post,
|
|
5829
|
+
runtimeRooted: meta.root.def.runtime === true,
|
|
5830
|
+
fields: meta.def.fields.map((f) => ({
|
|
5831
|
+
name: f.name,
|
|
5832
|
+
index: classFieldIndex(meta, f.name).index,
|
|
5833
|
+
type: this.nullableFields.storageType(className, f.name, f.type),
|
|
5834
|
+
})),
|
|
5835
|
+
};
|
|
5836
|
+
},
|
|
5837
|
+
classesInInterval: (pre, post) => byPre.filter((m) => m.pre >= pre && m.pre <= post).map((m) => m.def.name),
|
|
5838
|
+
});
|
|
5839
|
+
return this.publishWalkersCache;
|
|
5840
|
+
}
|
|
5841
|
+
/** `publish(value)` (@scriptc/threads): makes the graph immortal and
|
|
5842
|
+
* immutable (scr_publish with the static type's walker) and answers the
|
|
5843
|
+
* same value. Throws (pending) when a dyn part is unpublishable. */
|
|
5844
|
+
emitPublish(e) {
|
|
5845
|
+
const value = this.emitExpr(e.args[0]);
|
|
5846
|
+
if (this.B.isTerminated())
|
|
5847
|
+
return value;
|
|
5848
|
+
const lines = this.publishWalkers.emitPublish(value.name, value.type, e.loc);
|
|
5849
|
+
if (lines.length > 0) {
|
|
5850
|
+
for (const line of lines)
|
|
5851
|
+
this.B.line(line.trimStart());
|
|
5852
|
+
// A refusal unwinds while the operand temp still owns its reference.
|
|
5853
|
+
this.emitPendingCheck();
|
|
5854
|
+
}
|
|
5855
|
+
// The result IS the operand: ownership moves to the result temp.
|
|
5856
|
+
if (!isRefCounted(value.type))
|
|
5857
|
+
return { name: value.name, type: e.type };
|
|
5858
|
+
this.moveTemp(value);
|
|
5859
|
+
return this.own({ name: value.name, type: e.type });
|
|
5860
|
+
}
|
|
5861
|
+
/** Branches on "`obj` is published" (rc == SIZE_MAX); leaves the builder
|
|
5862
|
+
* in the cold block and answers the continuation label. */
|
|
5863
|
+
emitPublishedTest(obj) {
|
|
5864
|
+
const B = this.B;
|
|
5865
|
+
const rc = B.tmp(), frozen = B.tmp(), expected = B.tmp();
|
|
5866
|
+
B.line(`${rc} = load ${this.sizeType}, ptr ${obj}`);
|
|
5867
|
+
B.line(`${frozen} = icmp eq ${this.sizeType} ${rc}, -1`);
|
|
5868
|
+
this.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
5869
|
+
B.line(`${expected} = call i1 @llvm.expect.i1(i1 ${frozen}, i1 false)`);
|
|
5870
|
+
const cold = B.newLabel("pub.frozen"), ok = B.newLabel("pub.ok");
|
|
5871
|
+
B.condBr(expected, cold, ok);
|
|
5872
|
+
B.startBlock(cold);
|
|
5873
|
+
return ok;
|
|
5874
|
+
}
|
|
5875
|
+
publishedNamesSym = null;
|
|
5876
|
+
/** The class-name table the hierarchy field guards index by preorder. */
|
|
5877
|
+
publishedClassNamesDefs() {
|
|
5878
|
+
if (this.publishedNamesSym === null)
|
|
5879
|
+
return [];
|
|
5880
|
+
const metas = [...this.classMeta.values()];
|
|
5881
|
+
const size = metas.reduce((max, m) => Math.max(max, m.pre + 1), 0);
|
|
5882
|
+
const names = new Array(size).fill("ptr null");
|
|
5883
|
+
for (const m of metas)
|
|
5884
|
+
names[m.pre] = `ptr ${this.cstr(m.def.jsName ?? m.def.name)}`;
|
|
5885
|
+
return [
|
|
5886
|
+
`${this.publishedNamesSym} = internal constant [${size} x ptr] [ ${names.join(", ")} ]`,
|
|
5887
|
+
``,
|
|
5888
|
+
];
|
|
5889
|
+
}
|
|
5890
|
+
/** The frozen-object guard of a class field or record field store:
|
|
5891
|
+
* Node's "Cannot assign to read only property" TypeError when the target
|
|
5892
|
+
* is published. `className` null means a record (`#<Object>`). */
|
|
5893
|
+
emitPublishedFieldGuard(obj, className, field) {
|
|
5894
|
+
const published = this.publishedTypes;
|
|
5895
|
+
if (published === null)
|
|
5896
|
+
return;
|
|
5897
|
+
if (className !== null && !published.classGuarded(className))
|
|
5898
|
+
return;
|
|
5899
|
+
const B = this.B;
|
|
5900
|
+
const ok = this.emitPublishedTest(obj);
|
|
5901
|
+
let owner;
|
|
5902
|
+
const meta = className === null ? undefined : this.classMeta.get(className);
|
|
5903
|
+
if (meta === undefined)
|
|
5904
|
+
owner = this.cstr("Object");
|
|
5905
|
+
else if (!meta.hierarchy)
|
|
5906
|
+
owner = this.cstr(meta.def.jsName ?? meta.def.name);
|
|
5907
|
+
else {
|
|
5908
|
+
// The DYNAMIC class names the object, as in Node.
|
|
5909
|
+
this.publishedNamesSym ??= "@sc_pub_class_names";
|
|
5910
|
+
const vtp = B.tmp(), vt = B.tmp(), pre = B.tmp(), slot = B.tmp(), name = B.tmp();
|
|
5911
|
+
B.line(`${vtp} = getelementptr inbounds ptr, ptr ${obj}, i64 1`);
|
|
5912
|
+
B.line(`${vt} = load ptr, ptr ${vtp}`);
|
|
5913
|
+
B.line(`${pre} = load ${this.sizeType}, ptr ${vt}`);
|
|
5914
|
+
B.line(`${slot} = getelementptr inbounds ptr, ptr ${this.publishedNamesSym}, ${this.sizeType} ${pre}`);
|
|
5915
|
+
B.line(`${name} = load ptr, ptr ${slot}`);
|
|
5916
|
+
owner = name;
|
|
5917
|
+
}
|
|
5918
|
+
this.declare(`declare void @scr_throw_published_field(ptr, ptr)`);
|
|
5919
|
+
B.line(`call void @scr_throw_published_field(ptr ${this.cstr(field)}, ptr ${owner})`);
|
|
5920
|
+
this.emitUnwind();
|
|
5921
|
+
B.startBlock(ok);
|
|
5922
|
+
}
|
|
5923
|
+
/** The frozen-array guard (scr_throw_published_array's ops): `index` and
|
|
5924
|
+
* `count` are doubles (the written index, the argument count). */
|
|
5925
|
+
emitPublishedArrayGuard(arr, arrType, op, index = f64Lit(0), count = f64Lit(0)) {
|
|
5926
|
+
if (this.publishedTypes?.containerGuarded(arrType) !== true)
|
|
5927
|
+
return;
|
|
5928
|
+
const B = this.B;
|
|
5929
|
+
const ok = this.emitPublishedTest(arr);
|
|
5930
|
+
this.declare(`declare zeroext i1 @scr_throw_published_array(i32, ptr, double, double)`);
|
|
5931
|
+
const threw = B.tmp();
|
|
5932
|
+
B.line(`${threw} = call zeroext i1 @scr_throw_published_array(i32 ${op}, ptr ${arr}, double ${index}, double ${count})`);
|
|
5933
|
+
const unwind = B.newLabel("pub.throw");
|
|
5934
|
+
B.condBr(threw, unwind, ok);
|
|
5935
|
+
B.startBlock(unwind);
|
|
5936
|
+
this.emitUnwind();
|
|
5937
|
+
B.startBlock(ok);
|
|
5938
|
+
}
|
|
5939
|
+
/** The published Map/Set guard: "Cannot modify a published Map/Set". */
|
|
5940
|
+
emitPublishedCollectionGuard(m, t) {
|
|
5941
|
+
if (this.publishedTypes?.containerGuarded(t) !== true)
|
|
5942
|
+
return;
|
|
5943
|
+
const B = this.B;
|
|
5944
|
+
const ok = this.emitPublishedTest(m);
|
|
5945
|
+
this.declare(`declare void @scr_throw_published_collection(i1 zeroext)`);
|
|
5946
|
+
B.line(`call void @scr_throw_published_collection(i1 ${t.kind === "set" ? "true" : "false"})`);
|
|
5947
|
+
this.emitUnwind();
|
|
5948
|
+
B.startBlock(ok);
|
|
5949
|
+
}
|
|
4707
5950
|
emitSerializationExpr(e) {
|
|
4708
5951
|
return emitSerializationExpr(this, e);
|
|
4709
5952
|
}
|
|
@@ -4723,6 +5966,13 @@ export class LlEmitter {
|
|
|
4723
5966
|
return this.emitExpr(e);
|
|
4724
5967
|
if (e.kind === "strLit")
|
|
4725
5968
|
return { name: this.internLiteral(e.value), type: e.type };
|
|
5969
|
+
const guarded = guardedValue(e);
|
|
5970
|
+
if (guarded !== null) {
|
|
5971
|
+
// Throwing guards run in place, exactly as the sequence emits them.
|
|
5972
|
+
for (const s of e.stmts)
|
|
5973
|
+
this.emitStmt(s);
|
|
5974
|
+
return this.emitReadReceiver(guarded);
|
|
5975
|
+
}
|
|
4726
5976
|
if (e.kind === "varRef") {
|
|
4727
5977
|
this.materializeSplitLocal(e.localId);
|
|
4728
5978
|
const binding = this.binding(e.localId);
|
|
@@ -4744,24 +5994,58 @@ export class LlEmitter {
|
|
|
4744
5994
|
this.B.line(`${value} = load ptr, ptr ${binding.slot}`);
|
|
4745
5995
|
return { name: value, type: e.type };
|
|
4746
5996
|
}
|
|
4747
|
-
|
|
5997
|
+
if (e.kind === "arrayGet" && this.canBorrowReceiver(e)) {
|
|
5998
|
+
// The array keeps the element alive until the consumer; a missing
|
|
5999
|
+
// element still traps exactly like the owned read.
|
|
6000
|
+
const slot = this.B.slot();
|
|
6001
|
+
this.B.entryAllocas.push(`${slot} = alloca ptr`);
|
|
6002
|
+
emitBorrowedArrayRead(this, e, slot);
|
|
6003
|
+
const value = this.B.tmp();
|
|
6004
|
+
this.B.line(`${value} = load ptr, ptr ${slot}`);
|
|
6005
|
+
return { name: value, type: e.type };
|
|
6006
|
+
}
|
|
6007
|
+
const wrap = this.borrowableNullableWrap(e);
|
|
6008
|
+
if (wrap === "unit" && e.kind === "unionWrap")
|
|
6009
|
+
return { name: this.unitInstanceRef(e.unionId, e.tag), type: e.type };
|
|
6010
|
+
if (wrap === "ref" && e.kind === "unionWrap")
|
|
6011
|
+
return { name: this.emitReadReceiver(e.value).name, type: e.type };
|
|
6012
|
+
const read = matchMapRead(e, this.boxedUnionsById);
|
|
4748
6013
|
if (read) {
|
|
4749
6014
|
const result = emitStackMapRead(this, read);
|
|
4750
6015
|
if (result.owner)
|
|
4751
6016
|
this.ownSlot(result.owner.slot, result.owner.type);
|
|
4752
6017
|
return result.value;
|
|
4753
6018
|
}
|
|
4754
|
-
|
|
6019
|
+
const arrayRead = this.optionalArrayReads.get(e);
|
|
6020
|
+
if (arrayRead)
|
|
6021
|
+
return emitProjectedArrayRead(this, arrayRead);
|
|
6022
|
+
const narrow = this.checkedNarrows.get(e);
|
|
6023
|
+
const narrowed = narrow ? emitCheckedNarrowReceiver(this, narrow) : null;
|
|
6024
|
+
if (narrowed)
|
|
6025
|
+
return narrowed;
|
|
6026
|
+
if (canStackUnion(e, this.boxedUnionsById))
|
|
4755
6027
|
return emitStackUnion(this, e).value;
|
|
4756
6028
|
if (e.kind === "unionNarrow") {
|
|
4757
|
-
const union = this.
|
|
4758
|
-
return { name: this.unionPeek(union.name), type: e.type };
|
|
6029
|
+
const union = this.emitUnionProjection(e.value);
|
|
6030
|
+
return { name: this.unionPeek(union.name, e.unionId), type: e.type };
|
|
6031
|
+
}
|
|
6032
|
+
if ((e.kind === "downcast" || e.kind === "upcast") && e.value.type.kind === "object") {
|
|
6033
|
+
// Prefix layouts: a class cast reads the operand's own pointer.
|
|
6034
|
+
return { name: this.emitReadReceiver(e.value).name, type: e.type };
|
|
4759
6035
|
}
|
|
6036
|
+
// A nullable-pointer field has no box to borrow. Projections use
|
|
6037
|
+
// emitUnionProjection; other borrowers get an owned heap box.
|
|
6038
|
+
if (this.nullableFieldGet(e))
|
|
6039
|
+
return this.emitExpr(e);
|
|
4760
6040
|
if (e.kind === "recordGet" || e.kind === "fieldGet") {
|
|
4761
6041
|
const receiver = this.emitReadReceiver(e.obj);
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
: this.
|
|
6042
|
+
if (e.kind === "recordGet") {
|
|
6043
|
+
const { ptr, type } = this.recordFieldPtr(receiver.name, e.shapeId, e.field);
|
|
6044
|
+
return { name: this.loadRecordField(ptr, type), type: e.type };
|
|
6045
|
+
}
|
|
6046
|
+
const { ptr, type } = this.classFieldPtr(receiver.name, e.className, e.field);
|
|
6047
|
+
if (this.int32Slots.isField(e.className, e.field))
|
|
6048
|
+
return this.loadInt32Field(ptr, e.type);
|
|
4765
6049
|
return { name: this.loadField(ptr, type), type: e.type };
|
|
4766
6050
|
}
|
|
4767
6051
|
if (e.kind === "ternary" && this.canBorrowReceiver(e.then) && this.canBorrowReceiver(e.else_)) {
|
|
@@ -4789,8 +6073,160 @@ export class LlEmitter {
|
|
|
4789
6073
|
B.line(`${value} = load ptr, ptr ${slot}`);
|
|
4790
6074
|
return { name: value, type: e.type };
|
|
4791
6075
|
}
|
|
6076
|
+
if (e.kind === "call" && this.canBorrowReceiver(e))
|
|
6077
|
+
return emitBorrowedResultCall(this, e);
|
|
4792
6078
|
return this.emitExpr(e);
|
|
4793
6079
|
}
|
|
6080
|
+
/** A private stack union source with an independently owned payload. */
|
|
6081
|
+
canStackReceiver(e) {
|
|
6082
|
+
return this.isStackUnionSource(e);
|
|
6083
|
+
}
|
|
6084
|
+
/** True for union sources that emitReadReceiver can produce as a private
|
|
6085
|
+
* stack box with an independently owned payload (no heap union). */
|
|
6086
|
+
isStackUnionSource(e) {
|
|
6087
|
+
if (e.type.kind !== "union")
|
|
6088
|
+
return false;
|
|
6089
|
+
return (canStackUnion(e, this.boxedUnionsById) ||
|
|
6090
|
+
matchMapRead(e, this.boxedUnionsById) !== null ||
|
|
6091
|
+
this.optionalArrayReads.get(e) !== null);
|
|
6092
|
+
}
|
|
6093
|
+
/** The nullable-pointer representation behind a union-typed class field
|
|
6094
|
+
* read, or null for every other expression. */
|
|
6095
|
+
nullableFieldGet(e) {
|
|
6096
|
+
if (e.kind !== "fieldGet" || e.type.kind !== "union")
|
|
6097
|
+
return null;
|
|
6098
|
+
return this.nullableFields.get(e.className, e.field);
|
|
6099
|
+
}
|
|
6100
|
+
/** A union operand consumed only by a projection (tag test, payload
|
|
6101
|
+
* extraction, arm compare, truthiness). A nullable-pointer field read
|
|
6102
|
+
* becomes a private stack box whose payload is borrowed from the field:
|
|
6103
|
+
* the consumer must finish before any later expression can store to the
|
|
6104
|
+
* field, and must never retain, release, or store the box. Every other
|
|
6105
|
+
* operand takes the ordinary borrowed-receiver path. */
|
|
6106
|
+
emitUnionProjection(e) {
|
|
6107
|
+
const nullable = this.nullableFieldGet(e);
|
|
6108
|
+
if (!nullable || e.kind !== "fieldGet")
|
|
6109
|
+
return this.emitReadReceiver(e);
|
|
6110
|
+
const B = this.B;
|
|
6111
|
+
const receiver = this.emitReadReceiver(e.obj);
|
|
6112
|
+
const { ptr } = this.classFieldPtr(receiver.name, e.className, e.field);
|
|
6113
|
+
const p = B.tmp();
|
|
6114
|
+
B.line(`${p} = load ptr, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
|
|
6115
|
+
return { name: this.stackNullableBox(p, nullable), type: e.type };
|
|
6116
|
+
}
|
|
6117
|
+
/** A private stack box over a nullable-pointer field read whose payload
|
|
6118
|
+
* is retained into the current frame: an independent owner, so the box
|
|
6119
|
+
* may outlive later stores to the field (call arguments, local copies).
|
|
6120
|
+
* The box itself must still never reach an RC entry point. */
|
|
6121
|
+
emitOwnedNullableStack(e) {
|
|
6122
|
+
const nullable = this.nullableFieldGet(e);
|
|
6123
|
+
if (!nullable || e.kind !== "fieldGet")
|
|
6124
|
+
throw new InternalCompilerError("llvm emitter bug: owned nullable stack of non-field");
|
|
6125
|
+
const receiver = this.emitReadReceiver(e.obj);
|
|
6126
|
+
const { ptr } = this.classFieldPtr(receiver.name, e.className, e.field);
|
|
6127
|
+
const p = this.B.tmp();
|
|
6128
|
+
this.B.line(`${p} = load ptr, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
|
|
6129
|
+
const owner = this.own({ name: this.retainValue(p, nullable.arm), type: nullable.arm });
|
|
6130
|
+
return { box: this.stackNullableBox(owner.name, nullable), owner };
|
|
6131
|
+
}
|
|
6132
|
+
/** A private stack box over a borrowed nullable pointer. */
|
|
6133
|
+
stackNullableBox(p, nullable) {
|
|
6134
|
+
const B = this.B;
|
|
6135
|
+
const box = B.slot();
|
|
6136
|
+
B.entryAllocas.push(`${box} = alloca %ScrUnion`);
|
|
6137
|
+
const isNull = B.tmp(), tag = B.tmp(), tagPtr = B.tmp(), slot = B.tmp();
|
|
6138
|
+
B.line(`${isNull} = icmp eq ptr ${p}, null`);
|
|
6139
|
+
B.line(`${tag} = select i1 ${isNull}, i32 ${nullable.unitTag}, i32 ${nullable.refTag}`);
|
|
6140
|
+
B.line(`${tagPtr} = getelementptr inbounds %ScrUnion, ptr ${box}, i64 0, i32 1`);
|
|
6141
|
+
B.line(`store i32 ${tag}, ptr ${tagPtr}`);
|
|
6142
|
+
B.line(`${slot} = getelementptr inbounds %ScrUnion, ptr ${box}, i64 0, i32 5`);
|
|
6143
|
+
B.line(`store i64 0, ptr ${slot}`);
|
|
6144
|
+
B.line(`store ptr ${p}, ptr ${slot}`);
|
|
6145
|
+
return box;
|
|
6146
|
+
}
|
|
6147
|
+
/** The owned (+1) union value of a nullable-pointer slot: the unit arm's
|
|
6148
|
+
* immortal instance, or a fresh heap box around a retained instance. */
|
|
6149
|
+
nullableToOwnedUnion(p, nullable) {
|
|
6150
|
+
const B = this.B;
|
|
6151
|
+
const slot = B.slot();
|
|
6152
|
+
B.entryAllocas.push(`${slot} = alloca ptr`);
|
|
6153
|
+
const isNull = B.tmp();
|
|
6154
|
+
const unit = B.newLabel("nf.u"), ref = B.newLabel("nf.r"), join = B.newLabel("nf.j");
|
|
6155
|
+
B.line(`${isNull} = icmp eq ptr ${p}, null`);
|
|
6156
|
+
B.condBr(isNull, unit, ref);
|
|
6157
|
+
B.startBlock(unit);
|
|
6158
|
+
B.line(`store ptr ${this.unitInstanceRef(nullable.unionId, nullable.unitTag)}, ptr ${slot}`);
|
|
6159
|
+
B.br(join);
|
|
6160
|
+
B.startBlock(ref);
|
|
6161
|
+
const owned = this.retainValue(p, nullable.arm);
|
|
6162
|
+
const box = this.unionNewOwned(nullable.unionId, nullable.refTag, {
|
|
6163
|
+
name: owned,
|
|
6164
|
+
type: nullable.arm,
|
|
6165
|
+
});
|
|
6166
|
+
B.line(`store ptr ${box}, ptr ${slot}`);
|
|
6167
|
+
B.br(join);
|
|
6168
|
+
B.startBlock(join);
|
|
6169
|
+
const t = B.tmp();
|
|
6170
|
+
B.line(`${t} = load ptr, ptr ${slot}`);
|
|
6171
|
+
return t;
|
|
6172
|
+
}
|
|
6173
|
+
/** The owned (+1) instance pointer (NULL for the unit arm) of a union
|
|
6174
|
+
* box, which stays owned by its existing owner. */
|
|
6175
|
+
unionToNullable(u, nullable) {
|
|
6176
|
+
const B = this.B;
|
|
6177
|
+
const tag = this.unionTag(u, nullable.unionId);
|
|
6178
|
+
const isRef = B.tmp(), sel = B.tmp();
|
|
6179
|
+
B.line(`${isRef} = icmp eq i32 ${tag}, ${nullable.refTag}`);
|
|
6180
|
+
const payload = this.unionPeek(u, nullable.unionId);
|
|
6181
|
+
B.line(`${sel} = select i1 ${isRef}, ptr ${payload}, ptr null`);
|
|
6182
|
+
return this.retainValue(sel, nullable.arm);
|
|
6183
|
+
}
|
|
6184
|
+
/** `obj.f = value` for a nullable-pointer field: the new instance pointer
|
|
6185
|
+
* is produced without a union box where the value is a wrap, and as a
|
|
6186
|
+
* retained payload of a borrowed box otherwise. Unlink-then-release like
|
|
6187
|
+
* every field store. */
|
|
6188
|
+
emitNullableFieldStore(obj, className, field, nullable, value) {
|
|
6189
|
+
const B = this.B;
|
|
6190
|
+
let next;
|
|
6191
|
+
if (value.kind === "unionWrap" &&
|
|
6192
|
+
value.unionId === nullable.unionId &&
|
|
6193
|
+
(value.tag === nullable.refTag
|
|
6194
|
+
? typeEquals(value.value.type, nullable.arm)
|
|
6195
|
+
: value.tag === nullable.unitTag)) {
|
|
6196
|
+
if (value.tag === nullable.refTag) {
|
|
6197
|
+
const inner = this.emitExpr(value.value);
|
|
6198
|
+
this.moveTemp(inner);
|
|
6199
|
+
next = inner.name;
|
|
6200
|
+
}
|
|
6201
|
+
else {
|
|
6202
|
+
// Unit payloads carry nothing; a void payload runs for effects.
|
|
6203
|
+
if (!isUnitType(value.value.type))
|
|
6204
|
+
this.emitExpr(value.value);
|
|
6205
|
+
next = "null";
|
|
6206
|
+
}
|
|
6207
|
+
}
|
|
6208
|
+
else if (value.kind === "varRef" && !this.canBorrowReceiver(value)) {
|
|
6209
|
+
next = this.unionToNullable(this.emitExpr(value).name, nullable);
|
|
6210
|
+
}
|
|
6211
|
+
else {
|
|
6212
|
+
next = this.unionToNullable(this.emitUnionProjection(value).name, nullable);
|
|
6213
|
+
}
|
|
6214
|
+
const { ptr } = this.classFieldPtr(obj, className, field);
|
|
6215
|
+
const old = B.tmp();
|
|
6216
|
+
B.line(`${old} = load ptr, ptr ${ptr}`);
|
|
6217
|
+
B.line(`store ptr ${next}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
|
|
6218
|
+
this.releaseValue(old, nullable.arm);
|
|
6219
|
+
}
|
|
6220
|
+
/** A union binding whose slot may hold a private stack box: stack locals
|
|
6221
|
+
* and projection-only parameters (callers may pass stack boxes). Such a
|
|
6222
|
+
* box must only be projected: never retained, released, or stored. */
|
|
6223
|
+
isStackUnionLocal(localId) {
|
|
6224
|
+
return (this.projectedParameters.has(localId) ||
|
|
6225
|
+
this.localStackUnions.has(localId) ||
|
|
6226
|
+
this.localUnionStorage.has(localId) ||
|
|
6227
|
+
this.localArrayReads.has(localId) ||
|
|
6228
|
+
this.mapReadLifetimes.locals.has(localId));
|
|
6229
|
+
}
|
|
4794
6230
|
canBorrowReceiver(e) {
|
|
4795
6231
|
switch (e.kind) {
|
|
4796
6232
|
case "varRef": {
|
|
@@ -4802,20 +6238,166 @@ export class LlEmitter {
|
|
|
4802
6238
|
isRefCounted(e.type)));
|
|
4803
6239
|
}
|
|
4804
6240
|
case "unionNarrow":
|
|
6241
|
+
// Class casts reinterpret the same pointer (prefix layouts).
|
|
6242
|
+
case "downcast":
|
|
6243
|
+
case "upcast":
|
|
4805
6244
|
return this.canBorrowReceiver(e.value);
|
|
4806
6245
|
case "fieldGet":
|
|
4807
6246
|
case "recordGet":
|
|
4808
6247
|
return this.canBorrowReceiver(e.obj);
|
|
4809
6248
|
case "ternary":
|
|
4810
6249
|
return this.canBorrowReceiver(e.then) && this.canBorrowReceiver(e.else_);
|
|
6250
|
+
case "seqExpr": {
|
|
6251
|
+
const guarded = guardedValue(e);
|
|
6252
|
+
return guarded !== null && this.canBorrowReceiver(guarded);
|
|
6253
|
+
}
|
|
4811
6254
|
// The runtime always sets a pending exception; its unreachable
|
|
4812
6255
|
// typed dummy is null and owns no receiver.
|
|
4813
6256
|
case "libCall":
|
|
4814
6257
|
return e.fn === "error.nodeThrow";
|
|
6258
|
+
case "unionWrap": {
|
|
6259
|
+
const wrap = this.borrowableNullableWrap(e);
|
|
6260
|
+
return wrap === "unit" || (wrap === "ref" && this.canBorrowReceiver(e.value));
|
|
6261
|
+
}
|
|
6262
|
+
// A borrowed-return callee preserves edges, so its result stays
|
|
6263
|
+
// reachable from borrowable arguments until the consumer runs.
|
|
6264
|
+
case "call":
|
|
6265
|
+
return (this.borrowedReturns.has(e.callee) &&
|
|
6266
|
+
e.args.every((arg) => (!isRefCounted(arg.type) || this.canBorrowReceiver(arg)) &&
|
|
6267
|
+
this.referenceEffects.preserves(arg)));
|
|
6268
|
+
// A required element read: the (borrowed) array owns the element
|
|
6269
|
+
// while an edge-preserving index computes.
|
|
6270
|
+
case "arrayGet":
|
|
6271
|
+
return (e.arr.type.kind === "array" &&
|
|
6272
|
+
isRefCounted(e.arr.type.elem) &&
|
|
6273
|
+
this.canBorrowReceiver(e.arr) &&
|
|
6274
|
+
this.referenceEffects.preserves(e.index));
|
|
6275
|
+
default:
|
|
6276
|
+
return false;
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
/** A reference held as one plain pointer that emitReadReceiver can read
|
|
6280
|
+
* without owning: class instances, records, arrays, strings and
|
|
6281
|
+
* nullable-pointer unions. Tagged union boxes, closures and dynamic
|
|
6282
|
+
* values keep ordinary ownership. */
|
|
6283
|
+
plainReference(type) {
|
|
6284
|
+
switch (type.kind) {
|
|
6285
|
+
case "object":
|
|
6286
|
+
case "record":
|
|
6287
|
+
case "array":
|
|
6288
|
+
case "string":
|
|
6289
|
+
return true;
|
|
6290
|
+
case "union":
|
|
6291
|
+
return this.nullableUnions.has(type.unionId);
|
|
6292
|
+
default:
|
|
6293
|
+
return false;
|
|
6294
|
+
}
|
|
6295
|
+
}
|
|
6296
|
+
/** Functions returning a walk of their borrowed parameters return it
|
|
6297
|
+
* without a reference (+0) from their borrowing body; direct callers
|
|
6298
|
+
* either consume it as a receiver/walk or retain it at once, and the owned
|
|
6299
|
+
* adapter retains before releasing its parameters. Candidates preserve
|
|
6300
|
+
* every heap edge (so the result stays reachable from the arguments),
|
|
6301
|
+
* borrow every reference parameter and return plain pointers. The set
|
|
6302
|
+
* grows to a fixpoint because a call to a borrowed-return function with
|
|
6303
|
+
* walk arguments is itself a walk; walk facts are then recomputed with
|
|
6304
|
+
* the final set, admitting only parameters whose convention borrows. */
|
|
6305
|
+
analyzeBorrowedReturns() {
|
|
6306
|
+
const pointer = (type) => this.plainReference(type);
|
|
6307
|
+
const borrowedIds = (fn) => {
|
|
6308
|
+
const indexes = this.callLifetimes.borrowed.get(fn.name);
|
|
6309
|
+
return new Set(fn.params.filter((_, i) => indexes?.has(i)).map((p) => p.localId));
|
|
6310
|
+
};
|
|
6311
|
+
const hostFor = (fn) => {
|
|
6312
|
+
const allowed = borrowedIds(fn);
|
|
6313
|
+
return {
|
|
6314
|
+
pointerLocal: (local) => pointer(local.type),
|
|
6315
|
+
borrowsWithoutOwning: (e) => this.borrowsWithoutOwning(e),
|
|
6316
|
+
borrowedReturn: (callee) => this.borrowedReturns.has(callee),
|
|
6317
|
+
parameterAllowed: (id) => allowed.has(id),
|
|
6318
|
+
};
|
|
6319
|
+
};
|
|
6320
|
+
const preserving = [...this.fnByName.values()].filter((fn) => this.referenceEffects.functions.has(fn.name));
|
|
6321
|
+
const candidates = preserving.filter((fn) => {
|
|
6322
|
+
if (!pointer(fn.returnType) || fn.async || fn.generator || fn.captures)
|
|
6323
|
+
return false;
|
|
6324
|
+
// A borrowing vtable slot dispatches to the borrowing body (or its
|
|
6325
|
+
// virtual adapter) directly, and virtual callers own the result.
|
|
6326
|
+
if (this.implSlotBorrowed(fn.name).size > 0)
|
|
6327
|
+
return false;
|
|
6328
|
+
const indexes = this.callLifetimes.borrowed.get(fn.name);
|
|
6329
|
+
return (indexes !== undefined &&
|
|
6330
|
+
indexes.size > 0 &&
|
|
6331
|
+
fn.params.every((p, i) => !isRefCounted(p.type) || indexes.has(i)));
|
|
6332
|
+
});
|
|
6333
|
+
for (let changed = true; changed;) {
|
|
6334
|
+
changed = false;
|
|
6335
|
+
for (const fn of candidates) {
|
|
6336
|
+
if (this.borrowedReturns.has(fn.name))
|
|
6337
|
+
continue;
|
|
6338
|
+
if (!analyzeWalks(fn, hostFor(fn)).returnsWalk)
|
|
6339
|
+
continue;
|
|
6340
|
+
this.borrowedReturns.add(fn.name);
|
|
6341
|
+
changed = true;
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
6344
|
+
if (this.borrowedReturns.size === 0)
|
|
6345
|
+
return;
|
|
6346
|
+
for (const fn of preserving) {
|
|
6347
|
+
const walks = analyzeWalks(fn, hostFor(fn)).locals;
|
|
6348
|
+
if (walks.size > 0)
|
|
6349
|
+
this.walkBorrowsByFunction.set(fn.name, walks);
|
|
6350
|
+
}
|
|
6351
|
+
}
|
|
6352
|
+
/** emitReadReceiver produces this node's pointer without acquiring a
|
|
6353
|
+
* reference, given operands (and ternary arms) that do the same: plain
|
|
6354
|
+
* class/record field reads, class casts, nullable-pointer narrows and
|
|
6355
|
+
* wraps, and checked ternaries. Stack-boxed, map-read and boxed-field
|
|
6356
|
+
* sources are owned. Depends only on module facts, not the current
|
|
6357
|
+
* function. */
|
|
6358
|
+
borrowsWithoutOwning(e) {
|
|
6359
|
+
if (!isRefCounted(e.type) || this.isStackUnionSource(e))
|
|
6360
|
+
return false;
|
|
6361
|
+
if (matchMapRead(e, this.boxedUnionsById))
|
|
6362
|
+
return false;
|
|
6363
|
+
switch (e.kind) {
|
|
6364
|
+
case "fieldGet":
|
|
6365
|
+
return this.nullableFieldGet(e) === null;
|
|
6366
|
+
case "recordGet":
|
|
6367
|
+
return true;
|
|
6368
|
+
case "unionNarrow":
|
|
6369
|
+
return this.nullableUnions.has(e.unionId);
|
|
6370
|
+
case "downcast":
|
|
6371
|
+
case "upcast":
|
|
6372
|
+
return e.value.type.kind === "object";
|
|
6373
|
+
case "unionWrap":
|
|
6374
|
+
return this.borrowableNullableWrap(e) !== null;
|
|
6375
|
+
// The caller proves each arm separately.
|
|
6376
|
+
case "ternary":
|
|
6377
|
+
return true;
|
|
6378
|
+
// The caller proves the arguments are walks; their evaluation must not
|
|
6379
|
+
// remove an edge before the call reads them.
|
|
6380
|
+
case "call":
|
|
6381
|
+
return (this.borrowedReturns.has(e.callee) &&
|
|
6382
|
+
e.args.every((arg) => this.referenceEffects.preserves(arg)));
|
|
4815
6383
|
default:
|
|
4816
6384
|
return false;
|
|
4817
6385
|
}
|
|
4818
6386
|
}
|
|
6387
|
+
/** A wrap into a nullable-pointer union is the payload pointer itself
|
|
6388
|
+
* (the reference arm) or an immortal constant (a unit arm), so it can
|
|
6389
|
+
* borrow exactly when its payload can. Void payloads run for effects and
|
|
6390
|
+
* keep the ordinary path; tagged unions still construct a box. */
|
|
6391
|
+
borrowableNullableWrap(e) {
|
|
6392
|
+
if (e.kind !== "unionWrap")
|
|
6393
|
+
return null;
|
|
6394
|
+
const nullable = this.nullableUnions.get(e.unionId);
|
|
6395
|
+
if (!nullable)
|
|
6396
|
+
return null;
|
|
6397
|
+
if (isUnitType(e.value.type))
|
|
6398
|
+
return e.value.kind === "unitLit" ? "unit" : null;
|
|
6399
|
+
return e.tag === nullable.refTag && isRefCounted(e.value.type) ? "ref" : null;
|
|
6400
|
+
}
|
|
4819
6401
|
materializeSplitLocal(localId) {
|
|
4820
6402
|
const span = this.splitSpans.get(localId);
|
|
4821
6403
|
if (span)
|
|
@@ -4902,8 +6484,8 @@ export class LlEmitter {
|
|
|
4902
6484
|
resolveThunkFor(inner) {
|
|
4903
6485
|
return resolveThunkFor(this, inner);
|
|
4904
6486
|
}
|
|
4905
|
-
tagInSet(uName, tags) {
|
|
4906
|
-
return tagInSet(this, uName, tags);
|
|
6487
|
+
tagInSet(uName, unionId, tags) {
|
|
6488
|
+
return tagInSet(this, uName, unionId, tags);
|
|
4907
6489
|
}
|
|
4908
6490
|
arrPush(arr, acc, value) {
|
|
4909
6491
|
return arrPush(this, arr, acc, value);
|
|
@@ -4947,8 +6529,8 @@ export class LlEmitter {
|
|
|
4947
6529
|
emitBytesLength(elem, receiver, bytes) {
|
|
4948
6530
|
return emitBytesLength(this, elem, receiver, bytes);
|
|
4949
6531
|
}
|
|
4950
|
-
emitBytesGet(elem, receiver, index, expr, inBounds = false) {
|
|
4951
|
-
return emitBytesGet(this, elem, receiver, index, expr, inBounds);
|
|
6532
|
+
emitBytesGet(elem, receiver, index, expr, inBounds = false, invalidNaN = false) {
|
|
6533
|
+
return emitBytesGet(this, elem, receiver, index, expr, inBounds, invalidNaN);
|
|
4952
6534
|
}
|
|
4953
6535
|
emitToUint32(value, expr, uint32) {
|
|
4954
6536
|
return emitToUint32(this, value, expr, uint32);
|