@scriptc/compiler 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ambient/scriptc-node-fallback.d.ts +2 -0
- package/dist/backend/llvm/alloc.d.ts +3 -0
- package/dist/backend/llvm/alloc.js +54 -13
- package/dist/backend/llvm/alloc.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/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 +26 -18
- 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/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.test.js +89 -0
- package/dist/backend/llvm/dense-array-access.test.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +4 -0
- package/dist/backend/llvm/dyn.js +31 -20
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +184 -11
- package/dist/backend/llvm/emitter.js +1189 -125
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.test.js +21 -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 +85 -8
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-callbacks.js +29 -21
- package/dist/backend/llvm/expr-callbacks.js.map +1 -1
- package/dist/backend/llvm/expr-calls.d.ts +5 -0
- package/dist/backend/llvm/expr-calls.js +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 +230 -27
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-control.js +25 -10
- package/dist/backend/llvm/expr-control.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +72 -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.js +41 -31
- 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 +7 -11
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-process.js +8 -8
- package/dist/backend/llvm/lib-process.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +1 -0
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.js +73 -13
- 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 +106 -16
- package/dist/backend/llvm/reference-effects.js.map +1 -1
- package/dist/backend/llvm/reference-effects.test.js +52 -1
- package/dist/backend/llvm/reference-effects.test.js.map +1 -1
- package/dist/backend/llvm/shapes.d.ts +45 -17
- package/dist/backend/llvm/shapes.js +131 -53
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/shapes.test.js +33 -8
- 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 +76 -0
- package/dist/backend/llvm/string-reads.js.map +1 -0
- 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/union-repr.d.ts +15 -0
- package/dist/backend/llvm/union-repr.js +89 -0
- package/dist/backend/llvm/union-repr.js.map +1 -0
- package/dist/backend/llvm/virtual-borrows.d.ts +31 -0
- package/dist/backend/llvm/virtual-borrows.js +82 -0
- package/dist/backend/llvm/virtual-borrows.js.map +1 -0
- package/dist/backend/llvm/walk-borrows.d.ts +43 -0
- package/dist/backend/llvm/walk-borrows.js +150 -0
- package/dist/backend/llvm/walk-borrows.js.map +1 -0
- package/dist/backend/llvm/walk-borrows.test.d.ts +1 -0
- package/dist/backend/llvm/walk-borrows.test.js +119 -0
- package/dist/backend/llvm/walk-borrows.test.js.map +1 -0
- package/dist/backend/llvm/walkers.d.ts +5 -1
- package/dist/backend/llvm/walkers.js +24 -21
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/backend/may-throw.d.ts +29 -1
- package/dist/backend/may-throw.js +346 -3
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/may-throw.test.js +113 -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 +4 -0
- package/dist/backend/native-codegen.js +49 -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-pack-targets.test.js +1 -1
- package/dist/backend/runtime-pack.test.js +1 -1
- package/dist/cli/command.js +70 -7
- package/dist/cli/command.js.map +1 -1
- package/dist/cli/usage.d.ts +4 -1
- package/dist/cli/usage.js +8 -1
- package/dist/cli/usage.js.map +1 -1
- package/dist/compile-types.d.ts +12 -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/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 +120 -4
- 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/workers.js +4 -2
- package/dist/frontend/lowering/builtins/workers.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/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/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/lower-calls.d.ts +5 -0
- package/dist/frontend/lowering/lower-calls.js +226 -157
- 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 +13 -0
- package/dist/frontend/lowering/lower-exprs.js +431 -107
- 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 +24 -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 +36 -6
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +78 -2
- package/dist/frontend/lowering/lowerer.js +986 -73
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/runtime-optional-bounds.d.ts +19 -0
- package/dist/frontend/lowering/runtime-optional-bounds.js +354 -0
- package/dist/frontend/lowering/runtime-optional-bounds.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-bounds.test.d.ts +1 -0
- package/dist/frontend/lowering/runtime-optional-bounds.test.js +66 -0
- package/dist/frontend/lowering/runtime-optional-bounds.test.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-elements.d.ts +117 -0
- package/dist/frontend/lowering/runtime-optional-elements.js +738 -0
- package/dist/frontend/lowering/runtime-optional-elements.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-guards.d.ts +36 -0
- package/dist/frontend/lowering/runtime-optional-guards.js +375 -0
- package/dist/frontend/lowering/runtime-optional-guards.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-guards.test.d.ts +1 -0
- package/dist/frontend/lowering/runtime-optional-guards.test.js +92 -0
- package/dist/frontend/lowering/runtime-optional-guards.test.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-ownership.d.ts +63 -0
- package/dist/frontend/lowering/runtime-optional-ownership.js +382 -0
- package/dist/frontend/lowering/runtime-optional-ownership.js.map +1 -0
- package/dist/frontend/lowering/runtime-optional-provenance.d.ts +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/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 +6 -1
- package/dist/index.js +47 -9
- 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 +3 -0
- package/dist/ir/builtin-signatures.js.map +1 -1
- package/dist/ir/int32-slots.d.ts +72 -0
- package/dist/ir/int32-slots.js +837 -0
- package/dist/ir/int32-slots.js.map +1 -0
- package/dist/ir/int32-slots.test.d.ts +1 -0
- package/dist/ir/int32-slots.test.js +222 -0
- package/dist/ir/int32-slots.test.js.map +1 -0
- package/dist/ir/integer-ranges.d.ts +19 -3
- package/dist/ir/integer-ranges.js +168 -23
- package/dist/ir/integer-ranges.js.map +1 -1
- package/dist/ir/integer-ranges.test.js +74 -2
- package/dist/ir/integer-ranges.test.js.map +1 -1
- package/dist/ir/ir.d.ts +24 -6
- package/dist/ir/ir.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 +3 -0
- package/dist/native/cache.js +49 -1
- package/dist/native/cache.js.map +1 -1
- package/dist/native/cache.test.js +21 -1
- package/dist/native/cache.test.js.map +1 -1
- package/dist/native/cli.js +1 -1
- package/dist/native/cli.js.map +1 -1
- package/dist/native/compiler.d.ts +5 -0
- package/dist/native/compiler.js +51 -4
- package/dist/native/compiler.js.map +1 -1
- package/dist/native/prepare.d.ts +4 -0
- package/dist/native/prepare.js +2 -1
- package/dist/native/prepare.js.map +1 -1
- package/dist/startup-cache.d.ts +2 -0
- package/dist/startup-cache.js +2 -0
- package/dist/startup-cache.js.map +1 -1
- package/package.json +20 -20
- package/surface-manifest.json +1 -1
|
@@ -19,6 +19,7 @@ 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";
|
|
@@ -32,8 +33,9 @@ import { emitByteWindowGuard, findInitializedByteLoopBindings, matchByteWindow,
|
|
|
32
33
|
import { canDeferSplitPiece, emitSplitSpanNext, materializeSplitPiece, emitSplitCursor, emitSplitNext, emitSplitScratch, emitSplitSnapshot, findPrivateSplitLocals, loadSplitSnapshot, storeSplitSnapshot, stringSplit, } from "./split-loops.js";
|
|
33
34
|
import { scalarizeNumericRecords } from "../../ir/scalar-records.js";
|
|
34
35
|
import { specializeNumericCalls } from "../../ir/numeric-call-specialization.js";
|
|
35
|
-
import { everyStmtList } from "../../ir/traverse.js";
|
|
36
|
-
import { analyzeIntegerRanges } from "../../ir/integer-ranges.js";
|
|
36
|
+
import { everyExprChild, everyStmtChild, everyStmtList } from "../../ir/traverse.js";
|
|
37
|
+
import { analyzeIntegerRanges, INT32_RANGE } from "../../ir/integer-ranges.js";
|
|
38
|
+
import { analyzeInt32Slots } from "../../ir/int32-slots.js";
|
|
37
39
|
import { findIntegerViews } from "./integer-views.js";
|
|
38
40
|
import { findInitializerBindings, withInitializerBindings } from "../../ir/initializer-bindings.js";
|
|
39
41
|
import { findConstantNumericTables } from "../../ir/constant-tables.js";
|
|
@@ -47,10 +49,13 @@ import { allocateLocalUnion, findLocalUnionStorage, storeLocalUnion, } from "./l
|
|
|
47
49
|
import { BlockBuilder } from "./blocks.js";
|
|
48
50
|
import { NullableRefFields } from "./nullable-fields.js";
|
|
49
51
|
import { emitLocalArrayRead, emitProjectedArrayRead, emitBorrowedArrayRead, findLocalArrayReads, findCallArrayReads, OptionalArrayReads, } from "./local-array-reads.js";
|
|
52
|
+
import { CheckedNarrows, emitCheckedNarrowReceiver } from "./checked-narrows.js";
|
|
50
53
|
import { emitDenseArrayGet, emitDenseArraySet } from "./dense-array-access.js";
|
|
51
54
|
import { emitStackMapRead, findMapReadLifetimes, matchMapRead, } from "./map-read-lifetimes.js";
|
|
52
|
-
import { emitBorrowedFieldSequence } from "./borrowed-receivers.js";
|
|
55
|
+
import { emitBorrowedFieldSequence, guardedValue } from "./borrowed-receivers.js";
|
|
53
56
|
import { emitBorrowedInput } from "./borrowed-inputs.js";
|
|
57
|
+
import { analyzeWalks, findWalkBorrows } from "./walk-borrows.js";
|
|
58
|
+
import { findReboundParameters } from "./rebound-parameters.js";
|
|
54
59
|
import { ReferenceEffects } from "./reference-effects.js";
|
|
55
60
|
import { LlvmDebugInfo } from "./debug-info.js";
|
|
56
61
|
import { StackCallbacks } from "./stack-callbacks.js";
|
|
@@ -63,14 +68,16 @@ import { ABSENT_FIELD_PAYLOAD, emitFieldAbsentTest, f64Lit, ffiNativeTypeLl, ffi
|
|
|
63
68
|
import { ffiExtendsNarrowIntegers } from "../targets.js";
|
|
64
69
|
import { emitLiteralExpr, emitOperatorExpr, emitStringExpr, emitContainerExpr, emitRecordExpr, } from "./expr-primitives.js";
|
|
65
70
|
import { emitControlExpr } from "./expr-control.js";
|
|
66
|
-
import { emitCallExpr } from "./expr-calls.js";
|
|
71
|
+
import { emitBorrowedResultCall, emitCallExpr } from "./expr-calls.js";
|
|
67
72
|
import { emitDynamicExpr } from "./expr-dynamic.js";
|
|
68
73
|
import { emitIntrinsicExpr, emitSerializationExpr, emitAsyncExpr } from "./expr-async.js";
|
|
74
|
+
import { PublishWalkers } from "./publish.js";
|
|
75
|
+
import { computePublishedTypes } from "../publication.js";
|
|
69
76
|
import { emitJsInteropExpr, emitExpr } from "./expr-dispatch.js";
|
|
70
77
|
import { emitJsMarshal, emitJsOp, emitJsExit, islandAdapter, islandTypedAdapter, } from "./expr-island.js";
|
|
71
78
|
import { dynKind, raceAdapterFor, genResultThunkFor, childExitThunkFor, childExitSignalThunkFor, childDataThunkFor, execFileThunkFor, ipcMessageThunkFor, ipcSendThunkFor, emitterFixedAdapter, wrapEmitterListener, unwrapNullableClosure, closeBindThunkFor, closeOverrideWrapFor, } from "./expr-callbacks.js";
|
|
72
79
|
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";
|
|
80
|
+
import { emitStableReceiver, resolveThunkFor, tagInSet, arrPush, emitArrayCopyLoop, emitStrIntrinsic, emitArrIntrinsic, emitDiscardedSplice, wrapNullable, emitMapNew, mapSet, emitMapLikeIntrinsic, emitSetNew, } from "./expr-containers.js";
|
|
74
81
|
import { emitIntegerLoopIndex, emitBytesIndex, emitBytesData, emitBytesLength, emitBytesGet, emitToUint32, emitBytesSet, emitBytesIntrinsic, } from "./expr-bytes.js";
|
|
75
82
|
import { emitRegexIntrinsic, emitRecordKeyGet, keyedRecordReadInto } from "./expr-records.js";
|
|
76
83
|
import { dynPromiseAdapter, streamTypedRefCommitAdapter, liveDynUnionRefAdapter, streamTypedRefBoxValue, streamTypedRefMaterializeAdapter, streamFromArrayAdapter, } from "./expr-stream-bridges.js";
|
|
@@ -81,10 +88,16 @@ import { emitNetworkHttpLibCall } from "./lib-network.js";
|
|
|
81
88
|
import { emitAssertInspectLibCall, emitIoLibCall, emitGenericLibCall, emitLibCall, } from "./lib-dispatch.js";
|
|
82
89
|
import { buildClassGraph, classMembershipIntervals, emitClassMembershipHelper, classEnvironmentIndex, classFieldIndex, classStructSym, } from "./classes.js";
|
|
83
90
|
import { LlDyn } from "./dyn.js";
|
|
91
|
+
import { VirtualBorrows } from "./virtual-borrows.js";
|
|
84
92
|
import { LlvmUnsupportedError } from "./unsupported.js";
|
|
85
93
|
import { LlWalkers } from "./walkers.js";
|
|
86
|
-
import {
|
|
87
|
-
import {
|
|
94
|
+
import { isObjectArm, NULLABLE_ABSENT, NULLABLE_NULL, NullableUnions } from "./nullable-unions.js";
|
|
95
|
+
import { emitNullablePresent, emitUnionPeek, emitUnionTag } from "./union-repr.js";
|
|
96
|
+
import { literalCapWord, literalCapWord32 } from "./string-key-hash.js";
|
|
97
|
+
import { NUMBER_MAP_ENTRY_DECL } from "./map-number-lookup.js";
|
|
98
|
+
import { STRING_READ_HELPERS } from "./string-reads.js";
|
|
99
|
+
import { emitConcatInputs, emitMixedConcat, numberPart, stringParts, } from "./string-construction.js";
|
|
100
|
+
import { arrNewCall, boxAccess, boxNewCall, boxReleaseSym, computeTraced, elemAccess, emitInlineRcHelpers, emitNullableRetainWrappers, FN_ATTRS, inlineRcDecls, llFieldType, releaseSym, retainSym, traceArg, typedRefConstructor, vAdapters, } from "./shapes.js";
|
|
88
101
|
export { LlvmUnsupportedError } from "./unsupported.js";
|
|
89
102
|
function ffiCallbackDummyLl(callback) {
|
|
90
103
|
switch (callback.returns) {
|
|
@@ -130,6 +143,41 @@ export function emitLlvmModuleSource(mod, options = {}) {
|
|
|
130
143
|
function llStrBytes(text) {
|
|
131
144
|
return llBytes(Buffer.from(text, "utf8"));
|
|
132
145
|
}
|
|
146
|
+
const NO_BORROWED = new Set();
|
|
147
|
+
/** Whether any node of `e` names the local (reads, writes, declarations,
|
|
148
|
+
* captures). */
|
|
149
|
+
function mentionsLocal(e, localId) {
|
|
150
|
+
const exprNames = (node) => {
|
|
151
|
+
if (node.kind === "varRef" || node.kind === "incDec" || node.kind === "assignExpr")
|
|
152
|
+
return node.localId === localId;
|
|
153
|
+
if (node.kind === "closure")
|
|
154
|
+
return node.captures.includes(localId);
|
|
155
|
+
if (node.kind === "classRef")
|
|
156
|
+
return node.captures?.includes(localId) === true;
|
|
157
|
+
return false;
|
|
158
|
+
};
|
|
159
|
+
const stmtNames = (node) => {
|
|
160
|
+
if (node.kind === "varDecl" ||
|
|
161
|
+
node.kind === "assign" ||
|
|
162
|
+
node.kind === "forOf" ||
|
|
163
|
+
node.kind === "rethrow")
|
|
164
|
+
return node.localId === localId;
|
|
165
|
+
return false;
|
|
166
|
+
};
|
|
167
|
+
let found = false;
|
|
168
|
+
const expr = (node) => {
|
|
169
|
+
if (found || exprNames(node))
|
|
170
|
+
return !(found = true);
|
|
171
|
+
return everyExprChild(node, expr, stmt);
|
|
172
|
+
};
|
|
173
|
+
const stmt = (node) => {
|
|
174
|
+
if (found || stmtNames(node))
|
|
175
|
+
return !(found = true);
|
|
176
|
+
return everyStmtChild(node, expr, stmt);
|
|
177
|
+
};
|
|
178
|
+
expr(e);
|
|
179
|
+
return found;
|
|
180
|
+
}
|
|
133
181
|
export class LlEmitter {
|
|
134
182
|
mod;
|
|
135
183
|
localArrayReads = new Map();
|
|
@@ -178,17 +226,23 @@ export class LlEmitter {
|
|
|
178
226
|
ffiExtendNarrowIntegers;
|
|
179
227
|
cycleColorOffset;
|
|
180
228
|
wasi;
|
|
229
|
+
/** ELF worker executables give thread-locals the executable TLS models. */
|
|
230
|
+
executableTls;
|
|
181
231
|
emitLibraryIdentity;
|
|
182
232
|
runtimeAbiMarker;
|
|
183
233
|
/** Interned string literals: UTF-8 text → { symbol, byte length } —
|
|
184
234
|
* first-use order, the runtime ABI’s determinism discipline. */
|
|
185
235
|
literals = new Map();
|
|
236
|
+
/** Symbols of interned constant data, named by content (contentSymbol). */
|
|
237
|
+
contentSymbols = new Set();
|
|
186
238
|
/** Interned unit-armed union instances: "unionId:tag" → symbol — one
|
|
187
239
|
* immortal (rc == SIZE_MAX) static per (union, unit tag).
|
|
188
240
|
* RC entry points and the collector skip immortals. */
|
|
189
241
|
unitInstances = new Map();
|
|
190
242
|
/** unionId → symbol of the union's immortal ABSENT field-slot instance. */
|
|
191
243
|
absentInstances = new Map();
|
|
244
|
+
/** The nullable-union ABSENT sentinel was referenced (absentInstanceRef). */
|
|
245
|
+
needsNullableAbsent = false;
|
|
192
246
|
immortalValues = new Set();
|
|
193
247
|
/** Regex literal templates: "<flags>/<pattern>" → { symbol, interned
|
|
194
248
|
* source/flags literal refs } — one immortal ScrRegex template per distinct
|
|
@@ -231,6 +285,7 @@ export class LlEmitter {
|
|
|
231
285
|
fnDefText = new Map();
|
|
232
286
|
referenceEffects;
|
|
233
287
|
optionalArrayReads;
|
|
288
|
+
checkedNarrows;
|
|
234
289
|
callArrayReads = new Map();
|
|
235
290
|
mapReadLifetimes = { locals: new Map(), arguments: new Map() };
|
|
236
291
|
callLifetimes;
|
|
@@ -248,6 +303,18 @@ export class LlEmitter {
|
|
|
248
303
|
/** Parameters proven projection-only: callers may pass stack boxes. */
|
|
249
304
|
projectedParameters = new Set();
|
|
250
305
|
stableCallBindings = new Set();
|
|
306
|
+
/** Locals and rebound parameters holding borrowed pointers for their
|
|
307
|
+
* whole lifetime (walk-borrows.ts), per function and for the current one. */
|
|
308
|
+
walkBorrowsByFunction = new Map();
|
|
309
|
+
walkBorrows = new Set();
|
|
310
|
+
/** Functions whose borrowing body returns its result without a reference
|
|
311
|
+
* (analyzeBorrowedReturns), and whether the current body is one. */
|
|
312
|
+
borrowedReturns = new Set();
|
|
313
|
+
/** Rebound parameters that keep the borrowed convention with an owner
|
|
314
|
+
* slot (rebound-parameters.ts), per function, and the current owner slots. */
|
|
315
|
+
reboundByFunction = new Map();
|
|
316
|
+
reboundOwnerSlots = new Map();
|
|
317
|
+
currentBorrowedReturn = false;
|
|
251
318
|
/** Manifest-bound native imports, used by ffiCall emission. */
|
|
252
319
|
ffiByName = new Map();
|
|
253
320
|
/** C-ABI callback trampolines and (for raw/no-userdata callbacks) their
|
|
@@ -266,6 +333,15 @@ export class LlEmitter {
|
|
|
266
333
|
* checks are emitted only after calls that can actually raise. */
|
|
267
334
|
mayThrow;
|
|
268
335
|
indirectMayThrow;
|
|
336
|
+
/** Worker programs: the functions that poll for termination on entry
|
|
337
|
+
* (workerTerminationPolls); null outside worker programs. */
|
|
338
|
+
workerEntryPolls;
|
|
339
|
+
/** Functions that check the native stack guard on entry
|
|
340
|
+
* (stackCheckedFunctions); null where the runtime has no guard. */
|
|
341
|
+
stackChecks;
|
|
342
|
+
/** Static types whose values can be published (@scriptc/threads): their
|
|
343
|
+
* stores carry the frozen-object guard. Null without publish calls. */
|
|
344
|
+
publishedTypes;
|
|
269
345
|
/** Method names with at least one may-throw implementation — the
|
|
270
346
|
* virtualCall pending check's key. */
|
|
271
347
|
mayThrowMethods = new Set();
|
|
@@ -287,6 +363,10 @@ export class LlEmitter {
|
|
|
287
363
|
dynPromiseAdapters = new Map();
|
|
288
364
|
unionsById = new Map();
|
|
289
365
|
nullableFields;
|
|
366
|
+
/** Unions whose values are nullable arm pointers (nullable-unions.ts). */
|
|
367
|
+
nullableUnions;
|
|
368
|
+
/** The union table without nullable unions: the stack-box analyses. */
|
|
369
|
+
boxedUnionsById;
|
|
290
370
|
recordsById = new Map();
|
|
291
371
|
recordCloneShapes = new Set();
|
|
292
372
|
tracedShapes;
|
|
@@ -294,6 +374,8 @@ export class LlEmitter {
|
|
|
294
374
|
/** The class graph (buildClassGraph): preorder numbering, hierarchy
|
|
295
375
|
* membership, virtual slot lists. */
|
|
296
376
|
classMeta;
|
|
377
|
+
/** Which vtable slots borrow which parameters (virtual-borrows.ts). */
|
|
378
|
+
virtualBorrows;
|
|
297
379
|
/** Class objects (classes as first-class values): className → the
|
|
298
380
|
* interned .name literal ref — registered during body emission, the
|
|
299
381
|
* statics and construct thunks assemble around the bodies. */
|
|
@@ -334,6 +416,8 @@ export class LlEmitter {
|
|
|
334
416
|
storedSplits = new Map();
|
|
335
417
|
streamingSplitsEnabled = false;
|
|
336
418
|
initializerBindings;
|
|
419
|
+
/** Whole-program int32 storage and parameter/result facts. */
|
|
420
|
+
int32Slots;
|
|
337
421
|
numericLocals = new Map();
|
|
338
422
|
captureIds = new Set();
|
|
339
423
|
/** Active induction bindings retain their width independently of size_t. */
|
|
@@ -380,16 +464,23 @@ export class LlEmitter {
|
|
|
380
464
|
stmt: () => true,
|
|
381
465
|
expr: (expr) => !(expr.kind === "libCall" && expr.fn === "error.stack"),
|
|
382
466
|
}));
|
|
467
|
+
this.nullableUnions = new NullableUnions(mod);
|
|
383
468
|
this.debug =
|
|
384
469
|
options.debugSources === undefined
|
|
385
470
|
? null
|
|
386
|
-
: new LlvmDebugInfo(mod.sourceFile, options.debugSources, options.pointerBits, mod.unions);
|
|
471
|
+
: new LlvmDebugInfo(mod.sourceFile, options.debugSources, options.pointerBits, mod.unions, this.nullableUnions.armTypes());
|
|
387
472
|
this.constantNumericTables = findConstantNumericTables(mod);
|
|
388
473
|
this.initializerBindings = findInitializerBindings(mod);
|
|
474
|
+
this.int32Slots = analyzeInt32Slots(mod);
|
|
389
475
|
this.sizeType = options.pointerBits === 32 ? "i32" : "i64";
|
|
390
476
|
this.ffiExtendNarrowIntegers =
|
|
391
477
|
options.wasi === true || ffiExtendsNarrowIntegers(options.targetTriple);
|
|
392
478
|
this.wasi = options.wasi === true;
|
|
479
|
+
this.executableTls =
|
|
480
|
+
mod.workers === true &&
|
|
481
|
+
mod.lib === undefined &&
|
|
482
|
+
!this.wasi &&
|
|
483
|
+
!/apple|darwin|windows|mingw|cygwin|win32/.test(options.targetTriple || process.platform);
|
|
393
484
|
this.emitLibraryIdentity = options.emitLibraryIdentity !== false;
|
|
394
485
|
this.runtimeAbiMarker = options.runtimeAbiMarker === true;
|
|
395
486
|
this.rcHelpers = options.inlineRc === true ? new Set() : null;
|
|
@@ -405,11 +496,17 @@ export class LlEmitter {
|
|
|
405
496
|
for (const entry of mod.ffiImports ?? []) {
|
|
406
497
|
this.ffiByName.set(entry.name, entry);
|
|
407
498
|
}
|
|
408
|
-
|
|
499
|
+
// Executables on Linux and macOS install a native stack guard per thread
|
|
500
|
+
// and per async fiber; WASI, Windows and library builds have none.
|
|
501
|
+
const triple = (options.targetTriple ?? "").toLowerCase();
|
|
502
|
+
const windows = triple === "" ? process.platform === "win32" : triple.includes("windows");
|
|
503
|
+
const mt = computeMayThrow(mod, {
|
|
504
|
+
stackChecks: !this.wasi && mod.lib === undefined && !windows,
|
|
505
|
+
});
|
|
409
506
|
this.mayThrow = mt.fns;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
507
|
+
this.workerEntryPolls = mt.workerEntryPolls ?? null;
|
|
508
|
+
this.stackChecks = mt.stackChecks ?? null;
|
|
509
|
+
this.publishedTypes = computePublishedTypes(mod);
|
|
413
510
|
this.indirectMayThrow = mt.indirect || mod.workers === true;
|
|
414
511
|
for (const cls of mod.classes ?? []) {
|
|
415
512
|
for (const m of cls.methods ?? []) {
|
|
@@ -419,10 +516,56 @@ export class LlEmitter {
|
|
|
419
516
|
}
|
|
420
517
|
for (const u of mod.unions ?? [])
|
|
421
518
|
this.unionsById.set(u.id, u);
|
|
422
|
-
this.
|
|
519
|
+
this.boxedUnionsById = this.nullableUnions.boxedUnions(this.unionsById);
|
|
520
|
+
this.immortalValues.add("null");
|
|
521
|
+
this.immortalValues.add(NULLABLE_NULL);
|
|
522
|
+
this.nullableFields = new NullableRefFields(mod.classes ?? [], this.boxedUnionsById);
|
|
523
|
+
// Optional array reads also cover nullable unions: emitLocalArrayRead
|
|
524
|
+
// then yields the (borrowed or owned) element pointer, not a stack box.
|
|
423
525
|
this.optionalArrayReads = new OptionalArrayReads(this.fnByName, this.unionsById);
|
|
424
|
-
this.
|
|
425
|
-
this.
|
|
526
|
+
this.checkedNarrows = new CheckedNarrows(this.fnByName);
|
|
527
|
+
this.referenceEffects = new ReferenceEffects(this.fnByName, (call) => this.optionalArrayReads.get(call) !== null, mod.classes ?? []);
|
|
528
|
+
const walkParameters = new Map();
|
|
529
|
+
if (this.debug === null) {
|
|
530
|
+
const host = {
|
|
531
|
+
pointerLocal: (local) => this.plainReference(local.type),
|
|
532
|
+
borrowsWithoutOwning: (e) => this.borrowsWithoutOwning(e),
|
|
533
|
+
};
|
|
534
|
+
for (const fn of this.fnByName.values()) {
|
|
535
|
+
if (!this.referenceEffects.functions.has(fn.name))
|
|
536
|
+
continue;
|
|
537
|
+
const walks = findWalkBorrows(fn, host);
|
|
538
|
+
if (walks.size === 0)
|
|
539
|
+
continue;
|
|
540
|
+
this.walkBorrowsByFunction.set(fn.name, walks);
|
|
541
|
+
const indexes = new Set();
|
|
542
|
+
fn.params.forEach((param, index) => {
|
|
543
|
+
if (walks.has(param.localId))
|
|
544
|
+
indexes.add(index);
|
|
545
|
+
});
|
|
546
|
+
if (indexes.size > 0)
|
|
547
|
+
walkParameters.set(fn.name, indexes);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (this.debug === null) {
|
|
551
|
+
for (const fn of this.fnByName.values()) {
|
|
552
|
+
// Strings keep the owned parameter: their in-place append
|
|
553
|
+
// (`s = s + x`) needs a uniquely owned binding.
|
|
554
|
+
const rebound = findReboundParameters(fn, (type) => this.plainReference(type) &&
|
|
555
|
+
type.kind !== "string" &&
|
|
556
|
+
(type.kind !== "union" || this.nullableUnions.get(type.unionId)?.arm.kind !== "string"), this.walkBorrowsByFunction.get(fn.name) ?? new Set());
|
|
557
|
+
if (rebound.size === 0)
|
|
558
|
+
continue;
|
|
559
|
+
this.reboundByFunction.set(fn.name, rebound);
|
|
560
|
+
const indexes = walkParameters.get(fn.name) ?? new Set();
|
|
561
|
+
fn.params.forEach((param, index) => {
|
|
562
|
+
if (rebound.has(param.localId))
|
|
563
|
+
indexes.add(index);
|
|
564
|
+
});
|
|
565
|
+
walkParameters.set(fn.name, indexes);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
this.callLifetimes = analyzeCallLifetimes(this.fnByName, (className, field) => this.nullableFields.get(className, field) !== null, walkParameters);
|
|
426
569
|
this.constantCallbacks = findConstantCallbacks(mod, this.callLifetimes);
|
|
427
570
|
this.stackCallbacks = new StackCallbacks(this.fnByName);
|
|
428
571
|
this.receiverReaders = new AmbientReceiverReaders(this.fnByName);
|
|
@@ -438,6 +581,7 @@ export class LlEmitter {
|
|
|
438
581
|
cycleColorOffset: this.cycleColorOffset,
|
|
439
582
|
tracedShapes: this.tracedShapes,
|
|
440
583
|
tracedUnions: this.tracedUnions,
|
|
584
|
+
nullableUnions: this.nullableUnions,
|
|
441
585
|
recordsById: this.recordsById,
|
|
442
586
|
recordCloneShapes: this.recordCloneShapes,
|
|
443
587
|
objectAudit: options.objectAudit !== false,
|
|
@@ -445,11 +589,11 @@ export class LlEmitter {
|
|
|
445
589
|
pointerBits: options.pointerBits,
|
|
446
590
|
wasi: options.wasi,
|
|
447
591
|
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,
|
|
592
|
+
threadInstances: mod.lib?.threadInstances === true,
|
|
452
593
|
}),
|
|
594
|
+
// Worker programs keep one allocator per script thread: the inline
|
|
595
|
+
// paths address its thread-local state.
|
|
596
|
+
threadLocalAlloc: mod.workers === true,
|
|
453
597
|
rcHelpers: this.rcHelpers,
|
|
454
598
|
unionsById: this.unionsById,
|
|
455
599
|
declare: (decl) => this.declare(decl),
|
|
@@ -458,6 +602,7 @@ export class LlEmitter {
|
|
|
458
602
|
internLiteral: (text) => this.internLiteral(text),
|
|
459
603
|
cstr: (text) => this.cstr(text),
|
|
460
604
|
unitInstanceRef: (unionId, tag) => this.unitInstanceRef(unionId, tag),
|
|
605
|
+
fieldAbsentTestIn: (B, value, unionId) => this.fieldAbsentTestIn(B, value, unionId),
|
|
461
606
|
absentInstanceRef: (unionId) => this.absentInstanceRef(unionId),
|
|
462
607
|
liveDynRefAdapter: (type) => this.liveDynRefAdapter(type),
|
|
463
608
|
liveDynUnionRefAdapter: (type) => this.liveDynUnionRefAdapter(type),
|
|
@@ -470,6 +615,8 @@ export class LlEmitter {
|
|
|
470
615
|
.filter((meta) => intervals.some((range) => range.pre <= meta.pre && meta.pre <= range.post))
|
|
471
616
|
.map((meta) => meta.def.name);
|
|
472
617
|
},
|
|
618
|
+
pendingTestLines: (dest) => this.pendingTestLines(dest),
|
|
619
|
+
virtualEntry: (implFn) => this.virtualEntry(implFn),
|
|
473
620
|
};
|
|
474
621
|
this.walkers = new LlWalkers(this.shapeHost);
|
|
475
622
|
this.dyn = new LlDyn(this.shapeHost);
|
|
@@ -526,6 +673,21 @@ export class LlEmitter {
|
|
|
526
673
|
lib: rec.lib,
|
|
527
674
|
});
|
|
528
675
|
}
|
|
676
|
+
this.virtualBorrows = new VirtualBorrows(mod, this.classMeta, this.fnByName, this.callLifetimes.borrowed);
|
|
677
|
+
// After the vtable slots: borrowing slots exclude their implementations.
|
|
678
|
+
if (this.debug === null)
|
|
679
|
+
this.analyzeBorrowedReturns();
|
|
680
|
+
}
|
|
681
|
+
/** The borrowed parameters of a hierarchy's vtable slot (empty: owned). */
|
|
682
|
+
virtualSlotBorrowed(rootName, slotIndex) {
|
|
683
|
+
return this.virtualBorrows.slot(rootName, slotIndex);
|
|
684
|
+
}
|
|
685
|
+
/** The vtable entry for one implementation (VirtualBorrows.entry). */
|
|
686
|
+
virtualEntry(implFn) {
|
|
687
|
+
return this.virtualBorrows.entry(implFn);
|
|
688
|
+
}
|
|
689
|
+
implSlotBorrowed(implFn) {
|
|
690
|
+
return this.virtualBorrows.implSlotBorrowed(implFn);
|
|
529
691
|
}
|
|
530
692
|
abiOffset(native64, wasm32) {
|
|
531
693
|
return this.sizeType === "i32" ? wasm32 : native64;
|
|
@@ -566,7 +728,6 @@ export class LlEmitter {
|
|
|
566
728
|
const defs = [];
|
|
567
729
|
if (this.ffiCallbackAdapters.size === 0)
|
|
568
730
|
return { globals, defs };
|
|
569
|
-
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
570
731
|
this.declare(`declare void @scr_trap(ptr)`);
|
|
571
732
|
const expired = this.cstr("scriptc: native callback invoked outside its call-scoped lifetime\n");
|
|
572
733
|
const released = this.cstr("scriptc: native callback invoked outside its retained lifetime\n");
|
|
@@ -684,7 +845,7 @@ export class LlEmitter {
|
|
|
684
845
|
? ` %cb = load ptr, ptr @${adapter.tls}`
|
|
685
846
|
: adapter.global !== null
|
|
686
847
|
? ` %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:`,
|
|
848
|
+
: ` %cb = getelementptr inbounds i8, ptr %ctx, i64 0`, ` %missing = icmp eq ptr %cb, null`, ` br i1 %missing, label %expired, label %ready`, `expired:`, ` call void @scr_trap(ptr ${adapter.callback.lifetime === "call" ? expired : released})`, ` unreachable`, `ready:`, ...this.pendingTestLines("%pending"), ` br i1 %pending, label %skip, label %invoke`, `skip:`, ` ret ${ffiCallbackDummyLl(cb)}`, `invoke:`);
|
|
688
849
|
// Validate every native pointer before allocating any copy-in value.
|
|
689
850
|
// A later bad slot therefore cannot leak an earlier materialization.
|
|
690
851
|
for (let i = 0; i < cb.params.length; i++) {
|
|
@@ -832,6 +993,20 @@ export class LlEmitter {
|
|
|
832
993
|
return this.emitParts().join("\n");
|
|
833
994
|
}
|
|
834
995
|
emitParts() {
|
|
996
|
+
const parts = this.emitModuleParts();
|
|
997
|
+
if (!this.executableTls)
|
|
998
|
+
return parts;
|
|
999
|
+
// A worker executable's thread-locals all live in the executable's own
|
|
1000
|
+
// TLS block (the program's internal ones, the runtime's external ones),
|
|
1001
|
+
// so on ELF they can use the executable models: LLVM's default
|
|
1002
|
+
// general-dynamic sequence (relaxed by the linker to `mov %fs:0` plus
|
|
1003
|
+
// an add) becomes one %fs-relative access LLVM can fold into loads.
|
|
1004
|
+
// Darwin's thread-local variables have a single model.
|
|
1005
|
+
return parts.map((part) => part
|
|
1006
|
+
.replace(/^(@[-$._A-Za-z0-9]+ = internal )thread_local global /gm, "$1thread_local(localexec) global ")
|
|
1007
|
+
.replace(/^(@[-$._A-Za-z0-9]+ = external )thread_local global /gm, "$1thread_local(initialexec) global "));
|
|
1008
|
+
}
|
|
1009
|
+
emitModuleParts() {
|
|
835
1010
|
// Function bodies first (the literal/unit/fn-value tables fill as they
|
|
836
1011
|
// emit), then the file assembles around them — the runtime ABI’s order.
|
|
837
1012
|
const fnDefs = [];
|
|
@@ -858,7 +1033,7 @@ export class LlEmitter {
|
|
|
858
1033
|
this.declare(`declare void @scr_error_register_dyn(ptr, ptr)`);
|
|
859
1034
|
errorViews.push(` call void @scr_error_register_dyn(ptr @${mangleVtInstance(meta.def.name)}, ptr @${name})`);
|
|
860
1035
|
}
|
|
861
|
-
const layouts = emitLlvmLayouts(this.shapeHost, this.mod, this.classMeta, this.classObjs, this.fnByName, (t) => this.llType(t), this.nullableFields);
|
|
1036
|
+
const layouts = emitLlvmLayouts(this.shapeHost, this.mod, this.classMeta, this.classObjs, this.fnByName, (t) => this.llType(t), this.nullableFields, this.int32Slots);
|
|
862
1037
|
const shapes = layouts.records;
|
|
863
1038
|
const classShapes = layouts.classes;
|
|
864
1039
|
const classObjDefs = layouts.classObjects;
|
|
@@ -1061,12 +1236,15 @@ export class LlEmitter {
|
|
|
1061
1236
|
const topPendingReleases = asyncEntry ? globalReleaseLines("gp") : [];
|
|
1062
1237
|
const loopReportedReleases = runsLoop ? globalReleaseLines("gq") : [];
|
|
1063
1238
|
// main's epilogues read the flag / the loop entry points — declared
|
|
1064
|
-
// HERE, before the extern block flushes
|
|
1065
|
-
//
|
|
1239
|
+
// HERE, before the extern block flushes. They run once, so they keep
|
|
1240
|
+
// the out-of-line scr_exc_pending call; per-call checks inline the
|
|
1241
|
+
// test (pendingTestLines).
|
|
1066
1242
|
if (entryMayThrow || runsLoop)
|
|
1067
1243
|
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
1068
1244
|
if (runsLoop) {
|
|
1069
1245
|
this.declare(`declare zeroext i1 @scr_loop_run(ptr)`);
|
|
1246
|
+
if (this.mod.commonJsEntry === true)
|
|
1247
|
+
this.declare(`declare void @scr_loop_commonjs_entry()`);
|
|
1070
1248
|
this.declare(`declare zeroext i1 @scr_report_unhandled_rejections()`);
|
|
1071
1249
|
this.declare(`declare void @scr_discard_unhandled_rejections()`);
|
|
1072
1250
|
}
|
|
@@ -1157,6 +1335,8 @@ export class LlEmitter {
|
|
|
1157
1335
|
const helpers = [
|
|
1158
1336
|
...this.helperDefs(),
|
|
1159
1337
|
...emitClassMembershipHelper(this.classMeta, this.sizeType),
|
|
1338
|
+
...(this.publishWalkersCache?.defs() ?? []),
|
|
1339
|
+
...this.publishedClassNamesDefs(),
|
|
1160
1340
|
];
|
|
1161
1341
|
if (this.constantNumericTables.size > 0)
|
|
1162
1342
|
this.declare(`declare double @scr_arr_get_number(ptr, double)`);
|
|
@@ -1186,6 +1366,15 @@ export class LlEmitter {
|
|
|
1186
1366
|
...(this.rcHelpers === null
|
|
1187
1367
|
? []
|
|
1188
1368
|
: [`%ScrMapRc = type { ${this.sizeType}, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr }`]),
|
|
1369
|
+
// The complete ScrMap and its { key, val, hash } entry, read by the
|
|
1370
|
+
// inline number-key lookup (sc_map_entry_f64): entries is field 12,
|
|
1371
|
+
// the direct index and its length fields 18 and 19.
|
|
1372
|
+
...(this.decls.has(NUMBER_MAP_ENTRY_DECL)
|
|
1373
|
+
? [
|
|
1374
|
+
`%ScrMapIx = type { ${this.sizeType}, i32, i32, ptr, ptr, ptr, ptr, ptr, ptr, ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, ptr, ${this.sizeType}, ptr, ${this.sizeType}, ptr, ptr, ptr, ${this.sizeType} }`,
|
|
1375
|
+
`%ScrMapEntry = type { i64, i64, i64 }`,
|
|
1376
|
+
]
|
|
1377
|
+
: []),
|
|
1189
1378
|
// The runtime error prefix { rc, vt, name, message, code, cause } and the
|
|
1190
1379
|
// class-object shape { rc, pre, post, ctor, name } — field reads on
|
|
1191
1380
|
// builtin errors and classval loads GEP through these.
|
|
@@ -1231,7 +1420,7 @@ export class LlEmitter {
|
|
|
1231
1420
|
// data (string literals, unit arms, template arrays, vtables) stays
|
|
1232
1421
|
// shared.
|
|
1233
1422
|
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
|
|
1423
|
+
out.push(``, `@scr_error_vts = external ${tl}global [${RUNTIME_ERROR_CLASSES.size} x %ScrVt]`, `declare void @scr_init()`, `declare void @scr_lib_init(i32, ptr)`, ...(this.mod.workers ? [`declare void @scr_runtime_workers_v8()`] : []), ...(this.stackChecks?.size ? [`declare void @scr_stack_guard_init()`] : []), ...(this.runtimeAbiMarker && this.mod.lib === undefined
|
|
1235
1424
|
? [`declare void @${RUNTIME_ABI_MARKER}()`]
|
|
1236
1425
|
: []));
|
|
1237
1426
|
for (const d of this.decls)
|
|
@@ -1250,7 +1439,10 @@ export class LlEmitter {
|
|
|
1250
1439
|
// the runtime ABI’s static table, retain/release skip rc == SIZE_MAX.
|
|
1251
1440
|
// 64-bit (little-endian) targets split the capacity word: the high
|
|
1252
1441
|
// half carries the precomputed Map key hash (SCR_STR_HASH_CACHE).
|
|
1253
|
-
|
|
1442
|
+
// Bit 31 marks all-ASCII literals (SCR_STR_ASCII_BIT) on every target.
|
|
1443
|
+
const capWord = this.sizeType === "i64"
|
|
1444
|
+
? literalCapWord(Buffer.from(text, "utf8"))
|
|
1445
|
+
: literalCapWord32(Buffer.from(text, "utf8"));
|
|
1254
1446
|
out.push(`@${lit.sym} = internal global { ${this.sizeType}, ${this.sizeType}, ${this.sizeType}, [${lit.len + 1} x i8] } ` +
|
|
1255
1447
|
`{ ${this.sizeType} -1, ${this.sizeType} ${lit.len}, ${this.sizeType} ${capWord}, [${lit.len + 1} x i8] c"${llStrBytes(text)}" }`);
|
|
1256
1448
|
}
|
|
@@ -1276,6 +1468,20 @@ export class LlEmitter {
|
|
|
1276
1468
|
}
|
|
1277
1469
|
if (this.absentInstances.size > 0)
|
|
1278
1470
|
out.push(``);
|
|
1471
|
+
if (this.nullableUnions.usesNullSentinel) {
|
|
1472
|
+
// `null` in every `C | null | undefined` union (NULL is undefined):
|
|
1473
|
+
// one module-scope immortal object like the ABSENT sentinel.
|
|
1474
|
+
const S = this.sizeType;
|
|
1475
|
+
out.push(`${NULLABLE_NULL} = internal global { ${S}, ${S}, ${S}, ${S} } { ${S} -1, ${S} 0, ${S} 0, ${S} 0 } ; nullable null arm`, ``);
|
|
1476
|
+
}
|
|
1477
|
+
if (this.needsNullableAbsent) {
|
|
1478
|
+
// The ABSENT field-slot state of every nullable-pointer union: one
|
|
1479
|
+
// module-scope immortal object (rc == SIZE_MAX), distinct from NULL
|
|
1480
|
+
// (undefined) and from every instance. RC entry points and the
|
|
1481
|
+
// collector skip it like any immortal; field reads compare its address.
|
|
1482
|
+
const S = this.sizeType;
|
|
1483
|
+
out.push(`${NULLABLE_ABSENT} = internal global { ${S}, ${S}, ${S}, ${S} } { ${S} -1, ${S} 0, ${S} 0, ${S} 0 } ; nullable absent field`, ``);
|
|
1484
|
+
}
|
|
1279
1485
|
for (const [key, re] of this.regexInstances) {
|
|
1280
1486
|
// One immortal ScrRegex per (pattern, flags) literal, pointing at
|
|
1281
1487
|
// the interned source/flags strings. The bc and native-matcher slots
|
|
@@ -1442,7 +1648,10 @@ export class LlEmitter {
|
|
|
1442
1648
|
out.push(this.debug.render());
|
|
1443
1649
|
return out;
|
|
1444
1650
|
}
|
|
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()`,
|
|
1651
|
+
out.push(`define i32 @${this.mod.workers ? "sc_context_entry" : this.wasi ? "__main_argc_argv" : "main"}(i32 %argc, ptr %argv) ${FN_ATTRS} {`, `entry:`, ...(this.runtimeAbiMarker ? [` call void @${RUNTIME_ABI_MARKER}()`] : []), ...(this.mod.workers ? [` call void @scr_runtime_workers_v8()`] : []), ` call void @scr_init()`,
|
|
1652
|
+
// Each context (the main thread, every worker) installs its stack
|
|
1653
|
+
// guard before running compiled code that checks it.
|
|
1654
|
+
...(this.stackChecks?.size ? [` call void @scr_stack_guard_init()`] : []), ...stamps,
|
|
1446
1655
|
// Event-surface programs (signal/exit listeners) fill the loop's
|
|
1447
1656
|
// nullable event hooks before %main — scr_events.c links only when
|
|
1448
1657
|
// this line is emitted (native/executable.ts gates on the same predicate).
|
|
@@ -1487,6 +1696,7 @@ export class LlEmitter {
|
|
|
1487
1696
|
// both exit 1, like Node — the C main's loop block exactly.
|
|
1488
1697
|
...(runsLoop
|
|
1489
1698
|
? [
|
|
1699
|
+
...(this.mod.commonJsEntry === true ? [` call void @scr_loop_commonjs_entry()`] : []),
|
|
1490
1700
|
` %loop_rejection = call zeroext i1 @scr_loop_run(ptr ${asyncEntry ? "%top" : "null"})`,
|
|
1491
1701
|
` %lexc = call zeroext i1 @scr_exc_pending()`,
|
|
1492
1702
|
` br i1 %lexc, label %luncaught, label %lok`,
|
|
@@ -1802,10 +2012,19 @@ export class LlEmitter {
|
|
|
1802
2012
|
msgHelper("sc_bad_key", "sc_bad_key_msg", "scriptc: TypeError: record has no key (typed slot — no undefined is representable)\n");
|
|
1803
2013
|
}
|
|
1804
2014
|
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);
|
|
2015
|
+
// scr_box_retain is a static inline (increment-unless-immortal);
|
|
1807
2016
|
// 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`, `
|
|
2017
|
+
defs.push(`define internal ptr @sc_retain_box(ptr %b) ${FN_ATTRS} {`, `entry:`, ` %rc = load ${this.sizeType}, ptr %b`, ` %imm = icmp eq ${this.sizeType} %rc, -1`, ` br i1 %imm, label %done, label %inc`, `inc:`, ` %n = add ${this.sizeType} %rc, 1`, ` store ${this.sizeType} %n, ptr %b`, ` br label %done`, `done:`, ` ret ptr %b`, `}`, ``);
|
|
2018
|
+
}
|
|
2019
|
+
// Number-key Map/Set lookups probe the runtime's direct index inline:
|
|
2020
|
+
// while ndense is nonzero every live key is an integer below it, so an
|
|
2021
|
+
// in-range integral key reads its slot (entry index + 1, 0 = absent) and
|
|
2022
|
+
// any other key is absent. Maps without the index (ndense 0) and every
|
|
2023
|
+
// non-integral probe outside the table take the runtime lookup, which
|
|
2024
|
+
// keeps the complete SameValueZero semantics.
|
|
2025
|
+
if (this.decls.has(NUMBER_MAP_ENTRY_DECL)) {
|
|
2026
|
+
const sz = this.sizeType;
|
|
2027
|
+
defs.push(`define internal ptr @sc_map_entry_f64(ptr %m, double %k) ${FN_ATTRS} {`, `entry:`, ` %ndp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 19`, ` %nd = load ${sz}, ptr %ndp`, ` %ndf = uitofp ${sz} %nd to double`, ` %lo = fcmp oge double %k, 0.0`, ` %hi = fcmp olt double %k, %ndf`, ` %in = and i1 %lo, %hi`, ` br i1 %in, label %direct, label %outside`, `direct:`, ` %i = fptoui double %k to ${sz}`, ` %back = uitofp ${sz} %i to double`, ` %int = fcmp oeq double %back, %k`, ` br i1 %int, label %slot, label %miss`, `slot:`, ` %dp = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 18`, ` %dense = load ptr, ptr %dp`, ` %sp = getelementptr inbounds i32, ptr %dense, ${sz} %i`, ` %s = load i32, ptr %sp`, ` %empty = icmp eq i32 %s, 0`, ` br i1 %empty, label %miss, label %hit`, `hit:`, ` %e32 = sub i32 %s, 1`, sz === "i32" ? ` %e = add i32 %e32, 0` : ` %e = zext i32 %e32 to ${sz}`, ` %ep = getelementptr inbounds %ScrMapIx, ptr %m, i32 0, i32 12`, ` %entries = load ptr, ptr %ep`, ` %found = getelementptr inbounds %ScrMapEntry, ptr %entries, ${sz} %e`, ` ret ptr %found`, `miss:`, ` ret ptr null`, `outside:`, ` %dense.any = icmp ne ${sz} %nd, 0`, ` br i1 %dense.any, label %miss, label %slow`, `slow:`, ` %r = call ptr @scr_map_entry_f64(ptr %m, double %k)`, ` ret ptr %r`, `}`, ``);
|
|
1809
2028
|
}
|
|
1810
2029
|
// String comparisons enter through small internal wrappers that LLVM
|
|
1811
2030
|
// inlines at each site: interned literals and shared values compare by
|
|
@@ -1814,6 +2033,15 @@ export class LlEmitter {
|
|
|
1814
2033
|
if (this.decls.has(`declare zeroext i1 @scr_str_eq(ptr, ptr)`)) {
|
|
1815
2034
|
defs.push(`define internal zeroext i1 @sc_str_eq(ptr %a, ptr %b) ${FN_ATTRS} {`, `entry:`, ` %same = icmp eq ptr %a, %b`, ` br i1 %same, label %yes, label %lengths`, `lengths:`, ` %alen.p = getelementptr inbounds %ScrStr, ptr %a, i32 0, i32 1`, ` %blen.p = getelementptr inbounds %ScrStr, ptr %b, i32 0, i32 1`, ` %alen = load ${this.sizeType}, ptr %alen.p`, ` %blen = load ${this.sizeType}, ptr %blen.p`, ` %samelen = icmp eq ${this.sizeType} %alen, %blen`, ` br i1 %samelen, label %bytes, label %no`, `bytes:`, ` %eq = call zeroext i1 @scr_str_eq(ptr %a, ptr %b)`, ` ret i1 %eq`, `yes:`, ` ret i1 true`, `no:`, ` ret i1 false`, `}`, ``);
|
|
1816
2035
|
}
|
|
2036
|
+
// String length and charCodeAt read proven-ASCII strings inline: bit 31
|
|
2037
|
+
// of the capacity word (SCR_STR_ASCII_BIT, the low half's sign bit on
|
|
2038
|
+
// little-endian targets) makes UTF-16 units bytes. Unproven strings and
|
|
2039
|
+
// anything but an in-range index take the runtime's UTF-16 mapping.
|
|
2040
|
+
for (const helper of STRING_READ_HELPERS) {
|
|
2041
|
+
if (!this.decls.has(helper.decl))
|
|
2042
|
+
continue;
|
|
2043
|
+
defs.push(...helper.define(this.sizeType, FN_ATTRS), ``);
|
|
2044
|
+
}
|
|
1817
2045
|
// Ordering also decides inline when the first bytes differ: byte order
|
|
1818
2046
|
// is the plain comparison's order, and the UTF-16 order whenever both
|
|
1819
2047
|
// bytes are ASCII. Callers only test the sign of the result.
|
|
@@ -1861,7 +2089,18 @@ export class LlEmitter {
|
|
|
1861
2089
|
* here too. */
|
|
1862
2090
|
inlineRcTail(flushedDecls) {
|
|
1863
2091
|
const late = inlineRcDecls(this.shapeHost).filter((d) => !flushedDecls.has(d));
|
|
1864
|
-
|
|
2092
|
+
// NULL-skipping retain wrappers of nullable unions; a runtime inner
|
|
2093
|
+
// retain requested after the extern block flushed is declared here.
|
|
2094
|
+
for (const inner of this.nullableUnions.retainWrappers.values()) {
|
|
2095
|
+
const decl = `declare ptr ${inner}(ptr)`;
|
|
2096
|
+
if (inner.startsWith("@scr_") && !flushedDecls.has(decl) && !late.includes(decl))
|
|
2097
|
+
late.push(decl);
|
|
2098
|
+
}
|
|
2099
|
+
return [
|
|
2100
|
+
...late,
|
|
2101
|
+
...emitNullableRetainWrappers(this.shapeHost),
|
|
2102
|
+
...emitInlineRcHelpers(this.shapeHost),
|
|
2103
|
+
];
|
|
1865
2104
|
}
|
|
1866
2105
|
/** Env-signature wrappers + interned immortal closures for declared
|
|
1867
2106
|
* functions used as values (the runtime ABI’s sc_w_/sc_fc_ pair): every
|
|
@@ -1892,7 +2131,6 @@ export class LlEmitter {
|
|
|
1892
2131
|
const sizeOf = `ptrtoint (ptr getelementptr (%${pack}, ptr null, i32 1) to ${this.sizeType})`;
|
|
1893
2132
|
this.declare(`declare void @free(ptr)`);
|
|
1894
2133
|
this.declare(`declare ptr @malloc(${this.sizeType})`);
|
|
1895
|
-
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
1896
2134
|
const tr = [
|
|
1897
2135
|
`define internal void @${mangleTrampoline(fn.name)}(ptr %self, ptr %ap) ${FN_ATTRS} {`,
|
|
1898
2136
|
`entry:`,
|
|
@@ -2048,7 +2286,6 @@ export class LlEmitter {
|
|
|
2048
2286
|
this.declare(`declare void @scr_async_inline_enter(ptr)`);
|
|
2049
2287
|
this.declare(`declare ptr @scr_async_inline_leave(ptr)`);
|
|
2050
2288
|
this.declare(`declare void @scr_promise_reject_pending(ptr)`);
|
|
2051
|
-
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
2052
2289
|
if (lifted) {
|
|
2053
2290
|
this.declare(`declare ptr @scr_closure_retain_v(ptr)`);
|
|
2054
2291
|
this.declare(`declare void @scr_closure_release(ptr)`);
|
|
@@ -2064,7 +2301,7 @@ export class LlEmitter {
|
|
|
2064
2301
|
` call void @scr_async_inline_enter(ptr %frame)`,
|
|
2065
2302
|
retTy === "void" ? ` ${bodyCall}` : ` %r = ${bodyCall}`,
|
|
2066
2303
|
...(lifted ? [` call void @scr_closure_release(ptr %a0)`] : []),
|
|
2067
|
-
|
|
2304
|
+
...this.pendingTestLines("%pend"),
|
|
2068
2305
|
` %p = call ptr @scr_async_inline_leave(ptr %frame)`,
|
|
2069
2306
|
` br i1 %pend, label %thrown, label %clean`,
|
|
2070
2307
|
`clean:`,
|
|
@@ -2123,7 +2360,7 @@ export class LlEmitter {
|
|
|
2123
2360
|
if (fn.captures !== undefined) {
|
|
2124
2361
|
this.declare(`declare void @scr_closure_release(ptr)`);
|
|
2125
2362
|
}
|
|
2126
|
-
tr.push(
|
|
2363
|
+
tr.push(...this.pendingTestLines("%pend"), ` br i1 %pend, label %thrown, label %clean`, `clean:`, ` %pr = call ptr @scr_fiber_promise(ptr %self)`);
|
|
2127
2364
|
for (const line of this.asyncFulfillLines(ret, "%pr"))
|
|
2128
2365
|
tr.push(line);
|
|
2129
2366
|
tr.push(` ret void`, `thrown:`);
|
|
@@ -2232,7 +2469,7 @@ export class LlEmitter {
|
|
|
2232
2469
|
tr.push(` ret void`, `}`, ``);
|
|
2233
2470
|
}
|
|
2234
2471
|
else {
|
|
2235
|
-
tr.push(` %g = call ptr @scr_gen_of_fiber(ptr %self)`,
|
|
2472
|
+
tr.push(` %g = call ptr @scr_gen_of_fiber(ptr %self)`, ...this.pendingTestLines("%pend"), ` br i1 %pend, label %thrown, label %clean`, `clean:`);
|
|
2236
2473
|
switch (ret.kind) {
|
|
2237
2474
|
case "void":
|
|
2238
2475
|
tr.push(` br label %done ; void body: the done value is undefined (NONE)`);
|
|
@@ -2322,10 +2559,30 @@ export class LlEmitter {
|
|
|
2322
2559
|
return out;
|
|
2323
2560
|
}
|
|
2324
2561
|
// ── plumbing (frame and scope ownership) ──────
|
|
2562
|
+
/** A symbol derived from the constant's text rather than its first-use
|
|
2563
|
+
* index, so an edit that interns one more string leaves every other
|
|
2564
|
+
* constant's symbol, and the code referencing it, unchanged. This lets
|
|
2565
|
+
* native builds reuse the compiled partitions an edit does not touch. A
|
|
2566
|
+
* collision retries with a counter, still in first-use order. */
|
|
2567
|
+
contentSymbol(prefix, text) {
|
|
2568
|
+
for (let attempt = 0;; attempt++) {
|
|
2569
|
+
const digest = createHash("sha256")
|
|
2570
|
+
.update(attempt === 0 ? text : `${attempt}\0${text}`)
|
|
2571
|
+
.digest("hex");
|
|
2572
|
+
const symbol = `${prefix}${parseInt(digest.slice(0, 13), 16)}`;
|
|
2573
|
+
if (!this.contentSymbols.has(symbol)) {
|
|
2574
|
+
this.contentSymbols.add(symbol);
|
|
2575
|
+
return symbol;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2325
2579
|
internLiteral(text) {
|
|
2326
2580
|
let lit = this.literals.get(text);
|
|
2327
2581
|
if (!lit) {
|
|
2328
|
-
lit = {
|
|
2582
|
+
lit = {
|
|
2583
|
+
sym: this.contentSymbol("sc_lit_", text),
|
|
2584
|
+
len: Buffer.byteLength(text, "utf8"),
|
|
2585
|
+
};
|
|
2329
2586
|
this.literals.set(text, lit);
|
|
2330
2587
|
}
|
|
2331
2588
|
const name = `@${lit.sym}`;
|
|
@@ -2337,7 +2594,7 @@ export class LlEmitter {
|
|
|
2337
2594
|
cstr(text) {
|
|
2338
2595
|
let c = this.cstrs.get(text);
|
|
2339
2596
|
if (!c) {
|
|
2340
|
-
c = { sym:
|
|
2597
|
+
c = { sym: this.contentSymbol("sc_cs_", text), len: Buffer.byteLength(text, "utf8") };
|
|
2341
2598
|
this.cstrs.set(text, c);
|
|
2342
2599
|
}
|
|
2343
2600
|
return `@${c.sym}`;
|
|
@@ -2354,6 +2611,12 @@ export class LlEmitter {
|
|
|
2354
2611
|
if (!arm || !isUnitType(arm)) {
|
|
2355
2612
|
throw new InternalCompilerError(`llvm emitter bug: unit instance for non-unit arm ${tag} of ${unionId}`);
|
|
2356
2613
|
}
|
|
2614
|
+
const nullable = this.nullableUnions.get(unionId);
|
|
2615
|
+
if (nullable) {
|
|
2616
|
+
// NULL is the unit arm of a nullable union (`undefined` when it has
|
|
2617
|
+
// both); the `null` sentinel is immortal. Neither owns anything.
|
|
2618
|
+
return tag === nullable.nullTag ? NULLABLE_NULL : "null";
|
|
2619
|
+
}
|
|
2357
2620
|
const key = `${unionId}:${tag}`;
|
|
2358
2621
|
let sym = this.unitInstances.get(key);
|
|
2359
2622
|
if (!sym) {
|
|
@@ -2370,6 +2633,11 @@ export class LlEmitter {
|
|
|
2370
2633
|
if (undefinedArmTag({ kind: "union", unionId }, this.unionsById) < 0) {
|
|
2371
2634
|
throw new InternalCompilerError(`llvm emitter bug: absent state of ${unionId} without undefined`);
|
|
2372
2635
|
}
|
|
2636
|
+
if (this.nullableUnions.has(unionId)) {
|
|
2637
|
+
this.needsNullableAbsent = true;
|
|
2638
|
+
this.immortalValues.add(NULLABLE_ABSENT);
|
|
2639
|
+
return NULLABLE_ABSENT;
|
|
2640
|
+
}
|
|
2373
2641
|
let sym = this.absentInstances.get(unionId);
|
|
2374
2642
|
if (!sym) {
|
|
2375
2643
|
sym = `sc_absent_${this.absentInstances.size}`;
|
|
@@ -2382,7 +2650,18 @@ export class LlEmitter {
|
|
|
2382
2650
|
/** i1: a union value loaded from a field slot is the ABSENT state — the
|
|
2383
2651
|
* undefined arm's tag with the absent payload marker. */
|
|
2384
2652
|
fieldAbsentTest(value, unionId) {
|
|
2385
|
-
return
|
|
2653
|
+
return this.fieldAbsentTestIn(this.B, value, unionId);
|
|
2654
|
+
}
|
|
2655
|
+
/** fieldAbsentTest in another block builder (helper generators). A
|
|
2656
|
+
* nullable union's ABSENT state is the module's immortal sentinel. */
|
|
2657
|
+
fieldAbsentTestIn(B, value, unionId) {
|
|
2658
|
+
if (this.nullableUnions.has(unionId)) {
|
|
2659
|
+
this.needsNullableAbsent = true;
|
|
2660
|
+
const t = B.tmp();
|
|
2661
|
+
B.line(`${t} = icmp eq ptr ${value}, ${NULLABLE_ABSENT}`);
|
|
2662
|
+
return t;
|
|
2663
|
+
}
|
|
2664
|
+
return emitFieldAbsentTest(B, value, undefinedArmTag({ kind: "union", unionId }, this.unionsById));
|
|
2386
2665
|
}
|
|
2387
2666
|
/** Loads a record field slot as a VALUE: an undefined-armed union slot
|
|
2388
2667
|
* holding the ABSENT state surfaces as the ordinary undefined arm, so
|
|
@@ -2410,9 +2689,20 @@ export class LlEmitter {
|
|
|
2410
2689
|
throw new InternalCompilerError("llvm emitter bug: no active statement frame");
|
|
2411
2690
|
return frame;
|
|
2412
2691
|
}
|
|
2692
|
+
/** Whether a value is immortal: an interned constant, or a temp marked by
|
|
2693
|
+
* markImmortal. */
|
|
2694
|
+
isImmortal(name) {
|
|
2695
|
+
return this.immortalValues.has(name) || this.B.immortalTemps.has(name);
|
|
2696
|
+
}
|
|
2697
|
+
/** Record that a temp holds an immortal value (a choice between interned
|
|
2698
|
+
* literals): nothing owns it, and retains, releases and moves skip it. */
|
|
2699
|
+
markImmortal(v) {
|
|
2700
|
+
this.B.immortalTemps.add(v.name);
|
|
2701
|
+
return v;
|
|
2702
|
+
}
|
|
2413
2703
|
/** Registers an owned refcounted value on the current statement frame. */
|
|
2414
2704
|
own(v) {
|
|
2415
|
-
if (isRefCounted(v.type) && !this.
|
|
2705
|
+
if (isRefCounted(v.type) && !this.isImmortal(v.name))
|
|
2416
2706
|
this.currentFrame().push(v);
|
|
2417
2707
|
return v;
|
|
2418
2708
|
}
|
|
@@ -2424,7 +2714,7 @@ export class LlEmitter {
|
|
|
2424
2714
|
}
|
|
2425
2715
|
/** Strike a refcounted temp from its frame: ownership is being moved. */
|
|
2426
2716
|
moveTemp(v) {
|
|
2427
|
-
if (!isRefCounted(v.type) || this.
|
|
2717
|
+
if (!isRefCounted(v.type) || this.isImmortal(v.name))
|
|
2428
2718
|
return;
|
|
2429
2719
|
for (let i = this.frames.length - 1; i >= 0; i--) {
|
|
2430
2720
|
const idx = this.frames[i].findIndex((e) => e.name === v.name);
|
|
@@ -2438,7 +2728,7 @@ export class LlEmitter {
|
|
|
2438
2728
|
/** The retained (+1) read of a refcounted value — type-directed through
|
|
2439
2729
|
* the `_v` table (immortals skip, exactly the C retain calls). */
|
|
2440
2730
|
retainValue(name, type) {
|
|
2441
|
-
if (this.
|
|
2731
|
+
if (this.isImmortal(name))
|
|
2442
2732
|
return name;
|
|
2443
2733
|
const t = this.B.tmp();
|
|
2444
2734
|
this.B.line(`${t} = call ptr ${retainSym(this.shapeHost, type)}(ptr ${name})`);
|
|
@@ -2447,7 +2737,7 @@ export class LlEmitter {
|
|
|
2447
2737
|
/** The release call for one owned refcounted value — type-directed like
|
|
2448
2738
|
* releaseCallC (all runtime releases are NULL-tolerant). */
|
|
2449
2739
|
releaseValue(name, type) {
|
|
2450
|
-
if (this.
|
|
2740
|
+
if (this.isImmortal(name))
|
|
2451
2741
|
return;
|
|
2452
2742
|
this.B.line(`call void ${releaseSym(this.shapeHost, type)}(ptr ${name})`);
|
|
2453
2743
|
}
|
|
@@ -2472,7 +2762,21 @@ export class LlEmitter {
|
|
|
2472
2762
|
for (const e of scope) {
|
|
2473
2763
|
const t = this.B.tmp();
|
|
2474
2764
|
this.B.line(`${t} = load ptr, ptr ${e.slot}`);
|
|
2475
|
-
if (e.boxed) {
|
|
2765
|
+
if (e.boxed && e.lazy) {
|
|
2766
|
+
// Unreachable code after a terminator emits nothing; a new block
|
|
2767
|
+
// here would revive it.
|
|
2768
|
+
if (this.B.isTerminated())
|
|
2769
|
+
continue;
|
|
2770
|
+
const created = this.B.tmp();
|
|
2771
|
+
this.B.line(`${created} = icmp ne ptr ${t}, null`);
|
|
2772
|
+
const release = this.B.newLabel("lazy.release"), join = this.B.newLabel("lazy.release.j");
|
|
2773
|
+
this.B.condBr(created, release, join);
|
|
2774
|
+
this.B.startBlock(release);
|
|
2775
|
+
this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
|
|
2776
|
+
this.B.br(join);
|
|
2777
|
+
this.B.startBlock(join);
|
|
2778
|
+
}
|
|
2779
|
+
else if (e.boxed) {
|
|
2476
2780
|
this.B.line(`call void ${boxReleaseSym(this.shapeHost)}(ptr ${t})`);
|
|
2477
2781
|
}
|
|
2478
2782
|
else {
|
|
@@ -2523,8 +2827,11 @@ export class LlEmitter {
|
|
|
2523
2827
|
* function — and branch to the handler / return a dummy value (never
|
|
2524
2828
|
* read: callers of a may-throw function test the pending flag before
|
|
2525
2829
|
* using the result). Callers own the surrounding pending branch; a
|
|
2526
|
-
* `throw` unwinds unconditionally.
|
|
2527
|
-
|
|
2830
|
+
* `throw` unwinds unconditionally. `passThrough` names a scalar call
|
|
2831
|
+
* result of the function's own return type: with nothing to release, the
|
|
2832
|
+
* unwind returns it as the dummy, so a call in tail position keeps a single
|
|
2833
|
+
* return and stays eligible for tail-call and recursion elimination. */
|
|
2834
|
+
emitUnwind(passThrough) {
|
|
2528
2835
|
if (this.mod.workers && this.tryStack.length > 0) {
|
|
2529
2836
|
this.declare(`declare zeroext i1 @scr_context_stopping()`);
|
|
2530
2837
|
const stopped = this.B.tmp();
|
|
@@ -2574,6 +2881,11 @@ export class LlEmitter {
|
|
|
2574
2881
|
}
|
|
2575
2882
|
}
|
|
2576
2883
|
if (entries.length === 0) {
|
|
2884
|
+
if (passThrough !== undefined && !target && this.currentWasiCoro === null) {
|
|
2885
|
+
const t = this.llType(this.currentReturnType);
|
|
2886
|
+
if (t === "double" || t === "i1")
|
|
2887
|
+
terminator = `ret ${t} ${passThrough}`;
|
|
2888
|
+
}
|
|
2577
2889
|
this.B.terminate(terminator);
|
|
2578
2890
|
return;
|
|
2579
2891
|
}
|
|
@@ -2584,24 +2896,147 @@ export class LlEmitter {
|
|
|
2584
2896
|
}
|
|
2585
2897
|
this.B.br(cleanup.label);
|
|
2586
2898
|
}
|
|
2899
|
+
/** Instructions (two-space indented) that set the i1 `dest` to "an
|
|
2900
|
+
* exception is pending in the active context" — scr_exc_pending()'s
|
|
2901
|
+
* meaning — for the helper and adapter bodies that test it outside
|
|
2902
|
+
* emitPendingCheck. Executables read the runtime's exported active-cell
|
|
2903
|
+
* pointer and compare its i32 kind (offset 0) against SCR_EXC_NONE inline,
|
|
2904
|
+
* as emitPendingCheck does; the out-of-line call would be opaque to LLVM
|
|
2905
|
+
* because the runtime links as prebuilt objects. Libraries and worker
|
|
2906
|
+
* programs keep the runtime call, which is emitPendingCheck's path there
|
|
2907
|
+
* too (workers also observe cross-thread termination in it). */
|
|
2908
|
+
pendingTestLines(dest) {
|
|
2909
|
+
if (this.mod.lib !== undefined || this.mod.workers === true) {
|
|
2910
|
+
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
2911
|
+
return [` ${dest} = call zeroext i1 @scr_exc_pending()`];
|
|
2912
|
+
}
|
|
2913
|
+
this.declare(`@scr_exc_active = external global ptr`);
|
|
2914
|
+
return [
|
|
2915
|
+
` ${dest}.cell = load ptr, ptr @scr_exc_active`,
|
|
2916
|
+
` ${dest}.kind = load i32, ptr ${dest}.cell`,
|
|
2917
|
+
` ${dest} = icmp ne i32 ${dest}.kind, 0`,
|
|
2918
|
+
];
|
|
2919
|
+
}
|
|
2587
2920
|
/** The emitter contract for exceptions: after EVERY call that can throw
|
|
2588
2921
|
* (per the may-throw analysis), test the pending flag and unwind. The
|
|
2589
2922
|
* call's result temp must join its frame BEFORE this runs so the unwind
|
|
2590
|
-
* releases the dummy (NULL for refcounted kinds) harmlessly.
|
|
2591
|
-
|
|
2923
|
+
* releases the dummy (NULL for refcounted kinds) harmlessly. A scalar
|
|
2924
|
+
* call result of the function's return type may be passed as the unwind's
|
|
2925
|
+
* dummy value (see emitUnwind). */
|
|
2926
|
+
emitPendingCheck(passThrough) {
|
|
2927
|
+
const through = passThrough !== undefined &&
|
|
2928
|
+
this.llType(passThrough.type) === this.llType(this.currentReturnType) &&
|
|
2929
|
+
(passThrough.type.kind === "f64" || passThrough.type.kind === "bool")
|
|
2930
|
+
? passThrough.name
|
|
2931
|
+
: undefined;
|
|
2592
2932
|
const B = this.B;
|
|
2593
2933
|
if (B.isTerminated())
|
|
2594
2934
|
return;
|
|
2595
|
-
this.
|
|
2596
|
-
|
|
2597
|
-
|
|
2935
|
+
if (this.mod.lib !== undefined) {
|
|
2936
|
+
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
2937
|
+
const p = B.tmp();
|
|
2938
|
+
B.line(`${p} = call zeroext i1 @scr_exc_pending()`);
|
|
2939
|
+
const lu = B.newLabel("exc.u");
|
|
2940
|
+
const lk = B.newLabel("exc.k");
|
|
2941
|
+
B.condBr(p, lu, lk);
|
|
2942
|
+
B.startBlock(lu);
|
|
2943
|
+
this.emitUnwind(through);
|
|
2944
|
+
B.startBlock(lk);
|
|
2945
|
+
return;
|
|
2946
|
+
}
|
|
2947
|
+
// Executables test the active exception cell's kind inline (its first
|
|
2948
|
+
// field). Worker programs test the process-wide alert word instead: it
|
|
2949
|
+
// is nonzero while any script thread may have an exception pending in
|
|
2950
|
+
// its active cell or has been asked to stop, so the common case is one
|
|
2951
|
+
// ordinary global load (a thread-local access is a call on Darwin) and
|
|
2952
|
+
// the out-of-line scr_exc_pending answers for this thread, observing
|
|
2953
|
+
// cancellation and reinstalling the termination sentinel.
|
|
2954
|
+
this.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
2955
|
+
let word;
|
|
2956
|
+
if (this.mod.workers === true) {
|
|
2957
|
+
this.declare(`@scr_exc_alert = external global i32, align 64`);
|
|
2958
|
+
word = B.tmp();
|
|
2959
|
+
B.line(`${word} = load atomic i32, ptr @scr_exc_alert monotonic, align 64`);
|
|
2960
|
+
}
|
|
2961
|
+
else {
|
|
2962
|
+
this.declare(`@scr_exc_active = external global ptr`);
|
|
2963
|
+
let cell;
|
|
2964
|
+
if (B.excCellInvariant) {
|
|
2965
|
+
// Fiber switches restore the active cell before control returns to a
|
|
2966
|
+
// synchronous frame, so its pointer is one value for the whole call:
|
|
2967
|
+
// load it once in the entry block and test only the kind here.
|
|
2968
|
+
if (B.excCell === null) {
|
|
2969
|
+
B.excCell = "%exc.cell";
|
|
2970
|
+
B.entryAllocas.push(`${B.excCell} = load ptr, ptr @scr_exc_active`);
|
|
2971
|
+
}
|
|
2972
|
+
cell = B.excCell;
|
|
2973
|
+
}
|
|
2974
|
+
else {
|
|
2975
|
+
cell = B.tmp();
|
|
2976
|
+
B.line(`${cell} = load ptr, ptr @scr_exc_active`);
|
|
2977
|
+
}
|
|
2978
|
+
word = B.tmp();
|
|
2979
|
+
B.line(`${word} = load i32, ptr ${cell}`);
|
|
2980
|
+
}
|
|
2981
|
+
const hit = B.tmp();
|
|
2982
|
+
const cold = B.tmp();
|
|
2983
|
+
B.line(`${hit} = icmp ne i32 ${word}, 0`);
|
|
2984
|
+
B.line(`${cold} = call i1 @llvm.expect.i1(i1 ${hit}, i1 false)`);
|
|
2598
2985
|
const lu = B.newLabel("exc.u");
|
|
2599
2986
|
const lk = B.newLabel("exc.k");
|
|
2600
|
-
|
|
2987
|
+
if (this.mod.workers === true) {
|
|
2988
|
+
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
2989
|
+
const slow = B.newLabel("exc.s");
|
|
2990
|
+
B.condBr(cold, slow, lk);
|
|
2991
|
+
B.startBlock(slow);
|
|
2992
|
+
const p = B.tmp();
|
|
2993
|
+
B.line(`${p} = call zeroext i1 @scr_exc_pending()`);
|
|
2994
|
+
B.condBr(p, lu, lk);
|
|
2995
|
+
}
|
|
2996
|
+
else {
|
|
2997
|
+
B.condBr(cold, lu, lk);
|
|
2998
|
+
}
|
|
2601
2999
|
B.startBlock(lu);
|
|
2602
|
-
this.emitUnwind();
|
|
3000
|
+
this.emitUnwind(through);
|
|
2603
3001
|
B.startBlock(lk);
|
|
2604
3002
|
}
|
|
3003
|
+
/** After a runtime call that ALWAYS leaves an exception pending (the
|
|
3004
|
+
* compiler-resolved Node throws): the pending test would always answer
|
|
3005
|
+
* true, so unwind unconditionally. Emission continues in a block with no
|
|
3006
|
+
* predecessors so the caller's remaining instructions stay well-formed;
|
|
3007
|
+
* LLVM deletes it. */
|
|
3008
|
+
emitAlwaysPendingUnwind() {
|
|
3009
|
+
const B = this.B;
|
|
3010
|
+
if (B.isTerminated())
|
|
3011
|
+
return;
|
|
3012
|
+
this.emitUnwind();
|
|
3013
|
+
B.startBlock(B.newLabel("exc.dead"));
|
|
3014
|
+
}
|
|
3015
|
+
/** A recursive function's entry check: below the running stack's guard,
|
|
3016
|
+
* throw Node's RangeError and unwind like any other pending exception. */
|
|
3017
|
+
emitStackCheck() {
|
|
3018
|
+
const B = this.B;
|
|
3019
|
+
const tl = this.mod.workers === true ? "thread_local " : "";
|
|
3020
|
+
this.declare(`@scr_stack_guard = external ${tl}global ptr`);
|
|
3021
|
+
this.declare(`declare ptr @llvm.stacksave.p0()`);
|
|
3022
|
+
this.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
3023
|
+
this.declare(`declare void @scr_stack_overflow()`);
|
|
3024
|
+
const sp = B.tmp();
|
|
3025
|
+
const guard = B.tmp();
|
|
3026
|
+
const low = B.tmp();
|
|
3027
|
+
const cold = B.tmp();
|
|
3028
|
+
B.line(`${sp} = call ptr @llvm.stacksave.p0()`);
|
|
3029
|
+
B.line(`${guard} = load ptr, ptr @scr_stack_guard`);
|
|
3030
|
+
B.line(`${low} = icmp ult ptr ${sp}, ${guard}`);
|
|
3031
|
+
B.line(`${cold} = call i1 @llvm.expect.i1(i1 ${low}, i1 false)`);
|
|
3032
|
+
const over = B.newLabel("stack.over");
|
|
3033
|
+
const ok = B.newLabel("stack.ok");
|
|
3034
|
+
B.condBr(cold, over, ok);
|
|
3035
|
+
B.startBlock(over);
|
|
3036
|
+
B.line(`call void @scr_stack_overflow()`);
|
|
3037
|
+
this.emitUnwind();
|
|
3038
|
+
B.startBlock(ok);
|
|
3039
|
+
}
|
|
2605
3040
|
workerLoopBudget() {
|
|
2606
3041
|
if (!this.mod.workers)
|
|
2607
3042
|
return null;
|
|
@@ -2699,6 +3134,11 @@ export class LlEmitter {
|
|
|
2699
3134
|
const def = this.unionsById.get(v.type.unionId);
|
|
2700
3135
|
if (!def)
|
|
2701
3136
|
throw new InternalCompilerError(`llvm emitter bug: truthiness of unknown union ${v.type.unionId}`);
|
|
3137
|
+
const nullable = this.nullableUnions.get(def.id);
|
|
3138
|
+
// Object arms are always truthy: present iff neither unit encoding.
|
|
3139
|
+
if (nullable && isObjectArm(nullable))
|
|
3140
|
+
return emitNullablePresent(B, nullable, v.name);
|
|
3141
|
+
const unionId = def.id;
|
|
2702
3142
|
const slot = B.slot();
|
|
2703
3143
|
B.entryAllocas.push(`${slot} = alloca i1`);
|
|
2704
3144
|
const join = B.newLabel("ut.j");
|
|
@@ -2711,10 +3151,10 @@ export class LlEmitter {
|
|
|
2711
3151
|
valueName = this.unionGetBool(v.name);
|
|
2712
3152
|
}
|
|
2713
3153
|
else if (arm.kind === "string") {
|
|
2714
|
-
valueName = this.unionPeek(v.name);
|
|
3154
|
+
valueName = this.unionPeek(v.name, unionId);
|
|
2715
3155
|
}
|
|
2716
3156
|
else if (arm.kind === "bigint" || arm.kind === "dyn" || arm.kind === "jsval") {
|
|
2717
|
-
valueName = this.unionPeek(v.name);
|
|
3157
|
+
valueName = this.unionPeek(v.name, unionId);
|
|
2718
3158
|
}
|
|
2719
3159
|
const truthy = this.truthyOf(arm.kind, valueName, true);
|
|
2720
3160
|
B.line(`store i1 ${truthy}, ptr ${slot}`);
|
|
@@ -2820,22 +3260,22 @@ export class LlEmitter {
|
|
|
2820
3260
|
}
|
|
2821
3261
|
}
|
|
2822
3262
|
// ── union plumbing ──────────────────────────────────────────────────────
|
|
2823
|
-
/**
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
3263
|
+
/** A union value's tag (i32): the box's tag word, or the NULL test of a
|
|
3264
|
+
* nullable union's pointer. */
|
|
3265
|
+
unionTag(uName, unionId) {
|
|
3266
|
+
return emitUnionTag(this.B, this.nullableUnions.get(unionId), uName);
|
|
3267
|
+
}
|
|
3268
|
+
/** Guard for helpers that build or read a union's `%ScrUnion` box
|
|
3269
|
+
* directly (library-shaped unions with fixed arms): a nullable union
|
|
3270
|
+
* reaching one is an emitter bug, never a silent miscompile. */
|
|
3271
|
+
requireBoxedUnion(unionId) {
|
|
3272
|
+
if (this.nullableUnions.has(unionId))
|
|
3273
|
+
throw new InternalCompilerError(`llvm emitter bug: nullable union ${unionId} reached a tagged-box helper`);
|
|
2830
3274
|
}
|
|
2831
3275
|
/** 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;
|
|
3276
|
+
* the runtime's is a static inline); a nullable union is its payload. */
|
|
3277
|
+
unionPeek(uName, unionId) {
|
|
3278
|
+
return emitUnionPeek(this.B, this.nullableUnions.get(unionId), uName);
|
|
2839
3279
|
}
|
|
2840
3280
|
/** Emits `switch` over a union's tag with one block per arm; each arm
|
|
2841
3281
|
* body must TERMINATE its block (the callers branch to a join). The
|
|
@@ -2843,7 +3283,7 @@ export class LlEmitter {
|
|
|
2843
3283
|
* reads can group equivalent storage prefixes and emit one representative. */
|
|
2844
3284
|
unionTagSwitch(uName, def, arm, fieldGroups) {
|
|
2845
3285
|
const B = this.B;
|
|
2846
|
-
const tag = this.unionTag(uName);
|
|
3286
|
+
const tag = this.unionTag(uName, def.id);
|
|
2847
3287
|
const bad = B.newLabel("u.bad");
|
|
2848
3288
|
const labels = [];
|
|
2849
3289
|
if (fieldGroups) {
|
|
@@ -2889,12 +3329,12 @@ export class LlEmitter {
|
|
|
2889
3329
|
/** The +1 extraction of a union's single narrowed arm (unionNarrow /
|
|
2890
3330
|
* the nullish-family reads): scalars via inline payload loads, ref arms
|
|
2891
3331
|
* a retained peek. */
|
|
2892
|
-
unionExtract(uName, arm) {
|
|
3332
|
+
unionExtract(uName, unionId, arm) {
|
|
2893
3333
|
if (arm.kind === "f64" || arm.kind === "procStream")
|
|
2894
3334
|
return this.unionGetF64(uName);
|
|
2895
3335
|
if (arm.kind === "bool")
|
|
2896
3336
|
return this.unionGetBool(uName);
|
|
2897
|
-
return this.retainValue(this.unionPeek(uName), arm);
|
|
3337
|
+
return this.retainValue(this.unionPeek(uName, unionId), arm);
|
|
2898
3338
|
}
|
|
2899
3339
|
/** A scalar f64 arm's payload (scr_union_get_f64 inlined). Inline loads
|
|
2900
3340
|
* keep stack boxes free of escaping calls, so LLVM can scalarize them. */
|
|
@@ -2918,8 +3358,15 @@ export class LlEmitter {
|
|
|
2918
3358
|
/** Constructs a union box around an OWNED (+1, already moved) value —
|
|
2919
3359
|
* the scr_union_new_* dispatch of unionWrap and the wrap-into-join
|
|
2920
3360
|
* sites (shift). */
|
|
2921
|
-
unionNewOwned(tag, v) {
|
|
3361
|
+
unionNewOwned(unionId, tag, v) {
|
|
2922
3362
|
const B = this.B;
|
|
3363
|
+
const nullable = this.nullableUnions.get(unionId);
|
|
3364
|
+
if (nullable) {
|
|
3365
|
+
// The owned payload IS the nullable union's value.
|
|
3366
|
+
if (tag !== nullable.refTag)
|
|
3367
|
+
throw new InternalCompilerError(`llvm emitter bug: payload wrap into unit arm ${tag} of ${unionId}`);
|
|
3368
|
+
return v.name;
|
|
3369
|
+
}
|
|
2923
3370
|
const t = B.tmp();
|
|
2924
3371
|
if (v.type.kind === "f64" || v.type.kind === "procStream") {
|
|
2925
3372
|
this.declare(`declare ptr @scr_union_new_f64(i32, double)`);
|
|
@@ -3062,6 +3509,42 @@ export class LlEmitter {
|
|
|
3062
3509
|
B.line(`${z} = zext i1 ${value} to i8`);
|
|
3063
3510
|
B.line(`store i8 ${z}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
|
|
3064
3511
|
}
|
|
3512
|
+
/** Reads an int32-specialized class field: the i32 slot is the value,
|
|
3513
|
+
* and its widening is the exact double every program observation sees. */
|
|
3514
|
+
loadInt32Field(ptr, type) {
|
|
3515
|
+
const B = this.B;
|
|
3516
|
+
const raw = B.tmp();
|
|
3517
|
+
const number = B.tmp();
|
|
3518
|
+
B.line(`${raw} = load i32, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
|
|
3519
|
+
B.line(`${number} = sitofp i32 ${raw} to double`);
|
|
3520
|
+
return {
|
|
3521
|
+
name: number,
|
|
3522
|
+
type,
|
|
3523
|
+
uint32: raw,
|
|
3524
|
+
integer: { name: raw, type: "i32", signed: true, range: INT32_RANGE },
|
|
3525
|
+
};
|
|
3526
|
+
}
|
|
3527
|
+
/** Stores a value the whole-program analysis proved to be an int32 (never
|
|
3528
|
+
* -0, NaN or fractional) into an int32-specialized class field. */
|
|
3529
|
+
storeInt32Field(ptr, value, expr) {
|
|
3530
|
+
if (!this.int32Slots.provenWrites.has(expr))
|
|
3531
|
+
throw new InternalCompilerError("llvm emitter bug: unproven int32 field write");
|
|
3532
|
+
const B = this.B;
|
|
3533
|
+
let bits;
|
|
3534
|
+
if (value.integer?.type === "i32")
|
|
3535
|
+
bits = value.integer.name;
|
|
3536
|
+
else if (value.integer) {
|
|
3537
|
+
bits = B.tmp();
|
|
3538
|
+
B.line(`${bits} = trunc i64 ${value.integer.name} to i32`);
|
|
3539
|
+
}
|
|
3540
|
+
else if (value.uint32 !== undefined)
|
|
3541
|
+
bits = value.uint32;
|
|
3542
|
+
else {
|
|
3543
|
+
bits = B.tmp();
|
|
3544
|
+
B.line(`${bits} = fptosi double ${value.name} to i32`);
|
|
3545
|
+
}
|
|
3546
|
+
B.line(`store i32 ${bits}, ptr ${ptr}${this.fieldAliasAttachment(ptr)}`);
|
|
3547
|
+
}
|
|
3065
3548
|
// ── bindings ────────────────────────────────────────────────────────────
|
|
3066
3549
|
/** A binding's storage: a module global, a plain local slot, or a boxed
|
|
3067
3550
|
* local (the slot holds the capture BOX; access goes through it). */
|
|
@@ -3375,6 +3858,9 @@ export class LlEmitter {
|
|
|
3375
3858
|
emitFunction(fn) {
|
|
3376
3859
|
const B = new BlockBuilder();
|
|
3377
3860
|
this.B = B;
|
|
3861
|
+
// Fiber switches restore the active exception cell before control
|
|
3862
|
+
// returns to a synchronous frame (emitPendingCheck).
|
|
3863
|
+
B.excCellInvariant = fn.async !== true && fn.generator === undefined;
|
|
3378
3864
|
this.debugScope = this.debug?.function(fn) ?? null;
|
|
3379
3865
|
B.debugLocation = this.debug?.location(fn.loc, this.debugScope) ?? null;
|
|
3380
3866
|
this.frames = [];
|
|
@@ -3407,6 +3893,7 @@ export class LlEmitter {
|
|
|
3407
3893
|
const numericFn = withInitializerBindings(fn, initializerBindings);
|
|
3408
3894
|
this.numericLocals = new Map(numericFn.locals.map((l) => [l.id, l]));
|
|
3409
3895
|
this.borrowedParameters.clear();
|
|
3896
|
+
this.reboundOwnerSlots.clear();
|
|
3410
3897
|
this.projectedParameters.clear();
|
|
3411
3898
|
const projectedParameterIndexes = this.callLifetimes.parameters.get(fn.name);
|
|
3412
3899
|
if (projectedParameterIndexes)
|
|
@@ -3415,8 +3902,12 @@ export class LlEmitter {
|
|
|
3415
3902
|
this.stableCallBindings = this.callLifetimes.bindings.get(fn.name) ?? new Set();
|
|
3416
3903
|
const borrowedParameterIndexes = this.callLifetimes.borrowed.get(fn.name);
|
|
3417
3904
|
if (borrowedParameterIndexes) {
|
|
3905
|
+
// Rebound walk parameters borrow too, but only unchanged ones may
|
|
3906
|
+
// serve as stable owners for call arguments and aliases.
|
|
3418
3907
|
for (const index of borrowedParameterIndexes)
|
|
3419
|
-
this.
|
|
3908
|
+
if (!this.walkBorrowsByFunction.get(fn.name)?.has(fn.params[index].localId) &&
|
|
3909
|
+
!this.reboundByFunction.get(fn.name)?.has(fn.params[index].localId))
|
|
3910
|
+
this.borrowedParameters.add(fn.params[index].localId);
|
|
3420
3911
|
}
|
|
3421
3912
|
this.captureIds = new Set([...(fn.captures ?? []), ...(fn.classCaptures ?? [])].map((c) => c.localId));
|
|
3422
3913
|
this.integerLoopBindings.clear();
|
|
@@ -3437,17 +3928,19 @@ export class LlEmitter {
|
|
|
3437
3928
|
this.callArrayReads = findCallArrayReads(fn, this.optionalArrayReads, this.referenceEffects.functions, this.callLifetimes);
|
|
3438
3929
|
this.scalarStringSlices =
|
|
3439
3930
|
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.
|
|
3931
|
+
this.mapReadLifetimes = findMapReadLifetimes(fn, this.boxedUnionsById, this.callLifetimes);
|
|
3932
|
+
this.localStackUnions = findLocalStackUnions(fn, this.callLifetimes, this.boxedUnionsById);
|
|
3933
|
+
this.localUnionStorageProofs = findLocalUnionStorage(fn, this.callLifetimes, this.boxedUnionsById, (e) => this.nullableFieldGet(e) !== null);
|
|
3443
3934
|
this.localUnionStorage = new Map();
|
|
3444
|
-
this.
|
|
3935
|
+
this.walkBorrows = this.walkBorrowsByFunction.get(fn.name) ?? new Set();
|
|
3936
|
+
this.integerRanges = analyzeIntegerRanges(numericFn, this.int32Slots.facts(fn.name));
|
|
3445
3937
|
this.bytesBounds = findBytesBounds(numericFn, this.integerRanges);
|
|
3446
3938
|
this.chainSlots.clear();
|
|
3447
3939
|
this.finallyStack = [];
|
|
3448
3940
|
this.tryStack = [];
|
|
3449
3941
|
this.currentReturnType = fn.returnType;
|
|
3450
3942
|
this.currentGenerator = fn.generator ?? null;
|
|
3943
|
+
this.currentBorrowedReturn = this.borrowedReturns.has(fn.name);
|
|
3451
3944
|
this.currentWasiCoro = null;
|
|
3452
3945
|
this.logArgSlots = 0;
|
|
3453
3946
|
if (this.wasi && (fn.async === true || fn.generator !== undefined)) {
|
|
@@ -3551,7 +4044,12 @@ export class LlEmitter {
|
|
|
3551
4044
|
const cache = B.slot();
|
|
3552
4045
|
B.entryAllocas.push(`${cache} = alloca ptr ; lazy box ${this.currentLocals.get(id).name}`);
|
|
3553
4046
|
B.line(`store ptr null, ptr ${cache}`);
|
|
3554
|
-
fnScope.push({
|
|
4047
|
+
fnScope.push({
|
|
4048
|
+
slot: cache,
|
|
4049
|
+
type: this.currentLocals.get(id).type,
|
|
4050
|
+
boxed: true,
|
|
4051
|
+
lazy: true,
|
|
4052
|
+
});
|
|
3555
4053
|
this.lazyCaptureBoxes.set(id, cache);
|
|
3556
4054
|
// Environments that finish during the call can share an immortal
|
|
3557
4055
|
// frame box. Its payload owner is released with the function scope.
|
|
@@ -3578,6 +4076,15 @@ export class LlEmitter {
|
|
|
3578
4076
|
this.storeIntegerView(p.localId, { name: `%p_${mangleLocal(p.localId)}`, type: p.type });
|
|
3579
4077
|
if (isRefCounted(p.type) && !borrowed?.has(index))
|
|
3580
4078
|
fnScope.push({ slot, type: p.type });
|
|
4079
|
+
if (this.reboundByFunction.get(fn.name)?.has(p.localId) && borrowed?.has(index)) {
|
|
4080
|
+
// The caller's borrow stays in the parameter slot; values the body
|
|
4081
|
+
// assigns are owned here until rebinding or exit releases them.
|
|
4082
|
+
const owner = `${slot}.owner`;
|
|
4083
|
+
B.entryAllocas.push(`${owner} = alloca ptr`);
|
|
4084
|
+
B.line(`store ptr null, ptr ${owner}`);
|
|
4085
|
+
fnScope.push({ slot: owner, type: p.type });
|
|
4086
|
+
this.reboundOwnerSlots.set(p.localId, owner);
|
|
4087
|
+
}
|
|
3581
4088
|
}
|
|
3582
4089
|
this.scopes.push(fnScope);
|
|
3583
4090
|
// Stackful native fibers retain these frames while suspended. The
|
|
@@ -3590,7 +4097,9 @@ export class LlEmitter {
|
|
|
3590
4097
|
B.line(`call void @scr_stack_enter(ptr %source_frame, ptr ${this.cstr(frame)})`);
|
|
3591
4098
|
B.returnEpilogue = `call void @scr_stack_leave(ptr %source_frame)`;
|
|
3592
4099
|
}
|
|
3593
|
-
if (this.
|
|
4100
|
+
if (this.stackChecks?.has(fn.name))
|
|
4101
|
+
this.emitStackCheck();
|
|
4102
|
+
if (this.workerEntryPolls?.has(fn.name))
|
|
3594
4103
|
this.emitPendingCheck();
|
|
3595
4104
|
this.emitStmts(fn.body);
|
|
3596
4105
|
// Implicit exit of a void function: release the function scope unless
|
|
@@ -3662,24 +4171,55 @@ export class LlEmitter {
|
|
|
3662
4171
|
const debug = this.debugScope === null ? "" : ` !dbg ${this.debugScope}`;
|
|
3663
4172
|
const symbol = borrowed ? mangleBorrowedFunction(fn.name) : mangleFunction(fn.name);
|
|
3664
4173
|
const body = `define internal ${ret} @${symbol}(${params.join(", ")}) ${attrs}${debug} { ; ${fn.name}\n${B.render()}\n}`;
|
|
3665
|
-
|
|
4174
|
+
if (!borrowed)
|
|
4175
|
+
return body;
|
|
4176
|
+
const slot = this.implSlotBorrowed(fn.name);
|
|
4177
|
+
const virtual = slot.size > 0 && slot.size !== borrowed.size
|
|
4178
|
+
? "\n" + this.emitOwnedCallAdapter(fn, borrowed, slot)
|
|
4179
|
+
: "";
|
|
4180
|
+
return body + "\n" + this.emitOwnedCallAdapter(fn, borrowed) + virtual;
|
|
3666
4181
|
}
|
|
3667
4182
|
/** Keep the ordinary owned ABI for closures, virtual dispatch, generated
|
|
3668
4183
|
* runtime adapters and library exports. Only direct IR calls select the
|
|
3669
4184
|
* borrowing body. Ownership of other parameters moves into that body;
|
|
3670
4185
|
* borrowed parameters are released here on normal and exceptional exits.
|
|
3671
4186
|
* The pending exception remains for the adapter's caller to handle. */
|
|
3672
|
-
|
|
4187
|
+
/** With `kept`, the virtual adapter of a borrowing slot instead: the
|
|
4188
|
+
* slot's parameters stay borrowed, the body's other borrowed ones are
|
|
4189
|
+
* released here. */
|
|
4190
|
+
emitOwnedCallAdapter(fn, borrowed, kept = NO_BORROWED) {
|
|
3673
4191
|
const B = new BlockBuilder();
|
|
3674
4192
|
this.B = B;
|
|
3675
4193
|
const params = fn.params.map((param, index) => `${this.llType(param.type)} %p${index}`);
|
|
3676
4194
|
const ret = this.llType(fn.returnType);
|
|
3677
4195
|
const call = `call ${ret} @${mangleBorrowedFunction(fn.name)}(${params.join(", ")})`;
|
|
3678
4196
|
B.line(ret === "void" ? call : `%result = ${call}`);
|
|
4197
|
+
// A borrowed result is reachable from the parameters: own it first. A
|
|
4198
|
+
// throwing body returns a null dummy, which needs no owner (and string
|
|
4199
|
+
// or array retains do not accept NULL).
|
|
4200
|
+
let result = "%result";
|
|
4201
|
+
if (this.borrowedReturns.has(fn.name)) {
|
|
4202
|
+
const slot = B.slot();
|
|
4203
|
+
B.entryAllocas.push(`${slot} = alloca ptr`);
|
|
4204
|
+
B.line(`store ptr null, ptr ${slot}`);
|
|
4205
|
+
const present = B.tmp();
|
|
4206
|
+
const own = B.newLabel("result.own");
|
|
4207
|
+
const done = B.newLabel("result.done");
|
|
4208
|
+
B.line(`${present} = icmp ne ptr %result, null`);
|
|
4209
|
+
B.condBr(present, own, done);
|
|
4210
|
+
B.startBlock(own);
|
|
4211
|
+
B.line(`store ptr ${this.retainValue("%result", fn.returnType)}, ptr ${slot}`);
|
|
4212
|
+
B.br(done);
|
|
4213
|
+
B.startBlock(done);
|
|
4214
|
+
result = B.tmp();
|
|
4215
|
+
B.line(`${result} = load ptr, ptr ${slot}`);
|
|
4216
|
+
}
|
|
3679
4217
|
for (const index of borrowed)
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
4218
|
+
if (!kept.has(index))
|
|
4219
|
+
this.releaseValue(`%p${index}`, fn.params[index].type);
|
|
4220
|
+
B.terminate(ret === "void" ? "ret void" : `ret ${ret} ${result}`);
|
|
4221
|
+
const symbol = kept.size > 0 ? `${mangleBorrowedFunction(fn.name)}.virtual` : mangleFunction(fn.name);
|
|
4222
|
+
return `define internal ${ret} @${symbol}(${params.join(", ")}) ${FN_ATTRS} {\n${B.render()}\n}`;
|
|
3683
4223
|
}
|
|
3684
4224
|
/** A local stable binding keeps its value alive while later arguments
|
|
3685
4225
|
* and the callee execute. A proven local union box cannot escape or be
|
|
@@ -3688,6 +4228,33 @@ export class LlEmitter {
|
|
|
3688
4228
|
canBorrowCallArgument(value) {
|
|
3689
4229
|
if (value.kind === "strLit")
|
|
3690
4230
|
return true;
|
|
4231
|
+
// Class casts reinterpret the same pointer (prefix layouts).
|
|
4232
|
+
if ((value.kind === "upcast" || value.kind === "downcast") &&
|
|
4233
|
+
value.value.type.kind === "object")
|
|
4234
|
+
return this.canBorrowCallArgument(value.value);
|
|
4235
|
+
if (value.kind === "seqExpr") {
|
|
4236
|
+
const guarded = guardedValue(value);
|
|
4237
|
+
return guarded !== null && this.canBorrowCallArgument(guarded);
|
|
4238
|
+
}
|
|
4239
|
+
// A nullable-union wrap reinterprets its payload pointer; an immortal
|
|
4240
|
+
// unit constant needs no owner at all.
|
|
4241
|
+
const wrap = this.borrowableNullableWrap(value);
|
|
4242
|
+
if (wrap === "unit")
|
|
4243
|
+
return true;
|
|
4244
|
+
if (wrap === "ref" && value.kind === "unionWrap")
|
|
4245
|
+
return this.canBorrowCallArgument(value.value);
|
|
4246
|
+
// Checked casts (`x as C`, `x!` on a nullable) lower to a ternary whose
|
|
4247
|
+
// failing arm always throws; the successful arm projects the same
|
|
4248
|
+
// stable owner. The throwing arm's typed dummy owns nothing.
|
|
4249
|
+
if (value.kind === "unionNarrow")
|
|
4250
|
+
return this.canBorrowCallArgument(value.value);
|
|
4251
|
+
if (value.kind === "libCall")
|
|
4252
|
+
return value.fn === "error.nodeThrow";
|
|
4253
|
+
if (value.kind === "ternary")
|
|
4254
|
+
return (this.canBorrowReceiver(value.then) &&
|
|
4255
|
+
this.canBorrowReceiver(value.else_) &&
|
|
4256
|
+
this.canBorrowCallArgument(value.then) &&
|
|
4257
|
+
this.canBorrowCallArgument(value.else_));
|
|
3691
4258
|
if (value.kind !== "varRef")
|
|
3692
4259
|
return false;
|
|
3693
4260
|
const binding = this.binding(value.localId);
|
|
@@ -3742,6 +4309,12 @@ export class LlEmitter {
|
|
|
3742
4309
|
switch (s.kind) {
|
|
3743
4310
|
case "varDecl": {
|
|
3744
4311
|
const b = this.binding(s.localId);
|
|
4312
|
+
if (this.walkBorrows.has(s.localId) && b.kind === "local") {
|
|
4313
|
+
// A borrowed walk pointer: no retain now, no release at scope exit.
|
|
4314
|
+
const v = s.init === null ? "null" : this.emitReadReceiver(s.init).name;
|
|
4315
|
+
B.line(`store ptr ${v}, ptr ${b.slot}`);
|
|
4316
|
+
break;
|
|
4317
|
+
}
|
|
3745
4318
|
const slice = this.scalarStringSlices.get(s.localId);
|
|
3746
4319
|
if (slice) {
|
|
3747
4320
|
const snapshot = emitStringSliceSnapshot(this, slice);
|
|
@@ -3874,6 +4447,23 @@ export class LlEmitter {
|
|
|
3874
4447
|
break;
|
|
3875
4448
|
}
|
|
3876
4449
|
case "assign": {
|
|
4450
|
+
const owner = this.reboundOwnerSlots.get(s.localId);
|
|
4451
|
+
if (owner !== undefined && !this.walkBorrows.has(s.localId)) {
|
|
4452
|
+
// A rebound borrowed parameter: the new value moves into the owner
|
|
4453
|
+
// slot, replacing (and releasing) the previous owned value. This
|
|
4454
|
+
// precedes the in-place string append, which would release the
|
|
4455
|
+
// caller's borrow.
|
|
4456
|
+
const param = this.binding(s.localId);
|
|
4457
|
+
const slot = param.slot;
|
|
4458
|
+
const v = this.emitExpr(s.value);
|
|
4459
|
+
this.moveTemp(v);
|
|
4460
|
+
const old = B.tmp();
|
|
4461
|
+
B.line(`${old} = load ptr, ptr ${owner}`);
|
|
4462
|
+
this.releaseValue(old, param.type);
|
|
4463
|
+
B.line(`store ptr ${v.name}, ptr ${slot}`);
|
|
4464
|
+
B.line(`store ptr ${v.name}, ptr ${owner}`);
|
|
4465
|
+
break;
|
|
4466
|
+
}
|
|
3877
4467
|
const localStorage = this.localUnionStorage.get(s.localId);
|
|
3878
4468
|
if (localStorage) {
|
|
3879
4469
|
storeLocalUnion(this, localStorage, s.value);
|
|
@@ -3886,6 +4476,11 @@ export class LlEmitter {
|
|
|
3886
4476
|
break;
|
|
3887
4477
|
}
|
|
3888
4478
|
const b = this.binding(s.localId);
|
|
4479
|
+
if (this.walkBorrows.has(s.localId) && b.kind === "local") {
|
|
4480
|
+
// Rebinding a borrowed walk pointer releases nothing.
|
|
4481
|
+
B.line(`store ptr ${this.emitReadReceiver(s.value).name}, ptr ${b.slot}`);
|
|
4482
|
+
break;
|
|
4483
|
+
}
|
|
3889
4484
|
const v = this.emitExpr(s.value);
|
|
3890
4485
|
if (b.kind === "global" && !s.initializes)
|
|
3891
4486
|
this.checkGlobalTdz(s.localId);
|
|
@@ -3907,7 +4502,9 @@ export class LlEmitter {
|
|
|
3907
4502
|
break;
|
|
3908
4503
|
}
|
|
3909
4504
|
case "exprStmt":
|
|
3910
|
-
|
|
4505
|
+
// A statement-position splice never observes its removed elements.
|
|
4506
|
+
if (!emitDiscardedSplice(this, s.expr))
|
|
4507
|
+
this.emitDiscarded(s.expr);
|
|
3911
4508
|
break;
|
|
3912
4509
|
case "arraySet": {
|
|
3913
4510
|
// Evaluation order matches JS: array, index, then value. Ownership
|
|
@@ -3919,19 +4516,43 @@ export class LlEmitter {
|
|
|
3919
4516
|
if (s.arr.type.kind !== "array")
|
|
3920
4517
|
throw new InternalCompilerError("llvm emitter bug: arraySet on non-array");
|
|
3921
4518
|
const acc = elemAccess(s.arr.type.elem);
|
|
4519
|
+
this.emitPublishedArrayGuard(arr.name, s.arr.type, 0, idx.name);
|
|
3922
4520
|
if (acc === "ref")
|
|
3923
4521
|
this.moveTemp(v);
|
|
3924
4522
|
emitDenseArraySet(this, arr.name, idx, s.index, acc, s.arr.type.elem, v.name);
|
|
3925
4523
|
break;
|
|
3926
4524
|
}
|
|
3927
4525
|
case "arraySetLength": {
|
|
4526
|
+
// Pop's `length = 0` store on an empty array only matters when the
|
|
4527
|
+
// array can be frozen (published); its operand is a local.
|
|
4528
|
+
if (s.pop === true &&
|
|
4529
|
+
s.length.kind === "numLit" &&
|
|
4530
|
+
s.length.value === 0 &&
|
|
4531
|
+
this.publishedTypes?.containerGuarded(s.arr.type) !== true)
|
|
4532
|
+
break;
|
|
3928
4533
|
const arr = emitBorrowedInput(this, s.arr);
|
|
3929
4534
|
const length = this.emitExpr(s.length);
|
|
3930
4535
|
if (s.arr.type.kind !== "array")
|
|
3931
4536
|
throw new InternalCompilerError("llvm emitter bug: arraySetLength on non-array");
|
|
4537
|
+
this.emitPublishedArrayGuard(arr.name, s.arr.type, s.pop === true ? 2 : 3);
|
|
3932
4538
|
this.declare(`declare void @scr_arr_set_len(ptr, double)`);
|
|
4539
|
+
// Assigning the current length (a reused work array reset to where
|
|
4540
|
+
// it already is) changes nothing: the runtime's validation accepts
|
|
4541
|
+
// exactly the value equal to the length (-0 included, NaN never),
|
|
4542
|
+
// and no element is released. Everything else keeps the call.
|
|
4543
|
+
const lenPtr = B.tmp(), len = B.tmp(), lenNumber = B.tmp(), same = B.tmp();
|
|
4544
|
+
const resize = B.newLabel("arr.len.set"), done = B.newLabel("arr.len.done");
|
|
4545
|
+
B.line(`${lenPtr} = getelementptr inbounds %ScrArr, ptr ${arr.name}, i32 0, i32 1`);
|
|
4546
|
+
this.markMemoryPointer(lenPtr, "array:header");
|
|
4547
|
+
B.line(`${len} = load ${this.sizeType}, ptr ${lenPtr}${this.fieldAliasAttachment(lenPtr)}`);
|
|
4548
|
+
B.line(`${lenNumber} = uitofp ${this.sizeType} ${len} to double`);
|
|
4549
|
+
B.line(`${same} = fcmp oeq double ${length.name}, ${lenNumber}`);
|
|
4550
|
+
B.condBr(same, done, resize);
|
|
4551
|
+
B.startBlock(resize);
|
|
3933
4552
|
B.line(`call void @scr_arr_set_len(ptr ${arr.name}, double ${length.name})`);
|
|
3934
4553
|
this.emitPendingCheck();
|
|
4554
|
+
B.br(done);
|
|
4555
|
+
B.startBlock(done);
|
|
3935
4556
|
break;
|
|
3936
4557
|
}
|
|
3937
4558
|
case "arraySetUndefined":
|
|
@@ -3940,6 +4561,7 @@ export class LlEmitter {
|
|
|
3940
4561
|
const idx = this.emitExpr(s.index);
|
|
3941
4562
|
if (s.arr.type.kind !== "array")
|
|
3942
4563
|
throw new InternalCompilerError(`llvm emitter bug: ${s.kind} on non-array`);
|
|
4564
|
+
this.emitPublishedArrayGuard(arr.name, s.arr.type, s.kind === "arrayDelete" ? 5 : 0, idx.name);
|
|
3943
4565
|
const fn = s.kind === "arraySetUndefined" ? "scr_arr_set_undefined" : "scr_arr_delete";
|
|
3944
4566
|
this.declare(`declare ${s.kind === "arrayDelete" ? "zeroext i1" : "void"} @${fn}(ptr, double)`);
|
|
3945
4567
|
if (s.kind === "arrayDelete")
|
|
@@ -3971,14 +4593,27 @@ export class LlEmitter {
|
|
|
3971
4593
|
// Classes and records share the struct layout, so one emission.
|
|
3972
4594
|
const nullable = s.kind === "fieldSet" ? this.nullableFields.get(s.className, s.field) : null;
|
|
3973
4595
|
if (nullable && s.kind === "fieldSet") {
|
|
3974
|
-
const obj = this.
|
|
4596
|
+
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
4597
|
+
// (The guard precedes the value here: the nullable store
|
|
4598
|
+
// evaluates it itself.)
|
|
4599
|
+
this.emitPublishedFieldGuard(obj.name, s.className, s.field);
|
|
3975
4600
|
this.emitNullableFieldStore(obj.name, s.className, s.field, nullable, s.value);
|
|
3976
4601
|
break;
|
|
3977
4602
|
}
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
4603
|
+
if (s.kind === "fieldSet" && this.int32Slots.isField(s.className, s.field)) {
|
|
4604
|
+
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
4605
|
+
const v = this.emitExpr(s.value);
|
|
4606
|
+
this.emitPublishedFieldGuard(obj.name, s.className, s.field);
|
|
4607
|
+
const { ptr } = this.classFieldPtr(obj.name, s.className, s.field);
|
|
4608
|
+
this.storeInt32Field(ptr, v, s.value);
|
|
4609
|
+
break;
|
|
4610
|
+
}
|
|
4611
|
+
const obj = this.emitStableReceiver(s.obj, [s.value]);
|
|
3981
4612
|
const v = this.emitExpr(s.value);
|
|
4613
|
+
if (s.kind === "fieldSet")
|
|
4614
|
+
this.emitPublishedFieldGuard(obj.name, s.className, s.field);
|
|
4615
|
+
else if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
|
|
4616
|
+
this.emitPublishedFieldGuard(obj.name, null, s.field);
|
|
3982
4617
|
const { ptr, type } = s.kind === "fieldSet"
|
|
3983
4618
|
? this.classFieldPtr(obj.name, s.className, s.field)
|
|
3984
4619
|
: this.recordFieldPtr(obj.name, s.shapeId, s.field);
|
|
@@ -4012,6 +4647,11 @@ export class LlEmitter {
|
|
|
4012
4647
|
// the overflow (key and value released; absent keys no-op).
|
|
4013
4648
|
const obj = this.emitExpr(s.obj);
|
|
4014
4649
|
const key = this.emitExpr(s.key);
|
|
4650
|
+
if (this.publishedTypes?.recordGuarded(s.shapeId) === true) {
|
|
4651
|
+
// Node: "Cannot delete property 'k' of #<Object>" (key known at
|
|
4652
|
+
// run time only; the guard reports the frozen record generically).
|
|
4653
|
+
this.emitPublishedFieldGuard(obj.name, null, "[key]");
|
|
4654
|
+
}
|
|
4015
4655
|
const ovf = this.recordOvfPtr(obj.name, s.shapeId);
|
|
4016
4656
|
this.declare(`declare zeroext i1 @scr_map_delete_str(ptr, ptr)`);
|
|
4017
4657
|
const t = B.tmp();
|
|
@@ -4027,6 +4667,8 @@ export class LlEmitter {
|
|
|
4027
4667
|
const obj = this.emitExpr(s.obj);
|
|
4028
4668
|
const key = this.emitExpr(s.key);
|
|
4029
4669
|
const v = this.emitExpr(s.value);
|
|
4670
|
+
if (this.publishedTypes?.recordGuarded(s.shapeId) === true)
|
|
4671
|
+
this.emitPublishedFieldGuard(obj.name, null, "[key]");
|
|
4030
4672
|
if (isRefCounted(v.type))
|
|
4031
4673
|
this.moveTemp(v);
|
|
4032
4674
|
const shape = this.recordShape(s.shapeId);
|
|
@@ -4081,9 +4723,9 @@ export class LlEmitter {
|
|
|
4081
4723
|
B.line(`call void @scr_dyn_release(ptr ${v.name})`);
|
|
4082
4724
|
// Mismatched write: TypeError pending, field untouched — the
|
|
4083
4725
|
// statement-level check below unwinds.
|
|
4084
|
-
this.declare(`declare zeroext i1 @scr_exc_pending()`);
|
|
4085
4726
|
const pend = B.tmp();
|
|
4086
|
-
|
|
4727
|
+
for (const line of this.pendingTestLines(pend))
|
|
4728
|
+
B.line(line.trimStart());
|
|
4087
4729
|
const lw = B.newLabel("rks.w");
|
|
4088
4730
|
B.condBr(pend, join, lw);
|
|
4089
4731
|
B.startBlock(lw);
|
|
@@ -4611,7 +5253,12 @@ export class LlEmitter {
|
|
|
4611
5253
|
// the actual ret. The parked value owns a synthetic slot-backed
|
|
4612
5254
|
// scope entry during each copy so a throwing finally releases it.
|
|
4613
5255
|
let v = null;
|
|
4614
|
-
if (s.value !== null) {
|
|
5256
|
+
if (s.value !== null && this.currentBorrowedReturn) {
|
|
5257
|
+
// A borrowed-return body hands back its walk without a reference
|
|
5258
|
+
// (it has no try statements, so no finally can intervene).
|
|
5259
|
+
v = this.emitReadReceiver(s.value);
|
|
5260
|
+
}
|
|
5261
|
+
else if (s.value !== null) {
|
|
4615
5262
|
v = this.emitExpr(s.value);
|
|
4616
5263
|
this.moveTemp(v);
|
|
4617
5264
|
}
|
|
@@ -4986,24 +5633,62 @@ export class LlEmitter {
|
|
|
4986
5633
|
return emitLiteralExpr(this, e);
|
|
4987
5634
|
}
|
|
4988
5635
|
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
|
-
}
|
|
5636
|
+
if (e.kind === "seqExpr")
|
|
5637
|
+
return this.emitSequence(() => emitBorrowedFieldSequence(this, e) ?? emitOperatorExpr(this, e));
|
|
5005
5638
|
return emitOperatorExpr(this, e);
|
|
5006
5639
|
}
|
|
5640
|
+
/** Saved operands can feed later call arguments. Transfer ownership of a
|
|
5641
|
+
* sequence's locals to this expression's frame so they survive those
|
|
5642
|
+
* reads and still die on the path that created them, including inside
|
|
5643
|
+
* lazy branches. */
|
|
5644
|
+
emitSequence(body) {
|
|
5645
|
+
this.scopes.push([]);
|
|
5646
|
+
const result = body();
|
|
5647
|
+
for (const local of this.scopes.pop()) {
|
|
5648
|
+
this.currentFrame().push({
|
|
5649
|
+
name: local.slot,
|
|
5650
|
+
type: local.type,
|
|
5651
|
+
slot: true,
|
|
5652
|
+
...(local.boxed ? { boxed: true } : {}),
|
|
5653
|
+
});
|
|
5654
|
+
}
|
|
5655
|
+
return result;
|
|
5656
|
+
}
|
|
5657
|
+
/** A statement-position expression whose value is dropped. When a
|
|
5658
|
+
* sequence's result only reads an unchanged local (a derived
|
|
5659
|
+
* constructor's `super()` evaluates to `this`), its statements still run
|
|
5660
|
+
* in place, but the result is neither retained nor released. */
|
|
5661
|
+
/** Whether a discarded value can be skipped entirely: a borrowable read
|
|
5662
|
+
* with no effect of its own. canBorrowCallArgument also accepts checked
|
|
5663
|
+
* projections (a ternary or narrow whose failing arm throws, and the
|
|
5664
|
+
* throwing call itself) because a borrowed use still evaluates them;
|
|
5665
|
+
* skipping those would drop the throw. */
|
|
5666
|
+
discardableRead(value) {
|
|
5667
|
+
if (value.kind === "libCall" || value.kind === "ternary" || value.kind === "unionNarrow")
|
|
5668
|
+
return false;
|
|
5669
|
+
if ((value.kind === "upcast" || value.kind === "downcast" || value.kind === "unionWrap") &&
|
|
5670
|
+
!this.discardableRead(value.value))
|
|
5671
|
+
return false;
|
|
5672
|
+
if (value.kind === "seqExpr") {
|
|
5673
|
+
const guarded = guardedValue(value);
|
|
5674
|
+
if (guarded === null || !this.discardableRead(guarded))
|
|
5675
|
+
return false;
|
|
5676
|
+
}
|
|
5677
|
+
return this.canBorrowCallArgument(value);
|
|
5678
|
+
}
|
|
5679
|
+
emitDiscarded(e) {
|
|
5680
|
+
if (e.kind === "seqExpr" &&
|
|
5681
|
+
isRefCounted(e.result.type) &&
|
|
5682
|
+
e.result.kind !== "strLit" &&
|
|
5683
|
+
this.discardableRead(e.result)) {
|
|
5684
|
+
this.emitSequence(() => {
|
|
5685
|
+
for (const s of e.stmts)
|
|
5686
|
+
this.emitStmt(s);
|
|
5687
|
+
});
|
|
5688
|
+
return;
|
|
5689
|
+
}
|
|
5690
|
+
this.emitExpr(e);
|
|
5691
|
+
}
|
|
5007
5692
|
emitControlExpr(e) {
|
|
5008
5693
|
return emitControlExpr(this, e);
|
|
5009
5694
|
}
|
|
@@ -5017,8 +5702,17 @@ export class LlEmitter {
|
|
|
5017
5702
|
* value, making the snapshot unique unless a real observable alias exists.
|
|
5018
5703
|
*/
|
|
5019
5704
|
emitStringSelfConcatAssign(localId, left, suffix, retainForYield) {
|
|
5705
|
+
const moved = this.emitMovedSelfConcat(localId, left, suffix, retainForYield);
|
|
5706
|
+
if (moved)
|
|
5707
|
+
return moved;
|
|
5020
5708
|
const snapshot = this.emitExpr(left);
|
|
5021
|
-
|
|
5709
|
+
// A suffix of several parts, or a number, appends each operand to the
|
|
5710
|
+
// snapshot directly (in place once the binding lets go of it) instead
|
|
5711
|
+
// of first building the suffix as a separate string.
|
|
5712
|
+
const parts = stringParts(suffix);
|
|
5713
|
+
const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
|
|
5714
|
+
const inputs = mixed ? emitConcatInputs(this, parts) : [];
|
|
5715
|
+
const right = mixed ? null : this.emitExpr(suffix);
|
|
5022
5716
|
const b = this.binding(localId);
|
|
5023
5717
|
const B = this.B;
|
|
5024
5718
|
if (b.kind === "boxed") {
|
|
@@ -5031,9 +5725,15 @@ export class LlEmitter {
|
|
|
5031
5725
|
B.line(`store ptr null, ptr ${b.slot}`);
|
|
5032
5726
|
this.releaseValue(old, b.type);
|
|
5033
5727
|
}
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5728
|
+
let raw;
|
|
5729
|
+
if (right === null) {
|
|
5730
|
+
raw = emitMixedConcat(this, snapshot.name, inputs);
|
|
5731
|
+
}
|
|
5732
|
+
else {
|
|
5733
|
+
this.declare(`declare ptr @scr_str_concat(ptr, ptr)`);
|
|
5734
|
+
raw = B.tmp();
|
|
5735
|
+
B.line(`${raw} = call ptr @scr_str_concat(ptr ${snapshot.name}, ptr ${right.name})`);
|
|
5736
|
+
}
|
|
5037
5737
|
const result = this.own({ name: raw, type: left.type });
|
|
5038
5738
|
if (retainForYield) {
|
|
5039
5739
|
const stored = this.retainValue(result.name, result.type);
|
|
@@ -5051,6 +5751,43 @@ export class LlEmitter {
|
|
|
5051
5751
|
}
|
|
5052
5752
|
return result;
|
|
5053
5753
|
}
|
|
5754
|
+
/** `local = local + suffix` when the suffix cannot read or write the
|
|
5755
|
+
* local (an unboxed binding is only reachable through this function's own
|
|
5756
|
+
* expressions): reading the binding after the suffix gives the value the
|
|
5757
|
+
* left side had, so it moves into a consuming concat, and the result takes
|
|
5758
|
+
* its place. Null when the general snapshot path is needed. */
|
|
5759
|
+
emitMovedSelfConcat(localId, left, suffix, retainForYield) {
|
|
5760
|
+
const b = this.binding(localId);
|
|
5761
|
+
if (retainForYield ||
|
|
5762
|
+
b.kind !== "local" ||
|
|
5763
|
+
b.local === undefined ||
|
|
5764
|
+
b.local.boxed ||
|
|
5765
|
+
b.local.tdz ||
|
|
5766
|
+
b.type.kind !== "string" ||
|
|
5767
|
+
left.kind !== "varRef" ||
|
|
5768
|
+
left.localId !== localId ||
|
|
5769
|
+
mentionsLocal(suffix, localId))
|
|
5770
|
+
return null;
|
|
5771
|
+
const parts = stringParts(suffix);
|
|
5772
|
+
const mixed = parts.length > 1 || numberPart(parts[0]) !== null;
|
|
5773
|
+
const inputs = mixed ? emitConcatInputs(this, parts) : [];
|
|
5774
|
+
const right = mixed ? null : this.emitExpr(suffix);
|
|
5775
|
+
this.materializeSplitLocal(localId);
|
|
5776
|
+
const B = this.B;
|
|
5777
|
+
const head = B.tmp();
|
|
5778
|
+
B.line(`${head} = load ptr, ptr ${b.slot}`);
|
|
5779
|
+
let raw;
|
|
5780
|
+
if (right === null) {
|
|
5781
|
+
raw = emitMixedConcat(this, head, inputs, true);
|
|
5782
|
+
}
|
|
5783
|
+
else {
|
|
5784
|
+
this.declare(`declare ptr @scr_str_concat_move(ptr, ptr)`);
|
|
5785
|
+
raw = B.tmp();
|
|
5786
|
+
B.line(`${raw} = call ptr @scr_str_concat_move(ptr ${head}, ptr ${right.name})`);
|
|
5787
|
+
}
|
|
5788
|
+
B.line(`store ptr ${raw}, ptr ${b.slot}`);
|
|
5789
|
+
return { name: raw, type: left.type }; // moved into the binding
|
|
5790
|
+
}
|
|
5054
5791
|
emitContainerExpr(e) {
|
|
5055
5792
|
return emitContainerExpr(this, e);
|
|
5056
5793
|
}
|
|
@@ -5064,8 +5801,152 @@ export class LlEmitter {
|
|
|
5064
5801
|
return emitDynamicExpr(this, e);
|
|
5065
5802
|
}
|
|
5066
5803
|
emitIntrinsicExpr(e) {
|
|
5804
|
+
if (e.name === "threads.publish")
|
|
5805
|
+
return this.emitPublish(e);
|
|
5067
5806
|
return emitIntrinsicExpr(this, e);
|
|
5068
5807
|
}
|
|
5808
|
+
publishWalkersCache = null;
|
|
5809
|
+
/** Per-type publication walkers, created on the first publish. */
|
|
5810
|
+
get publishWalkers() {
|
|
5811
|
+
if (this.publishWalkersCache !== null)
|
|
5812
|
+
return this.publishWalkersCache;
|
|
5813
|
+
const byPre = [...this.classMeta.values()].sort((a, b) => a.pre - b.pre);
|
|
5814
|
+
this.publishWalkersCache = new PublishWalkers({
|
|
5815
|
+
declare: (decl) => this.declare(decl),
|
|
5816
|
+
cstr: (text) => this.cstr(text),
|
|
5817
|
+
sizeType: this.sizeType,
|
|
5818
|
+
tracedShapes: this.tracedShapes,
|
|
5819
|
+
unionsById: this.unionsById,
|
|
5820
|
+
recordsById: this.recordsById,
|
|
5821
|
+
nullableArm: (t) => this.nullableUnions.of(t)?.arm ?? null,
|
|
5822
|
+
classInfo: (className) => {
|
|
5823
|
+
const meta = this.classMetaOf(className);
|
|
5824
|
+
return {
|
|
5825
|
+
def: meta.def,
|
|
5826
|
+
hierarchy: meta.hierarchy,
|
|
5827
|
+
pre: meta.pre,
|
|
5828
|
+
post: meta.post,
|
|
5829
|
+
runtimeRooted: meta.root.def.runtime === true,
|
|
5830
|
+
fields: meta.def.fields.map((f) => ({
|
|
5831
|
+
name: f.name,
|
|
5832
|
+
index: classFieldIndex(meta, f.name).index,
|
|
5833
|
+
type: this.nullableFields.storageType(className, f.name, f.type),
|
|
5834
|
+
})),
|
|
5835
|
+
};
|
|
5836
|
+
},
|
|
5837
|
+
classesInInterval: (pre, post) => byPre.filter((m) => m.pre >= pre && m.pre <= post).map((m) => m.def.name),
|
|
5838
|
+
});
|
|
5839
|
+
return this.publishWalkersCache;
|
|
5840
|
+
}
|
|
5841
|
+
/** `publish(value)` (@scriptc/threads): makes the graph immortal and
|
|
5842
|
+
* immutable (scr_publish with the static type's walker) and answers the
|
|
5843
|
+
* same value. Throws (pending) when a dyn part is unpublishable. */
|
|
5844
|
+
emitPublish(e) {
|
|
5845
|
+
const value = this.emitExpr(e.args[0]);
|
|
5846
|
+
if (this.B.isTerminated())
|
|
5847
|
+
return value;
|
|
5848
|
+
const lines = this.publishWalkers.emitPublish(value.name, value.type, e.loc);
|
|
5849
|
+
if (lines.length > 0) {
|
|
5850
|
+
for (const line of lines)
|
|
5851
|
+
this.B.line(line.trimStart());
|
|
5852
|
+
// A refusal unwinds while the operand temp still owns its reference.
|
|
5853
|
+
this.emitPendingCheck();
|
|
5854
|
+
}
|
|
5855
|
+
// The result IS the operand: ownership moves to the result temp.
|
|
5856
|
+
if (!isRefCounted(value.type))
|
|
5857
|
+
return { name: value.name, type: e.type };
|
|
5858
|
+
this.moveTemp(value);
|
|
5859
|
+
return this.own({ name: value.name, type: e.type });
|
|
5860
|
+
}
|
|
5861
|
+
/** Branches on "`obj` is published" (rc == SIZE_MAX); leaves the builder
|
|
5862
|
+
* in the cold block and answers the continuation label. */
|
|
5863
|
+
emitPublishedTest(obj) {
|
|
5864
|
+
const B = this.B;
|
|
5865
|
+
const rc = B.tmp(), frozen = B.tmp(), expected = B.tmp();
|
|
5866
|
+
B.line(`${rc} = load ${this.sizeType}, ptr ${obj}`);
|
|
5867
|
+
B.line(`${frozen} = icmp eq ${this.sizeType} ${rc}, -1`);
|
|
5868
|
+
this.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
5869
|
+
B.line(`${expected} = call i1 @llvm.expect.i1(i1 ${frozen}, i1 false)`);
|
|
5870
|
+
const cold = B.newLabel("pub.frozen"), ok = B.newLabel("pub.ok");
|
|
5871
|
+
B.condBr(expected, cold, ok);
|
|
5872
|
+
B.startBlock(cold);
|
|
5873
|
+
return ok;
|
|
5874
|
+
}
|
|
5875
|
+
publishedNamesSym = null;
|
|
5876
|
+
/** The class-name table the hierarchy field guards index by preorder. */
|
|
5877
|
+
publishedClassNamesDefs() {
|
|
5878
|
+
if (this.publishedNamesSym === null)
|
|
5879
|
+
return [];
|
|
5880
|
+
const metas = [...this.classMeta.values()];
|
|
5881
|
+
const size = metas.reduce((max, m) => Math.max(max, m.pre + 1), 0);
|
|
5882
|
+
const names = new Array(size).fill("ptr null");
|
|
5883
|
+
for (const m of metas)
|
|
5884
|
+
names[m.pre] = `ptr ${this.cstr(m.def.jsName ?? m.def.name)}`;
|
|
5885
|
+
return [
|
|
5886
|
+
`${this.publishedNamesSym} = internal constant [${size} x ptr] [ ${names.join(", ")} ]`,
|
|
5887
|
+
``,
|
|
5888
|
+
];
|
|
5889
|
+
}
|
|
5890
|
+
/** The frozen-object guard of a class field or record field store:
|
|
5891
|
+
* Node's "Cannot assign to read only property" TypeError when the target
|
|
5892
|
+
* is published. `className` null means a record (`#<Object>`). */
|
|
5893
|
+
emitPublishedFieldGuard(obj, className, field) {
|
|
5894
|
+
const published = this.publishedTypes;
|
|
5895
|
+
if (published === null)
|
|
5896
|
+
return;
|
|
5897
|
+
if (className !== null && !published.classGuarded(className))
|
|
5898
|
+
return;
|
|
5899
|
+
const B = this.B;
|
|
5900
|
+
const ok = this.emitPublishedTest(obj);
|
|
5901
|
+
let owner;
|
|
5902
|
+
const meta = className === null ? undefined : this.classMeta.get(className);
|
|
5903
|
+
if (meta === undefined)
|
|
5904
|
+
owner = this.cstr("Object");
|
|
5905
|
+
else if (!meta.hierarchy)
|
|
5906
|
+
owner = this.cstr(meta.def.jsName ?? meta.def.name);
|
|
5907
|
+
else {
|
|
5908
|
+
// The DYNAMIC class names the object, as in Node.
|
|
5909
|
+
this.publishedNamesSym ??= "@sc_pub_class_names";
|
|
5910
|
+
const vtp = B.tmp(), vt = B.tmp(), pre = B.tmp(), slot = B.tmp(), name = B.tmp();
|
|
5911
|
+
B.line(`${vtp} = getelementptr inbounds ptr, ptr ${obj}, i64 1`);
|
|
5912
|
+
B.line(`${vt} = load ptr, ptr ${vtp}`);
|
|
5913
|
+
B.line(`${pre} = load ${this.sizeType}, ptr ${vt}`);
|
|
5914
|
+
B.line(`${slot} = getelementptr inbounds ptr, ptr ${this.publishedNamesSym}, ${this.sizeType} ${pre}`);
|
|
5915
|
+
B.line(`${name} = load ptr, ptr ${slot}`);
|
|
5916
|
+
owner = name;
|
|
5917
|
+
}
|
|
5918
|
+
this.declare(`declare void @scr_throw_published_field(ptr, ptr)`);
|
|
5919
|
+
B.line(`call void @scr_throw_published_field(ptr ${this.cstr(field)}, ptr ${owner})`);
|
|
5920
|
+
this.emitUnwind();
|
|
5921
|
+
B.startBlock(ok);
|
|
5922
|
+
}
|
|
5923
|
+
/** The frozen-array guard (scr_throw_published_array's ops): `index` and
|
|
5924
|
+
* `count` are doubles (the written index, the argument count). */
|
|
5925
|
+
emitPublishedArrayGuard(arr, arrType, op, index = f64Lit(0), count = f64Lit(0)) {
|
|
5926
|
+
if (this.publishedTypes?.containerGuarded(arrType) !== true)
|
|
5927
|
+
return;
|
|
5928
|
+
const B = this.B;
|
|
5929
|
+
const ok = this.emitPublishedTest(arr);
|
|
5930
|
+
this.declare(`declare zeroext i1 @scr_throw_published_array(i32, ptr, double, double)`);
|
|
5931
|
+
const threw = B.tmp();
|
|
5932
|
+
B.line(`${threw} = call zeroext i1 @scr_throw_published_array(i32 ${op}, ptr ${arr}, double ${index}, double ${count})`);
|
|
5933
|
+
const unwind = B.newLabel("pub.throw");
|
|
5934
|
+
B.condBr(threw, unwind, ok);
|
|
5935
|
+
B.startBlock(unwind);
|
|
5936
|
+
this.emitUnwind();
|
|
5937
|
+
B.startBlock(ok);
|
|
5938
|
+
}
|
|
5939
|
+
/** The published Map/Set guard: "Cannot modify a published Map/Set". */
|
|
5940
|
+
emitPublishedCollectionGuard(m, t) {
|
|
5941
|
+
if (this.publishedTypes?.containerGuarded(t) !== true)
|
|
5942
|
+
return;
|
|
5943
|
+
const B = this.B;
|
|
5944
|
+
const ok = this.emitPublishedTest(m);
|
|
5945
|
+
this.declare(`declare void @scr_throw_published_collection(i1 zeroext)`);
|
|
5946
|
+
B.line(`call void @scr_throw_published_collection(i1 ${t.kind === "set" ? "true" : "false"})`);
|
|
5947
|
+
this.emitUnwind();
|
|
5948
|
+
B.startBlock(ok);
|
|
5949
|
+
}
|
|
5069
5950
|
emitSerializationExpr(e) {
|
|
5070
5951
|
return emitSerializationExpr(this, e);
|
|
5071
5952
|
}
|
|
@@ -5085,6 +5966,13 @@ export class LlEmitter {
|
|
|
5085
5966
|
return this.emitExpr(e);
|
|
5086
5967
|
if (e.kind === "strLit")
|
|
5087
5968
|
return { name: this.internLiteral(e.value), type: e.type };
|
|
5969
|
+
const guarded = guardedValue(e);
|
|
5970
|
+
if (guarded !== null) {
|
|
5971
|
+
// Throwing guards run in place, exactly as the sequence emits them.
|
|
5972
|
+
for (const s of e.stmts)
|
|
5973
|
+
this.emitStmt(s);
|
|
5974
|
+
return this.emitReadReceiver(guarded);
|
|
5975
|
+
}
|
|
5088
5976
|
if (e.kind === "varRef") {
|
|
5089
5977
|
this.materializeSplitLocal(e.localId);
|
|
5090
5978
|
const binding = this.binding(e.localId);
|
|
@@ -5106,7 +5994,22 @@ export class LlEmitter {
|
|
|
5106
5994
|
this.B.line(`${value} = load ptr, ptr ${binding.slot}`);
|
|
5107
5995
|
return { name: value, type: e.type };
|
|
5108
5996
|
}
|
|
5109
|
-
|
|
5997
|
+
if (e.kind === "arrayGet" && this.canBorrowReceiver(e)) {
|
|
5998
|
+
// The array keeps the element alive until the consumer; a missing
|
|
5999
|
+
// element still traps exactly like the owned read.
|
|
6000
|
+
const slot = this.B.slot();
|
|
6001
|
+
this.B.entryAllocas.push(`${slot} = alloca ptr`);
|
|
6002
|
+
emitBorrowedArrayRead(this, e, slot);
|
|
6003
|
+
const value = this.B.tmp();
|
|
6004
|
+
this.B.line(`${value} = load ptr, ptr ${slot}`);
|
|
6005
|
+
return { name: value, type: e.type };
|
|
6006
|
+
}
|
|
6007
|
+
const wrap = this.borrowableNullableWrap(e);
|
|
6008
|
+
if (wrap === "unit" && e.kind === "unionWrap")
|
|
6009
|
+
return { name: this.unitInstanceRef(e.unionId, e.tag), type: e.type };
|
|
6010
|
+
if (wrap === "ref" && e.kind === "unionWrap")
|
|
6011
|
+
return { name: this.emitReadReceiver(e.value).name, type: e.type };
|
|
6012
|
+
const read = matchMapRead(e, this.boxedUnionsById);
|
|
5110
6013
|
if (read) {
|
|
5111
6014
|
const result = emitStackMapRead(this, read);
|
|
5112
6015
|
if (result.owner)
|
|
@@ -5116,11 +6019,19 @@ export class LlEmitter {
|
|
|
5116
6019
|
const arrayRead = this.optionalArrayReads.get(e);
|
|
5117
6020
|
if (arrayRead)
|
|
5118
6021
|
return emitProjectedArrayRead(this, arrayRead);
|
|
5119
|
-
|
|
6022
|
+
const narrow = this.checkedNarrows.get(e);
|
|
6023
|
+
const narrowed = narrow ? emitCheckedNarrowReceiver(this, narrow) : null;
|
|
6024
|
+
if (narrowed)
|
|
6025
|
+
return narrowed;
|
|
6026
|
+
if (canStackUnion(e, this.boxedUnionsById))
|
|
5120
6027
|
return emitStackUnion(this, e).value;
|
|
5121
6028
|
if (e.kind === "unionNarrow") {
|
|
5122
6029
|
const union = this.emitUnionProjection(e.value);
|
|
5123
|
-
return { name: this.unionPeek(union.name), type: e.type };
|
|
6030
|
+
return { name: this.unionPeek(union.name, e.unionId), type: e.type };
|
|
6031
|
+
}
|
|
6032
|
+
if ((e.kind === "downcast" || e.kind === "upcast") && e.value.type.kind === "object") {
|
|
6033
|
+
// Prefix layouts: a class cast reads the operand's own pointer.
|
|
6034
|
+
return { name: this.emitReadReceiver(e.value).name, type: e.type };
|
|
5124
6035
|
}
|
|
5125
6036
|
// A nullable-pointer field has no box to borrow. Projections use
|
|
5126
6037
|
// emitUnionProjection; other borrowers get an owned heap box.
|
|
@@ -5133,6 +6044,8 @@ export class LlEmitter {
|
|
|
5133
6044
|
return { name: this.loadRecordField(ptr, type), type: e.type };
|
|
5134
6045
|
}
|
|
5135
6046
|
const { ptr, type } = this.classFieldPtr(receiver.name, e.className, e.field);
|
|
6047
|
+
if (this.int32Slots.isField(e.className, e.field))
|
|
6048
|
+
return this.loadInt32Field(ptr, e.type);
|
|
5136
6049
|
return { name: this.loadField(ptr, type), type: e.type };
|
|
5137
6050
|
}
|
|
5138
6051
|
if (e.kind === "ternary" && this.canBorrowReceiver(e.then) && this.canBorrowReceiver(e.else_)) {
|
|
@@ -5160,6 +6073,8 @@ export class LlEmitter {
|
|
|
5160
6073
|
B.line(`${value} = load ptr, ptr ${slot}`);
|
|
5161
6074
|
return { name: value, type: e.type };
|
|
5162
6075
|
}
|
|
6076
|
+
if (e.kind === "call" && this.canBorrowReceiver(e))
|
|
6077
|
+
return emitBorrowedResultCall(this, e);
|
|
5163
6078
|
return this.emitExpr(e);
|
|
5164
6079
|
}
|
|
5165
6080
|
/** A private stack union source with an independently owned payload. */
|
|
@@ -5171,8 +6086,8 @@ export class LlEmitter {
|
|
|
5171
6086
|
isStackUnionSource(e) {
|
|
5172
6087
|
if (e.type.kind !== "union")
|
|
5173
6088
|
return false;
|
|
5174
|
-
return (canStackUnion(e, this.
|
|
5175
|
-
matchMapRead(e, this.
|
|
6089
|
+
return (canStackUnion(e, this.boxedUnionsById) ||
|
|
6090
|
+
matchMapRead(e, this.boxedUnionsById) !== null ||
|
|
5176
6091
|
this.optionalArrayReads.get(e) !== null);
|
|
5177
6092
|
}
|
|
5178
6093
|
/** The nullable-pointer representation behind a union-typed class field
|
|
@@ -5244,7 +6159,10 @@ export class LlEmitter {
|
|
|
5244
6159
|
B.br(join);
|
|
5245
6160
|
B.startBlock(ref);
|
|
5246
6161
|
const owned = this.retainValue(p, nullable.arm);
|
|
5247
|
-
const box = this.unionNewOwned(nullable.
|
|
6162
|
+
const box = this.unionNewOwned(nullable.unionId, nullable.refTag, {
|
|
6163
|
+
name: owned,
|
|
6164
|
+
type: nullable.arm,
|
|
6165
|
+
});
|
|
5248
6166
|
B.line(`store ptr ${box}, ptr ${slot}`);
|
|
5249
6167
|
B.br(join);
|
|
5250
6168
|
B.startBlock(join);
|
|
@@ -5256,10 +6174,10 @@ export class LlEmitter {
|
|
|
5256
6174
|
* box, which stays owned by its existing owner. */
|
|
5257
6175
|
unionToNullable(u, nullable) {
|
|
5258
6176
|
const B = this.B;
|
|
5259
|
-
const tag = this.unionTag(u);
|
|
6177
|
+
const tag = this.unionTag(u, nullable.unionId);
|
|
5260
6178
|
const isRef = B.tmp(), sel = B.tmp();
|
|
5261
6179
|
B.line(`${isRef} = icmp eq i32 ${tag}, ${nullable.refTag}`);
|
|
5262
|
-
const payload = this.unionPeek(u);
|
|
6180
|
+
const payload = this.unionPeek(u, nullable.unionId);
|
|
5263
6181
|
B.line(`${sel} = select i1 ${isRef}, ptr ${payload}, ptr null`);
|
|
5264
6182
|
return this.retainValue(sel, nullable.arm);
|
|
5265
6183
|
}
|
|
@@ -5320,20 +6238,166 @@ export class LlEmitter {
|
|
|
5320
6238
|
isRefCounted(e.type)));
|
|
5321
6239
|
}
|
|
5322
6240
|
case "unionNarrow":
|
|
6241
|
+
// Class casts reinterpret the same pointer (prefix layouts).
|
|
6242
|
+
case "downcast":
|
|
6243
|
+
case "upcast":
|
|
5323
6244
|
return this.canBorrowReceiver(e.value);
|
|
5324
6245
|
case "fieldGet":
|
|
5325
6246
|
case "recordGet":
|
|
5326
6247
|
return this.canBorrowReceiver(e.obj);
|
|
5327
6248
|
case "ternary":
|
|
5328
6249
|
return this.canBorrowReceiver(e.then) && this.canBorrowReceiver(e.else_);
|
|
6250
|
+
case "seqExpr": {
|
|
6251
|
+
const guarded = guardedValue(e);
|
|
6252
|
+
return guarded !== null && this.canBorrowReceiver(guarded);
|
|
6253
|
+
}
|
|
5329
6254
|
// The runtime always sets a pending exception; its unreachable
|
|
5330
6255
|
// typed dummy is null and owns no receiver.
|
|
5331
6256
|
case "libCall":
|
|
5332
6257
|
return e.fn === "error.nodeThrow";
|
|
6258
|
+
case "unionWrap": {
|
|
6259
|
+
const wrap = this.borrowableNullableWrap(e);
|
|
6260
|
+
return wrap === "unit" || (wrap === "ref" && this.canBorrowReceiver(e.value));
|
|
6261
|
+
}
|
|
6262
|
+
// A borrowed-return callee preserves edges, so its result stays
|
|
6263
|
+
// reachable from borrowable arguments until the consumer runs.
|
|
6264
|
+
case "call":
|
|
6265
|
+
return (this.borrowedReturns.has(e.callee) &&
|
|
6266
|
+
e.args.every((arg) => (!isRefCounted(arg.type) || this.canBorrowReceiver(arg)) &&
|
|
6267
|
+
this.referenceEffects.preserves(arg)));
|
|
6268
|
+
// A required element read: the (borrowed) array owns the element
|
|
6269
|
+
// while an edge-preserving index computes.
|
|
6270
|
+
case "arrayGet":
|
|
6271
|
+
return (e.arr.type.kind === "array" &&
|
|
6272
|
+
isRefCounted(e.arr.type.elem) &&
|
|
6273
|
+
this.canBorrowReceiver(e.arr) &&
|
|
6274
|
+
this.referenceEffects.preserves(e.index));
|
|
6275
|
+
default:
|
|
6276
|
+
return false;
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
/** A reference held as one plain pointer that emitReadReceiver can read
|
|
6280
|
+
* without owning: class instances, records, arrays, strings and
|
|
6281
|
+
* nullable-pointer unions. Tagged union boxes, closures and dynamic
|
|
6282
|
+
* values keep ordinary ownership. */
|
|
6283
|
+
plainReference(type) {
|
|
6284
|
+
switch (type.kind) {
|
|
6285
|
+
case "object":
|
|
6286
|
+
case "record":
|
|
6287
|
+
case "array":
|
|
6288
|
+
case "string":
|
|
6289
|
+
return true;
|
|
6290
|
+
case "union":
|
|
6291
|
+
return this.nullableUnions.has(type.unionId);
|
|
5333
6292
|
default:
|
|
5334
6293
|
return false;
|
|
5335
6294
|
}
|
|
5336
6295
|
}
|
|
6296
|
+
/** Functions returning a walk of their borrowed parameters return it
|
|
6297
|
+
* without a reference (+0) from their borrowing body; direct callers
|
|
6298
|
+
* either consume it as a receiver/walk or retain it at once, and the owned
|
|
6299
|
+
* adapter retains before releasing its parameters. Candidates preserve
|
|
6300
|
+
* every heap edge (so the result stays reachable from the arguments),
|
|
6301
|
+
* borrow every reference parameter and return plain pointers. The set
|
|
6302
|
+
* grows to a fixpoint because a call to a borrowed-return function with
|
|
6303
|
+
* walk arguments is itself a walk; walk facts are then recomputed with
|
|
6304
|
+
* the final set, admitting only parameters whose convention borrows. */
|
|
6305
|
+
analyzeBorrowedReturns() {
|
|
6306
|
+
const pointer = (type) => this.plainReference(type);
|
|
6307
|
+
const borrowedIds = (fn) => {
|
|
6308
|
+
const indexes = this.callLifetimes.borrowed.get(fn.name);
|
|
6309
|
+
return new Set(fn.params.filter((_, i) => indexes?.has(i)).map((p) => p.localId));
|
|
6310
|
+
};
|
|
6311
|
+
const hostFor = (fn) => {
|
|
6312
|
+
const allowed = borrowedIds(fn);
|
|
6313
|
+
return {
|
|
6314
|
+
pointerLocal: (local) => pointer(local.type),
|
|
6315
|
+
borrowsWithoutOwning: (e) => this.borrowsWithoutOwning(e),
|
|
6316
|
+
borrowedReturn: (callee) => this.borrowedReturns.has(callee),
|
|
6317
|
+
parameterAllowed: (id) => allowed.has(id),
|
|
6318
|
+
};
|
|
6319
|
+
};
|
|
6320
|
+
const preserving = [...this.fnByName.values()].filter((fn) => this.referenceEffects.functions.has(fn.name));
|
|
6321
|
+
const candidates = preserving.filter((fn) => {
|
|
6322
|
+
if (!pointer(fn.returnType) || fn.async || fn.generator || fn.captures)
|
|
6323
|
+
return false;
|
|
6324
|
+
// A borrowing vtable slot dispatches to the borrowing body (or its
|
|
6325
|
+
// virtual adapter) directly, and virtual callers own the result.
|
|
6326
|
+
if (this.implSlotBorrowed(fn.name).size > 0)
|
|
6327
|
+
return false;
|
|
6328
|
+
const indexes = this.callLifetimes.borrowed.get(fn.name);
|
|
6329
|
+
return (indexes !== undefined &&
|
|
6330
|
+
indexes.size > 0 &&
|
|
6331
|
+
fn.params.every((p, i) => !isRefCounted(p.type) || indexes.has(i)));
|
|
6332
|
+
});
|
|
6333
|
+
for (let changed = true; changed;) {
|
|
6334
|
+
changed = false;
|
|
6335
|
+
for (const fn of candidates) {
|
|
6336
|
+
if (this.borrowedReturns.has(fn.name))
|
|
6337
|
+
continue;
|
|
6338
|
+
if (!analyzeWalks(fn, hostFor(fn)).returnsWalk)
|
|
6339
|
+
continue;
|
|
6340
|
+
this.borrowedReturns.add(fn.name);
|
|
6341
|
+
changed = true;
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
6344
|
+
if (this.borrowedReturns.size === 0)
|
|
6345
|
+
return;
|
|
6346
|
+
for (const fn of preserving) {
|
|
6347
|
+
const walks = analyzeWalks(fn, hostFor(fn)).locals;
|
|
6348
|
+
if (walks.size > 0)
|
|
6349
|
+
this.walkBorrowsByFunction.set(fn.name, walks);
|
|
6350
|
+
}
|
|
6351
|
+
}
|
|
6352
|
+
/** emitReadReceiver produces this node's pointer without acquiring a
|
|
6353
|
+
* reference, given operands (and ternary arms) that do the same: plain
|
|
6354
|
+
* class/record field reads, class casts, nullable-pointer narrows and
|
|
6355
|
+
* wraps, and checked ternaries. Stack-boxed, map-read and boxed-field
|
|
6356
|
+
* sources are owned. Depends only on module facts, not the current
|
|
6357
|
+
* function. */
|
|
6358
|
+
borrowsWithoutOwning(e) {
|
|
6359
|
+
if (!isRefCounted(e.type) || this.isStackUnionSource(e))
|
|
6360
|
+
return false;
|
|
6361
|
+
if (matchMapRead(e, this.boxedUnionsById))
|
|
6362
|
+
return false;
|
|
6363
|
+
switch (e.kind) {
|
|
6364
|
+
case "fieldGet":
|
|
6365
|
+
return this.nullableFieldGet(e) === null;
|
|
6366
|
+
case "recordGet":
|
|
6367
|
+
return true;
|
|
6368
|
+
case "unionNarrow":
|
|
6369
|
+
return this.nullableUnions.has(e.unionId);
|
|
6370
|
+
case "downcast":
|
|
6371
|
+
case "upcast":
|
|
6372
|
+
return e.value.type.kind === "object";
|
|
6373
|
+
case "unionWrap":
|
|
6374
|
+
return this.borrowableNullableWrap(e) !== null;
|
|
6375
|
+
// The caller proves each arm separately.
|
|
6376
|
+
case "ternary":
|
|
6377
|
+
return true;
|
|
6378
|
+
// The caller proves the arguments are walks; their evaluation must not
|
|
6379
|
+
// remove an edge before the call reads them.
|
|
6380
|
+
case "call":
|
|
6381
|
+
return (this.borrowedReturns.has(e.callee) &&
|
|
6382
|
+
e.args.every((arg) => this.referenceEffects.preserves(arg)));
|
|
6383
|
+
default:
|
|
6384
|
+
return false;
|
|
6385
|
+
}
|
|
6386
|
+
}
|
|
6387
|
+
/** A wrap into a nullable-pointer union is the payload pointer itself
|
|
6388
|
+
* (the reference arm) or an immortal constant (a unit arm), so it can
|
|
6389
|
+
* borrow exactly when its payload can. Void payloads run for effects and
|
|
6390
|
+
* keep the ordinary path; tagged unions still construct a box. */
|
|
6391
|
+
borrowableNullableWrap(e) {
|
|
6392
|
+
if (e.kind !== "unionWrap")
|
|
6393
|
+
return null;
|
|
6394
|
+
const nullable = this.nullableUnions.get(e.unionId);
|
|
6395
|
+
if (!nullable)
|
|
6396
|
+
return null;
|
|
6397
|
+
if (isUnitType(e.value.type))
|
|
6398
|
+
return e.value.kind === "unitLit" ? "unit" : null;
|
|
6399
|
+
return e.tag === nullable.refTag && isRefCounted(e.value.type) ? "ref" : null;
|
|
6400
|
+
}
|
|
5337
6401
|
materializeSplitLocal(localId) {
|
|
5338
6402
|
const span = this.splitSpans.get(localId);
|
|
5339
6403
|
if (span)
|
|
@@ -5420,8 +6484,8 @@ export class LlEmitter {
|
|
|
5420
6484
|
resolveThunkFor(inner) {
|
|
5421
6485
|
return resolveThunkFor(this, inner);
|
|
5422
6486
|
}
|
|
5423
|
-
tagInSet(uName, tags) {
|
|
5424
|
-
return tagInSet(this, uName, tags);
|
|
6487
|
+
tagInSet(uName, unionId, tags) {
|
|
6488
|
+
return tagInSet(this, uName, unionId, tags);
|
|
5425
6489
|
}
|
|
5426
6490
|
arrPush(arr, acc, value) {
|
|
5427
6491
|
return arrPush(this, arr, acc, value);
|
|
@@ -5465,8 +6529,8 @@ export class LlEmitter {
|
|
|
5465
6529
|
emitBytesLength(elem, receiver, bytes) {
|
|
5466
6530
|
return emitBytesLength(this, elem, receiver, bytes);
|
|
5467
6531
|
}
|
|
5468
|
-
emitBytesGet(elem, receiver, index, expr, inBounds = false) {
|
|
5469
|
-
return emitBytesGet(this, elem, receiver, index, expr, inBounds);
|
|
6532
|
+
emitBytesGet(elem, receiver, index, expr, inBounds = false, invalidNaN = false) {
|
|
6533
|
+
return emitBytesGet(this, elem, receiver, index, expr, inBounds, invalidNaN);
|
|
5470
6534
|
}
|
|
5471
6535
|
emitToUint32(value, expr, uint32) {
|
|
5472
6536
|
return emitToUint32(this, value, expr, uint32);
|