[rust] Recurse into functions in other passes
Updates eliminate_redundant_phis and constant_propagation to recurse into function expressions. I also realized there was a bug in EliminateRedundantPhis in which we wouldn't traverse into function expressions encountered after finding a back edge, so i fixed that logic in both versions.
Joe Savona committed
Jul 13, 2023 at 13:34 UTC
f3d40c8c248f9369dcdfdac78a12188db7474a59
5 files changed
+90
-18
compiler/forget/crates/fixtures/tests/fixtures/function-expressions.js
+9
-1
@@ -1,5 +1,13 @@
1
function Component(props) {
2
+ const y = 2;
3
const foo = function foo(x) {
3
- return x + 1;
4
+ let a = 1;
5
+ let b;
6
+ if (a === 1) {
7
+ b = 5 + 3;
8
+ } else {
9
+ b = false;
10
+ }
11
+ return x + y + b;
12
};
13
}
compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@function-expressions.js.snap
+39
-13
@@ -5,30 +5,56 @@ input_file: crates/fixtures/tests/fixtures/function-expressions.js
5
---
6
Input:
7
function Component(props) {
8
+ const y = 2;
9
const foo = function foo(x) {
9
- return x + 1;
10
+ let a = 1;
11
+ let b;
12
+ if (a === 1) {
13
+ b = 5 + 3;
14
+ } else {
15
+ b = false;
16
+ }
17
+ return x + y + b;
18
};
19
}
20
21
22
Output:
23
function Component(
16
- unknown props$3,
24
+ unknown props$6,
25
)
26
entry bb0
27
bb0 (block)
20
- [0] #0 = Function @deps[] @context[]:
28
+ [0] #0 = 2
29
+ [1] #1 = StoreLocal Const unknown y$7 = unknown #0
30
+ [2] #2 = Function @deps[] @context[]:
31
function foo(
22
- unknown x$0,
32
+ unknown x$8,
33
)
34
entry bb1
35
bb1 (block)
26
- [0] #0 = LoadLocal unknown x$0
27
- [1] #1 = 1
28
- [2] #2 = Binary unknown #0 + unknown #1
29
- [3] Return unknown #2
30
- [1] #1 = StoreLocal Const unknown foo$4 = unknown #0
31
- [2] #2 = <undefined>
32
- [3] Return unknown #2
33
-
34
-
36
+ [0] #0 = 1
37
+ [1] #1 = StoreLocal Let unknown a$9 = unknown #0
38
+ [2] #2 = DeclareLocal Let unknown b$10
39
+ [3] #9 = 1
40
+ [4] #10 = 1
41
+ [5] #11 = true
42
+ [6] Goto bb3
43
+ bb3 (block)
44
+ predecessors: bb1
45
+ [7] #3 = 5
46
+ [8] #4 = 3
47
+ [9] #5 = 8
48
+ [10] #6 = StoreLocal Reassign unknown b$11 = unknown #5
49
+ [11] Goto bb2
50
+ bb2 (block)
51
+ predecessors: bb3
52
+ [12] #12 = LoadLocal unknown x$8
53
+ [13] #13 = LoadLocal unknown y$0
54
+ [14] #14 = Binary unknown #12 + unknown #13
55
+ [15] #15 = 8
56
+ [16] #16 = Binary unknown #14 + unknown #15
57
+ [17] Return unknown #16
58
+ [3] #3 = StoreLocal Const unknown foo$16 = unknown #2
59
+ [4] #4 = <undefined>
60
+ [5] Return unknown #4
compiler/forget/crates/hir-optimization/src/constant_propagation.rs
+30
-1
@@ -10,8 +10,16 @@ use hir_ssa::eliminate_redundant_phis;
10
11
pub fn constant_propagation<'a>(env: &Environment<'a>, fun: &mut Function<'a>) {
12
let mut constants = Constants::new();
13
+ constant_propagation_impl(env, fun, &mut constants);
14
+}
15
+
16
+fn constant_propagation_impl<'a>(
17
+ env: &Environment<'a>,
18
+ fun: &mut Function<'a>,
19
+ constants: &mut Constants<'a>,
20
+) {
21
loop {
14
- let have_terminals_changed = apply_constant_propagation(env, fun, &mut constants);
22
+ let have_terminals_changed = apply_constant_propagation(env, fun, constants);
23
if !have_terminals_changed {
24
break;
25
}
@@ -162,6 +170,27 @@ fn evaluate_instruction<'a>(
170
constants.insert(value.lvalue.identifier.identifier.id, const_value);
171
}
172
}
173
+ InstructionValue::Function(value) => {
174
+ // TODO: due to the outer fixpoint iteration this could visit the same
175
+ // function many times. However we only strictly have to visit the function
176
+ // again if the context variable's constant values have changed since last
177
+ // time.
178
+ // Instead, we can:
179
+ // - Create a filtered Constants instance that extracts just the values for
180
+ // the function (using its context variables list)
181
+ // - Track the last such filtered Constants instance we visited the function
182
+ // with. Only visit again if the Constants have changed.
183
+ let mut inner_constants: Constants<'a> = value
184
+ .lowered_function
185
+ .context
186
+ .iter()
187
+ .filter_map(|id| {
188
+ let value = constants.get(&id.identifier.id);
189
+ value.map(|value| (id.identifier.id, value.clone()))
190
+ })
191
+ .collect();
192
+ constant_propagation_impl(env, &mut value.lowered_function, &mut inner_constants);
193
+ }
194
_ => {
195
// no-op, not all instructions can be processed
196
}
compiler/forget/crates/hir-ssa/src/eliminate_redundant_phis.rs
+10
-2
@@ -1,6 +1,6 @@
1
use std::collections::{HashMap, HashSet};
2
3
-use hir::{BlockId, Environment, Function, Identifier, IdentifierId, HIR};
3
+use hir::{BlockId, Environment, Function, Identifier, IdentifierId, InstructionValue, HIR};
4
use utils::RetainMut;
5
6
/// Pass to eliminate redundant phi nodes:
@@ -16,7 +16,7 @@ use utils::RetainMut;
16
/// and phis rewrite all their identifiers based on this table. The algorithm loops over the CFG repeatedly
17
/// until there are no new rewrites: for a CFG without back-edges it completes in a single pass.
18
type Rewrites<'a> = HashMap<IdentifierId, Identifier<'a>>;
19
-pub fn eliminate_redundant_phis<'a>(_env: &Environment, fun: &mut Function<'a>) {
19
+pub fn eliminate_redundant_phis<'a>(env: &Environment, fun: &mut Function<'a>) {
20
let hir = &mut fun.body;
21
let mut rewrites = Rewrites::new();
22
@@ -25,6 +25,7 @@ pub fn eliminate_redundant_phis<'a>(_env: &Environment, fun: &mut Function<'a>)
25
26
let mut len;
27
loop {
28
+ let is_first_iteration = !has_back_edge;
29
len = rewrites.len();
30
31
for (_, block) in hir.blocks.iter_mut() {
@@ -75,6 +76,13 @@ pub fn eliminate_redundant_phis<'a>(_env: &Environment, fun: &mut Function<'a>)
76
rewrite(&rewrites, &mut store.identifier.identifier)
77
});
78
instr.each_identifier_load(|load| rewrite(&rewrites, &mut load.identifier));
79
+ // Visit function expressions on first iteration of each block to
80
+ // recursively eliminate any of their redundant phis
81
+ if is_first_iteration {
82
+ if let InstructionValue::Function(fun) = &mut instr.value {
83
+ eliminate_redundant_phis(env, &mut fun.lowered_function);
84
+ }
85
+ }
86
}
87
}
88
compiler/forget/packages/babel-plugin-react-forget/src/SSA/EliminateRedundantPhi.ts
+2
-1
@@ -42,6 +42,7 @@ export function eliminateRedundantPhi(fn: HIRFunction): void {
42
// compare to see if any new rewrites were added in that iteration.
43
let size = rewrites.size;
44
do {
45
+ const isFirstIteration = !hasBackEdge;
46
size = rewrites.size;
47
for (const [blockId, block] of ir.blocks) {
48
// On the first iteration of the loop check for any back-edges.
@@ -105,7 +106,7 @@ export function eliminateRedundantPhi(fn: HIRFunction): void {
106
107
// visit function expressions on first iteration of each block
108
if (
108
- !hasBackEdge &&
109
+ isFirstIteration &&
110
instr.value.kind === "FunctionExpression" &&
111
fn.env.enableOptimizeFunctionExpressions
112
) {