@scriptc/compiler 0.2.10 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ambient/scriptc-node-fallback.d.ts +7 -38
- package/ambient/scriptc-overrides.d.ts +1 -25
- package/ambient/scriptc.d.ts +1 -9
- package/dist/backend/cycle-analysis.test.js +1 -1
- package/dist/backend/cycle-mutability.d.ts +1 -1
- package/dist/backend/cycle-mutability.js +1 -18
- package/dist/backend/cycle-mutability.js.map +1 -1
- package/dist/backend/executable-features.d.ts +1 -1
- package/dist/backend/executable-features.js +3 -8
- package/dist/backend/executable-features.js.map +1 -1
- package/dist/backend/immutability.d.ts +1 -1
- package/dist/backend/immutability.js +1 -1
- package/dist/backend/immutability.test.js +1 -1
- package/dist/backend/library-pack.js +0 -2
- package/dist/backend/library-pack.js.map +1 -1
- package/dist/backend/linker.js +1 -1
- package/dist/backend/linker.js.map +1 -1
- package/dist/backend/llvm/bigint-wrap.d.ts +4 -0
- package/dist/backend/llvm/bigint-wrap.js +590 -0
- package/dist/backend/llvm/bigint-wrap.js.map +1 -0
- package/dist/backend/llvm/blocks.d.ts +2 -0
- package/dist/backend/llvm/blocks.js +4 -0
- package/dist/backend/llvm/blocks.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.js +15 -4
- package/dist/backend/llvm/call-lifetimes.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.test.js +14 -3
- package/dist/backend/llvm/call-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/checked-narrows.test.js +2 -2
- package/dist/backend/llvm/checked-narrows.test.js.map +1 -1
- package/dist/backend/llvm/classes.js +60 -21
- package/dist/backend/llvm/classes.js.map +1 -1
- package/dist/backend/llvm/classes.test.js +11 -11
- package/dist/backend/llvm/classes.test.js.map +1 -1
- package/dist/backend/llvm/common.d.ts +7 -5
- package/dist/backend/llvm/common.js +7 -5
- package/dist/backend/llvm/common.js.map +1 -1
- package/dist/backend/llvm/constant-callbacks.d.ts +1 -1
- package/dist/backend/llvm/constant-callbacks.js +13 -31
- package/dist/backend/llvm/constant-callbacks.js.map +1 -1
- package/dist/backend/llvm/constant-callbacks.test.js +1 -1
- package/dist/backend/llvm/dense-array-access.js +133 -45
- package/dist/backend/llvm/dense-array-access.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.test.js +54 -2
- package/dist/backend/llvm/dense-array-access.test.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +2 -1
- package/dist/backend/llvm/dyn.js +4 -73
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +123 -23
- package/dist/backend/llvm/emitter.js +729 -258
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/emitter.test.js +17 -10
- package/dist/backend/llvm/emitter.test.js.map +1 -1
- package/dist/backend/llvm/expr-async.js +120 -43
- package/dist/backend/llvm/expr-async.js.map +1 -1
- package/dist/backend/llvm/expr-async.test.js +2 -2
- package/dist/backend/llvm/expr-async.test.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.js +51 -36
- package/dist/backend/llvm/expr-bytes.js.map +1 -1
- package/dist/backend/llvm/expr-callbacks.js +72 -3
- package/dist/backend/llvm/expr-callbacks.js.map +1 -1
- package/dist/backend/llvm/expr-calls.d.ts +1 -1
- package/dist/backend/llvm/expr-calls.js +159 -30
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-calls.test.js +132 -0
- package/dist/backend/llvm/expr-calls.test.js.map +1 -0
- package/dist/backend/llvm/expr-containers.js +25 -8
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-context.d.ts +2 -0
- package/dist/backend/llvm/expr-control.js +4 -107
- package/dist/backend/llvm/expr-control.js.map +1 -1
- package/dist/backend/llvm/expr-dispatch.d.ts +1 -2
- package/dist/backend/llvm/expr-dispatch.js +3 -41
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.d.ts +1 -1
- package/dist/backend/llvm/expr-dynamic.js +39 -31
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.d.ts +7 -0
- package/dist/backend/llvm/expr-primitives.js +80 -22
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.test.js +97 -7
- package/dist/backend/llvm/expr-primitives.test.js.map +1 -1
- package/dist/backend/llvm/expr-records.d.ts +9 -0
- package/dist/backend/llvm/expr-records.js +134 -16
- package/dist/backend/llvm/expr-records.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.d.ts +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +80 -1
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.test.js +51 -2
- package/dist/backend/llvm/expr-stream-bridges.test.js.map +1 -1
- package/dist/backend/llvm/int32-abi.d.ts +31 -0
- package/dist/backend/llvm/int32-abi.js +62 -0
- package/dist/backend/llvm/int32-abi.js.map +1 -0
- package/dist/backend/llvm/int32-abi.test.js +107 -0
- package/dist/backend/llvm/int32-abi.test.js.map +1 -0
- package/dist/backend/llvm/int32-locals.d.ts +30 -0
- package/dist/backend/llvm/int32-locals.js +95 -0
- package/dist/backend/llvm/int32-locals.js.map +1 -0
- package/dist/backend/llvm/integer-values.d.ts +4 -0
- package/dist/backend/llvm/integer-values.js +30 -1
- package/dist/backend/llvm/integer-values.js.map +1 -1
- package/dist/backend/llvm/integer-views.d.ts +8 -1
- package/dist/backend/llvm/integer-views.js +47 -2
- package/dist/backend/llvm/integer-views.js.map +1 -1
- package/dist/backend/llvm/lazy-captures.d.ts +16 -0
- package/dist/backend/llvm/lazy-captures.js +88 -2
- package/dist/backend/llvm/lazy-captures.js.map +1 -1
- package/dist/backend/llvm/lazy-captures.test.js +48 -0
- package/dist/backend/llvm/lazy-captures.test.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +14 -1
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +3 -13
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +7 -8
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.js +21 -2
- package/dist/backend/llvm/local-array-reads.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.test.js +4 -3
- package/dist/backend/llvm/local-array-reads.test.js.map +1 -1
- package/dist/backend/llvm/local-union-storage.test.js +3 -3
- package/dist/backend/llvm/local-union-storage.test.js.map +1 -1
- package/dist/backend/llvm/map-number-lookup.d.ts +7 -0
- package/dist/backend/llvm/map-number-lookup.js +16 -2
- package/dist/backend/llvm/map-number-lookup.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.js +7 -1
- package/dist/backend/llvm/map-read-lifetimes.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.test.js +25 -8
- package/dist/backend/llvm/map-read-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/reference-effects.js +2 -0
- package/dist/backend/llvm/reference-effects.js.map +1 -1
- package/dist/backend/llvm/reference-writes.d.ts +3 -0
- package/dist/backend/llvm/reference-writes.js +10 -2
- package/dist/backend/llvm/reference-writes.js.map +1 -1
- package/dist/backend/llvm/regex-matchers.d.ts +39 -0
- package/dist/backend/llvm/regex-matchers.js +1649 -0
- package/dist/backend/llvm/regex-matchers.js.map +1 -0
- package/dist/backend/llvm/regex-matchers.test.js +96 -0
- package/dist/backend/llvm/regex-matchers.test.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +36 -1
- package/dist/backend/llvm/shapes.js +128 -16
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/shapes.test.js +3 -3
- package/dist/backend/llvm/stack-unions.test.js +2 -2
- package/dist/backend/llvm/stack-unions.test.js.map +1 -1
- package/dist/backend/llvm/string-construction.test.js +3 -3
- package/dist/backend/llvm/string-construction.test.js.map +1 -1
- package/dist/backend/llvm/string-lifetimes.test.js +6 -6
- package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/string-literal-equality.d.ts +22 -0
- package/dist/backend/llvm/string-literal-equality.js +78 -0
- package/dist/backend/llvm/string-literal-equality.js.map +1 -0
- package/dist/backend/llvm/switch-dispatch.js +59 -13
- package/dist/backend/llvm/switch-dispatch.js.map +1 -1
- package/dist/backend/llvm/switch-dispatch.test.js +72 -7
- package/dist/backend/llvm/switch-dispatch.test.js.map +1 -1
- package/dist/backend/llvm/union-retype.test.d.ts +1 -0
- package/dist/backend/llvm/union-retype.test.js +229 -0
- package/dist/backend/llvm/union-retype.test.js.map +1 -0
- package/dist/backend/llvm/wrapping-sums.d.ts +17 -0
- package/dist/backend/llvm/wrapping-sums.js +113 -0
- package/dist/backend/llvm/wrapping-sums.js.map +1 -0
- package/dist/backend/loop-polls.js +1 -0
- package/dist/backend/loop-polls.js.map +1 -1
- package/dist/backend/mangle.d.ts +9 -0
- package/dist/backend/mangle.js +15 -0
- package/dist/backend/mangle.js.map +1 -1
- package/dist/backend/may-throw.js +6 -12
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/may-throw.test.js +1 -1
- package/dist/backend/native/artifact-stamps.js +0 -2
- package/dist/backend/native/artifact-stamps.js.map +1 -1
- package/dist/backend/native/cache-warm.d.ts +1 -1
- package/dist/backend/native/cache-warm.js +2 -3
- package/dist/backend/native/cache-warm.js.map +1 -1
- package/dist/backend/native/compiler-fingerprint.d.ts +1 -1
- package/dist/backend/native/compiler-fingerprint.js +4 -7
- package/dist/backend/native/compiler-fingerprint.js.map +1 -1
- package/dist/backend/native/contracts.d.ts +3 -28
- package/dist/backend/native/driver.js +1 -3
- package/dist/backend/native/driver.js.map +1 -1
- package/dist/backend/native/executable.d.ts +0 -15
- package/dist/backend/native/executable.js +17 -172
- package/dist/backend/native/executable.js.map +1 -1
- package/dist/backend/native/tool-identity.js +0 -1
- package/dist/backend/native/tool-identity.js.map +1 -1
- package/dist/backend/native/vendor.d.ts +1 -1
- package/dist/backend/native/vendor.js +1 -1
- package/dist/backend/native/vendor.js.map +1 -1
- package/dist/backend/native-link-info-core.d.ts +0 -2
- package/dist/backend/native-link-info-core.js.map +1 -1
- package/dist/backend/native-toolchain.test.js +23 -33
- package/dist/backend/native-toolchain.test.js.map +1 -1
- package/dist/backend/publication.test.js +1 -1
- package/dist/backend/runtime-abi.d.ts +2 -2
- package/dist/backend/runtime-abi.js +2 -2
- package/dist/backend/runtime-pack-core.d.ts +1 -2
- package/dist/backend/runtime-pack-core.js +5 -14
- package/dist/backend/runtime-pack-core.js.map +1 -1
- package/dist/backend/runtime-pack-targets.test.js +3 -8
- package/dist/backend/runtime-pack-targets.test.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +22 -19
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/backend/target-diagnostics.d.ts +0 -5
- package/dist/backend/target-diagnostics.js +1 -11
- package/dist/backend/target-diagnostics.js.map +1 -1
- package/dist/backend/target-diagnostics.test.js +1 -1
- package/dist/backend/vendor-archives.d.ts +1 -7
- package/dist/backend/vendor-archives.js +9 -189
- package/dist/backend/vendor-archives.js.map +1 -1
- package/dist/backend/vendor-inputs.d.ts +0 -1
- package/dist/backend/vendor-inputs.js +0 -1
- package/dist/backend/vendor-inputs.js.map +1 -1
- package/dist/backend/warm-cache.js +3 -5
- package/dist/backend/warm-cache.js.map +1 -1
- package/dist/cli/command.js +4 -8
- package/dist/cli/command.js.map +1 -1
- package/dist/cli/host.d.ts +1 -1
- package/dist/cli/usage.d.ts +1 -5
- package/dist/cli/usage.js +4 -9
- package/dist/cli/usage.js.map +1 -1
- package/dist/compat/fetch-profile.d.ts +2 -2
- package/dist/compat/fetch-profile.js +16 -25
- package/dist/compat/fetch-profile.js.map +1 -1
- package/dist/compile-types.d.ts +2 -17
- package/dist/contracts/check.js +6 -6
- package/dist/contracts/check.js.map +1 -1
- package/dist/coverage/report.d.ts +5 -22
- package/dist/coverage/report.js +12 -76
- package/dist/coverage/report.js.map +1 -1
- package/dist/coverage/surface-manifest.d.ts +3 -3
- package/dist/coverage/surface-manifest.js +22 -27
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/coverage/verdict-cache.d.ts +1 -3
- package/dist/coverage/verdict-cache.js +2 -7
- package/dist/coverage/verdict-cache.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +11 -60
- package/dist/diagnostics/diagnostic.js +39 -118
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/diagnostics/envelope.d.ts +3 -4
- package/dist/diagnostics/envelope.js +0 -9
- package/dist/diagnostics/envelope.js.map +1 -1
- package/dist/diagnostics/envelope.test.js +1 -1
- package/dist/diagnostics/envelope.test.js.map +1 -1
- package/dist/executable/executable-cache.d.ts +0 -3
- package/dist/executable/executable-cache.js +2 -4
- package/dist/executable/executable-cache.js.map +1 -1
- package/dist/executable/executable-cache.test.js +0 -4
- package/dist/executable/executable-cache.test.js.map +1 -1
- package/dist/executable/prepare.js +5 -2
- package/dist/executable/prepare.js.map +1 -1
- package/dist/frontend/analysis.js +10 -10
- package/dist/frontend/analysis.js.map +1 -1
- package/dist/frontend/cjs-syntax.js +0 -88
- package/dist/frontend/cjs-syntax.js.map +1 -1
- package/dist/frontend/dts-paths.d.ts +1 -1
- package/dist/frontend/dts-paths.js +1 -1
- package/dist/frontend/lowering/abstract-equality.js +1 -4
- package/dist/frontend/lowering/abstract-equality.js.map +1 -1
- package/dist/frontend/lowering/array-values.d.ts +14 -0
- package/dist/frontend/lowering/array-values.js +33 -11
- package/dist/frontend/lowering/array-values.js.map +1 -1
- package/dist/frontend/lowering/builtins/arguments.js +0 -13
- package/dist/frontend/lowering/builtins/arguments.js.map +1 -1
- package/dist/frontend/lowering/builtins/crypto.d.ts +0 -4
- package/dist/frontend/lowering/builtins/crypto.js +0 -4
- package/dist/frontend/lowering/builtins/crypto.js.map +1 -1
- package/dist/frontend/lowering/builtins/json.js +1 -39
- package/dist/frontend/lowering/builtins/json.js.map +1 -1
- package/dist/frontend/lowering/builtins/module-resolution.d.ts +0 -15
- package/dist/frontend/lowering/builtins/module-resolution.js +5 -44
- package/dist/frontend/lowering/builtins/module-resolution.js.map +1 -1
- package/dist/frontend/lowering/builtins/numbers.d.ts +0 -6
- package/dist/frontend/lowering/builtins/numbers.js +6 -57
- package/dist/frontend/lowering/builtins/numbers.js.map +1 -1
- package/dist/frontend/lowering/builtins/promises.js +4 -67
- package/dist/frontend/lowering/builtins/promises.js.map +1 -1
- package/dist/frontend/lowering/builtins/workers.js +1 -1
- package/dist/frontend/lowering/builtins/workers.js.map +1 -1
- package/dist/frontend/lowering/call-arguments.d.ts +1 -37
- package/dist/frontend/lowering/call-arguments.js +11 -94
- package/dist/frontend/lowering/call-arguments.js.map +1 -1
- package/dist/frontend/lowering/call-signatures.d.ts +1 -6
- package/dist/frontend/lowering/call-signatures.js +16 -68
- package/dist/frontend/lowering/call-signatures.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.d.ts +19 -4
- package/dist/frontend/lowering/class-dynamic-dispatch.js +168 -98
- package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.test.js +44 -4
- package/dist/frontend/lowering/class-dynamic-dispatch.test.js.map +1 -1
- package/dist/frontend/lowering/class-method-mutations.js +0 -2
- package/dist/frontend/lowering/class-method-mutations.js.map +1 -1
- package/dist/frontend/lowering/class-protocols.js +1 -1
- package/dist/frontend/lowering/class-protocols.js.map +1 -1
- package/dist/frontend/lowering/coercions/function-adapters.js +5 -35
- package/dist/frontend/lowering/coercions/function-adapters.js.map +1 -1
- package/dist/frontend/lowering/coercions/state.d.ts +0 -1
- package/dist/frontend/lowering/coercions/state.js +0 -1
- package/dist/frontend/lowering/coercions/state.js.map +1 -1
- package/dist/frontend/lowering/coercions/structural-plans.js +2 -4
- package/dist/frontend/lowering/coercions/structural-plans.js.map +1 -1
- package/dist/frontend/lowering/coercions/structural.js +2 -12
- package/dist/frontend/lowering/coercions/structural.js.map +1 -1
- package/dist/frontend/lowering/coercions/unions.js +0 -1
- package/dist/frontend/lowering/coercions/unions.js.map +1 -1
- package/dist/frontend/lowering/containers/array-callbacks.js +49 -28
- package/dist/frontend/lowering/containers/array-callbacks.js.map +1 -1
- package/dist/frontend/lowering/containers/array-construction.js +2 -3
- package/dist/frontend/lowering/containers/array-construction.js.map +1 -1
- package/dist/frontend/lowering/containers/array-methods.js +10 -54
- package/dist/frontend/lowering/containers/array-methods.js.map +1 -1
- package/dist/frontend/lowering/containers/array-pipelines.d.ts +54 -0
- package/dist/frontend/lowering/containers/array-pipelines.js +1827 -0
- package/dist/frontend/lowering/containers/array-pipelines.js.map +1 -0
- package/dist/frontend/lowering/containers/bytes.js +0 -1
- package/dist/frontend/lowering/containers/bytes.js.map +1 -1
- package/dist/frontend/lowering/containers/callback-arguments.js +2 -10
- package/dist/frontend/lowering/containers/callback-arguments.js.map +1 -1
- package/dist/frontend/lowering/containers/collection-methods.js +1 -6
- package/dist/frontend/lowering/containers/collection-methods.js.map +1 -1
- package/dist/frontend/lowering/containers/exec-result-uses.d.ts +10 -0
- package/dist/frontend/lowering/containers/exec-result-uses.js +88 -0
- package/dist/frontend/lowering/containers/exec-result-uses.js.map +1 -0
- package/dist/frontend/lowering/containers/indexed-objects.js +0 -2
- package/dist/frontend/lowering/containers/indexed-objects.js.map +1 -1
- package/dist/frontend/lowering/containers/string-and-regexp.d.ts +0 -6
- package/dist/frontend/lowering/containers/string-and-regexp.js +17 -17
- package/dist/frontend/lowering/containers/string-and-regexp.js.map +1 -1
- package/dist/frontend/lowering/containers/string-replacement.d.ts +2 -1
- package/dist/frontend/lowering/containers/string-replacement.js +32 -136
- package/dist/frontend/lowering/containers/string-replacement.js.map +1 -1
- package/dist/frontend/lowering/devirtualize.d.ts +33 -0
- package/dist/frontend/lowering/devirtualize.js +199 -0
- package/dist/frontend/lowering/devirtualize.js.map +1 -0
- package/dist/frontend/lowering/expressions/evaluation-safety.js +0 -3
- package/dist/frontend/lowering/expressions/evaluation-safety.js.map +1 -1
- package/dist/frontend/lowering/expressions/native-proxy.js +1 -1
- package/dist/frontend/lowering/expressions/native-proxy.js.map +1 -1
- package/dist/frontend/lowering/expressions/nullish-assignment.js +55 -8
- package/dist/frontend/lowering/expressions/nullish-assignment.js.map +1 -1
- package/dist/frontend/lowering/expressions/object-literals.d.ts +0 -8
- package/dist/frontend/lowering/expressions/object-literals.js +8 -35
- package/dist/frontend/lowering/expressions/object-literals.js.map +1 -1
- package/dist/frontend/lowering/expressions/optional-chains.d.ts +0 -4
- package/dist/frontend/lowering/expressions/optional-chains.js +3 -66
- package/dist/frontend/lowering/expressions/optional-chains.js.map +1 -1
- package/dist/frontend/lowering/function-context.js +0 -1
- package/dist/frontend/lowering/function-context.js.map +1 -1
- package/dist/frontend/lowering/generic-functions.js +0 -1
- package/dist/frontend/lowering/generic-functions.js.map +1 -1
- package/dist/frontend/lowering/identifier-uses.d.ts +7 -0
- package/dist/frontend/lowering/identifier-uses.js +23 -0
- package/dist/frontend/lowering/identifier-uses.js.map +1 -0
- package/dist/frontend/lowering/iterator-adapters.js +51 -3
- package/dist/frontend/lowering/iterator-adapters.js.map +1 -1
- package/dist/frontend/lowering/lib-boundary.js +10 -77
- package/dist/frontend/lowering/lib-boundary.js.map +1 -1
- package/dist/frontend/lowering/lower-array-sort.js +14 -10
- package/dist/frontend/lowering/lower-array-sort.js.map +1 -1
- package/dist/frontend/lowering/lower-array-sort.test.js +2 -2
- package/dist/frontend/lowering/lower-array-sort.test.js.map +1 -1
- package/dist/frontend/lowering/lower-assert.js +2 -3
- package/dist/frontend/lowering/lower-assert.js.map +1 -1
- package/dist/frontend/lowering/lower-async-iteration.js +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +2 -27
- package/dist/frontend/lowering/lower-calls.js +111 -493
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +12 -60
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-comptime.js +2 -3
- package/dist/frontend/lowering/lower-comptime.js.map +1 -1
- package/dist/frontend/lowering/lower-console.d.ts +14 -0
- package/dist/frontend/lowering/lower-console.js +195 -0
- package/dist/frontend/lowering/lower-console.js.map +1 -0
- package/dist/frontend/lowering/lower-enums.js +5 -8
- package/dist/frontend/lowering/lower-enums.js.map +1 -1
- package/dist/frontend/lowering/lower-event-emitter.js +3 -4
- package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-expando.js +3 -5
- package/dist/frontend/lowering/lower-expando.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +0 -8
- package/dist/frontend/lowering/lower-exprs.js +180 -905
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-functions.js +9 -8
- package/dist/frontend/lowering/lower-functions.js.map +1 -1
- package/dist/frontend/lowering/lower-generators.d.ts +10 -0
- package/dist/frontend/lowering/lower-generators.js +108 -41
- package/dist/frontend/lowering/lower-generators.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +1 -50
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.d.ts +0 -43
- package/dist/frontend/lowering/lower-modules.js +29 -329
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.test.js +5 -5
- package/dist/frontend/lowering/lower-modules.test.js.map +1 -1
- package/dist/frontend/lowering/lower-server.js +0 -30
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stdlib.d.ts +98 -0
- package/dist/frontend/lowering/{lower-island.js → lower-stdlib.js} +97 -1160
- package/dist/frontend/lowering/lower-stdlib.js.map +1 -0
- package/dist/frontend/lowering/lower-stmts.d.ts +0 -16
- package/dist/frontend/lowering/lower-stmts.js +58 -1016
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.test.js +1 -1
- package/dist/frontend/lowering/lower-stmts.test.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +14 -13
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +42 -155
- package/dist/frontend/lowering/lowerer.js +192 -816
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/lowerer.test.js +4 -51
- package/dist/frontend/lowering/lowerer.test.js.map +1 -1
- package/dist/frontend/lowering/nan-coded-reads.d.ts +16 -0
- package/dist/frontend/lowering/nan-coded-reads.js +108 -1
- package/dist/frontend/lowering/nan-coded-reads.js.map +1 -1
- package/dist/frontend/lowering/narrowing-stability.js +1 -1
- package/dist/frontend/lowering/narrowing-stability.js.map +1 -1
- package/dist/frontend/lowering/optional-arguments.js +0 -2
- package/dist/frontend/lowering/optional-arguments.js.map +1 -1
- package/dist/frontend/lowering/runtime-optional-bounds.d.ts +4 -0
- package/dist/frontend/lowering/runtime-optional-bounds.js +2 -0
- package/dist/frontend/lowering/runtime-optional-bounds.js.map +1 -1
- package/dist/frontend/lowering/runtime-optional-bounds.test.js +27 -0
- package/dist/frontend/lowering/runtime-optional-bounds.test.js.map +1 -1
- package/dist/frontend/lowering/sanitize-class-types.test.js +2 -2
- package/dist/frontend/lowering/split-consumers.js +2 -18
- package/dist/frontend/lowering/split-consumers.js.map +1 -1
- package/dist/frontend/lowering/strict-equality.js +1 -1
- package/dist/frontend/lowering/strict-equality.js.map +1 -1
- package/dist/frontend/lowering/surfaces.d.ts +6 -45
- package/dist/frontend/lowering/surfaces.js +8 -73
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/lowering/union-retag.d.ts +6 -1
- package/dist/frontend/lowering/union-retag.js +64 -1
- package/dist/frontend/lowering/union-retag.js.map +1 -1
- package/dist/frontend/lowering/union-retag.test.js +46 -0
- package/dist/frontend/lowering/union-retag.test.js.map +1 -1
- package/dist/frontend/module-syntax.d.ts +0 -3
- package/dist/frontend/module-syntax.js.map +1 -1
- package/dist/frontend/npm-node.d.ts +0 -7
- package/dist/frontend/npm-node.js +0 -7
- package/dist/frontend/npm-node.js.map +1 -1
- package/dist/frontend/npm-static-rewrite-syntax.d.ts +1 -1
- package/dist/frontend/npm-static-rewrite-syntax.js +6 -82
- package/dist/frontend/npm-static-rewrite-syntax.js.map +1 -1
- package/dist/frontend/npm-static.d.ts +1 -1
- package/dist/frontend/npm-static.js +6 -71
- package/dist/frontend/npm-static.js.map +1 -1
- package/dist/frontend/npm.d.ts +3 -319
- package/dist/frontend/npm.js +6 -1007
- package/dist/frontend/npm.js.map +1 -1
- package/dist/frontend/npm.test.js +2 -197
- package/dist/frontend/npm.test.js.map +1 -1
- package/dist/frontend/pipeline-native.js +1 -2
- package/dist/frontend/pipeline-native.js.map +1 -1
- package/dist/frontend/pipeline.d.ts +3 -11
- package/dist/frontend/pipeline.js +37 -56
- package/dist/frontend/pipeline.js.map +1 -1
- package/dist/frontend/pipeline.test.js +12 -6
- package/dist/frontend/pipeline.test.js.map +1 -1
- package/dist/frontend/program.d.ts +0 -34
- package/dist/frontend/program.js +12 -97
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/provenance-core.d.ts +0 -4
- package/dist/frontend/provenance-core.js +4 -37
- package/dist/frontend/provenance-core.js.map +1 -1
- package/dist/frontend/provenance-registry.d.ts +0 -4
- package/dist/frontend/provenance-registry.js +0 -20
- package/dist/frontend/provenance-registry.js.map +1 -1
- package/dist/frontend/resolve.d.ts +0 -12
- package/dist/frontend/resolve.js +0 -34
- package/dist/frontend/resolve.js.map +1 -1
- package/dist/frontend/runtime-resolve.js +2 -2
- package/dist/frontend/runtime-resolve.js.map +1 -1
- package/dist/frontend/services-node.js +2 -3
- package/dist/frontend/services-node.js.map +1 -1
- package/dist/frontend/services.d.ts +1 -8
- package/dist/frontend/services.js +2 -22
- package/dist/frontend/services.js.map +1 -1
- package/dist/frontend/services.test.js +0 -38
- package/dist/frontend/services.test.js.map +1 -1
- package/dist/frontend/tsc-codes.js +0 -11
- package/dist/frontend/tsc-codes.js.map +1 -1
- package/dist/frontend/type-mapper.d.ts +2 -27
- package/dist/frontend/type-mapper.js +58 -156
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -10
- package/dist/index.js.map +1 -1
- package/dist/ir/api.d.ts +1 -1
- package/dist/ir/api.js +1 -1
- package/dist/ir/api.js.map +1 -1
- package/dist/ir/builtin-effects.js +2 -5
- package/dist/ir/builtin-effects.js.map +1 -1
- package/dist/ir/builtin-signatures.d.ts +1 -1
- package/dist/ir/builtin-signatures.js +13 -56
- package/dist/ir/builtin-signatures.js.map +1 -1
- package/dist/ir/constant-globals.test.js +1 -1
- package/dist/ir/constant-tables.test.js +1 -1
- package/dist/ir/final-fields.test.js +1 -1
- package/dist/ir/function-references.d.ts +12 -0
- package/dist/ir/function-references.js +29 -0
- package/dist/ir/function-references.js.map +1 -0
- package/dist/ir/function-references.test.d.ts +1 -0
- package/dist/ir/function-references.test.js +276 -0
- package/dist/ir/function-references.test.js.map +1 -0
- package/dist/ir/initializer-bindings.test.js +1 -1
- package/dist/ir/int32-slots.d.ts +10 -7
- package/dist/ir/int32-slots.js +219 -68
- package/dist/ir/int32-slots.js.map +1 -1
- package/dist/ir/int32-slots.test.js +39 -2
- package/dist/ir/int32-slots.test.js.map +1 -1
- package/dist/ir/integer-ranges.d.ts +4 -4
- package/dist/ir/integer-ranges.js +89 -64
- package/dist/ir/integer-ranges.js.map +1 -1
- package/dist/ir/integer-ranges.test.js +71 -9
- package/dist/ir/integer-ranges.test.js.map +1 -1
- package/dist/ir/intrinsic-signatures.js +1 -0
- package/dist/ir/intrinsic-signatures.js.map +1 -1
- package/dist/ir/ir.d.ts +64 -290
- package/dist/ir/ir.js +11 -259
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/ir.test.js +2 -7
- package/dist/ir/ir.test.js.map +1 -1
- package/dist/ir/runtime-features.d.ts +3 -30
- package/dist/ir/runtime-features.js +8 -43
- package/dist/ir/runtime-features.js.map +1 -1
- package/dist/ir/scalar-records.test.js +1 -1
- package/dist/ir/serialize.d.ts +1 -1
- package/dist/ir/serialize.js +3 -3
- package/dist/ir/serialize.js.map +1 -1
- package/dist/ir/traverse.js +4 -21
- package/dist/ir/traverse.js.map +1 -1
- package/dist/ir/unshare.test.js +1 -1
- package/dist/ir/validate.js +66 -213
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.js +31 -13
- package/dist/ir/validate.test.js.map +1 -1
- package/dist/library/fence-eval.test.js +1 -1
- package/dist/library/int-infer.test.js +4 -4
- package/dist/library/library-cache.test.js +5 -5
- package/dist/library/prepare.js +5 -20
- package/dist/library/prepare.js.map +1 -1
- package/dist/native/cli.js +0 -2
- package/dist/native/cli.js.map +1 -1
- package/dist/native/compiler.test.js +2 -4
- package/dist/native/compiler.test.js.map +1 -1
- package/dist/native/prepare.js +2 -4
- package/dist/native/prepare.js.map +1 -1
- package/dist/native/sanitizer.js +2 -16
- package/dist/native/sanitizer.js.map +1 -1
- package/dist/native/sanitizer.test.js +0 -3
- package/dist/native/sanitizer.test.js.map +1 -1
- package/dist/startup-cache.js +0 -2
- package/dist/startup-cache.js.map +1 -1
- package/package.json +20 -20
- package/surface-manifest.json +80 -104
- package/dist/backend/llvm/expr-island.d.ts +0 -15
- package/dist/backend/llvm/expr-island.js +0 -709
- package/dist/backend/llvm/expr-island.js.map +0 -1
- package/dist/frontend/lowering/coercions/island.d.ts +0 -37
- package/dist/frontend/lowering/coercions/island.js +0 -350
- package/dist/frontend/lowering/coercions/island.js.map +0 -1
- package/dist/frontend/lowering/lower-island.d.ts +0 -229
- package/dist/frontend/lowering/lower-island.js.map +0 -1
- package/dist/frontend/lowering/lower-island.test.js +0 -56
- package/dist/frontend/lowering/lower-island.test.js.map +0 -1
- package/dist/frontend/npm-fetch-analysis.d.ts +0 -17
- package/dist/frontend/npm-fetch-analysis.js +0 -109
- package/dist/frontend/npm-fetch-analysis.js.map +0 -1
- package/dist/frontend/npm-fetch-analysis.test.js +0 -130
- package/dist/frontend/npm-fetch-analysis.test.js.map +0 -1
- package/dist/frontend/npm-fetch-node.d.ts +0 -2
- package/dist/frontend/npm-fetch-node.js +0 -12
- package/dist/frontend/npm-fetch-node.js.map +0 -1
- package/dist/frontend/npm-fetch-syntax.d.ts +0 -6
- package/dist/frontend/npm-fetch-syntax.js +0 -202
- package/dist/frontend/npm-fetch-syntax.js.map +0 -1
- package/dist/native/runtime-typescript.d.ts +0 -4
- package/dist/native/runtime-typescript.js +0 -74
- package/dist/native/runtime-typescript.js.map +0 -1
- package/dist/native/runtime-typescript.test.js +0 -41
- package/dist/native/runtime-typescript.test.js.map +0 -1
- /package/dist/{frontend/lowering/lower-island.test.d.ts → backend/llvm/expr-calls.test.d.ts} +0 -0
- /package/dist/{frontend/npm-fetch-analysis.test.d.ts → backend/llvm/int32-abi.test.d.ts} +0 -0
- /package/dist/{native/runtime-typescript.test.d.ts → backend/llvm/regex-matchers.test.d.ts} +0 -0
|
@@ -0,0 +1,1827 @@
|
|
|
1
|
+
import { boolLit, countedFor, numLit, varRef } from "../../../ir/build.js";
|
|
2
|
+
import { BOOL, F64, VOID, typeEquals, typeKey, } from "../../../ir/ir.js";
|
|
3
|
+
import { everyExprChild, everyStmtChild, everyStmtList, transformStmtList, } from "../../../ir/traverse.js";
|
|
4
|
+
import { arrayIndexPresent, arrayValueRead, arrayValueStore, arrayValueType, } from "../array-values.js";
|
|
5
|
+
const PRODUCERS = new Set(["map", "filter", "flatMap", "flatMapArray"]);
|
|
6
|
+
const TERMINALS = new Set([
|
|
7
|
+
"reduce",
|
|
8
|
+
"some",
|
|
9
|
+
"every",
|
|
10
|
+
"findIndex",
|
|
11
|
+
"forEach",
|
|
12
|
+
]);
|
|
13
|
+
function emptyEffects() {
|
|
14
|
+
return { throws: false, loops: false, reads: new Set(), writes: new Set() };
|
|
15
|
+
}
|
|
16
|
+
function mergeEffects(into, from) {
|
|
17
|
+
into.throws ||= from.throws;
|
|
18
|
+
into.loops ||= from.loops;
|
|
19
|
+
for (const id of from.reads)
|
|
20
|
+
into.reads.add(id);
|
|
21
|
+
for (const id of from.writes)
|
|
22
|
+
into.writes.add(id);
|
|
23
|
+
}
|
|
24
|
+
const PURE_MATH = new Set([
|
|
25
|
+
"math.abs",
|
|
26
|
+
"math.acos",
|
|
27
|
+
"math.acosh",
|
|
28
|
+
"math.asin",
|
|
29
|
+
"math.asinh",
|
|
30
|
+
"math.atan",
|
|
31
|
+
"math.atan2",
|
|
32
|
+
"math.atanh",
|
|
33
|
+
"math.cbrt",
|
|
34
|
+
"math.ceil",
|
|
35
|
+
"math.clz32",
|
|
36
|
+
"math.cos",
|
|
37
|
+
"math.cosh",
|
|
38
|
+
"math.exp",
|
|
39
|
+
"math.expm1",
|
|
40
|
+
"math.floor",
|
|
41
|
+
"math.fround",
|
|
42
|
+
"math.imul",
|
|
43
|
+
"math.log",
|
|
44
|
+
"math.log10",
|
|
45
|
+
"math.log1p",
|
|
46
|
+
"math.log2",
|
|
47
|
+
"math.max",
|
|
48
|
+
"math.min",
|
|
49
|
+
"math.pow",
|
|
50
|
+
"math.round",
|
|
51
|
+
"math.sign",
|
|
52
|
+
"math.sin",
|
|
53
|
+
"math.sinh",
|
|
54
|
+
"math.sqrt",
|
|
55
|
+
"math.tan",
|
|
56
|
+
"math.tanh",
|
|
57
|
+
"math.trunc",
|
|
58
|
+
]);
|
|
59
|
+
/** String methods that cannot throw or call user code on string/number
|
|
60
|
+
* arguments. */
|
|
61
|
+
const PURE_STRING = new Set([
|
|
62
|
+
"length",
|
|
63
|
+
"charCodeAt",
|
|
64
|
+
"charAt",
|
|
65
|
+
"indexOf",
|
|
66
|
+
"includes",
|
|
67
|
+
"startsWith",
|
|
68
|
+
"endsWith",
|
|
69
|
+
"slice",
|
|
70
|
+
"substring",
|
|
71
|
+
"trim",
|
|
72
|
+
"trimStart",
|
|
73
|
+
"trimEnd",
|
|
74
|
+
"toLowerCase",
|
|
75
|
+
"toUpperCase",
|
|
76
|
+
"isWellFormed",
|
|
77
|
+
"toWellFormed",
|
|
78
|
+
"cpAt",
|
|
79
|
+
]);
|
|
80
|
+
function primitive(t) {
|
|
81
|
+
return t.kind === "f64" || t.kind === "string" || t.kind === "bool";
|
|
82
|
+
}
|
|
83
|
+
class CallbackEffects {
|
|
84
|
+
fns;
|
|
85
|
+
/** The unique function that creates each closure, or null when several do. */
|
|
86
|
+
creators = new Map();
|
|
87
|
+
memo = new Map();
|
|
88
|
+
active = new Set();
|
|
89
|
+
globals;
|
|
90
|
+
constructor(functions, globals) {
|
|
91
|
+
this.fns = new Map(functions.map((fn) => [fn.name, fn]));
|
|
92
|
+
this.globals = new Map(globals.map((g) => [g.id, g]));
|
|
93
|
+
for (const fn of functions) {
|
|
94
|
+
everyStmtList(fn.body, {
|
|
95
|
+
expr: (e) => {
|
|
96
|
+
if (e.kind === "closure") {
|
|
97
|
+
const prior = this.creators.get(e.fnName);
|
|
98
|
+
this.creators.set(e.fnName, prior === undefined || prior === fn ? fn : null);
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
},
|
|
102
|
+
stmt: () => true,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
fn(name) {
|
|
107
|
+
return this.fns.get(name);
|
|
108
|
+
}
|
|
109
|
+
scope(fn, all) {
|
|
110
|
+
return {
|
|
111
|
+
fn,
|
|
112
|
+
locals: new Map(fn.locals.map((l) => [l.id, l])),
|
|
113
|
+
captures: new Set((fn.captures ?? []).map((c) => c.localId)),
|
|
114
|
+
all,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Effects of calling `fn`, or null when they are not modeled. */
|
|
118
|
+
ofFunction(fn) {
|
|
119
|
+
const cached = this.memo.get(fn.name);
|
|
120
|
+
if (cached !== undefined)
|
|
121
|
+
return cached;
|
|
122
|
+
if (this.active.has(fn.name))
|
|
123
|
+
return null; // recursion
|
|
124
|
+
if (fn.async || fn.generator || fn.classCaptures?.length) {
|
|
125
|
+
this.memo.set(fn.name, null);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
this.active.add(fn.name);
|
|
129
|
+
const effects = emptyEffects();
|
|
130
|
+
const scope = this.scope(fn, false);
|
|
131
|
+
const ok = fn.body.every((s) => this.stmt(s, scope, effects));
|
|
132
|
+
this.active.delete(fn.name);
|
|
133
|
+
const result = ok ? effects : null;
|
|
134
|
+
this.memo.set(fn.name, result);
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
/** Effects of evaluating `e` in `scope`; false when not modeled. */
|
|
138
|
+
expr(e, scope, out) {
|
|
139
|
+
const child = (c) => this.expr(c, scope, out);
|
|
140
|
+
const stmt = (s) => this.stmt(s, scope, out);
|
|
141
|
+
switch (e.kind) {
|
|
142
|
+
case "numLit":
|
|
143
|
+
case "strLit":
|
|
144
|
+
case "boolLit":
|
|
145
|
+
case "unitLit":
|
|
146
|
+
return true;
|
|
147
|
+
case "varRef":
|
|
148
|
+
return this.read(e.localId, scope, out);
|
|
149
|
+
case "assignExpr":
|
|
150
|
+
return this.write(e.localId, scope, out) && child(e.value);
|
|
151
|
+
case "incDec":
|
|
152
|
+
return this.read(e.localId, scope, out) && this.write(e.localId, scope, out);
|
|
153
|
+
case "bin":
|
|
154
|
+
return (e.left.type.kind !== "bigint" &&
|
|
155
|
+
e.right.type.kind !== "bigint" &&
|
|
156
|
+
child(e.left) &&
|
|
157
|
+
child(e.right));
|
|
158
|
+
case "logical":
|
|
159
|
+
case "ternary":
|
|
160
|
+
case "nullish":
|
|
161
|
+
case "strConcat":
|
|
162
|
+
case "strEq":
|
|
163
|
+
case "strCmp":
|
|
164
|
+
case "toBool":
|
|
165
|
+
case "recordGet":
|
|
166
|
+
case "recordLit":
|
|
167
|
+
case "recordClone":
|
|
168
|
+
case "fieldGet":
|
|
169
|
+
case "unionWrap":
|
|
170
|
+
case "unionNarrow":
|
|
171
|
+
case "unionIsTag":
|
|
172
|
+
case "unionDisc":
|
|
173
|
+
case "upcast":
|
|
174
|
+
case "seqExpr":
|
|
175
|
+
return everyExprChild(e, child, stmt);
|
|
176
|
+
case "unary":
|
|
177
|
+
return primitive(e.operand.type) && child(e.operand);
|
|
178
|
+
case "toString":
|
|
179
|
+
return (primitive(e.operand.type) || e.operand.type.kind === "union") && child(e.operand);
|
|
180
|
+
case "arrayLit":
|
|
181
|
+
return !e.spreads?.length && everyExprChild(e, child, stmt);
|
|
182
|
+
case "arrIntrinsic":
|
|
183
|
+
return e.method === "length" && e.receiver.type.kind === "array" && child(e.receiver);
|
|
184
|
+
case "strIntrinsic":
|
|
185
|
+
return (PURE_STRING.has(e.method) &&
|
|
186
|
+
e.args.every((a) => a.type.kind === "f64" || a.type.kind === "string") &&
|
|
187
|
+
everyExprChild(e, child, stmt));
|
|
188
|
+
case "libCall":
|
|
189
|
+
if (e.fn === "error.nodeThrow") {
|
|
190
|
+
out.throws = true;
|
|
191
|
+
return everyExprChild(e, child, stmt);
|
|
192
|
+
}
|
|
193
|
+
if (e.fn === "error.new")
|
|
194
|
+
return everyExprChild(e, child, stmt);
|
|
195
|
+
return PURE_MATH.has(e.fn) && everyExprChild(e, child, stmt);
|
|
196
|
+
case "closure":
|
|
197
|
+
return !e.adapts;
|
|
198
|
+
case "call": {
|
|
199
|
+
const callee = this.fns.get(e.callee);
|
|
200
|
+
if (!callee || callee.captures?.length)
|
|
201
|
+
return false;
|
|
202
|
+
const effects = this.ofFunction(callee);
|
|
203
|
+
if (!effects)
|
|
204
|
+
return false;
|
|
205
|
+
mergeEffects(out, effects);
|
|
206
|
+
return e.args.every(child);
|
|
207
|
+
}
|
|
208
|
+
case "callValue": {
|
|
209
|
+
if (e.receiver)
|
|
210
|
+
return false;
|
|
211
|
+
const callee = this.resolve(e.callee, scope.fn, 0);
|
|
212
|
+
if (!callee)
|
|
213
|
+
return false;
|
|
214
|
+
const effects = this.ofFunction(callee);
|
|
215
|
+
if (!effects)
|
|
216
|
+
return false;
|
|
217
|
+
mergeEffects(out, effects);
|
|
218
|
+
return child(e.callee) && e.args.every(child);
|
|
219
|
+
}
|
|
220
|
+
default:
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
stmt(s, scope, out) {
|
|
225
|
+
const child = (c) => this.expr(c, scope, out);
|
|
226
|
+
const stmt = (c) => this.stmt(c, scope, out);
|
|
227
|
+
switch (s.kind) {
|
|
228
|
+
case "varDecl":
|
|
229
|
+
return s.init === null || child(s.init);
|
|
230
|
+
case "assign":
|
|
231
|
+
return this.write(s.localId, scope, out) && child(s.value);
|
|
232
|
+
case "throw":
|
|
233
|
+
out.throws = true;
|
|
234
|
+
return child(s.value);
|
|
235
|
+
case "while":
|
|
236
|
+
case "doWhile":
|
|
237
|
+
case "for":
|
|
238
|
+
out.loops = true;
|
|
239
|
+
return everyStmtChild(s, child, stmt);
|
|
240
|
+
case "exprStmt":
|
|
241
|
+
case "return":
|
|
242
|
+
case "if":
|
|
243
|
+
case "block":
|
|
244
|
+
case "break":
|
|
245
|
+
case "continue":
|
|
246
|
+
return everyStmtChild(s, child, stmt);
|
|
247
|
+
default:
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
read(id, scope, out) {
|
|
252
|
+
const local = scope.locals.get(id);
|
|
253
|
+
if (local) {
|
|
254
|
+
if (local.tdz)
|
|
255
|
+
out.throws = true;
|
|
256
|
+
if (scope.all || scope.captures.has(id))
|
|
257
|
+
out.reads.add(id);
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
const global = this.globals.get(id);
|
|
261
|
+
if (!global)
|
|
262
|
+
return false;
|
|
263
|
+
if (global.tdz || global.initFlag)
|
|
264
|
+
out.throws = true;
|
|
265
|
+
out.reads.add(id);
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
write(id, scope, out) {
|
|
269
|
+
const local = scope.locals.get(id);
|
|
270
|
+
if (local) {
|
|
271
|
+
if (local.tdz)
|
|
272
|
+
out.throws = true;
|
|
273
|
+
if (scope.all || scope.captures.has(id))
|
|
274
|
+
out.writes.add(id);
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
const global = this.globals.get(id);
|
|
278
|
+
if (!global)
|
|
279
|
+
return false;
|
|
280
|
+
if (global.tdz || global.initFlag)
|
|
281
|
+
out.throws = true;
|
|
282
|
+
out.writes.add(id);
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
/** The one function a function-valued expression evaluated in `fn` can
|
|
286
|
+
* hold: a closure literal, or a never-reassigned binding initialized by
|
|
287
|
+
* one (directly, or through a call whose every return is the same
|
|
288
|
+
* closure literal). Null when unknown. */
|
|
289
|
+
resolve(e, fn, depth) {
|
|
290
|
+
if (depth > 4)
|
|
291
|
+
return null;
|
|
292
|
+
if (e.kind === "closure") {
|
|
293
|
+
const target = this.fns.get(e.fnName);
|
|
294
|
+
return target && !e.adapts ? target : null;
|
|
295
|
+
}
|
|
296
|
+
if (e.kind !== "varRef")
|
|
297
|
+
return null;
|
|
298
|
+
const id = e.localId;
|
|
299
|
+
if (!fn.locals.some((l) => l.id === id))
|
|
300
|
+
return null;
|
|
301
|
+
if (fn.params.some((p) => p.localId === id))
|
|
302
|
+
return null;
|
|
303
|
+
if ((fn.captures ?? []).some((c) => c.localId === id)) {
|
|
304
|
+
const creator = this.creators.get(fn.name);
|
|
305
|
+
return creator ? this.resolveBinding(id, creator, depth + 1) : null;
|
|
306
|
+
}
|
|
307
|
+
return this.resolveBinding(id, fn, depth + 1);
|
|
308
|
+
}
|
|
309
|
+
resolveBinding(id, fn, depth) {
|
|
310
|
+
const local = fn.locals.find((l) => l.id === id);
|
|
311
|
+
if (!local || local.mutable || local.tdz)
|
|
312
|
+
return null;
|
|
313
|
+
if (fn.params.some((p) => p.localId === id))
|
|
314
|
+
return null;
|
|
315
|
+
let init = null;
|
|
316
|
+
let definitions = 0;
|
|
317
|
+
everyStmtList(fn.body, {
|
|
318
|
+
expr: (node) => {
|
|
319
|
+
if ((node.kind === "assignExpr" || node.kind === "incDec") && node.localId === id)
|
|
320
|
+
definitions += 2;
|
|
321
|
+
return true;
|
|
322
|
+
},
|
|
323
|
+
stmt: (node) => {
|
|
324
|
+
if (node.kind === "varDecl" && node.localId === id) {
|
|
325
|
+
definitions++;
|
|
326
|
+
init = node.init;
|
|
327
|
+
}
|
|
328
|
+
else if ((node.kind === "assign" || node.kind === "forOf") && node.localId === id) {
|
|
329
|
+
definitions += 2;
|
|
330
|
+
}
|
|
331
|
+
return true;
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
const value = init;
|
|
335
|
+
if (definitions !== 1 || value === null)
|
|
336
|
+
return null;
|
|
337
|
+
return this.resolveValue(value, fn, depth);
|
|
338
|
+
}
|
|
339
|
+
resolveValue(e, fn, depth) {
|
|
340
|
+
if (e.kind === "closure" || e.kind === "varRef")
|
|
341
|
+
return this.resolve(e, fn, depth);
|
|
342
|
+
if (e.kind !== "call")
|
|
343
|
+
return null;
|
|
344
|
+
const factory = this.fns.get(e.callee);
|
|
345
|
+
if (!factory || factory.captures?.length || factory.async || factory.generator)
|
|
346
|
+
return null;
|
|
347
|
+
let target = null;
|
|
348
|
+
let returns = 0;
|
|
349
|
+
const ok = everyStmtList(factory.body, {
|
|
350
|
+
expr: () => true,
|
|
351
|
+
stmt: (node) => {
|
|
352
|
+
if (node.kind !== "return")
|
|
353
|
+
return true;
|
|
354
|
+
returns++;
|
|
355
|
+
const value = node.value;
|
|
356
|
+
if (value?.kind !== "closure" || value.adapts)
|
|
357
|
+
return false;
|
|
358
|
+
if (target !== null && target !== value.fnName)
|
|
359
|
+
return false;
|
|
360
|
+
target = value.fnName;
|
|
361
|
+
return true;
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
const name = target;
|
|
365
|
+
if (!ok || returns === 0 || name === null)
|
|
366
|
+
return null;
|
|
367
|
+
return this.fns.get(name) ?? null;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function isFunc(e) {
|
|
371
|
+
return e !== undefined && e.type.kind === "func";
|
|
372
|
+
}
|
|
373
|
+
function stageOf(lowerer, e) {
|
|
374
|
+
if (e.kind !== "call")
|
|
375
|
+
return null;
|
|
376
|
+
const info = lowerer.arrHofInfo.get(e.callee);
|
|
377
|
+
if (!info)
|
|
378
|
+
return null;
|
|
379
|
+
const expected = 2 + (info.method === "reduce" && info.hasInit ? 1 : 0) + (info.split ? 1 : 0);
|
|
380
|
+
if (e.args.length !== expected)
|
|
381
|
+
return null;
|
|
382
|
+
const receiver = e.args[0];
|
|
383
|
+
const slow = e.args[1];
|
|
384
|
+
const fast = info.split ? e.args[e.args.length - 1] : undefined;
|
|
385
|
+
if (!isFunc(slow) || (info.split && !isFunc(fast)))
|
|
386
|
+
return null;
|
|
387
|
+
if (receiver.type.kind !== "array" || !typeEquals(receiver.type.elem, info.elem))
|
|
388
|
+
return null;
|
|
389
|
+
return {
|
|
390
|
+
stage: { info, slow, fast: fast && isFunc(fast) ? fast : null, loc: e.loc },
|
|
391
|
+
receiver,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function matchChain(lowerer, e) {
|
|
395
|
+
let terminal;
|
|
396
|
+
let receiver;
|
|
397
|
+
const stages = [];
|
|
398
|
+
if (e.kind === "arrIntrinsic" && e.method === "length" && e.args.length === 0) {
|
|
399
|
+
if (e.receiver.type.kind !== "array")
|
|
400
|
+
return null;
|
|
401
|
+
terminal = { kind: "length" };
|
|
402
|
+
receiver = e.receiver;
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
const top = stageOf(lowerer, e);
|
|
406
|
+
if (!top)
|
|
407
|
+
return null;
|
|
408
|
+
receiver = top.receiver;
|
|
409
|
+
if (TERMINALS.has(top.stage.info.method)) {
|
|
410
|
+
const init = top.stage.info.method === "reduce" && top.stage.info.hasInit && e.kind === "call"
|
|
411
|
+
? e.args[2]
|
|
412
|
+
: null;
|
|
413
|
+
terminal = { kind: "call", stage: top.stage, init };
|
|
414
|
+
}
|
|
415
|
+
else if (PRODUCERS.has(top.stage.info.method)) {
|
|
416
|
+
terminal = { kind: "collect" };
|
|
417
|
+
stages.push(top.stage);
|
|
418
|
+
}
|
|
419
|
+
else
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
for (;;) {
|
|
423
|
+
const inner = stageOf(lowerer, receiver);
|
|
424
|
+
if (!inner || !PRODUCERS.has(inner.stage.info.method))
|
|
425
|
+
break;
|
|
426
|
+
stages.unshift(inner.stage);
|
|
427
|
+
receiver = inner.receiver;
|
|
428
|
+
}
|
|
429
|
+
let slice = null;
|
|
430
|
+
if (receiver.kind === "arrIntrinsic" &&
|
|
431
|
+
receiver.method === "slice" &&
|
|
432
|
+
receiver.args.length <= 2 &&
|
|
433
|
+
receiver.receiver.type.kind === "array" &&
|
|
434
|
+
receiver.type.kind === "array" &&
|
|
435
|
+
typeEquals(receiver.type, receiver.receiver.type) &&
|
|
436
|
+
receiver.args.every((a) => a.type.kind === "f64")) {
|
|
437
|
+
slice = { start: receiver.args[0] ?? null, end: receiver.args[1] ?? null };
|
|
438
|
+
receiver = receiver.receiver;
|
|
439
|
+
}
|
|
440
|
+
if (receiver.type.kind !== "array")
|
|
441
|
+
return null;
|
|
442
|
+
const removed = stages.length + (slice ? 1 : 0) - (terminal.kind === "collect" ? 1 : 0);
|
|
443
|
+
if (removed < 1)
|
|
444
|
+
return null;
|
|
445
|
+
return {
|
|
446
|
+
source: receiver,
|
|
447
|
+
sourceType: receiver.type,
|
|
448
|
+
slice,
|
|
449
|
+
stages,
|
|
450
|
+
terminal,
|
|
451
|
+
type: e.type,
|
|
452
|
+
loc: e.loc,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
/** Fuse chains of array-HOF helper calls into one loop.
|
|
456
|
+
*
|
|
457
|
+
* `xs.filter(f).map(g).reduce(h, z)` lowers to nested helper calls, each
|
|
458
|
+
* materializing a full intermediate array. When every callback's effects
|
|
459
|
+
* are modeled (see CallbackEffects) and reordering them cannot be
|
|
460
|
+
* observed, the chain becomes one synthetic helper that runs each element
|
|
461
|
+
* through all stages before reading the next, and never builds the
|
|
462
|
+
* intermediate arrays. The rules, from strictest to weakest need:
|
|
463
|
+
*
|
|
464
|
+
* - No callback writes the heap, calls host code or an unknown function,
|
|
465
|
+
* or recurses. Allocation and reads are fine: with no heap writes, every
|
|
466
|
+
* read sees the same value in either order.
|
|
467
|
+
* - Callbacks may assign shared bindings (captured locals, globals), but
|
|
468
|
+
* no other callback in the chain reads or assigns those bindings.
|
|
469
|
+
* - At most one callback may throw (or contain a loop, which might not
|
|
470
|
+
* terminate); if one does, no other callback assigns anything. A throw
|
|
471
|
+
* then leaves the same observable state in either order.
|
|
472
|
+
* - some/every/findIndex stop early, so the stages before them must not
|
|
473
|
+
* throw, loop or assign at all: the unfused chain would have run them on
|
|
474
|
+
* every element.
|
|
475
|
+
* - Index parameters receive the position in the array the unfused
|
|
476
|
+
* stage would have visited: the source index while only maps (which
|
|
477
|
+
* keep holes in place) precede it, else a count of the values that
|
|
478
|
+
* reached the stage. Only the first stage over an unsliced source may
|
|
479
|
+
* take the array parameter; any other would name an intermediate array.
|
|
480
|
+
* - Holes: map and slice preserve them, filter/flatMap drop them; every
|
|
481
|
+
* consumer here skips them except findIndex, which is not fused when a
|
|
482
|
+
* hole can reach it. Lengths are read once, before any callback, and no
|
|
483
|
+
* callback can write an array.
|
|
484
|
+
*
|
|
485
|
+
* A `const` holding a chain that is read once, as the receiver of the next
|
|
486
|
+
* statement's consumer, is folded into that consumer when the rest of that
|
|
487
|
+
* statement is free of effects. */
|
|
488
|
+
export function fuseArrayPipelines(lowerer, functions) {
|
|
489
|
+
if (lowerer.arrHofInfo.size === 0)
|
|
490
|
+
return;
|
|
491
|
+
const pass = new PipelineFusion(lowerer, functions);
|
|
492
|
+
for (const fn of functions.slice())
|
|
493
|
+
pass.run(fn);
|
|
494
|
+
functions.push(...pass.added);
|
|
495
|
+
}
|
|
496
|
+
class PipelineFusion {
|
|
497
|
+
lowerer;
|
|
498
|
+
added = [];
|
|
499
|
+
effects;
|
|
500
|
+
helpers = new Map();
|
|
501
|
+
constructor(lowerer, functions) {
|
|
502
|
+
this.lowerer = lowerer;
|
|
503
|
+
this.effects = new CallbackEffects(functions, lowerer.globalsList);
|
|
504
|
+
}
|
|
505
|
+
run(fn) {
|
|
506
|
+
const infos = this.lowerer.arrHofInfo;
|
|
507
|
+
const candidate = !everyStmtList(fn.body, {
|
|
508
|
+
expr: (e) => !(e.kind === "call" && infos.has(e.callee)),
|
|
509
|
+
stmt: () => true,
|
|
510
|
+
});
|
|
511
|
+
if (!candidate)
|
|
512
|
+
return;
|
|
513
|
+
const removed = new Set();
|
|
514
|
+
let body = this.lists(fn.body, fn, removed);
|
|
515
|
+
body = transformStmtList(body, {
|
|
516
|
+
stmt: (s) => s,
|
|
517
|
+
expr: (e) => this.fuse(e, fn) ?? e,
|
|
518
|
+
});
|
|
519
|
+
fn.body = body;
|
|
520
|
+
if (removed.size > 0)
|
|
521
|
+
fn.locals = fn.locals.filter((l) => !removed.has(l.id));
|
|
522
|
+
}
|
|
523
|
+
/* ── statement lists: fold single-use chain bindings ── */
|
|
524
|
+
lists(list, fn, removed) {
|
|
525
|
+
const nested = list.map((s) => this.nested(s, fn, removed));
|
|
526
|
+
const out = [];
|
|
527
|
+
for (let i = 0; i < nested.length; i++) {
|
|
528
|
+
let current = nested[i];
|
|
529
|
+
while (i + 1 < nested.length) {
|
|
530
|
+
const merged = this.fold(current, nested[i + 1], fn);
|
|
531
|
+
if (!merged)
|
|
532
|
+
break;
|
|
533
|
+
removed.add(current.localId);
|
|
534
|
+
current = merged;
|
|
535
|
+
i++;
|
|
536
|
+
}
|
|
537
|
+
out.push(current);
|
|
538
|
+
}
|
|
539
|
+
return out;
|
|
540
|
+
}
|
|
541
|
+
nested(s, fn, removed) {
|
|
542
|
+
const l = (body) => this.lists(body, fn, removed);
|
|
543
|
+
switch (s.kind) {
|
|
544
|
+
case "if":
|
|
545
|
+
return { ...s, then: l(s.then), else_: s.else_ === null ? null : l(s.else_) };
|
|
546
|
+
case "while":
|
|
547
|
+
case "doWhile":
|
|
548
|
+
case "for":
|
|
549
|
+
case "forOf":
|
|
550
|
+
case "block":
|
|
551
|
+
return { ...s, body: l(s.body) };
|
|
552
|
+
case "switch":
|
|
553
|
+
return { ...s, cases: s.cases.map((c) => ({ ...c, body: l(c.body) })) };
|
|
554
|
+
case "tryCatch":
|
|
555
|
+
return {
|
|
556
|
+
...s,
|
|
557
|
+
tryBody: l(s.tryBody),
|
|
558
|
+
catchBody: s.catchBody === null ? null : l(s.catchBody),
|
|
559
|
+
finallyBody: s.finallyBody === null ? null : l(s.finallyBody),
|
|
560
|
+
};
|
|
561
|
+
default:
|
|
562
|
+
return s;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
/** `const L = <chain>; <stmt using L once as a consumer receiver>`. */
|
|
566
|
+
fold(decl, next, fn) {
|
|
567
|
+
if (decl.kind !== "varDecl" || decl.init === null)
|
|
568
|
+
return null;
|
|
569
|
+
const init = decl.init;
|
|
570
|
+
const id = decl.localId;
|
|
571
|
+
const local = fn.locals.find((l) => l.id === id);
|
|
572
|
+
if (!local || local.boxed || local.tdz || !typeEquals(local.type, init.type))
|
|
573
|
+
return null;
|
|
574
|
+
if (!matchChain(this.lowerer, init) && !stageOf(this.lowerer, init)) {
|
|
575
|
+
if (!(init.kind === "arrIntrinsic" && init.method === "slice"))
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
if (useCount(fn, id) !== 1)
|
|
579
|
+
return null;
|
|
580
|
+
// Only expression statements whose value is computed before any store.
|
|
581
|
+
let root;
|
|
582
|
+
switch (next.kind) {
|
|
583
|
+
case "varDecl":
|
|
584
|
+
if (next.init === null)
|
|
585
|
+
return null;
|
|
586
|
+
root = next.init;
|
|
587
|
+
break;
|
|
588
|
+
case "assign":
|
|
589
|
+
root = next.value;
|
|
590
|
+
break;
|
|
591
|
+
case "exprStmt":
|
|
592
|
+
root = next.expr;
|
|
593
|
+
break;
|
|
594
|
+
case "return":
|
|
595
|
+
if (next.value === null)
|
|
596
|
+
return null;
|
|
597
|
+
root = next.value;
|
|
598
|
+
break;
|
|
599
|
+
default:
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
const site = locateUse(this.lowerer, root, id);
|
|
603
|
+
if (!site)
|
|
604
|
+
return null;
|
|
605
|
+
const substituted = substituteReceiver(site.node, init);
|
|
606
|
+
// The chain now runs after the rest of `next`'s operands are evaluated;
|
|
607
|
+
// those must not observe or change anything the chain touches.
|
|
608
|
+
const plan = this.plan(substituted, fn);
|
|
609
|
+
const chainArgs = matchChain(this.lowerer, substituted);
|
|
610
|
+
if (!plan || !chainArgs)
|
|
611
|
+
return null;
|
|
612
|
+
if (site.conditional && (plan.risky || plan.writes.size > 0))
|
|
613
|
+
return null;
|
|
614
|
+
const scope = this.effects.scope(fn, true);
|
|
615
|
+
const rest = emptyEffects();
|
|
616
|
+
const placeholder = { kind: "numLit", value: 0, type: F64, loc: site.node.loc };
|
|
617
|
+
if (!this.effects.expr(replaceNode(root, site.node, placeholder), scope, rest))
|
|
618
|
+
return null;
|
|
619
|
+
if (rest.throws || rest.writes.size > 0 || intersects(rest.reads, plan.writes))
|
|
620
|
+
return null;
|
|
621
|
+
const moved = emptyEffects();
|
|
622
|
+
const movedArgs = [chainArgs.source];
|
|
623
|
+
if (chainArgs.slice?.start)
|
|
624
|
+
movedArgs.push(chainArgs.slice.start);
|
|
625
|
+
if (chainArgs.slice?.end)
|
|
626
|
+
movedArgs.push(chainArgs.slice.end);
|
|
627
|
+
if (!movedArgs.every((a) => this.effects.expr(a, scope, moved)))
|
|
628
|
+
return null;
|
|
629
|
+
if (moved.throws || moved.writes.size > 0 || moved.loops)
|
|
630
|
+
return null;
|
|
631
|
+
const value = replaceNode(root, site.node, substituted);
|
|
632
|
+
switch (next.kind) {
|
|
633
|
+
case "varDecl":
|
|
634
|
+
return { ...next, init: value };
|
|
635
|
+
case "assign":
|
|
636
|
+
return { ...next, value };
|
|
637
|
+
case "exprStmt":
|
|
638
|
+
return { ...next, expr: value };
|
|
639
|
+
case "return":
|
|
640
|
+
return { ...next, value };
|
|
641
|
+
}
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
/* ── expressions ── */
|
|
645
|
+
fuse(e, fn) {
|
|
646
|
+
const plan = this.plan(e, fn);
|
|
647
|
+
return plan ? this.emit(plan) : null;
|
|
648
|
+
}
|
|
649
|
+
/** Decide whether `e` is a fusable chain; null when it is not. */
|
|
650
|
+
plan(e, fn) {
|
|
651
|
+
const chain = matchChain(this.lowerer, e);
|
|
652
|
+
if (!chain)
|
|
653
|
+
return null;
|
|
654
|
+
const { stages, terminal } = chain;
|
|
655
|
+
const terminalStage = terminal.kind === "call" ? terminal.stage : null;
|
|
656
|
+
const callbacks = terminalStage ? [...stages, terminalStage] : stages;
|
|
657
|
+
// Element types must line up stage to stage.
|
|
658
|
+
let elem = chain.sourceType.elem;
|
|
659
|
+
for (const stage of callbacks) {
|
|
660
|
+
if (!typeEquals(stage.info.elem, elem))
|
|
661
|
+
return null;
|
|
662
|
+
if (stage.info.method === "filter" && !typeEquals(stage.info.outElem, stage.info.elem))
|
|
663
|
+
return null;
|
|
664
|
+
if ((stage.info.method === "map" || stage.info.method === "flatMap") &&
|
|
665
|
+
!typeEquals(stage.info.outElem, stage.info.fnRet))
|
|
666
|
+
return null;
|
|
667
|
+
elem = stage.info.outElem;
|
|
668
|
+
}
|
|
669
|
+
// Index arguments are reproduced; an array argument only names the source.
|
|
670
|
+
for (let k = 0; k < callbacks.length; k++) {
|
|
671
|
+
const stage = callbacks[k];
|
|
672
|
+
const lead = stage.info.method === "reduce" ? 1 : 0;
|
|
673
|
+
if (stage.info.arity > lead + 3)
|
|
674
|
+
return null;
|
|
675
|
+
if (stage.info.arity === lead + 3 && (k !== 0 || chain.slice))
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
if (terminalStage?.info.method === "reduce" && !terminalStage.info.hasInit)
|
|
679
|
+
return null;
|
|
680
|
+
const holes = holeCarrying(chain);
|
|
681
|
+
if (terminalStage?.info.method === "findIndex" && holes)
|
|
682
|
+
return null;
|
|
683
|
+
// Effects of each callback, in chain order.
|
|
684
|
+
const scope = this.effects.scope(fn, true);
|
|
685
|
+
const summaries = [];
|
|
686
|
+
const argReads = emptyEffects();
|
|
687
|
+
for (const stage of callbacks) {
|
|
688
|
+
for (const arg of [stage.slow, stage.fast]) {
|
|
689
|
+
if (!arg)
|
|
690
|
+
continue;
|
|
691
|
+
if (!this.effects.expr(arg, scope, argReads))
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
const target = this.effects.resolve(stage.fast ?? stage.slow, fn, 0);
|
|
695
|
+
const summary = target ? this.effects.ofFunction(target) : null;
|
|
696
|
+
if (!summary)
|
|
697
|
+
return null;
|
|
698
|
+
if (stage.fast) {
|
|
699
|
+
const slow = this.effects.resolve(stage.slow, fn, 0);
|
|
700
|
+
const slowSummary = slow ? this.effects.ofFunction(slow) : null;
|
|
701
|
+
if (!slowSummary)
|
|
702
|
+
return null;
|
|
703
|
+
mergeEffects(summary, slowSummary);
|
|
704
|
+
}
|
|
705
|
+
summaries.push(summary);
|
|
706
|
+
}
|
|
707
|
+
if (argReads.throws || argReads.writes.size > 0)
|
|
708
|
+
return null;
|
|
709
|
+
const init = terminal.kind === "call" ? terminal.init : null;
|
|
710
|
+
if (init) {
|
|
711
|
+
if (!this.effects.expr(init, scope, argReads))
|
|
712
|
+
return null;
|
|
713
|
+
if (argReads.throws || argReads.writes.size > 0 || argReads.loops)
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
const writes = new Set();
|
|
717
|
+
let riskyCount = 0;
|
|
718
|
+
for (const s of summaries) {
|
|
719
|
+
for (const id of s.writes)
|
|
720
|
+
writes.add(id);
|
|
721
|
+
if (s.throws || s.loops)
|
|
722
|
+
riskyCount++;
|
|
723
|
+
}
|
|
724
|
+
if (riskyCount > 1)
|
|
725
|
+
return null;
|
|
726
|
+
for (let j = 0; j < summaries.length; j++) {
|
|
727
|
+
const sj = summaries[j];
|
|
728
|
+
if (riskyCount === 1 && !(sj.throws || sj.loops) && sj.writes.size > 0)
|
|
729
|
+
return null;
|
|
730
|
+
for (let k = 0; k < summaries.length; k++) {
|
|
731
|
+
if (j === k)
|
|
732
|
+
continue;
|
|
733
|
+
const sk = summaries[k];
|
|
734
|
+
if (intersects(sj.writes, sk.reads) || intersects(sj.writes, sk.writes))
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (intersects(argReads.reads, writes))
|
|
739
|
+
return null;
|
|
740
|
+
const early = terminalStage !== null &&
|
|
741
|
+
(terminalStage.info.method === "some" ||
|
|
742
|
+
terminalStage.info.method === "every" ||
|
|
743
|
+
terminalStage.info.method === "findIndex");
|
|
744
|
+
if (early) {
|
|
745
|
+
for (let k = 0; k < stages.length; k++) {
|
|
746
|
+
const s = summaries[k];
|
|
747
|
+
if (s.throws || s.loops || s.writes.size > 0)
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return { chain, callbacks, writes, risky: riskyCount > 0 };
|
|
752
|
+
}
|
|
753
|
+
/* ── helper construction ── */
|
|
754
|
+
/** A closure-literal callback whose body can be copied into the loop:
|
|
755
|
+
* small, no nested closures, and its captured bindings only read (they
|
|
756
|
+
* are passed in by value, read when the fused call starts; no callback
|
|
757
|
+
* in the chain assigns a binding another one reads). */
|
|
758
|
+
inlineTarget = (arg) => {
|
|
759
|
+
if (arg.kind !== "closure" || arg.adapts)
|
|
760
|
+
return null;
|
|
761
|
+
const fn = this.effects.fn(arg.fnName);
|
|
762
|
+
if (!fn || fn.async || fn.generator || fn.classCaptures?.length)
|
|
763
|
+
return null;
|
|
764
|
+
const captures = new Set((fn.captures ?? []).map((c) => c.localId));
|
|
765
|
+
if (captures.size !== arg.captures.length)
|
|
766
|
+
return null;
|
|
767
|
+
const effects = this.effects.ofFunction(fn);
|
|
768
|
+
if (!effects || intersects(effects.writes, captures))
|
|
769
|
+
return null;
|
|
770
|
+
if (fn.locals.some((l) => l.tdz || (l.boxed && !captures.has(l.id))))
|
|
771
|
+
return null;
|
|
772
|
+
let size = 0;
|
|
773
|
+
const small = everyStmtList(fn.body, {
|
|
774
|
+
expr: (e) => ++size <= MAX_INLINE_NODES && e.kind !== "closure" && e.kind !== "selfRef",
|
|
775
|
+
stmt: (s) => ++size <= MAX_INLINE_NODES && s.kind !== "tryCatch",
|
|
776
|
+
});
|
|
777
|
+
return small ? fn : null;
|
|
778
|
+
};
|
|
779
|
+
emit(plan) {
|
|
780
|
+
const built = new FusedLoop(this.lowerer, plan.chain, this.inlineTarget).build();
|
|
781
|
+
if (!built)
|
|
782
|
+
return null;
|
|
783
|
+
let name = this.helpers.get(built.key);
|
|
784
|
+
if (!name) {
|
|
785
|
+
name = `%arr.fused.${this.helpers.size}`;
|
|
786
|
+
this.helpers.set(built.key, name);
|
|
787
|
+
this.added.push({ ...built.fn, name });
|
|
788
|
+
}
|
|
789
|
+
return {
|
|
790
|
+
kind: "call",
|
|
791
|
+
callee: name,
|
|
792
|
+
args: built.args,
|
|
793
|
+
type: plan.chain.type,
|
|
794
|
+
loc: plan.chain.loc,
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
function intersects(a, b) {
|
|
799
|
+
for (const id of a)
|
|
800
|
+
if (b.has(id))
|
|
801
|
+
return true;
|
|
802
|
+
return false;
|
|
803
|
+
}
|
|
804
|
+
/** Map and slice keep holes in place; filter and flatMap drop them. */
|
|
805
|
+
function holeCarrying(chain) {
|
|
806
|
+
return chain.stages.every((s) => s.info.method === "map");
|
|
807
|
+
}
|
|
808
|
+
function useCount(fn, id) {
|
|
809
|
+
let uses = 0;
|
|
810
|
+
everyStmtList(fn.body, {
|
|
811
|
+
expr: (e) => {
|
|
812
|
+
switch (e.kind) {
|
|
813
|
+
case "varRef":
|
|
814
|
+
case "assignExpr":
|
|
815
|
+
case "incDec":
|
|
816
|
+
if (e.localId === id)
|
|
817
|
+
uses++;
|
|
818
|
+
break;
|
|
819
|
+
case "closure":
|
|
820
|
+
if (e.captures.includes(id))
|
|
821
|
+
uses += 2;
|
|
822
|
+
break;
|
|
823
|
+
}
|
|
824
|
+
return true;
|
|
825
|
+
},
|
|
826
|
+
stmt: (s) => {
|
|
827
|
+
if ((s.kind === "assign" || s.kind === "forOf") && s.localId === id)
|
|
828
|
+
uses += 2;
|
|
829
|
+
if (s.kind === "varDecl" && s.localId === id && s.init === null)
|
|
830
|
+
uses += 2;
|
|
831
|
+
return true;
|
|
832
|
+
},
|
|
833
|
+
});
|
|
834
|
+
return uses;
|
|
835
|
+
}
|
|
836
|
+
/** The consumer node in `root` whose receiver is `varRef id`, and whether
|
|
837
|
+
* it is evaluated only conditionally. */
|
|
838
|
+
function locateUse(lowerer, root, id) {
|
|
839
|
+
let found = null;
|
|
840
|
+
const isUse = (e) => {
|
|
841
|
+
const receiver = e.kind === "arrIntrinsic" && e.method === "length"
|
|
842
|
+
? e.receiver
|
|
843
|
+
: e.kind === "call" && lowerer.arrHofInfo.has(e.callee)
|
|
844
|
+
? e.args[0]
|
|
845
|
+
: undefined;
|
|
846
|
+
return receiver?.kind === "varRef" && receiver.localId === id;
|
|
847
|
+
};
|
|
848
|
+
const walk = (e, conditional) => {
|
|
849
|
+
if (isUse(e)) {
|
|
850
|
+
found = { node: e, conditional };
|
|
851
|
+
return false;
|
|
852
|
+
}
|
|
853
|
+
const branching = conditional ||
|
|
854
|
+
e.kind === "ternary" ||
|
|
855
|
+
e.kind === "logical" ||
|
|
856
|
+
e.kind === "nullish" ||
|
|
857
|
+
e.kind === "orDefault" ||
|
|
858
|
+
e.kind === "optChain" ||
|
|
859
|
+
e.kind === "seqExpr" ||
|
|
860
|
+
e.kind === "closure";
|
|
861
|
+
return everyExprChild(e, (c) => walk(c, branching && !(e.kind === "ternary" && c === e.cond)), () => true);
|
|
862
|
+
};
|
|
863
|
+
walk(root, false);
|
|
864
|
+
return found;
|
|
865
|
+
}
|
|
866
|
+
function substituteReceiver(node, receiver) {
|
|
867
|
+
if (node.kind === "arrIntrinsic")
|
|
868
|
+
return { ...node, receiver };
|
|
869
|
+
if (node.kind === "call")
|
|
870
|
+
return { ...node, args: [receiver, ...node.args.slice(1)] };
|
|
871
|
+
return node;
|
|
872
|
+
}
|
|
873
|
+
function replaceNode(root, target, replacement) {
|
|
874
|
+
if (root === target)
|
|
875
|
+
return replacement;
|
|
876
|
+
return transformExprOnce(root, target, replacement);
|
|
877
|
+
}
|
|
878
|
+
function transformExprOnce(root, target, replacement) {
|
|
879
|
+
const [stmt] = transformStmtList([{ kind: "exprStmt", expr: root, loc: root.loc }], {
|
|
880
|
+
stmt: (s) => s,
|
|
881
|
+
expr: (e) => (e === target ? replacement : e),
|
|
882
|
+
});
|
|
883
|
+
return stmt.expr;
|
|
884
|
+
}
|
|
885
|
+
const MAX_INLINE_NODES = 400;
|
|
886
|
+
class FusedLoop {
|
|
887
|
+
lowerer;
|
|
888
|
+
chain;
|
|
889
|
+
inlineTarget;
|
|
890
|
+
params = [];
|
|
891
|
+
locals = [];
|
|
892
|
+
args = [];
|
|
893
|
+
keyParts = [];
|
|
894
|
+
callees = [];
|
|
895
|
+
loc;
|
|
896
|
+
next = 0;
|
|
897
|
+
failed = false;
|
|
898
|
+
constructor(lowerer, chain, inlineTarget) {
|
|
899
|
+
this.lowerer = lowerer;
|
|
900
|
+
this.chain = chain;
|
|
901
|
+
this.inlineTarget = inlineTarget;
|
|
902
|
+
this.loc = chain.loc;
|
|
903
|
+
}
|
|
904
|
+
fresh(name) {
|
|
905
|
+
return `${name}.${this.next++}`;
|
|
906
|
+
}
|
|
907
|
+
local(name, type, mutable = false) {
|
|
908
|
+
const id = this.fresh(name);
|
|
909
|
+
this.locals.push({ id, name, type, mutable });
|
|
910
|
+
return id;
|
|
911
|
+
}
|
|
912
|
+
param(name, value, key) {
|
|
913
|
+
const id = this.fresh(name);
|
|
914
|
+
this.params.push({ localId: id, name, type: value.type });
|
|
915
|
+
this.locals.push({ id, name, type: value.type, mutable: false });
|
|
916
|
+
this.args.push(value);
|
|
917
|
+
this.keyParts.push(`${key}:${typeKey(value.type)}`);
|
|
918
|
+
return varRef(id, value.type, this.loc);
|
|
919
|
+
}
|
|
920
|
+
/** How the loop reaches one callback argument; decided at first use. */
|
|
921
|
+
callee(arg) {
|
|
922
|
+
const known = this.callees.find((c) => c.arg === arg);
|
|
923
|
+
if (known)
|
|
924
|
+
return known;
|
|
925
|
+
const target = this.inlineTarget(arg);
|
|
926
|
+
let callee;
|
|
927
|
+
if (target && arg.kind === "closure") {
|
|
928
|
+
const captures = new Map();
|
|
929
|
+
(target.captures ?? []).forEach((capture, index) => {
|
|
930
|
+
const source = arg.captures[index];
|
|
931
|
+
const ref = this.param("cap", varRef(source, capture.type, this.loc), `cap@${target.name}`);
|
|
932
|
+
captures.set(capture.localId, ref.localId);
|
|
933
|
+
});
|
|
934
|
+
this.keyParts.push(`inline@${target.name}`);
|
|
935
|
+
callee = { arg, ref: null, inline: target, captures };
|
|
936
|
+
}
|
|
937
|
+
else {
|
|
938
|
+
const site = arg.kind === "closure" ? `@${arg.fnName}` : "";
|
|
939
|
+
callee = { arg, ref: this.param("f", arg, `cb${site}`), inline: null, captures: null };
|
|
940
|
+
}
|
|
941
|
+
this.callees.push(callee);
|
|
942
|
+
return callee;
|
|
943
|
+
}
|
|
944
|
+
/** Statements and the value of calling `callee` with `args`. */
|
|
945
|
+
invoke(callee, args, ret) {
|
|
946
|
+
const loc = this.loc;
|
|
947
|
+
if (!callee.inline) {
|
|
948
|
+
return {
|
|
949
|
+
pre: [],
|
|
950
|
+
value: { kind: "callValue", callee: callee.ref, args, type: ret, loc },
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
const fn = callee.inline;
|
|
954
|
+
if (fn.params.length !== args.length) {
|
|
955
|
+
this.failed = true;
|
|
956
|
+
return { pre: [], value: { kind: "numLit", value: 0, type: F64, loc } };
|
|
957
|
+
}
|
|
958
|
+
const renamed = new Map(callee.captures);
|
|
959
|
+
// A parameter the body never assigns can name its argument's local
|
|
960
|
+
// directly: the loop's value locals are not assigned while the
|
|
961
|
+
// inlined body runs.
|
|
962
|
+
const assigned = new Set();
|
|
963
|
+
everyStmtList(fn.body, {
|
|
964
|
+
expr: (e) => {
|
|
965
|
+
if (e.kind === "assignExpr" || e.kind === "incDec")
|
|
966
|
+
assigned.add(e.localId);
|
|
967
|
+
return true;
|
|
968
|
+
},
|
|
969
|
+
stmt: (st) => {
|
|
970
|
+
if (st.kind === "assign" || st.kind === "forOf")
|
|
971
|
+
assigned.add(st.localId);
|
|
972
|
+
return true;
|
|
973
|
+
},
|
|
974
|
+
});
|
|
975
|
+
const aliased = new Set();
|
|
976
|
+
fn.params.forEach((p, index) => {
|
|
977
|
+
const arg = args[index];
|
|
978
|
+
if (arg.kind === "varRef" && !assigned.has(p.localId) && typeEquals(arg.type, p.type)) {
|
|
979
|
+
renamed.set(p.localId, arg.localId);
|
|
980
|
+
aliased.add(p.localId);
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
for (const l of fn.locals) {
|
|
984
|
+
if (renamed.has(l.id))
|
|
985
|
+
continue;
|
|
986
|
+
const id = this.local(l.name, l.type, true);
|
|
987
|
+
renamed.set(l.id, id);
|
|
988
|
+
}
|
|
989
|
+
const labels = new Map();
|
|
990
|
+
const exit = this.fresh("%fused.exit");
|
|
991
|
+
const label = (name) => {
|
|
992
|
+
let id = labels.get(name);
|
|
993
|
+
if (!id) {
|
|
994
|
+
id = this.fresh("%fused.label");
|
|
995
|
+
labels.set(name, id);
|
|
996
|
+
}
|
|
997
|
+
return id;
|
|
998
|
+
};
|
|
999
|
+
const result = fn.returnType.kind === "void" ? null : this.local("ret", fn.returnType, true);
|
|
1000
|
+
const rename = (id) => renamed.get(id) ?? id;
|
|
1001
|
+
const body = transformStmtList(fn.body, {
|
|
1002
|
+
expr: (e) => {
|
|
1003
|
+
switch (e.kind) {
|
|
1004
|
+
case "varRef":
|
|
1005
|
+
case "incDec":
|
|
1006
|
+
case "assignExpr":
|
|
1007
|
+
return { ...e, localId: rename(e.localId) };
|
|
1008
|
+
default:
|
|
1009
|
+
return e;
|
|
1010
|
+
}
|
|
1011
|
+
},
|
|
1012
|
+
stmt: (s) => {
|
|
1013
|
+
switch (s.kind) {
|
|
1014
|
+
case "return": {
|
|
1015
|
+
const exitStmt = { kind: "break", label: exit, loc: s.loc };
|
|
1016
|
+
if (s.value === null || result === null) {
|
|
1017
|
+
return s.value === null
|
|
1018
|
+
? exitStmt
|
|
1019
|
+
: {
|
|
1020
|
+
kind: "block",
|
|
1021
|
+
body: [{ kind: "exprStmt", expr: s.value, loc: s.loc }, exitStmt],
|
|
1022
|
+
loc: s.loc,
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
return {
|
|
1026
|
+
kind: "block",
|
|
1027
|
+
body: [{ kind: "assign", localId: result, value: s.value, loc: s.loc }, exitStmt],
|
|
1028
|
+
loc: s.loc,
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
case "varDecl":
|
|
1032
|
+
case "assign":
|
|
1033
|
+
return { ...s, localId: rename(s.localId) };
|
|
1034
|
+
case "break":
|
|
1035
|
+
case "continue":
|
|
1036
|
+
return s.label && s.label !== exit ? { ...s, label: label(s.label) } : s;
|
|
1037
|
+
case "while":
|
|
1038
|
+
case "doWhile":
|
|
1039
|
+
case "for":
|
|
1040
|
+
case "block":
|
|
1041
|
+
return s.labels ? { ...s, labels: s.labels.map(label) } : s;
|
|
1042
|
+
default:
|
|
1043
|
+
return s;
|
|
1044
|
+
}
|
|
1045
|
+
},
|
|
1046
|
+
});
|
|
1047
|
+
const bind = fn.params.flatMap((p, index) => aliased.has(p.localId)
|
|
1048
|
+
? []
|
|
1049
|
+
: [{ kind: "varDecl", localId: rename(p.localId), init: args[index], loc }]);
|
|
1050
|
+
const pre = [];
|
|
1051
|
+
if (result !== null)
|
|
1052
|
+
pre.push({ kind: "varDecl", localId: result, init: null, loc });
|
|
1053
|
+
pre.push({ kind: "block", labels: [exit], body: [...bind, ...body], loc });
|
|
1054
|
+
return {
|
|
1055
|
+
pre,
|
|
1056
|
+
value: result === null
|
|
1057
|
+
? { kind: "numLit", value: 0, type: F64, loc }
|
|
1058
|
+
: varRef(result, fn.returnType, loc),
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
build() {
|
|
1062
|
+
const { chain, loc } = this;
|
|
1063
|
+
const arrT = chain.sourceType;
|
|
1064
|
+
const a = this.param("a", chain.source, "src");
|
|
1065
|
+
const n = this.local("n", F64);
|
|
1066
|
+
const body = [
|
|
1067
|
+
{
|
|
1068
|
+
kind: "varDecl",
|
|
1069
|
+
localId: n,
|
|
1070
|
+
init: { kind: "arrIntrinsic", method: "length", receiver: a, args: [], type: F64, loc },
|
|
1071
|
+
loc,
|
|
1072
|
+
},
|
|
1073
|
+
];
|
|
1074
|
+
const nRef = varRef(n, F64, loc);
|
|
1075
|
+
// Slice bounds: ToIntegerOrInfinity, then relative to the length.
|
|
1076
|
+
let lo = numLit(0, loc);
|
|
1077
|
+
let hi = nRef;
|
|
1078
|
+
if (chain.slice) {
|
|
1079
|
+
const bound = (value, name, fallback) => {
|
|
1080
|
+
if (!value)
|
|
1081
|
+
return fallback;
|
|
1082
|
+
const raw = this.param(name, value, name);
|
|
1083
|
+
const t = this.local(`${name}Int`, F64);
|
|
1084
|
+
const at = this.local(`${name}At`, F64);
|
|
1085
|
+
const tRef = varRef(t, F64, loc);
|
|
1086
|
+
const sum = { kind: "bin", op: "+", left: nRef, right: tRef, type: F64, loc };
|
|
1087
|
+
const lt = (left, right) => ({
|
|
1088
|
+
kind: "bin",
|
|
1089
|
+
op: "<",
|
|
1090
|
+
left,
|
|
1091
|
+
right,
|
|
1092
|
+
type: BOOL,
|
|
1093
|
+
loc,
|
|
1094
|
+
});
|
|
1095
|
+
const pick = (cond, then, else_) => ({
|
|
1096
|
+
kind: "ternary",
|
|
1097
|
+
cond,
|
|
1098
|
+
then,
|
|
1099
|
+
else_,
|
|
1100
|
+
type: F64,
|
|
1101
|
+
loc,
|
|
1102
|
+
});
|
|
1103
|
+
body.push({
|
|
1104
|
+
kind: "varDecl",
|
|
1105
|
+
localId: t,
|
|
1106
|
+
init: pick({ kind: "bin", op: "!==", left: raw, right: raw, type: BOOL, loc }, numLit(0, loc), { kind: "libCall", fn: "math.trunc", args: [raw], type: F64, loc }),
|
|
1107
|
+
loc,
|
|
1108
|
+
}, {
|
|
1109
|
+
kind: "varDecl",
|
|
1110
|
+
localId: at,
|
|
1111
|
+
init: pick(lt(tRef, numLit(0, loc)), pick(lt(sum, numLit(0, loc)), numLit(0, loc), sum), pick(lt(nRef, tRef), nRef, tRef)),
|
|
1112
|
+
loc,
|
|
1113
|
+
});
|
|
1114
|
+
return varRef(at, F64, loc);
|
|
1115
|
+
};
|
|
1116
|
+
lo = bound(chain.slice.start, "start", numLit(0, loc));
|
|
1117
|
+
hi = bound(chain.slice.end, "end", nRef);
|
|
1118
|
+
this.keyParts.push(`slice:${chain.slice.start ? 1 : 0}${chain.slice.end ? 1 : 0}`);
|
|
1119
|
+
}
|
|
1120
|
+
const terminalStage = chain.terminal.kind === "call" ? chain.terminal.stage : null;
|
|
1121
|
+
for (const stage of terminalStage ? [...chain.stages, terminalStage] : chain.stages) {
|
|
1122
|
+
this.keyParts.push(`${stage.info.method}:${typeKey(stage.info.elem)}:${typeKey(stage.info.fnRet)}:${stage.info.arity}:${stage.info.present ? "p" : ""}`);
|
|
1123
|
+
}
|
|
1124
|
+
const holes = holeCarrying(chain);
|
|
1125
|
+
const resultT = chain.type;
|
|
1126
|
+
const terminal = chain.terminal;
|
|
1127
|
+
const epilogue = [];
|
|
1128
|
+
// Terminal state. The reduce seed is a parameter evaluated after the
|
|
1129
|
+
// callbacks, like the unfused call's argument.
|
|
1130
|
+
let acc = null;
|
|
1131
|
+
let count = null;
|
|
1132
|
+
let out = null;
|
|
1133
|
+
const stateDecls = [];
|
|
1134
|
+
if (terminal.kind === "call" && terminal.stage.info.method === "reduce") {
|
|
1135
|
+
acc = this.local("acc", terminal.stage.info.outElem, true);
|
|
1136
|
+
epilogue.push({ kind: "return", value: varRef(acc, terminal.stage.info.outElem, loc), loc });
|
|
1137
|
+
}
|
|
1138
|
+
else if (terminal.kind === "length" ||
|
|
1139
|
+
(terminal.kind === "call" && terminal.stage.info.method === "findIndex")) {
|
|
1140
|
+
count = this.local("count", F64, true);
|
|
1141
|
+
stateDecls.push({ kind: "varDecl", localId: count, init: numLit(0, loc), loc });
|
|
1142
|
+
epilogue.push({
|
|
1143
|
+
kind: "return",
|
|
1144
|
+
value: terminal.kind === "length" ? varRef(count, F64, loc) : numLit(-1, loc),
|
|
1145
|
+
loc,
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
else if (terminal.kind === "collect") {
|
|
1149
|
+
out = this.local("out", resultT);
|
|
1150
|
+
stateDecls.push({
|
|
1151
|
+
kind: "varDecl",
|
|
1152
|
+
localId: out,
|
|
1153
|
+
init: { kind: "arrayLit", elems: [], type: resultT, loc },
|
|
1154
|
+
loc,
|
|
1155
|
+
});
|
|
1156
|
+
if (holes) {
|
|
1157
|
+
stateDecls.push({
|
|
1158
|
+
kind: "arraySetLength",
|
|
1159
|
+
arr: varRef(out, resultT, loc),
|
|
1160
|
+
length: {
|
|
1161
|
+
kind: "ternary",
|
|
1162
|
+
cond: { kind: "bin", op: "<", left: lo, right: hi, type: BOOL, loc },
|
|
1163
|
+
then: { kind: "bin", op: "-", left: hi, right: lo, type: F64, loc },
|
|
1164
|
+
else_: numLit(0, loc),
|
|
1165
|
+
type: F64,
|
|
1166
|
+
loc,
|
|
1167
|
+
},
|
|
1168
|
+
loc,
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
epilogue.push({ kind: "return", value: varRef(out, resultT, loc), loc });
|
|
1172
|
+
}
|
|
1173
|
+
else if (terminal.kind === "call") {
|
|
1174
|
+
const method = terminal.stage.info.method;
|
|
1175
|
+
if (method === "some" || method === "every")
|
|
1176
|
+
epilogue.push({ kind: "return", value: boolLit(method === "every", loc), loc });
|
|
1177
|
+
}
|
|
1178
|
+
const i = varRef("i.0", F64, loc);
|
|
1179
|
+
this.locals.push({ id: "i.0", name: "i", type: F64, mutable: true });
|
|
1180
|
+
const slotRead = () => ({ kind: "arrayGet", arr: a, index: i, type: arrT.elem, loc });
|
|
1181
|
+
const outIndex = () => chain.slice ? { kind: "bin", op: "-", left: i, right: lo, type: F64, loc } : i;
|
|
1182
|
+
const counters = new Map();
|
|
1183
|
+
/** The index argument for the value now reaching callback `k`. */
|
|
1184
|
+
const indexArg = (k, pre) => {
|
|
1185
|
+
if (chain.stages.slice(0, k).every((s) => s.info.method === "map"))
|
|
1186
|
+
return outIndex();
|
|
1187
|
+
let counter = counters.get(k);
|
|
1188
|
+
if (counter === undefined) {
|
|
1189
|
+
counter = this.local("index", F64, true);
|
|
1190
|
+
counters.set(k, counter);
|
|
1191
|
+
stateDecls.push({ kind: "varDecl", localId: counter, init: numLit(0, loc), loc });
|
|
1192
|
+
}
|
|
1193
|
+
const ix = this.local("ix", F64);
|
|
1194
|
+
pre.push({ kind: "varDecl", localId: ix, init: varRef(counter, F64, loc), loc }, increment(counter, loc));
|
|
1195
|
+
return varRef(ix, F64, loc);
|
|
1196
|
+
};
|
|
1197
|
+
/** Call callback `k` with `lead` arguments, a value, and the index and
|
|
1198
|
+
* array arguments it declares. */
|
|
1199
|
+
const deliver = (stage, k, lead, flow) => {
|
|
1200
|
+
const arity = stage.info.arity;
|
|
1201
|
+
const value = varRef(flow.id, flow.type, loc);
|
|
1202
|
+
const at = lead.length;
|
|
1203
|
+
const pick = () => {
|
|
1204
|
+
if (arity <= at)
|
|
1205
|
+
return { arg: stage.slow, value: null };
|
|
1206
|
+
const fast = stage.fast;
|
|
1207
|
+
if (fast && typeEquals(fast.type.params[at], flow.type))
|
|
1208
|
+
return { arg: fast, value };
|
|
1209
|
+
const paramT = stage.slow.type.params[at];
|
|
1210
|
+
if (typeEquals(paramT, flow.type))
|
|
1211
|
+
return { arg: stage.slow, value };
|
|
1212
|
+
if (paramT.kind === "union" && flow.type.kind !== "union") {
|
|
1213
|
+
const tag = this.lowerer.armTag(paramT.unionId, flow.type);
|
|
1214
|
+
if (tag >= 0)
|
|
1215
|
+
return {
|
|
1216
|
+
arg: stage.slow,
|
|
1217
|
+
value: { kind: "unionWrap", unionId: paramT.unionId, tag, value, type: paramT, loc },
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
return null;
|
|
1221
|
+
};
|
|
1222
|
+
const chosen = pick();
|
|
1223
|
+
if (!chosen)
|
|
1224
|
+
return null;
|
|
1225
|
+
const before = [];
|
|
1226
|
+
const args = chosen.value ? [...lead, chosen.value] : lead.slice();
|
|
1227
|
+
if (chosen.value && arity > args.length)
|
|
1228
|
+
args.push(indexArg(k, before));
|
|
1229
|
+
if (chosen.value && arity > args.length)
|
|
1230
|
+
args.push(a);
|
|
1231
|
+
const called = this.invoke(this.callee(chosen.arg), args.slice(0, arity), chosen.arg.type.ret);
|
|
1232
|
+
return { pre: [...before, ...called.pre], value: called.value };
|
|
1233
|
+
};
|
|
1234
|
+
const fail = () => {
|
|
1235
|
+
this.failed = true;
|
|
1236
|
+
return [];
|
|
1237
|
+
};
|
|
1238
|
+
/** Turn the source slot into a value for the callback of `stage`. */
|
|
1239
|
+
const read = (stage, rest) => {
|
|
1240
|
+
if (stage.info.present) {
|
|
1241
|
+
const v = this.local("v", arrT.elem);
|
|
1242
|
+
return [
|
|
1243
|
+
{ kind: "varDecl", localId: v, init: slotRead(), loc },
|
|
1244
|
+
...rest({ kind: "value", id: v, type: arrT.elem }),
|
|
1245
|
+
];
|
|
1246
|
+
}
|
|
1247
|
+
const valueT = arrayValueType(this.lowerer, arrT.elem);
|
|
1248
|
+
if (stage.fast) {
|
|
1249
|
+
// The split visit: a value goes on as the plain element, a present
|
|
1250
|
+
// undefined as the undefined arm.
|
|
1251
|
+
const missing = this.lowerer.wrappedUndefined(valueT, loc);
|
|
1252
|
+
if (!missing)
|
|
1253
|
+
return fail();
|
|
1254
|
+
const v = this.local("v", arrT.elem);
|
|
1255
|
+
const u = this.local("u", valueT);
|
|
1256
|
+
return [
|
|
1257
|
+
{
|
|
1258
|
+
kind: "if",
|
|
1259
|
+
cond: {
|
|
1260
|
+
kind: "bin",
|
|
1261
|
+
op: "===",
|
|
1262
|
+
left: { kind: "arrayState", arr: a, index: i, type: F64, loc },
|
|
1263
|
+
right: numLit(1, loc),
|
|
1264
|
+
type: BOOL,
|
|
1265
|
+
loc,
|
|
1266
|
+
},
|
|
1267
|
+
then: [
|
|
1268
|
+
{ kind: "varDecl", localId: v, init: slotRead(), loc },
|
|
1269
|
+
...rest({ kind: "value", id: v, type: arrT.elem }),
|
|
1270
|
+
],
|
|
1271
|
+
else_: [
|
|
1272
|
+
{ kind: "varDecl", localId: u, init: missing, loc },
|
|
1273
|
+
...rest({ kind: "value", id: u, type: valueT }),
|
|
1274
|
+
],
|
|
1275
|
+
loc,
|
|
1276
|
+
},
|
|
1277
|
+
];
|
|
1278
|
+
}
|
|
1279
|
+
const u = this.local("u", valueT);
|
|
1280
|
+
return [
|
|
1281
|
+
{
|
|
1282
|
+
kind: "varDecl",
|
|
1283
|
+
localId: u,
|
|
1284
|
+
init: arrayValueRead(this.lowerer, a, i, arrT.elem, loc),
|
|
1285
|
+
loc,
|
|
1286
|
+
},
|
|
1287
|
+
...rest({ kind: "value", id: u, type: valueT }),
|
|
1288
|
+
];
|
|
1289
|
+
};
|
|
1290
|
+
/** The statements that run one value through stages k.. and the terminal. */
|
|
1291
|
+
const visit = (k, flow) => {
|
|
1292
|
+
if (k === chain.stages.length)
|
|
1293
|
+
return finish(flow);
|
|
1294
|
+
const stage = chain.stages[k];
|
|
1295
|
+
const info = stage.info;
|
|
1296
|
+
if (flow.kind === "slot")
|
|
1297
|
+
return read(stage, (value) => visit(k, value));
|
|
1298
|
+
const called = deliver(stage, k, [], flow);
|
|
1299
|
+
if (!called)
|
|
1300
|
+
return fail();
|
|
1301
|
+
const { pre, value: result } = called;
|
|
1302
|
+
switch (info.method) {
|
|
1303
|
+
case "map":
|
|
1304
|
+
case "flatMap": {
|
|
1305
|
+
const id = this.local("m", info.fnRet);
|
|
1306
|
+
return [
|
|
1307
|
+
...pre,
|
|
1308
|
+
{ kind: "varDecl", localId: id, init: result, loc },
|
|
1309
|
+
...visit(k + 1, { kind: "value", id, type: info.fnRet }),
|
|
1310
|
+
];
|
|
1311
|
+
}
|
|
1312
|
+
case "filter":
|
|
1313
|
+
return [
|
|
1314
|
+
...pre,
|
|
1315
|
+
{
|
|
1316
|
+
kind: "if",
|
|
1317
|
+
cond: result.type.kind === "bool"
|
|
1318
|
+
? result
|
|
1319
|
+
: { kind: "toBool", operand: result, type: BOOL, loc },
|
|
1320
|
+
then: visit(k + 1, flow),
|
|
1321
|
+
else_: null,
|
|
1322
|
+
loc,
|
|
1323
|
+
},
|
|
1324
|
+
];
|
|
1325
|
+
case "flatMapArray": {
|
|
1326
|
+
const r = this.local("r", info.fnRet);
|
|
1327
|
+
const m = this.local("len", F64);
|
|
1328
|
+
const j = this.local("j", F64, true);
|
|
1329
|
+
const inner = info.fnRet.elem;
|
|
1330
|
+
const rRef = varRef(r, info.fnRet, loc);
|
|
1331
|
+
const jRef = varRef(j, F64, loc);
|
|
1332
|
+
let each;
|
|
1333
|
+
if (k + 1 === chain.stages.length && terminal.kind === "length") {
|
|
1334
|
+
each = finish({ kind: "slot" });
|
|
1335
|
+
}
|
|
1336
|
+
else {
|
|
1337
|
+
const w = this.local("w", arrayValueType(this.lowerer, inner));
|
|
1338
|
+
const element = arrayValueRead(this.lowerer, rRef, jRef, inner, loc);
|
|
1339
|
+
each = [
|
|
1340
|
+
{ kind: "varDecl", localId: w, init: element, loc },
|
|
1341
|
+
...visit(k + 1, { kind: "value", id: w, type: element.type }),
|
|
1342
|
+
];
|
|
1343
|
+
}
|
|
1344
|
+
return [
|
|
1345
|
+
...pre,
|
|
1346
|
+
{ kind: "varDecl", localId: r, init: result, loc },
|
|
1347
|
+
{
|
|
1348
|
+
kind: "varDecl",
|
|
1349
|
+
localId: m,
|
|
1350
|
+
init: {
|
|
1351
|
+
kind: "arrIntrinsic",
|
|
1352
|
+
method: "length",
|
|
1353
|
+
receiver: rRef,
|
|
1354
|
+
args: [],
|
|
1355
|
+
type: F64,
|
|
1356
|
+
loc,
|
|
1357
|
+
},
|
|
1358
|
+
loc,
|
|
1359
|
+
},
|
|
1360
|
+
{
|
|
1361
|
+
kind: "for",
|
|
1362
|
+
init: { kind: "varDecl", localId: j, init: numLit(0, loc), loc },
|
|
1363
|
+
cond: {
|
|
1364
|
+
kind: "bin",
|
|
1365
|
+
op: "<",
|
|
1366
|
+
left: jRef,
|
|
1367
|
+
right: varRef(m, F64, loc),
|
|
1368
|
+
type: BOOL,
|
|
1369
|
+
loc,
|
|
1370
|
+
},
|
|
1371
|
+
update: {
|
|
1372
|
+
kind: "assign",
|
|
1373
|
+
localId: j,
|
|
1374
|
+
value: { kind: "bin", op: "+", left: jRef, right: numLit(1, loc), type: F64, loc },
|
|
1375
|
+
loc,
|
|
1376
|
+
},
|
|
1377
|
+
body: [
|
|
1378
|
+
{
|
|
1379
|
+
kind: "if",
|
|
1380
|
+
cond: arrayIndexPresent(rRef, jRef, loc),
|
|
1381
|
+
then: each,
|
|
1382
|
+
else_: null,
|
|
1383
|
+
loc,
|
|
1384
|
+
},
|
|
1385
|
+
],
|
|
1386
|
+
loc,
|
|
1387
|
+
},
|
|
1388
|
+
];
|
|
1389
|
+
}
|
|
1390
|
+
default:
|
|
1391
|
+
return fail();
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
/** The terminal's action for one value. */
|
|
1395
|
+
const finish = (flow) => {
|
|
1396
|
+
if (terminal.kind === "length")
|
|
1397
|
+
return [increment(count, loc)];
|
|
1398
|
+
if (terminal.kind === "collect") {
|
|
1399
|
+
if (flow.kind !== "value")
|
|
1400
|
+
return fail();
|
|
1401
|
+
const last = chain.stages[chain.stages.length - 1];
|
|
1402
|
+
const outT = resultT;
|
|
1403
|
+
if (!typeEquals(outT.elem, last.info.outElem))
|
|
1404
|
+
return fail();
|
|
1405
|
+
const outRef = varRef(out, resultT, loc);
|
|
1406
|
+
const index = holes
|
|
1407
|
+
? outIndex()
|
|
1408
|
+
: { kind: "arrIntrinsic", method: "length", receiver: outRef, args: [], type: F64, loc };
|
|
1409
|
+
return [
|
|
1410
|
+
arrayValueStore(this.lowerer, outRef, index, varRef(flow.id, flow.type, loc), outT.elem, loc),
|
|
1411
|
+
];
|
|
1412
|
+
}
|
|
1413
|
+
const stage = terminal.stage;
|
|
1414
|
+
const method = stage.info.method;
|
|
1415
|
+
if (flow.kind === "slot")
|
|
1416
|
+
return read(stage, (value) => finish(value));
|
|
1417
|
+
if (method === "reduce") {
|
|
1418
|
+
const accT = stage.info.outElem;
|
|
1419
|
+
const called = deliver(stage, chain.stages.length, [varRef(acc, accT, loc)], flow);
|
|
1420
|
+
if (!called || !typeEquals(called.value.type, accT))
|
|
1421
|
+
return fail();
|
|
1422
|
+
return [...called.pre, { kind: "assign", localId: acc, value: called.value, loc }];
|
|
1423
|
+
}
|
|
1424
|
+
const called = deliver(stage, chain.stages.length, [], flow);
|
|
1425
|
+
if (!called)
|
|
1426
|
+
return fail();
|
|
1427
|
+
const { pre, value: result } = called;
|
|
1428
|
+
if (method === "forEach") {
|
|
1429
|
+
return stage.slow.type.ret.kind === "void" && result.kind === "numLit"
|
|
1430
|
+
? pre
|
|
1431
|
+
: [...pre, { kind: "exprStmt", expr: result, loc }];
|
|
1432
|
+
}
|
|
1433
|
+
const test = predicateToBoolean(result);
|
|
1434
|
+
if (method === "some")
|
|
1435
|
+
return [
|
|
1436
|
+
...pre,
|
|
1437
|
+
{
|
|
1438
|
+
kind: "if",
|
|
1439
|
+
cond: test,
|
|
1440
|
+
then: [{ kind: "return", value: boolLit(true, loc), loc }],
|
|
1441
|
+
else_: null,
|
|
1442
|
+
loc,
|
|
1443
|
+
},
|
|
1444
|
+
];
|
|
1445
|
+
if (method === "every")
|
|
1446
|
+
return [
|
|
1447
|
+
...pre,
|
|
1448
|
+
{
|
|
1449
|
+
kind: "if",
|
|
1450
|
+
cond: { kind: "unary", op: "!", operand: test, type: BOOL, loc },
|
|
1451
|
+
then: [{ kind: "return", value: boolLit(false, loc), loc }],
|
|
1452
|
+
else_: null,
|
|
1453
|
+
loc,
|
|
1454
|
+
},
|
|
1455
|
+
];
|
|
1456
|
+
if (method === "findIndex")
|
|
1457
|
+
return [
|
|
1458
|
+
...pre,
|
|
1459
|
+
{
|
|
1460
|
+
kind: "if",
|
|
1461
|
+
cond: test,
|
|
1462
|
+
then: [{ kind: "return", value: varRef(count, F64, loc), loc }],
|
|
1463
|
+
else_: null,
|
|
1464
|
+
loc,
|
|
1465
|
+
},
|
|
1466
|
+
increment(count, loc),
|
|
1467
|
+
];
|
|
1468
|
+
return fail();
|
|
1469
|
+
};
|
|
1470
|
+
// Callbacks are parameters in stage order; the reduce seed follows them.
|
|
1471
|
+
for (const stage of terminalStage ? [...chain.stages, terminalStage] : chain.stages) {
|
|
1472
|
+
void stage;
|
|
1473
|
+
}
|
|
1474
|
+
const present = visit(0, { kind: "slot" });
|
|
1475
|
+
if (this.failed)
|
|
1476
|
+
return null;
|
|
1477
|
+
if (acc !== null && terminal.kind === "call") {
|
|
1478
|
+
const z = this.param("z", terminal.init, "init");
|
|
1479
|
+
stateDecls.unshift({ kind: "varDecl", localId: acc, init: z, loc });
|
|
1480
|
+
}
|
|
1481
|
+
body.push(...stateDecls);
|
|
1482
|
+
const hole = holes && terminal.kind === "length" ? [increment(count, loc)] : null;
|
|
1483
|
+
const loopBody = [
|
|
1484
|
+
{ kind: "if", cond: arrayIndexPresent(a, i, loc), then: present, else_: hole, loc },
|
|
1485
|
+
];
|
|
1486
|
+
const loop = chain.slice
|
|
1487
|
+
? {
|
|
1488
|
+
kind: "for",
|
|
1489
|
+
init: { kind: "varDecl", localId: "i.0", init: lo, loc },
|
|
1490
|
+
cond: { kind: "bin", op: "<", left: i, right: hi, type: BOOL, loc },
|
|
1491
|
+
update: {
|
|
1492
|
+
kind: "assign",
|
|
1493
|
+
localId: "i.0",
|
|
1494
|
+
value: { kind: "bin", op: "+", left: i, right: numLit(1, loc), type: F64, loc },
|
|
1495
|
+
loc,
|
|
1496
|
+
},
|
|
1497
|
+
body: loopBody,
|
|
1498
|
+
loc,
|
|
1499
|
+
}
|
|
1500
|
+
: countedFor(loc, nRef, () => loopBody);
|
|
1501
|
+
body.push(loop, ...epilogue);
|
|
1502
|
+
this.keyParts.push(`${terminal.kind}:${typeKey(resultT)}`);
|
|
1503
|
+
const returnType = terminal.kind === "call" && terminal.stage.info.method === "forEach" ? VOID : resultT;
|
|
1504
|
+
const fn = {
|
|
1505
|
+
params: this.params,
|
|
1506
|
+
returnType,
|
|
1507
|
+
locals: this.locals,
|
|
1508
|
+
body,
|
|
1509
|
+
loc,
|
|
1510
|
+
};
|
|
1511
|
+
foldKnownUnions(fn);
|
|
1512
|
+
scalarizeRecordTemporaries(this.lowerer, fn);
|
|
1513
|
+
return { fn, args: this.args, key: this.keyParts.join("|") };
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
function increment(id, loc) {
|
|
1517
|
+
return {
|
|
1518
|
+
kind: "assign",
|
|
1519
|
+
localId: id,
|
|
1520
|
+
value: {
|
|
1521
|
+
kind: "bin",
|
|
1522
|
+
op: "+",
|
|
1523
|
+
left: varRef(id, F64, loc),
|
|
1524
|
+
right: numLit(1, loc),
|
|
1525
|
+
type: F64,
|
|
1526
|
+
loc,
|
|
1527
|
+
},
|
|
1528
|
+
loc,
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
/** ToBoolean of a predicate result, as the some/every/findIndex helpers apply it. */
|
|
1532
|
+
function predicateToBoolean(e) {
|
|
1533
|
+
if (e.type.kind === "bool")
|
|
1534
|
+
return e;
|
|
1535
|
+
if (e.type.kind === "dyn")
|
|
1536
|
+
return { kind: "dynTest", test: "truthy", value: e, type: BOOL, loc: e.loc };
|
|
1537
|
+
return { kind: "toBool", operand: e, type: BOOL, loc: e.loc };
|
|
1538
|
+
}
|
|
1539
|
+
/** A union local the fused loop wrapped from a plain value has a known
|
|
1540
|
+
* tag: its tag tests fold, its narrowing is the plain value, and the wrap
|
|
1541
|
+
* itself goes away once nothing reads it. */
|
|
1542
|
+
function foldKnownUnions(fn) {
|
|
1543
|
+
const writes = new Map();
|
|
1544
|
+
const count = (id) => {
|
|
1545
|
+
writes.set(id, (writes.get(id) ?? 0) + 1);
|
|
1546
|
+
};
|
|
1547
|
+
everyStmtList(fn.body, {
|
|
1548
|
+
expr: (e) => {
|
|
1549
|
+
if (e.kind === "assignExpr" || e.kind === "incDec")
|
|
1550
|
+
count(e.localId);
|
|
1551
|
+
return true;
|
|
1552
|
+
},
|
|
1553
|
+
stmt: (s) => {
|
|
1554
|
+
if (s.kind === "varDecl" || s.kind === "assign" || s.kind === "forOf")
|
|
1555
|
+
count(s.localId);
|
|
1556
|
+
return true;
|
|
1557
|
+
},
|
|
1558
|
+
});
|
|
1559
|
+
const known = new Map();
|
|
1560
|
+
everyStmtList(fn.body, {
|
|
1561
|
+
expr: () => true,
|
|
1562
|
+
stmt: (s) => {
|
|
1563
|
+
if (s.kind === "varDecl" &&
|
|
1564
|
+
s.init?.kind === "unionWrap" &&
|
|
1565
|
+
s.init.value.kind === "varRef" &&
|
|
1566
|
+
writes.get(s.localId) === 1 &&
|
|
1567
|
+
writes.get(s.init.value.localId) === 1)
|
|
1568
|
+
known.set(s.localId, { tag: s.init.tag, value: s.init.value });
|
|
1569
|
+
return true;
|
|
1570
|
+
},
|
|
1571
|
+
});
|
|
1572
|
+
if (known.size === 0)
|
|
1573
|
+
return;
|
|
1574
|
+
const fold = (e) => {
|
|
1575
|
+
if (e.kind === "unionIsTag" && e.value.kind === "varRef") {
|
|
1576
|
+
const k = known.get(e.value.localId);
|
|
1577
|
+
if (k)
|
|
1578
|
+
return boolLit((k.tag === e.tag) !== e.negated, e.loc);
|
|
1579
|
+
}
|
|
1580
|
+
if (e.kind === "unionNarrow" && e.value.kind === "varRef") {
|
|
1581
|
+
const k = known.get(e.value.localId);
|
|
1582
|
+
if (k && k.tag === e.tag && typeEquals(k.value.type, e.type))
|
|
1583
|
+
return { ...k.value, loc: e.loc };
|
|
1584
|
+
}
|
|
1585
|
+
if (e.kind === "ternary") {
|
|
1586
|
+
const cond = fold(e.cond);
|
|
1587
|
+
if (cond.kind === "boolLit")
|
|
1588
|
+
return fold(cond.value ? e.then : e.else_);
|
|
1589
|
+
}
|
|
1590
|
+
return e;
|
|
1591
|
+
};
|
|
1592
|
+
let body = transformStmtList(fn.body, { stmt: (s) => s, expr: fold });
|
|
1593
|
+
const reads = new Set();
|
|
1594
|
+
everyStmtList(body, {
|
|
1595
|
+
expr: (e) => {
|
|
1596
|
+
if (e.kind === "varRef")
|
|
1597
|
+
reads.add(e.localId);
|
|
1598
|
+
return true;
|
|
1599
|
+
},
|
|
1600
|
+
stmt: () => true,
|
|
1601
|
+
});
|
|
1602
|
+
const dead = new Set([...known.keys()].filter((id) => !reads.has(id)));
|
|
1603
|
+
if (dead.size > 0) {
|
|
1604
|
+
const prune = (list) => list
|
|
1605
|
+
.filter((s) => !(s.kind === "varDecl" && dead.has(s.localId)))
|
|
1606
|
+
.map((s) => {
|
|
1607
|
+
switch (s.kind) {
|
|
1608
|
+
case "if":
|
|
1609
|
+
return { ...s, then: prune(s.then), else_: s.else_ === null ? null : prune(s.else_) };
|
|
1610
|
+
case "for":
|
|
1611
|
+
case "while":
|
|
1612
|
+
case "doWhile":
|
|
1613
|
+
case "block":
|
|
1614
|
+
return { ...s, body: prune(s.body) };
|
|
1615
|
+
default:
|
|
1616
|
+
return s;
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
body = prune(body);
|
|
1620
|
+
fn.locals = fn.locals.filter((l) => !dead.has(l.id));
|
|
1621
|
+
}
|
|
1622
|
+
fn.body = body;
|
|
1623
|
+
}
|
|
1624
|
+
/* ── per-element record temporaries ──────────────────────────────────── */
|
|
1625
|
+
/** Inside a fused loop, a record one inlined stage builds and the next
|
|
1626
|
+
* only reads field by field never needs to exist. A group of record locals
|
|
1627
|
+
* connected by copies, each defined only by a literal of their shape (or a
|
|
1628
|
+
* copy within the group) and used only through field reads, becomes one
|
|
1629
|
+
* scalar local per field. Literal fields still evaluate in source order
|
|
1630
|
+
* (through temporaries, so a literal may read the fields it replaces).
|
|
1631
|
+
* Identity cannot be observed: no use besides a field read survives. */
|
|
1632
|
+
function scalarizeRecordTemporaries(lowerer, fn) {
|
|
1633
|
+
const params = new Set(fn.params.map((p) => p.localId));
|
|
1634
|
+
const candidates = new Map();
|
|
1635
|
+
for (const l of fn.locals) {
|
|
1636
|
+
if (l.type.kind !== "record" || l.boxed || l.tdz || params.has(l.id))
|
|
1637
|
+
continue;
|
|
1638
|
+
const shape = lowerer.shapes.get(l.type.shapeId);
|
|
1639
|
+
if (!shape ||
|
|
1640
|
+
shape.tuple ||
|
|
1641
|
+
shape.indexValue ||
|
|
1642
|
+
shape.fields.length === 0 ||
|
|
1643
|
+
shape.fields.length > 8 ||
|
|
1644
|
+
!shape.fields.every((f) => f.type.kind === "f64" || f.type.kind === "bool"))
|
|
1645
|
+
continue;
|
|
1646
|
+
candidates.set(l.id, l);
|
|
1647
|
+
}
|
|
1648
|
+
if (candidates.size === 0)
|
|
1649
|
+
return;
|
|
1650
|
+
const parent = new Map();
|
|
1651
|
+
const find = (id) => {
|
|
1652
|
+
let root = id;
|
|
1653
|
+
while (parent.has(root))
|
|
1654
|
+
root = parent.get(root);
|
|
1655
|
+
return root;
|
|
1656
|
+
};
|
|
1657
|
+
const union = (x, y) => {
|
|
1658
|
+
const rx = find(x);
|
|
1659
|
+
const ry = find(y);
|
|
1660
|
+
if (rx !== ry)
|
|
1661
|
+
parent.set(rx, ry);
|
|
1662
|
+
};
|
|
1663
|
+
const bad = new Set();
|
|
1664
|
+
const sameShape = (x, y) => candidates.get(x).type.shapeId === candidates.get(y).type.shapeId;
|
|
1665
|
+
const literalOk = (e, shapeId) => e.kind === "recordLit" &&
|
|
1666
|
+
e.type.kind === "record" &&
|
|
1667
|
+
e.type.shapeId === shapeId &&
|
|
1668
|
+
e.fields.every((f) => !f.overflow && !f.drop);
|
|
1669
|
+
const expr = (e) => {
|
|
1670
|
+
if (e.kind === "recordGet" && e.obj.kind === "varRef" && candidates.has(e.obj.localId))
|
|
1671
|
+
return true;
|
|
1672
|
+
if ((e.kind === "varRef" || e.kind === "assignExpr" || e.kind === "incDec") &&
|
|
1673
|
+
candidates.has(e.localId))
|
|
1674
|
+
bad.add(e.localId);
|
|
1675
|
+
if (e.kind === "closure")
|
|
1676
|
+
for (const id of e.captures)
|
|
1677
|
+
if (candidates.has(id))
|
|
1678
|
+
bad.add(id);
|
|
1679
|
+
return everyExprChild(e, expr, stmt);
|
|
1680
|
+
};
|
|
1681
|
+
const definition = (id, value) => {
|
|
1682
|
+
const target = candidates.get(id);
|
|
1683
|
+
if (value === null)
|
|
1684
|
+
return;
|
|
1685
|
+
if (value.kind === "varRef" && candidates.has(value.localId)) {
|
|
1686
|
+
if (sameShape(id, value.localId))
|
|
1687
|
+
union(id, value.localId);
|
|
1688
|
+
else
|
|
1689
|
+
bad.add(id);
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
if (literalOk(value, target.type.shapeId)) {
|
|
1693
|
+
for (const f of value.fields)
|
|
1694
|
+
expr(f.value);
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
bad.add(id);
|
|
1698
|
+
expr(value);
|
|
1699
|
+
};
|
|
1700
|
+
const stmt = (s) => {
|
|
1701
|
+
if ((s.kind === "varDecl" || s.kind === "assign") && candidates.has(s.localId)) {
|
|
1702
|
+
definition(s.localId, s.kind === "varDecl" ? s.init : s.value);
|
|
1703
|
+
return true;
|
|
1704
|
+
}
|
|
1705
|
+
if (s.kind === "forOf" && candidates.has(s.localId))
|
|
1706
|
+
bad.add(s.localId);
|
|
1707
|
+
return everyStmtChild(s, expr, stmt);
|
|
1708
|
+
};
|
|
1709
|
+
fn.body.forEach(stmt);
|
|
1710
|
+
const badRoots = new Set([...bad].map(find));
|
|
1711
|
+
const chosen = [...candidates.keys()].filter((id) => !badRoots.has(find(id)));
|
|
1712
|
+
if (chosen.length === 0)
|
|
1713
|
+
return;
|
|
1714
|
+
const fields = new Map();
|
|
1715
|
+
const added = [];
|
|
1716
|
+
let next = 0;
|
|
1717
|
+
const used = new Set(fn.locals.map((l) => l.id));
|
|
1718
|
+
const fresh = (name) => {
|
|
1719
|
+
let id;
|
|
1720
|
+
do
|
|
1721
|
+
id = `${name}.s${next++}`;
|
|
1722
|
+
while (used.has(id));
|
|
1723
|
+
used.add(id);
|
|
1724
|
+
return id;
|
|
1725
|
+
};
|
|
1726
|
+
for (const id of chosen) {
|
|
1727
|
+
const local = candidates.get(id);
|
|
1728
|
+
const shape = lowerer.shapes.get(local.type.shapeId);
|
|
1729
|
+
const map = new Map();
|
|
1730
|
+
for (const f of shape.fields) {
|
|
1731
|
+
const fid = fresh(`${local.name}.${f.name}`);
|
|
1732
|
+
map.set(f.name, fid);
|
|
1733
|
+
added.push({ id: fid, name: `${local.name}.${f.name}`, type: f.type, mutable: true });
|
|
1734
|
+
}
|
|
1735
|
+
fields.set(id, map);
|
|
1736
|
+
}
|
|
1737
|
+
const fieldType = (id, field) => lowerer.shapes.get(candidates.get(id).type.shapeId).fields.find((f) => f.name === field)
|
|
1738
|
+
.type;
|
|
1739
|
+
const rewriteExpr = (e) => {
|
|
1740
|
+
const [s] = transformStmtList([{ kind: "exprStmt", expr: e, loc: e.loc }], {
|
|
1741
|
+
stmt: (x) => x,
|
|
1742
|
+
expr: (x) => {
|
|
1743
|
+
if (x.kind === "recordGet" && x.obj.kind === "varRef") {
|
|
1744
|
+
const id = fields.get(x.obj.localId)?.get(x.field);
|
|
1745
|
+
if (id)
|
|
1746
|
+
return { kind: "varRef", localId: id, type: x.type, loc: x.loc };
|
|
1747
|
+
}
|
|
1748
|
+
return x;
|
|
1749
|
+
},
|
|
1750
|
+
});
|
|
1751
|
+
return s.expr;
|
|
1752
|
+
};
|
|
1753
|
+
const rewriteList = (list) => list.flatMap(rewriteStmt);
|
|
1754
|
+
const rewriteStmt = (s) => {
|
|
1755
|
+
if ((s.kind === "varDecl" || s.kind === "assign") && fields.has(s.localId)) {
|
|
1756
|
+
const map = fields.get(s.localId);
|
|
1757
|
+
const value = s.kind === "varDecl" ? s.init : s.value;
|
|
1758
|
+
const store = (fid, v) => s.kind === "varDecl"
|
|
1759
|
+
? { kind: "varDecl", localId: fid, init: v, loc: s.loc }
|
|
1760
|
+
: { kind: "assign", localId: fid, value: v, loc: s.loc };
|
|
1761
|
+
if (value === null)
|
|
1762
|
+
return [...map.values()].map((fid) => store(fid, null));
|
|
1763
|
+
if (value.kind === "varRef") {
|
|
1764
|
+
const from = fields.get(value.localId);
|
|
1765
|
+
return [...map].map(([name, fid]) => store(fid, varRef(from.get(name), fieldType(s.localId, name), s.loc)));
|
|
1766
|
+
}
|
|
1767
|
+
const literal = value;
|
|
1768
|
+
const temps = [];
|
|
1769
|
+
const values = new Map();
|
|
1770
|
+
for (const f of literal.fields) {
|
|
1771
|
+
const t = fresh(`${candidates.get(s.localId).name}.${f.name}.t`);
|
|
1772
|
+
const type = fieldType(s.localId, f.name);
|
|
1773
|
+
added.push({ id: t, name: f.name, type, mutable: false });
|
|
1774
|
+
temps.push({ kind: "varDecl", localId: t, init: rewriteExpr(f.value), loc: s.loc });
|
|
1775
|
+
values.set(f.name, varRef(t, type, s.loc));
|
|
1776
|
+
}
|
|
1777
|
+
return [...temps, ...[...map].map(([name, fid]) => store(fid, values.get(name)))];
|
|
1778
|
+
}
|
|
1779
|
+
return rewriteNested(s);
|
|
1780
|
+
};
|
|
1781
|
+
const rewriteNested = (s) => {
|
|
1782
|
+
switch (s.kind) {
|
|
1783
|
+
case "if":
|
|
1784
|
+
return [
|
|
1785
|
+
{
|
|
1786
|
+
...s,
|
|
1787
|
+
cond: rewriteExpr(s.cond),
|
|
1788
|
+
then: rewriteList(s.then),
|
|
1789
|
+
else_: s.else_ === null ? null : rewriteList(s.else_),
|
|
1790
|
+
},
|
|
1791
|
+
];
|
|
1792
|
+
case "while":
|
|
1793
|
+
return [{ ...s, cond: rewriteExpr(s.cond), body: rewriteList(s.body) }];
|
|
1794
|
+
case "doWhile":
|
|
1795
|
+
return [{ ...s, cond: rewriteExpr(s.cond), body: rewriteList(s.body) }];
|
|
1796
|
+
case "for":
|
|
1797
|
+
return [
|
|
1798
|
+
{
|
|
1799
|
+
...s,
|
|
1800
|
+
init: s.init === null ? null : (rewriteStmt(s.init)[0] ?? null),
|
|
1801
|
+
cond: s.cond === null ? null : rewriteExpr(s.cond),
|
|
1802
|
+
update: s.update === null ? null : (rewriteStmt(s.update)[0] ?? null),
|
|
1803
|
+
body: rewriteList(s.body),
|
|
1804
|
+
},
|
|
1805
|
+
];
|
|
1806
|
+
case "block":
|
|
1807
|
+
return [{ ...s, body: rewriteList(s.body) }];
|
|
1808
|
+
default: {
|
|
1809
|
+
const [x] = transformStmtList([s], {
|
|
1810
|
+
stmt: (y) => y,
|
|
1811
|
+
expr: (y) => {
|
|
1812
|
+
if (y.kind === "recordGet" && y.obj.kind === "varRef") {
|
|
1813
|
+
const id = fields.get(y.obj.localId)?.get(y.field);
|
|
1814
|
+
if (id)
|
|
1815
|
+
return { kind: "varRef", localId: id, type: y.type, loc: y.loc };
|
|
1816
|
+
}
|
|
1817
|
+
return y;
|
|
1818
|
+
},
|
|
1819
|
+
});
|
|
1820
|
+
return [x];
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
};
|
|
1824
|
+
fn.body = rewriteList(fn.body);
|
|
1825
|
+
fn.locals = [...fn.locals.filter((l) => !fields.has(l.id)), ...added];
|
|
1826
|
+
}
|
|
1827
|
+
//# sourceMappingURL=array-pipelines.js.map
|