@harmoniclabs/pebble 0.2.0 → 0.3.0
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/dist/IR/CompilationCtx.d.ts +40 -0
- package/dist/IR/CompilationCtx.js +54 -0
- package/dist/IR/IRHash.d.ts +23 -2
- package/dist/IR/IRHash.js +10 -60
- package/dist/IR/IRNodes/IRConst.js +22 -2
- package/dist/IR/IRNodes/IRHoisted.d.ts +0 -1
- package/dist/IR/IRNodes/IRHoisted.js +4 -6
- package/dist/IR/IRNodes/IRLetted.d.ts +0 -1
- package/dist/IR/IRNodes/IRLetted.js +4 -6
- package/dist/IR/IRNodes/IRNative/IRNativeTag.d.ts +22 -2
- package/dist/IR/IRNodes/IRNative/IRNativeTag.js +26 -2
- package/dist/IR/IRNodes/IRNative/index.d.ts +16 -1
- package/dist/IR/IRNodes/IRNative/index.js +27 -2
- package/dist/IR/IRNodes/utils/hashVarSym.d.ts +0 -1
- package/dist/IR/IRNodes/utils/hashVarSym.js +27 -33
- package/dist/IR/toUPLC/CompilerOptions.d.ts +12 -0
- package/dist/IR/toUPLC/CompilerOptions.js +14 -9
- package/dist/IR/toUPLC/compileIRToUPLC.js +39 -3
- package/dist/IR/toUPLC/subRoutines/inlineSingleUseLetBindingsAndReturnRoot.d.ts +23 -0
- package/dist/IR/toUPLC/subRoutines/inlineSingleUseLetBindingsAndReturnRoot.js +263 -0
- package/dist/IR/toUPLC/subRoutines/introduceCaseForDualHeadTailAndReturnRoot.d.ts +35 -0
- package/dist/IR/toUPLC/subRoutines/introduceCaseForDualHeadTailAndReturnRoot.js +169 -0
- package/dist/IR/toUPLC/subRoutines/replaceHoistedWithLetted.d.ts +0 -1
- package/dist/IR/toUPLC/subRoutines/replaceHoistedWithLetted.js +6 -6
- package/dist/IR/toUPLC/subRoutines/replaceNatives/nativeToIR.d.ts +2 -3
- package/dist/IR/toUPLC/subRoutines/replaceNatives/nativeToIR.js +106 -65
- package/dist/IR/toUPLC/subRoutines/rewriteHeadTailInCaseConsAndReturnRoot.d.ts +30 -0
- package/dist/IR/toUPLC/subRoutines/rewriteHeadTailInCaseConsAndReturnRoot.js +95 -0
- package/dist/IR/toUPLC/subRoutines/rewriteNativesAppliedToConstantsAndReturnRoot.js +36 -5
- package/dist/IR/toUPLC/subRoutines/rewriteToCaseOverConstAndReturnRoot.d.ts +35 -0
- package/dist/IR/toUPLC/subRoutines/rewriteToCaseOverConstAndReturnRoot.js +169 -0
- package/dist/IR/tree_utils/_ir_caseList.d.ts +15 -0
- package/dist/IR/tree_utils/_ir_caseList.js +19 -0
- package/dist/ast/nodes/statements/declarations/StructDecl.d.ts +16 -2
- package/dist/ast/nodes/statements/declarations/StructDecl.js +15 -1
- package/dist/compiler/AstCompiler/AstCompiler.d.ts +1 -0
- package/dist/compiler/AstCompiler/AstCompiler.js +41 -4
- package/dist/compiler/AstCompiler/internal/_deriveContractBody/_deriveContractBody.js +3 -3
- package/dist/compiler/AstCompiler/internal/exprs/_compileCaseExpr.js +31 -0
- package/dist/compiler/AstCompiler/internal/exprs/_compileIsExpr.js +12 -0
- package/dist/compiler/AstCompiler/internal/exprs/_compilePropAccessExpr.js +36 -0
- package/dist/compiler/AstCompiler/internal/exprs/_compileUnaryPrefixExpr.js +13 -1
- package/dist/compiler/AstCompiler/internal/exprs/binary/_compileAddExpr.js +18 -5
- package/dist/compiler/AstCompiler/internal/exprs/binary/_compileEqualExpr.js +3 -1
- package/dist/compiler/AstCompiler/internal/exprs/binary/_compileGreaterThanEqualExpr.js +2 -1
- package/dist/compiler/AstCompiler/internal/exprs/binary/_compileGreaterThanExpr.js +2 -1
- package/dist/compiler/AstCompiler/internal/exprs/binary/_compileLessThanEqualExpr.js +2 -1
- package/dist/compiler/AstCompiler/internal/exprs/binary/_compileLessThanExpr.js +2 -1
- package/dist/compiler/AstCompiler/internal/exprs/binary/_compileMultExpr.js +24 -6
- package/dist/compiler/AstCompiler/internal/exprs/binary/_compileNotEqualExpr.js +2 -1
- package/dist/compiler/AstCompiler/internal/exprs/binary/_compileSubExpr.js +16 -5
- package/dist/compiler/AstCompiler/internal/statements/_compileMatchStmt.js +33 -20
- package/dist/compiler/AstCompiler/utils/getPropAccessReturnType.js +11 -0
- package/dist/compiler/Compiler.js +20 -27
- package/dist/compiler/TirCompiler/expressify/ExpressifyCtx.js +1 -1
- package/dist/compiler/TirCompiler/expressify/expressify.js +30 -2
- package/dist/compiler/TirCompiler/expressify/expressifyForStmt.d.ts +2 -1
- package/dist/compiler/TirCompiler/expressify/expressifyForStmt.js +45 -7
- package/dist/compiler/TirCompiler/expressify/expressifyVars.d.ts +0 -1
- package/dist/compiler/TirCompiler/expressify/expressifyVars.js +23 -8
- package/dist/compiler/tir/expressions/TirCaseExpr.d.ts +9 -0
- package/dist/compiler/tir/expressions/TirCaseExpr.js +144 -122
- package/dist/compiler/tir/expressions/TirElemAccessExpr.js +2 -2
- package/dist/compiler/tir/expressions/TirFromDataExpr.js +102 -67
- package/dist/compiler/tir/expressions/TirIsExpr.js +14 -1
- package/dist/compiler/tir/expressions/TirNativeFunc.d.ts +1 -2
- package/dist/compiler/tir/expressions/TirNativeFunc.js +2 -12
- package/dist/compiler/tir/expressions/TirToDataExpr.js +3 -0
- package/dist/compiler/tir/expressions/TirTypeConversionExpr.js +10 -0
- package/dist/compiler/tir/expressions/TirVariableAccessExpr.d.ts +2 -3
- package/dist/compiler/tir/expressions/TirVariableAccessExpr.js +1 -4
- package/dist/compiler/tir/expressions/ToIRTermCtx.d.ts +20 -3
- package/dist/compiler/tir/expressions/ToIRTermCtx.js +48 -3
- package/dist/compiler/tir/expressions/binary/TirBinaryExpr.d.ts +2 -2
- package/dist/compiler/tir/expressions/binary/TirBinaryExpr.js +45 -8
- package/dist/compiler/tir/expressions/litteral/TirLitEnumMemberExpr.d.ts +19 -0
- package/dist/compiler/tir/expressions/litteral/TirLitEnumMemberExpr.js +24 -0
- package/dist/compiler/tir/expressions/litteral/TirLitteralExpr.d.ts +2 -1
- package/dist/compiler/tir/expressions/litteral/TirLitteralExpr.js +2 -0
- package/dist/compiler/tir/expressions/unary/TirUnaryMinus.js +4 -1
- package/dist/compiler/tir/program/stdScope/populateStdNamespace.js +49 -4
- package/dist/compiler/tir/program/stdScope/prelude/preludeTypesSrc.js +35 -2
- package/dist/compiler/tir/program/stdScope/stdScope.d.ts +7 -0
- package/dist/compiler/tir/program/stdScope/stdScope.js +83 -40
- package/dist/compiler/tir/types/TirEnumType.d.ts +21 -0
- package/dist/compiler/tir/types/TirEnumType.js +36 -0
- package/dist/compiler/tir/types/TirNativeType/TirNativeType.d.ts +4 -2
- package/dist/compiler/tir/types/TirNativeType/TirNativeType.js +5 -0
- package/dist/compiler/tir/types/TirNativeType/native/array.d.ts +16 -0
- package/dist/compiler/tir/types/TirNativeType/native/array.js +38 -0
- package/dist/compiler/tir/types/TirNativeType/native/index.d.ts +2 -0
- package/dist/compiler/tir/types/TirNativeType/native/index.js +2 -0
- package/dist/compiler/tir/types/TirNativeType/native/value.d.ts +18 -0
- package/dist/compiler/tir/types/TirNativeType/native/value.js +17 -0
- package/dist/compiler/tir/types/TirStructType.js +6 -1
- package/dist/compiler/tir/types/TirType.d.ts +3 -2
- package/dist/compiler/tir/types/TirType.js +4 -1
- package/dist/compiler/tir/types/utils/canAssignTo.js +28 -0
- package/dist/compiler/tir/types/utils/canCastTo.js +14 -1
- package/dist/compiler/tir/types/utils/getDeconstructableType.d.ts +2 -1
- package/dist/compiler/tir/types/utils/getDeconstructableType.js +2 -0
- package/dist/compiler/tir/types/utils/inferTypeArgs.js +4 -0
- package/dist/compiler/tir/types/utils/normalizeEnumToInt.d.ts +10 -0
- package/dist/compiler/tir/types/utils/normalizeEnumToInt.js +17 -0
- package/dist/compiler/tir/types/utils/substituteTypeParams.js +5 -0
- package/dist/diagnostics/diagnosticMessages.generated.d.ts +5 -0
- package/dist/diagnostics/diagnosticMessages.generated.js +10 -0
- package/dist/parser/Parser.js +29 -13
- package/dist/tokenizer/Token.d.ts +8 -7
- package/dist/tokenizer/Token.js +8 -7
- package/dist/tokenizer/utils/tokenFromKeyword.js +2 -0
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/package.json +3 -3
- package/dist/IR/tree_utils/_ir_lazyChooseList.d.ts +0 -3
- package/dist/IR/tree_utils/_ir_lazyChooseList.js +0 -7
|
@@ -84,6 +84,18 @@ export interface CompilerOptions {
|
|
|
84
84
|
*
|
|
85
85
|
**/
|
|
86
86
|
addMarker: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Controls the default Data encoding for single-constructor shortcut
|
|
89
|
+
* struct declarations (`struct Foo { x: int }`).
|
|
90
|
+
*
|
|
91
|
+
* - `"default"`: shortcut single-constructor structs are encoded as
|
|
92
|
+
* `constrData(0, fields)` (backwards-compatible). Use the explicit
|
|
93
|
+
* `untagged` keyword to opt a specific struct in to the minimal form.
|
|
94
|
+
* - `"minimal"`: shortcut single-constructor structs are encoded as
|
|
95
|
+
* `listData(fields)` (smaller / cheaper). The explicit `untagged`
|
|
96
|
+
* keyword still forces minimal regardless.
|
|
97
|
+
*/
|
|
98
|
+
encodingStrategy: "default" | "minimal";
|
|
87
99
|
}
|
|
88
100
|
/**
|
|
89
101
|
* Option templates intentionally omit `compilerVersion` — every caller must
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isObject } from "@harmoniclabs/obj-utils";
|
|
2
|
-
import {
|
|
2
|
+
import { UPLCVersion } from "@harmoniclabs/uplc";
|
|
3
3
|
import { COMPILER_VERSION } from "../../version.generated.js";
|
|
4
|
+
const defaultTargetUplcVersion = new UPLCVersion(1, 1, 0);
|
|
4
5
|
export { COMPILER_VERSION };
|
|
5
6
|
export const productionUplcOptimizations = Object.freeze({
|
|
6
7
|
groupApplications: true,
|
|
@@ -39,33 +40,36 @@ export const extremeOptions = Object.freeze({
|
|
|
39
40
|
root: ".",
|
|
40
41
|
outDir: "./out",
|
|
41
42
|
silent: false,
|
|
42
|
-
targetUplcVersion:
|
|
43
|
+
targetUplcVersion: defaultTargetUplcVersion,
|
|
43
44
|
removeTraces: true,
|
|
44
45
|
delayHoists: true,
|
|
45
46
|
uplcOptimizations: productionUplcOptimizations,
|
|
46
|
-
addMarker: true
|
|
47
|
+
addMarker: true,
|
|
48
|
+
encodingStrategy: "minimal"
|
|
47
49
|
});
|
|
48
50
|
export const productionOptions = Object.freeze({
|
|
49
51
|
entry: "./src/index.pebble",
|
|
50
52
|
root: ".",
|
|
51
53
|
outDir: "./out",
|
|
52
54
|
silent: false,
|
|
53
|
-
targetUplcVersion:
|
|
55
|
+
targetUplcVersion: defaultTargetUplcVersion,
|
|
54
56
|
removeTraces: true,
|
|
55
57
|
delayHoists: true,
|
|
56
58
|
uplcOptimizations: productionUplcOptimizations,
|
|
57
|
-
addMarker: true
|
|
59
|
+
addMarker: true,
|
|
60
|
+
encodingStrategy: "default"
|
|
58
61
|
});
|
|
59
62
|
export const debugOptions = Object.freeze({
|
|
60
63
|
entry: "./src/index.pebble",
|
|
61
64
|
root: ".",
|
|
62
65
|
outDir: "./out",
|
|
63
66
|
silent: false,
|
|
64
|
-
targetUplcVersion:
|
|
67
|
+
targetUplcVersion: defaultTargetUplcVersion,
|
|
65
68
|
removeTraces: false,
|
|
66
69
|
delayHoists: false,
|
|
67
70
|
uplcOptimizations: debugUplcOptimizations,
|
|
68
|
-
addMarker: false
|
|
71
|
+
addMarker: false,
|
|
72
|
+
encodingStrategy: "default"
|
|
69
73
|
});
|
|
70
74
|
export const defaultOptions = Object.freeze({
|
|
71
75
|
...productionOptions,
|
|
@@ -77,7 +81,7 @@ export const testOptions = Object.freeze({
|
|
|
77
81
|
});
|
|
78
82
|
export const defulatCompilerOptions = defaultOptions;
|
|
79
83
|
export function completeCompilerOptions(options, complete = defaultOptions) {
|
|
80
|
-
let targetUplcVersion = options.targetUplcVersion instanceof UPLCVersion ? complete.targetUplcVersion :
|
|
84
|
+
let targetUplcVersion = options.targetUplcVersion instanceof UPLCVersion ? complete.targetUplcVersion : defaultTargetUplcVersion;
|
|
81
85
|
complete = {
|
|
82
86
|
...defaultOptions,
|
|
83
87
|
...complete
|
|
@@ -107,6 +111,7 @@ export function completeCompilerOptions(options, complete = defaultOptions) {
|
|
|
107
111
|
removeTraces: options.removeTraces ?? complete.removeTraces,
|
|
108
112
|
delayHoists: options.delayHoists ?? complete.delayHoists,
|
|
109
113
|
uplcOptimizations: completeUplcOptimizations(uplcOptimizations),
|
|
110
|
-
addMarker: options.addMarker ?? complete.addMarker
|
|
114
|
+
addMarker: options.addMarker ?? complete.addMarker,
|
|
115
|
+
encodingStrategy: options.encodingStrategy ?? complete.encodingStrategy ?? "default"
|
|
111
116
|
};
|
|
112
117
|
}
|
|
@@ -16,6 +16,10 @@ import { IRCase, IRConstr, IRFunc, IRNative } from "../IRNodes/index.js";
|
|
|
16
16
|
import { replaceForcedNativesWithHoisted } from "./subRoutines/replaceForcedNativesWithHoisted.js";
|
|
17
17
|
import { performUplcOptimizationsAndReturnRoot } from "./subRoutines/performUplcOptimizationsAndReturnRoot/performUplcOptimizationsAndReturnRoot.js";
|
|
18
18
|
import { rewriteNativesAppliedToConstantsAndReturnRoot } from "./subRoutines/rewriteNativesAppliedToConstantsAndReturnRoot.js";
|
|
19
|
+
import { rewriteToCaseOverConstAndReturnRoot } from "./subRoutines/rewriteToCaseOverConstAndReturnRoot.js";
|
|
20
|
+
import { rewriteHeadTailInCaseConsAndReturnRoot } from "./subRoutines/rewriteHeadTailInCaseConsAndReturnRoot.js";
|
|
21
|
+
import { introduceCaseForDualHeadTailAndReturnRoot } from "./subRoutines/introduceCaseForDualHeadTailAndReturnRoot.js";
|
|
22
|
+
import { inlineSingleUseLetBindingsAndReturnRoot } from "./subRoutines/inlineSingleUseLetBindingsAndReturnRoot.js";
|
|
19
23
|
import { _debug_assertClosedIR } from "../utils/index.js";
|
|
20
24
|
import { ToUplcCtx } from "./ctx/ToUplcCtx.js";
|
|
21
25
|
import { removeUnusedVarsAndReturnRoot } from "./subRoutines/removeUnusuedVarsAndReturnRoot/removeUnusuedVarsAndReturnRoot.js";
|
|
@@ -57,6 +61,29 @@ export function compileIRToUPLC(term, paritalOptions = defaultOptions) {
|
|
|
57
61
|
term = replaceNativesAndReturnRoot(term);
|
|
58
62
|
// re-call rewrite to optimize introduced hoisted
|
|
59
63
|
term = rewriteNativesAppliedToConstantsAndReturnRoot(term);
|
|
64
|
+
// Lower `strictIfThenElse` triple-apps to `IRCase` BEFORE
|
|
65
|
+
// `replaceForcedNativesWithHoisted` would otherwise hoist
|
|
66
|
+
// `(force ifThenElse)` into a shared variable that's no longer
|
|
67
|
+
// pattern-matchable as a native. (`strictChooseList` is already
|
|
68
|
+
// lowered to `IRCase` unconditionally by the earlier
|
|
69
|
+
// `rewriteNativesAppliedToConstantsAndReturnRoot` pass.)
|
|
70
|
+
term = rewriteToCaseOverConstAndReturnRoot(term);
|
|
71
|
+
// Inside `case L of cons h t -> body`, replace any `headList(L)` /
|
|
72
|
+
// `tailList(L)` calls within `body` with `h` / `t`. Drop the now-dead
|
|
73
|
+
// `h`/`t` bindings via a fresh unused-vars sweep.
|
|
74
|
+
term = rewriteHeadTailInCaseConsAndReturnRoot(term);
|
|
75
|
+
term = removeUnusedVarsAndReturnRoot(term);
|
|
76
|
+
// For every IRFunc body where the same list L is accessed via BOTH
|
|
77
|
+
// `headList(L)` and `tailList(L)`, wrap the body in
|
|
78
|
+
// `case L of cons h t -> body' | nil -> error` and substitute the two
|
|
79
|
+
// builtin calls with `h` / `t`. Empirically (bench.headTailVsCase):
|
|
80
|
+
// one case dispatch costs ~128K CPU vs ~160K for two builtin calls,
|
|
81
|
+
// and only ~9 bytes vs ~16. The previous head/tail-in-case-cons pass
|
|
82
|
+
// can then make a second sweep to substitute any further internal
|
|
83
|
+
// references the new case introduced.
|
|
84
|
+
term = introduceCaseForDualHeadTailAndReturnRoot(term);
|
|
85
|
+
term = rewriteHeadTailInCaseConsAndReturnRoot(term);
|
|
86
|
+
term = removeUnusedVarsAndReturnRoot(term);
|
|
60
87
|
// debugAsserts && _debug_assertions( term );
|
|
61
88
|
// unwrap top level letted and hoisted;
|
|
62
89
|
// some natives may be converted to hoisted;
|
|
@@ -144,12 +171,21 @@ export function compileIRToUPLC(term, paritalOptions = defaultOptions) {
|
|
|
144
171
|
// somethingWasInlined = inlineResult.somethingWasInlined;
|
|
145
172
|
// }
|
|
146
173
|
term = removeUnusedVarsAndReturnRoot(term);
|
|
174
|
+
// After `handleLettedAndReturnRoot` lowers `IRLetted` into the
|
|
175
|
+
// `IRApp(IRFunc([p], body), value)` shape, this is the first point
|
|
176
|
+
// where the let-as-application pattern is syntactically visible.
|
|
177
|
+
// Run the inliner here (NOT earlier, where lets are still IRLetted
|
|
178
|
+
// nodes the inliner doesn't recognize). Single-use uses trapped
|
|
179
|
+
// inside nested closures are skipped — see the pass for details.
|
|
180
|
+
term = inlineSingleUseLetBindingsAndReturnRoot(term);
|
|
181
|
+
term = removeUnusedVarsAndReturnRoot(term);
|
|
147
182
|
term = performUplcOptimizationsAndReturnRoot(term, options);
|
|
183
|
+
// Rewrite strictIfThenElse into IRCase-over-Const, and prune
|
|
184
|
+
// trailing IRError continuations from any IRCase.
|
|
185
|
+
term = rewriteToCaseOverConstAndReturnRoot(term);
|
|
148
186
|
term = ensureProperlyForcedBuiltinsAndReturnRoot(term);
|
|
149
187
|
if (options.addMarker &&
|
|
150
|
-
options.targetUplcVersion.
|
|
151
|
-
options.targetUplcVersion.minor >= 1 &&
|
|
152
|
-
options.targetUplcVersion.patch >= 0) {
|
|
188
|
+
options.targetUplcVersion.isV3Friendly()) {
|
|
153
189
|
term = new IRCase(new IRConstr(0, []), [
|
|
154
190
|
term,
|
|
155
191
|
// never evaluated
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline single-use let-bindings of the shape `((λp. body) value)`:
|
|
3
|
+
*
|
|
4
|
+
* - If `body` references `p` exactly once AND that reference is NOT
|
|
5
|
+
* trapped inside a nested closure (IRFunc/IRRecursive) within
|
|
6
|
+
* `body`, replace the entire app with `body[p := value]`. The
|
|
7
|
+
* nested-closure guard is critical: a use that's syntactically
|
|
8
|
+
* "once" inside a recursive body is actually evaluated once per
|
|
9
|
+
* iteration, and inlining there would move per-iteration work to a
|
|
10
|
+
* value the caller currently evaluates once.
|
|
11
|
+
* - If `body` references `p` zero times, drop the let and replace
|
|
12
|
+
* the app with `body`.
|
|
13
|
+
*
|
|
14
|
+
* IRCase continuations are themselves IRFunc nodes (case-cons branches
|
|
15
|
+
* take h/t lambdas), but the case dispatches each continuation AT MOST
|
|
16
|
+
* ONCE per case eval, so they don't count as "trapping" closures — we
|
|
17
|
+
* treat IRFunc reached *as a direct case continuation* as transparent.
|
|
18
|
+
*
|
|
19
|
+
* If `body` uses `p` two or more times we don't inline either (would
|
|
20
|
+
* duplicate the term in the output).
|
|
21
|
+
*/
|
|
22
|
+
import { IRTerm } from "../../IRTerm.js";
|
|
23
|
+
export declare function inlineSingleUseLetBindingsAndReturnRoot(term: IRTerm): IRTerm;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline single-use let-bindings of the shape `((λp. body) value)`:
|
|
3
|
+
*
|
|
4
|
+
* - If `body` references `p` exactly once AND that reference is NOT
|
|
5
|
+
* trapped inside a nested closure (IRFunc/IRRecursive) within
|
|
6
|
+
* `body`, replace the entire app with `body[p := value]`. The
|
|
7
|
+
* nested-closure guard is critical: a use that's syntactically
|
|
8
|
+
* "once" inside a recursive body is actually evaluated once per
|
|
9
|
+
* iteration, and inlining there would move per-iteration work to a
|
|
10
|
+
* value the caller currently evaluates once.
|
|
11
|
+
* - If `body` references `p` zero times, drop the let and replace
|
|
12
|
+
* the app with `body`.
|
|
13
|
+
*
|
|
14
|
+
* IRCase continuations are themselves IRFunc nodes (case-cons branches
|
|
15
|
+
* take h/t lambdas), but the case dispatches each continuation AT MOST
|
|
16
|
+
* ONCE per case eval, so they don't count as "trapping" closures — we
|
|
17
|
+
* treat IRFunc reached *as a direct case continuation* as transparent.
|
|
18
|
+
*
|
|
19
|
+
* If `body` uses `p` two or more times we don't inline either (would
|
|
20
|
+
* duplicate the term in the output).
|
|
21
|
+
*/
|
|
22
|
+
import { IRApp } from "../../IRNodes/IRApp.js";
|
|
23
|
+
import { IRCase } from "../../IRNodes/IRCase.js";
|
|
24
|
+
import { IRConst } from "../../IRNodes/IRConst.js";
|
|
25
|
+
import { IRDelayed } from "../../IRNodes/IRDelayed.js";
|
|
26
|
+
import { IRFunc } from "../../IRNodes/IRFunc.js";
|
|
27
|
+
import { IRHoisted } from "../../IRNodes/IRHoisted.js";
|
|
28
|
+
import { IRLetted } from "../../IRNodes/IRLetted.js";
|
|
29
|
+
import { IRRecursive } from "../../IRNodes/IRRecursive.js";
|
|
30
|
+
import { IRVar } from "../../IRNodes/IRVar.js";
|
|
31
|
+
import { _modifyChildFromTo } from "../_internal/_modifyChildFromTo.js";
|
|
32
|
+
export function inlineSingleUseLetBindingsAndReturnRoot(term) {
|
|
33
|
+
// Iterate until a full pass finds nothing to rewrite. Each rewrite
|
|
34
|
+
// restarts the walk from the (possibly new) root because the tree
|
|
35
|
+
// shape near the rewrite changes and may expose further candidates.
|
|
36
|
+
let didChange = true;
|
|
37
|
+
while (didChange) {
|
|
38
|
+
didChange = false;
|
|
39
|
+
const stack = [term];
|
|
40
|
+
outer: while (stack.length > 0) {
|
|
41
|
+
const t = stack.pop();
|
|
42
|
+
if (t instanceof IRApp
|
|
43
|
+
&& t.fn instanceof IRFunc
|
|
44
|
+
&& t.fn.params.length === 1) {
|
|
45
|
+
const fn = t.fn;
|
|
46
|
+
const p = fn.params[0];
|
|
47
|
+
const value = t.arg;
|
|
48
|
+
// Beta-reduce when the argument is itself just a
|
|
49
|
+
// variable access. Replacing every `IRVar(p)` with
|
|
50
|
+
// `IRVar(x)` costs the same per access (a single env
|
|
51
|
+
// lookup either way) and saves the surrounding
|
|
52
|
+
// `(λp. …) x` lambda + application. Always safe,
|
|
53
|
+
// regardless of how many times `p` is used and
|
|
54
|
+
// regardless of whether the uses sit inside a recursive
|
|
55
|
+
// body — there's no work to multiply.
|
|
56
|
+
if (value instanceof IRVar) {
|
|
57
|
+
const newBody = substituteAllVar(fn.body, p, value);
|
|
58
|
+
term = replaceWithBody(term, t, newBody);
|
|
59
|
+
didChange = true;
|
|
60
|
+
break outer;
|
|
61
|
+
}
|
|
62
|
+
const stats = countVarUses(fn.body, p);
|
|
63
|
+
if (stats.count === 0) {
|
|
64
|
+
// Dead let — replace with body, dropping the value.
|
|
65
|
+
term = replaceWithBody(term, t, fn.body);
|
|
66
|
+
didChange = true;
|
|
67
|
+
break outer;
|
|
68
|
+
}
|
|
69
|
+
if (stats.count === 1 && !stats.trapped) {
|
|
70
|
+
substituteVar(fn.body, p, value);
|
|
71
|
+
term = replaceWithBody(term, t, fn.body);
|
|
72
|
+
didChange = true;
|
|
73
|
+
break outer;
|
|
74
|
+
}
|
|
75
|
+
// Trapped single use: the lone occurrence sits inside a
|
|
76
|
+
// nested closure (recursive body or non-case lambda).
|
|
77
|
+
// Inlining a COMPUTATION there would duplicate the
|
|
78
|
+
// per-iteration work, but a syntactic VALUE (closure /
|
|
79
|
+
// constant / var / delay) only pays its (essentially
|
|
80
|
+
// zero) construction cost — the same as a fresh lookup.
|
|
81
|
+
// Pebble also stores recursive helpers as open-recursion
|
|
82
|
+
// lambdas whose body references their own binding name;
|
|
83
|
+
// inlining such a lambda would orphan that self-ref, so
|
|
84
|
+
// also require that the value has no free `IRVar(p)`.
|
|
85
|
+
if (stats.count === 1
|
|
86
|
+
&& stats.trapped
|
|
87
|
+
&& isSyntacticValue(value)
|
|
88
|
+
&& !containsFreeVar(value, p)) {
|
|
89
|
+
substituteVar(fn.body, p, value);
|
|
90
|
+
term = replaceWithBody(term, t, fn.body);
|
|
91
|
+
didChange = true;
|
|
92
|
+
break outer;
|
|
93
|
+
}
|
|
94
|
+
// count >= 2, or trapped-with-computational-value → leave alone
|
|
95
|
+
}
|
|
96
|
+
stack.push(...t.children());
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return term;
|
|
100
|
+
}
|
|
101
|
+
/** Returns true if `term` contains a free `IRVar(p)` reference. A
|
|
102
|
+
* binding inside `term` that re-uses `p`'s symbol shadows it (these
|
|
103
|
+
* are rare given fresh-symbol minting, but the check is cheap). */
|
|
104
|
+
function containsFreeVar(term, p) {
|
|
105
|
+
const stack = [term];
|
|
106
|
+
while (stack.length > 0) {
|
|
107
|
+
const t = stack.pop();
|
|
108
|
+
if (t instanceof IRVar && t.name === p)
|
|
109
|
+
return true;
|
|
110
|
+
if (t instanceof IRFunc && t.params.includes(p))
|
|
111
|
+
continue;
|
|
112
|
+
if (t instanceof IRRecursive && t.name === p)
|
|
113
|
+
continue;
|
|
114
|
+
stack.push(...t.children());
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A "syntactic value" is a term whose evaluation does no work beyond
|
|
120
|
+
* binding/closure construction — duplicating it across a recursive
|
|
121
|
+
* boundary doesn't multiply runtime cost. Specifically:
|
|
122
|
+
*
|
|
123
|
+
* - `IRFunc` / `IRRecursive` — closure values; per-construction is
|
|
124
|
+
* ~zero cost, and the body only runs when the closure is applied
|
|
125
|
+
* (which happens at the use-site frequency either way).
|
|
126
|
+
* - `IRConst` — literal value.
|
|
127
|
+
* - `IRVar` — already just an environment lookup.
|
|
128
|
+
* - `IRDelayed` — produces a thunk; the inner term only runs on
|
|
129
|
+
* force, at the use-site frequency.
|
|
130
|
+
*
|
|
131
|
+
* NOTE: `IRNative` is deliberately NOT included. While a bare native
|
|
132
|
+
* reference is itself cheap, in Pebble's pipeline `hoistForcedNatives`
|
|
133
|
+
* specifically wraps each forced builtin (e.g. `headList`,
|
|
134
|
+
* `tailList`, `ifThenElse`) once at the top via `(λvar. …) IRNative`,
|
|
135
|
+
* so that the runtime `force` happens once and is shared. Inlining
|
|
136
|
+
* the `IRNative` back into a recursive body undoes that sharing —
|
|
137
|
+
* the resulting compiled UPLC re-issues the force per iteration.
|
|
138
|
+
* `IRHoisted`/`IRLetted` are transparent wrappers: we unwrap to check
|
|
139
|
+
* the inner term.
|
|
140
|
+
*/
|
|
141
|
+
function isSyntacticValue(t) {
|
|
142
|
+
while (t instanceof IRHoisted)
|
|
143
|
+
t = t.hoisted;
|
|
144
|
+
while (t instanceof IRLetted)
|
|
145
|
+
t = t.value;
|
|
146
|
+
return (t instanceof IRFunc
|
|
147
|
+
|| t instanceof IRRecursive
|
|
148
|
+
|| t instanceof IRConst
|
|
149
|
+
|| t instanceof IRVar
|
|
150
|
+
|| t instanceof IRDelayed);
|
|
151
|
+
}
|
|
152
|
+
function replaceWithBody(root, app, body) {
|
|
153
|
+
const parent = app.parent;
|
|
154
|
+
if (parent === undefined) {
|
|
155
|
+
body.parent = undefined;
|
|
156
|
+
return body;
|
|
157
|
+
}
|
|
158
|
+
_modifyChildFromTo(parent, app, body);
|
|
159
|
+
return root;
|
|
160
|
+
}
|
|
161
|
+
/** Count IRVar(p) references in `body` accurately. Caps at 2 — we
|
|
162
|
+
* only care about 0/1/many — but ALWAYS walks the whole tree, so we
|
|
163
|
+
* correctly distinguish "1 trapped use" from "1 trapped + 1 more".
|
|
164
|
+
*/
|
|
165
|
+
function countVarUses(body, p) {
|
|
166
|
+
let count = 0;
|
|
167
|
+
let firstUseTrapped = false;
|
|
168
|
+
const stack = [{ node: body, trapped: false }];
|
|
169
|
+
while (stack.length > 0) {
|
|
170
|
+
const { node: t, trapped } = stack.pop();
|
|
171
|
+
if (t instanceof IRVar) {
|
|
172
|
+
if (t.name === p) {
|
|
173
|
+
if (count === 0)
|
|
174
|
+
firstUseTrapped = trapped;
|
|
175
|
+
count++;
|
|
176
|
+
if (count >= 2)
|
|
177
|
+
return { count: 2, trapped: false };
|
|
178
|
+
}
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
// Symbols are minted fresh on each binder, so shadowing is rare,
|
|
182
|
+
// but the guard is cheap and protects against any reuse.
|
|
183
|
+
if (t instanceof IRFunc && t.params.includes(p))
|
|
184
|
+
continue;
|
|
185
|
+
if (t instanceof IRRecursive && t.name === p)
|
|
186
|
+
continue;
|
|
187
|
+
// Determine the trap state for descending into this node:
|
|
188
|
+
// - IRRecursive always traps (loop introduces multi-eval).
|
|
189
|
+
// - IRFunc traps EXCEPT when it's a direct case continuation
|
|
190
|
+
// (`IRCase(_, […, this IRFunc, …])`): the case dispatches
|
|
191
|
+
// it at most once per case eval, so it's transparent.
|
|
192
|
+
let childTrapped = trapped;
|
|
193
|
+
if (t instanceof IRRecursive)
|
|
194
|
+
childTrapped = true;
|
|
195
|
+
else if (t instanceof IRFunc) {
|
|
196
|
+
const parent = t.parent;
|
|
197
|
+
const isCaseContinuation = parent instanceof IRCase
|
|
198
|
+
// direct continuation array membership
|
|
199
|
+
&& Array.from(parent.continuations).includes(t);
|
|
200
|
+
if (!isCaseContinuation)
|
|
201
|
+
childTrapped = true;
|
|
202
|
+
}
|
|
203
|
+
for (const c of t.children())
|
|
204
|
+
stack.push({ node: c, trapped: childTrapped });
|
|
205
|
+
}
|
|
206
|
+
return { count: count, trapped: firstUseTrapped };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Replace EVERY `IRVar(p)` inside `body` with a fresh clone of
|
|
210
|
+
* `replacement`. Used for the var-arg beta-reduction: substituting one
|
|
211
|
+
* variable name for another anywhere it appears.
|
|
212
|
+
*
|
|
213
|
+
* Returns the (possibly new) body. If `body` itself is `IRVar(p)`,
|
|
214
|
+
* returns a fresh clone of `replacement` — the caller is responsible
|
|
215
|
+
* for wiring it into the parent.
|
|
216
|
+
*/
|
|
217
|
+
function substituteAllVar(body, p, replacement) {
|
|
218
|
+
if (body instanceof IRVar && body.name === p) {
|
|
219
|
+
return replacement.clone();
|
|
220
|
+
}
|
|
221
|
+
const stack = [body];
|
|
222
|
+
while (stack.length > 0) {
|
|
223
|
+
const t = stack.pop();
|
|
224
|
+
// Don't descend into scopes that shadow p (unique-symbol
|
|
225
|
+
// invariant makes this rare, but the guard is cheap).
|
|
226
|
+
if (t instanceof IRFunc && t.params.includes(p))
|
|
227
|
+
continue;
|
|
228
|
+
if (t instanceof IRRecursive && t.name === p)
|
|
229
|
+
continue;
|
|
230
|
+
// Snapshot children before mutating (children() returns a fresh
|
|
231
|
+
// array, so the loop is stable across modifications).
|
|
232
|
+
const children = t.children();
|
|
233
|
+
for (const child of children) {
|
|
234
|
+
if (child instanceof IRVar && child.name === p) {
|
|
235
|
+
// `t` must be a parent term because we got `child` from
|
|
236
|
+
// `t.children()`. Cast to satisfy the type checker.
|
|
237
|
+
_modifyChildFromTo(t, child, replacement.clone());
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
stack.push(child);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return body;
|
|
245
|
+
}
|
|
246
|
+
/** Replace the (single) IRVar(p) inside `body` with `replacement`. */
|
|
247
|
+
function substituteVar(body, p, replacement) {
|
|
248
|
+
const stack = [body];
|
|
249
|
+
while (stack.length > 0) {
|
|
250
|
+
const t = stack.pop();
|
|
251
|
+
if (t instanceof IRVar && t.name === p) {
|
|
252
|
+
const parent = t.parent;
|
|
253
|
+
if (parent !== undefined)
|
|
254
|
+
_modifyChildFromTo(parent, t, replacement);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (t instanceof IRFunc && t.params.includes(p))
|
|
258
|
+
continue;
|
|
259
|
+
if (t instanceof IRRecursive && t.name === p)
|
|
260
|
+
continue;
|
|
261
|
+
stack.push(...t.children());
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When the same list `L` is used for BOTH `headList(L)` and `tailList(L)`
|
|
3
|
+
* within a single function body, those two builtin calls can be replaced
|
|
4
|
+
* by a single `case L of cons h t -> body[h/headList(L), t/tailList(L)] |
|
|
5
|
+
* nil -> error`. The trade-off (measured in `bench.headTailVsCase`):
|
|
6
|
+
*
|
|
7
|
+
* - one `headList` (or `tailList`) builtin call: 112,100 CPU / 800 mem
|
|
8
|
+
* - one `case L of cons h t -> h | nil -> error`: 128,100 CPU / 900 mem
|
|
9
|
+
*
|
|
10
|
+
* So for a single access the builtin wins; for dual access the case wins
|
|
11
|
+
* by a single dispatch (~96K CPU / 700 mem).
|
|
12
|
+
*
|
|
13
|
+
* This pass walks every `IRFunc` body bottom-up. For each body, it scans
|
|
14
|
+
* the top-level (without descending into nested `IRFunc`/`IRRecursive`,
|
|
15
|
+
* because nested scopes are processed independently). If a free list var
|
|
16
|
+
* `L` has both a `headList(L)` and a `tailList(L)` use in the body, the
|
|
17
|
+
* body is wrapped with `IRCase(IRVar(L), [IRFunc([h, t], body'), IRError])`
|
|
18
|
+
* — where `body'` has those calls replaced with `IRVar(h)` / `IRVar(t)`.
|
|
19
|
+
*
|
|
20
|
+
* Notes:
|
|
21
|
+
* - We only act on `IRFunc` bodies that are NOT immediate continuations
|
|
22
|
+
* of an `IRCase` whose scrutinee is `IRVar(L)` for the same L — that
|
|
23
|
+
* case-cons branch already binds head/tail and the prior
|
|
24
|
+
* `rewriteHeadTailInCaseConsAndReturnRoot` pass has already done the
|
|
25
|
+
* substitution. Wrapping again would be a no-op constructor pair.
|
|
26
|
+
* - The nil branch is `IRError`. Original code that calls `headList` or
|
|
27
|
+
* `tailList` on a nil list errors at evaluation; the new code errors
|
|
28
|
+
* at the case dispatch — same observable behavior whenever either
|
|
29
|
+
* call is actually reached.
|
|
30
|
+
* - The pass iterates: after introducing a case for L, the (now
|
|
31
|
+
* substituted) body might still contain a different L' with dual
|
|
32
|
+
* head/tail uses — handled by re-scanning until no more pairs.
|
|
33
|
+
*/
|
|
34
|
+
import { IRTerm } from "../../IRTerm.js";
|
|
35
|
+
export declare function introduceCaseForDualHeadTailAndReturnRoot(term: IRTerm): IRTerm;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When the same list `L` is used for BOTH `headList(L)` and `tailList(L)`
|
|
3
|
+
* within a single function body, those two builtin calls can be replaced
|
|
4
|
+
* by a single `case L of cons h t -> body[h/headList(L), t/tailList(L)] |
|
|
5
|
+
* nil -> error`. The trade-off (measured in `bench.headTailVsCase`):
|
|
6
|
+
*
|
|
7
|
+
* - one `headList` (or `tailList`) builtin call: 112,100 CPU / 800 mem
|
|
8
|
+
* - one `case L of cons h t -> h | nil -> error`: 128,100 CPU / 900 mem
|
|
9
|
+
*
|
|
10
|
+
* So for a single access the builtin wins; for dual access the case wins
|
|
11
|
+
* by a single dispatch (~96K CPU / 700 mem).
|
|
12
|
+
*
|
|
13
|
+
* This pass walks every `IRFunc` body bottom-up. For each body, it scans
|
|
14
|
+
* the top-level (without descending into nested `IRFunc`/`IRRecursive`,
|
|
15
|
+
* because nested scopes are processed independently). If a free list var
|
|
16
|
+
* `L` has both a `headList(L)` and a `tailList(L)` use in the body, the
|
|
17
|
+
* body is wrapped with `IRCase(IRVar(L), [IRFunc([h, t], body'), IRError])`
|
|
18
|
+
* — where `body'` has those calls replaced with `IRVar(h)` / `IRVar(t)`.
|
|
19
|
+
*
|
|
20
|
+
* Notes:
|
|
21
|
+
* - We only act on `IRFunc` bodies that are NOT immediate continuations
|
|
22
|
+
* of an `IRCase` whose scrutinee is `IRVar(L)` for the same L — that
|
|
23
|
+
* case-cons branch already binds head/tail and the prior
|
|
24
|
+
* `rewriteHeadTailInCaseConsAndReturnRoot` pass has already done the
|
|
25
|
+
* substitution. Wrapping again would be a no-op constructor pair.
|
|
26
|
+
* - The nil branch is `IRError`. Original code that calls `headList` or
|
|
27
|
+
* `tailList` on a nil list errors at evaluation; the new code errors
|
|
28
|
+
* at the case dispatch — same observable behavior whenever either
|
|
29
|
+
* call is actually reached.
|
|
30
|
+
* - The pass iterates: after introducing a case for L, the (now
|
|
31
|
+
* substituted) body might still contain a different L' with dual
|
|
32
|
+
* head/tail uses — handled by re-scanning until no more pairs.
|
|
33
|
+
*/
|
|
34
|
+
import { IRApp } from "../../IRNodes/IRApp.js";
|
|
35
|
+
import { IRCase } from "../../IRNodes/IRCase.js";
|
|
36
|
+
import { IRError } from "../../IRNodes/IRError.js";
|
|
37
|
+
import { IRFunc } from "../../IRNodes/IRFunc.js";
|
|
38
|
+
import { IRHoisted } from "../../IRNodes/IRHoisted.js";
|
|
39
|
+
import { IRLetted } from "../../IRNodes/IRLetted.js";
|
|
40
|
+
import { IRNative } from "../../IRNodes/IRNative/index.js";
|
|
41
|
+
import { IRNativeTag } from "../../IRNodes/IRNative/IRNativeTag.js";
|
|
42
|
+
import { IRRecursive } from "../../IRNodes/IRRecursive.js";
|
|
43
|
+
import { IRVar } from "../../IRNodes/IRVar.js";
|
|
44
|
+
import { _modifyChildFromTo } from "../_internal/_modifyChildFromTo.js";
|
|
45
|
+
function unwrap(t) {
|
|
46
|
+
while (t instanceof IRHoisted)
|
|
47
|
+
t = t.hoisted;
|
|
48
|
+
while (t instanceof IRLetted)
|
|
49
|
+
t = t.value;
|
|
50
|
+
return t;
|
|
51
|
+
}
|
|
52
|
+
export function introduceCaseForDualHeadTailAndReturnRoot(term) {
|
|
53
|
+
processNode(term);
|
|
54
|
+
return term;
|
|
55
|
+
}
|
|
56
|
+
function processNode(node) {
|
|
57
|
+
// Post-order: recurse into children first, then process this node.
|
|
58
|
+
for (const child of node.children())
|
|
59
|
+
processNode(child);
|
|
60
|
+
if (node instanceof IRFunc) {
|
|
61
|
+
// Avoid wrapping case-cons branches that the previous pass already
|
|
62
|
+
// optimized: if `node` is the cons continuation of an outer
|
|
63
|
+
// `IRCase(IRVar(L), [node, ...])`, then head(L)/tail(L) were
|
|
64
|
+
// already substituted; if any dual remains it's for a *different*
|
|
65
|
+
// list L', which we handle normally.
|
|
66
|
+
tryWrap(node);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function tryWrap(fn) {
|
|
70
|
+
while (true) {
|
|
71
|
+
const choice = findFirstDualHeadTailList(fn.body, new Set(fn.params));
|
|
72
|
+
if (choice === undefined)
|
|
73
|
+
return;
|
|
74
|
+
const listSym = choice;
|
|
75
|
+
const desc = listSym.description ?? "L";
|
|
76
|
+
const hSym = Symbol("h_" + desc);
|
|
77
|
+
const tSym = Symbol("t_" + desc);
|
|
78
|
+
// Substitute head(L)/tail(L) inside the current body (won't descend
|
|
79
|
+
// into nested scopes that re-bind L, h, or t — but we just minted
|
|
80
|
+
// h/t so they don't collide with anything).
|
|
81
|
+
substituteHeadTailInBody(fn.body, listSym, hSym, tSym);
|
|
82
|
+
// Wrap.
|
|
83
|
+
const oldBody = fn.body;
|
|
84
|
+
const consFn = new IRFunc([hSym, tSym], oldBody);
|
|
85
|
+
const wrapped = new IRCase(new IRVar(listSym), [consFn, new IRError()]);
|
|
86
|
+
fn.body = wrapped;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Scan `root` for free `headList(L)`/`tailList(L)` uses where `L` is NOT
|
|
91
|
+
* shadowed by an enclosing nested IRFunc/IRRecursive within `root`. Returns
|
|
92
|
+
* the first list symbol with at least one head AND one tail use.
|
|
93
|
+
*/
|
|
94
|
+
function findFirstDualHeadTailList(root, boundInOuterFunc) {
|
|
95
|
+
const heads = new Set();
|
|
96
|
+
const tails = new Set();
|
|
97
|
+
function walk(node, locallyBound) {
|
|
98
|
+
if (node instanceof IRFunc) {
|
|
99
|
+
const next = new Set(locallyBound);
|
|
100
|
+
for (const p of node.params)
|
|
101
|
+
next.add(p);
|
|
102
|
+
walk(node.body, next);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (node instanceof IRRecursive) {
|
|
106
|
+
const next = new Set(locallyBound);
|
|
107
|
+
next.add(node.name);
|
|
108
|
+
walk(node.body, next);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (node instanceof IRApp) {
|
|
112
|
+
const fn = unwrap(node.fn);
|
|
113
|
+
const arg = node.arg;
|
|
114
|
+
if (fn instanceof IRNative
|
|
115
|
+
&& arg instanceof IRVar
|
|
116
|
+
&& !locallyBound.has(arg.name)
|
|
117
|
+
&& (fn.tag === IRNativeTag.headList
|
|
118
|
+
|| fn.tag === IRNativeTag.tailList)) {
|
|
119
|
+
(fn.tag === IRNativeTag.headList ? heads : tails).add(arg.name);
|
|
120
|
+
// do not descend further — this is a leaf for our purposes
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// generic descent
|
|
124
|
+
}
|
|
125
|
+
for (const c of node.children())
|
|
126
|
+
walk(c, locallyBound);
|
|
127
|
+
}
|
|
128
|
+
walk(root, boundInOuterFunc);
|
|
129
|
+
for (const sym of heads) {
|
|
130
|
+
if (tails.has(sym))
|
|
131
|
+
return sym;
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* In-place: replace every `IRApp(unwrap=headList, IRVar(L))` /
|
|
137
|
+
* `IRApp(unwrap=tailList, IRVar(L))` inside `root` with `IRVar(h)` /
|
|
138
|
+
* `IRVar(t)`. Stops descending into any nested IRFunc/IRRecursive that
|
|
139
|
+
* shadows `L`, `h`, or `t`.
|
|
140
|
+
*/
|
|
141
|
+
function substituteHeadTailInBody(root, listSym, hSym, tSym) {
|
|
142
|
+
const stack = [root];
|
|
143
|
+
while (stack.length > 0) {
|
|
144
|
+
const t = stack.pop();
|
|
145
|
+
if (t instanceof IRFunc || t instanceof IRRecursive) {
|
|
146
|
+
const params = t instanceof IRFunc ? t.params : [t.name];
|
|
147
|
+
if (params.includes(listSym)
|
|
148
|
+
|| params.includes(hSym)
|
|
149
|
+
|| params.includes(tSym))
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (t instanceof IRApp) {
|
|
153
|
+
const fn = unwrap(t.fn);
|
|
154
|
+
const arg = t.arg;
|
|
155
|
+
if (fn instanceof IRNative
|
|
156
|
+
&& arg instanceof IRVar
|
|
157
|
+
&& arg.name === listSym
|
|
158
|
+
&& (fn.tag === IRNativeTag.headList || fn.tag === IRNativeTag.tailList)) {
|
|
159
|
+
const newSym = fn.tag === IRNativeTag.headList ? hSym : tSym;
|
|
160
|
+
const parent = t.parent;
|
|
161
|
+
if (parent !== undefined) {
|
|
162
|
+
_modifyChildFromTo(parent, t, new IRVar(newSym));
|
|
163
|
+
}
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
stack.push(...t.children());
|
|
168
|
+
}
|
|
169
|
+
}
|