@scriptc/compiler 0.2.7 → 0.2.9
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 +3 -0
- package/dist/backend/cycle-analysis.js +50 -17
- package/dist/backend/cycle-analysis.js.map +1 -1
- package/dist/backend/cycle-analysis.test.js +138 -14
- package/dist/backend/cycle-analysis.test.js.map +1 -1
- package/dist/backend/cycle-mutability.d.ts +31 -6
- package/dist/backend/cycle-mutability.js +120 -18
- package/dist/backend/cycle-mutability.js.map +1 -1
- package/dist/backend/llvm/alloc.d.ts +16 -3
- package/dist/backend/llvm/alloc.js +78 -23
- package/dist/backend/llvm/alloc.js.map +1 -1
- package/dist/backend/llvm/alloc.test.js +5 -5
- package/dist/backend/llvm/alloc.test.js.map +1 -1
- package/dist/backend/llvm/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/byte-numbers.js +2 -3
- package/dist/backend/llvm/byte-numbers.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.d.ts +4 -1
- package/dist/backend/llvm/call-lifetimes.js +14 -3
- package/dist/backend/llvm/call-lifetimes.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.test.js +8 -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 +13 -1
- package/dist/backend/llvm/classes.js +32 -23
- package/dist/backend/llvm/classes.js.map +1 -1
- package/dist/backend/llvm/classes.test.js +32 -3
- package/dist/backend/llvm/classes.test.js.map +1 -1
- package/dist/backend/llvm/common.d.ts +17 -0
- package/dist/backend/llvm/common.js +28 -0
- package/dist/backend/llvm/common.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.js +1 -1
- package/dist/backend/llvm/dense-array-access.test.js +89 -0
- package/dist/backend/llvm/dense-array-access.test.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +8 -4
- package/dist/backend/llvm/dyn.js +58 -31
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +227 -12
- package/dist/backend/llvm/emitter.js +1492 -169
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.test.js +54 -1
- package/dist/backend/llvm/emitter.test.js.map +1 -1
- package/dist/backend/llvm/expr-async.js +4 -4
- package/dist/backend/llvm/expr-async.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.d.ts +1 -1
- package/dist/backend/llvm/expr-bytes.js +116 -32
- 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 +55 -10
- 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 +231 -28
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-context.d.ts +2 -0
- package/dist/backend/llvm/expr-control.js +70 -14
- package/dist/backend/llvm/expr-control.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +75 -21
- 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.js +41 -12
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.test.js +50 -2
- package/dist/backend/llvm/expr-primitives.test.js.map +1 -1
- package/dist/backend/llvm/expr-records.js +28 -14
- package/dist/backend/llvm/expr-records.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.d.ts +3 -0
- package/dist/backend/llvm/expr-stream-bridges.js +114 -42
- package/dist/backend/llvm/expr-stream-bridges.js.map +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/layouts.d.ts +2 -1
- package/dist/backend/llvm/layouts.js +2 -2
- package/dist/backend/llvm/layouts.js.map +1 -1
- 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 +25 -13
- 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 +2 -0
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.d.ts +17 -6
- package/dist/backend/llvm/local-array-reads.js +223 -51
- package/dist/backend/llvm/local-array-reads.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.test.js +7 -3
- package/dist/backend/llvm/local-array-reads.test.js.map +1 -1
- package/dist/backend/llvm/local-union-storage.js +10 -6
- package/dist/backend/llvm/local-union-storage.js.map +1 -1
- package/dist/backend/llvm/local-union-storage.test.js +3 -2
- 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 +38 -19
- package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -1
- 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 +140 -17
- 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/shapes.d.ts +58 -19
- package/dist/backend/llvm/shapes.js +171 -62
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/shapes.test.js +36 -11
- package/dist/backend/llvm/shapes.test.js.map +1 -1
- package/dist/backend/llvm/stack-unions.test.js +5 -2
- 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-key-hash.d.ts +9 -2
- package/dist/backend/llvm/string-key-hash.js +20 -3
- package/dist/backend/llvm/string-key-hash.js.map +1 -1
- package/dist/backend/llvm/string-key-hash.test.js +14 -3
- package/dist/backend/llvm/string-key-hash.test.js.map +1 -1
- package/dist/backend/llvm/string-lifetimes.test.js +24 -11
- 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 +79 -0
- package/dist/backend/llvm/string-reads.js.map +1 -0
- package/dist/backend/llvm/string-slices.js +1 -1
- package/dist/backend/llvm/switch-dispatch.test.js +1 -1
- 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/trunc.d.ts +8 -0
- package/dist/backend/llvm/trunc.js +28 -0
- package/dist/backend/llvm/trunc.js.map +1 -0
- package/dist/backend/llvm/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/loop-polls.d.ts +14 -0
- package/dist/backend/loop-polls.js +100 -0
- package/dist/backend/loop-polls.js.map +1 -0
- package/dist/backend/loop-polls.test.d.ts +1 -0
- package/dist/backend/loop-polls.test.js +86 -0
- package/dist/backend/loop-polls.test.js.map +1 -0
- package/dist/backend/may-throw.d.ts +29 -1
- package/dist/backend/may-throw.js +365 -3
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/may-throw.test.js +136 -1
- package/dist/backend/may-throw.test.js.map +1 -1
- package/dist/backend/native/runtime-inputs.d.ts +1 -1
- package/dist/backend/native/runtime-inputs.js +1 -0
- package/dist/backend/native/runtime-inputs.js.map +1 -1
- package/dist/backend/native-codegen-core.d.ts +7 -5
- 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 +2 -0
- package/dist/backend/native-codegen.js +21 -2
- package/dist/backend/native-codegen.js.map +1 -1
- package/dist/backend/native-codegen.test.js +41 -10
- package/dist/backend/native-codegen.test.js.map +1 -1
- package/dist/backend/native-tools.d.ts +2 -0
- package/dist/backend/native-tools.js +3 -0
- package/dist/backend/native-tools.js.map +1 -1
- 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-abi.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.js +1 -1
- package/dist/backend/runtime-pack-targets.test.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +1 -1
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/cli/command.js +78 -8
- package/dist/cli/command.js.map +1 -1
- package/dist/cli/output-options.d.ts +1 -0
- package/dist/cli/output-options.js +3 -0
- package/dist/cli/output-options.js.map +1 -1
- package/dist/cli/usage.d.ts +8 -1
- package/dist/cli/usage.js +14 -1
- package/dist/cli/usage.js.map +1 -1
- package/dist/compile-types.d.ts +17 -3
- 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 +74 -4
- 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 -0
- package/dist/executable/executable-cache.js +3 -0
- package/dist/executable/executable-cache.js.map +1 -1
- package/dist/executable/executable-cache.test.js +1 -0
- package/dist/executable/executable-cache.test.js.map +1 -1
- package/dist/executable/prepare.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/array-values.d.ts +7 -0
- package/dist/frontend/lowering/array-values.js +157 -5
- package/dist/frontend/lowering/array-values.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 +4 -0
- package/dist/frontend/lowering/builtins/process.js +10 -0
- package/dist/frontend/lowering/builtins/process.js.map +1 -1
- package/dist/frontend/lowering/builtins/strings.js +4 -1
- package/dist/frontend/lowering/builtins/strings.js.map +1 -1
- package/dist/frontend/lowering/builtins/workers.js +4 -2
- package/dist/frontend/lowering/builtins/workers.js.map +1 -1
- package/dist/frontend/lowering/call-arguments.js +96 -4
- package/dist/frontend/lowering/call-arguments.js.map +1 -1
- package/dist/frontend/lowering/class-assertions.d.ts +8 -0
- package/dist/frontend/lowering/class-assertions.js +217 -1
- package/dist/frontend/lowering/class-assertions.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.js +8 -5
- package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
- package/dist/frontend/lowering/class-method-mutations.js +88 -0
- package/dist/frontend/lowering/class-method-mutations.js.map +1 -1
- package/dist/frontend/lowering/coercions/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/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/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-callbacks.js +81 -44
- package/dist/frontend/lowering/containers/array-callbacks.js.map +1 -1
- package/dist/frontend/lowering/containers/array-construction.js +15 -2
- package/dist/frontend/lowering/containers/array-construction.js.map +1 -1
- package/dist/frontend/lowering/containers/array-methods.js +231 -12
- package/dist/frontend/lowering/containers/array-methods.js.map +1 -1
- package/dist/frontend/lowering/containers/bytes.js +15 -4
- package/dist/frontend/lowering/containers/bytes.js.map +1 -1
- package/dist/frontend/lowering/containers/callback-arguments.js +43 -1
- package/dist/frontend/lowering/containers/callback-arguments.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 +6 -1
- package/dist/frontend/lowering/cycle-initialization.js +59 -33
- package/dist/frontend/lowering/cycle-initialization.js.map +1 -1
- 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/object-literals.js +40 -2
- package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
- package/dist/frontend/lowering/expressions/union-fields.d.ts +19 -0
- package/dist/frontend/lowering/expressions/union-fields.js +236 -0
- package/dist/frontend/lowering/expressions/union-fields.js.map +1 -0
- package/dist/frontend/lowering/lower-calls.d.ts +5 -0
- package/dist/frontend/lowering/lower-calls.js +237 -158
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +4 -0
- package/dist/frontend/lowering/lower-classes.js +72 -22
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +19 -0
- package/dist/frontend/lowering/lower-exprs.js +592 -109
- package/dist/frontend/lowering/lower-exprs.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 +3 -2
- 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 +10 -7
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +90 -1
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +47 -7
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +83 -2
- package/dist/frontend/lowering/lowerer.js +1158 -79
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/nan-coded-reads.d.ts +161 -0
- package/dist/frontend/lowering/nan-coded-reads.js +1342 -0
- package/dist/frontend/lowering/nan-coded-reads.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-bounds.d.ts +31 -0
- package/dist/frontend/lowering/runtime-optional-bounds.js +360 -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 +94 -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 +2 -1
- package/dist/frontend/lowering/runtime-optional-provenance.js +2 -1
- package/dist/frontend/lowering/runtime-optional-provenance.js.map +1 -1
- 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 +8 -0
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- 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 +25 -0
- package/dist/frontend/program.js +613 -17
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/program.test.js +64 -1
- 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 +3 -0
- package/dist/frontend/type-mapper.js +72 -6
- package/dist/frontend/type-mapper.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 +7 -2
- package/dist/index.js +67 -17
- package/dist/index.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 -0
- package/dist/ir/builtin-signatures.js.map +1 -1
- package/dist/ir/constant-globals.d.ts +14 -0
- package/dist/ir/constant-globals.js +81 -0
- package/dist/ir/constant-globals.js.map +1 -0
- package/dist/ir/constant-globals.test.d.ts +1 -0
- package/dist/ir/constant-globals.test.js +97 -0
- package/dist/ir/constant-globals.test.js.map +1 -0
- package/dist/ir/final-fields.d.ts +35 -0
- package/dist/ir/final-fields.js +187 -0
- package/dist/ir/final-fields.js.map +1 -0
- package/dist/ir/final-fields.test.d.ts +1 -0
- package/dist/ir/final-fields.test.js +106 -0
- package/dist/ir/final-fields.test.js.map +1 -0
- package/dist/ir/int32-slots.d.ts +81 -0
- package/dist/ir/int32-slots.js +850 -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 +249 -0
- package/dist/ir/int32-slots.test.js.map +1 -0
- package/dist/ir/integer-loops.d.ts +5 -0
- package/dist/ir/integer-loops.js +20 -4
- package/dist/ir/integer-loops.js.map +1 -1
- package/dist/ir/integer-loops.test.js +17 -0
- package/dist/ir/integer-loops.test.js.map +1 -1
- 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 +30 -7
- package/dist/ir/ir.js +4 -0
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/serialize.d.ts +12 -2
- package/dist/ir/serialize.js +190 -39
- package/dist/ir/serialize.js.map +1 -1
- package/dist/ir/serialize.test.js +65 -1
- package/dist/ir/serialize.test.js.map +1 -1
- package/dist/ir/validate.js +21 -2
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.js +39 -0
- package/dist/ir/validate.test.js.map +1 -1
- package/dist/native/cache.d.ts +11 -1
- package/dist/native/cache.js +87 -2
- package/dist/native/cache.js.map +1 -1
- package/dist/native/cache.test.js +32 -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 +55 -7
- package/dist/native/compiler.js.map +1 -1
- package/dist/native/prepare.d.ts +7 -1
- package/dist/native/prepare.js +65 -29
- package/dist/native/prepare.js.map +1 -1
- package/dist/native/prepare.test.js +23 -1
- package/dist/native/prepare.test.js.map +1 -1
- package/dist/native/text-file.d.ts +10 -0
- package/dist/native/text-file.js +41 -0
- package/dist/native/text-file.js.map +1 -0
- package/dist/startup-cache.d.ts +2 -0
- package/dist/startup-cache.js +2 -0
- package/dist/startup-cache.js.map +1 -1
- package/dist/text-parts.d.ts +5 -0
- package/dist/text-parts.js +36 -0
- package/dist/text-parts.js.map +1 -0
- package/package.json +20 -20
- package/surface-manifest.json +7 -1
- package/dist/frontend/lowering/expressions/union-field-write.d.ts +0 -8
- package/dist/frontend/lowering/expressions/union-field-write.js +0 -108
- package/dist/frontend/lowering/expressions/union-field-write.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { emitLlvmLayouts } from "./layouts.js";
|
|
2
|
-
import { inlineAllocSupported } from "./alloc.js";
|
|
2
|
+
import { FIRST_EMITTED_CYC_KIND, inlineAllocSupported } from "./alloc.js";
|
|
3
3
|
import { llvmBytes as llBytes } from "../literals.js";
|
|
4
4
|
import { InternalCompilerError } from "../../errors.js";
|
|
5
5
|
/** Lower typed IR to LLVM, using the runtime's C ABI.
|
|
@@ -19,9 +19,11 @@ import { InternalCompilerError } from "../../errors.js";
|
|
|
19
19
|
* Dynamic values and islands share the runtime ABI; embedded npm modules use
|
|
20
20
|
* compressed source tables that the runtime inflates on demand.
|
|
21
21
|
*/
|
|
22
|
+
import { createHash } from "node:crypto";
|
|
22
23
|
import { deflateRawSync } from "node:zlib";
|
|
23
24
|
import { endsWithJump, matchStringSelfConcat, streamTypedRefEligible, undefinedArmTag, } from "../../ir/analysis.js";
|
|
24
25
|
import { emitLibraryIdentityLines } from "../library-identity-markers.js";
|
|
26
|
+
import { loopTestsEveryIteration } from "../loop-polls.js";
|
|
25
27
|
import { CAUGHT, ffiCallbackType, isDynTypedRefType, isFfiContextParam, isRefCounted, isUnitType, POINTER_KINDS, RUNTIME_EMITTER_CLASS, RUNTIME_ERROR_CLASSES, RUNTIME_STREAM_CLASSES, typeEquals, typeKey, STRING, VOID, } from "../../ir/ir.js";
|
|
26
28
|
import { moduleRuntimeFeatures, moduleEmbedsBuiltin, moduleEmbedsCompressedNpm, NPM_COMPRESS_MIN, } from "../../ir/runtime-features.js";
|
|
27
29
|
import { matchIntegerArrayForLoop, matchIntegerBytesForLoop, matchIntegerCountedForLoop, } from "../../ir/integer-loops.js";
|
|
@@ -32,8 +34,11 @@ import { emitByteWindowGuard, findInitializedByteLoopBindings, matchByteWindow,
|
|
|
32
34
|
import { canDeferSplitPiece, emitSplitSpanNext, materializeSplitPiece, emitSplitCursor, emitSplitNext, emitSplitScratch, emitSplitSnapshot, findPrivateSplitLocals, loadSplitSnapshot, storeSplitSnapshot, stringSplit, } from "./split-loops.js";
|
|
33
35
|
import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
|
|
34
36
|
import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js";
|
|
35
|
-
import { everyStmtList } from "../../ir/traverse.js";
|
|
36
|
-
import { analyzeIntegerRanges } from "../../ir/integer-ranges.js";
|
|
37
|
+
import { everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
|
|
38
|
+
import { analyzeIntegerRanges, INT32_RANGE } from "../../ir/integer-ranges.js";
|
|
39
|
+
import { analyzeInt32Slots } from "../../ir/int32-slots.js";
|
|
40
|
+
import { analyzeFinalFields, constructorClass } from "../../ir/final-fields.js";
|
|
41
|
+
import { foldConstantGlobals } from "../../ir/constant-globals.js";
|
|
37
42
|
import { findIntegerViews } from "./integer-views.js";
|
|
38
43
|
import { findInitializerBindings, withInitializerBindings } from "../../ir/initializer-bindings.js";
|
|
39
44
|
import { findConstantNumericTables } from "../../ir/constant-tables.js";
|
|
@@ -47,10 +52,13 @@ import { allocateLocalUnion, findLocalUnionStorage, storeLocalUnion, } from "./l
|
|
|
47
52
|
import { BlockBuilder } from "./blocks.js";
|
|
48
53
|
import { NullableRefFields } from "./nullable-fields.js";
|
|
49
54
|
import { emitLocalArrayRead, emitProjectedArrayRead, emitBorrowedArrayRead, findLocalArrayReads, findCallArrayReads, OptionalArrayReads, } from "./local-array-reads.js";
|
|
55
|
+
import { CheckedNarrows, emitCheckedNarrowReceiver } from "./checked-narrows.js";
|
|
50
56
|
import { emitDenseArrayGet, emitDenseArraySet } from "./dense-array-access.js";
|
|
51
57
|
import { emitStackMapRead, findMapReadLifetimes, matchMapRead, } from "./map-read-lifetimes.js";
|
|
52
|
-
import { emitBorrowedFieldSequence } from "./borrowed-receivers.js";
|
|
58
|
+
import { emitBorrowedFieldSequence, guardedValue } from "./borrowed-receivers.js";
|
|
53
59
|
import { emitBorrowedInput } from "./borrowed-inputs.js";
|
|
60
|
+
import { analyzeWalks, findWalkBorrows } from "./walk-borrows.js";
|
|
61
|
+
import { findReboundParameters } from "./rebound-parameters.js";
|
|
54
62
|
import { ReferenceEffects } from "./reference-effects.js";
|
|
55
63
|
import { LlvmDebugInfo } from "./debug-info.js";
|
|
56
64
|
import { StackCallbacks } from "./stack-callbacks.js";
|
|
@@ -59,18 +67,20 @@ import { AmbientReceiverReaders, findConstantCallbacks } from "./constant-callba
|
|
|
59
67
|
import { findScalarStringSlices, emitStringSliceSnapshot, } from "./string-slices.js";
|
|
60
68
|
import { LazyCaptures } from "./lazy-captures.js";
|
|
61
69
|
import { StackCaptures } from "./stack-captures.js";
|
|
62
|
-
import { ABSENT_FIELD_PAYLOAD, emitFieldAbsentTest, f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, } from "./common.js";
|
|
70
|
+
import { ABSENT_FIELD_PAYLOAD, emitFieldAbsentTest, exactIndexLines, f64Lit, ffiNativeTypeLl, ffiNativeParamLl, ffiNativeReturnLl, llvmCommentText, } from "./common.js";
|
|
63
71
|
import { ffiExtendsNarrowIntegers } from "../targets.js";
|
|
64
72
|
import { emitLiteralExpr, emitOperatorExpr, emitStringExpr, emitContainerExpr, emitRecordExpr, } from "./expr-primitives.js";
|
|
65
73
|
import { emitControlExpr } from "./expr-control.js";
|
|
66
|
-
import { emitCallExpr } from "./expr-calls.js";
|
|
74
|
+
import { emitBorrowedResultCall, emitCallExpr } from "./expr-calls.js";
|
|
67
75
|
import { emitDynamicExpr } from "./expr-dynamic.js";
|
|
68
76
|
import { emitIntrinsicExpr, emitSerializationExpr, emitAsyncExpr } from "./expr-async.js";
|
|
77
|
+
import { PublishWalkers } from "./publish.js";
|
|
78
|
+
import { computePublishedTypes } from "../publication.js";
|
|
69
79
|
import { emitJsInteropExpr, emitExpr } from "./expr-dispatch.js";
|
|
70
80
|
import { emitJsMarshal, emitJsOp, emitJsExit, islandAdapter, islandTypedAdapter, } from "./expr-island.js";
|
|
71
81
|
import { dynKind, raceAdapterFor, genResultThunkFor, childExitThunkFor, childExitSignalThunkFor, childDataThunkFor, execFileThunkFor, ipcMessageThunkFor, ipcSendThunkFor, emitterFixedAdapter, wrapEmitterListener, unwrapNullableClosure, closeBindThunkFor, closeOverrideWrapFor, } from "./expr-callbacks.js";
|
|
72
82
|
import { streamDataAdapter, streamDoneFnFor, cryptoBytesThunkFor, fsRenameThunkFor, streamCbThunkFor, zlibBytesThunkFor, } from "./expr-stream-callbacks.js";
|
|
73
|
-
import { emitStableReceiver, resolveThunkFor, tagInSet, arrPush, emitArrayCopyLoop, emitStrIntrinsic, emitArrIntrinsic, wrapNullable, emitMapNew, mapSet, emitMapLikeIntrinsic, emitSetNew, } from "./expr-containers.js";
|
|
83
|
+
import { emitStableReceiver, resolveThunkFor, tagInSet, arrPush, emitArrayCopyLoop, emitStrIntrinsic, emitArrIntrinsic, emitDiscardedSplice, wrapNullable, emitMapNew, mapSet, emitMapLikeIntrinsic, emitSetNew, } from "./expr-containers.js";
|
|
74
84
|
import { emitIntegerLoopIndex, emitBytesIndex, emitBytesData, emitBytesLength, emitBytesGet, emitToUint32, emitBytesSet, emitBytesIntrinsic, } from "./expr-bytes.js";
|
|
75
85
|
import { emitRegexIntrinsic, emitRecordKeyGet, keyedRecordReadInto } from "./expr-records.js";
|
|
76
86
|
import { dynPromiseAdapter, streamTypedRefCommitAdapter, liveDynUnionRefAdapter, streamTypedRefBoxValue, streamTypedRefMaterializeAdapter, streamFromArrayAdapter, } from "./expr-stream-bridges.js";
|
|
@@ -81,10 +91,16 @@ import { emitNetworkHttpLibCall } from "./lib-network.js";
|
|
|
81
91
|
import { emitAssertInspectLibCall, emitIoLibCall, emitGenericLibCall, emitLibCall, } from "./lib-dispatch.js";
|
|
82
92
|
import { buildClassGraph, classMembershipIntervals, emitClassMembershipHelper, classEnvironmentIndex, classFieldIndex, classStructSym, } from "./classes.js";
|
|
83
93
|
import { LlDyn } from "./dyn.js";
|
|
94
|
+
import { VirtualBorrows } from "./virtual-borrows.js";
|
|
84
95
|
import { LlvmUnsupportedError } from "./unsupported.js";
|
|
85
96
|
import { LlWalkers } from "./walkers.js";
|
|
86
|
-
import {
|
|
87
|
-
import {
|
|
97
|
+
import { isObjectArm, NULLABLE_ABSENT, NULLABLE_NULL, NullableUnions } from "./nullable-unions.js";
|
|
98
|
+
import { emitNullablePresent, emitUnionPeek, emitUnionTag } from "./union-repr.js";
|
|
99
|
+
import { literalCapWord, literalCapWord32 } from "./string-key-hash.js";
|
|
100
|
+
import { NUMBER_MAP_ENTRY_DECL } from "./map-number-lookup.js";
|
|
101
|
+
import { STRING_READ_HELPERS } from "./string-reads.js";
|
|
102
|
+
import { emitConcatInputs, emitMixedConcat, numberPart, stringParts, } from "./string-construction.js";
|
|
103
|
+
import { arrNewCall, boxAccess, boxNewCall, boxReleaseSym, computeTraced, elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, FN_ATTRS, inlineRcDecls, llFieldType, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
|
|
88
104
|
export { LlvmUnsupportedError } from "./unsupported.js";
|
|
89
105
|
function ffiCallbackDummyLl(callback) {
|
|
90
106
|
switch (callback.returns) {
|
|
@@ -114,13 +130,13 @@ function ffiCallbackDummyLl(callback) {
|
|
|
114
130
|
export function emitLlvmModule(mod, options = {}) {
|
|
115
131
|
// Keep source storage intact for debugger inspection in dev builds.
|
|
116
132
|
return new LlEmitter(options.debugSources === undefined
|
|
117
|
-
? scalarizeNumericRecords(specializeNumericCalls(mod, mangleFunction))
|
|
133
|
+
? scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction))
|
|
118
134
|
: mod, options).emit();
|
|
119
135
|
}
|
|
120
136
|
/** Keep large translation units below the host's single-string limit. */
|
|
121
137
|
export function emitLlvmModuleSource(mod, options = {}) {
|
|
122
138
|
const parts = new LlEmitter(options.debugSources === undefined
|
|
123
|
-
? scalarizeNumericRecords(specializeNumericCalls(mod, mangleFunction))
|
|
139
|
+
? scalarizeNumericRecords(specializeNumericCalls(foldConstantGlobals(mod), mangleFunction))
|
|
124
140
|
: mod, options).emitParts();
|
|
125
141
|
const length = parts.reduce((total, part) => total + part.length, Math.max(0, parts.length - 1));
|
|
126
142
|
if (length <= 256 * 1024 * 1024)
|
|
@@ -130,6 +146,41 @@ export function emitLlvmModuleSource(mod, options = {}) {
|
|
|
130
146
|
function llStrBytes(text) {
|
|
131
147
|
return llBytes(Buffer.from(text, "utf8"));
|
|
132
148
|
}
|
|
149
|
+
const NO_BORROWED = new Set();
|
|
150
|
+
/** Whether any node of `e` names the local (reads, writes, declarations,
|
|
151
|
+
* captures). */
|
|
152
|
+
function mentionsLocal(e, localId) {
|
|
153
|
+
const exprNames = (node) => {
|
|
154
|
+
if (node.kind === "varRef" || node.kind === "incDec" || node.kind === "assignExpr")
|
|
155
|
+
return node.localId === localId;
|
|
156
|
+
if (node.kind === "closure")
|
|
157
|
+
return node.captures.includes(localId);
|
|
158
|
+
if (node.kind === "classRef")
|
|
159
|
+
return node.captures?.includes(localId) === true;
|
|
160
|
+
return false;
|
|
161
|
+
};
|
|
162
|
+
const stmtNames = (node) => {
|
|
163
|
+
if (node.kind === "varDecl" ||
|
|
164
|
+
node.kind === "assign" ||
|
|
165
|
+
node.kind === "forOf" ||
|
|
166
|
+
node.kind === "rethrow")
|
|
167
|
+
return node.localId === localId;
|
|
168
|
+
return false;
|
|
169
|
+
};
|
|
170
|
+
let found = false;
|
|
171
|
+
const expr = (node) => {
|
|
172
|
+
if (found || exprNames(node))
|
|
173
|
+
return !(found = true);
|
|
174
|
+
return everyExprChild(node, expr, stmt);
|
|
175
|
+
};
|
|
176
|
+
const stmt = (node) => {
|
|
177
|
+
if (found || stmtNames(node))
|
|
178
|
+
return !(found = true);
|
|
179
|
+
return everyStmtChild(node, expr, stmt);
|
|
180
|
+
};
|
|
181
|
+
expr(e);
|
|
182
|
+
return found;
|
|
183
|
+
}
|
|
133
184
|
export class LlEmitter {
|
|
134
185
|
mod;
|
|
135
186
|
localArrayReads = new Map();
|
|
@@ -176,19 +227,40 @@ export class LlEmitter {
|
|
|
176
227
|
debugScope = null;
|
|
177
228
|
sizeType;
|
|
178
229
|
ffiExtendNarrowIntegers;
|
|
179
|
-
|
|
230
|
+
cycleBufferedOffset;
|
|
231
|
+
/** Emitted ScrCycType descriptors so far (their collector statistics
|
|
232
|
+
* indices; see ShapeHost.nextCycKind). */
|
|
233
|
+
cycKinds = 0;
|
|
180
234
|
wasi;
|
|
235
|
+
/** The target lacks a rounding instruction at its baseline CPU (x86-64
|
|
236
|
+
* before SSE4.1), so `llvm.trunc` would lower to a libm call: emit
|
|
237
|
+
* truncation inline through an integer round trip instead. */
|
|
238
|
+
inlineTrunc;
|
|
239
|
+
/** ELF worker executables give thread-locals the executable TLS models. */
|
|
240
|
+
executableTls;
|
|
241
|
+
/** Source loops enclosing the statement being emitted in the current
|
|
242
|
+
* function body. */
|
|
243
|
+
loopDepth = 0;
|
|
244
|
+
/** Darwin worker executables read the stack guard from a pthread
|
|
245
|
+
* thread-specific-data slot off the thread pointer (scr_runtime.h's
|
|
246
|
+
* SCR_STACK_GUARD_TSD): the architecture names the access sequence. */
|
|
247
|
+
stackGuardTsd;
|
|
181
248
|
emitLibraryIdentity;
|
|
182
249
|
runtimeAbiMarker;
|
|
250
|
+
detachedExit;
|
|
183
251
|
/** Interned string literals: UTF-8 text → { symbol, byte length } —
|
|
184
252
|
* first-use order, the runtime ABI’s determinism discipline. */
|
|
185
253
|
literals = new Map();
|
|
254
|
+
/** Symbols of interned constant data, named by content (contentSymbol). */
|
|
255
|
+
contentSymbols = new Set();
|
|
186
256
|
/** Interned unit-armed union instances: "unionId:tag" → symbol — one
|
|
187
257
|
* immortal (rc == SIZE_MAX) static per (union, unit tag).
|
|
188
258
|
* RC entry points and the collector skip immortals. */
|
|
189
259
|
unitInstances = new Map();
|
|
190
260
|
/** unionId → symbol of the union's immortal ABSENT field-slot instance. */
|
|
191
261
|
absentInstances = new Map();
|
|
262
|
+
/** The nullable-union ABSENT sentinel was referenced (absentInstanceRef). */
|
|
263
|
+
needsNullableAbsent = false;
|
|
192
264
|
immortalValues = new Set();
|
|
193
265
|
/** Regex literal templates: "<flags>/<pattern>" → { symbol, interned
|
|
194
266
|
* source/flags literal refs } — one immortal ScrRegex template per distinct
|
|
@@ -231,6 +303,7 @@ export class LlEmitter {
|
|
|
231
303
|
fnDefText = new Map();
|
|
232
304
|
referenceEffects;
|
|
233
305
|
optionalArrayReads;
|
|
306
|
+
checkedNarrows;
|
|
234
307
|
callArrayReads = new Map();
|
|
235
308
|
mapReadLifetimes = { locals: new Map(), arguments: new Map() };
|
|
236
309
|
callLifetimes;
|
|
@@ -248,6 +321,18 @@ export class LlEmitter {
|
|
|
248
321
|
/** Parameters proven projection-only: callers may pass stack boxes. */
|
|
249
322
|
projectedParameters = new Set();
|
|
250
323
|
stableCallBindings = new Set();
|
|
324
|
+
/** Locals and rebound parameters holding borrowed pointers for their
|
|
325
|
+
* whole lifetime (walk-borrows.ts), per function and for the current one. */
|
|
326
|
+
walkBorrowsByFunction = new Map();
|
|
327
|
+
walkBorrows = new Set();
|
|
328
|
+
/** Functions whose borrowing body returns its result without a reference
|
|
329
|
+
* (analyzeBorrowedReturns), and whether the current body is one. */
|
|
330
|
+
borrowedReturns = new Set();
|
|
331
|
+
/** Rebound parameters that keep the borrowed convention with an owner
|
|
332
|
+
* slot (rebound-parameters.ts), per function, and the current owner slots. */
|
|
333
|
+
reboundByFunction = new Map();
|
|
334
|
+
reboundOwnerSlots = new Map();
|
|
335
|
+
currentBorrowedReturn = false;
|
|
251
336
|
/** Manifest-bound native imports, used by ffiCall emission. */
|
|
252
337
|
ffiByName = new Map();
|
|
253
338
|
/** C-ABI callback trampolines and (for raw/no-userdata callbacks) their
|
|
@@ -266,6 +351,15 @@ export class LlEmitter {
|
|
|
266
351
|
* checks are emitted only after calls that can actually raise. */
|
|
267
352
|
mayThrow;
|
|
268
353
|
indirectMayThrow;
|
|
354
|
+
/** Worker programs: the functions that poll for termination on entry
|
|
355
|
+
* (workerTerminationPolls); null outside worker programs. */
|
|
356
|
+
workerEntryPolls;
|
|
357
|
+
/** Functions that check the native stack guard on entry
|
|
358
|
+
* (stackCheckedFunctions); null where the runtime has no guard. */
|
|
359
|
+
stackChecks;
|
|
360
|
+
/** Static types whose values can be published (@scriptc/threads): their
|
|
361
|
+
* stores carry the frozen-object guard. Null without publish calls. */
|
|
362
|
+
publishedTypes;
|
|
269
363
|
/** Method names with at least one may-throw implementation — the
|
|
270
364
|
* virtualCall pending check's key. */
|
|
271
365
|
mayThrowMethods = new Set();
|
|
@@ -287,6 +381,10 @@ export class LlEmitter {
|
|
|
287
381
|
dynPromiseAdapters = new Map();
|
|
288
382
|
unionsById = new Map();
|
|
289
383
|
nullableFields;
|
|
384
|
+
/** Unions whose values are nullable arm pointers (nullable-unions.ts). */
|
|
385
|
+
nullableUnions;
|
|
386
|
+
/** The union table without nullable unions: the stack-box analyses. */
|
|
387
|
+
boxedUnionsById;
|
|
290
388
|
recordsById = new Map();
|
|
291
389
|
recordCloneShapes = new Set();
|
|
292
390
|
tracedShapes;
|
|
@@ -294,6 +392,8 @@ export class LlEmitter {
|
|
|
294
392
|
/** The class graph (buildClassGraph): preorder numbering, hierarchy
|
|
295
393
|
* membership, virtual slot lists. */
|
|
296
394
|
classMeta;
|
|
395
|
+
/** Which vtable slots borrow which parameters (virtual-borrows.ts). */
|
|
396
|
+
virtualBorrows;
|
|
297
397
|
/** Class objects (classes as first-class values): className → the
|
|
298
398
|
* interned .name literal ref — registered during body emission, the
|
|
299
399
|
* statics and construct thunks assemble around the bodies. */
|
|
@@ -334,11 +434,18 @@ export class LlEmitter {
|
|
|
334
434
|
storedSplits = new Map();
|
|
335
435
|
streamingSplitsEnabled = false;
|
|
336
436
|
initializerBindings;
|
|
437
|
+
/** Whole-program int32 storage and parameter/result facts. */
|
|
438
|
+
int32Slots;
|
|
337
439
|
numericLocals = new Map();
|
|
338
440
|
captureIds = new Set();
|
|
339
441
|
/** Active induction bindings retain their width independently of size_t. */
|
|
340
442
|
integerLoopBindings = new Map();
|
|
341
443
|
countedLoopsEnabled = false;
|
|
444
|
+
/** Whole-program final reference fields (ir/final-fields.ts). */
|
|
445
|
+
finalFields;
|
|
446
|
+
/** The current function may borrow final fields of borrowable objects:
|
|
447
|
+
* not a constructor (where they change), not async or a generator. */
|
|
448
|
+
finalFieldBorrows = false;
|
|
342
449
|
integerRanges = new Map();
|
|
343
450
|
byteWindowsEnabled = true;
|
|
344
451
|
byteWindowEntries = new Map();
|
|
@@ -380,23 +487,49 @@ export class LlEmitter {
|
|
|
380
487
|
stmt: () => true,
|
|
381
488
|
expr: (expr) => !(expr.kind === "libCall" && expr.fn === "error.stack"),
|
|
382
489
|
}));
|
|
490
|
+
this.nullableUnions = new NullableUnions(mod);
|
|
383
491
|
this.debug =
|
|
384
492
|
options.debugSources === undefined
|
|
385
493
|
? null
|
|
386
|
-
: new LlvmDebugInfo(mod.sourceFile, options.debugSources, options.pointerBits, mod.unions);
|
|
494
|
+
: new LlvmDebugInfo(mod.sourceFile, options.debugSources, options.pointerBits, mod.unions, this.nullableUnions.armTypes());
|
|
387
495
|
this.constantNumericTables = findConstantNumericTables(mod);
|
|
388
496
|
this.initializerBindings = findInitializerBindings(mod);
|
|
497
|
+
this.int32Slots = analyzeInt32Slots(mod);
|
|
498
|
+
this.finalFields = analyzeFinalFields(mod);
|
|
389
499
|
this.sizeType = options.pointerBits === 32 ? "i32" : "i64";
|
|
390
500
|
this.ffiExtendNarrowIntegers =
|
|
391
501
|
options.wasi === true || ffiExtendsNarrowIntegers(options.targetTriple);
|
|
392
502
|
this.wasi = options.wasi === true;
|
|
503
|
+
// An empty triple targets the host, as for executableTls below: the
|
|
504
|
+
// Node-hosted CLI passes none, the native CLI passes its toolchain's, and
|
|
505
|
+
// both must emit the same module for the same target.
|
|
506
|
+
this.inlineTrunc =
|
|
507
|
+
!this.wasi &&
|
|
508
|
+
(options.targetTriple
|
|
509
|
+
? /^(x86_64|amd64)\b/i.test(options.targetTriple)
|
|
510
|
+
: process.arch === "x64");
|
|
511
|
+
this.executableTls =
|
|
512
|
+
mod.workers === true &&
|
|
513
|
+
mod.lib === undefined &&
|
|
514
|
+
!this.wasi &&
|
|
515
|
+
!/apple|darwin|windows|mingw|cygwin|win32/.test(options.targetTriple || process.platform);
|
|
516
|
+
const host = options.targetTriple || `${process.arch}-${process.platform}`;
|
|
517
|
+
this.stackGuardTsd =
|
|
518
|
+
mod.workers === true && mod.lib === undefined && !this.wasi && /apple|darwin/.test(host)
|
|
519
|
+
? /^(arm64|aarch64)/.test(host)
|
|
520
|
+
? "aarch64"
|
|
521
|
+
: /^(x86_64|amd64|x64)/.test(host)
|
|
522
|
+
? "x86_64"
|
|
523
|
+
: null
|
|
524
|
+
: null;
|
|
393
525
|
this.emitLibraryIdentity = options.emitLibraryIdentity !== false;
|
|
394
526
|
this.runtimeAbiMarker = options.runtimeAbiMarker === true;
|
|
527
|
+
this.detachedExit = options.detachedExit === true;
|
|
395
528
|
this.rcHelpers = options.inlineRc === true ? new Set() : null;
|
|
396
|
-
// ScrCycHdr.
|
|
397
|
-
//
|
|
398
|
-
//
|
|
399
|
-
this.
|
|
529
|
+
// ScrCycHdr.buffered sits 6 bytes behind every object. The wasm32
|
|
530
|
+
// header pads before its descriptor so its payload remains
|
|
531
|
+
// double-aligned without changing this ABI offset.
|
|
532
|
+
this.cycleBufferedOffset = 6;
|
|
400
533
|
this.ffiCallbackAdapters = allocateFfiCallbackAdapters(mod.ffiImports ?? []);
|
|
401
534
|
this.ffiHasRetainedCallback = hasRetainedFfiCallback(mod.ffiImports ?? []);
|
|
402
535
|
this.ffiHasForeignCallback = hasForeignFfiCallback(mod.ffiImports ?? []);
|
|
@@ -405,11 +538,17 @@ export class LlEmitter {
|
|
|
405
538
|
for (const entry of mod.ffiImports ?? []) {
|
|
406
539
|
this.ffiByName.set(entry.name, entry);
|
|
407
540
|
}
|
|
408
|
-
|
|
541
|
+
// Executables on Linux and macOS install a native stack guard per thread
|
|
542
|
+
// and per async fiber; WASI, Windows and library builds have none.
|
|
543
|
+
const triple = (options.targetTriple ?? "").toLowerCase();
|
|
544
|
+
const windows = triple === "" ? process.platform === "win32" : triple.includes("windows");
|
|
545
|
+
const mt = computeMayThrow(mod, {
|
|
546
|
+
stackChecks: !this.wasi && mod.lib === undefined && !windows,
|
|
547
|
+
});
|
|
409
548
|
this.mayThrow = mt.fns;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
549
|
+
this.workerEntryPolls = mt.workerEntryPolls ?? null;
|
|
550
|
+
this.stackChecks = mt.stackChecks ?? null;
|
|
551
|
+
this.publishedTypes = computePublishedTypes(mod);
|
|
413
552
|
this.indirectMayThrow = mt.indirect || mod.workers === true;
|
|
414
553
|
for (const cls of mod.classes ?? []) {
|
|
415
554
|
for (const m of cls.methods ?? []) {
|
|
@@ -419,10 +558,57 @@ export class LlEmitter {
|
|
|
419
558
|
}
|
|
420
559
|
for (const u of mod.unions ?? [])
|
|
421
560
|
this.unionsById.set(u.id, u);
|
|
422
|
-
this.
|
|
423
|
-
this.
|
|
424
|
-
this.
|
|
425
|
-
this.
|
|
561
|
+
this.boxedUnionsById = this.nullableUnions.boxedUnions(this.unionsById);
|
|
562
|
+
this.immortalValues.add("null");
|
|
563
|
+
this.immortalValues.add(NULLABLE_NULL);
|
|
564
|
+
this.nullableFields = new NullableRefFields(mod.classes ?? [], this.boxedUnionsById);
|
|
565
|
+
// Optional array reads also cover nullable unions: emitLocalArrayRead
|
|
566
|
+
// then yields the (borrowed or owned) element pointer, not a stack box.
|
|
567
|
+
// Arrays of such a union are read the same way.
|
|
568
|
+
this.optionalArrayReads = new OptionalArrayReads(this.fnByName, this.unionsById, (id) => this.nullableUnions.has(id));
|
|
569
|
+
this.checkedNarrows = new CheckedNarrows(this.fnByName);
|
|
570
|
+
this.referenceEffects = new ReferenceEffects(this.fnByName, (call) => this.optionalArrayReads.get(call) !== null, mod.classes ?? []);
|
|
571
|
+
const walkParameters = new Map();
|
|
572
|
+
if (this.debug === null) {
|
|
573
|
+
const host = {
|
|
574
|
+
pointerLocal: (local) => this.plainReference(local.type),
|
|
575
|
+
borrowsWithoutOwning: (e) => this.borrowsWithoutOwning(e),
|
|
576
|
+
};
|
|
577
|
+
for (const fn of this.fnByName.values()) {
|
|
578
|
+
if (!this.referenceEffects.functions.has(fn.name))
|
|
579
|
+
continue;
|
|
580
|
+
const walks = findWalkBorrows(fn, host);
|
|
581
|
+
if (walks.size === 0)
|
|
582
|
+
continue;
|
|
583
|
+
this.walkBorrowsByFunction.set(fn.name, walks);
|
|
584
|
+
const indexes = new Set();
|
|
585
|
+
fn.params.forEach((param, index) => {
|
|
586
|
+
if (walks.has(param.localId))
|
|
587
|
+
indexes.add(index);
|
|
588
|
+
});
|
|
589
|
+
if (indexes.size > 0)
|
|
590
|
+
walkParameters.set(fn.name, indexes);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (this.debug === null) {
|
|
594
|
+
for (const fn of this.fnByName.values()) {
|
|
595
|
+
// Strings keep the owned parameter: their in-place append
|
|
596
|
+
// (`s = s + x`) needs a uniquely owned binding.
|
|
597
|
+
const rebound = findReboundParameters(fn, (type) => this.plainReference(type) &&
|
|
598
|
+
type.kind !== "string" &&
|
|
599
|
+
(type.kind !== "union" || this.nullableUnions.get(type.unionId)?.arm.kind !== "string"), this.walkBorrowsByFunction.get(fn.name) ?? new Set());
|
|
600
|
+
if (rebound.size === 0)
|
|
601
|
+
continue;
|
|
602
|
+
this.reboundByFunction.set(fn.name, rebound);
|
|
603
|
+
const indexes = walkParameters.get(fn.name) ?? new Set();
|
|
604
|
+
fn.params.forEach((param, index) => {
|
|
605
|
+
if (rebound.has(param.localId))
|
|
606
|
+
indexes.add(index);
|
|
607
|
+
});
|
|
608
|
+
walkParameters.set(fn.name, indexes);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
this.callLifetimes = analyzeCallLifetimes(this.fnByName, (className, field) => this.nullableFields.get(className, field) !== null, walkParameters);
|
|
426
612
|
this.constantCallbacks = findConstantCallbacks(mod, this.callLifetimes);
|
|
427
613
|
this.stackCallbacks = new StackCallbacks(this.fnByName);
|
|
428
614
|
this.receiverReaders = new AmbientReceiverReaders(this.fnByName);
|
|
@@ -435,9 +621,10 @@ export class LlEmitter {
|
|
|
435
621
|
this.tracedUnions = traced.unions;
|
|
436
622
|
this.shapeHost = {
|
|
437
623
|
sizeType: this.sizeType,
|
|
438
|
-
|
|
624
|
+
cycleBufferedOffset: this.cycleBufferedOffset,
|
|
439
625
|
tracedShapes: this.tracedShapes,
|
|
440
626
|
tracedUnions: this.tracedUnions,
|
|
627
|
+
nullableUnions: this.nullableUnions,
|
|
441
628
|
recordsById: this.recordsById,
|
|
442
629
|
recordCloneShapes: this.recordCloneShapes,
|
|
443
630
|
objectAudit: options.objectAudit !== false,
|
|
@@ -445,12 +632,13 @@ export class LlEmitter {
|
|
|
445
632
|
pointerBits: options.pointerBits,
|
|
446
633
|
wasi: options.wasi,
|
|
447
634
|
targetTriple: options.targetTriple,
|
|
448
|
-
|
|
449
|
-
// scr_weak_dispose_hook thread-local and compile the allocator out,
|
|
450
|
-
// exactly like thread-instanced libraries.
|
|
451
|
-
threadInstances: mod.lib?.threadInstances === true || mod.workers === true,
|
|
635
|
+
threadInstances: mod.lib?.threadInstances === true,
|
|
452
636
|
}),
|
|
637
|
+
// Worker programs keep one allocator per script thread: the inline
|
|
638
|
+
// paths address its thread-local state.
|
|
639
|
+
threadLocalAlloc: mod.workers === true,
|
|
453
640
|
rcHelpers: this.rcHelpers,
|
|
641
|
+
nextCycKind: () => FIRST_EMITTED_CYC_KIND + this.cycKinds++,
|
|
454
642
|
unionsById: this.unionsById,
|
|
455
643
|
declare: (decl) => this.declare(decl),
|
|
456
644
|
needOom: () => this.needOom(),
|
|
@@ -458,6 +646,7 @@ export class LlEmitter {
|
|
|
458
646
|
internLiteral: (text) => this.internLiteral(text),
|
|
459
647
|
cstr: (text) => this.cstr(text),
|
|
460
648
|
unitInstanceRef: (unionId, tag) => this.unitInstanceRef(unionId, tag),
|
|
649
|
+
fieldAbsentTestIn: (B, value, unionId) => this.fieldAbsentTestIn(B, value, unionId),
|
|
461
650
|
absentInstanceRef: (unionId) => this.absentInstanceRef(unionId),
|
|
462
651
|
liveDynRefAdapter: (type) => this.liveDynRefAdapter(type),
|
|
463
652
|
liveDynUnionRefAdapter: (type) => this.liveDynUnionRefAdapter(type),
|
|
@@ -470,6 +659,8 @@ export class LlEmitter {
|
|
|
470
659
|
.filter((meta) => intervals.some((range) => range.pre <= meta.pre && meta.pre <= range.post))
|
|
471
660
|
.map((meta) => meta.def.name);
|
|
472
661
|
},
|
|
662
|
+
pendingTestLines: (dest) => this.pendingTestLines(dest),
|
|
663
|
+
virtualEntry: (implFn) => this.virtualEntry(implFn),
|
|
473
664
|
};
|
|
474
665
|
this.walkers = new LlWalkers(this.shapeHost);
|
|
475
666
|
this.dyn = new LlDyn(this.shapeHost);
|
|
@@ -526,6 +717,21 @@ export class LlEmitter {
|
|
|
526
717
|
lib: rec.lib,
|
|
527
718
|
});
|
|
528
719
|
}
|
|
720
|
+
this.virtualBorrows = new VirtualBorrows(mod, this.classMeta, this.fnByName, this.callLifetimes.borrowed);
|
|
721
|
+
// After the vtable slots: borrowing slots exclude their implementations.
|
|
722
|
+
if (this.debug === null)
|
|
723
|
+
this.analyzeBorrowedReturns();
|
|
724
|
+
}
|
|
725
|
+
/** The borrowed parameters of a hierarchy's vtable slot (empty: owned). */
|
|
726
|
+
virtualSlotBorrowed(rootName, slotIndex) {
|
|
727
|
+
return this.virtualBorrows.slot(rootName, slotIndex);
|
|
728
|
+
}
|
|
729
|
+
/** The vtable entry for one implementation (VirtualBorrows.entry). */
|
|
730
|
+
virtualEntry(implFn) {
|
|
731
|
+
return this.virtualBorrows.entry(implFn);
|
|
732
|
+
}
|
|
733
|
+
implSlotBorrowed(implFn) {
|
|
734
|
+
return this.virtualBorrows.implSlotBorrowed(implFn);
|
|
529
735
|
}
|
|
530
736
|
abiOffset(native64, wasm32) {
|
|
531
737
|
return this.sizeType === "i32" ? wasm32 : native64;
|
|
@@ -566,7 +772,6 @@ export class LlEmitter {
|
|
|
566
772
|
const defs = [];
|
|
567
773
|
if (this.ffiCallbackAdapters.size === 0)
|
|
568
774
|
return { globals, defs };
|
|
569
|
-
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
570
775
|
this.declare(`declare void @scr_trap(ptr)`);
|
|
571
776
|
const expired = this.cstr("scriptc: native callback invoked outside its call-scoped lifetime\n");
|
|
572
777
|
const released = this.cstr("scriptc: native callback invoked outside its retained lifetime\n");
|
|
@@ -684,7 +889,7 @@ export class LlEmitter {
|
|
|
684
889
|
? ` %cb = load ptr, ptr @${adapter.tls}`
|
|
685
890
|
: adapter.global !== null
|
|
686
891
|
? ` %cb = load ptr, ptr @${adapter.global}`
|
|
687
|
-
: ` %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:`,
|
|
892
|
+
: ` %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:`);
|
|
688
893
|
// Validate every native pointer before allocating any copy-in value.
|
|
689
894
|
// A later bad slot therefore cannot leak an earlier materialization.
|
|
690
895
|
for (let i = 0; i < cb.params.length; i++) {
|
|
@@ -832,6 +1037,22 @@ export class LlEmitter {
|
|
|
832
1037
|
return this.emitParts().join("\n");
|
|
833
1038
|
}
|
|
834
1039
|
emitParts() {
|
|
1040
|
+
const parts = this.emitModuleParts();
|
|
1041
|
+
if (!this.executableTls)
|
|
1042
|
+
return parts;
|
|
1043
|
+
// A worker executable's thread-locals all live in the executable's own
|
|
1044
|
+
// TLS block: the program's internal ones, and the runtime's external
|
|
1045
|
+
// ones, because the runtime is always linked statically into the
|
|
1046
|
+
// executable. So on ELF they can all use the local-exec model: LLVM's
|
|
1047
|
+
// default general-dynamic sequence (relaxed by the linker to `mov %fs:0`
|
|
1048
|
+
// plus an add), and even initial-exec (relaxed to `mov $offset` plus an
|
|
1049
|
+
// indexed access), become one %fs-relative operand LLVM can fold into
|
|
1050
|
+
// loads and compares. Darwin's thread-local variables have one model.
|
|
1051
|
+
return parts.map((part) => part
|
|
1052
|
+
.replace(/^(@[-$._A-Za-z0-9]+ = internal )thread_local global /gm, "$1thread_local(localexec) global ")
|
|
1053
|
+
.replace(/^(@[-$._A-Za-z0-9]+ = external )thread_local global /gm, "$1thread_local(localexec) global "));
|
|
1054
|
+
}
|
|
1055
|
+
emitModuleParts() {
|
|
835
1056
|
// Function bodies first (the literal/unit/fn-value tables fill as they
|
|
836
1057
|
// emit), then the file assembles around them — the runtime ABI’s order.
|
|
837
1058
|
const fnDefs = [];
|
|
@@ -858,7 +1079,7 @@ export class LlEmitter {
|
|
|
858
1079
|
this.declare(`declare void @scr_error_register_dyn(ptr, ptr)`);
|
|
859
1080
|
errorViews.push(` call void @scr_error_register_dyn(ptr @${mangleVtInstance(meta.def.name)}, ptr @${name})`);
|
|
860
1081
|
}
|
|
861
|
-
const layouts = emitLlvmLayouts(this.shapeHost, this.mod, this.classMeta, this.classObjs, this.fnByName, (t) => this.llType(t), this.nullableFields);
|
|
1082
|
+
const layouts = emitLlvmLayouts(this.shapeHost, this.mod, this.classMeta, this.classObjs, this.fnByName, (t) => this.llType(t), this.nullableFields, this.int32Slots);
|
|
862
1083
|
const shapes = layouts.records;
|
|
863
1084
|
const classShapes = layouts.classes;
|
|
864
1085
|
const classObjDefs = layouts.classObjects;
|
|
@@ -1061,12 +1282,15 @@ export class LlEmitter {
|
|
|
1061
1282
|
const topPendingReleases = asyncEntry ? globalReleaseLines("gp") : [];
|
|
1062
1283
|
const loopReportedReleases = runsLoop ? globalReleaseLines("gq") : [];
|
|
1063
1284
|
// main's epilogues read the flag / the loop entry points — declared
|
|
1064
|
-
// HERE, before the extern block flushes
|
|
1065
|
-
//
|
|
1285
|
+
// HERE, before the extern block flushes. They run once, so they keep
|
|
1286
|
+
// the out-of-line scr_exc_pending call; per-call checks inline the
|
|
1287
|
+
// test (pendingTestLines).
|
|
1066
1288
|
if (entryMayThrow || runsLoop)
|
|
1067
1289
|
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
1068
1290
|
if (runsLoop) {
|
|
1069
1291
|
this.declare(`declare zeroext i1 @scr_loop_run(ptr)`);
|
|
1292
|
+
if (this.mod.commonJsEntry === true)
|
|
1293
|
+
this.declare(`declare void @scr_loop_commonjs_entry()`);
|
|
1070
1294
|
this.declare(`declare zeroext i1 @scr_report_unhandled_rejections()`);
|
|
1071
1295
|
this.declare(`declare void @scr_discard_unhandled_rejections()`);
|
|
1072
1296
|
}
|
|
@@ -1157,6 +1381,8 @@ export class LlEmitter {
|
|
|
1157
1381
|
const helpers = [
|
|
1158
1382
|
...this.helperDefs(),
|
|
1159
1383
|
...emitClassMembershipHelper(this.classMeta, this.sizeType),
|
|
1384
|
+
...(this.publishWalkersCache?.defs() ?? []),
|
|
1385
|
+
...this.publishedClassNamesDefs(),
|
|
1160
1386
|
];
|
|
1161
1387
|
if (this.constantNumericTables.size > 0)
|
|
1162
1388
|
this.declare(`declare double @scr_arr_get_number(ptr, double)`);
|
|
@@ -1176,16 +1402,28 @@ export class LlEmitter {
|
|
|
1176
1402
|
`%ScrClosure = type { ${this.sizeType}, ptr, ${this.sizeType}, ptr, i32, i32, ptr }`,
|
|
1177
1403
|
`%ScrFfiTable = type { ptr, ${this.sizeType}, ${this.sizeType}, ptr, i8, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ptr }`,
|
|
1178
1404
|
`%ScrRegex = type { ${this.sizeType}, ptr, ptr, ptr, double, ptr, ptr }`,
|
|
1179
|
-
// ScrArr mirrors scr_runtime.h field-for-field
|
|
1180
|
-
//
|
|
1181
|
-
//
|
|
1182
|
-
|
|
1405
|
+
// ScrArr mirrors scr_runtime.h field-for-field: { rc, len, cap, elem,
|
|
1406
|
+
// elem_retain, elem_release, elem_trace, data, present, ext }, where
|
|
1407
|
+
// ext points at the out-of-line sparse, property and metadata storage
|
|
1408
|
+
// (NULL until first used). Live dynamic stream commits swap its dense,
|
|
1409
|
+
// presence and out-of-line storage while preserving the target
|
|
1410
|
+
// object's identity.
|
|
1411
|
+
`%ScrArr = type { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, i32, ptr, ptr, ptr, ptr, ptr, ptr }`,
|
|
1183
1412
|
// The ScrMap prefix { rc, key_kind, val_kind, val_retain, val_release,
|
|
1184
1413
|
// val_trace, key_retain, key_release, key_trace } — the inline RC
|
|
1185
1414
|
// fast paths test the two trace slots (shapes.ts).
|
|
1186
1415
|
...(this.rcHelpers === null
|
|
1187
1416
|
? []
|
|
1188
1417
|
: [`%ScrMapRc = type { ${this.sizeType}, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr }`]),
|
|
1418
|
+
// The complete ScrMap and its { key, val, hash } entry, read by the
|
|
1419
|
+
// inline number-key lookup (sc_map_entry_f64): entries is field 12,
|
|
1420
|
+
// the direct index and its length fields 18 and 19.
|
|
1421
|
+
...(this.decls.has(NUMBER_MAP_ENTRY_DECL)
|
|
1422
|
+
? [
|
|
1423
|
+
`%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} }`,
|
|
1424
|
+
`%ScrMapEntry = type { i64, i64, i64 }`,
|
|
1425
|
+
]
|
|
1426
|
+
: []),
|
|
1189
1427
|
// The runtime error prefix { rc, vt, name, message, code, cause } and the
|
|
1190
1428
|
// class-object shape { rc, pre, post, ctor, name } — field reads on
|
|
1191
1429
|
// builtin errors and classval loads GEP through these.
|
|
@@ -1231,8 +1469,12 @@ export class LlEmitter {
|
|
|
1231
1469
|
// data (string literals, unit arms, template arrays, vtables) stays
|
|
1232
1470
|
// shared.
|
|
1233
1471
|
const tl = this.mod.workers === true || this.mod.lib?.threadInstances === true ? "thread_local " : "";
|
|
1234
|
-
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.runtimeAbiMarker && this.mod.lib === undefined
|
|
1472
|
+
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
|
|
1235
1473
|
? [`declare void @${RUNTIME_ABI_MARKER}()`]
|
|
1474
|
+
: []),
|
|
1475
|
+
// Overrides the runtime's weak default (scr_lib.c).
|
|
1476
|
+
...(this.detachedExit && this.mod.lib === undefined
|
|
1477
|
+
? [`@scr_detached_exit = constant i8 1`]
|
|
1236
1478
|
: []));
|
|
1237
1479
|
for (const d of this.decls)
|
|
1238
1480
|
out.push(d);
|
|
@@ -1240,17 +1482,19 @@ export class LlEmitter {
|
|
|
1240
1482
|
const flushedDecls = new Set(this.decls);
|
|
1241
1483
|
for (const table of this.constantNumericTables.values()) {
|
|
1242
1484
|
const n = table.values.length;
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
` %index = fptoui double %i to ${this.sizeType}`, ` %roundtrip = uitofp ${this.sizeType} %index to double`, ` %integer = fcmp oeq double %roundtrip, %i`, ` br i1 %integer, label %read, label %fallback`, `read:`, ` %slot = getelementptr inbounds [${n} x double], ptr @${table.symbol}, ${this.sizeType} 0, ${this.sizeType} %index`, ` %value = load double, ptr %slot`, ` ret double %value`, `fallback:`, ` %generic = call double @scr_arr_get_number(ptr %a, double %i)`, ` ret double %generic`, `}`, ``);
|
|
1485
|
+
let tableTmp = 0;
|
|
1486
|
+
const tableIndex = exactIndexLines("%i", String(n), "i64", () => `%ix${tableTmp++}`);
|
|
1487
|
+
out.push(`@${table.symbol} = private constant [${n} x double] [${table.values.map((v) => `double ${f64Lit(v)}`).join(", ")}]`, `define internal double @${table.symbol}_get(ptr %a, double %i) alwaysinline #0 {`, `entry:`, ` %initialized = icmp ne ptr %a, null`, ...tableIndex.lines.map((line) => ` ${line}`), ` %safe = and i1 %initialized, ${tableIndex.ok}`, ` br i1 %safe, label %read, label %fallback`, `read:`, ` %slot = getelementptr inbounds [${n} x double], ptr @${table.symbol}, i64 0, i64 ${tableIndex.wide}`, ` %value = load double, ptr %slot`, ` ret double %value`, `fallback:`, ` %generic = call double @scr_arr_get_number(ptr %a, double %i)`, ` ret double %generic`, `}`, ``);
|
|
1247
1488
|
}
|
|
1248
1489
|
for (const [text, lit] of this.literals) {
|
|
1249
1490
|
// Immortal interned ScrStr: { rc = SIZE_MAX, len, cap = len, bytes\0 } —
|
|
1250
1491
|
// the runtime ABI’s static table, retain/release skip rc == SIZE_MAX.
|
|
1251
1492
|
// 64-bit (little-endian) targets split the capacity word: the high
|
|
1252
1493
|
// half carries the precomputed Map key hash (SCR_STR_HASH_CACHE).
|
|
1253
|
-
|
|
1494
|
+
// Bit 31 marks all-ASCII literals (SCR_STR_ASCII_BIT) on every target.
|
|
1495
|
+
const capWord = this.sizeType === "i64"
|
|
1496
|
+
? literalCapWord(Buffer.from(text, "utf8"))
|
|
1497
|
+
: literalCapWord32(Buffer.from(text, "utf8"));
|
|
1254
1498
|
out.push(`@${lit.sym} = internal global { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, [${lit.len + 1} x i8] } ` +
|
|
1255
1499
|
`{ ${this.sizeType} -1, ${this.sizeType} ${lit.len}, ${this.sizeType} ${capWord}, [${lit.len + 1} x i8] c"${llStrBytes(text)}" }`);
|
|
1256
1500
|
}
|
|
@@ -1276,6 +1520,20 @@ export class LlEmitter {
|
|
|
1276
1520
|
}
|
|
1277
1521
|
if (this.absentInstances.size > 0)
|
|
1278
1522
|
out.push(``);
|
|
1523
|
+
if (this.nullableUnions.usesNullSentinel) {
|
|
1524
|
+
// `null` in every `C | null | undefined` union (NULL is undefined):
|
|
1525
|
+
// one module-scope immortal object like the ABSENT sentinel.
|
|
1526
|
+
const S = this.sizeType;
|
|
1527
|
+
out.push(`${NULLABLE_NULL} = internal global { ${S}, ${S}, ${S}, ${S} } { ${S} -1, ${S} 0, ${S} 0, ${S} 0 } ; nullable null arm`, ``);
|
|
1528
|
+
}
|
|
1529
|
+
if (this.needsNullableAbsent) {
|
|
1530
|
+
// The ABSENT field-slot state of every nullable-pointer union: one
|
|
1531
|
+
// module-scope immortal object (rc == SIZE_MAX), distinct from NULL
|
|
1532
|
+
// (undefined) and from every instance. RC entry points and the
|
|
1533
|
+
// collector skip it like any immortal; field reads compare its address.
|
|
1534
|
+
const S = this.sizeType;
|
|
1535
|
+
out.push(`${NULLABLE_ABSENT} = internal global { ${S}, ${S}, ${S}, ${S} } { ${S} -1, ${S} 0, ${S} 0, ${S} 0 } ; nullable absent field`, ``);
|
|
1536
|
+
}
|
|
1279
1537
|
for (const [key, re] of this.regexInstances) {
|
|
1280
1538
|
// One immortal ScrRegex per (pattern, flags) literal, pointing at
|
|
1281
1539
|
// the interned source/flags strings. The bc and native-matcher slots
|
|
@@ -1293,7 +1551,7 @@ export class LlEmitter {
|
|
|
1293
1551
|
// strings — a no-op.
|
|
1294
1552
|
const n = inst.slots.length;
|
|
1295
1553
|
const present = n === 0 ? "zeroinitializer" : `[ ${inst.slots.map(() => "i8 1").join(", ")} ]`;
|
|
1296
|
-
out.push(`@${inst.sym}_data = internal constant [${n} x ptr] [ ${inst.slots.map((s) => `ptr ${s}`).join(", ")} ]`, `@${inst.sym}_present = internal constant [${n} x i8] ${present}`, `@${inst.sym} = internal global %ScrArr { ${this.sizeType} -1, ${this.sizeType} ${n}, ${this.sizeType} ${n}, i32 2, ptr null, ptr null, ptr null, ptr @${inst.sym}_data, ptr @${inst.sym}_present, ptr null
|
|
1554
|
+
out.push(`@${inst.sym}_data = internal constant [${n} x ptr] [ ${inst.slots.map((s) => `ptr ${s}`).join(", ")} ]`, `@${inst.sym}_present = internal constant [${n} x i8] ${present}`, `@${inst.sym} = internal global %ScrArr { ${this.sizeType} -1, ${this.sizeType} ${n}, ${this.sizeType} ${n}, i32 2, ptr null, ptr null, ptr null, ptr @${inst.sym}_data, ptr @${inst.sym}_present, ptr null }`);
|
|
1297
1555
|
}
|
|
1298
1556
|
if (this.templateStringsInstances.size > 0)
|
|
1299
1557
|
out.push(``);
|
|
@@ -1442,7 +1700,10 @@ export class LlEmitter {
|
|
|
1442
1700
|
out.push(this.debug.render());
|
|
1443
1701
|
return out;
|
|
1444
1702
|
}
|
|
1445
|
-
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()`,
|
|
1703
|
+
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()`,
|
|
1704
|
+
// Each context (the main thread, every worker) installs its stack
|
|
1705
|
+
// guard before running compiled code that checks it.
|
|
1706
|
+
...(this.stackChecks?.size ? [` call void @scr_stack_guard_init()`] : []), ...stamps,
|
|
1446
1707
|
// Event-surface programs (signal/exit listeners) fill the loop's
|
|
1447
1708
|
// nullable event hooks before %main — scr_events.c links only when
|
|
1448
1709
|
// this line is emitted (native/executable.ts gates on the same predicate).
|
|
@@ -1487,6 +1748,7 @@ export class LlEmitter {
|
|
|
1487
1748
|
// both exit 1, like Node — the C main's loop block exactly.
|
|
1488
1749
|
...(runsLoop
|
|
1489
1750
|
? [
|
|
1751
|
+
...(this.mod.commonJsEntry === true ? [` call void @scr_loop_commonjs_entry()`] : []),
|
|
1490
1752
|
` %loop_rejection = call zeroext i1 @scr_loop_run(ptr ${asyncEntry ? "%top" : "null"})`,
|
|
1491
1753
|
` %lexc = call zeroext i1 @scr_exc_pending()`,
|
|
1492
1754
|
` br i1 %lexc, label %luncaught, label %lok`,
|
|
@@ -1802,10 +2064,25 @@ export class LlEmitter {
|
|
|
1802
2064
|
msgHelper("sc_bad_key", "sc_bad_key_msg", "scriptc: TypeError: record has no key (typed slot — no undefined is representable)\n");
|
|
1803
2065
|
}
|
|
1804
2066
|
if (this.needsRetainBox) {
|
|
1805
|
-
// scr_box_retain is a static inline (increment-unless-immortal
|
|
1806
|
-
// mark the cycle header live — every box is collector-headered);
|
|
2067
|
+
// scr_box_retain is a static inline (increment-unless-immortal);
|
|
1807
2068
|
// emitted once with internal linkage, like the record retains.
|
|
1808
|
-
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`, `
|
|
2069
|
+
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`, `}`, ``);
|
|
2070
|
+
}
|
|
2071
|
+
// Number-key Map/Set lookups probe the runtime's direct index inline:
|
|
2072
|
+
// while ndense is nonzero every live key is an integer below it, so an
|
|
2073
|
+
// in-range integral key reads its slot (entry index + 1, 0 = absent) and
|
|
2074
|
+
// any other key is absent. Maps without the index (ndense 0) and every
|
|
2075
|
+
// non-integral probe outside the table take the runtime lookup, which
|
|
2076
|
+
// keeps the complete SameValueZero semantics.
|
|
2077
|
+
if (this.decls.has(NUMBER_MAP_ENTRY_DECL)) {
|
|
2078
|
+
const sz = this.sizeType;
|
|
2079
|
+
let mapTmp = 0;
|
|
2080
|
+
const mapIndex = exactIndexLines("%k", "%nd", sz, () => `%ix${mapTmp++}`);
|
|
2081
|
+
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`,
|
|
2082
|
+
// A non-integral key inside the table is as absent as one outside it.
|
|
2083
|
+
...mapIndex.lines.map((line) => ` ${line}`), ` br i1 ${mapIndex.ok}, label %slot, label %outside`, `slot:`, sz === "i64"
|
|
2084
|
+
? ` %i = add i64 ${mapIndex.wide}, 0`
|
|
2085
|
+
: ` %i = trunc i64 ${mapIndex.wide} to ${sz}`, ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 18`, ` %dense = load ptr, ptr %dp`, ` %sp = getelementptr inbounds i32, ptr %dense, ${sz} %i`, ` %s = load i32, ptr %sp`, ` %empty = icmp eq i32 %s, 0`, ` br i1 %empty, label %miss, label %hit`, `hit:`, ` %e32 = sub i32 %s, 1`, sz === "i32" ? ` %e = add i32 %e32, 0` : ` %e = zext i32 %e32 to ${sz}`, ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 12`, ` %entries = load ptr, ptr %ep`, ` %found = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %e`, ` ret ptr %found`, `miss:`, ` ret ptr null`, `outside:`, ` %dense.any = icmp ne ${sz} %nd, 0`, ` br i1 %dense.any, label %miss, label %slow`, `slow:`, ` %r = call ptr @scr_map_entry_f64(ptr %m, double %k)`, ` ret ptr %r`, `}`, ``);
|
|
1809
2086
|
}
|
|
1810
2087
|
// String comparisons enter through small internal wrappers that LLVM
|
|
1811
2088
|
// inlines at each site: interned literals and shared values compare by
|
|
@@ -1814,6 +2091,15 @@ export class LlEmitter {
|
|
|
1814
2091
|
if (this.decls.has(`declare zeroext i1 @scr_str_eq(ptr, ptr)`)) {
|
|
1815
2092
|
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`, `}`, ``);
|
|
1816
2093
|
}
|
|
2094
|
+
// String length and charCodeAt read proven-ASCII strings inline: bit 31
|
|
2095
|
+
// of the capacity word (SCR_STR_ASCII_BIT, the low half's sign bit on
|
|
2096
|
+
// little-endian targets) makes UTF-16 units bytes. Unproven strings and
|
|
2097
|
+
// anything but an in-range index take the runtime's UTF-16 mapping.
|
|
2098
|
+
for (const helper of STRING_READ_HELPERS) {
|
|
2099
|
+
if (!this.decls.has(helper.decl))
|
|
2100
|
+
continue;
|
|
2101
|
+
defs.push(...helper.define(this.sizeType, FN_ATTRS), ``);
|
|
2102
|
+
}
|
|
1817
2103
|
// Ordering also decides inline when the first bytes differ: byte order
|
|
1818
2104
|
// is the plain comparison's order, and the UTF-16 order whenever both
|
|
1819
2105
|
// bytes are ASCII. Callers only test the sign of the result.
|
|
@@ -1861,7 +2147,18 @@ export class LlEmitter {
|
|
|
1861
2147
|
* here too. */
|
|
1862
2148
|
inlineRcTail(flushedDecls) {
|
|
1863
2149
|
const late = inlineRcDecls(this.shapeHost).filter((d) => !flushedDecls.has(d));
|
|
1864
|
-
|
|
2150
|
+
// NULL-skipping retain wrappers of nullable unions; a runtime inner
|
|
2151
|
+
// retain requested after the extern block flushed is declared here.
|
|
2152
|
+
for (const inner of this.nullableUnions.retainWrappers.values()) {
|
|
2153
|
+
const decl = `declare ptr ${inner}(ptr)`;
|
|
2154
|
+
if (inner.startsWith("@scr_") && !flushedDecls.has(decl) && !late.includes(decl))
|
|
2155
|
+
late.push(decl);
|
|
2156
|
+
}
|
|
2157
|
+
return [
|
|
2158
|
+
...late,
|
|
2159
|
+
...emitNullableRetainWrappers(this.shapeHost),
|
|
2160
|
+
...emitInlineRcHelpers(this.shapeHost),
|
|
2161
|
+
];
|
|
1865
2162
|
}
|
|
1866
2163
|
/** Env-signature wrappers + interned immortal closures for declared
|
|
1867
2164
|
* functions used as values (the runtime ABI’s sc_w_/sc_fc_ pair): every
|
|
@@ -1892,7 +2189,6 @@ export class LlEmitter {
|
|
|
1892
2189
|
const sizeOf = `ptrtoint (ptr getelementptr (%${pack}, ptr null, i32 1) to ${this.sizeType})`;
|
|
1893
2190
|
this.declare(`declare void @free(ptr)`);
|
|
1894
2191
|
this.declare(`declare ptr @malloc(${this.sizeType})`);
|
|
1895
|
-
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
1896
2192
|
const tr = [
|
|
1897
2193
|
`define internal void @${mangleTrampoline(fn.name)}(ptr %self, ptr %ap) ${FN_ATTRS} {`,
|
|
1898
2194
|
`entry:`,
|
|
@@ -2048,7 +2344,6 @@ export class LlEmitter {
|
|
|
2048
2344
|
this.declare(`declare void @scr_async_inline_enter(ptr)`);
|
|
2049
2345
|
this.declare(`declare ptr @scr_async_inline_leave(ptr)`);
|
|
2050
2346
|
this.declare(`declare void @scr_promise_reject_pending(ptr)`);
|
|
2051
|
-
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
2052
2347
|
if (lifted) {
|
|
2053
2348
|
this.declare(`declare ptr @scr_closure_retain_v(ptr)`);
|
|
2054
2349
|
this.declare(`declare void @scr_closure_release(ptr)`);
|
|
@@ -2064,7 +2359,7 @@ export class LlEmitter {
|
|
|
2064
2359
|
` call void @scr_async_inline_enter(ptr %frame)`,
|
|
2065
2360
|
retTy === "void" ? ` ${bodyCall}` : ` %r = ${bodyCall}`,
|
|
2066
2361
|
...(lifted ? [` call void @scr_closure_release(ptr %a0)`] : []),
|
|
2067
|
-
|
|
2362
|
+
...this.pendingTestLines("%pend"),
|
|
2068
2363
|
` %p = call ptr @scr_async_inline_leave(ptr %frame)`,
|
|
2069
2364
|
` br i1 %pend, label %thrown, label %clean`,
|
|
2070
2365
|
`clean:`,
|
|
@@ -2123,7 +2418,7 @@ export class LlEmitter {
|
|
|
2123
2418
|
if (fn.captures !== undefined) {
|
|
2124
2419
|
this.declare(`declare void @scr_closure_release(ptr)`);
|
|
2125
2420
|
}
|
|
2126
|
-
tr.push(
|
|
2421
|
+
tr.push(...this.pendingTestLines("%pend"), ` br i1 %pend, label %thrown, label %clean`, `clean:`, ` %pr = call ptr @scr_fiber_promise(ptr %self)`);
|
|
2127
2422
|
for (const line of this.asyncFulfillLines(ret, "%pr"))
|
|
2128
2423
|
tr.push(line);
|
|
2129
2424
|
tr.push(` ret void`, `thrown:`);
|
|
@@ -2232,7 +2527,7 @@ export class LlEmitter {
|
|
|
2232
2527
|
tr.push(` ret void`, `}`, ``);
|
|
2233
2528
|
}
|
|
2234
2529
|
else {
|
|
2235
|
-
tr.push(` %g = call ptr @scr_gen_of_fiber(ptr %self)`,
|
|
2530
|
+
tr.push(` %g = call ptr @scr_gen_of_fiber(ptr %self)`, ...this.pendingTestLines("%pend"), ` br i1 %pend, label %thrown, label %clean`, `clean:`);
|
|
2236
2531
|
switch (ret.kind) {
|
|
2237
2532
|
case "void":
|
|
2238
2533
|
tr.push(` br label %done ; void body: the done value is undefined (NONE)`);
|
|
@@ -2322,10 +2617,30 @@ export class LlEmitter {
|
|
|
2322
2617
|
return out;
|
|
2323
2618
|
}
|
|
2324
2619
|
// ── plumbing (frame and scope ownership) ──────
|
|
2620
|
+
/** A symbol derived from the constant's text rather than its first-use
|
|
2621
|
+
* index, so an edit that interns one more string leaves every other
|
|
2622
|
+
* constant's symbol, and the code referencing it, unchanged. This lets
|
|
2623
|
+
* native builds reuse the compiled partitions an edit does not touch. A
|
|
2624
|
+
* collision retries with a counter, still in first-use order. */
|
|
2625
|
+
contentSymbol(prefix, text) {
|
|
2626
|
+
for (let attempt = 0;; attempt++) {
|
|
2627
|
+
const digest = createHash("sha256")
|
|
2628
|
+
.update(attempt === 0 ? text : `${attempt}\0${text}`)
|
|
2629
|
+
.digest("hex");
|
|
2630
|
+
const symbol = `${prefix}${parseInt(digest.slice(0, 13), 16)}`;
|
|
2631
|
+
if (!this.contentSymbols.has(symbol)) {
|
|
2632
|
+
this.contentSymbols.add(symbol);
|
|
2633
|
+
return symbol;
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2325
2637
|
internLiteral(text) {
|
|
2326
2638
|
let lit = this.literals.get(text);
|
|
2327
2639
|
if (!lit) {
|
|
2328
|
-
lit = {
|
|
2640
|
+
lit = {
|
|
2641
|
+
sym: this.contentSymbol("sc_lit_", text),
|
|
2642
|
+
len: Buffer.byteLength(text, "utf8"),
|
|
2643
|
+
};
|
|
2329
2644
|
this.literals.set(text, lit);
|
|
2330
2645
|
}
|
|
2331
2646
|
const name = `@${lit.sym}`;
|
|
@@ -2337,7 +2652,7 @@ export class LlEmitter {
|
|
|
2337
2652
|
cstr(text) {
|
|
2338
2653
|
let c = this.cstrs.get(text);
|
|
2339
2654
|
if (!c) {
|
|
2340
|
-
c = { sym:
|
|
2655
|
+
c = { sym: this.contentSymbol("sc_cs_", text), len: Buffer.byteLength(text, "utf8") };
|
|
2341
2656
|
this.cstrs.set(text, c);
|
|
2342
2657
|
}
|
|
2343
2658
|
return `@${c.sym}`;
|
|
@@ -2354,6 +2669,12 @@ export class LlEmitter {
|
|
|
2354
2669
|
if (!arm || !isUnitType(arm)) {
|
|
2355
2670
|
throw new InternalCompilerError(`llvm emitter bug: unit instance for non-unit arm ${tag} of ${unionId}`);
|
|
2356
2671
|
}
|
|
2672
|
+
const nullable = this.nullableUnions.get(unionId);
|
|
2673
|
+
if (nullable) {
|
|
2674
|
+
// NULL is the unit arm of a nullable union (`undefined` when it has
|
|
2675
|
+
// both); the `null` sentinel is immortal. Neither owns anything.
|
|
2676
|
+
return tag === nullable.nullTag ? NULLABLE_NULL : "null";
|
|
2677
|
+
}
|
|
2357
2678
|
const key = `${unionId}:${tag}`;
|
|
2358
2679
|
let sym = this.unitInstances.get(key);
|
|
2359
2680
|
if (!sym) {
|
|
@@ -2370,6 +2691,11 @@ export class LlEmitter {
|
|
|
2370
2691
|
if (undefinedArmTag({ kind: "union", unionId }, this.unionsById) < 0) {
|
|
2371
2692
|
throw new InternalCompilerError(`llvm emitter bug: absent state of ${unionId} without undefined`);
|
|
2372
2693
|
}
|
|
2694
|
+
if (this.nullableUnions.has(unionId)) {
|
|
2695
|
+
this.needsNullableAbsent = true;
|
|
2696
|
+
this.immortalValues.add(NULLABLE_ABSENT);
|
|
2697
|
+
return NULLABLE_ABSENT;
|
|
2698
|
+
}
|
|
2373
2699
|
let sym = this.absentInstances.get(unionId);
|
|
2374
2700
|
if (!sym) {
|
|
2375
2701
|
sym = `sc_absent_${this.absentInstances.size}`;
|
|
@@ -2382,7 +2708,18 @@ export class LlEmitter {
|
|
|
2382
2708
|
/** i1: a union value loaded from a field slot is the ABSENT state — the
|
|
2383
2709
|
* undefined arm's tag with the absent payload marker. */
|
|
2384
2710
|
fieldAbsentTest(value, unionId) {
|
|
2385
|
-
return
|
|
2711
|
+
return this.fieldAbsentTestIn(this.B, value, unionId);
|
|
2712
|
+
}
|
|
2713
|
+
/** fieldAbsentTest in another block builder (helper generators). A
|
|
2714
|
+
* nullable union's ABSENT state is the module's immortal sentinel. */
|
|
2715
|
+
fieldAbsentTestIn(B, value, unionId) {
|
|
2716
|
+
if (this.nullableUnions.has(unionId)) {
|
|
2717
|
+
this.needsNullableAbsent = true;
|
|
2718
|
+
const t = B.tmp();
|
|
2719
|
+
B.line(`${t} = icmp eq ptr ${value}, ${NULLABLE_ABSENT}`);
|
|
2720
|
+
return t;
|
|
2721
|
+
}
|
|
2722
|
+
return emitFieldAbsentTest(B, value, undefinedArmTag({ kind: "union", unionId }, this.unionsById));
|
|
2386
2723
|
}
|
|
2387
2724
|
/** Loads a record field slot as a VALUE: an undefined-armed union slot
|
|
2388
2725
|
* holding the ABSENT state surfaces as the ordinary undefined arm, so
|
|
@@ -2410,9 +2747,20 @@ export class LlEmitter {
|
|
|
2410
2747
|
throw new InternalCompilerError("llvm emitter bug: no active statement frame");
|
|
2411
2748
|
return frame;
|
|
2412
2749
|
}
|
|
2750
|
+
/** Whether a value is immortal: an interned constant, or a temp marked by
|
|
2751
|
+
* markImmortal. */
|
|
2752
|
+
isImmortal(name) {
|
|
2753
|
+
return this.immortalValues.has(name) || this.B.immortalTemps.has(name);
|
|
2754
|
+
}
|
|
2755
|
+
/** Record that a temp holds an immortal value (a choice between interned
|
|
2756
|
+
* literals): nothing owns it, and retains, releases and moves skip it. */
|
|
2757
|
+
markImmortal(v) {
|
|
2758
|
+
this.B.immortalTemps.add(v.name);
|
|
2759
|
+
return v;
|
|
2760
|
+
}
|
|
2413
2761
|
/** Registers an owned refcounted value on the current statement frame. */
|
|
2414
2762
|
own(v) {
|
|
2415
|
-
if (isRefCounted(v.type) && !this.
|
|
2763
|
+
if (isRefCounted(v.type) && !this.isImmortal(v.name))
|
|
2416
2764
|
this.currentFrame().push(v);
|
|
2417
2765
|
return v;
|
|
2418
2766
|
}
|
|
@@ -2424,7 +2772,7 @@ export class LlEmitter {
|
|
|
2424
2772
|
}
|
|
2425
2773
|
/** Strike a refcounted temp from its frame: ownership is being moved. */
|
|
2426
2774
|
moveTemp(v) {
|
|
2427
|
-
if (!isRefCounted(v.type) || this.
|
|
2775
|
+
if (!isRefCounted(v.type) || this.isImmortal(v.name))
|
|
2428
2776
|
return;
|
|
2429
2777
|
for (let i = this.frames.length - 1; i >= 0; i--) {
|
|
2430
2778
|
const idx = this.frames[i].findIndex((e) => e.name === v.name);
|
|
@@ -2438,7 +2786,7 @@ export class LlEmitter {
|
|
|
2438
2786
|
/** The retained (+1) read of a refcounted value — type-directed through
|
|
2439
2787
|
* the `_v` table (immortals skip, exactly the C retain calls). */
|
|
2440
2788
|
retainValue(name, type) {
|
|
2441
|
-
if (this.
|
|
2789
|
+
if (this.isImmortal(name))
|
|
2442
2790
|
return name;
|
|
2443
2791
|
const t = this.B.tmp();
|
|
2444
2792
|
this.B.line(`${t} = call ptr ${retainSym(this.shapeHost, type)}(ptr ${name})`);
|
|
@@ -2447,7 +2795,7 @@ export class LlEmitter {
|
|
|
2447
2795
|
/** The release call for one owned refcounted value — type-directed like
|
|
2448
2796
|
* releaseCallC (all runtime releases are NULL-tolerant). */
|
|
2449
2797
|
releaseValue(name, type) {
|
|
2450
|
-
if (this.
|
|
2798
|
+
if (this.isImmortal(name))
|
|
2451
2799
|
return;
|
|
2452
2800
|
this.B.line(`call void ${releaseSym(this.shapeHost, type)}(ptr ${name})`);
|
|
2453
2801
|
}
|
|
@@ -2472,7 +2820,21 @@ export class LlEmitter {
|
|
|
2472
2820
|
for (const e of scope) {
|
|
2473
2821
|
const t = this.B.tmp();
|
|
2474
2822
|
this.B.line(`${t} = load ptr, ptr ${e.slot}`);
|
|
2475
|
-
if (e.boxed) {
|
|
2823
|
+
if (e.boxed && e.lazy) {
|
|
2824
|
+
// Unreachable code after a terminator emits nothing; a new block
|
|
2825
|
+
// here would revive it.
|
|
2826
|
+
if (this.B.isTerminated())
|
|
2827
|
+
continue;
|
|
2828
|
+
const created = this.B.tmp();
|
|
2829
|
+
this.B.line(`${created} = icmp ne ptr ${t}, null`);
|
|
2830
|
+
const release = this.B.newLabel("lazy.release"), join = this.B.newLabel("lazy.release.j");
|
|
2831
|
+
this.B.condBr(created, release, join);
|
|
2832
|
+
this.B.startBlock(release);
|
|
2833
|
+
this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
|
|
2834
|
+
this.B.br(join);
|
|
2835
|
+
this.B.startBlock(join);
|
|
2836
|
+
}
|
|
2837
|
+
else if (e.boxed) {
|
|
2476
2838
|
this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
|
|
2477
2839
|
}
|
|
2478
2840
|
else {
|
|
@@ -2523,16 +2885,34 @@ export class LlEmitter {
|
|
|
2523
2885
|
* function — and branch to the handler / return a dummy value (never
|
|
2524
2886
|
* read: callers of a may-throw function test the pending flag before
|
|
2525
2887
|
* using the result). Callers own the surrounding pending branch; a
|
|
2526
|
-
* `throw` unwinds unconditionally.
|
|
2527
|
-
|
|
2888
|
+
* `throw` unwinds unconditionally. `passThrough` names a scalar call
|
|
2889
|
+
* result of the function's own return type: with nothing to release, the
|
|
2890
|
+
* unwind returns it as the dummy, so a call in tail position keeps a single
|
|
2891
|
+
* return and stays eligible for tail-call and recursion elimination. */
|
|
2892
|
+
emitUnwind(passThrough) {
|
|
2528
2893
|
if (this.mod.workers && this.tryStack.length > 0) {
|
|
2529
|
-
this.declare(`declare zeroext i1 @scr_context_stopping()`);
|
|
2530
|
-
const stopped = this.B.tmp();
|
|
2531
|
-
this.B.line(`${stopped} = call zeroext i1 @scr_context_stopping()`);
|
|
2532
2894
|
const stop = this.B.newLabel("context.stop");
|
|
2533
2895
|
const ordinary = this.B.newLabel("context.throw");
|
|
2534
|
-
this.
|
|
2535
|
-
|
|
2896
|
+
if (this.executableTls) {
|
|
2897
|
+
// A set stop bit unwinds while the active cell may still be clear:
|
|
2898
|
+
// install the termination sentinel before skipping the handlers.
|
|
2899
|
+
const word = this.loadExcFlag();
|
|
2900
|
+
const bit = this.B.tmp();
|
|
2901
|
+
const stopped = this.B.tmp();
|
|
2902
|
+
this.B.line(`${bit} = and i32 ${word}, 2`);
|
|
2903
|
+
this.B.line(`${stopped} = icmp ne i32 ${bit}, 0`);
|
|
2904
|
+
this.B.condBr(stopped, stop, ordinary);
|
|
2905
|
+
this.B.startBlock(stop);
|
|
2906
|
+
this.declare(`declare zeroext i1 @scr_context_checkpoint()`);
|
|
2907
|
+
this.B.line(`call zeroext i1 @scr_context_checkpoint()`);
|
|
2908
|
+
}
|
|
2909
|
+
else {
|
|
2910
|
+
this.declare(`declare zeroext i1 @scr_context_stopping()`);
|
|
2911
|
+
const stopped = this.B.tmp();
|
|
2912
|
+
this.B.line(`${stopped} = call zeroext i1 @scr_context_stopping()`);
|
|
2913
|
+
this.B.condBr(stopped, stop, ordinary);
|
|
2914
|
+
this.B.startBlock(stop);
|
|
2915
|
+
}
|
|
2536
2916
|
const handlers = this.tryStack;
|
|
2537
2917
|
this.tryStack = [];
|
|
2538
2918
|
this.emitUnwind();
|
|
@@ -2574,6 +2954,11 @@ export class LlEmitter {
|
|
|
2574
2954
|
}
|
|
2575
2955
|
}
|
|
2576
2956
|
if (entries.length === 0) {
|
|
2957
|
+
if (passThrough !== undefined && !target && this.currentWasiCoro === null) {
|
|
2958
|
+
const t = this.llType(this.currentReturnType);
|
|
2959
|
+
if (t === "double" || t === "i1")
|
|
2960
|
+
terminator = `ret ${t} ${passThrough}`;
|
|
2961
|
+
}
|
|
2577
2962
|
this.B.terminate(terminator);
|
|
2578
2963
|
return;
|
|
2579
2964
|
}
|
|
@@ -2584,27 +2969,250 @@ export class LlEmitter {
|
|
|
2584
2969
|
}
|
|
2585
2970
|
this.B.br(cleanup.label);
|
|
2586
2971
|
}
|
|
2972
|
+
/** Loads the running thread's exact pending word (ELF worker
|
|
2973
|
+
* executables, scr_exception.c): bit 0 mirrors the active cell, bit 1
|
|
2974
|
+
* requests the context's stop. It lives in the executable's static TLS
|
|
2975
|
+
* block, so the access is one thread-pointer-relative load. Monotonic by
|
|
2976
|
+
* default: a loop must not hoist the load out and miss a cross-thread
|
|
2977
|
+
* stop (see emitPendingCheck for the unordered tests). */
|
|
2978
|
+
loadExcFlag(unordered = false) {
|
|
2979
|
+
this.declare(`@scr_exc_flag = external thread_local(localexec) global i32, align 4`);
|
|
2980
|
+
const word = this.B.tmp();
|
|
2981
|
+
this.B.line(`${word} = load atomic i32, ptr @scr_exc_flag ${unordered ? "unordered" : "monotonic"}, align 4`);
|
|
2982
|
+
return word;
|
|
2983
|
+
}
|
|
2984
|
+
/** Instructions (two-space indented) that set the i1 `dest` to "an
|
|
2985
|
+
* exception is pending in the active context" — scr_exc_pending()'s
|
|
2986
|
+
* meaning — for the helper and adapter bodies that test it outside
|
|
2987
|
+
* emitPendingCheck. Executables read the runtime's exported active-cell
|
|
2988
|
+
* pointer and compare its i32 kind (offset 0) against SCR_EXC_NONE inline,
|
|
2989
|
+
* as emitPendingCheck does; the out-of-line call would be opaque to LLVM
|
|
2990
|
+
* because the runtime links as prebuilt objects. Libraries and worker
|
|
2991
|
+
* programs keep the runtime call, which is emitPendingCheck's path there
|
|
2992
|
+
* too (workers also observe cross-thread termination in it). */
|
|
2993
|
+
pendingTestLines(dest) {
|
|
2994
|
+
if (this.mod.lib !== undefined || this.mod.workers === true) {
|
|
2995
|
+
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
2996
|
+
return [` ${dest} = call zeroext i1 @scr_exc_pending()`];
|
|
2997
|
+
}
|
|
2998
|
+
this.declare(`@scr_exc_active = external global ptr`);
|
|
2999
|
+
return [
|
|
3000
|
+
` ${dest}.cell = load ptr, ptr @scr_exc_active`,
|
|
3001
|
+
` ${dest}.kind = load i32, ptr ${dest}.cell`,
|
|
3002
|
+
` ${dest} = icmp ne i32 ${dest}.kind, 0`,
|
|
3003
|
+
];
|
|
3004
|
+
}
|
|
2587
3005
|
/** The emitter contract for exceptions: after EVERY call that can throw
|
|
2588
3006
|
* (per the may-throw analysis), test the pending flag and unwind. The
|
|
2589
3007
|
* call's result temp must join its frame BEFORE this runs so the unwind
|
|
2590
|
-
* releases the dummy (NULL for refcounted kinds) harmlessly.
|
|
2591
|
-
|
|
3008
|
+
* releases the dummy (NULL for refcounted kinds) harmlessly. A scalar
|
|
3009
|
+
* call result of the function's return type may be passed as the unwind's
|
|
3010
|
+
* dummy value (see emitUnwind). `poll` marks a worker termination poll
|
|
3011
|
+
* (function entry or loop budget), whose test must stay ordered. */
|
|
3012
|
+
emitPendingCheck(passThrough, poll = false) {
|
|
3013
|
+
const through = passThrough !== undefined &&
|
|
3014
|
+
this.llType(passThrough.type) === this.llType(this.currentReturnType) &&
|
|
3015
|
+
(passThrough.type.kind === "f64" || passThrough.type.kind === "bool")
|
|
3016
|
+
? passThrough.name
|
|
3017
|
+
: undefined;
|
|
2592
3018
|
const B = this.B;
|
|
2593
3019
|
if (B.isTerminated())
|
|
2594
3020
|
return;
|
|
2595
|
-
this.
|
|
2596
|
-
|
|
2597
|
-
|
|
3021
|
+
if (this.mod.lib !== undefined) {
|
|
3022
|
+
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
3023
|
+
const p = B.tmp();
|
|
3024
|
+
B.line(`${p} = call zeroext i1 @scr_exc_pending()`);
|
|
3025
|
+
const lu = B.newLabel("exc.u");
|
|
3026
|
+
const lk = B.newLabel("exc.k");
|
|
3027
|
+
B.condBr(p, lu, lk);
|
|
3028
|
+
B.startBlock(lu);
|
|
3029
|
+
this.emitUnwind(through);
|
|
3030
|
+
B.startBlock(lk);
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
3033
|
+
// Executables test the active exception cell's kind inline (its first
|
|
3034
|
+
// field). ELF worker executables test the thread's exact pending word,
|
|
3035
|
+
// which is also nonzero once the context must stop, and unwind directly:
|
|
3036
|
+
// no site carries an out-of-line call, so LLVM's inliner sees the check
|
|
3037
|
+
// as two instructions. Other worker programs test the process-wide
|
|
3038
|
+
// alert word: it is nonzero while any script thread may have an
|
|
3039
|
+
// exception pending in its active cell or has been asked to stop, so
|
|
3040
|
+
// the common case is one ordinary global load (a thread-local access is
|
|
3041
|
+
// a call on Darwin) and the out-of-line scr_exc_pending answers for this
|
|
3042
|
+
// thread, observing cancellation and reinstalling the termination
|
|
3043
|
+
// sentinel.
|
|
3044
|
+
this.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
3045
|
+
let word;
|
|
3046
|
+
const exact = this.mod.workers !== true || this.executableTls;
|
|
3047
|
+
if (this.executableTls) {
|
|
3048
|
+
// Termination stays observable where the polls were dropped for it:
|
|
3049
|
+
// polls, tests inside source loops (exc's loop-poll rule relies on
|
|
3050
|
+
// them every iteration) and tail-position tests (a dead ordered load
|
|
3051
|
+
// keeps recursion from becoming a loop without stack growth) use an
|
|
3052
|
+
// ordered load, which LLVM never deletes, hoists or merges. Every
|
|
3053
|
+
// other test is an unordered load: it reads the same word, but LLVM's
|
|
3054
|
+
// memory analyses no longer treat it as a write, so loads around it
|
|
3055
|
+
// stay forwardable after inlining. Same-thread changes only happen in
|
|
3056
|
+
// runtime calls, which clobber memory.
|
|
3057
|
+
word = this.loadExcFlag(!poll && through === undefined && this.loopDepth === 0);
|
|
3058
|
+
}
|
|
3059
|
+
else if (this.mod.workers === true) {
|
|
3060
|
+
// dso_local: executables link the runtime statically, so the word is
|
|
3061
|
+
// addressed directly instead of through the GOT.
|
|
3062
|
+
this.declare(`@scr_exc_alert = external dso_local global i32, align 64`);
|
|
3063
|
+
word = B.tmp();
|
|
3064
|
+
B.line(`${word} = load atomic i32, ptr @scr_exc_alert monotonic, align 64`);
|
|
3065
|
+
}
|
|
3066
|
+
else {
|
|
3067
|
+
this.declare(`@scr_exc_active = external global ptr`);
|
|
3068
|
+
let cell;
|
|
3069
|
+
if (B.excCellInvariant) {
|
|
3070
|
+
// Fiber switches restore the active cell before control returns to a
|
|
3071
|
+
// synchronous frame, so its pointer is one value for the whole call:
|
|
3072
|
+
// load it once in the entry block and test only the kind here.
|
|
3073
|
+
if (B.excCell === null) {
|
|
3074
|
+
B.excCell = "%exc.cell";
|
|
3075
|
+
B.entryAllocas.push(`${B.excCell} = load ptr, ptr @scr_exc_active`);
|
|
3076
|
+
}
|
|
3077
|
+
cell = B.excCell;
|
|
3078
|
+
}
|
|
3079
|
+
else {
|
|
3080
|
+
cell = B.tmp();
|
|
3081
|
+
B.line(`${cell} = load ptr, ptr @scr_exc_active`);
|
|
3082
|
+
}
|
|
3083
|
+
word = B.tmp();
|
|
3084
|
+
B.line(`${word} = load i32, ptr ${cell}`);
|
|
3085
|
+
}
|
|
3086
|
+
const hit = B.tmp();
|
|
3087
|
+
const cold = B.tmp();
|
|
3088
|
+
B.line(`${hit} = icmp ne i32 ${word}, 0`);
|
|
3089
|
+
B.line(`${cold} = call i1 @llvm.expect.i1(i1 ${hit}, i1 false)`);
|
|
2598
3090
|
const lu = B.newLabel("exc.u");
|
|
2599
3091
|
const lk = B.newLabel("exc.k");
|
|
2600
|
-
|
|
3092
|
+
if (!exact) {
|
|
3093
|
+
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
3094
|
+
const slow = B.newLabel("exc.s");
|
|
3095
|
+
B.condBr(cold, slow, lk);
|
|
3096
|
+
B.startBlock(slow);
|
|
3097
|
+
const p = B.tmp();
|
|
3098
|
+
B.line(`${p} = call zeroext i1 @scr_exc_pending()`);
|
|
3099
|
+
B.condBr(p, lu, lk);
|
|
3100
|
+
}
|
|
3101
|
+
else {
|
|
3102
|
+
B.condBr(cold, lu, lk);
|
|
3103
|
+
}
|
|
2601
3104
|
B.startBlock(lu);
|
|
2602
|
-
this.emitUnwind();
|
|
3105
|
+
this.emitUnwind(through);
|
|
2603
3106
|
B.startBlock(lk);
|
|
2604
3107
|
}
|
|
2605
|
-
|
|
3108
|
+
/** After a runtime call that ALWAYS leaves an exception pending (the
|
|
3109
|
+
* compiler-resolved Node throws): the pending test would always answer
|
|
3110
|
+
* true, so unwind unconditionally. Emission continues in a block with no
|
|
3111
|
+
* predecessors so the caller's remaining instructions stay well-formed;
|
|
3112
|
+
* LLVM deletes it. */
|
|
3113
|
+
emitAlwaysPendingUnwind() {
|
|
3114
|
+
const B = this.B;
|
|
3115
|
+
if (B.isTerminated())
|
|
3116
|
+
return;
|
|
3117
|
+
this.emitUnwind();
|
|
3118
|
+
B.startBlock(B.newLabel("exc.dead"));
|
|
3119
|
+
}
|
|
3120
|
+
/** A recursive function's entry check: below the running stack's guard,
|
|
3121
|
+
* throw Node's RangeError and unwind like any other pending exception. */
|
|
3122
|
+
emitStackCheck() {
|
|
3123
|
+
const B = this.B;
|
|
3124
|
+
this.declare(`declare ptr @llvm.stacksave.p0()`);
|
|
3125
|
+
this.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
3126
|
+
this.declare(`declare void @scr_stack_overflow()`);
|
|
3127
|
+
const over = B.newLabel("stack.over");
|
|
3128
|
+
const ok = B.newLabel("stack.ok");
|
|
3129
|
+
const sp = B.tmp();
|
|
3130
|
+
B.line(`${sp} = call ptr @llvm.stacksave.p0()`);
|
|
3131
|
+
const test = (guard) => {
|
|
3132
|
+
const low = B.tmp();
|
|
3133
|
+
const cold = B.tmp();
|
|
3134
|
+
B.line(`${low} = icmp ult ptr ${sp}, ${guard}`);
|
|
3135
|
+
B.line(`${cold} = call i1 @llvm.expect.i1(i1 ${low}, i1 false)`);
|
|
3136
|
+
B.condBr(cold, over, ok);
|
|
3137
|
+
};
|
|
3138
|
+
if (this.stackGuardTsd !== null) {
|
|
3139
|
+
// A Darwin thread-local access is a call to its TLV thunk: read the
|
|
3140
|
+
// guard from its thread-specific-data slot at the runtime-validated
|
|
3141
|
+
// offset instead, and call out only when the runtime found no slot.
|
|
3142
|
+
this.declare(`@scr_stack_guard_tsd = external dso_local global i64`);
|
|
3143
|
+
this.declare(`declare i64 @scr_stack_guard_get()`);
|
|
3144
|
+
const offset = B.tmp();
|
|
3145
|
+
const direct = B.tmp();
|
|
3146
|
+
const likely = B.tmp();
|
|
3147
|
+
B.line(`${offset} = load i64, ptr @scr_stack_guard_tsd`);
|
|
3148
|
+
B.line(`${direct} = icmp ne i64 ${offset}, 0`);
|
|
3149
|
+
B.line(`${likely} = call i1 @llvm.expect.i1(i1 ${direct}, i1 true)`);
|
|
3150
|
+
const fast = B.newLabel("stack.tsd");
|
|
3151
|
+
const slow = B.newLabel("stack.tlv");
|
|
3152
|
+
B.condBr(likely, fast, slow);
|
|
3153
|
+
B.startBlock(fast);
|
|
3154
|
+
const guard = B.tmp();
|
|
3155
|
+
if (this.stackGuardTsd === "aarch64") {
|
|
3156
|
+
this.declare(`declare i64 @llvm.read_register.i64(metadata)`);
|
|
3157
|
+
const thread = B.tmp();
|
|
3158
|
+
const base = B.tmp();
|
|
3159
|
+
const slot = B.tmp();
|
|
3160
|
+
const address = B.tmp();
|
|
3161
|
+
B.line(`${thread} = call i64 @llvm.read_register.i64(metadata !{!"tpidrro_el0"})`);
|
|
3162
|
+
B.line(`${base} = and i64 ${thread}, -8`);
|
|
3163
|
+
B.line(`${slot} = add i64 ${base}, ${offset}`);
|
|
3164
|
+
B.line(`${address} = inttoptr i64 ${slot} to ptr`);
|
|
3165
|
+
B.line(`${guard} = load ptr, ptr ${address}`);
|
|
3166
|
+
}
|
|
3167
|
+
else {
|
|
3168
|
+
const address = B.tmp();
|
|
3169
|
+
B.line(`${address} = inttoptr i64 ${offset} to ptr addrspace(256)`);
|
|
3170
|
+
B.line(`${guard} = load ptr, ptr addrspace(256) ${address}`);
|
|
3171
|
+
}
|
|
3172
|
+
test(guard);
|
|
3173
|
+
B.startBlock(slow);
|
|
3174
|
+
// uintptr_t scr_stack_guard_get(void): an i64 in the C ABI.
|
|
3175
|
+
const raw = B.tmp();
|
|
3176
|
+
const fallback = B.tmp();
|
|
3177
|
+
B.line(`${raw} = call i64 @scr_stack_guard_get()`);
|
|
3178
|
+
B.line(`${fallback} = inttoptr i64 ${raw} to ptr`);
|
|
3179
|
+
test(fallback);
|
|
3180
|
+
}
|
|
3181
|
+
else {
|
|
3182
|
+
const tl = this.mod.workers === true ? "thread_local " : "";
|
|
3183
|
+
this.declare(`@scr_stack_guard = external ${tl}global ptr`);
|
|
3184
|
+
const guard = B.tmp();
|
|
3185
|
+
B.line(`${guard} = load ptr, ptr @scr_stack_guard`);
|
|
3186
|
+
test(guard);
|
|
3187
|
+
}
|
|
3188
|
+
B.startBlock(over);
|
|
3189
|
+
B.line(`call void @scr_stack_overflow()`);
|
|
3190
|
+
this.emitUnwind();
|
|
3191
|
+
B.startBlock(ok);
|
|
3192
|
+
}
|
|
3193
|
+
/** Whether the emitter follows this call with a pending test (the
|
|
3194
|
+
* conditions in expr-calls.ts). */
|
|
3195
|
+
callTestsPending(e) {
|
|
3196
|
+
switch (e.kind) {
|
|
3197
|
+
case "call":
|
|
3198
|
+
return this.mayThrow.has(e.callee);
|
|
3199
|
+
case "new":
|
|
3200
|
+
return this.mayThrow.has(`%${e.className}.constructor`);
|
|
3201
|
+
case "virtualCall":
|
|
3202
|
+
return this.mayThrowMethods.has(e.method);
|
|
3203
|
+
case "callValue":
|
|
3204
|
+
return this.indirectMayThrow;
|
|
3205
|
+
default:
|
|
3206
|
+
return false;
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
workerLoopBudget(loop) {
|
|
2606
3210
|
if (!this.mod.workers)
|
|
2607
3211
|
return null;
|
|
3212
|
+
// A loop that runs a pending test on every iteration observes a
|
|
3213
|
+
// terminate request through it; a separate poll would only add work.
|
|
3214
|
+
if (loopTestsEveryIteration(loop, (e) => this.callTestsPending(e)))
|
|
3215
|
+
return null;
|
|
2608
3216
|
const slot = this.B.slot();
|
|
2609
3217
|
this.B.entryAllocas.push(`${slot} = alloca i32 ; worker cancellation budget`);
|
|
2610
3218
|
this.B.line(`store i32 0, ptr ${slot}`);
|
|
@@ -2626,7 +3234,7 @@ export class LlEmitter {
|
|
|
2626
3234
|
// Calls and throwing operations retain their own checks. Amortize the
|
|
2627
3235
|
// extra cancellation poll across bounded loop batches so scalar work
|
|
2628
3236
|
// does not cross the runtime ABI on every iteration.
|
|
2629
|
-
this.emitPendingCheck();
|
|
3237
|
+
this.emitPendingCheck(undefined, true);
|
|
2630
3238
|
B.br(next);
|
|
2631
3239
|
B.startBlock(next);
|
|
2632
3240
|
}
|
|
@@ -2699,6 +3307,11 @@ export class LlEmitter {
|
|
|
2699
3307
|
const def = this.unionsById.get(v.type.unionId);
|
|
2700
3308
|
if (!def)
|
|
2701
3309
|
throw new InternalCompilerError(`llvm emitter bug: truthiness of unknown union ${v.type.unionId}`);
|
|
3310
|
+
const nullable = this.nullableUnions.get(def.id);
|
|
3311
|
+
// Object arms are always truthy: present iff neither unit encoding.
|
|
3312
|
+
if (nullable && isObjectArm(nullable))
|
|
3313
|
+
return emitNullablePresent(B, nullable, v.name);
|
|
3314
|
+
const unionId = def.id;
|
|
2702
3315
|
const slot = B.slot();
|
|
2703
3316
|
B.entryAllocas.push(`${slot} = alloca i1`);
|
|
2704
3317
|
const join = B.newLabel("ut.j");
|
|
@@ -2711,10 +3324,10 @@ export class LlEmitter {
|
|
|
2711
3324
|
valueName = this.unionGetBool(v.name);
|
|
2712
3325
|
}
|
|
2713
3326
|
else if (arm.kind === "string") {
|
|
2714
|
-
valueName = this.unionPeek(v.name);
|
|
3327
|
+
valueName = this.unionPeek(v.name, unionId);
|
|
2715
3328
|
}
|
|
2716
3329
|
else if (arm.kind === "bigint" || arm.kind === "dyn" || arm.kind === "jsval") {
|
|
2717
|
-
valueName = this.unionPeek(v.name);
|
|
3330
|
+
valueName = this.unionPeek(v.name, unionId);
|
|
2718
3331
|
}
|
|
2719
3332
|
const truthy = this.truthyOf(arm.kind, valueName, true);
|
|
2720
3333
|
B.line(`store i1 ${truthy}, ptr ${slot}`);
|
|
@@ -2820,22 +3433,22 @@ export class LlEmitter {
|
|
|
2820
3433
|
}
|
|
2821
3434
|
}
|
|
2822
3435
|
// ── union plumbing ──────────────────────────────────────────────────────
|
|
2823
|
-
/**
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
3436
|
+
/** A union value's tag (i32): the box's tag word, or the NULL test of a
|
|
3437
|
+
* nullable union's pointer. */
|
|
3438
|
+
unionTag(uName, unionId) {
|
|
3439
|
+
return emitUnionTag(this.B, this.nullableUnions.get(unionId), uName);
|
|
3440
|
+
}
|
|
3441
|
+
/** Guard for helpers that build or read a union's `%ScrUnion` box
|
|
3442
|
+
* directly (library-shaped unions with fixed arms): a nullable union
|
|
3443
|
+
* reaching one is an emitter bug, never a silent miscompile. */
|
|
3444
|
+
requireBoxedUnion(unionId) {
|
|
3445
|
+
if (this.nullableUnions.has(unionId))
|
|
3446
|
+
throw new InternalCompilerError(`llvm emitter bug: nullable union ${unionId} reached a tagged-box helper`);
|
|
2830
3447
|
}
|
|
2831
3448
|
/** The BORROWED payload pointer of a ref arm (scr_union_peek inlined —
|
|
2832
|
-
* the runtime's is a static inline). */
|
|
2833
|
-
unionPeek(uName) {
|
|
2834
|
-
|
|
2835
|
-
const t = this.B.tmp();
|
|
2836
|
-
this.B.line(`${p} = getelementptr inbounds %ScrUnion, ptr ${uName}, i64 0, i32 5`);
|
|
2837
|
-
this.B.line(`${t} = load ptr, ptr ${p}`);
|
|
2838
|
-
return t;
|
|
3449
|
+
* the runtime's is a static inline); a nullable union is its payload. */
|
|
3450
|
+
unionPeek(uName, unionId) {
|
|
3451
|
+
return emitUnionPeek(this.B, this.nullableUnions.get(unionId), uName);
|
|
2839
3452
|
}
|
|
2840
3453
|
/** Emits `switch` over a union's tag with one block per arm; each arm
|
|
2841
3454
|
* body must TERMINATE its block (the callers branch to a join). The
|
|
@@ -2843,7 +3456,7 @@ export class LlEmitter {
|
|
|
2843
3456
|
* reads can group equivalent storage prefixes and emit one representative. */
|
|
2844
3457
|
unionTagSwitch(uName, def, arm, fieldGroups) {
|
|
2845
3458
|
const B = this.B;
|
|
2846
|
-
const tag = this.unionTag(uName);
|
|
3459
|
+
const tag = this.unionTag(uName, def.id);
|
|
2847
3460
|
const bad = B.newLabel("u.bad");
|
|
2848
3461
|
const labels = [];
|
|
2849
3462
|
if (fieldGroups) {
|
|
@@ -2889,12 +3502,12 @@ export class LlEmitter {
|
|
|
2889
3502
|
/** The +1 extraction of a union's single narrowed arm (unionNarrow /
|
|
2890
3503
|
* the nullish-family reads): scalars via inline payload loads, ref arms
|
|
2891
3504
|
* a retained peek. */
|
|
2892
|
-
unionExtract(uName, arm) {
|
|
3505
|
+
unionExtract(uName, unionId, arm) {
|
|
2893
3506
|
if (arm.kind === "f64" || arm.kind === "procStream")
|
|
2894
3507
|
return this.unionGetF64(uName);
|
|
2895
3508
|
if (arm.kind === "bool")
|
|
2896
3509
|
return this.unionGetBool(uName);
|
|
2897
|
-
return this.retainValue(this.unionPeek(uName), arm);
|
|
3510
|
+
return this.retainValue(this.unionPeek(uName, unionId), arm);
|
|
2898
3511
|
}
|
|
2899
3512
|
/** A scalar f64 arm's payload (scr_union_get_f64 inlined). Inline loads
|
|
2900
3513
|
* keep stack boxes free of escaping calls, so LLVM can scalarize them. */
|
|
@@ -2918,8 +3531,15 @@ export class LlEmitter {
|
|
|
2918
3531
|
/** Constructs a union box around an OWNED (+1, already moved) value —
|
|
2919
3532
|
* the scr_union_new_* dispatch of unionWrap and the wrap-into-join
|
|
2920
3533
|
* sites (shift). */
|
|
2921
|
-
unionNewOwned(tag, v) {
|
|
3534
|
+
unionNewOwned(unionId, tag, v) {
|
|
2922
3535
|
const B = this.B;
|
|
3536
|
+
const nullable = this.nullableUnions.get(unionId);
|
|
3537
|
+
if (nullable) {
|
|
3538
|
+
// The owned payload IS the nullable union's value.
|
|
3539
|
+
if (tag !== nullable.refTag)
|
|
3540
|
+
throw new InternalCompilerError(`llvm emitter bug: payload wrap into unit arm ${tag} of ${unionId}`);
|
|
3541
|
+
return v.name;
|
|
3542
|
+
}
|
|
2923
3543
|
const t = B.tmp();
|
|
2924
3544
|
if (v.type.kind === "f64" || v.type.kind === "procStream") {
|
|
2925
3545
|
this.declare(`declare ptr @scr_union_new_f64(i32, double)`);
|
|
@@ -3062,6 +3682,42 @@ export class LlEmitter {
|
|
|
3062
3682
|
B.line(`${z} = zext i1 ${value} to i8`);
|
|
3063
3683
|
B.line(`store i8 ${z}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
|
|
3064
3684
|
}
|
|
3685
|
+
/** Reads an int32-specialized class field: the i32 slot is the value,
|
|
3686
|
+
* and its widening is the exact double every program observation sees. */
|
|
3687
|
+
loadInt32Field(ptr, type) {
|
|
3688
|
+
const B = this.B;
|
|
3689
|
+
const raw = B.tmp();
|
|
3690
|
+
const number = B.tmp();
|
|
3691
|
+
B.line(`${raw} = load i32, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
|
|
3692
|
+
B.line(`${number} = sitofp i32 ${raw} to double`);
|
|
3693
|
+
return {
|
|
3694
|
+
name: number,
|
|
3695
|
+
type,
|
|
3696
|
+
uint32: raw,
|
|
3697
|
+
integer: { name: raw, type: "i32", signed: true, range: INT32_RANGE },
|
|
3698
|
+
};
|
|
3699
|
+
}
|
|
3700
|
+
/** Stores a value the whole-program analysis proved to be an int32 (never
|
|
3701
|
+
* -0, NaN or fractional) into an int32-specialized class field. */
|
|
3702
|
+
storeInt32Field(ptr, value, expr) {
|
|
3703
|
+
if (!this.int32Slots.provenWrites.has(expr))
|
|
3704
|
+
throw new InternalCompilerError("llvm emitter bug: unproven int32 field write");
|
|
3705
|
+
const B = this.B;
|
|
3706
|
+
let bits;
|
|
3707
|
+
if (value.integer?.type === "i32")
|
|
3708
|
+
bits = value.integer.name;
|
|
3709
|
+
else if (value.integer) {
|
|
3710
|
+
bits = B.tmp();
|
|
3711
|
+
B.line(`${bits} = trunc i64 ${value.integer.name} to i32`);
|
|
3712
|
+
}
|
|
3713
|
+
else if (value.uint32 !== undefined)
|
|
3714
|
+
bits = value.uint32;
|
|
3715
|
+
else {
|
|
3716
|
+
bits = B.tmp();
|
|
3717
|
+
B.line(`${bits} = fptosi double ${value.name} to i32`);
|
|
3718
|
+
}
|
|
3719
|
+
B.line(`store i32 ${bits}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
|
|
3720
|
+
}
|
|
3065
3721
|
// ── bindings ────────────────────────────────────────────────────────────
|
|
3066
3722
|
/** A binding's storage: a module global, a plain local slot, or a boxed
|
|
3067
3723
|
* local (the slot holds the capture BOX; access goes through it). */
|
|
@@ -3182,18 +3838,38 @@ export class LlEmitter {
|
|
|
3182
3838
|
this.declare(`declare void @scr_box_set_ref(ptr, ptr)`);
|
|
3183
3839
|
B.line(`call void @scr_box_set_ref(ptr ${box}, ptr ${cell})`);
|
|
3184
3840
|
}
|
|
3841
|
+
/** The payload a TDZ box holds before its declaration runs, when that is
|
|
3842
|
+
* not NULL. A nullable union's value is its arm pointer with NULL as a
|
|
3843
|
+
* unit arm (nullable-unions.ts), so a stored `null`/`undefined` would
|
|
3844
|
+
* read back as the temporal dead zone; such boxes start at the module's
|
|
3845
|
+
* immortal NULLABLE_ABSENT sentinel instead, which no value of the
|
|
3846
|
+
* binding can hold and every RC entry point and the collector skip. */
|
|
3847
|
+
tdzEmptySentinel(t) {
|
|
3848
|
+
if (!this.nullableUnions.of(t))
|
|
3849
|
+
return null;
|
|
3850
|
+
this.needsNullableAbsent = true;
|
|
3851
|
+
this.immortalValues.add(NULLABLE_ABSENT);
|
|
3852
|
+
return NULLABLE_ABSENT;
|
|
3853
|
+
}
|
|
3185
3854
|
/** The TDZ-guarded read of a boxed binding: an empty payload slot is
|
|
3186
3855
|
* the temporal dead zone — throw Node's exact catchable ReferenceError
|
|
3187
3856
|
* (exprs.ts's varRef guard). Scalars then peek the one-element
|
|
3188
3857
|
* array cell; ref kinds read the box normally (+1). */
|
|
3189
|
-
checkTdz(box, name) {
|
|
3858
|
+
checkTdz(box, t, name) {
|
|
3190
3859
|
const B = this.B;
|
|
3191
3860
|
const slotp = B.tmp();
|
|
3192
3861
|
const slotv = B.tmp();
|
|
3193
3862
|
const empty = B.tmp();
|
|
3194
3863
|
B.line(`${slotp} = getelementptr inbounds %ScrBox, ptr ${box}, i64 0, i32 5`);
|
|
3195
|
-
|
|
3196
|
-
|
|
3864
|
+
const sentinel = this.tdzEmptySentinel(t);
|
|
3865
|
+
if (sentinel !== null) {
|
|
3866
|
+
B.line(`${slotv} = load ptr, ptr ${slotp}`);
|
|
3867
|
+
B.line(`${empty} = icmp eq ptr ${slotv}, ${sentinel}`);
|
|
3868
|
+
}
|
|
3869
|
+
else {
|
|
3870
|
+
B.line(`${slotv} = load i64, ptr ${slotp}`);
|
|
3871
|
+
B.line(`${empty} = icmp eq i64 ${slotv}, 0`);
|
|
3872
|
+
}
|
|
3197
3873
|
this.throwIfUninitialized(empty, name);
|
|
3198
3874
|
return slotv;
|
|
3199
3875
|
}
|
|
@@ -3230,7 +3906,7 @@ export class LlEmitter {
|
|
|
3230
3906
|
B.startBlock(lk);
|
|
3231
3907
|
}
|
|
3232
3908
|
tdzBoxRead(box, t, name) {
|
|
3233
|
-
const slotv = this.checkTdz(box, name);
|
|
3909
|
+
const slotv = this.checkTdz(box, t, name);
|
|
3234
3910
|
const B = this.B;
|
|
3235
3911
|
const acc = boxAccess(t);
|
|
3236
3912
|
if (acc === "ref")
|
|
@@ -3250,7 +3926,7 @@ export class LlEmitter {
|
|
|
3250
3926
|
writeBindingBox(box, local, value, initializes = false, borrowed = false) {
|
|
3251
3927
|
if (local.tdz) {
|
|
3252
3928
|
const first = initializes || !local.mutable;
|
|
3253
|
-
const slotv = first ? null : this.checkTdz(box, local.name);
|
|
3929
|
+
const slotv = first ? null : this.checkTdz(box, local.type, local.name);
|
|
3254
3930
|
const acc = boxAccess(local.type);
|
|
3255
3931
|
if (acc !== "ref") {
|
|
3256
3932
|
if (first)
|
|
@@ -3375,6 +4051,9 @@ export class LlEmitter {
|
|
|
3375
4051
|
emitFunction(fn) {
|
|
3376
4052
|
const B = new BlockBuilder();
|
|
3377
4053
|
this.B = B;
|
|
4054
|
+
// Fiber switches restore the active exception cell before control
|
|
4055
|
+
// returns to a synchronous frame (emitPendingCheck).
|
|
4056
|
+
B.excCellInvariant = fn.async !== true && fn.generator === undefined;
|
|
3378
4057
|
this.debugScope = this.debug?.function(fn) ?? null;
|
|
3379
4058
|
B.debugLocation = this.debug?.location(fn.loc, this.debugScope) ?? null;
|
|
3380
4059
|
this.frames = [];
|
|
@@ -3407,6 +4086,7 @@ export class LlEmitter {
|
|
|
3407
4086
|
const numericFn = withInitializerBindings(fn, initializerBindings);
|
|
3408
4087
|
this.numericLocals = new Map(numericFn.locals.map((l) => [l.id, l]));
|
|
3409
4088
|
this.borrowedParameters.clear();
|
|
4089
|
+
this.reboundOwnerSlots.clear();
|
|
3410
4090
|
this.projectedParameters.clear();
|
|
3411
4091
|
const projectedParameterIndexes = this.callLifetimes.parameters.get(fn.name);
|
|
3412
4092
|
if (projectedParameterIndexes)
|
|
@@ -3415,12 +4095,18 @@ export class LlEmitter {
|
|
|
3415
4095
|
this.stableCallBindings = this.callLifetimes.bindings.get(fn.name) ?? new Set();
|
|
3416
4096
|
const borrowedParameterIndexes = this.callLifetimes.borrowed.get(fn.name);
|
|
3417
4097
|
if (borrowedParameterIndexes) {
|
|
4098
|
+
// Rebound walk parameters borrow too, but only unchanged ones may
|
|
4099
|
+
// serve as stable owners for call arguments and aliases.
|
|
3418
4100
|
for (const index of borrowedParameterIndexes)
|
|
3419
|
-
this.
|
|
4101
|
+
if (!this.walkBorrowsByFunction.get(fn.name)?.has(fn.params[index].localId) &&
|
|
4102
|
+
!this.reboundByFunction.get(fn.name)?.has(fn.params[index].localId))
|
|
4103
|
+
this.borrowedParameters.add(fn.params[index].localId);
|
|
3420
4104
|
}
|
|
3421
4105
|
this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
|
|
3422
4106
|
this.integerLoopBindings.clear();
|
|
3423
4107
|
this.countedLoopsEnabled = this.debug === null && !fn.async && !fn.generator;
|
|
4108
|
+
this.finalFieldBorrows =
|
|
4109
|
+
!fn.async && !fn.generator && constructorClass(fn.name) === null && this.finalFields.size > 0;
|
|
3424
4110
|
this.byteWindowsEnabled = this.countedLoopsEnabled;
|
|
3425
4111
|
this.byteWindowEntries =
|
|
3426
4112
|
this.byteWindowsEnabled && numericFn.locals.some((l) => l.type.kind === "bytes")
|
|
@@ -3433,21 +4119,24 @@ export class LlEmitter {
|
|
|
3433
4119
|
this.debug === null
|
|
3434
4120
|
? findLoopArrayBorrows(fn, this.callLifetimes, this.referenceEffects, this.optionalArrayReads)
|
|
3435
4121
|
: new Set();
|
|
3436
|
-
this.localArrayReads = findLocalArrayReads(fn, this.fnByName, this.unionsById, this.referenceEffects.functions, this.callLifetimes, this.optionalArrayReads, this.loopArrayBorrows);
|
|
3437
|
-
this.callArrayReads = findCallArrayReads(fn, this.optionalArrayReads, this.referenceEffects.functions, this.callLifetimes);
|
|
4122
|
+
this.localArrayReads = findLocalArrayReads(fn, this.fnByName, this.unionsById, this.referenceEffects.functions, this.callLifetimes, this.optionalArrayReads, this.loopArrayBorrows, this.debug === null ? this.referenceEffects : undefined);
|
|
4123
|
+
this.callArrayReads = findCallArrayReads(fn, this.optionalArrayReads, this.referenceEffects.functions, this.callLifetimes, this.debug === null ? this.referenceEffects : undefined);
|
|
3438
4124
|
this.scalarStringSlices =
|
|
3439
4125
|
this.debug === null ? findScalarStringSlices(fn, this.callLifetimes) : new Map();
|
|
3440
|
-
this.mapReadLifetimes = findMapReadLifetimes(fn, this.
|
|
3441
|
-
this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.
|
|
3442
|
-
this.localUnionStorageProofs = findLocalUnionStorage(fn, this.callLifetimes, this.
|
|
4126
|
+
this.mapReadLifetimes = findMapReadLifetimes(fn, this.boxedUnionsById, this.callLifetimes);
|
|
4127
|
+
this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.boxedUnionsById);
|
|
4128
|
+
this.localUnionStorageProofs = findLocalUnionStorage(fn, this.callLifetimes, this.boxedUnionsById, (e) => this.nullableFieldGet(e) !== null);
|
|
3443
4129
|
this.localUnionStorage = new Map();
|
|
3444
|
-
this.
|
|
4130
|
+
this.walkBorrows = this.walkBorrowsByFunction.get(fn.name) ?? new Set();
|
|
4131
|
+
this.integerRanges = analyzeIntegerRanges(numericFn, this.int32Slots.facts(fn.name));
|
|
3445
4132
|
this.bytesBounds = findBytesBounds(numericFn, this.integerRanges);
|
|
3446
4133
|
this.chainSlots.clear();
|
|
3447
4134
|
this.finallyStack = [];
|
|
3448
4135
|
this.tryStack = [];
|
|
4136
|
+
this.loopDepth = 0;
|
|
3449
4137
|
this.currentReturnType = fn.returnType;
|
|
3450
4138
|
this.currentGenerator = fn.generator ?? null;
|
|
4139
|
+
this.currentBorrowedReturn = this.borrowedReturns.has(fn.name);
|
|
3451
4140
|
this.currentWasiCoro = null;
|
|
3452
4141
|
this.logArgSlots = 0;
|
|
3453
4142
|
if (this.wasi && (fn.async === true || fn.generator !== undefined)) {
|
|
@@ -3551,7 +4240,12 @@ export class LlEmitter {
|
|
|
3551
4240
|
const cache = B.slot();
|
|
3552
4241
|
B.entryAllocas.push(`${cache} = alloca ptr ; lazy box ${this.currentLocals.get(id).name}`);
|
|
3553
4242
|
B.line(`store ptr null, ptr ${cache}`);
|
|
3554
|
-
fnScope.push({
|
|
4243
|
+
fnScope.push({
|
|
4244
|
+
slot: cache,
|
|
4245
|
+
type: this.currentLocals.get(id).type,
|
|
4246
|
+
boxed: true,
|
|
4247
|
+
lazy: true,
|
|
4248
|
+
});
|
|
3555
4249
|
this.lazyCaptureBoxes.set(id, cache);
|
|
3556
4250
|
// Environments that finish during the call can share an immortal
|
|
3557
4251
|
// frame box. Its payload owner is released with the function scope.
|
|
@@ -3578,6 +4272,15 @@ export class LlEmitter {
|
|
|
3578
4272
|
this.storeIntegerView(p.localId, { name: `%p_${mangleLocal(p.localId)}`, type: p.type });
|
|
3579
4273
|
if (isRefCounted(p.type) && !borrowed?.has(index))
|
|
3580
4274
|
fnScope.push({ slot, type: p.type });
|
|
4275
|
+
if (this.reboundByFunction.get(fn.name)?.has(p.localId) && borrowed?.has(index)) {
|
|
4276
|
+
// The caller's borrow stays in the parameter slot; values the body
|
|
4277
|
+
// assigns are owned here until rebinding or exit releases them.
|
|
4278
|
+
const owner = `${slot}.owner`;
|
|
4279
|
+
B.entryAllocas.push(`${owner} = alloca ptr`);
|
|
4280
|
+
B.line(`store ptr null, ptr ${owner}`);
|
|
4281
|
+
fnScope.push({ slot: owner, type: p.type });
|
|
4282
|
+
this.reboundOwnerSlots.set(p.localId, owner);
|
|
4283
|
+
}
|
|
3581
4284
|
}
|
|
3582
4285
|
this.scopes.push(fnScope);
|
|
3583
4286
|
// Stackful native fibers retain these frames while suspended. The
|
|
@@ -3590,8 +4293,10 @@ export class LlEmitter {
|
|
|
3590
4293
|
B.line(`call void @scr_stack_enter(ptr %source_frame, ptr ${this.cstr(frame)})`);
|
|
3591
4294
|
B.returnEpilogue = `call void @scr_stack_leave(ptr %source_frame)`;
|
|
3592
4295
|
}
|
|
3593
|
-
if (this.
|
|
3594
|
-
this.
|
|
4296
|
+
if (this.stackChecks?.has(fn.name))
|
|
4297
|
+
this.emitStackCheck();
|
|
4298
|
+
if (this.workerEntryPolls?.has(fn.name))
|
|
4299
|
+
this.emitPendingCheck(undefined, true);
|
|
3595
4300
|
this.emitStmts(fn.body);
|
|
3596
4301
|
// Implicit exit of a void function: release the function scope unless
|
|
3597
4302
|
// the body already terminated its final block (return, or a throw
|
|
@@ -3662,24 +4367,55 @@ export class LlEmitter {
|
|
|
3662
4367
|
const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`;
|
|
3663
4368
|
const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
|
|
3664
4369
|
const body = `define internal ${ret} @${symbol}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
|
|
3665
|
-
|
|
4370
|
+
if (!borrowed)
|
|
4371
|
+
return body;
|
|
4372
|
+
const slot = this.implSlotBorrowed(fn.name);
|
|
4373
|
+
const virtual = slot.size > 0 && slot.size !== borrowed.size
|
|
4374
|
+
? "\n" + this.emitOwnedCallAdapter(fn, borrowed, slot)
|
|
4375
|
+
: "";
|
|
4376
|
+
return body + "\n" + this.emitOwnedCallAdapter(fn, borrowed) + virtual;
|
|
3666
4377
|
}
|
|
3667
4378
|
/** Keep the ordinary owned ABI for closures, virtual dispatch, generated
|
|
3668
4379
|
* runtime adapters and library exports. Only direct IR calls select the
|
|
3669
4380
|
* borrowing body. Ownership of other parameters moves into that body;
|
|
3670
4381
|
* borrowed parameters are released here on normal and exceptional exits.
|
|
3671
4382
|
* The pending exception remains for the adapter's caller to handle. */
|
|
3672
|
-
|
|
4383
|
+
/** With `kept`, the virtual adapter of a borrowing slot instead: the
|
|
4384
|
+
* slot's parameters stay borrowed, the body's other borrowed ones are
|
|
4385
|
+
* released here. */
|
|
4386
|
+
emitOwnedCallAdapter(fn, borrowed, kept = NO_BORROWED) {
|
|
3673
4387
|
const B = new BlockBuilder();
|
|
3674
4388
|
this.B = B;
|
|
3675
4389
|
const params = fn.params.map((param, index) => `${this.llType(param.type)} %p${index}`);
|
|
3676
4390
|
const ret = this.llType(fn.returnType);
|
|
3677
4391
|
const call = `call ${ret} @${mangleBorrowedFunction(fn.name)}(${params.join(", ")})`;
|
|
3678
4392
|
B.line(ret === "void" ? call : `%result = ${call}`);
|
|
4393
|
+
// A borrowed result is reachable from the parameters: own it first. A
|
|
4394
|
+
// throwing body returns a null dummy, which needs no owner (and string
|
|
4395
|
+
// or array retains do not accept NULL).
|
|
4396
|
+
let result = "%result";
|
|
4397
|
+
if (this.borrowedReturns.has(fn.name)) {
|
|
4398
|
+
const slot = B.slot();
|
|
4399
|
+
B.entryAllocas.push(`${slot} = alloca ptr`);
|
|
4400
|
+
B.line(`store ptr null, ptr ${slot}`);
|
|
4401
|
+
const present = B.tmp();
|
|
4402
|
+
const own = B.newLabel("result.own");
|
|
4403
|
+
const done = B.newLabel("result.done");
|
|
4404
|
+
B.line(`${present} = icmp ne ptr %result, null`);
|
|
4405
|
+
B.condBr(present, own, done);
|
|
4406
|
+
B.startBlock(own);
|
|
4407
|
+
B.line(`store ptr ${this.retainValue("%result", fn.returnType)}, ptr ${slot}`);
|
|
4408
|
+
B.br(done);
|
|
4409
|
+
B.startBlock(done);
|
|
4410
|
+
result = B.tmp();
|
|
4411
|
+
B.line(`${result} = load ptr, ptr ${slot}`);
|
|
4412
|
+
}
|
|
3679
4413
|
for (const index of borrowed)
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
4414
|
+
if (!kept.has(index))
|
|
4415
|
+
this.releaseValue(`%p${index}`, fn.params[index].type);
|
|
4416
|
+
B.terminate(ret === "void" ? "ret void" : `ret ${ret} ${result}`);
|
|
4417
|
+
const symbol = kept.size > 0 ? `${mangleBorrowedFunction(fn.name)}.virtual` : mangleFunction(fn.name);
|
|
4418
|
+
return `define internal ${ret} @${symbol}(${params.join(", ")}) ${FN_ATTRS} {\n${B.render()}\n}`;
|
|
3683
4419
|
}
|
|
3684
4420
|
/** A local stable binding keeps its value alive while later arguments
|
|
3685
4421
|
* and the callee execute. A proven local union box cannot escape or be
|
|
@@ -3688,6 +4424,46 @@ export class LlEmitter {
|
|
|
3688
4424
|
canBorrowCallArgument(value) {
|
|
3689
4425
|
if (value.kind === "strLit")
|
|
3690
4426
|
return true;
|
|
4427
|
+
// Class casts reinterpret the same pointer (prefix layouts).
|
|
4428
|
+
if ((value.kind === "upcast" || value.kind === "downcast") &&
|
|
4429
|
+
value.value.type.kind === "object")
|
|
4430
|
+
return this.canBorrowCallArgument(value.value);
|
|
4431
|
+
if (value.kind === "seqExpr") {
|
|
4432
|
+
const guarded = guardedValue(value);
|
|
4433
|
+
return guarded !== null && this.canBorrowCallArgument(guarded);
|
|
4434
|
+
}
|
|
4435
|
+
// A nullable-union wrap reinterprets its payload pointer; an immortal
|
|
4436
|
+
// unit constant needs no owner at all.
|
|
4437
|
+
const wrap = this.borrowableNullableWrap(value);
|
|
4438
|
+
if (wrap === "unit")
|
|
4439
|
+
return true;
|
|
4440
|
+
if (wrap === "ref" && value.kind === "unionWrap")
|
|
4441
|
+
return this.canBorrowCallArgument(value.value);
|
|
4442
|
+
// Checked casts (`x as C`, `x!` on a nullable) lower to a ternary whose
|
|
4443
|
+
// failing arm always throws; the successful arm projects the same
|
|
4444
|
+
// stable owner. The throwing arm's typed dummy owns nothing.
|
|
4445
|
+
if (value.kind === "unionNarrow")
|
|
4446
|
+
return this.canBorrowCallArgument(value.value);
|
|
4447
|
+
if (value.kind === "libCall")
|
|
4448
|
+
return value.fn === "error.nodeThrow";
|
|
4449
|
+
// A final field never changes outside constructors: while its object is
|
|
4450
|
+
// borrowable, so is the field's value. Nullable-pointer fields have no
|
|
4451
|
+
// box to lend and keep their owning path.
|
|
4452
|
+
if (value.kind === "fieldGet" &&
|
|
4453
|
+
this.finalFieldBorrows &&
|
|
4454
|
+
isRefCounted(value.type) &&
|
|
4455
|
+
this.finalFields.isFinal(value.className, value.field) &&
|
|
4456
|
+
!this.nullableFieldGet(value))
|
|
4457
|
+
return this.canBorrowReceiver(value.obj) && this.canBorrowCallArgument(value.obj);
|
|
4458
|
+
if (value.kind === "ternary")
|
|
4459
|
+
return (this.canBorrowReceiver(value.then) &&
|
|
4460
|
+
this.canBorrowReceiver(value.else_) &&
|
|
4461
|
+
this.canBorrowCallArgument(value.then) &&
|
|
4462
|
+
this.canBorrowCallArgument(value.else_));
|
|
4463
|
+
// An optional chain's bound receiver stays unchanged until its body
|
|
4464
|
+
// finishes (see emitReadReceiver).
|
|
4465
|
+
if (value.kind === "chainRecv")
|
|
4466
|
+
return this.chainSlots.has(value.id);
|
|
3691
4467
|
if (value.kind !== "varRef")
|
|
3692
4468
|
return false;
|
|
3693
4469
|
const binding = this.binding(value.localId);
|
|
@@ -3729,11 +4505,16 @@ export class LlEmitter {
|
|
|
3729
4505
|
emitStmt(s) {
|
|
3730
4506
|
const previous = this.B.debugLocation;
|
|
3731
4507
|
this.B.debugLocation = this.debug?.location(s.loc, this.debugScope) ?? null;
|
|
4508
|
+
const loop = s.kind === "while" || s.kind === "doWhile" || s.kind === "for" || s.kind === "forOf";
|
|
4509
|
+
if (loop)
|
|
4510
|
+
this.loopDepth++;
|
|
3732
4511
|
try {
|
|
3733
4512
|
this.emitStmtBody(s);
|
|
3734
4513
|
}
|
|
3735
4514
|
finally {
|
|
3736
4515
|
this.B.debugLocation = previous;
|
|
4516
|
+
if (loop)
|
|
4517
|
+
this.loopDepth--;
|
|
3737
4518
|
}
|
|
3738
4519
|
}
|
|
3739
4520
|
emitStmtBody(s) {
|
|
@@ -3742,6 +4523,12 @@ export class LlEmitter {
|
|
|
3742
4523
|
switch (s.kind) {
|
|
3743
4524
|
case "varDecl": {
|
|
3744
4525
|
const b = this.binding(s.localId);
|
|
4526
|
+
if (this.walkBorrows.has(s.localId) && b.kind === "local") {
|
|
4527
|
+
// A borrowed walk pointer: no retain now, no release at scope exit.
|
|
4528
|
+
const v = s.init === null ? "null" : this.emitReadReceiver(s.init).name;
|
|
4529
|
+
B.line(`store ptr ${v}, ptr ${b.slot}`);
|
|
4530
|
+
break;
|
|
4531
|
+
}
|
|
3745
4532
|
const slice = this.scalarStringSlices.get(s.localId);
|
|
3746
4533
|
if (slice) {
|
|
3747
4534
|
const snapshot = emitStringSliceSnapshot(this, slice);
|
|
@@ -3824,8 +4611,14 @@ export class LlEmitter {
|
|
|
3824
4611
|
B.line(`${box} = ${boxNew} ; let ${b.local.name} (boxed)`);
|
|
3825
4612
|
B.line(`store ptr ${box}, ptr ${b.slot}`);
|
|
3826
4613
|
this.scopes[this.scopes.length - 1].push({ slot: b.slot, type: b.type, boxed: true });
|
|
3827
|
-
if (s.init === null)
|
|
4614
|
+
if (s.init === null) {
|
|
4615
|
+
// An empty TDZ box whose payload can legitimately be NULL
|
|
4616
|
+
// starts at its distinct sentinel (tdzEmptySentinel).
|
|
4617
|
+
const sentinel = b.local.tdz === true ? this.tdzEmptySentinel(b.type) : null;
|
|
4618
|
+
if (sentinel !== null)
|
|
4619
|
+
B.line(`store ptr ${sentinel}, ptr ${this.boxSlot(box)}`);
|
|
3828
4620
|
break;
|
|
4621
|
+
}
|
|
3829
4622
|
const v = this.emitExpr(s.init);
|
|
3830
4623
|
if (isRefCounted(v.type))
|
|
3831
4624
|
this.moveTemp(v); // the box takes ownership
|
|
@@ -3874,6 +4667,23 @@ export class LlEmitter {
|
|
|
3874
4667
|
break;
|
|
3875
4668
|
}
|
|
3876
4669
|
case "assign": {
|
|
4670
|
+
const owner = this.reboundOwnerSlots.get(s.localId);
|
|
4671
|
+
if (owner !== undefined && !this.walkBorrows.has(s.localId)) {
|
|
4672
|
+
// A rebound borrowed parameter: the new value moves into the owner
|
|
4673
|
+
// slot, replacing (and releasing) the previous owned value. This
|
|
4674
|
+
// precedes the in-place string append, which would release the
|
|
4675
|
+
// caller's borrow.
|
|
4676
|
+
const param = this.binding(s.localId);
|
|
4677
|
+
const slot = param.slot;
|
|
4678
|
+
const v = this.emitExpr(s.value);
|
|
4679
|
+
this.moveTemp(v);
|
|
4680
|
+
const old = B.tmp();
|
|
4681
|
+
B.line(`${old} = load ptr, ptr ${owner}`);
|
|
4682
|
+
this.releaseValue(old, param.type);
|
|
4683
|
+
B.line(`store ptr ${v.name}, ptr ${slot}`);
|
|
4684
|
+
B.line(`store ptr ${v.name}, ptr ${owner}`);
|
|
4685
|
+
break;
|
|
4686
|
+
}
|
|
3877
4687
|
const localStorage = this.localUnionStorage.get(s.localId);
|
|
3878
4688
|
if (localStorage) {
|
|
3879
4689
|
storeLocalUnion(this, localStorage, s.value);
|
|
@@ -3886,6 +4696,11 @@ export class LlEmitter {
|
|
|
3886
4696
|
break;
|
|
3887
4697
|
}
|
|
3888
4698
|
const b = this.binding(s.localId);
|
|
4699
|
+
if (this.walkBorrows.has(s.localId) && b.kind === "local") {
|
|
4700
|
+
// Rebinding a borrowed walk pointer releases nothing.
|
|
4701
|
+
B.line(`store ptr ${this.emitReadReceiver(s.value).name}, ptr ${b.slot}`);
|
|
4702
|
+
break;
|
|
4703
|
+
}
|
|
3889
4704
|
const v = this.emitExpr(s.value);
|
|
3890
4705
|
if (b.kind === "global" && !s.initializes)
|
|
3891
4706
|
this.checkGlobalTdz(s.localId);
|
|
@@ -3907,7 +4722,9 @@ export class LlEmitter {
|
|
|
3907
4722
|
break;
|
|
3908
4723
|
}
|
|
3909
4724
|
case "exprStmt":
|
|
3910
|
-
|
|
4725
|
+
// A statement-position splice never observes its removed elements.
|
|
4726
|
+
if (!emitDiscardedSplice(this, s.expr))
|
|
4727
|
+
this.emitDiscarded(s.expr);
|
|
3911
4728
|
break;
|
|
3912
4729
|
case "arraySet": {
|
|
3913
4730
|
// Evaluation order matches JS: array, index, then value. Ownership
|
|
@@ -3919,19 +4736,43 @@ export class LlEmitter {
|
|
|
3919
4736
|
if (s.arr.type.kind !== "array")
|
|
3920
4737
|
throw new InternalCompilerError("llvm emitter bug: arraySet on non-array");
|
|
3921
4738
|
const acc = elemAccess(s.arr.type.elem);
|
|
4739
|
+
this.emitPublishedArrayGuard(arr.name, s.arr.type, 0, idx.name);
|
|
3922
4740
|
if (acc === "ref")
|
|
3923
4741
|
this.moveTemp(v);
|
|
3924
4742
|
emitDenseArraySet(this, arr.name, idx, s.index, acc, s.arr.type.elem, v.name);
|
|
3925
4743
|
break;
|
|
3926
4744
|
}
|
|
3927
4745
|
case "arraySetLength": {
|
|
4746
|
+
// Pop's `length = 0` store on an empty array only matters when the
|
|
4747
|
+
// array can be frozen (published); its operand is a local.
|
|
4748
|
+
if (s.pop === true &&
|
|
4749
|
+
s.length.kind === "numLit" &&
|
|
4750
|
+
s.length.value === 0 &&
|
|
4751
|
+
this.publishedTypes?.containerGuarded(s.arr.type) !== true)
|
|
4752
|
+
break;
|
|
3928
4753
|
const arr = emitBorrowedInput(this, s.arr);
|
|
3929
4754
|
const length = this.emitExpr(s.length);
|
|
3930
4755
|
if (s.arr.type.kind !== "array")
|
|
3931
4756
|
throw new InternalCompilerError("llvm emitter bug: arraySetLength on non-array");
|
|
4757
|
+
this.emitPublishedArrayGuard(arr.name, s.arr.type, s.pop === true ? 2 : 3);
|
|
3932
4758
|
this.declare(`declare void @scr_arr_set_len(ptr, double)`);
|
|
4759
|
+
// Assigning the current length (a reused work array reset to where
|
|
4760
|
+
// it already is) changes nothing: the runtime's validation accepts
|
|
4761
|
+
// exactly the value equal to the length (-0 included, NaN never),
|
|
4762
|
+
// and no element is released. Everything else keeps the call.
|
|
4763
|
+
const lenPtr = B.tmp(), len = B.tmp(), lenNumber = B.tmp(), same = B.tmp();
|
|
4764
|
+
const resize = B.newLabel("arr.len.set"), done = B.newLabel("arr.len.done");
|
|
4765
|
+
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
4766
|
+
this.markMemoryPointer(lenPtr, "array:header");
|
|
4767
|
+
B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
4768
|
+
B.line(`${lenNumber} = sitofp ${this.sizeType} ${len} to double`);
|
|
4769
|
+
B.line(`${same} = fcmp oeq double ${length.name}, ${lenNumber}`);
|
|
4770
|
+
B.condBr(same, done, resize);
|
|
4771
|
+
B.startBlock(resize);
|
|
3933
4772
|
B.line(`call void @scr_arr_set_len(ptr ${arr.name}, double ${length.name})`);
|
|
3934
4773
|
this.emitPendingCheck();
|
|
4774
|
+
B.br(done);
|
|
4775
|
+
B.startBlock(done);
|
|
3935
4776
|
break;
|
|
3936
4777
|
}
|
|
3937
4778
|
case "arraySetUndefined":
|
|
@@ -3940,6 +4781,7 @@ export class LlEmitter {
|
|
|
3940
4781
|
const idx = this.emitExpr(s.index);
|
|
3941
4782
|
if (s.arr.type.kind !== "array")
|
|
3942
4783
|
throw new InternalCompilerError(`llvm emitter bug: ${s.kind} on non-array`);
|
|
4784
|
+
this.emitPublishedArrayGuard(arr.name, s.arr.type, s.kind === "arrayDelete" ? 5 : 0, idx.name);
|
|
3943
4785
|
const fn = s.kind === "arraySetUndefined" ? "scr_arr_set_undefined" : "scr_arr_delete";
|
|
3944
4786
|
this.declare(`declare ${s.kind === "arrayDelete" ? "zeroext i1" : "void"} @${fn}(ptr, double)`);
|
|
3945
4787
|
if (s.kind === "arrayDelete")
|
|
@@ -3971,14 +4813,27 @@ export class LlEmitter {
|
|
|
3971
4813
|
// Classes and records share the struct layout, so one emission.
|
|
3972
4814
|
const nullable = s.kind === "fieldSet" ? this.nullableFields.get(s.className, s.field) : null;
|
|
3973
4815
|
if (nullable && s.kind === "fieldSet") {
|
|
3974
|
-
const obj = this.
|
|
4816
|
+
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
4817
|
+
// (The guard precedes the value here: the nullable store
|
|
4818
|
+
// evaluates it itself.)
|
|
4819
|
+
this.emitPublishedFieldGuard(obj.name, s.className, s.field);
|
|
3975
4820
|
this.emitNullableFieldStore(obj.name, s.className, s.field, nullable, s.value);
|
|
3976
4821
|
break;
|
|
3977
4822
|
}
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
4823
|
+
if (s.kind === "fieldSet" && this.int32Slots.isField(s.className, s.field)) {
|
|
4824
|
+
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
4825
|
+
const v = this.emitExpr(s.value);
|
|
4826
|
+
this.emitPublishedFieldGuard(obj.name, s.className, s.field);
|
|
4827
|
+
const { ptr } = this.classFieldPtr(obj.name, s.className, s.field);
|
|
4828
|
+
this.storeInt32Field(ptr, v, s.value);
|
|
4829
|
+
break;
|
|
4830
|
+
}
|
|
4831
|
+
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
3981
4832
|
const v = this.emitExpr(s.value);
|
|
4833
|
+
if (s.kind === "fieldSet")
|
|
4834
|
+
this.emitPublishedFieldGuard(obj.name, s.className, s.field);
|
|
4835
|
+
else if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
|
|
4836
|
+
this.emitPublishedFieldGuard(obj.name, null, s.field);
|
|
3982
4837
|
const { ptr, type } = s.kind === "fieldSet"
|
|
3983
4838
|
? this.classFieldPtr(obj.name, s.className, s.field)
|
|
3984
4839
|
: this.recordFieldPtr(obj.name, s.shapeId, s.field);
|
|
@@ -4012,6 +4867,11 @@ export class LlEmitter {
|
|
|
4012
4867
|
// the overflow (key and value released; absent keys no-op).
|
|
4013
4868
|
const obj = this.emitExpr(s.obj);
|
|
4014
4869
|
const key = this.emitExpr(s.key);
|
|
4870
|
+
if (this.publishedTypes?.recordGuarded(s.shapeId) === true) {
|
|
4871
|
+
// Node: "Cannot delete property 'k' of #<Object>" (key known at
|
|
4872
|
+
// run time only; the guard reports the frozen record generically).
|
|
4873
|
+
this.emitPublishedFieldGuard(obj.name, null, "[key]");
|
|
4874
|
+
}
|
|
4015
4875
|
const ovf = this.recordOvfPtr(obj.name, s.shapeId);
|
|
4016
4876
|
this.declare(`declare zeroext i1 @scr_map_delete_str(ptr, ptr)`);
|
|
4017
4877
|
const t = B.tmp();
|
|
@@ -4027,6 +4887,8 @@ export class LlEmitter {
|
|
|
4027
4887
|
const obj = this.emitExpr(s.obj);
|
|
4028
4888
|
const key = this.emitExpr(s.key);
|
|
4029
4889
|
const v = this.emitExpr(s.value);
|
|
4890
|
+
if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
|
|
4891
|
+
this.emitPublishedFieldGuard(obj.name, null, "[key]");
|
|
4030
4892
|
if (isRefCounted(v.type))
|
|
4031
4893
|
this.moveTemp(v);
|
|
4032
4894
|
const shape = this.recordShape(s.shapeId);
|
|
@@ -4081,9 +4943,9 @@ export class LlEmitter {
|
|
|
4081
4943
|
B.line(`call void @scr_dyn_release(ptr ${v.name})`);
|
|
4082
4944
|
// Mismatched write: TypeError pending, field untouched — the
|
|
4083
4945
|
// statement-level check below unwinds.
|
|
4084
|
-
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
4085
4946
|
const pend = B.tmp();
|
|
4086
|
-
|
|
4947
|
+
for (const line of this.pendingTestLines(pend))
|
|
4948
|
+
B.line(line.trimStart());
|
|
4087
4949
|
const lw = B.newLabel("rks.w");
|
|
4088
4950
|
B.condBr(pend, join, lw);
|
|
4089
4951
|
B.startBlock(lw);
|
|
@@ -4191,7 +5053,7 @@ export class LlEmitter {
|
|
|
4191
5053
|
break;
|
|
4192
5054
|
}
|
|
4193
5055
|
case "while": {
|
|
4194
|
-
const workerBudget = this.workerLoopBudget();
|
|
5056
|
+
const workerBudget = this.workerLoopBudget(s);
|
|
4195
5057
|
const lc = B.newLabel("loop.c");
|
|
4196
5058
|
const lb = B.newLabel("loop.b");
|
|
4197
5059
|
const le = B.newLabel("loop.e");
|
|
@@ -4216,7 +5078,7 @@ export class LlEmitter {
|
|
|
4216
5078
|
break;
|
|
4217
5079
|
}
|
|
4218
5080
|
case "doWhile": {
|
|
4219
|
-
const workerBudget = this.workerLoopBudget();
|
|
5081
|
+
const workerBudget = this.workerLoopBudget(s);
|
|
4220
5082
|
// Body first (runs at least once); continue jumps to the CONDITION.
|
|
4221
5083
|
const lb = B.newLabel("loop.b");
|
|
4222
5084
|
const lc = B.newLabel("loop.c");
|
|
@@ -4281,12 +5143,39 @@ export class LlEmitter {
|
|
|
4281
5143
|
}
|
|
4282
5144
|
let countedJoin = null;
|
|
4283
5145
|
if (countedLoop?.guarded) {
|
|
4284
|
-
|
|
4285
|
-
|
|
5146
|
+
let safe = "true";
|
|
5147
|
+
if (countedLoop.limitGuarded) {
|
|
5148
|
+
const bound = this.emitExpr(countedLoop.limit);
|
|
5149
|
+
safe = B.tmp();
|
|
5150
|
+
B.line(`${safe} = fcmp ${countedLoop.step > 0 ? "ole" : "oge"} double ${bound.name}, ${f64Lit(countedLoop.guardLimit)}`);
|
|
5151
|
+
}
|
|
5152
|
+
if (countedLoop.startGuarded) {
|
|
5153
|
+
// An exact integer of at most 2^53 - 1 in magnitude, and not -0
|
|
5154
|
+
// (the bit patterns of the value and its round trip agree).
|
|
5155
|
+
// NaN and infinities fail the ordered range tests.
|
|
5156
|
+
const start = this.emitExpr(countedLoop.start);
|
|
5157
|
+
const low = B.tmp(), high = B.tmp(), raw = B.tmp(), wide = B.tmp(), back = B.tmp(), bits = B.tmp(), backBits = B.tmp(), exact = B.tmp(), inRange = B.tmp(), ok = B.tmp();
|
|
5158
|
+
B.line(`${low} = fcmp oge double ${start.name}, ${f64Lit(-Number.MAX_SAFE_INTEGER)}`);
|
|
5159
|
+
B.line(`${high} = fcmp ole double ${start.name}, ${f64Lit(Number.MAX_SAFE_INTEGER)}`);
|
|
5160
|
+
B.line(`${inRange} = and i1 ${low}, ${high}`);
|
|
5161
|
+
B.line(`${raw} = fptosi double ${start.name} to i64`);
|
|
5162
|
+
B.line(`${wide} = freeze i64 ${raw}`);
|
|
5163
|
+
B.line(`${back} = sitofp i64 ${wide} to double`);
|
|
5164
|
+
B.line(`${bits} = bitcast double ${start.name} to i64`);
|
|
5165
|
+
B.line(`${backBits} = bitcast double ${back} to i64`);
|
|
5166
|
+
B.line(`${exact} = icmp eq i64 ${bits}, ${backBits}`);
|
|
5167
|
+
B.line(`${ok} = and i1 ${inRange}, ${exact}`);
|
|
5168
|
+
if (safe === "true")
|
|
5169
|
+
safe = ok;
|
|
5170
|
+
else {
|
|
5171
|
+
const both = B.tmp();
|
|
5172
|
+
B.line(`${both} = and i1 ${safe}, ${ok}`);
|
|
5173
|
+
safe = both;
|
|
5174
|
+
}
|
|
5175
|
+
}
|
|
4286
5176
|
const fast = B.newLabel("counted.fast");
|
|
4287
5177
|
const slow = B.newLabel("counted.slow");
|
|
4288
5178
|
countedJoin = B.newLabel("counted.done");
|
|
4289
|
-
B.line(`${safe} = fcmp ${countedLoop.step > 0 ? "ole" : "oge"} double ${bound.name}, ${f64Lit(countedLoop.guardLimit)}`);
|
|
4290
5179
|
B.condBr(safe, fast, slow);
|
|
4291
5180
|
B.startBlock(slow);
|
|
4292
5181
|
this.countedLoopsEnabled = false;
|
|
@@ -4367,7 +5256,7 @@ export class LlEmitter {
|
|
|
4367
5256
|
};
|
|
4368
5257
|
const lu = s.update || capturedLets.length > 0 ? B.newLabel("loop.u") : lc;
|
|
4369
5258
|
freshenBindings();
|
|
4370
|
-
const workerBudget = this.workerLoopBudget();
|
|
5259
|
+
const workerBudget = this.workerLoopBudget(s);
|
|
4371
5260
|
B.br(lc);
|
|
4372
5261
|
B.startBlock(lc);
|
|
4373
5262
|
this.emitWorkerLoopCheck(workerBudget);
|
|
@@ -4475,7 +5364,7 @@ export class LlEmitter {
|
|
|
4475
5364
|
const lb = B.newLabel("fof.b");
|
|
4476
5365
|
const lu = B.newLabel("fof.u");
|
|
4477
5366
|
const le = B.newLabel("fof.e");
|
|
4478
|
-
const workerBudget = this.workerLoopBudget();
|
|
5367
|
+
const workerBudget = this.workerLoopBudget(s);
|
|
4479
5368
|
B.br(lc);
|
|
4480
5369
|
B.startBlock(lc);
|
|
4481
5370
|
this.emitWorkerLoopCheck(workerBudget);
|
|
@@ -4494,7 +5383,7 @@ export class LlEmitter {
|
|
|
4494
5383
|
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
4495
5384
|
this.markMemoryPointer(lenPtr, "array:header");
|
|
4496
5385
|
B.line(`${rawLen} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
4497
|
-
B.line(`${len} =
|
|
5386
|
+
B.line(`${len} = sitofp ${this.sizeType} ${rawLen} to double`);
|
|
4498
5387
|
B.line(`${inBounds} = fcmp olt double ${i}, ${len}`);
|
|
4499
5388
|
cur = i;
|
|
4500
5389
|
}
|
|
@@ -4611,7 +5500,12 @@ export class LlEmitter {
|
|
|
4611
5500
|
// the actual ret. The parked value owns a synthetic slot-backed
|
|
4612
5501
|
// scope entry during each copy so a throwing finally releases it.
|
|
4613
5502
|
let v = null;
|
|
4614
|
-
if (s.value !== null) {
|
|
5503
|
+
if (s.value !== null && this.currentBorrowedReturn) {
|
|
5504
|
+
// A borrowed-return body hands back its walk without a reference
|
|
5505
|
+
// (it has no try statements, so no finally can intervene).
|
|
5506
|
+
v = this.emitReadReceiver(s.value);
|
|
5507
|
+
}
|
|
5508
|
+
else if (s.value !== null) {
|
|
4615
5509
|
v = this.emitExpr(s.value);
|
|
4616
5510
|
this.moveTemp(v);
|
|
4617
5511
|
}
|
|
@@ -4986,24 +5880,62 @@ export class LlEmitter {
|
|
|
4986
5880
|
return emitLiteralExpr(this, e);
|
|
4987
5881
|
}
|
|
4988
5882
|
emitOperatorExpr(e) {
|
|
4989
|
-
if (e.kind === "seqExpr")
|
|
4990
|
-
|
|
4991
|
-
// to this expression's frame so they survive those reads and still
|
|
4992
|
-
// die on the path that created them, including inside lazy branches.
|
|
4993
|
-
this.scopes.push([]);
|
|
4994
|
-
const result = emitBorrowedFieldSequence(this, e) ?? emitOperatorExpr(this, e);
|
|
4995
|
-
for (const local of this.scopes.pop()) {
|
|
4996
|
-
this.currentFrame().push({
|
|
4997
|
-
name: local.slot,
|
|
4998
|
-
type: local.type,
|
|
4999
|
-
slot: true,
|
|
5000
|
-
...(local.boxed ? { boxed: true } : {}),
|
|
5001
|
-
});
|
|
5002
|
-
}
|
|
5003
|
-
return result;
|
|
5004
|
-
}
|
|
5883
|
+
if (e.kind === "seqExpr")
|
|
5884
|
+
return this.emitSequence(() => emitBorrowedFieldSequence(this, e) ?? emitOperatorExpr(this, e));
|
|
5005
5885
|
return emitOperatorExpr(this, e);
|
|
5006
5886
|
}
|
|
5887
|
+
/** Saved operands can feed later call arguments. Transfer ownership of a
|
|
5888
|
+
* sequence's locals to this expression's frame so they survive those
|
|
5889
|
+
* reads and still die on the path that created them, including inside
|
|
5890
|
+
* lazy branches. */
|
|
5891
|
+
emitSequence(body) {
|
|
5892
|
+
this.scopes.push([]);
|
|
5893
|
+
const result = body();
|
|
5894
|
+
for (const local of this.scopes.pop()) {
|
|
5895
|
+
this.currentFrame().push({
|
|
5896
|
+
name: local.slot,
|
|
5897
|
+
type: local.type,
|
|
5898
|
+
slot: true,
|
|
5899
|
+
...(local.boxed ? { boxed: true } : {}),
|
|
5900
|
+
});
|
|
5901
|
+
}
|
|
5902
|
+
return result;
|
|
5903
|
+
}
|
|
5904
|
+
/** A statement-position expression whose value is dropped. When a
|
|
5905
|
+
* sequence's result only reads an unchanged local (a derived
|
|
5906
|
+
* constructor's `super()` evaluates to `this`), its statements still run
|
|
5907
|
+
* in place, but the result is neither retained nor released. */
|
|
5908
|
+
/** Whether a discarded value can be skipped entirely: a borrowable read
|
|
5909
|
+
* with no effect of its own. canBorrowCallArgument also accepts checked
|
|
5910
|
+
* projections (a ternary or narrow whose failing arm throws, and the
|
|
5911
|
+
* throwing call itself) because a borrowed use still evaluates them;
|
|
5912
|
+
* skipping those would drop the throw. */
|
|
5913
|
+
discardableRead(value) {
|
|
5914
|
+
if (value.kind === "libCall" || value.kind === "ternary" || value.kind === "unionNarrow")
|
|
5915
|
+
return false;
|
|
5916
|
+
if ((value.kind === "upcast" || value.kind === "downcast" || value.kind === "unionWrap") &&
|
|
5917
|
+
!this.discardableRead(value.value))
|
|
5918
|
+
return false;
|
|
5919
|
+
if (value.kind === "seqExpr") {
|
|
5920
|
+
const guarded = guardedValue(value);
|
|
5921
|
+
if (guarded === null || !this.discardableRead(guarded))
|
|
5922
|
+
return false;
|
|
5923
|
+
}
|
|
5924
|
+
return this.canBorrowCallArgument(value);
|
|
5925
|
+
}
|
|
5926
|
+
emitDiscarded(e) {
|
|
5927
|
+
if (e.kind === "seqExpr" &&
|
|
5928
|
+
isRefCounted(e.result.type) &&
|
|
5929
|
+
e.result.kind !== "strLit" &&
|
|
5930
|
+
this.discardableRead(e.result)) {
|
|
5931
|
+
this.emitSequence(() => {
|
|
5932
|
+
for (const s of e.stmts)
|
|
5933
|
+
this.emitStmt(s);
|
|
5934
|
+
});
|
|
5935
|
+
return;
|
|
5936
|
+
}
|
|
5937
|
+
this.emitExpr(e);
|
|
5938
|
+
}
|
|
5007
5939
|
emitControlExpr(e) {
|
|
5008
5940
|
return emitControlExpr(this, e);
|
|
5009
5941
|
}
|
|
@@ -5017,8 +5949,17 @@ export class LlEmitter {
|
|
|
5017
5949
|
* value, making the snapshot unique unless a real observable alias exists.
|
|
5018
5950
|
*/
|
|
5019
5951
|
emitStringSelfConcatAssign(localId, left, suffix, retainForYield) {
|
|
5952
|
+
const moved = this.emitMovedSelfConcat(localId, left, suffix, retainForYield);
|
|
5953
|
+
if (moved)
|
|
5954
|
+
return moved;
|
|
5020
5955
|
const snapshot = this.emitExpr(left);
|
|
5021
|
-
|
|
5956
|
+
// A suffix of several parts, or a number, appends each operand to the
|
|
5957
|
+
// snapshot directly (in place once the binding lets go of it) instead
|
|
5958
|
+
// of first building the suffix as a separate string.
|
|
5959
|
+
const parts = stringParts(suffix);
|
|
5960
|
+
const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
|
|
5961
|
+
const inputs = mixed ? emitConcatInputs(this, parts) : [];
|
|
5962
|
+
const right = mixed ? null : this.emitExpr(suffix);
|
|
5022
5963
|
const b = this.binding(localId);
|
|
5023
5964
|
const B = this.B;
|
|
5024
5965
|
if (b.kind === "boxed") {
|
|
@@ -5031,9 +5972,15 @@ export class LlEmitter {
|
|
|
5031
5972
|
B.line(`store ptr null, ptr ${b.slot}`);
|
|
5032
5973
|
this.releaseValue(old, b.type);
|
|
5033
5974
|
}
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5975
|
+
let raw;
|
|
5976
|
+
if (right === null) {
|
|
5977
|
+
raw = emitMixedConcat(this, snapshot.name, inputs);
|
|
5978
|
+
}
|
|
5979
|
+
else {
|
|
5980
|
+
this.declare(`declare ptr @scr_str_concat(ptr, ptr)`);
|
|
5981
|
+
raw = B.tmp();
|
|
5982
|
+
B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`);
|
|
5983
|
+
}
|
|
5037
5984
|
const result = this.own({ name: raw, type: left.type });
|
|
5038
5985
|
if (retainForYield) {
|
|
5039
5986
|
const stored = this.retainValue(result.name, result.type);
|
|
@@ -5051,6 +5998,43 @@ export class LlEmitter {
|
|
|
5051
5998
|
}
|
|
5052
5999
|
return result;
|
|
5053
6000
|
}
|
|
6001
|
+
/** `local = local + suffix` when the suffix cannot read or write the
|
|
6002
|
+
* local (an unboxed binding is only reachable through this function's own
|
|
6003
|
+
* expressions): reading the binding after the suffix gives the value the
|
|
6004
|
+
* left side had, so it moves into a consuming concat, and the result takes
|
|
6005
|
+
* its place. Null when the general snapshot path is needed. */
|
|
6006
|
+
emitMovedSelfConcat(localId, left, suffix, retainForYield) {
|
|
6007
|
+
const b = this.binding(localId);
|
|
6008
|
+
if (retainForYield ||
|
|
6009
|
+
b.kind !== "local" ||
|
|
6010
|
+
b.local === undefined ||
|
|
6011
|
+
b.local.boxed ||
|
|
6012
|
+
b.local.tdz ||
|
|
6013
|
+
b.type.kind !== "string" ||
|
|
6014
|
+
left.kind !== "varRef" ||
|
|
6015
|
+
left.localId !== localId ||
|
|
6016
|
+
mentionsLocal(suffix, localId))
|
|
6017
|
+
return null;
|
|
6018
|
+
const parts = stringParts(suffix);
|
|
6019
|
+
const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
|
|
6020
|
+
const inputs = mixed ? emitConcatInputs(this, parts) : [];
|
|
6021
|
+
const right = mixed ? null : this.emitExpr(suffix);
|
|
6022
|
+
this.materializeSplitLocal(localId);
|
|
6023
|
+
const B = this.B;
|
|
6024
|
+
const head = B.tmp();
|
|
6025
|
+
B.line(`${head} = load ptr, ptr ${b.slot}`);
|
|
6026
|
+
let raw;
|
|
6027
|
+
if (right === null) {
|
|
6028
|
+
raw = emitMixedConcat(this, head, inputs, true);
|
|
6029
|
+
}
|
|
6030
|
+
else {
|
|
6031
|
+
this.declare(`declare ptr @scr_str_concat_move(ptr, ptr)`);
|
|
6032
|
+
raw = B.tmp();
|
|
6033
|
+
B.line(`${raw} = call ptr @scr_str_concat_move(ptr ${head}, ptr ${right.name})`);
|
|
6034
|
+
}
|
|
6035
|
+
B.line(`store ptr ${raw}, ptr ${b.slot}`);
|
|
6036
|
+
return { name: raw, type: left.type }; // moved into the binding
|
|
6037
|
+
}
|
|
5054
6038
|
emitContainerExpr(e) {
|
|
5055
6039
|
return emitContainerExpr(this, e);
|
|
5056
6040
|
}
|
|
@@ -5064,8 +6048,152 @@ export class LlEmitter {
|
|
|
5064
6048
|
return emitDynamicExpr(this, e);
|
|
5065
6049
|
}
|
|
5066
6050
|
emitIntrinsicExpr(e) {
|
|
6051
|
+
if (e.name === "threads.publish")
|
|
6052
|
+
return this.emitPublish(e);
|
|
5067
6053
|
return emitIntrinsicExpr(this, e);
|
|
5068
6054
|
}
|
|
6055
|
+
publishWalkersCache = null;
|
|
6056
|
+
/** Per-type publication walkers, created on the first publish. */
|
|
6057
|
+
get publishWalkers() {
|
|
6058
|
+
if (this.publishWalkersCache !== null)
|
|
6059
|
+
return this.publishWalkersCache;
|
|
6060
|
+
const byPre = [...this.classMeta.values()].sort((a, b) => a.pre - b.pre);
|
|
6061
|
+
this.publishWalkersCache = new PublishWalkers({
|
|
6062
|
+
declare: (decl) => this.declare(decl),
|
|
6063
|
+
cstr: (text) => this.cstr(text),
|
|
6064
|
+
sizeType: this.sizeType,
|
|
6065
|
+
tracedShapes: this.tracedShapes,
|
|
6066
|
+
unionsById: this.unionsById,
|
|
6067
|
+
recordsById: this.recordsById,
|
|
6068
|
+
nullableArm: (t) => this.nullableUnions.of(t)?.arm ?? null,
|
|
6069
|
+
classInfo: (className) => {
|
|
6070
|
+
const meta = this.classMetaOf(className);
|
|
6071
|
+
return {
|
|
6072
|
+
def: meta.def,
|
|
6073
|
+
hierarchy: meta.hierarchy,
|
|
6074
|
+
pre: meta.pre,
|
|
6075
|
+
post: meta.post,
|
|
6076
|
+
runtimeRooted: meta.root.def.runtime === true,
|
|
6077
|
+
fields: meta.def.fields.map((f) => ({
|
|
6078
|
+
name: f.name,
|
|
6079
|
+
index: classFieldIndex(meta, f.name).index,
|
|
6080
|
+
type: this.nullableFields.storageType(className, f.name, f.type),
|
|
6081
|
+
})),
|
|
6082
|
+
};
|
|
6083
|
+
},
|
|
6084
|
+
classesInInterval: (pre, post) => byPre.filter((m) => m.pre >= pre && m.pre <= post).map((m) => m.def.name),
|
|
6085
|
+
});
|
|
6086
|
+
return this.publishWalkersCache;
|
|
6087
|
+
}
|
|
6088
|
+
/** `publish(value)` (@scriptc/threads): makes the graph immortal and
|
|
6089
|
+
* immutable (scr_publish with the static type's walker) and answers the
|
|
6090
|
+
* same value. Throws (pending) when a dyn part is unpublishable. */
|
|
6091
|
+
emitPublish(e) {
|
|
6092
|
+
const value = this.emitExpr(e.args[0]);
|
|
6093
|
+
if (this.B.isTerminated())
|
|
6094
|
+
return value;
|
|
6095
|
+
const lines = this.publishWalkers.emitPublish(value.name, value.type, e.loc);
|
|
6096
|
+
if (lines.length > 0) {
|
|
6097
|
+
for (const line of lines)
|
|
6098
|
+
this.B.line(line.trimStart());
|
|
6099
|
+
// A refusal unwinds while the operand temp still owns its reference.
|
|
6100
|
+
this.emitPendingCheck();
|
|
6101
|
+
}
|
|
6102
|
+
// The result IS the operand: ownership moves to the result temp.
|
|
6103
|
+
if (!isRefCounted(value.type))
|
|
6104
|
+
return { name: value.name, type: e.type };
|
|
6105
|
+
this.moveTemp(value);
|
|
6106
|
+
return this.own({ name: value.name, type: e.type });
|
|
6107
|
+
}
|
|
6108
|
+
/** Branches on "`obj` is published" (rc == SIZE_MAX); leaves the builder
|
|
6109
|
+
* in the cold block and answers the continuation label. */
|
|
6110
|
+
emitPublishedTest(obj) {
|
|
6111
|
+
const B = this.B;
|
|
6112
|
+
const rc = B.tmp(), frozen = B.tmp(), expected = B.tmp();
|
|
6113
|
+
B.line(`${rc} = load ${this.sizeType}, ptr ${obj}`);
|
|
6114
|
+
B.line(`${frozen} = icmp eq ${this.sizeType} ${rc}, -1`);
|
|
6115
|
+
this.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
6116
|
+
B.line(`${expected} = call i1 @llvm.expect.i1(i1 ${frozen}, i1 false)`);
|
|
6117
|
+
const cold = B.newLabel("pub.frozen"), ok = B.newLabel("pub.ok");
|
|
6118
|
+
B.condBr(expected, cold, ok);
|
|
6119
|
+
B.startBlock(cold);
|
|
6120
|
+
return ok;
|
|
6121
|
+
}
|
|
6122
|
+
publishedNamesSym = null;
|
|
6123
|
+
/** The class-name table the hierarchy field guards index by preorder. */
|
|
6124
|
+
publishedClassNamesDefs() {
|
|
6125
|
+
if (this.publishedNamesSym === null)
|
|
6126
|
+
return [];
|
|
6127
|
+
const metas = [...this.classMeta.values()];
|
|
6128
|
+
const size = metas.reduce((max, m) => Math.max(max, m.pre + 1), 0);
|
|
6129
|
+
const names = new Array(size).fill("ptr null");
|
|
6130
|
+
for (const m of metas)
|
|
6131
|
+
names[m.pre] = `ptr ${this.cstr(m.def.jsName ?? m.def.name)}`;
|
|
6132
|
+
return [
|
|
6133
|
+
`${this.publishedNamesSym} = internal constant [${size} x ptr] [ ${names.join(", ")} ]`,
|
|
6134
|
+
``,
|
|
6135
|
+
];
|
|
6136
|
+
}
|
|
6137
|
+
/** The frozen-object guard of a class field or record field store:
|
|
6138
|
+
* Node's "Cannot assign to read only property" TypeError when the target
|
|
6139
|
+
* is published. `className` null means a record (`#<Object>`). */
|
|
6140
|
+
emitPublishedFieldGuard(obj, className, field) {
|
|
6141
|
+
const published = this.publishedTypes;
|
|
6142
|
+
if (published === null)
|
|
6143
|
+
return;
|
|
6144
|
+
if (className !== null && !published.classGuarded(className))
|
|
6145
|
+
return;
|
|
6146
|
+
const B = this.B;
|
|
6147
|
+
const ok = this.emitPublishedTest(obj);
|
|
6148
|
+
let owner;
|
|
6149
|
+
const meta = className === null ? undefined : this.classMeta.get(className);
|
|
6150
|
+
if (meta === undefined)
|
|
6151
|
+
owner = this.cstr("Object");
|
|
6152
|
+
else if (!meta.hierarchy)
|
|
6153
|
+
owner = this.cstr(meta.def.jsName ?? meta.def.name);
|
|
6154
|
+
else {
|
|
6155
|
+
// The DYNAMIC class names the object, as in Node.
|
|
6156
|
+
this.publishedNamesSym ??= "@sc_pub_class_names";
|
|
6157
|
+
const vtp = B.tmp(), vt = B.tmp(), pre = B.tmp(), slot = B.tmp(), name = B.tmp();
|
|
6158
|
+
B.line(`${vtp} = getelementptr inbounds ptr, ptr ${obj}, i64 1`);
|
|
6159
|
+
B.line(`${vt} = load ptr, ptr ${vtp}`);
|
|
6160
|
+
B.line(`${pre} = load ${this.sizeType}, ptr ${vt}`);
|
|
6161
|
+
B.line(`${slot} = getelementptr inbounds ptr, ptr ${this.publishedNamesSym}, ${this.sizeType} ${pre}`);
|
|
6162
|
+
B.line(`${name} = load ptr, ptr ${slot}`);
|
|
6163
|
+
owner = name;
|
|
6164
|
+
}
|
|
6165
|
+
this.declare(`declare void @scr_throw_published_field(ptr, ptr)`);
|
|
6166
|
+
B.line(`call void @scr_throw_published_field(ptr ${this.cstr(field)}, ptr ${owner})`);
|
|
6167
|
+
this.emitUnwind();
|
|
6168
|
+
B.startBlock(ok);
|
|
6169
|
+
}
|
|
6170
|
+
/** The frozen-array guard (scr_throw_published_array's ops): `index` and
|
|
6171
|
+
* `count` are doubles (the written index, the argument count). */
|
|
6172
|
+
emitPublishedArrayGuard(arr, arrType, op, index = f64Lit(0), count = f64Lit(0)) {
|
|
6173
|
+
if (this.publishedTypes?.containerGuarded(arrType) !== true)
|
|
6174
|
+
return;
|
|
6175
|
+
const B = this.B;
|
|
6176
|
+
const ok = this.emitPublishedTest(arr);
|
|
6177
|
+
this.declare(`declare zeroext i1 @scr_throw_published_array(i32, ptr, double, double)`);
|
|
6178
|
+
const threw = B.tmp();
|
|
6179
|
+
B.line(`${threw} = call zeroext i1 @scr_throw_published_array(i32 ${op}, ptr ${arr}, double ${index}, double ${count})`);
|
|
6180
|
+
const unwind = B.newLabel("pub.throw");
|
|
6181
|
+
B.condBr(threw, unwind, ok);
|
|
6182
|
+
B.startBlock(unwind);
|
|
6183
|
+
this.emitUnwind();
|
|
6184
|
+
B.startBlock(ok);
|
|
6185
|
+
}
|
|
6186
|
+
/** The published Map/Set guard: "Cannot modify a published Map/Set". */
|
|
6187
|
+
emitPublishedCollectionGuard(m, t) {
|
|
6188
|
+
if (this.publishedTypes?.containerGuarded(t) !== true)
|
|
6189
|
+
return;
|
|
6190
|
+
const B = this.B;
|
|
6191
|
+
const ok = this.emitPublishedTest(m);
|
|
6192
|
+
this.declare(`declare void @scr_throw_published_collection(i1 zeroext)`);
|
|
6193
|
+
B.line(`call void @scr_throw_published_collection(i1 ${t.kind === "set" ? "true" : "false"})`);
|
|
6194
|
+
this.emitUnwind();
|
|
6195
|
+
B.startBlock(ok);
|
|
6196
|
+
}
|
|
5069
6197
|
emitSerializationExpr(e) {
|
|
5070
6198
|
return emitSerializationExpr(this, e);
|
|
5071
6199
|
}
|
|
@@ -5085,6 +6213,13 @@ export class LlEmitter {
|
|
|
5085
6213
|
return this.emitExpr(e);
|
|
5086
6214
|
if (e.kind === "strLit")
|
|
5087
6215
|
return { name: this.internLiteral(e.value), type: e.type };
|
|
6216
|
+
const guarded = guardedValue(e);
|
|
6217
|
+
if (guarded !== null) {
|
|
6218
|
+
// Throwing guards run in place, exactly as the sequence emits them.
|
|
6219
|
+
for (const s of e.stmts)
|
|
6220
|
+
this.emitStmt(s);
|
|
6221
|
+
return this.emitReadReceiver(guarded);
|
|
6222
|
+
}
|
|
5088
6223
|
if (e.kind === "varRef") {
|
|
5089
6224
|
this.materializeSplitLocal(e.localId);
|
|
5090
6225
|
const binding = this.binding(e.localId);
|
|
@@ -5106,7 +6241,32 @@ export class LlEmitter {
|
|
|
5106
6241
|
this.B.line(`${value} = load ptr, ptr ${binding.slot}`);
|
|
5107
6242
|
return { name: value, type: e.type };
|
|
5108
6243
|
}
|
|
5109
|
-
|
|
6244
|
+
if (e.kind === "chainRecv") {
|
|
6245
|
+
// The chain's bind slot holds its receiver unchanged until the body
|
|
6246
|
+
// finishes, owned by the slot itself or by the chain's receiver.
|
|
6247
|
+
const bound = this.chainSlots.get(e.id);
|
|
6248
|
+
if (!bound)
|
|
6249
|
+
throw new InternalCompilerError(`llvm emitter bug: chainRecv "${e.id}" outside its chain`);
|
|
6250
|
+
const value = this.B.tmp();
|
|
6251
|
+
this.B.line(`${value} = load ${this.llType(bound.type)}, ptr ${bound.name}`);
|
|
6252
|
+
return { name: value, type: e.type };
|
|
6253
|
+
}
|
|
6254
|
+
if (e.kind === "arrayGet" && this.canBorrowReceiver(e)) {
|
|
6255
|
+
// The array keeps the element alive until the consumer; a missing
|
|
6256
|
+
// element still traps exactly like the owned read.
|
|
6257
|
+
const slot = this.B.slot();
|
|
6258
|
+
this.B.entryAllocas.push(`${slot} = alloca ptr`);
|
|
6259
|
+
emitBorrowedArrayRead(this, e, slot);
|
|
6260
|
+
const value = this.B.tmp();
|
|
6261
|
+
this.B.line(`${value} = load ptr, ptr ${slot}`);
|
|
6262
|
+
return { name: value, type: e.type };
|
|
6263
|
+
}
|
|
6264
|
+
const wrap = this.borrowableNullableWrap(e);
|
|
6265
|
+
if (wrap === "unit" && e.kind === "unionWrap")
|
|
6266
|
+
return { name: this.unitInstanceRef(e.unionId, e.tag), type: e.type };
|
|
6267
|
+
if (wrap === "ref" && e.kind === "unionWrap")
|
|
6268
|
+
return { name: this.emitReadReceiver(e.value).name, type: e.type };
|
|
6269
|
+
const read = matchMapRead(e, this.boxedUnionsById);
|
|
5110
6270
|
if (read) {
|
|
5111
6271
|
const result = emitStackMapRead(this, read);
|
|
5112
6272
|
if (result.owner)
|
|
@@ -5116,11 +6276,19 @@ export class LlEmitter {
|
|
|
5116
6276
|
const arrayRead = this.optionalArrayReads.get(e);
|
|
5117
6277
|
if (arrayRead)
|
|
5118
6278
|
return emitProjectedArrayRead(this, arrayRead);
|
|
5119
|
-
|
|
6279
|
+
const narrow = this.checkedNarrows.get(e);
|
|
6280
|
+
const narrowed = narrow ? emitCheckedNarrowReceiver(this, narrow) : null;
|
|
6281
|
+
if (narrowed)
|
|
6282
|
+
return narrowed;
|
|
6283
|
+
if (canStackUnion(e, this.boxedUnionsById))
|
|
5120
6284
|
return emitStackUnion(this, e).value;
|
|
5121
6285
|
if (e.kind === "unionNarrow") {
|
|
5122
6286
|
const union = this.emitUnionProjection(e.value);
|
|
5123
|
-
return { name: this.unionPeek(union.name), type: e.type };
|
|
6287
|
+
return { name: this.unionPeek(union.name, e.unionId), type: e.type };
|
|
6288
|
+
}
|
|
6289
|
+
if ((e.kind === "downcast" || e.kind === "upcast") && e.value.type.kind === "object") {
|
|
6290
|
+
// Prefix layouts: a class cast reads the operand's own pointer.
|
|
6291
|
+
return { name: this.emitReadReceiver(e.value).name, type: e.type };
|
|
5124
6292
|
}
|
|
5125
6293
|
// A nullable-pointer field has no box to borrow. Projections use
|
|
5126
6294
|
// emitUnionProjection; other borrowers get an owned heap box.
|
|
@@ -5133,6 +6301,8 @@ export class LlEmitter {
|
|
|
5133
6301
|
return { name: this.loadRecordField(ptr, type), type: e.type };
|
|
5134
6302
|
}
|
|
5135
6303
|
const { ptr, type } = this.classFieldPtr(receiver.name, e.className, e.field);
|
|
6304
|
+
if (this.int32Slots.isField(e.className, e.field))
|
|
6305
|
+
return this.loadInt32Field(ptr, e.type);
|
|
5136
6306
|
return { name: this.loadField(ptr, type), type: e.type };
|
|
5137
6307
|
}
|
|
5138
6308
|
if (e.kind === "ternary" && this.canBorrowReceiver(e.then) && this.canBorrowReceiver(e.else_)) {
|
|
@@ -5160,6 +6330,8 @@ export class LlEmitter {
|
|
|
5160
6330
|
B.line(`${value} = load ptr, ptr ${slot}`);
|
|
5161
6331
|
return { name: value, type: e.type };
|
|
5162
6332
|
}
|
|
6333
|
+
if (e.kind === "call" && this.canBorrowReceiver(e))
|
|
6334
|
+
return emitBorrowedResultCall(this, e);
|
|
5163
6335
|
return this.emitExpr(e);
|
|
5164
6336
|
}
|
|
5165
6337
|
/** A private stack union source with an independently owned payload. */
|
|
@@ -5171,8 +6343,8 @@ export class LlEmitter {
|
|
|
5171
6343
|
isStackUnionSource(e) {
|
|
5172
6344
|
if (e.type.kind !== "union")
|
|
5173
6345
|
return false;
|
|
5174
|
-
return (canStackUnion(e, this.
|
|
5175
|
-
matchMapRead(e, this.
|
|
6346
|
+
return (canStackUnion(e, this.boxedUnionsById) ||
|
|
6347
|
+
matchMapRead(e, this.boxedUnionsById) !== null ||
|
|
5176
6348
|
this.optionalArrayReads.get(e) !== null);
|
|
5177
6349
|
}
|
|
5178
6350
|
/** The nullable-pointer representation behind a union-typed class field
|
|
@@ -5244,7 +6416,10 @@ export class LlEmitter {
|
|
|
5244
6416
|
B.br(join);
|
|
5245
6417
|
B.startBlock(ref);
|
|
5246
6418
|
const owned = this.retainValue(p, nullable.arm);
|
|
5247
|
-
const box = this.unionNewOwned(nullable.
|
|
6419
|
+
const box = this.unionNewOwned(nullable.unionId, nullable.refTag, {
|
|
6420
|
+
name: owned,
|
|
6421
|
+
type: nullable.arm,
|
|
6422
|
+
});
|
|
5248
6423
|
B.line(`store ptr ${box}, ptr ${slot}`);
|
|
5249
6424
|
B.br(join);
|
|
5250
6425
|
B.startBlock(join);
|
|
@@ -5256,10 +6431,10 @@ export class LlEmitter {
|
|
|
5256
6431
|
* box, which stays owned by its existing owner. */
|
|
5257
6432
|
unionToNullable(u, nullable) {
|
|
5258
6433
|
const B = this.B;
|
|
5259
|
-
const tag = this.unionTag(u);
|
|
6434
|
+
const tag = this.unionTag(u, nullable.unionId);
|
|
5260
6435
|
const isRef = B.tmp(), sel = B.tmp();
|
|
5261
6436
|
B.line(`${isRef} = icmp eq i32 ${tag}, ${nullable.refTag}`);
|
|
5262
|
-
const payload = this.unionPeek(u);
|
|
6437
|
+
const payload = this.unionPeek(u, nullable.unionId);
|
|
5263
6438
|
B.line(`${sel} = select i1 ${isRef}, ptr ${payload}, ptr null`);
|
|
5264
6439
|
return this.retainValue(sel, nullable.arm);
|
|
5265
6440
|
}
|
|
@@ -5311,6 +6486,8 @@ export class LlEmitter {
|
|
|
5311
6486
|
}
|
|
5312
6487
|
canBorrowReceiver(e) {
|
|
5313
6488
|
switch (e.kind) {
|
|
6489
|
+
case "chainRecv":
|
|
6490
|
+
return this.chainSlots.has(e.id);
|
|
5314
6491
|
case "varRef": {
|
|
5315
6492
|
const binding = this.binding(e.localId);
|
|
5316
6493
|
return (binding.kind !== "boxed" ||
|
|
@@ -5320,20 +6497,166 @@ export class LlEmitter {
|
|
|
5320
6497
|
isRefCounted(e.type)));
|
|
5321
6498
|
}
|
|
5322
6499
|
case "unionNarrow":
|
|
6500
|
+
// Class casts reinterpret the same pointer (prefix layouts).
|
|
6501
|
+
case "downcast":
|
|
6502
|
+
case "upcast":
|
|
5323
6503
|
return this.canBorrowReceiver(e.value);
|
|
5324
6504
|
case "fieldGet":
|
|
5325
6505
|
case "recordGet":
|
|
5326
6506
|
return this.canBorrowReceiver(e.obj);
|
|
5327
6507
|
case "ternary":
|
|
5328
6508
|
return this.canBorrowReceiver(e.then) && this.canBorrowReceiver(e.else_);
|
|
6509
|
+
case "seqExpr": {
|
|
6510
|
+
const guarded = guardedValue(e);
|
|
6511
|
+
return guarded !== null && this.canBorrowReceiver(guarded);
|
|
6512
|
+
}
|
|
5329
6513
|
// The runtime always sets a pending exception; its unreachable
|
|
5330
6514
|
// typed dummy is null and owns no receiver.
|
|
5331
6515
|
case "libCall":
|
|
5332
6516
|
return e.fn === "error.nodeThrow";
|
|
6517
|
+
case "unionWrap": {
|
|
6518
|
+
const wrap = this.borrowableNullableWrap(e);
|
|
6519
|
+
return wrap === "unit" || (wrap === "ref" && this.canBorrowReceiver(e.value));
|
|
6520
|
+
}
|
|
6521
|
+
// A borrowed-return callee preserves edges, so its result stays
|
|
6522
|
+
// reachable from borrowable arguments until the consumer runs.
|
|
6523
|
+
case "call":
|
|
6524
|
+
return (this.borrowedReturns.has(e.callee) &&
|
|
6525
|
+
e.args.every((arg) => (!isRefCounted(arg.type) || this.canBorrowReceiver(arg)) &&
|
|
6526
|
+
this.referenceEffects.preserves(arg)));
|
|
6527
|
+
// A required element read: the (borrowed) array owns the element
|
|
6528
|
+
// while an edge-preserving index computes.
|
|
6529
|
+
case "arrayGet":
|
|
6530
|
+
return (e.arr.type.kind === "array" &&
|
|
6531
|
+
isRefCounted(e.arr.type.elem) &&
|
|
6532
|
+
this.canBorrowReceiver(e.arr) &&
|
|
6533
|
+
this.referenceEffects.preserves(e.index));
|
|
5333
6534
|
default:
|
|
5334
6535
|
return false;
|
|
5335
6536
|
}
|
|
5336
6537
|
}
|
|
6538
|
+
/** A reference held as one plain pointer that emitReadReceiver can read
|
|
6539
|
+
* without owning: class instances, records, arrays, strings and
|
|
6540
|
+
* nullable-pointer unions. Tagged union boxes, closures and dynamic
|
|
6541
|
+
* values keep ordinary ownership. */
|
|
6542
|
+
plainReference(type) {
|
|
6543
|
+
switch (type.kind) {
|
|
6544
|
+
case "object":
|
|
6545
|
+
case "record":
|
|
6546
|
+
case "array":
|
|
6547
|
+
case "string":
|
|
6548
|
+
return true;
|
|
6549
|
+
case "union":
|
|
6550
|
+
return this.nullableUnions.has(type.unionId);
|
|
6551
|
+
default:
|
|
6552
|
+
return false;
|
|
6553
|
+
}
|
|
6554
|
+
}
|
|
6555
|
+
/** Functions returning a walk of their borrowed parameters return it
|
|
6556
|
+
* without a reference (+0) from their borrowing body; direct callers
|
|
6557
|
+
* either consume it as a receiver/walk or retain it at once, and the owned
|
|
6558
|
+
* adapter retains before releasing its parameters. Candidates preserve
|
|
6559
|
+
* every heap edge (so the result stays reachable from the arguments),
|
|
6560
|
+
* borrow every reference parameter and return plain pointers. The set
|
|
6561
|
+
* grows to a fixpoint because a call to a borrowed-return function with
|
|
6562
|
+
* walk arguments is itself a walk; walk facts are then recomputed with
|
|
6563
|
+
* the final set, admitting only parameters whose convention borrows. */
|
|
6564
|
+
analyzeBorrowedReturns() {
|
|
6565
|
+
const pointer = (type) => this.plainReference(type);
|
|
6566
|
+
const borrowedIds = (fn) => {
|
|
6567
|
+
const indexes = this.callLifetimes.borrowed.get(fn.name);
|
|
6568
|
+
return new Set(fn.params.filter((_, i) => indexes?.has(i)).map((p) => p.localId));
|
|
6569
|
+
};
|
|
6570
|
+
const hostFor = (fn) => {
|
|
6571
|
+
const allowed = borrowedIds(fn);
|
|
6572
|
+
return {
|
|
6573
|
+
pointerLocal: (local) => pointer(local.type),
|
|
6574
|
+
borrowsWithoutOwning: (e) => this.borrowsWithoutOwning(e),
|
|
6575
|
+
borrowedReturn: (callee) => this.borrowedReturns.has(callee),
|
|
6576
|
+
parameterAllowed: (id) => allowed.has(id),
|
|
6577
|
+
};
|
|
6578
|
+
};
|
|
6579
|
+
const preserving = [...this.fnByName.values()].filter((fn) => this.referenceEffects.functions.has(fn.name));
|
|
6580
|
+
const candidates = preserving.filter((fn) => {
|
|
6581
|
+
if (!pointer(fn.returnType) || fn.async || fn.generator || fn.captures)
|
|
6582
|
+
return false;
|
|
6583
|
+
// A borrowing vtable slot dispatches to the borrowing body (or its
|
|
6584
|
+
// virtual adapter) directly, and virtual callers own the result.
|
|
6585
|
+
if (this.implSlotBorrowed(fn.name).size > 0)
|
|
6586
|
+
return false;
|
|
6587
|
+
const indexes = this.callLifetimes.borrowed.get(fn.name);
|
|
6588
|
+
return (indexes !== undefined &&
|
|
6589
|
+
indexes.size > 0 &&
|
|
6590
|
+
fn.params.every((p, i) => !isRefCounted(p.type) || indexes.has(i)));
|
|
6591
|
+
});
|
|
6592
|
+
for (let changed = true; changed;) {
|
|
6593
|
+
changed = false;
|
|
6594
|
+
for (const fn of candidates) {
|
|
6595
|
+
if (this.borrowedReturns.has(fn.name))
|
|
6596
|
+
continue;
|
|
6597
|
+
if (!analyzeWalks(fn, hostFor(fn)).returnsWalk)
|
|
6598
|
+
continue;
|
|
6599
|
+
this.borrowedReturns.add(fn.name);
|
|
6600
|
+
changed = true;
|
|
6601
|
+
}
|
|
6602
|
+
}
|
|
6603
|
+
if (this.borrowedReturns.size === 0)
|
|
6604
|
+
return;
|
|
6605
|
+
for (const fn of preserving) {
|
|
6606
|
+
const walks = analyzeWalks(fn, hostFor(fn)).locals;
|
|
6607
|
+
if (walks.size > 0)
|
|
6608
|
+
this.walkBorrowsByFunction.set(fn.name, walks);
|
|
6609
|
+
}
|
|
6610
|
+
}
|
|
6611
|
+
/** emitReadReceiver produces this node's pointer without acquiring a
|
|
6612
|
+
* reference, given operands (and ternary arms) that do the same: plain
|
|
6613
|
+
* class/record field reads, class casts, nullable-pointer narrows and
|
|
6614
|
+
* wraps, and checked ternaries. Stack-boxed, map-read and boxed-field
|
|
6615
|
+
* sources are owned. Depends only on module facts, not the current
|
|
6616
|
+
* function. */
|
|
6617
|
+
borrowsWithoutOwning(e) {
|
|
6618
|
+
if (!isRefCounted(e.type) || this.isStackUnionSource(e))
|
|
6619
|
+
return false;
|
|
6620
|
+
if (matchMapRead(e, this.boxedUnionsById))
|
|
6621
|
+
return false;
|
|
6622
|
+
switch (e.kind) {
|
|
6623
|
+
case "fieldGet":
|
|
6624
|
+
return this.nullableFieldGet(e) === null;
|
|
6625
|
+
case "recordGet":
|
|
6626
|
+
return true;
|
|
6627
|
+
case "unionNarrow":
|
|
6628
|
+
return this.nullableUnions.has(e.unionId);
|
|
6629
|
+
case "downcast":
|
|
6630
|
+
case "upcast":
|
|
6631
|
+
return e.value.type.kind === "object";
|
|
6632
|
+
case "unionWrap":
|
|
6633
|
+
return this.borrowableNullableWrap(e) !== null;
|
|
6634
|
+
// The caller proves each arm separately.
|
|
6635
|
+
case "ternary":
|
|
6636
|
+
return true;
|
|
6637
|
+
// The caller proves the arguments are walks; their evaluation must not
|
|
6638
|
+
// remove an edge before the call reads them.
|
|
6639
|
+
case "call":
|
|
6640
|
+
return (this.borrowedReturns.has(e.callee) &&
|
|
6641
|
+
e.args.every((arg) => this.referenceEffects.preserves(arg)));
|
|
6642
|
+
default:
|
|
6643
|
+
return false;
|
|
6644
|
+
}
|
|
6645
|
+
}
|
|
6646
|
+
/** A wrap into a nullable-pointer union is the payload pointer itself
|
|
6647
|
+
* (the reference arm) or an immortal constant (a unit arm), so it can
|
|
6648
|
+
* borrow exactly when its payload can. Void payloads run for effects and
|
|
6649
|
+
* keep the ordinary path; tagged unions still construct a box. */
|
|
6650
|
+
borrowableNullableWrap(e) {
|
|
6651
|
+
if (e.kind !== "unionWrap")
|
|
6652
|
+
return null;
|
|
6653
|
+
const nullable = this.nullableUnions.get(e.unionId);
|
|
6654
|
+
if (!nullable)
|
|
6655
|
+
return null;
|
|
6656
|
+
if (isUnitType(e.value.type))
|
|
6657
|
+
return e.value.kind === "unitLit" ? "unit" : null;
|
|
6658
|
+
return e.tag === nullable.refTag && isRefCounted(e.value.type) ? "ref" : null;
|
|
6659
|
+
}
|
|
5337
6660
|
materializeSplitLocal(localId) {
|
|
5338
6661
|
const span = this.splitSpans.get(localId);
|
|
5339
6662
|
if (span)
|
|
@@ -5420,8 +6743,8 @@ export class LlEmitter {
|
|
|
5420
6743
|
resolveThunkFor(inner) {
|
|
5421
6744
|
return resolveThunkFor(this, inner);
|
|
5422
6745
|
}
|
|
5423
|
-
tagInSet(uName, tags) {
|
|
5424
|
-
return tagInSet(this, uName, tags);
|
|
6746
|
+
tagInSet(uName, unionId, tags) {
|
|
6747
|
+
return tagInSet(this, uName, unionId, tags);
|
|
5425
6748
|
}
|
|
5426
6749
|
arrPush(arr, acc, value) {
|
|
5427
6750
|
return arrPush(this, arr, acc, value);
|
|
@@ -5465,8 +6788,8 @@ export class LlEmitter {
|
|
|
5465
6788
|
emitBytesLength(elem, receiver, bytes) {
|
|
5466
6789
|
return emitBytesLength(this, elem, receiver, bytes);
|
|
5467
6790
|
}
|
|
5468
|
-
emitBytesGet(elem, receiver, index, expr, inBounds = false) {
|
|
5469
|
-
return emitBytesGet(this, elem, receiver, index, expr, inBounds);
|
|
6791
|
+
emitBytesGet(elem, receiver, index, expr, inBounds = false, invalidNaN = false) {
|
|
6792
|
+
return emitBytesGet(this, elem, receiver, index, expr, inBounds, invalidNaN);
|
|
5470
6793
|
}
|
|
5471
6794
|
emitToUint32(value, expr, uint32) {
|
|
5472
6795
|
return emitToUint32(this, value, expr, uint32);
|