@scriptc/compiler 0.3.4 → 0.3.6
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 +409 -42
- package/dist/backend/llvm/array-search.js +1 -1
- package/dist/backend/llvm/array-search.js.map +1 -1
- package/dist/backend/llvm/call-lifetimes.d.ts +6 -0
- package/dist/backend/llvm/call-lifetimes.js +19 -0
- package/dist/backend/llvm/call-lifetimes.js.map +1 -1
- package/dist/backend/llvm/constant-callbacks.d.ts +2 -21
- package/dist/backend/llvm/constant-callbacks.js +36 -219
- package/dist/backend/llvm/constant-callbacks.js.map +1 -1
- package/dist/backend/llvm/constant-callbacks.test.js +22 -0
- package/dist/backend/llvm/constant-callbacks.test.js.map +1 -1
- package/dist/backend/llvm/dense-array-access.d.ts +9 -0
- package/dist/backend/llvm/dense-array-access.js +27 -1
- package/dist/backend/llvm/dense-array-access.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +6 -0
- package/dist/backend/llvm/dyn.js +71 -7
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.d.ts +120 -2
- package/dist/backend/llvm/emitter.js +820 -142
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/expr-async.js +17 -7
- package/dist/backend/llvm/expr-async.js.map +1 -1
- package/dist/backend/llvm/expr-bytes.js +1 -1
- package/dist/backend/llvm/expr-callbacks.d.ts +11 -0
- package/dist/backend/llvm/expr-callbacks.js +53 -0
- package/dist/backend/llvm/expr-callbacks.js.map +1 -1
- package/dist/backend/llvm/expr-calls.js +88 -2
- package/dist/backend/llvm/expr-calls.js.map +1 -1
- package/dist/backend/llvm/expr-containers.js +50 -27
- package/dist/backend/llvm/expr-containers.js.map +1 -1
- package/dist/backend/llvm/expr-dispatch.js +1 -0
- package/dist/backend/llvm/expr-dispatch.js.map +1 -1
- package/dist/backend/llvm/expr-dynamic.js +25 -1
- package/dist/backend/llvm/expr-dynamic.js.map +1 -1
- package/dist/backend/llvm/expr-primitives.d.ts +1 -1
- package/dist/backend/llvm/expr-primitives.js +20 -1
- package/dist/backend/llvm/expr-primitives.js.map +1 -1
- package/dist/backend/llvm/expr-stream-bridges.js +4 -0
- package/dist/backend/llvm/expr-stream-bridges.js.map +1 -1
- package/dist/backend/llvm/integer-clones.d.ts +103 -0
- package/dist/backend/llvm/integer-clones.js +1336 -0
- package/dist/backend/llvm/integer-clones.js.map +1 -0
- package/dist/backend/llvm/integer-clones.test.d.ts +1 -0
- package/dist/backend/llvm/integer-clones.test.js +219 -0
- package/dist/backend/llvm/integer-clones.test.js.map +1 -0
- package/dist/backend/llvm/lib-datagram.js +14 -0
- package/dist/backend/llvm/lib-datagram.js.map +1 -1
- package/dist/backend/llvm/lib-dispatch.js +32 -1
- package/dist/backend/llvm/lib-dispatch.js.map +1 -1
- package/dist/backend/llvm/lib-filesystem.js +61 -1
- package/dist/backend/llvm/lib-filesystem.js.map +1 -1
- package/dist/backend/llvm/lib-network.js +21 -3
- package/dist/backend/llvm/lib-network.js.map +1 -1
- package/dist/backend/llvm/lib-process.js +34 -0
- package/dist/backend/llvm/lib-process.js.map +1 -1
- package/dist/backend/llvm/lib-shared.js +77 -2
- package/dist/backend/llvm/lib-shared.js.map +1 -1
- package/dist/backend/llvm/lib-test.js +16 -3
- package/dist/backend/llvm/lib-test.js.map +1 -1
- package/dist/backend/llvm/lib-tls.js +6 -0
- package/dist/backend/llvm/lib-tls.js.map +1 -1
- package/dist/backend/llvm/local-array-reads.js +1 -1
- package/dist/backend/llvm/local-array-reads.js.map +1 -1
- package/dist/backend/llvm/map-entry-fusion.d.ts +28 -0
- package/dist/backend/llvm/map-entry-fusion.js +233 -0
- package/dist/backend/llvm/map-entry-fusion.js.map +1 -0
- package/dist/backend/llvm/map-entry-fusion.test.d.ts +1 -0
- package/dist/backend/llvm/map-entry-fusion.test.js +183 -0
- package/dist/backend/llvm/map-entry-fusion.test.js.map +1 -0
- package/dist/backend/llvm/map-number-lookup.d.ts +2 -0
- package/dist/backend/llvm/map-number-lookup.js +1 -1
- package/dist/backend/llvm/map-number-lookup.js.map +1 -1
- package/dist/backend/llvm/map-number-lookup.test.js +10 -11
- package/dist/backend/llvm/map-number-lookup.test.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.d.ts +2 -0
- package/dist/backend/llvm/map-read-lifetimes.js +3 -0
- package/dist/backend/llvm/map-read-lifetimes.js.map +1 -1
- package/dist/backend/llvm/map-read-lifetimes.test.js +2 -2
- package/dist/backend/llvm/nullable-unions.d.ts +6 -3
- package/dist/backend/llvm/nullable-unions.js +2 -0
- package/dist/backend/llvm/nullable-unions.js.map +1 -1
- package/dist/backend/llvm/publish.d.ts +16 -0
- package/dist/backend/llvm/publish.js +81 -10
- package/dist/backend/llvm/publish.js.map +1 -1
- package/dist/backend/llvm/publish.test.d.ts +1 -0
- package/dist/backend/llvm/publish.test.js +94 -0
- package/dist/backend/llvm/publish.test.js.map +1 -0
- package/dist/backend/llvm/record-results.d.ts +1 -1
- package/dist/backend/llvm/record-results.js +78 -4
- package/dist/backend/llvm/record-results.js.map +1 -1
- package/dist/backend/llvm/record-sources.d.ts +7 -0
- package/dist/backend/llvm/record-sources.js +14 -0
- package/dist/backend/llvm/record-sources.js.map +1 -0
- package/dist/backend/llvm/reference-writes.js +1 -0
- package/dist/backend/llvm/reference-writes.js.map +1 -1
- package/dist/backend/llvm/shapes.d.ts +12 -3
- package/dist/backend/llvm/shapes.js +23 -14
- package/dist/backend/llvm/shapes.js.map +1 -1
- package/dist/backend/llvm/shapes.test.js +5 -3
- package/dist/backend/llvm/shapes.test.js.map +1 -1
- package/dist/backend/llvm/speculative-integers.d.ts +74 -0
- package/dist/backend/llvm/speculative-integers.js +571 -0
- package/dist/backend/llvm/speculative-integers.js.map +1 -0
- package/dist/backend/llvm/speculative-integers.test.d.ts +1 -0
- package/dist/backend/llvm/speculative-integers.test.js +148 -0
- package/dist/backend/llvm/speculative-integers.test.js.map +1 -0
- package/dist/backend/llvm/string-lifetimes.test.js +75 -1
- package/dist/backend/llvm/string-lifetimes.test.js.map +1 -1
- package/dist/backend/llvm/walkers.d.ts +17 -0
- package/dist/backend/llvm/walkers.js +123 -0
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/backend/may-throw.js +2 -0
- package/dist/backend/may-throw.js.map +1 -1
- package/dist/backend/native/contracts.d.ts +2 -1
- package/dist/backend/native/executable.js +6 -1
- package/dist/backend/native/executable.js.map +1 -1
- package/dist/backend/native/runtime-inputs.d.ts +1 -1
- package/dist/backend/native/runtime-inputs.js +1 -0
- package/dist/backend/native/runtime-inputs.js.map +1 -1
- package/dist/backend/runtime-abi.d.ts +2 -2
- package/dist/backend/runtime-abi.js +2 -2
- package/dist/backend/runtime-pack-targets.test.js +1 -1
- package/dist/backend/runtime-pack.d.ts +7 -3
- package/dist/backend/runtime-pack.js +12 -6
- package/dist/backend/runtime-pack.js.map +1 -1
- package/dist/backend/runtime-pack.test.js +16 -7
- package/dist/backend/runtime-pack.test.js.map +1 -1
- package/dist/contracts/check.js +1 -0
- package/dist/contracts/check.js.map +1 -1
- package/dist/coverage/surface-manifest.js +3 -3
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +1 -1
- package/dist/diagnostics/diagnostic.js +6 -2
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lowering/builtin-calls.js +13 -0
- package/dist/frontend/lowering/builtin-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/callable-aliases.js +82 -8
- package/dist/frontend/lowering/builtins/callable-aliases.js.map +1 -1
- package/dist/frontend/lowering/builtins/child-process-async.d.ts +8 -6
- package/dist/frontend/lowering/builtins/child-process-async.js +45 -29
- package/dist/frontend/lowering/builtins/child-process-async.js.map +1 -1
- package/dist/frontend/lowering/builtins/compression.d.ts +9 -0
- package/dist/frontend/lowering/builtins/compression.js +145 -30
- package/dist/frontend/lowering/builtins/compression.js.map +1 -1
- package/dist/frontend/lowering/builtins/crypto.js +12 -1
- package/dist/frontend/lowering/builtins/crypto.js.map +1 -1
- package/dist/frontend/lowering/builtins/errors.js +5 -3
- package/dist/frontend/lowering/builtins/errors.js.map +1 -1
- package/dist/frontend/lowering/builtins/filesystem-calls.d.ts +6 -0
- package/dist/frontend/lowering/builtins/filesystem-calls.js +16 -0
- package/dist/frontend/lowering/builtins/filesystem-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/filesystem.d.ts +8 -0
- package/dist/frontend/lowering/builtins/filesystem.js +199 -2
- package/dist/frontend/lowering/builtins/filesystem.js.map +1 -1
- package/dist/frontend/lowering/builtins/fs-streams.d.ts +26 -0
- package/dist/frontend/lowering/builtins/fs-streams.js +131 -0
- package/dist/frontend/lowering/builtins/fs-streams.js.map +1 -0
- package/dist/frontend/lowering/builtins/hash-stream.d.ts +12 -0
- package/dist/frontend/lowering/builtins/hash-stream.js +80 -0
- package/dist/frontend/lowering/builtins/hash-stream.js.map +1 -0
- package/dist/frontend/lowering/builtins/module-calls.js +17 -3
- package/dist/frontend/lowering/builtins/module-calls.js.map +1 -1
- package/dist/frontend/lowering/builtins/process.js +9 -2
- package/dist/frontend/lowering/builtins/process.js.map +1 -1
- package/dist/frontend/lowering/builtins/readline.d.ts +45 -13
- package/dist/frontend/lowering/builtins/readline.js +812 -47
- package/dist/frontend/lowering/builtins/readline.js.map +1 -1
- package/dist/frontend/lowering/builtins/workers.d.ts +4 -0
- package/dist/frontend/lowering/builtins/workers.js +58 -0
- package/dist/frontend/lowering/builtins/workers.js.map +1 -1
- package/dist/frontend/lowering/class-dynamic-dispatch.js +150 -8
- package/dist/frontend/lowering/class-dynamic-dispatch.js.map +1 -1
- package/dist/frontend/lowering/class-protocols.d.ts +55 -0
- package/dist/frontend/lowering/class-protocols.js +735 -0
- package/dist/frontend/lowering/class-protocols.js.map +1 -1
- package/dist/frontend/lowering/containers/array-from-async.d.ts +18 -0
- package/dist/frontend/lowering/containers/array-from-async.js +282 -0
- package/dist/frontend/lowering/containers/array-from-async.js.map +1 -0
- package/dist/frontend/lowering/containers/bytes.js +33 -12
- package/dist/frontend/lowering/containers/bytes.js.map +1 -1
- package/dist/frontend/lowering/containers/collection-grouping.js +2 -1
- package/dist/frontend/lowering/containers/collection-grouping.js.map +1 -1
- package/dist/frontend/lowering/containers/collection-methods.js +27 -23
- package/dist/frontend/lowering/containers/collection-methods.js.map +1 -1
- package/dist/frontend/lowering/devirtualize.js +20 -7
- package/dist/frontend/lowering/devirtualize.js.map +1 -1
- package/dist/frontend/lowering/divergences.js +188 -15
- package/dist/frontend/lowering/divergences.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +129 -25
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +46 -1
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-dgram.js +55 -15
- package/dist/frontend/lowering/lower-dgram.js.map +1 -1
- package/dist/frontend/lowering/lower-event-emitter.d.ts +9 -1
- package/dist/frontend/lowering/lower-event-emitter.js +88 -1
- package/dist/frontend/lowering/lower-event-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +4 -0
- package/dist/frontend/lowering/lower-exprs.js +239 -71
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.js +49 -3
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-mock.d.ts +11 -1
- package/dist/frontend/lowering/lower-mock.js +377 -61
- package/dist/frontend/lowering/lower-mock.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.js +1 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-server.d.ts +2 -1
- package/dist/frontend/lowering/lower-server.js +234 -21
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +60 -13
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.d.ts +1 -3
- package/dist/frontend/lowering/lower-stream.js +325 -20
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lower-test.js +147 -48
- package/dist/frontend/lowering/lower-test.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +13 -1
- package/dist/frontend/lowering/lowerer.js +10 -2
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/null-prototype.d.ts +13 -0
- package/dist/frontend/lowering/null-prototype.js +33 -0
- package/dist/frontend/lowering/null-prototype.js.map +1 -0
- package/dist/frontend/lowering/record-sources.d.ts +15 -0
- package/dist/frontend/lowering/record-sources.js +46 -0
- package/dist/frontend/lowering/record-sources.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +3 -3
- package/dist/frontend/lowering/surfaces.js +59 -5
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/mock-shapes.d.ts +5 -0
- package/dist/frontend/mock-shapes.js +27 -2
- package/dist/frontend/mock-shapes.js.map +1 -1
- package/dist/frontend/program.js +5 -4
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/type-mapper.d.ts +3 -0
- package/dist/frontend/type-mapper.js +148 -4
- package/dist/frontend/type-mapper.js.map +1 -1
- package/dist/index.js +18 -8
- package/dist/index.js.map +1 -1
- package/dist/ir/analysis.d.ts +10 -0
- package/dist/ir/analysis.js +35 -0
- package/dist/ir/analysis.js.map +1 -1
- package/dist/ir/analysis.test.js +69 -1
- package/dist/ir/analysis.test.js.map +1 -1
- package/dist/ir/builtin-effects.js +72 -0
- package/dist/ir/builtin-effects.js.map +1 -1
- package/dist/ir/builtin-signatures.d.ts +1 -1
- package/dist/ir/builtin-signatures.js +258 -13
- package/dist/ir/builtin-signatures.js.map +1 -1
- package/dist/ir/capture-values.d.ts +107 -0
- package/dist/ir/capture-values.js +850 -0
- package/dist/ir/capture-values.js.map +1 -0
- package/dist/ir/capture-values.test.d.ts +1 -0
- package/dist/ir/capture-values.test.js +268 -0
- package/dist/ir/capture-values.test.js.map +1 -0
- package/dist/ir/field-values.d.ts +16 -0
- package/dist/ir/field-values.js +128 -0
- package/dist/ir/field-values.js.map +1 -0
- package/dist/ir/field-values.test.d.ts +1 -0
- package/dist/ir/field-values.test.js +160 -0
- package/dist/ir/field-values.test.js.map +1 -0
- package/dist/ir/final-fields.d.ts +4 -1
- package/dist/ir/final-fields.js +11 -3
- package/dist/ir/final-fields.js.map +1 -1
- package/dist/ir/function-references.test.js +2 -1
- package/dist/ir/function-references.test.js.map +1 -1
- package/dist/ir/init-only-globals.d.ts +16 -0
- package/dist/ir/init-only-globals.js +121 -0
- package/dist/ir/init-only-globals.js.map +1 -0
- package/dist/ir/init-only-globals.test.d.ts +1 -0
- package/dist/ir/init-only-globals.test.js +115 -0
- package/dist/ir/init-only-globals.test.js.map +1 -0
- package/dist/ir/initializer-bindings.d.ts +18 -1
- package/dist/ir/initializer-bindings.js +91 -1
- package/dist/ir/initializer-bindings.js.map +1 -1
- package/dist/ir/initializer-bindings.test.js +57 -2
- package/dist/ir/initializer-bindings.test.js.map +1 -1
- package/dist/ir/int32-slots.d.ts +22 -0
- package/dist/ir/int32-slots.js +398 -13
- package/dist/ir/int32-slots.js.map +1 -1
- package/dist/ir/int32-slots.test.js +119 -8
- package/dist/ir/int32-slots.test.js.map +1 -1
- package/dist/ir/integer-loop-facts.d.ts +27 -1
- package/dist/ir/integer-loop-facts.js +239 -7
- package/dist/ir/integer-loop-facts.js.map +1 -1
- package/dist/ir/integer-ranges.d.ts +8 -5
- package/dist/ir/integer-ranges.js +147 -59
- package/dist/ir/integer-ranges.js.map +1 -1
- package/dist/ir/integer-ranges.test.js +285 -7
- package/dist/ir/integer-ranges.test.js.map +1 -1
- package/dist/ir/ir.d.ts +22 -1
- package/dist/ir/ir.js +4 -0
- package/dist/ir/ir.js.map +1 -1
- package/dist/ir/property-bags.d.ts +14 -0
- package/dist/ir/property-bags.js +121 -0
- package/dist/ir/property-bags.js.map +1 -0
- package/dist/ir/receiver-readers.d.ts +21 -0
- package/dist/ir/receiver-readers.js +217 -0
- package/dist/ir/receiver-readers.js.map +1 -0
- package/dist/ir/runtime-features.d.ts +3 -1
- package/dist/ir/runtime-features.js +102 -7
- package/dist/ir/runtime-features.js.map +1 -1
- package/dist/ir/runtime-features.test.js +62 -0
- package/dist/ir/runtime-features.test.js.map +1 -1
- package/dist/ir/traverse.js +4 -0
- package/dist/ir/traverse.js.map +1 -1
- package/dist/ir/validate.js +71 -3
- package/dist/ir/validate.js.map +1 -1
- package/dist/ir/validate.test.js +59 -0
- package/dist/ir/validate.test.js.map +1 -1
- package/dist/library/int-infer.js +3 -0
- package/dist/library/int-infer.js.map +1 -1
- package/dist/library/ir-policy.js +5 -0
- package/dist/library/ir-policy.js.map +1 -1
- package/package.json +20 -20
- package/surface-manifest.json +57 -19
|
@@ -0,0 +1,1336 @@
|
|
|
1
|
+
import { withinInt32 } from "../../ir/integer-ranges.js";
|
|
2
|
+
import { mangleFunction } from "../mangle.js";
|
|
3
|
+
import { BlockBuilder } from "./blocks.js";
|
|
4
|
+
import { SCR_ARR_FIELD } from "./common.js";
|
|
5
|
+
export const NO_INTEGER_CLONES = {
|
|
6
|
+
members: new Set(),
|
|
7
|
+
dispatched: new Set(),
|
|
8
|
+
component: new Map(),
|
|
9
|
+
tailLinked: new Set(),
|
|
10
|
+
};
|
|
11
|
+
/** A clone's result: the value and whether the clone bailed. */
|
|
12
|
+
export const CLONE_RESULT = "{ i32, i1 }";
|
|
13
|
+
/** The result of a clone that bailed: no caller reads its value. */
|
|
14
|
+
const BAIL = `${CLONE_RESULT} { i32 poison, i1 true }`;
|
|
15
|
+
/** The stack one replaced recursion level is charged, in bytes. Ordinary
|
|
16
|
+
* frames of these functions are about that size. */
|
|
17
|
+
const VIRTUAL_FRAME = 32;
|
|
18
|
+
/** The single-threaded stack guard, declared as the emitter's ordinary
|
|
19
|
+
* stack checks declare it (clones only exist without workers). */
|
|
20
|
+
export const STACK_GUARD_DECL = "@scr_stack_guard = external global ptr";
|
|
21
|
+
export const INTEGER_CLONE_SUFFIX = ".int";
|
|
22
|
+
export const INTEGER_DISPATCH_SUFFIX = ".spec";
|
|
23
|
+
function scalarKind(type) {
|
|
24
|
+
return type.kind === "f64" ? "num" : type.kind === "bool" ? "bool" : null;
|
|
25
|
+
}
|
|
26
|
+
function valueKind(type) {
|
|
27
|
+
return type.kind === "array" && type.elem.kind === "f64" ? "arr" : scalarKind(type);
|
|
28
|
+
}
|
|
29
|
+
/** The LLVM type of a value of `kind` inside a clone. */
|
|
30
|
+
function kindType(kind) {
|
|
31
|
+
return kind === "num" ? "i32" : kind === "bool" ? "i1" : "ptr";
|
|
32
|
+
}
|
|
33
|
+
function isInt32Literal(value) {
|
|
34
|
+
return (Number.isInteger(value) && value >= -0x80000000 && value <= 0x7fffffff && !Object.is(value, -0));
|
|
35
|
+
}
|
|
36
|
+
/** The call targets of `fn` when its body has the qualifying shape. */
|
|
37
|
+
function cloneShape(fn, borrowed) {
|
|
38
|
+
if (fn.async || fn.generator !== undefined || fn.captures !== undefined)
|
|
39
|
+
return null;
|
|
40
|
+
if (fn.classCaptures !== undefined && fn.classCaptures.length > 0)
|
|
41
|
+
return null;
|
|
42
|
+
const result = scalarKind(fn.returnType);
|
|
43
|
+
if (result === null)
|
|
44
|
+
return null;
|
|
45
|
+
const locals = new Map(fn.locals.map((l) => [l.id, l]));
|
|
46
|
+
// Array parameters are only read: never assigned, and their locals are
|
|
47
|
+
// the parameters themselves.
|
|
48
|
+
const arrays = new Set();
|
|
49
|
+
for (const [index, p] of fn.params.entries())
|
|
50
|
+
if (valueKind(p.type) === "arr") {
|
|
51
|
+
if (borrowed?.(fn.name, index) !== true)
|
|
52
|
+
return null;
|
|
53
|
+
arrays.add(p.localId);
|
|
54
|
+
}
|
|
55
|
+
const localKind = (id) => {
|
|
56
|
+
const local = locals.get(id);
|
|
57
|
+
if (!local || local.boxed || local.tdz)
|
|
58
|
+
return null;
|
|
59
|
+
return scalarKind(local.type);
|
|
60
|
+
};
|
|
61
|
+
const arrayRef = (e) => e.kind === "varRef" && arrays.has(e.localId);
|
|
62
|
+
for (const p of fn.params)
|
|
63
|
+
if (!arrays.has(p.localId) && (localKind(p.localId) === null || scalarKind(p.type) === null))
|
|
64
|
+
return null;
|
|
65
|
+
const callees = new Set();
|
|
66
|
+
const expr = (e, want) => {
|
|
67
|
+
if (scalarKind(e.type) !== want)
|
|
68
|
+
return false;
|
|
69
|
+
switch (e.kind) {
|
|
70
|
+
case "numLit":
|
|
71
|
+
return isInt32Literal(e.value);
|
|
72
|
+
case "boolLit":
|
|
73
|
+
return true;
|
|
74
|
+
case "varRef":
|
|
75
|
+
return localKind(e.localId) === want;
|
|
76
|
+
case "bin":
|
|
77
|
+
switch (e.op) {
|
|
78
|
+
case "+":
|
|
79
|
+
case "-":
|
|
80
|
+
case "*":
|
|
81
|
+
case "%":
|
|
82
|
+
case "&":
|
|
83
|
+
case "|":
|
|
84
|
+
case "^":
|
|
85
|
+
case "<<":
|
|
86
|
+
case ">>":
|
|
87
|
+
case ">>>":
|
|
88
|
+
return want === "num" && expr(e.left, "num") && expr(e.right, "num");
|
|
89
|
+
case "<":
|
|
90
|
+
case "<=":
|
|
91
|
+
case ">":
|
|
92
|
+
case ">=":
|
|
93
|
+
return want === "bool" && expr(e.left, "num") && expr(e.right, "num");
|
|
94
|
+
case "===":
|
|
95
|
+
case "!==": {
|
|
96
|
+
if (want !== "bool")
|
|
97
|
+
return false;
|
|
98
|
+
const operand = scalarKind(e.left.type);
|
|
99
|
+
return operand !== null && expr(e.left, operand) && expr(e.right, operand);
|
|
100
|
+
}
|
|
101
|
+
default:
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
case "unary":
|
|
105
|
+
return e.op === "!"
|
|
106
|
+
? want === "bool" && expr(e.operand, "bool")
|
|
107
|
+
: want === "num" && expr(e.operand, "num");
|
|
108
|
+
case "toBool": {
|
|
109
|
+
const operand = scalarKind(e.operand.type);
|
|
110
|
+
return want === "bool" && operand !== null && expr(e.operand, operand);
|
|
111
|
+
}
|
|
112
|
+
case "logical":
|
|
113
|
+
return want === "bool" && expr(e.left, "bool") && expr(e.right, "bool");
|
|
114
|
+
case "ternary":
|
|
115
|
+
return expr(e.cond, "bool") && expr(e.then, want) && expr(e.else_, want);
|
|
116
|
+
case "arrIntrinsic":
|
|
117
|
+
return (want === "num" && e.method === "length" && e.args.length === 0 && arrayRef(e.receiver));
|
|
118
|
+
case "arrayGet":
|
|
119
|
+
return want === "num" && arrayRef(e.arr) && expr(e.index, "num");
|
|
120
|
+
case "call":
|
|
121
|
+
callees.add(e.callee);
|
|
122
|
+
return e.args.every((a) => {
|
|
123
|
+
const kind = valueKind(a.type);
|
|
124
|
+
return kind === "arr" ? arrayRef(a) : kind !== null && expr(a, kind);
|
|
125
|
+
});
|
|
126
|
+
default:
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const stmts = (list) => list.every(stmt);
|
|
131
|
+
const stmt = (s) => {
|
|
132
|
+
switch (s.kind) {
|
|
133
|
+
case "return":
|
|
134
|
+
return s.value !== null && s.awaited !== true && expr(s.value, result);
|
|
135
|
+
case "if":
|
|
136
|
+
return expr(s.cond, "bool") && stmts(s.then) && (s.else_ === null || stmts(s.else_));
|
|
137
|
+
case "block":
|
|
138
|
+
return (s.labels === undefined || s.labels.length === 0) && stmts(s.body);
|
|
139
|
+
case "varDecl": {
|
|
140
|
+
const kind = localKind(s.localId);
|
|
141
|
+
return kind !== null && s.init !== null && expr(s.init, kind);
|
|
142
|
+
}
|
|
143
|
+
case "assign": {
|
|
144
|
+
const kind = localKind(s.localId);
|
|
145
|
+
return kind !== null && expr(s.value, kind);
|
|
146
|
+
}
|
|
147
|
+
case "exprStmt": {
|
|
148
|
+
const kind = scalarKind(s.expr.type);
|
|
149
|
+
return kind !== null && expr(s.expr, kind);
|
|
150
|
+
}
|
|
151
|
+
default:
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
return stmts(fn.body) ? callees : null;
|
|
156
|
+
}
|
|
157
|
+
/** Every call in `fn`'s body has arguments of its callee's parameter kinds. */
|
|
158
|
+
function callsMatch(fn, byName) {
|
|
159
|
+
let ok = true;
|
|
160
|
+
const visit = (e) => {
|
|
161
|
+
switch (e.kind) {
|
|
162
|
+
case "call": {
|
|
163
|
+
const callee = byName.get(e.callee);
|
|
164
|
+
if (!callee ||
|
|
165
|
+
callee.params.length !== e.args.length ||
|
|
166
|
+
scalarKind(callee.returnType) !== scalarKind(e.type) ||
|
|
167
|
+
callee.params.some((p, i) => valueKind(p.type) !== valueKind(e.args[i].type)))
|
|
168
|
+
ok = false;
|
|
169
|
+
e.args.forEach(visit);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
case "bin":
|
|
173
|
+
case "logical":
|
|
174
|
+
visit(e.left);
|
|
175
|
+
visit(e.right);
|
|
176
|
+
return;
|
|
177
|
+
case "unary":
|
|
178
|
+
case "toBool":
|
|
179
|
+
visit(e.operand);
|
|
180
|
+
return;
|
|
181
|
+
case "ternary":
|
|
182
|
+
visit(e.cond);
|
|
183
|
+
visit(e.then);
|
|
184
|
+
visit(e.else_);
|
|
185
|
+
return;
|
|
186
|
+
case "arrayGet":
|
|
187
|
+
visit(e.index);
|
|
188
|
+
return;
|
|
189
|
+
default:
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
const stmts = (list) => {
|
|
194
|
+
for (const s of list) {
|
|
195
|
+
if (s.kind === "return" && s.value)
|
|
196
|
+
visit(s.value);
|
|
197
|
+
else if (s.kind === "if") {
|
|
198
|
+
visit(s.cond);
|
|
199
|
+
stmts(s.then);
|
|
200
|
+
if (s.else_)
|
|
201
|
+
stmts(s.else_);
|
|
202
|
+
}
|
|
203
|
+
else if (s.kind === "block")
|
|
204
|
+
stmts(s.body);
|
|
205
|
+
else if (s.kind === "varDecl" && s.init)
|
|
206
|
+
visit(s.init);
|
|
207
|
+
else if (s.kind === "assign")
|
|
208
|
+
visit(s.value);
|
|
209
|
+
else if (s.kind === "exprStmt")
|
|
210
|
+
visit(s.expr);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
stmts(fn.body);
|
|
214
|
+
return ok;
|
|
215
|
+
}
|
|
216
|
+
export function findIntegerClones(mod, borrowed) {
|
|
217
|
+
const byName = new Map(mod.functions.map((fn) => [fn.name, fn]));
|
|
218
|
+
const edges = new Map();
|
|
219
|
+
for (const fn of mod.functions) {
|
|
220
|
+
const callees = cloneShape(fn, borrowed);
|
|
221
|
+
if (callees && callsMatch(fn, byName))
|
|
222
|
+
edges.set(fn.name, callees);
|
|
223
|
+
}
|
|
224
|
+
// Drop functions that call a non-member until none does.
|
|
225
|
+
for (let changed = true; changed;) {
|
|
226
|
+
changed = false;
|
|
227
|
+
for (const [name, callees] of edges)
|
|
228
|
+
for (const callee of callees)
|
|
229
|
+
if (!edges.has(callee)) {
|
|
230
|
+
edges.delete(name);
|
|
231
|
+
changed = true;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (edges.size === 0)
|
|
236
|
+
return NO_INTEGER_CLONES;
|
|
237
|
+
// Tarjan's strongly connected components over the member call graph.
|
|
238
|
+
const index = new Map();
|
|
239
|
+
const low = new Map();
|
|
240
|
+
const onStack = new Set();
|
|
241
|
+
const stack = [];
|
|
242
|
+
const component = new Map();
|
|
243
|
+
const dispatched = new Set();
|
|
244
|
+
let counter = 0;
|
|
245
|
+
let components = 0;
|
|
246
|
+
const strong = (name) => {
|
|
247
|
+
index.set(name, counter);
|
|
248
|
+
low.set(name, counter++);
|
|
249
|
+
stack.push(name);
|
|
250
|
+
onStack.add(name);
|
|
251
|
+
for (const callee of edges.get(name)) {
|
|
252
|
+
if (!index.has(callee)) {
|
|
253
|
+
strong(callee);
|
|
254
|
+
low.set(name, Math.min(low.get(name), low.get(callee)));
|
|
255
|
+
}
|
|
256
|
+
else if (onStack.has(callee))
|
|
257
|
+
low.set(name, Math.min(low.get(name), index.get(callee)));
|
|
258
|
+
}
|
|
259
|
+
if (low.get(name) !== index.get(name))
|
|
260
|
+
return;
|
|
261
|
+
const members = [];
|
|
262
|
+
for (;;) {
|
|
263
|
+
const top = stack.pop();
|
|
264
|
+
onStack.delete(top);
|
|
265
|
+
members.push(top);
|
|
266
|
+
if (top === name)
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
const id = components++;
|
|
270
|
+
if (members.length > 1 || edges.get(name).has(name))
|
|
271
|
+
for (const member of members) {
|
|
272
|
+
dispatched.add(member);
|
|
273
|
+
component.set(member, id);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
for (const name of edges.keys())
|
|
277
|
+
if (!index.has(name))
|
|
278
|
+
strong(name);
|
|
279
|
+
// Components with a tail call from one member to another.
|
|
280
|
+
const mutual = new Set();
|
|
281
|
+
for (const name of dispatched)
|
|
282
|
+
for (const callee of tailCallees(byName.get(name)))
|
|
283
|
+
if (callee !== name && component.get(callee) === component.get(name))
|
|
284
|
+
mutual.add(component.get(name));
|
|
285
|
+
if (dispatched.size === 0)
|
|
286
|
+
return NO_INTEGER_CLONES;
|
|
287
|
+
const tailLinked = new Set([...dispatched].filter((name) => mutual.has(component.get(name))));
|
|
288
|
+
// Clones are entered only through dispatchers: keep the members a
|
|
289
|
+
// recursive member reaches.
|
|
290
|
+
const members = new Set();
|
|
291
|
+
const reach = (name) => {
|
|
292
|
+
if (members.has(name))
|
|
293
|
+
return;
|
|
294
|
+
members.add(name);
|
|
295
|
+
for (const callee of edges.get(name))
|
|
296
|
+
reach(callee);
|
|
297
|
+
};
|
|
298
|
+
for (const name of dispatched)
|
|
299
|
+
reach(name);
|
|
300
|
+
return { members, dispatched, component, tailLinked };
|
|
301
|
+
}
|
|
302
|
+
/** The functions `fn` calls in tail position: as a returned value, directly
|
|
303
|
+
* or as an arm of a returned conditional. */
|
|
304
|
+
function tailCallees(fn) {
|
|
305
|
+
const result = new Set();
|
|
306
|
+
const value = (e) => {
|
|
307
|
+
if (e.kind === "call")
|
|
308
|
+
result.add(e.callee);
|
|
309
|
+
else if (e.kind === "ternary") {
|
|
310
|
+
value(e.then);
|
|
311
|
+
value(e.else_);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
const stmts = (list) => {
|
|
315
|
+
for (const s of list) {
|
|
316
|
+
if (s.kind === "return" && s.value)
|
|
317
|
+
value(s.value);
|
|
318
|
+
else if (s.kind === "if") {
|
|
319
|
+
stmts(s.then);
|
|
320
|
+
if (s.else_)
|
|
321
|
+
stmts(s.else_);
|
|
322
|
+
}
|
|
323
|
+
else if (s.kind === "block")
|
|
324
|
+
stmts(s.body);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
stmts(fn.body);
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
330
|
+
/** Whether a direct call from `caller` to `callee` enters the callee's
|
|
331
|
+
* dispatcher: callers inside the callee's recursion run its ordinary body
|
|
332
|
+
* (they are the fallback). */
|
|
333
|
+
export function dispatchesCall(clones, caller, callee) {
|
|
334
|
+
if (!clones.dispatched.has(callee))
|
|
335
|
+
return false;
|
|
336
|
+
const own = clones.component.get(caller);
|
|
337
|
+
return own === undefined || own !== clones.component.get(callee);
|
|
338
|
+
}
|
|
339
|
+
/** Calls in `e` (an expression that calls nothing is free to evaluate early). */
|
|
340
|
+
function callsAnything(e) {
|
|
341
|
+
switch (e.kind) {
|
|
342
|
+
case "call":
|
|
343
|
+
return true;
|
|
344
|
+
case "bin":
|
|
345
|
+
case "logical":
|
|
346
|
+
return callsAnything(e.left) || callsAnything(e.right);
|
|
347
|
+
case "unary":
|
|
348
|
+
case "toBool":
|
|
349
|
+
return callsAnything(e.operand);
|
|
350
|
+
case "ternary":
|
|
351
|
+
return callsAnything(e.cond) || callsAnything(e.then) || callsAnything(e.else_);
|
|
352
|
+
case "arrayGet":
|
|
353
|
+
return callsAnything(e.index);
|
|
354
|
+
default:
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
/** Whether emitNum answers an untested CLONE_RESULT for `e`. */
|
|
359
|
+
function mayBail(e) {
|
|
360
|
+
if (e.kind === "call")
|
|
361
|
+
return true;
|
|
362
|
+
return e.kind === "ternary" && (mayBail(e.then) || mayBail(e.else_));
|
|
363
|
+
}
|
|
364
|
+
/** IR nodes of `e`. */
|
|
365
|
+
function exprSize(e) {
|
|
366
|
+
switch (e.kind) {
|
|
367
|
+
case "bin":
|
|
368
|
+
case "logical":
|
|
369
|
+
return 1 + exprSize(e.left) + exprSize(e.right);
|
|
370
|
+
case "unary":
|
|
371
|
+
case "toBool":
|
|
372
|
+
return 1 + exprSize(e.operand);
|
|
373
|
+
case "ternary":
|
|
374
|
+
return 1 + exprSize(e.cond) + exprSize(e.then) + exprSize(e.else_);
|
|
375
|
+
case "call":
|
|
376
|
+
return 1 + e.args.reduce((n, a) => n + exprSize(a), 0);
|
|
377
|
+
default:
|
|
378
|
+
return 1;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function stmtsSize(list) {
|
|
382
|
+
let n = 0;
|
|
383
|
+
for (const s of list) {
|
|
384
|
+
n += 1;
|
|
385
|
+
if (s.kind === "return" && s.value)
|
|
386
|
+
n += exprSize(s.value);
|
|
387
|
+
else if (s.kind === "if")
|
|
388
|
+
n += exprSize(s.cond) + stmtsSize(s.then) + stmtsSize(s.else_ ?? []);
|
|
389
|
+
else if (s.kind === "block")
|
|
390
|
+
n += stmtsSize(s.body);
|
|
391
|
+
else if (s.kind === "varDecl" && s.init)
|
|
392
|
+
n += exprSize(s.init);
|
|
393
|
+
else if (s.kind === "assign")
|
|
394
|
+
n += exprSize(s.value);
|
|
395
|
+
else if (s.kind === "exprStmt")
|
|
396
|
+
n += exprSize(s.expr);
|
|
397
|
+
}
|
|
398
|
+
return n;
|
|
399
|
+
}
|
|
400
|
+
/** IR nodes the inlined copies of a body's self calls may add. */
|
|
401
|
+
const INLINE_BUDGET = 160;
|
|
402
|
+
class CloneEmitter {
|
|
403
|
+
fn;
|
|
404
|
+
host;
|
|
405
|
+
stackCheck;
|
|
406
|
+
B = new BlockBuilder();
|
|
407
|
+
result;
|
|
408
|
+
bailLabel = null;
|
|
409
|
+
frame;
|
|
410
|
+
slotCount = 0;
|
|
411
|
+
/** The real stack pointer at the clone's entry (i64), once the stack
|
|
412
|
+
* check read it for an accumulating loop. */
|
|
413
|
+
entrySp = null;
|
|
414
|
+
/** Inline one level of non-tail self calls: each removes the call, the
|
|
415
|
+
* stack check and the frame of a recursion level (leaf calls entirely). */
|
|
416
|
+
inlineSelf;
|
|
417
|
+
/** Tail calls to other members pass through (IntegerClones.tailLinked). */
|
|
418
|
+
tailLinked;
|
|
419
|
+
constructor(fn, host, stackCheck) {
|
|
420
|
+
this.fn = fn;
|
|
421
|
+
this.host = host;
|
|
422
|
+
this.stackCheck = stackCheck;
|
|
423
|
+
this.result = scalarKind(fn.returnType);
|
|
424
|
+
this.tailLinked = host.integerClones.tailLinked.has(fn.name);
|
|
425
|
+
const size = stmtsSize(fn.body);
|
|
426
|
+
this.inlineSelf = size * this.selfCallSites() <= INLINE_BUDGET;
|
|
427
|
+
}
|
|
428
|
+
/** Self calls the body makes outside its tail positions. */
|
|
429
|
+
selfCallSites() {
|
|
430
|
+
let all = 0;
|
|
431
|
+
const count = (e) => {
|
|
432
|
+
if (this.isSelfCall(e))
|
|
433
|
+
all++;
|
|
434
|
+
switch (e.kind) {
|
|
435
|
+
case "bin":
|
|
436
|
+
case "logical":
|
|
437
|
+
count(e.left);
|
|
438
|
+
count(e.right);
|
|
439
|
+
return;
|
|
440
|
+
case "unary":
|
|
441
|
+
case "toBool":
|
|
442
|
+
count(e.operand);
|
|
443
|
+
return;
|
|
444
|
+
case "ternary":
|
|
445
|
+
count(e.cond);
|
|
446
|
+
count(e.then);
|
|
447
|
+
count(e.else_);
|
|
448
|
+
return;
|
|
449
|
+
case "call":
|
|
450
|
+
e.args.forEach(count);
|
|
451
|
+
return;
|
|
452
|
+
default:
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
let tails = 0;
|
|
457
|
+
const tail = (e) => {
|
|
458
|
+
if (e.kind === "ternary") {
|
|
459
|
+
tail(e.then);
|
|
460
|
+
tail(e.else_);
|
|
461
|
+
}
|
|
462
|
+
else if (this.isSelfCall(e) || this.accumulatingCall(e) !== null)
|
|
463
|
+
tails++;
|
|
464
|
+
};
|
|
465
|
+
const stmts = (list) => {
|
|
466
|
+
for (const s of list) {
|
|
467
|
+
if (s.kind === "return" && s.value) {
|
|
468
|
+
count(s.value);
|
|
469
|
+
tail(s.value);
|
|
470
|
+
}
|
|
471
|
+
else if (s.kind === "if") {
|
|
472
|
+
count(s.cond);
|
|
473
|
+
stmts(s.then);
|
|
474
|
+
if (s.else_)
|
|
475
|
+
stmts(s.else_);
|
|
476
|
+
}
|
|
477
|
+
else if (s.kind === "block")
|
|
478
|
+
stmts(s.body);
|
|
479
|
+
else if (s.kind === "varDecl" && s.init)
|
|
480
|
+
count(s.init);
|
|
481
|
+
else if (s.kind === "assign")
|
|
482
|
+
count(s.value);
|
|
483
|
+
else if (s.kind === "exprStmt")
|
|
484
|
+
count(s.expr);
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
stmts(this.fn.body);
|
|
488
|
+
return all - tails;
|
|
489
|
+
}
|
|
490
|
+
/** Allocates an activation's locals, binds its parameters to `args` and
|
|
491
|
+
* opens its tail loop. */
|
|
492
|
+
openFrame(args, inline) {
|
|
493
|
+
const { B, fn } = this;
|
|
494
|
+
const parent = inline === null ? null : this.frame;
|
|
495
|
+
const frame = {
|
|
496
|
+
slots: new Map(),
|
|
497
|
+
loopLabel: null,
|
|
498
|
+
accSlot: null,
|
|
499
|
+
depthSlot: null,
|
|
500
|
+
inline,
|
|
501
|
+
};
|
|
502
|
+
const params = new Set(fn.params.map((p) => p.localId));
|
|
503
|
+
for (const local of fn.locals) {
|
|
504
|
+
const kind = params.has(local.id) ? valueKind(local.type) : scalarKind(local.type);
|
|
505
|
+
if (kind === null || local.boxed)
|
|
506
|
+
continue;
|
|
507
|
+
const slot = `%l${this.slotCount++}`;
|
|
508
|
+
B.entryAllocas.push(`${slot} = alloca ${kindType(kind)} ; ${local.name}`);
|
|
509
|
+
frame.slots.set(local.id, { slot, kind });
|
|
510
|
+
}
|
|
511
|
+
fn.params.forEach((p, i) => {
|
|
512
|
+
const { slot, kind } = frame.slots.get(p.localId);
|
|
513
|
+
B.line(`store ${kindType(kind)} ${args[i]}, ptr ${slot}`);
|
|
514
|
+
});
|
|
515
|
+
this.frame = frame;
|
|
516
|
+
if (inline === null && this.stackCheck)
|
|
517
|
+
this.emitStackCheck();
|
|
518
|
+
const tail = this.tailShape();
|
|
519
|
+
if (!tail.loop) {
|
|
520
|
+
this.assumeParamRanges();
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (tail.accumulates) {
|
|
524
|
+
this.openVirtualStack(frame, parent);
|
|
525
|
+
frame.accSlot = `%l${this.slotCount++}`;
|
|
526
|
+
B.entryAllocas.push(`${frame.accSlot} = alloca i32 ; accumulator`);
|
|
527
|
+
B.line(`store i32 0, ptr ${frame.accSlot}`);
|
|
528
|
+
}
|
|
529
|
+
frame.loopLabel = B.newLabel("tail");
|
|
530
|
+
B.br(frame.loopLabel);
|
|
531
|
+
B.startBlock(frame.loopLabel);
|
|
532
|
+
this.assumeParamRanges();
|
|
533
|
+
}
|
|
534
|
+
/** Starts the virtual stack pointer of an activation with an accumulating
|
|
535
|
+
* loop. */
|
|
536
|
+
openVirtualStack(frame, parent) {
|
|
537
|
+
const { B } = this;
|
|
538
|
+
frame.depthSlot = `%l${this.slotCount++}`;
|
|
539
|
+
B.entryAllocas.push(`${frame.depthSlot} = alloca i64 ; virtual stack pointer`);
|
|
540
|
+
// The activation starts at the stack pointer its entry check read; an
|
|
541
|
+
// inlined one a frame below its parent's virtual stack pointer, or at
|
|
542
|
+
// the real one read afresh.
|
|
543
|
+
let start = frame.inline === null ? this.entrySp : null;
|
|
544
|
+
if (parent !== null && parent.depthSlot !== null) {
|
|
545
|
+
const at = B.tmp();
|
|
546
|
+
start = B.tmp();
|
|
547
|
+
B.line(`${at} = load i64, ptr ${parent.depthSlot}`);
|
|
548
|
+
B.line(`${start} = sub i64 ${at}, ${VIRTUAL_FRAME}`);
|
|
549
|
+
}
|
|
550
|
+
else if (start === null) {
|
|
551
|
+
this.host.declare(`declare ptr @llvm.stacksave.p0()`);
|
|
552
|
+
const sp = B.tmp();
|
|
553
|
+
start = B.tmp();
|
|
554
|
+
B.line(`${sp} = call ptr @llvm.stacksave.p0()`);
|
|
555
|
+
B.line(`${start} = ptrtoint ptr ${sp} to i64`);
|
|
556
|
+
}
|
|
557
|
+
B.line(`store i64 ${start}, ptr ${frame.depthSlot}`);
|
|
558
|
+
}
|
|
559
|
+
/** Tells LLVM the proven ranges of the current activation's number
|
|
560
|
+
* parameters (`%` of non-negative operands then needs no sign handling). */
|
|
561
|
+
assumeParamRanges() {
|
|
562
|
+
const { B } = this;
|
|
563
|
+
for (const p of this.fn.params) {
|
|
564
|
+
const range = this.host.integerParamRange(this.fn.name, p.localId);
|
|
565
|
+
const slot = this.frame.slots.get(p.localId);
|
|
566
|
+
if (!range || slot?.kind !== "num")
|
|
567
|
+
continue;
|
|
568
|
+
const bounds = [];
|
|
569
|
+
const value = B.tmp();
|
|
570
|
+
B.line(`${value} = load i32, ptr ${slot.slot}`);
|
|
571
|
+
if (range.min > -0x80000000) {
|
|
572
|
+
const t = B.tmp();
|
|
573
|
+
B.line(`${t} = icmp sge i32 ${value}, ${range.min}`);
|
|
574
|
+
bounds.push(t);
|
|
575
|
+
}
|
|
576
|
+
if (range.max < 0x7fffffff) {
|
|
577
|
+
const t = B.tmp();
|
|
578
|
+
B.line(`${t} = icmp sle i32 ${value}, ${range.max}`);
|
|
579
|
+
bounds.push(t);
|
|
580
|
+
}
|
|
581
|
+
if (bounds.length === 0)
|
|
582
|
+
continue;
|
|
583
|
+
this.host.declare(`declare void @llvm.assume(i1)`);
|
|
584
|
+
for (const bound of bounds)
|
|
585
|
+
B.line(`call void @llvm.assume(i1 ${bound})`);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
emit() {
|
|
589
|
+
const { B, fn } = this;
|
|
590
|
+
const params = fn.params.map((p, i) => `${kindType(valueKind(p.type))} %a${i}`);
|
|
591
|
+
this.openFrame(fn.params.map((_, i) => `%a${i}`), null);
|
|
592
|
+
this.emitStmts(fn.body);
|
|
593
|
+
// A number or boolean function never falls off its end; bail if it did.
|
|
594
|
+
if (!B.isTerminated())
|
|
595
|
+
B.terminate(`ret ${BAIL}`);
|
|
596
|
+
if (this.bailLabel !== null) {
|
|
597
|
+
B.startBlock(this.bailLabel);
|
|
598
|
+
B.terminate(`ret ${BAIL}`);
|
|
599
|
+
}
|
|
600
|
+
if (this.overflowLabel !== null) {
|
|
601
|
+
this.host.declare(`declare void @scr_stack_overflow()`);
|
|
602
|
+
B.startBlock(this.overflowLabel);
|
|
603
|
+
B.line(`call void @scr_stack_overflow()`);
|
|
604
|
+
B.terminate(`ret ${BAIL}`);
|
|
605
|
+
}
|
|
606
|
+
const symbol = `${mangleFunction(fn.name)}${INTEGER_CLONE_SUFFIX}`;
|
|
607
|
+
return `define internal ${CLONE_RESULT} @${symbol}(${params.join(", ")}) #0 { ; ${fn.name} (integer clone)\n${B.render()}\n}`;
|
|
608
|
+
}
|
|
609
|
+
/** A non-tail self call of the clone's own activation, inlined: the
|
|
610
|
+
* callee's int32 result (or boolean). Its own self calls stay calls. */
|
|
611
|
+
emitInlineCall(call) {
|
|
612
|
+
const { B } = this;
|
|
613
|
+
const args = this.emitArgs(call);
|
|
614
|
+
const outer = this.frame;
|
|
615
|
+
const type = this.result === "num" ? "i32" : "i1";
|
|
616
|
+
const result = `%l${this.slotCount++}`;
|
|
617
|
+
B.entryAllocas.push(`${result} = alloca ${type} ; inlined result`);
|
|
618
|
+
const done = B.newLabel("inlined");
|
|
619
|
+
this.openFrame(args, { result, done });
|
|
620
|
+
this.emitStmts(this.fn.body);
|
|
621
|
+
if (!B.isTerminated())
|
|
622
|
+
B.br(this.bail());
|
|
623
|
+
this.frame = outer;
|
|
624
|
+
B.startBlock(done);
|
|
625
|
+
const value = B.tmp();
|
|
626
|
+
B.line(`${value} = load ${type}, ptr ${result}`);
|
|
627
|
+
return value;
|
|
628
|
+
}
|
|
629
|
+
/** Whether a call is inlined (see inlineSelf). */
|
|
630
|
+
inlines(e) {
|
|
631
|
+
return this.inlineSelf && this.frame.inline === null && this.isSelfCall(e);
|
|
632
|
+
}
|
|
633
|
+
/** Whether the body has self tail calls, and whether one accumulates. */
|
|
634
|
+
tailShape() {
|
|
635
|
+
let loop = false;
|
|
636
|
+
let accumulates = false;
|
|
637
|
+
const ret = (e) => {
|
|
638
|
+
if (e.kind === "ternary") {
|
|
639
|
+
ret(e.then);
|
|
640
|
+
ret(e.else_);
|
|
641
|
+
}
|
|
642
|
+
else if (this.isSelfCall(e))
|
|
643
|
+
loop = true;
|
|
644
|
+
else if (this.accumulatingCall(e) !== null)
|
|
645
|
+
loop = accumulates = true;
|
|
646
|
+
};
|
|
647
|
+
const stmts = (list) => {
|
|
648
|
+
for (const s of list) {
|
|
649
|
+
if (s.kind === "return" && s.value)
|
|
650
|
+
ret(s.value);
|
|
651
|
+
else if (s.kind === "if") {
|
|
652
|
+
stmts(s.then);
|
|
653
|
+
if (s.else_)
|
|
654
|
+
stmts(s.else_);
|
|
655
|
+
}
|
|
656
|
+
else if (s.kind === "block")
|
|
657
|
+
stmts(s.body);
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
stmts(this.fn.body);
|
|
661
|
+
return { loop, accumulates };
|
|
662
|
+
}
|
|
663
|
+
isSelfCall(e) {
|
|
664
|
+
return e.kind === "call" && e.callee === this.fn.name;
|
|
665
|
+
}
|
|
666
|
+
/** `x + f(...)`, or `f(...) + x` with a call-free `x`: the self call and
|
|
667
|
+
* the term the loop adds to its accumulator. */
|
|
668
|
+
accumulatingCall(e) {
|
|
669
|
+
if (this.result !== "num" || e.kind !== "bin" || e.op !== "+")
|
|
670
|
+
return null;
|
|
671
|
+
if (this.isSelfCall(e.right))
|
|
672
|
+
return { call: e.right, term: e.left };
|
|
673
|
+
if (this.isSelfCall(e.left) && !callsAnything(e.right))
|
|
674
|
+
return { call: e.left, term: e.right };
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
bail() {
|
|
678
|
+
return (this.bailLabel ??= this.B.newLabel("bail"));
|
|
679
|
+
}
|
|
680
|
+
/** Continue only when the i1 `ok` holds. */
|
|
681
|
+
require(ok) {
|
|
682
|
+
const { B } = this;
|
|
683
|
+
this.host.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
684
|
+
const likely = B.tmp();
|
|
685
|
+
B.line(`${likely} = call i1 @llvm.expect.i1(i1 ${ok}, i1 true)`);
|
|
686
|
+
const next = B.newLabel("ok");
|
|
687
|
+
B.condBr(likely, next, this.bail());
|
|
688
|
+
B.startBlock(next);
|
|
689
|
+
}
|
|
690
|
+
emitStackCheck() {
|
|
691
|
+
const { B, host } = this;
|
|
692
|
+
host.declare(`declare ptr @llvm.stacksave.p0()`);
|
|
693
|
+
host.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
694
|
+
host.declare(`declare void @scr_stack_overflow()`);
|
|
695
|
+
host.declare(STACK_GUARD_DECL);
|
|
696
|
+
const sp = B.tmp();
|
|
697
|
+
const guard = B.tmp();
|
|
698
|
+
const low = B.tmp();
|
|
699
|
+
const cold = B.tmp();
|
|
700
|
+
B.line(`${sp} = call ptr @llvm.stacksave.p0()`);
|
|
701
|
+
B.line(`${guard} = load ptr, ptr @scr_stack_guard`);
|
|
702
|
+
B.line(`${low} = icmp ult ptr ${sp}, ${guard}`);
|
|
703
|
+
B.line(`${cold} = call i1 @llvm.expect.i1(i1 ${low}, i1 false)`);
|
|
704
|
+
const ok = B.newLabel("stack.ok");
|
|
705
|
+
B.condBr(cold, this.overflow(), ok);
|
|
706
|
+
B.startBlock(ok);
|
|
707
|
+
if (!this.tailShape().accumulates)
|
|
708
|
+
return;
|
|
709
|
+
const top = B.tmp();
|
|
710
|
+
B.line(`${top} = ptrtoint ptr ${sp} to i64`);
|
|
711
|
+
this.entrySp = top;
|
|
712
|
+
}
|
|
713
|
+
overflowLabel = null;
|
|
714
|
+
/** The shared block that throws Node's stack-overflow RangeError and
|
|
715
|
+
* bails (the dispatcher forwards the pending exception). */
|
|
716
|
+
overflow() {
|
|
717
|
+
return (this.overflowLabel ??= this.B.newLabel("stack.over"));
|
|
718
|
+
}
|
|
719
|
+
/** Lowers the current activation's virtual stack pointer by one frame,
|
|
720
|
+
* continuing at the overflow block past the guard. */
|
|
721
|
+
consumeFrame() {
|
|
722
|
+
const { B } = this;
|
|
723
|
+
this.host.declare(STACK_GUARD_DECL);
|
|
724
|
+
const depth = B.tmp();
|
|
725
|
+
const next = B.tmp();
|
|
726
|
+
const guardPtr = B.tmp();
|
|
727
|
+
const guard = B.tmp();
|
|
728
|
+
const more = B.tmp();
|
|
729
|
+
const likely = B.tmp();
|
|
730
|
+
B.line(`${depth} = load i64, ptr ${this.frame.depthSlot}`);
|
|
731
|
+
B.line(`${next} = sub i64 ${depth}, ${VIRTUAL_FRAME}`);
|
|
732
|
+
B.line(`store i64 ${next}, ptr ${this.frame.depthSlot}`);
|
|
733
|
+
B.line(`${guardPtr} = load ptr, ptr @scr_stack_guard`);
|
|
734
|
+
B.line(`${guard} = ptrtoint ptr ${guardPtr} to i64`);
|
|
735
|
+
// Unsigned: a pointer that wrapped below zero is far below the guard.
|
|
736
|
+
B.line(`${more} = icmp uge i64 ${next}, ${guard}`);
|
|
737
|
+
this.host.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
738
|
+
B.line(`${likely} = call i1 @llvm.expect.i1(i1 ${more}, i1 true)`);
|
|
739
|
+
const ok = B.newLabel("frame.ok");
|
|
740
|
+
B.condBr(likely, ok, this.overflow());
|
|
741
|
+
B.startBlock(ok);
|
|
742
|
+
}
|
|
743
|
+
emitStmts(list) {
|
|
744
|
+
for (const s of list) {
|
|
745
|
+
if (this.B.isTerminated())
|
|
746
|
+
return;
|
|
747
|
+
this.emitStmt(s);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
emitStmt(s) {
|
|
751
|
+
const { B } = this;
|
|
752
|
+
switch (s.kind) {
|
|
753
|
+
case "return":
|
|
754
|
+
this.emitReturn(s.value);
|
|
755
|
+
return;
|
|
756
|
+
case "if": {
|
|
757
|
+
const cond = this.emitBool(s.cond);
|
|
758
|
+
const then = B.newLabel("if.t");
|
|
759
|
+
const join = B.newLabel("if.j");
|
|
760
|
+
const otherwise = s.else_ ? B.newLabel("if.e") : join;
|
|
761
|
+
B.condBr(cond, then, otherwise);
|
|
762
|
+
B.startBlock(then);
|
|
763
|
+
this.emitStmts(s.then);
|
|
764
|
+
if (!B.isTerminated())
|
|
765
|
+
B.br(join);
|
|
766
|
+
if (s.else_) {
|
|
767
|
+
B.startBlock(otherwise);
|
|
768
|
+
this.emitStmts(s.else_);
|
|
769
|
+
if (!B.isTerminated())
|
|
770
|
+
B.br(join);
|
|
771
|
+
}
|
|
772
|
+
B.startBlock(join);
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
case "block":
|
|
776
|
+
this.emitStmts(s.body);
|
|
777
|
+
return;
|
|
778
|
+
case "varDecl":
|
|
779
|
+
case "assign": {
|
|
780
|
+
const target = this.frame.slots.get(s.localId);
|
|
781
|
+
const value = s.kind === "varDecl" ? s.init : s.value;
|
|
782
|
+
if (target.kind === "num")
|
|
783
|
+
B.line(`store i32 ${this.narrow(this.emitNum(value))}, ptr ${target.slot}`);
|
|
784
|
+
else
|
|
785
|
+
B.line(`store i1 ${this.emitBool(value)}, ptr ${target.slot}`);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
case "exprStmt":
|
|
789
|
+
if (scalarKind(s.expr.type) === "num")
|
|
790
|
+
this.narrow(this.emitNum(s.expr));
|
|
791
|
+
else
|
|
792
|
+
this.emitBool(s.expr);
|
|
793
|
+
return;
|
|
794
|
+
default:
|
|
795
|
+
throw new Error(`integer clone: unexpected statement ${s.kind}`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
emitReturn(e) {
|
|
799
|
+
const { B } = this;
|
|
800
|
+
if (e.kind === "ternary") {
|
|
801
|
+
const cond = this.emitBool(e.cond);
|
|
802
|
+
const then = B.newLabel("ret.t");
|
|
803
|
+
const otherwise = B.newLabel("ret.e");
|
|
804
|
+
B.condBr(cond, then, otherwise);
|
|
805
|
+
B.startBlock(then);
|
|
806
|
+
this.emitReturn(e.then);
|
|
807
|
+
B.startBlock(otherwise);
|
|
808
|
+
this.emitReturn(e.else_);
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
if (e.kind === "call" && this.passesThrough(e))
|
|
812
|
+
return this.emitMutualTailCall(e);
|
|
813
|
+
if (this.frame.loopLabel !== null) {
|
|
814
|
+
if (this.isSelfCall(e))
|
|
815
|
+
return this.emitTailCall(e, null);
|
|
816
|
+
const accumulating = this.accumulatingCall(e);
|
|
817
|
+
if (accumulating)
|
|
818
|
+
return this.emitTailCall(accumulating.call, accumulating.term);
|
|
819
|
+
}
|
|
820
|
+
if (this.result === "bool")
|
|
821
|
+
this.finishReturn(this.emitBool(e));
|
|
822
|
+
else
|
|
823
|
+
this.finishReturn(this.emitNum(e));
|
|
824
|
+
}
|
|
825
|
+
/** Returns `value` from the current activation: a number result adds the
|
|
826
|
+
* activation's accumulator. */
|
|
827
|
+
finishReturn(result) {
|
|
828
|
+
const { B } = this;
|
|
829
|
+
const inline = this.frame.inline;
|
|
830
|
+
if (typeof result === "string") {
|
|
831
|
+
const value = result;
|
|
832
|
+
if (inline) {
|
|
833
|
+
B.line(`store i1 ${value}, ptr ${inline.result}`);
|
|
834
|
+
B.br(inline.done);
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
const wide = B.tmp();
|
|
838
|
+
B.line(`${wide} = zext i1 ${value} to i32`);
|
|
839
|
+
B.terminate(`ret ${CLONE_RESULT} ${this.result32(wide)}`);
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
let value = result;
|
|
843
|
+
if (this.frame.accSlot !== null) {
|
|
844
|
+
const acc = B.tmp();
|
|
845
|
+
B.line(`${acc} = load i32, ptr ${this.frame.accSlot}`);
|
|
846
|
+
value = this.add("+", { name: acc, wide: false }, value);
|
|
847
|
+
}
|
|
848
|
+
if (inline) {
|
|
849
|
+
B.line(`store i32 ${this.narrow(value)}, ptr ${inline.result}`);
|
|
850
|
+
B.br(inline.done);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (value.wide) {
|
|
854
|
+
B.terminate(`ret ${CLONE_RESULT} ${value.name}`);
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
B.terminate(`ret ${CLONE_RESULT} ${this.result32(value.name)}`);
|
|
858
|
+
}
|
|
859
|
+
/** A tail call to another member of a tail-linked component, from the
|
|
860
|
+
* clone's own activation with nothing accumulated: it returns the
|
|
861
|
+
* callee's result as is. */
|
|
862
|
+
passesThrough(call) {
|
|
863
|
+
const clones = this.host.integerClones;
|
|
864
|
+
return (this.tailLinked &&
|
|
865
|
+
this.frame.inline === null &&
|
|
866
|
+
this.frame.accSlot === null &&
|
|
867
|
+
call.callee !== this.fn.name &&
|
|
868
|
+
clones.tailLinked.has(call.callee) &&
|
|
869
|
+
clones.component.get(call.callee) === clones.component.get(this.fn.name));
|
|
870
|
+
}
|
|
871
|
+
emitMutualTailCall(call) {
|
|
872
|
+
const { B } = this;
|
|
873
|
+
const args = this.emitArgs(call);
|
|
874
|
+
const result = this.emitCloneCall(call.callee, args, call.args, "tail ");
|
|
875
|
+
B.terminate(`ret ${CLONE_RESULT} ${result}`);
|
|
876
|
+
}
|
|
877
|
+
emitTailCall(call, term) {
|
|
878
|
+
const { B } = this;
|
|
879
|
+
// `f(...) + x` evaluates its call-free term first; the order is
|
|
880
|
+
// unobservable.
|
|
881
|
+
const addend = term === null ? null : this.emitNum(term);
|
|
882
|
+
const args = this.emitArgs(call);
|
|
883
|
+
if (addend !== null) {
|
|
884
|
+
// A BAIL term stops the loop at once: a pending stack overflow must
|
|
885
|
+
// not keep running calls.
|
|
886
|
+
const acc = B.tmp();
|
|
887
|
+
B.line(`${acc} = load i32, ptr ${this.frame.accSlot}`);
|
|
888
|
+
const sum = this.add("+", { name: acc, wide: false }, addend);
|
|
889
|
+
B.line(`store i32 ${sum.name}, ptr ${this.frame.accSlot}`);
|
|
890
|
+
}
|
|
891
|
+
this.fn.params.forEach((p, i) => {
|
|
892
|
+
const { slot, kind } = this.frame.slots.get(p.localId);
|
|
893
|
+
B.line(`store ${kindType(kind)} ${args[i]}, ptr ${slot}`);
|
|
894
|
+
});
|
|
895
|
+
// Only an accumulating call is real recursion in the ordinary body.
|
|
896
|
+
if (addend !== null)
|
|
897
|
+
this.consumeFrame();
|
|
898
|
+
B.br(this.frame.loopLabel);
|
|
899
|
+
}
|
|
900
|
+
/** A call of `callee`'s clone with evaluated arguments. */
|
|
901
|
+
emitCloneCall(callee, args, exprs, marker = "") {
|
|
902
|
+
const list = args.map((a, i) => `${kindType(valueKind(exprs[i].type))} ${a}`);
|
|
903
|
+
const result = this.B.tmp();
|
|
904
|
+
this.B.line(`${result} = ${marker}call ${CLONE_RESULT} @${mangleFunction(callee)}${INTEGER_CLONE_SUFFIX}(${list.join(", ")})`);
|
|
905
|
+
return result;
|
|
906
|
+
}
|
|
907
|
+
/** The borrowed array an array parameter holds. */
|
|
908
|
+
emitArray(e) {
|
|
909
|
+
const value = this.B.tmp();
|
|
910
|
+
if (e.kind !== "varRef")
|
|
911
|
+
throw new Error("integer clone: unexpected array expression");
|
|
912
|
+
this.B.line(`${value} = load ptr, ptr ${this.frame.slots.get(e.localId).slot}`);
|
|
913
|
+
return value;
|
|
914
|
+
}
|
|
915
|
+
/** `a.length`, bailing beyond the int32 range. */
|
|
916
|
+
emitLength(array) {
|
|
917
|
+
const { B } = this;
|
|
918
|
+
const field = B.tmp();
|
|
919
|
+
const len = B.tmp();
|
|
920
|
+
const fits = B.tmp();
|
|
921
|
+
const value = B.tmp();
|
|
922
|
+
B.line(`${field} = getelementptr inbounds %ScrArr, ptr ${array}, i32 0, i32 ${SCR_ARR_FIELD.len}`);
|
|
923
|
+
B.line(`${len} = load i64, ptr ${field}`);
|
|
924
|
+
B.line(`${fits} = icmp ule i64 ${len}, 2147483647`);
|
|
925
|
+
this.require(fits);
|
|
926
|
+
B.line(`${value} = trunc i64 ${len} to i32`);
|
|
927
|
+
return value;
|
|
928
|
+
}
|
|
929
|
+
/** `a[i]` for an index inside the array's hole-free prefix (the runtime
|
|
930
|
+
* keeps every slot below `filled` a present value) whose element is
|
|
931
|
+
* exactly an int32. Everything else (holes, indices outside the prefix,
|
|
932
|
+
* other numbers) bails: the ordinary body reads it. */
|
|
933
|
+
emitElement(e) {
|
|
934
|
+
const { B } = this;
|
|
935
|
+
const array = this.emitArray(e.arr);
|
|
936
|
+
const index = this.narrow(this.emitNum(e.index));
|
|
937
|
+
const filledField = B.tmp();
|
|
938
|
+
const filled = B.tmp();
|
|
939
|
+
const inside = B.tmp();
|
|
940
|
+
B.line(`${filledField} = getelementptr inbounds %ScrArr, ptr ${array}, i32 0, i32 ${SCR_ARR_FIELD.filled}`);
|
|
941
|
+
B.line(`${filled} = load i32, ptr ${filledField}`);
|
|
942
|
+
// Unsigned: a negative index lies above every bound.
|
|
943
|
+
B.line(`${inside} = icmp ult i32 ${index}, ${filled}`);
|
|
944
|
+
this.require(inside);
|
|
945
|
+
const dataField = B.tmp();
|
|
946
|
+
const data = B.tmp();
|
|
947
|
+
const offset = B.tmp();
|
|
948
|
+
const slot = B.tmp();
|
|
949
|
+
const number = B.tmp();
|
|
950
|
+
B.line(`${dataField} = getelementptr inbounds %ScrArr, ptr ${array}, i32 0, i32 ${SCR_ARR_FIELD.data}`);
|
|
951
|
+
B.line(`${data} = load ptr, ptr ${dataField}`);
|
|
952
|
+
B.line(`${offset} = zext i32 ${index} to i64`);
|
|
953
|
+
B.line(`${slot} = getelementptr inbounds double, ptr ${data}, i64 ${offset}`);
|
|
954
|
+
B.line(`${number} = load double, ptr ${slot}`);
|
|
955
|
+
return this.exactInt32(number);
|
|
956
|
+
}
|
|
957
|
+
/** The int32 the double `number` equals bit for bit (never -0, NaN, a
|
|
958
|
+
* fraction or out of range), bailing otherwise. */
|
|
959
|
+
exactInt32(number) {
|
|
960
|
+
const { B } = this;
|
|
961
|
+
this.host.declare(`declare i32 @llvm.fptosi.sat.i32.f64(double)`);
|
|
962
|
+
const value = B.tmp();
|
|
963
|
+
const back = B.tmp();
|
|
964
|
+
const a = B.tmp();
|
|
965
|
+
const b = B.tmp();
|
|
966
|
+
const exact = B.tmp();
|
|
967
|
+
B.line(`${value} = call i32 @llvm.fptosi.sat.i32.f64(double ${number})`);
|
|
968
|
+
B.line(`${back} = sitofp i32 ${value} to double`);
|
|
969
|
+
B.line(`${a} = bitcast double ${back} to i64`);
|
|
970
|
+
B.line(`${b} = bitcast double ${number} to i64`);
|
|
971
|
+
B.line(`${exact} = icmp eq i64 ${a}, ${b}`);
|
|
972
|
+
this.require(exact);
|
|
973
|
+
return value;
|
|
974
|
+
}
|
|
975
|
+
emitArgs(call) {
|
|
976
|
+
return call.args.map((a) => {
|
|
977
|
+
const kind = valueKind(a.type);
|
|
978
|
+
return kind === "arr"
|
|
979
|
+
? this.emitArray(a)
|
|
980
|
+
: kind === "num"
|
|
981
|
+
? this.narrow(this.emitNum(a))
|
|
982
|
+
: this.emitBool(a);
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
emitCall(call) {
|
|
986
|
+
const args = this.emitArgs(call);
|
|
987
|
+
return this.emitCloneCall(call.callee, args, call.args);
|
|
988
|
+
}
|
|
989
|
+
/** The successful CLONE_RESULT of the i32 `value`. */
|
|
990
|
+
result32(value) {
|
|
991
|
+
const pair = this.B.tmp();
|
|
992
|
+
this.B.line(`${pair} = insertvalue ${CLONE_RESULT} { i32 poison, i1 false }, i32 ${value}, 0`);
|
|
993
|
+
return pair;
|
|
994
|
+
}
|
|
995
|
+
/** The int32 of `v`, forwarding a bail. */
|
|
996
|
+
narrow(v) {
|
|
997
|
+
if (!v.wide)
|
|
998
|
+
return v.name;
|
|
999
|
+
return this.cloneValue(v.name);
|
|
1000
|
+
}
|
|
1001
|
+
/** The value of the clone result `pair`, forwarding a bail. The bail
|
|
1002
|
+
* returns `pair` itself, so its registers pass through unchanged. */
|
|
1003
|
+
cloneValue(pair) {
|
|
1004
|
+
const { B } = this;
|
|
1005
|
+
this.host.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
1006
|
+
const bailed = B.tmp();
|
|
1007
|
+
const unlikely = B.tmp();
|
|
1008
|
+
const value = B.tmp();
|
|
1009
|
+
B.line(`${bailed} = extractvalue ${CLONE_RESULT} ${pair}, 1`);
|
|
1010
|
+
B.line(`${unlikely} = call i1 @llvm.expect.i1(i1 ${bailed}, i1 false)`);
|
|
1011
|
+
const forward = B.newLabel("forward");
|
|
1012
|
+
const next = B.newLabel("ok");
|
|
1013
|
+
B.condBr(unlikely, forward, next);
|
|
1014
|
+
B.startBlock(forward);
|
|
1015
|
+
B.terminate(`ret ${CLONE_RESULT} ${pair}`);
|
|
1016
|
+
B.startBlock(next);
|
|
1017
|
+
B.line(`${value} = extractvalue ${CLONE_RESULT} ${pair}, 0`);
|
|
1018
|
+
return value;
|
|
1019
|
+
}
|
|
1020
|
+
widen(v) {
|
|
1021
|
+
return v.wide ? v.name : this.result32(v.name);
|
|
1022
|
+
}
|
|
1023
|
+
/** Checked `+`/`-`. */
|
|
1024
|
+
add(op, left, right) {
|
|
1025
|
+
const l = this.narrow(left);
|
|
1026
|
+
const r = this.narrow(right);
|
|
1027
|
+
return { name: this.overflowing(op === "+" ? "sadd" : "ssub", l, r), wide: false };
|
|
1028
|
+
}
|
|
1029
|
+
overflowing(op, left, right) {
|
|
1030
|
+
const { B } = this;
|
|
1031
|
+
this.host.declare(`declare { i32, i1 } @llvm.${op}.with.overflow.i32(i32, i32)`);
|
|
1032
|
+
const pair = B.tmp();
|
|
1033
|
+
const value = B.tmp();
|
|
1034
|
+
const overflow = B.tmp();
|
|
1035
|
+
const ok = B.tmp();
|
|
1036
|
+
B.line(`${pair} = call { i32, i1 } @llvm.${op}.with.overflow.i32(i32 ${left}, i32 ${right})`);
|
|
1037
|
+
B.line(`${value} = extractvalue { i32, i1 } ${pair}, 0`);
|
|
1038
|
+
B.line(`${overflow} = extractvalue { i32, i1 } ${pair}, 1`);
|
|
1039
|
+
B.line(`${ok} = xor i1 ${overflow}, true`);
|
|
1040
|
+
this.require(ok);
|
|
1041
|
+
return value;
|
|
1042
|
+
}
|
|
1043
|
+
emitNum(e) {
|
|
1044
|
+
const { B } = this;
|
|
1045
|
+
switch (e.kind) {
|
|
1046
|
+
case "numLit":
|
|
1047
|
+
return { name: String(e.value), wide: false };
|
|
1048
|
+
case "varRef": {
|
|
1049
|
+
const value = B.tmp();
|
|
1050
|
+
B.line(`${value} = load i32, ptr ${this.frame.slots.get(e.localId).slot}`);
|
|
1051
|
+
return { name: value, wide: false };
|
|
1052
|
+
}
|
|
1053
|
+
case "call":
|
|
1054
|
+
if (this.inlines(e))
|
|
1055
|
+
return { name: this.emitInlineCall(e), wide: false };
|
|
1056
|
+
return { name: this.emitCall(e), wide: true };
|
|
1057
|
+
case "arrIntrinsic":
|
|
1058
|
+
return { name: this.emitLength(this.emitArray(e.receiver)), wide: false };
|
|
1059
|
+
case "arrayGet":
|
|
1060
|
+
return { name: this.emitElement(e), wide: false };
|
|
1061
|
+
case "unary": {
|
|
1062
|
+
const operand = this.narrow(this.emitNum(e.operand));
|
|
1063
|
+
const value = B.tmp();
|
|
1064
|
+
if (e.op === "~") {
|
|
1065
|
+
B.line(`${value} = xor i32 ${operand}, -1`);
|
|
1066
|
+
return { name: value, wide: false };
|
|
1067
|
+
}
|
|
1068
|
+
// -0 is not an int32.
|
|
1069
|
+
const nonzero = B.tmp();
|
|
1070
|
+
B.line(`${nonzero} = icmp ne i32 ${operand}, 0`);
|
|
1071
|
+
this.require(nonzero);
|
|
1072
|
+
return { name: this.overflowing("ssub", "0", operand), wide: false };
|
|
1073
|
+
}
|
|
1074
|
+
case "ternary": {
|
|
1075
|
+
const cond = this.emitBool(e.cond);
|
|
1076
|
+
const then = B.newLabel("sel.t");
|
|
1077
|
+
const otherwise = B.newLabel("sel.e");
|
|
1078
|
+
const join = B.newLabel("sel.j");
|
|
1079
|
+
// Both arms join as one kind: CLONE_RESULT when either is untested.
|
|
1080
|
+
const wide = mayBail(e);
|
|
1081
|
+
const arm = (label, value) => {
|
|
1082
|
+
B.startBlock(label);
|
|
1083
|
+
const v = this.emitNum(value);
|
|
1084
|
+
const name = wide ? this.widen(v) : v.name;
|
|
1085
|
+
const end = B.currentLabel();
|
|
1086
|
+
B.br(join);
|
|
1087
|
+
return { name, label: end };
|
|
1088
|
+
};
|
|
1089
|
+
B.condBr(cond, then, otherwise);
|
|
1090
|
+
const a = arm(then, e.then);
|
|
1091
|
+
const b = arm(otherwise, e.else_);
|
|
1092
|
+
B.startBlock(join);
|
|
1093
|
+
const value = B.tmp();
|
|
1094
|
+
B.line(`${value} = phi ${wide ? CLONE_RESULT : "i32"} [ ${a.name}, %${a.label} ], [ ${b.name}, %${b.label} ]`);
|
|
1095
|
+
return { name: value, wide };
|
|
1096
|
+
}
|
|
1097
|
+
case "bin": {
|
|
1098
|
+
const left = this.emitNum(e.left);
|
|
1099
|
+
const right = this.emitNum(e.right);
|
|
1100
|
+
if (e.op === "+" || e.op === "-") {
|
|
1101
|
+
// A sum the static analysis proves int32 cannot overflow; clone
|
|
1102
|
+
// values are never -0, so neither is their sum.
|
|
1103
|
+
if (withinInt32(this.host.integerRanges.get(e))) {
|
|
1104
|
+
const value = B.tmp();
|
|
1105
|
+
B.line(`${value} = ${e.op === "+" ? "add" : "sub"} nsw i32 ${this.narrow(left)}, ${this.narrow(right)}`);
|
|
1106
|
+
return { name: value, wide: false };
|
|
1107
|
+
}
|
|
1108
|
+
return this.add(e.op, left, right);
|
|
1109
|
+
}
|
|
1110
|
+
const l = this.narrow(left);
|
|
1111
|
+
const r = this.narrow(right);
|
|
1112
|
+
const value = B.tmp();
|
|
1113
|
+
switch (e.op) {
|
|
1114
|
+
case "*": {
|
|
1115
|
+
const product = this.overflowing("smul", l, r);
|
|
1116
|
+
// A zero product of a negative factor is -0.
|
|
1117
|
+
const either = B.tmp();
|
|
1118
|
+
const negative = B.tmp();
|
|
1119
|
+
const zero = B.tmp();
|
|
1120
|
+
const minusZero = B.tmp();
|
|
1121
|
+
const ok = B.tmp();
|
|
1122
|
+
B.line(`${either} = or i32 ${l}, ${r}`);
|
|
1123
|
+
B.line(`${negative} = icmp slt i32 ${either}, 0`);
|
|
1124
|
+
B.line(`${zero} = icmp eq i32 ${product}, 0`);
|
|
1125
|
+
B.line(`${minusZero} = and i1 ${zero}, ${negative}`);
|
|
1126
|
+
B.line(`${ok} = xor i1 ${minusZero}, true`);
|
|
1127
|
+
this.require(ok);
|
|
1128
|
+
return { name: product, wide: false };
|
|
1129
|
+
}
|
|
1130
|
+
case "%": {
|
|
1131
|
+
// x % 0 is NaN; a zero remainder of a negative dividend is -0.
|
|
1132
|
+
// x % -1 is (-)0 like x % 1, which also avoids srem's overflow.
|
|
1133
|
+
const nonzero = B.tmp();
|
|
1134
|
+
const minusOne = B.tmp();
|
|
1135
|
+
const divisor = B.tmp();
|
|
1136
|
+
B.line(`${nonzero} = icmp ne i32 ${r}, 0`);
|
|
1137
|
+
this.require(nonzero);
|
|
1138
|
+
B.line(`${minusOne} = icmp eq i32 ${r}, -1`);
|
|
1139
|
+
B.line(`${divisor} = select i1 ${minusOne}, i32 1, i32 ${r}`);
|
|
1140
|
+
B.line(`${value} = srem i32 ${l}, ${divisor}`);
|
|
1141
|
+
const zero = B.tmp();
|
|
1142
|
+
const negative = B.tmp();
|
|
1143
|
+
const minusZero = B.tmp();
|
|
1144
|
+
const ok = B.tmp();
|
|
1145
|
+
B.line(`${zero} = icmp eq i32 ${value}, 0`);
|
|
1146
|
+
B.line(`${negative} = icmp slt i32 ${l}, 0`);
|
|
1147
|
+
B.line(`${minusZero} = and i1 ${zero}, ${negative}`);
|
|
1148
|
+
B.line(`${ok} = xor i1 ${minusZero}, true`);
|
|
1149
|
+
this.require(ok);
|
|
1150
|
+
return { name: value, wide: false };
|
|
1151
|
+
}
|
|
1152
|
+
case "&":
|
|
1153
|
+
case "|":
|
|
1154
|
+
case "^":
|
|
1155
|
+
B.line(`${value} = ${e.op === "&" ? "and" : e.op === "|" ? "or" : "xor"} i32 ${l}, ${r}`);
|
|
1156
|
+
return { name: value, wide: false };
|
|
1157
|
+
case "<<":
|
|
1158
|
+
case ">>":
|
|
1159
|
+
case ">>>": {
|
|
1160
|
+
const count = B.tmp();
|
|
1161
|
+
B.line(`${count} = and i32 ${r}, 31`);
|
|
1162
|
+
const op = e.op === "<<" ? "shl" : e.op === ">>" ? "ashr" : "lshr";
|
|
1163
|
+
B.line(`${value} = ${op} i32 ${l}, ${count}`);
|
|
1164
|
+
if (e.op === ">>>") {
|
|
1165
|
+
// A Uint32 result of 2^31 or more is not an int32.
|
|
1166
|
+
const fits = B.tmp();
|
|
1167
|
+
B.line(`${fits} = icmp sge i32 ${value}, 0`);
|
|
1168
|
+
this.require(fits);
|
|
1169
|
+
}
|
|
1170
|
+
return { name: value, wide: false };
|
|
1171
|
+
}
|
|
1172
|
+
default:
|
|
1173
|
+
throw new Error(`integer clone: unexpected number operator ${e.op}`);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
default:
|
|
1177
|
+
throw new Error(`integer clone: unexpected number expression ${e.kind}`);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
emitBool(e) {
|
|
1181
|
+
const { B } = this;
|
|
1182
|
+
switch (e.kind) {
|
|
1183
|
+
case "boolLit":
|
|
1184
|
+
return e.value ? "true" : "false";
|
|
1185
|
+
case "varRef": {
|
|
1186
|
+
const value = B.tmp();
|
|
1187
|
+
B.line(`${value} = load i1, ptr ${this.frame.slots.get(e.localId).slot}`);
|
|
1188
|
+
return value;
|
|
1189
|
+
}
|
|
1190
|
+
case "call": {
|
|
1191
|
+
if (this.inlines(e))
|
|
1192
|
+
return this.emitInlineCall(e);
|
|
1193
|
+
const result = this.cloneValue(this.emitCall(e));
|
|
1194
|
+
const value = B.tmp();
|
|
1195
|
+
B.line(`${value} = trunc i32 ${result} to i1`);
|
|
1196
|
+
return value;
|
|
1197
|
+
}
|
|
1198
|
+
case "unary": {
|
|
1199
|
+
const operand = this.emitBool(e.operand);
|
|
1200
|
+
const value = B.tmp();
|
|
1201
|
+
B.line(`${value} = xor i1 ${operand}, true`);
|
|
1202
|
+
return value;
|
|
1203
|
+
}
|
|
1204
|
+
case "toBool": {
|
|
1205
|
+
if (scalarKind(e.operand.type) === "bool")
|
|
1206
|
+
return this.emitBool(e.operand);
|
|
1207
|
+
const operand = this.narrow(this.emitNum(e.operand));
|
|
1208
|
+
const value = B.tmp();
|
|
1209
|
+
B.line(`${value} = icmp ne i32 ${operand}, 0`);
|
|
1210
|
+
return value;
|
|
1211
|
+
}
|
|
1212
|
+
case "bin": {
|
|
1213
|
+
const value = B.tmp();
|
|
1214
|
+
if (scalarKind(e.left.type) === "bool") {
|
|
1215
|
+
const l = this.emitBool(e.left);
|
|
1216
|
+
const r = this.emitBool(e.right);
|
|
1217
|
+
B.line(`${value} = icmp ${e.op === "===" ? "eq" : "ne"} i1 ${l}, ${r}`);
|
|
1218
|
+
return value;
|
|
1219
|
+
}
|
|
1220
|
+
const l = this.narrow(this.emitNum(e.left));
|
|
1221
|
+
const r = this.narrow(this.emitNum(e.right));
|
|
1222
|
+
const predicate = {
|
|
1223
|
+
"<": "slt",
|
|
1224
|
+
"<=": "sle",
|
|
1225
|
+
">": "sgt",
|
|
1226
|
+
">=": "sge",
|
|
1227
|
+
"===": "eq",
|
|
1228
|
+
"!==": "ne",
|
|
1229
|
+
};
|
|
1230
|
+
B.line(`${value} = icmp ${predicate[e.op]} i32 ${l}, ${r}`);
|
|
1231
|
+
return value;
|
|
1232
|
+
}
|
|
1233
|
+
case "logical": {
|
|
1234
|
+
const left = this.emitBool(e.left);
|
|
1235
|
+
const leftLabel = B.currentLabel();
|
|
1236
|
+
const right = B.newLabel("logic.r");
|
|
1237
|
+
const join = B.newLabel("logic.j");
|
|
1238
|
+
if (e.op === "&&")
|
|
1239
|
+
B.condBr(left, right, join);
|
|
1240
|
+
else
|
|
1241
|
+
B.condBr(left, join, right);
|
|
1242
|
+
B.startBlock(right);
|
|
1243
|
+
const r = this.emitBool(e.right);
|
|
1244
|
+
const rightLabel = B.currentLabel();
|
|
1245
|
+
B.br(join);
|
|
1246
|
+
B.startBlock(join);
|
|
1247
|
+
const value = B.tmp();
|
|
1248
|
+
B.line(`${value} = phi i1 [ ${e.op === "&&" ? "false" : "true"}, %${leftLabel} ], [ ${r}, %${rightLabel} ]`);
|
|
1249
|
+
return value;
|
|
1250
|
+
}
|
|
1251
|
+
case "ternary": {
|
|
1252
|
+
const cond = this.emitBool(e.cond);
|
|
1253
|
+
const then = B.newLabel("sel.t");
|
|
1254
|
+
const otherwise = B.newLabel("sel.e");
|
|
1255
|
+
const join = B.newLabel("sel.j");
|
|
1256
|
+
B.condBr(cond, then, otherwise);
|
|
1257
|
+
B.startBlock(then);
|
|
1258
|
+
const a = this.emitBool(e.then);
|
|
1259
|
+
const aLabel = B.currentLabel();
|
|
1260
|
+
B.br(join);
|
|
1261
|
+
B.startBlock(otherwise);
|
|
1262
|
+
const b = this.emitBool(e.else_);
|
|
1263
|
+
const bLabel = B.currentLabel();
|
|
1264
|
+
B.br(join);
|
|
1265
|
+
B.startBlock(join);
|
|
1266
|
+
const value = B.tmp();
|
|
1267
|
+
B.line(`${value} = phi i1 [ ${a}, %${aLabel} ], [ ${b}, %${bLabel} ]`);
|
|
1268
|
+
return value;
|
|
1269
|
+
}
|
|
1270
|
+
default:
|
|
1271
|
+
throw new Error(`integer clone: unexpected boolean expression ${e.kind}`);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
/** The dispatcher of member `fn` for the direct-call entry `entry` (the
|
|
1276
|
+
* ordinary symbol, or its i32 entry: `int32Params`/`int32Result` give that
|
|
1277
|
+
* entry's i32 positions). It has the entry's signature, so a call site
|
|
1278
|
+
* only switches its target. */
|
|
1279
|
+
export function emitIntegerDispatcher(fn, host, entry, int32Params, int32Result) {
|
|
1280
|
+
host.declare(`declare i32 @llvm.fptosi.sat.i32.f64(double)`);
|
|
1281
|
+
host.declare(`declare i1 @llvm.expect.i1(i1, i1)`);
|
|
1282
|
+
const lines = [];
|
|
1283
|
+
const params = [];
|
|
1284
|
+
const entryArgs = [];
|
|
1285
|
+
const cloneArgs = [];
|
|
1286
|
+
const exact = [];
|
|
1287
|
+
fn.params.forEach((p, i) => {
|
|
1288
|
+
const kind = valueKind(p.type);
|
|
1289
|
+
if (kind === "bool" || kind === "arr") {
|
|
1290
|
+
// Borrowed arrays pass through to the clone and the ordinary entry.
|
|
1291
|
+
const type = kindType(kind);
|
|
1292
|
+
params.push(`${type} %p${i}`);
|
|
1293
|
+
entryArgs.push(`${type} %p${i}`);
|
|
1294
|
+
cloneArgs.push(`${type} %p${i}`);
|
|
1295
|
+
}
|
|
1296
|
+
else if (int32Params[i]) {
|
|
1297
|
+
params.push(`i32 %p${i}`);
|
|
1298
|
+
entryArgs.push(`i32 %p${i}`);
|
|
1299
|
+
cloneArgs.push(`i32 %p${i}`);
|
|
1300
|
+
}
|
|
1301
|
+
else {
|
|
1302
|
+
// Exactly an int32: the bits round-trip (so never -0 or NaN).
|
|
1303
|
+
params.push(`double %p${i}`);
|
|
1304
|
+
entryArgs.push(`double %p${i}`);
|
|
1305
|
+
cloneArgs.push(`i32 %i${i}`);
|
|
1306
|
+
lines.push(` %i${i} = call i32 @llvm.fptosi.sat.i32.f64(double %p${i})`, ` %d${i} = sitofp i32 %i${i} to double`, ` %x${i} = bitcast double %d${i} to i64`, ` %y${i} = bitcast double %p${i} to i64`, ` %e${i} = icmp eq i64 %x${i}, %y${i}`);
|
|
1307
|
+
exact.push(`%e${i}`);
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
let all = "true";
|
|
1311
|
+
exact.forEach((e, i) => {
|
|
1312
|
+
if (i === 0)
|
|
1313
|
+
all = e;
|
|
1314
|
+
else {
|
|
1315
|
+
lines.push(` %all${i} = and i1 ${all}, ${e}`);
|
|
1316
|
+
all = `%all${i}`;
|
|
1317
|
+
}
|
|
1318
|
+
});
|
|
1319
|
+
const bool = scalarKind(fn.returnType) === "bool";
|
|
1320
|
+
const ret = bool ? "i1" : int32Result ? "i32" : "double";
|
|
1321
|
+
const dummy = bool ? "false" : int32Result ? "0" : "0.0";
|
|
1322
|
+
lines.push(` %fast = call i1 @llvm.expect.i1(i1 ${all}, i1 true)`, ` br i1 %fast, label %clone, label %ordinary`, `clone:`, ` %r = call ${CLONE_RESULT} @${mangleFunction(fn.name)}${INTEGER_CLONE_SUFFIX}(${cloneArgs.join(", ")})`, ` %bailed = extractvalue ${CLONE_RESULT} %r, 1`, ` %ok = xor i1 %bailed, true`, ` %n = extractvalue ${CLONE_RESULT} %r, 0`, bool
|
|
1323
|
+
? ` %v = trunc i32 %n to i1`
|
|
1324
|
+
: int32Result
|
|
1325
|
+
? ` %v = add i32 %n, 0`
|
|
1326
|
+
: ` %v = sitofp i32 %n to double`);
|
|
1327
|
+
lines.push(` %likely = call i1 @llvm.expect.i1(i1 %ok, i1 true)`, ` br i1 %likely, label %done, label %bail`, `done:`, ` ret ${ret} %v`, `bail:`,
|
|
1328
|
+
// A stack overflow left its RangeError pending: forward it.
|
|
1329
|
+
...host.pendingTestLines("%pending"), ` br i1 %pending, label %thrown, label %ordinary`, `thrown:`, ` ret ${ret} ${dummy}`, `ordinary:`, ` %o = call ${ret} @${entry}(${entryArgs.join(", ")})`, ` ret ${ret} %o`);
|
|
1330
|
+
return `define internal ${ret} @${entry}${INTEGER_DISPATCH_SUFFIX}(${params.join(", ")}) #0 { ; ${fn.name} (integer dispatch)\nentry:\n${lines.join("\n")}\n}`;
|
|
1331
|
+
}
|
|
1332
|
+
/** The integer clone of member `fn`. */
|
|
1333
|
+
export function emitIntegerClone(fn, host, stackCheck) {
|
|
1334
|
+
return new CloneEmitter(fn, host, stackCheck).emit();
|
|
1335
|
+
}
|
|
1336
|
+
//# sourceMappingURL=integer-clones.js.map
|