@samitouri / QOS-React-2 / commits / c7ccc5dd30

[rust] Initial port of EnterSSA

This is a first pass at porting EnterSSA to Rust. First pass in the sense that it's hard to fully test it, and also in the sense that we'll likely figure out even better ways to work w the HIR as we iterate. Oh and i didn't do recusing into function expressions yet, since we can't even represent function expressions yet, and that may require modifying the design a bit (though i have an idea that i think will work, which is for the Builder to have an optional parent. When we encounter a block with no predecessors, we check the parent. I _think_ this will make all the borrowing "just work"). A few notes: Rust's compilation model parallelizes and incrementally computes at the crate granularity, so builds are faster if we split up our code into more but smaller crates. Setting up a clean dependency graph can dramatically improve build performance too. For example, to run the `fixtures` tests we can build `estree` and `hir` in parallel, then once those build _all_ of our other crates can be built in parallel until we get to `fixtures` which depends on the everything else. The various passes don't have any build dependencies on each other so they can build in parallel. Hence the new code for SSA stuff is in a separate `hir-ssa` crate. We should similarly group other passes (approximately one crate per folder in the babel-plugin-react-forget/src/ directory, eg SSA, Inference, Optimization, etc). Second, shared mutable ownership can be modeled in Rust but requires wrappers such as `Rc<RefCell<>>`. It's generally more efficient and more idiomatic to rethink the data model and algorithm. For EnterSSA, the Builder object holds a reference into the HIR that it only ever reads, and the pass (which drives the builder) also holds a reference into the HIR, which it mutates. The previous PR split up Blocks and Instructions, and the value of that is more apparent in `enter_ssa()`. The Rust equivalent of the builder holds a _shared_ (immutable) reference to just the HIR's blocks, while the pass (driving the builder) holds a _unique_ (mutable) reference to just the HIR's instructions. This lets us keep the overall feel of the algorithm while keeping Rust happy. Also note that the other change — to making operands be InstrIx indices into the instructions array — means that the SSA logic is simpler. Most instructions don't have to be visited at all, since they don't deal with loads/stores. Terminals also don't need to be visited, since they reference instructions, not identifiers. The Phi concept seems to just work too. I also updated the printer to print predecessors and phis.

Joe Savona committed Jul 12, 2023 at 12:38 UTC c7ccc5dd30acfb29c0e95e42ade5247514a2f270
17 files changed +543 -74
compiler/forget/crates/build-hir/src/build.rs
+30 -10
@@ -6,8 +6,8 @@ use estree::{
6 };
7 use hir::{
8 ArrayElement, BlockKind, BranchTerminal, Environment, ForTerminal, Function, GotoKind,
9 - Identifier, IdentifierOperand, InstrIx, InstructionKind, InstructionValue, LValue, LoadGlobal,
10 - LoadLocal, Operand, PrimitiveValue, TerminalValue,
9 + IdentifierOperand, InstrIx, InstructionKind, InstructionValue, LValue, LoadGlobal, LoadLocal,
10 + Operand, PrimitiveValue, TerminalValue,
11 };
12
13 use crate::{
@@ -23,17 +23,17 @@ use crate::{
23 /// Failures generally include nonsensical input (`delete 1`) or syntax
24 /// that is not yet supported.
25 pub fn build<'a>(
26 - environment: &'a Environment<'a>,
26 + env: &'a Environment<'a>,
27 fun: FunctionDeclaration,
28 -) -> Result<Function<'a>, BuildDiagnostic> {
29 - let mut builder = Builder::new(environment);
28 +) -> Result<&'a mut Function<'a>, BuildDiagnostic> {
29 + let mut builder = Builder::new(env);
30
31 match fun.function.body {
32 Some(estree::FunctionBody::BlockStatement(body)) => {
33 - lower_block_statement(environment, &mut builder, *body, None)?
33 + lower_block_statement(env, &mut builder, *body, None)?
34 }
35 Some(estree::FunctionBody::Expression(body)) => {
36 - lower_expression(environment, &mut builder, body)?;
36 + lower_expression(env, &mut builder, body)?;
37 }
38 None => {
39 return Err(BuildDiagnostic::new(
@@ -44,11 +44,26 @@ pub fn build<'a>(
44 }
45 }
46
47 + let mut params = Vec::with_capacity_in(fun.function.params.len(), &env.allocator);
48 + for param in fun.function.params {
49 + match param {
50 + Pattern::Identifier(param) => {
51 + let identifier = lower_identifier_for_assignment(
52 + env,
53 + &mut builder,
54 + InstructionKind::Let,
55 + *param,
56 + )?;
57 + params.push(identifier);
58 + }
59 + }
60 + }
61 +
62 // In case the function did not explicitly return, terminate the final
63 // block with an explicit `return undefined`. If the function *did* return,
64 // this will be unreachable and get pruned later.
65 let implicit_return_value = lower_value_to_temporary(
51 - environment,
66 + env,
67 &mut builder,
68 InstructionValue::Primitive(hir::Primitive {
69 value: PrimitiveValue::Undefined,
@@ -65,11 +80,16 @@ pub fn build<'a>(
80 );
81
82 let body = builder.build()?;
68 - Ok(Function {
83 + Ok(env.alloc(Function {
84 + id: fun
85 + .function
86 + .id
87 + .map(|id| String::from_str_in(&id.name, &env.allocator)),
88 body,
89 + params,
90 is_async: fun.function.is_async,
91 is_generator: fun.function.is_generator,
72 - })
92 + }))
93 }
94
95 fn lower_block_statement<'a>(
compiler/forget/crates/build-hir/src/builder.rs
+2
@@ -171,6 +171,7 @@ impl<'a> Builder<'a> {
171 value: terminal,
172 },
173 predecessors: Default::default(),
174 + phis: Vec::new_in(&self.environment.allocator),
175 },
176 &self.environment.allocator,
177 ),
@@ -226,6 +227,7 @@ impl<'a> Builder<'a> {
227 value: terminal,
228 },
229 predecessors: Default::default(),
230 + phis: Vec::new_in(&self.environment.allocator),
231 },
232 &self.environment.allocator,
233 ),
compiler/forget/crates/fixtures/Cargo.toml
+1
@@ -11,6 +11,7 @@ insta = "1.30.0"
11 estree = { path = "../estree" }
12 estree-swc = { path = "../estree-swc" }
13 hir = { path = "../hir" }
14 +hir-ssa = { path = "../hir-ssa" }
15 build-hir = { path = "../build-hir" }
16 bumpalo = { version = "3.13.0", features = ["collections"] }
17 miette = { version = "5.9.0", features = ["backtrace", "fancy"] }
compiler/forget/crates/fixtures/tests/fixtures/ssa-reassign-if.js new
+16
@@ -0,0 +1,16 @@
1 +function Component(a, b) {
2 + let x;
3 + let y = 0;
4 + let z = 10;
5 + if (a) {
6 + x = 1;
7 + if (b) {
8 + z = 20;
9 + } else {
10 + z = 30;
11 + }
12 + } else {
13 + x = 2;
14 + }
15 + return x + y + z;
16 +}
compiler/forget/crates/fixtures/tests/fixtures_test.rs
+4 -2
@@ -5,6 +5,7 @@ use bumpalo::Bump;
5 use estree::{ModuleItem, Statement};
6 use estree_swc::parse;
7 use hir::{Environment, Print, Registry};
8 +use hir_ssa::enter_ssa;
9 use insta::{assert_snapshot, glob};
10 use miette::{NamedSource, Report};
11
@@ -31,8 +32,9 @@ fn fixtures() {
32 output.push_str("\n\n");
33 }
34 match build(&environment, *fun) {
34 - Ok(hir) => {
35 - hir.print(&hir.body, &mut output).unwrap();
35 + Ok(mut fun) => {
36 + enter_ssa(&environment, &mut fun).unwrap();
37 + fun.print(&fun.body, &mut output).unwrap();
38 }
39 Err(error) => {
40 write!(&mut output, "{}", error,).unwrap();
compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@for-statement.js.snap
+24 -15
@@ -14,33 +14,42 @@ function foo() {
14
15
16 Output:
17 +function foo(
18 +)
19 entry bb0
18 -bb0
20 +bb0 (block)
21 [0] #0 = 0
20 - [1] #1 = StoreLocal Let unknown x$0 = unknown #0
22 + [1] #1 = StoreLocal Let unknown x$2 = unknown #0
23 [2] For init=bb3 test=bb1 update=bb4 body=bb5 fallthrough=bb2
22 -bb3
24 +bb3 (loop)
25 + predecessors: bb0
26 [3] #2 = 0
24 - [4] #3 = StoreLocal Let unknown i$1 = unknown #2
27 + [4] #3 = StoreLocal Let unknown i$3 = unknown #2
28 [5] Goto bb1
26 -bb1
27 - [6] #12 = LoadLocal unknown i$1
29 +bb1 (loop)
30 + predecessors: bb3, bb4
31 + i$4: phi(bb3: i$3, bb4: i$7)
32 + x$5: phi(bb3: x$2, bb4: x$6)
33 + [6] #12 = LoadLocal unknown i$4
34 [7] #13 = 10
35 [8] #14 = Binary unknown #12 < unknown #13
36 [9] Branch unknown #14 consequent=bb5 alternate=bb2
31 -bb5
32 - [10] #8 = LoadLocal unknown x$0
33 - [11] #9 = LoadLocal unknown i$1
37 +bb5 (block)
38 + predecessors: bb1
39 + [10] #8 = LoadLocal unknown x$5
40 + [11] #9 = LoadLocal unknown i$4
41 [12] #10 = Binary unknown #8 + unknown #9
35 - [13] #11 = StoreLocal Reassign unknown x$0 = unknown #10
42 + [13] #11 = StoreLocal Reassign unknown x$6 = unknown #10
43 [14] Goto bb4
37 -bb4
38 - [15] #4 = LoadLocal unknown i$1
44 +bb4 (loop)
45 + predecessors: bb5
46 + [15] #4 = LoadLocal unknown i$4
47 [16] #5 = 1
48 [17] #6 = Binary unknown #4 + unknown #5
41 - [18] #7 = StoreLocal Reassign unknown i$1 = unknown #6
49 + [18] #7 = StoreLocal Reassign unknown i$7 = unknown #6
50 [19] Goto bb1
43 -bb2
44 - [20] #15 = LoadLocal unknown x$0
51 +bb2 (block)
52 + predecessors: bb1
53 + [20] #15 = LoadLocal unknown x$5
54 [21] Return unknown #15
55
compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@identifiers.js.snap
+10 -7
@@ -23,17 +23,20 @@ function id(x) {
23
24
25 Output:
26 +function id(
27 + unknown x$4,
28 +)
29 entry bb0
27 -bb0
30 +bb0 (block)
31 [0] #0 = LoadGlobal Math
32 [1] #1 = LoadGlobal id
33 [2] #2 = true
31 - [3] #3 = StoreLocal Let unknown y$1 = unknown #2
34 + [3] #3 = StoreLocal Let unknown y$5 = unknown #2
35 [4] #4 = false
33 - [5] #5 = StoreLocal Reassign unknown y$1 = unknown #4
34 - [6] #6 = LoadLocal unknown y$1
35 - [7] #7 = DeclareLocal Let unknown z$2
36 - [8] #8 = LoadLocal unknown z$2
37 - [9] #9 = LoadLocal unknown x$3
36 + [5] #5 = StoreLocal Reassign unknown y$6 = unknown #4
37 + [6] #6 = LoadLocal unknown y$6
38 + [7] #7 = DeclareLocal Let unknown z$7
39 + [8] #8 = LoadLocal unknown z$7
40 + [9] #9 = LoadLocal unknown x$4
41 [10] Return unknown #9
42
compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@if-statement.js.snap
+17 -8
@@ -15,18 +15,27 @@ function foo(a, b, c, d) {
15
16
17 Output:
18 +function foo(
19 + unknown a$4,
20 + unknown b$5,
21 + unknown c$6,
22 + unknown d$7,
23 +)
24 entry bb0
19 -bb0
20 - [0] #2 = LoadLocal unknown a$2
25 +bb0 (block)
26 + [0] #2 = LoadLocal unknown a$4
27 [1] If unknown #2 consequent=bb2 alternate=bb4 fallthrough=bb1
22 -bb2
23 - [2] #0 = LoadLocal unknown b$0
28 +bb2 (block)
29 + predecessors: bb0
30 + [2] #0 = LoadLocal unknown b$5
31 [3] Return unknown #0
25 -bb4
26 - [4] #1 = LoadLocal unknown c$1
32 +bb4 (block)
33 + predecessors: bb0
34 + [4] #1 = LoadLocal unknown c$6
35 [5] Goto bb1
28 -bb1
29 - [6] #3 = LoadLocal unknown d$3
36 +bb1 (block)
37 + predecessors: bb4
38 + [6] #3 = LoadLocal unknown d$7
39 [7] #4 = <undefined>
40 [8] Return unknown #4
41
compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@simple.js.snap
+3 -1
@@ -11,8 +11,10 @@ function test() {
11
12
13 Output:
14 +function test(
15 +)
16 entry bb0
15 -bb0
17 +bb0 (block)
18 [0] #0 = true
19 [1] #1 = false
20 [2] #2 = null
compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@ssa-reassign-if.js.snap new
+77
@@ -0,0 +1,77 @@
1 +---
2 +source: crates/fixtures/tests/fixtures_test.rs
3 +expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")"
4 +input_file: crates/fixtures/tests/fixtures/ssa-reassign-if.js
5 +---
6 +Input:
7 +function Component(a, b) {
8 + let x;
9 + let y = 0;
10 + let z = 10;
11 + if (a) {
12 + x = 1;
13 + if (b) {
14 + z = 20;
15 + } else {
16 + z = 30;
17 + }
18 + } else {
19 + x = 2;
20 + }
21 + return x + y + z;
22 +}
23 +
24 +
25 +Output:
26 +function Component(
27 + unknown a$5,
28 + unknown b$6,
29 +)
30 +entry bb0
31 +bb0 (block)
32 + [0] #0 = DeclareLocal Let unknown x$7
33 + [1] #1 = 0
34 + [2] #2 = StoreLocal Let unknown y$8 = unknown #1
35 + [3] #3 = 10
36 + [4] #4 = StoreLocal Let unknown z$9 = unknown #3
37 + [5] #14 = LoadLocal unknown a$5
38 + [6] If unknown #14 consequent=bb2 alternate=bb6 fallthrough=bb1
39 +bb2 (block)
40 + predecessors: bb0
41 + [7] #5 = 1
42 + [8] #6 = StoreLocal Reassign unknown x$10 = unknown #5
43 + [9] #11 = LoadLocal unknown b$6
44 + [10] If unknown #11 consequent=bb4 alternate=bb5 fallthrough=bb3
45 +bb4 (block)
46 + predecessors: bb2
47 + [11] #7 = 20
48 + [12] #8 = StoreLocal Reassign unknown z$11 = unknown #7
49 + [13] Goto bb3
50 +bb5 (block)
51 + predecessors: bb2
52 + [14] #9 = 30
53 + [15] #10 = StoreLocal Reassign unknown z$12 = unknown #9
54 + [16] Goto bb3
55 +bb3 (block)
56 + predecessors: bb4, bb5
57 + x$15: phi(bb4: x$10, bb5: x$10)
58 + y$17: phi(bb4: y$8, bb5: y$8)
59 + z$19: phi(bb4: z$11, bb5: z$12)
60 + [17] Goto bb1
61 +bb6 (block)
62 + predecessors: bb0
63 + [18] #12 = 2
64 + [19] #13 = StoreLocal Reassign unknown x$13 = unknown #12
65 + [20] Goto bb1
66 +bb1 (block)
67 + predecessors: bb3, bb6
68 + x$14: phi(bb3: x$15, bb6: x$13)
69 + y$16: phi(bb3: y$17, bb6: y$8)
70 + z$18: phi(bb3: z$19, bb6: z$9)
71 + [21] #15 = LoadLocal unknown x$14
72 + [22] #16 = LoadLocal unknown y$16
73 + [23] #17 = Binary unknown #15 + unknown #16
74 + [24] #18 = LoadLocal unknown z$18
75 + [25] #19 = Binary unknown #17 + unknown #18
76 + [26] Return unknown #19
77 +
compiler/forget/crates/hir-ssa/Cargo.toml
+1
@@ -8,5 +8,6 @@ edition = "2021"
8 [dependencies]
9 hir = { path = "../hir" }
10 bumpalo = "3.13.0"
11 +indexmap = "2.0.0"
12 miette = { version = "5.9.0" }
13 thiserror = "1.0.41"
\ No newline at end of file
compiler/forget/crates/hir-ssa/src/enter.rs
+231 -21
@@ -1,38 +1,248 @@
1 -use std::collections::HashSet;
1 +use std::{cell::RefCell, rc::Rc};
2
3 -use hir::{BlockId, Environment, Function, HIR};
3 +use bumpalo::collections::{CollectIn, Vec};
4 +use hir::{
5 + BasicBlock, BlockId, Blocks, Environment, Function, Identifier, IdentifierData, IdentifierId,
6 + IdentifierOperand, Instruction, LValue, MutableRange, Phi, HIR,
7 +};
8 +use indexmap::{IndexMap, IndexSet};
9 use thiserror::Error;
10
11 #[derive(Error, Debug)]
12 #[error("Error constructing SSA form")]
13 pub struct SSAError;
14
10 -pub fn enter_ssa<'a>(env: &'a Environment<'a>, hir: &mut Function<'a>) -> Result<(), SSAError> {
11 - let mut builder = Builder::new(env);
12 - enter_ssa_impl(&mut builder, hir, hir.body.entry)
15 +pub fn enter_ssa<'a>(env: &Environment<'a>, fun: &mut Function<'a>) -> Result<(), SSAError> {
16 + let blocks = &fun.body.blocks;
17 + let instructions = &mut fun.body.instructions;
18 + let mut builder = Builder::new(env, fun.body.entry, blocks);
19 + for param in &mut fun.params {
20 + builder.visit_param(param);
21 + }
22 + enter_ssa_impl(&mut builder, instructions)?;
23 +
24 + let mut states = builder.complete();
25 +
26 + for block in fun.body.blocks.values_mut() {
27 + let state = states.remove(&block.id).unwrap();
28 + block.phis = state.phis;
29 + }
30 +
31 + Ok(())
32 }
33
15 -struct Builder<'a> {
16 - env: &'a Environment<'a>,
34 +fn enter_ssa_impl<'a, 'e, 'f>(
35 + builder: &mut Builder<'a, 'e, 'f>,
36 + instructions: &mut Vec<'a, Instruction<'a>>,
37 +) -> Result<(), SSAError> {
38 + builder.each_block(|block, builder| {
39 + for instr_ix in &block.instructions {
40 + let instr = &mut instructions[usize::from(*instr_ix)];
41 + instr.each_identifier_store(|store| builder.visit_store(store));
42 + instr.each_identifier_load(|load| builder.visit_load(load));
43 + }
44 + });
45 + Ok(())
46 +}
47 +
48 +#[derive(Debug)]
49 +struct Builder<'a, 'e, 'f> {
50 + env: &'e Environment<'a>,
51 + blocks: &'f Blocks<'a>,
52 +
53 + states: IndexMap<BlockId, BlockState<'a>>,
54 + current: BlockId,
55 + unsealed_predecessors: IndexMap<BlockId, usize>,
56 + unknown: IndexSet<IdentifierId>,
57 + context: IndexSet<IdentifierId>,
58 }
59
19 -impl<'a> Builder<'a> {
20 - fn new(env: &'a Environment<'a>) -> Self {
21 - Self { env }
60 +#[derive(Debug)]
61 +struct BlockState<'a> {
62 + defs: IndexMap<IdentifierId, Identifier<'a>>,
63 + incomplete_phis: Vec<'a, IncompletePhi<'a>>,
64 + phis: Vec<'a, Phi<'a>>,
65 +}
66 +
67 +impl<'a> BlockState<'a> {
68 + fn new(env: &Environment<'a>) -> Self {
69 + Self {
70 + defs: Default::default(),
71 + incomplete_phis: Vec::new_in(&env.allocator),
72 + phis: Vec::new_in(&env.allocator),
73 + }
74 }
75 }
76
25 -fn enter_ssa_impl<'a>(
26 - builder: &mut Builder<'a>,
27 - hir: &mut Function<'a>,
28 - root_block_id: BlockId,
29 -) -> Result<(), SSAError> {
30 - let mut visited = HashSet::with_capacity(hir.body.blocks.len());
31 - for (block_id, block) in hir.body.blocks.iter_mut() {
32 - if !visited.insert(*block_id) {
33 - // Visited the same block twice
34 - return Err(SSAError);
77 +#[derive(Debug)]
78 +struct IncompletePhi<'a> {
79 + old_id: Identifier<'a>,
80 + new_id: Identifier<'a>,
81 +}
82 +
83 +impl<'a, 'e, 'f> Builder<'a, 'e, 'f> {
84 + fn new(env: &'e Environment<'a>, entry: BlockId, blocks: &'f Blocks<'a>) -> Self {
85 + let states = blocks
86 + .keys()
87 + .map(|block_id| (*block_id, BlockState::new(env)))
88 + .collect();
89 + Self {
90 + env,
91 + blocks,
92 + states,
93 + current: entry,
94 + unsealed_predecessors: Default::default(),
95 + unknown: Default::default(),
96 + context: Default::default(),
97 + }
98 + }
99 +
100 + fn complete(self) -> IndexMap<BlockId, BlockState<'a>> {
101 + self.states
102 + }
103 +
104 + fn next_ssa_id(&self) -> IdentifierId {
105 + self.env.next_identifier_id()
106 + }
107 +
108 + fn visit_store(&mut self, lvalue: &mut LValue<'a>) -> () {
109 + let old_identifier = &lvalue.identifier.identifier;
110 + // TODO: use Result (?)
111 + assert!(!self.unknown.contains(&old_identifier.id), "EnterSSA: Expected identifier to be defined before being used. Identifier {old_identifier:?} is undefined.");
112 +
113 + if self.context.contains(&old_identifier.id) {
114 + let new_identifier = self.get_id_at(self.current, old_identifier);
115 + lvalue.identifier.identifier = new_identifier;
116 + return;
117 + }
118 +
119 + let new_identifier = self.make_identifier(old_identifier);
120 + let state = self.states.get_mut(&self.current).unwrap();
121 + state.defs.insert(old_identifier.id, new_identifier.clone());
122 + lvalue.identifier.identifier = new_identifier;
123 + }
124 +
125 + fn visit_param(&mut self, param: &mut IdentifierOperand<'a>) -> () {
126 + let old_identifier = &param.identifier;
127 + let new_identifier = self.make_identifier(old_identifier);
128 + let state = self.states.get_mut(&self.current).unwrap();
129 + state.defs.insert(old_identifier.id, new_identifier.clone());
130 + param.identifier = new_identifier;
131 + }
132 +
133 + fn visit_load(&mut self, local: &mut IdentifierOperand<'a>) -> () {
134 + let new_identifier = self.get_id_at(self.current, &local.identifier);
135 + local.identifier = new_identifier;
136 + }
137 +
138 + fn get_id_at(&mut self, block_id: BlockId, old_identifier: &Identifier<'a>) -> Identifier<'a> {
139 + // Check if we've already resolved this identifier in this block
140 + let state = self.states.get(&block_id).unwrap();
141 + if let Some(identifier) = state.defs.get(&old_identifier.id) {
142 + return identifier.clone();
143 + }
144 + // Else we have to look at predecessor blocks: bail if no predecessors
145 + let block = self.blocks.get(&block_id).unwrap();
146 + if block.predecessors.is_empty() {
147 + println!("Unable to find previous id for {old_identifier:?}");
148 + self.unknown.insert(old_identifier.id);
149 + return old_identifier.clone();
150 + }
151 + // If we haven't visited all predecessors, synthesize a new identifier
152 + // and save it as an incomplete phi
153 + if self.unsealed_predecessors.get(&block_id).cloned().unwrap() > 0 {
154 + let new_identifier = self.make_identifier(old_identifier);
155 + let state = self.states.get_mut(&block_id).unwrap();
156 + state.incomplete_phis.push(IncompletePhi {
157 + old_id: old_identifier.clone(),
158 + new_id: new_identifier.clone(),
159 + });
160 + state.defs.insert(old_identifier.id, new_identifier.clone());
161 + return new_identifier;
162 + }
163 + // If exactly one predecessor, check to see if we have a definition there
164 + if block.predecessors.len() == 1 {
165 + let predecessor = block.predecessors.iter().next().unwrap();
166 + let new_identifier = self.get_id_at(*predecessor, old_identifier);
167 + let state = self.states.get_mut(&block_id).unwrap();
168 + state.defs.insert(old_identifier.id, new_identifier.clone());
169 + return new_identifier;
170 + }
171 + // There are multiple predecessors, we may need a phi
172 + let new_identifier = self.make_identifier(old_identifier);
173 + let state = self.states.get_mut(&block_id).unwrap();
174 + state.defs.insert(old_identifier.id, new_identifier.clone());
175 + self.add_phi(block_id, old_identifier, new_identifier)
176 + }
177 +
178 + fn add_phi(
179 + &mut self,
180 + block_id: BlockId,
181 + old_identifier: &Identifier<'a>,
182 + new_identifier: Identifier<'a>,
183 + ) -> Identifier<'a> {
184 + let mut phi = Phi {
185 + identifier: new_identifier.clone(),
186 + operands: Default::default(),
187 + };
188 + let block = self.blocks.get(&block_id).unwrap();
189 + let preds = block.predecessors.clone();
190 + for pred_block_id in preds {
191 + let pred_id = self.get_id_at(pred_block_id, old_identifier);
192 + phi.operands.insert(pred_block_id, pred_id);
193 + }
194 + let state = self.states.get_mut(&block_id).unwrap();
195 + state.phis.push(phi);
196 + new_identifier
197 + }
198 +
199 + fn make_identifier(&self, old_identifier: &Identifier<'a>) -> Identifier<'a> {
200 + let old_data = old_identifier.data.borrow();
201 + Identifier {
202 + id: self.next_ssa_id(),
203 + name: old_identifier.name.clone(),
204 + data: Rc::new(RefCell::new(IdentifierData {
205 + mutable_range: MutableRange::new(),
206 + scope: None,
207 + type_: old_data.type_.clone(),
208 + })),
209 + }
210 + }
211 +
212 + fn fix_incomplete_phis(&mut self, block_id: BlockId) -> () {
213 + let state = self.states.get_mut(&block_id).unwrap();
214 + let incomplete_phis =
215 + std::mem::replace(&mut state.incomplete_phis, Vec::new_in(&self.env.allocator));
216 + for phi in incomplete_phis {
217 + self.add_phi(block_id, &phi.old_id, phi.new_id);
218 + }
219 + }
220 +
221 + fn each_block<F>(&mut self, mut f: F) -> ()
222 + where
223 + F: FnMut(&BasicBlock<'a>, &mut Self) -> (),
224 + {
225 + let mut visited = IndexSet::new();
226 + let block_ids: Vec<_> = self.blocks.keys().cloned().collect_in(self.env.allocator);
227 + for block_id in block_ids {
228 + visited.insert(block_id);
229 + self.current = block_id;
230 + let block = self.blocks.get(&block_id).unwrap();
231 + f(block, self);
232 + let successors = block.terminal.value.successors();
233 + for successor in successors {
234 + let block = self.blocks.get(&successor).unwrap();
235 + let count = self
236 + .unsealed_predecessors
237 + .get(&successor)
238 + .cloned()
239 + .unwrap_or(block.predecessors.len())
240 + - 1;
241 + self.unsealed_predecessors.insert(successor, count);
242 + if count == 0 && visited.contains(&successor) {
243 + self.fix_incomplete_phis(successor)
244 + }
245 + }
246 }
247 }
37 - Ok(())
248 }
compiler/forget/crates/hir/src/basic_block.rs
+24 -3
@@ -1,6 +1,8 @@
1 -use std::collections::HashSet;
1 +use std::fmt::Display;
2
3 -use crate::{id_types::BlockId, InstrIx, Terminal};
3 +use indexmap::{IndexMap, IndexSet};
4 +
5 +use crate::{id_types::BlockId, Identifier, InstrIx, Terminal};
6
7 /// Represents a sequence of instructions that will always[1] execute
8 /// consecutively. Concretely, a block may have zero or more instructions
@@ -26,7 +28,15 @@ pub struct BasicBlock<'a> {
28 pub terminal: Terminal<'a>,
29
30 /// The immediate predecessors of this block
29 - pub predecessors: HashSet<BlockId>,
31 + pub predecessors: IndexSet<BlockId>,
32 +
33 + pub phis: bumpalo::collections::Vec<'a, Phi<'a>>,
34 +}
35 +
36 +#[derive(Debug)]
37 +pub struct Phi<'a> {
38 + pub identifier: Identifier<'a>,
39 + pub operands: IndexMap<BlockId, Identifier<'a>>,
40 }
41
42 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
@@ -36,3 +46,14 @@ pub enum BlockKind {
46 Loop,
47 Sequence,
48 }
49 +
50 +impl Display for BlockKind {
51 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 + match self {
53 + Self::Block => f.write_str("block"),
54 + Self::Value => f.write_str("value"),
55 + Self::Loop => f.write_str("loop"),
56 + Self::Sequence => f.write_str("sequence"),
57 + }
58 + }
59 +}
compiler/forget/crates/hir/src/function.rs
+12 -3
@@ -1,13 +1,19 @@
1 use std::cell::Cell;
2
3 -use bumpalo::{boxed::Box, collections::Vec};
3 +use bumpalo::{
4 + boxed::Box,
5 + collections::{String, Vec},
6 +};
7 use indexmap::IndexMap;
8
6 -use crate::{BasicBlock, BlockId, Instruction};
9 +use crate::{BasicBlock, BlockId, IdentifierOperand, Instruction};
10
11 /// Represents either a React function or a function expression
12 +#[derive(Debug)]
13 pub struct Function<'a> {
14 + pub id: Option<String<'a>>,
15 pub body: HIR<'a>,
16 + pub params: Vec<'a, IdentifierOperand<'a>>,
17 pub is_async: bool,
18 pub is_generator: bool,
19 }
@@ -16,18 +22,21 @@ pub struct Function<'a> {
22 /// Blocks are stored in reverse postorder (predecessors before successors)
23 /// so that compiler passes can complete forward data flow analysis in a
24 /// single pass over the CFG in the case where there are no loops.
25 +#[derive(Debug)]
26 pub struct HIR<'a> {
27 /// The id of the first block
28 pub entry: BlockId,
29
30 /// Blocks are stored in a map for easy retrieval by their id,
31 /// but the blocks are in reverse postorder
25 - pub blocks: IndexMap<BlockId, Box<'a, BasicBlock<'a>>>,
32 + pub blocks: Blocks<'a>,
33
34 /// All instructions for the block. This may contain unused items,
35 pub instructions: Vec<'a, Instruction<'a>>,
36 }
37
38 +pub type Blocks<'a> = IndexMap<BlockId, Box<'a, BasicBlock<'a>>>;
39 +
40 impl<'a> HIR<'a> {
41 pub fn block(&self, id: BlockId) -> &BasicBlock<'a> {
42 self.blocks.get(&id).unwrap()
compiler/forget/crates/hir/src/instruction.rs
+42
@@ -11,6 +11,48 @@ pub struct Instruction<'a> {
11 pub value: InstructionValue<'a>,
12 }
13
14 +impl<'a> Instruction<'a> {
15 + pub fn each_identifier_store<F>(&mut self, mut f: F) -> ()
16 + where
17 + F: FnMut(&mut LValue<'a>) -> (),
18 + {
19 + match &mut self.value {
20 + InstructionValue::Array(_) => {}
21 + InstructionValue::Binary(_) => {}
22 + InstructionValue::DeclareContext(instr) => {
23 + f(&mut instr.lvalue);
24 + }
25 + InstructionValue::DeclareLocal(instr) => {
26 + f(&mut instr.lvalue);
27 + }
28 + InstructionValue::LoadContext(_) => {}
29 + InstructionValue::LoadGlobal(_) => {}
30 + InstructionValue::LoadLocal(_) => {}
31 + InstructionValue::Primitive(_) => {}
32 + InstructionValue::StoreLocal(instr) => {
33 + f(&mut instr.lvalue);
34 + }
35 + }
36 + }
37 +
38 + pub fn each_identifier_load<F>(&mut self, mut f: F) -> ()
39 + where
40 + F: FnMut(&mut IdentifierOperand<'a>) -> (),
41 + {
42 + match &mut self.value {
43 + InstructionValue::Array(_) => {}
44 + InstructionValue::Binary(_) => {}
45 + InstructionValue::DeclareContext(_) => {}
46 + InstructionValue::DeclareLocal(_) => {}
47 + InstructionValue::LoadContext(_) => {}
48 + InstructionValue::LoadGlobal(_) => {}
49 + InstructionValue::LoadLocal(instr) => f(&mut instr.place),
50 + InstructionValue::Primitive(_) => {}
51 + InstructionValue::StoreLocal(_) => {}
52 + }
53 + }
54 +}
55 +
56 #[derive(Debug)]
57 pub enum InstructionValue<'a> {
58 Array(Array<'a>),
compiler/forget/crates/hir/src/print.rs
+47 -2
@@ -2,7 +2,7 @@ use std::fmt::{Result, Write};
2
3 use crate::{
4 ArrayElement, BasicBlock, Function, Identifier, IdentifierOperand, Instruction,
5 - InstructionValue, LValue, Operand, PrimitiveValue, Terminal, TerminalValue, HIR,
5 + InstructionValue, LValue, Operand, Phi, PrimitiveValue, Terminal, TerminalValue, HIR,
6 };
7
8 /// Trait for HIR types to describe how they print themselves.
@@ -16,6 +16,20 @@ pub trait Print<'a> {
16
17 impl<'a> Print<'a> for Function<'a> {
18 fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
19 + writeln!(
20 + out,
21 + "function {}(",
22 + match &self.id {
23 + Some(id) => id,
24 + None => "<anonymous>",
25 + }
26 + )?;
27 + for param in &self.params {
28 + write!(out, " ")?;
29 + param.print(hir, out)?;
30 + writeln!(out, ",")?;
31 + }
32 + writeln!(out, ")")?;
33 writeln!(out, "entry {}", self.body.entry)?;
34 for (_, block) in self.body.blocks.iter() {
35 block.print(hir, out)?;
@@ -26,7 +40,21 @@ impl<'a> Print<'a> for Function<'a> {
40
41 impl<'a> Print<'a> for BasicBlock<'a> {
42 fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
29 - writeln!(out, "{}", self.id)?;
43 + writeln!(out, "{} ({})", self.id, self.kind)?;
44 + if !self.predecessors.is_empty() {
45 + write!(out, " predecessors: ")?;
46 + for (ix, pred) in self.predecessors.iter().enumerate() {
47 + if ix != 0 {
48 + write!(out, ", ")?;
49 + }
50 + write!(out, "{}", *pred)?;
51 + }
52 + writeln!(out)?;
53 + }
54 + for phi in self.phis.iter() {
55 + phi.print(hir, out)?;
56 + writeln!(out)?;
57 + }
58 for ix in &self.instructions {
59 let instr = &hir.instructions[usize::from(*ix)];
60 write!(out, " {} {} = ", instr.id, ix)?;
@@ -38,6 +66,23 @@ impl<'a> Print<'a> for BasicBlock<'a> {
66 }
67 }
68
69 +impl<'a> Print<'a> for Phi<'a> {
70 + fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
71 + write!(out, " ")?;
72 + self.identifier.print(hir, out)?;
73 + write!(out, ": phi(")?;
74 + for (ix, (pred_id, id)) in self.operands.iter().enumerate() {
75 + if ix != 0 {
76 + write!(out, ", ")?;
77 + }
78 + write!(out, "{}: ", pred_id)?;
79 + id.print(hir, out)?;
80 + }
81 + write!(out, ")")?;
82 + Ok(())
83 + }
84 +}
85 +
86 impl<'a> Print<'a> for Instruction<'a> {
87 fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
88 write!(out, " {} ", self.id)?;
compiler/forget/crates/hir/src/types.rs
+2 -2
@@ -1,6 +1,6 @@
1 use crate::{FunctionId, ObjectId, TypeVarId};
2
3 -#[derive(Debug)]
3 +#[derive(Clone, Debug)]
4 pub enum Type {
5 Builtin(BuiltinType),
6 // Phi(Box<PhiType>),
@@ -9,7 +9,7 @@ pub enum Type {
9 // Prop(Box<PropType>),
10 }
11
12 -#[derive(Debug)]
12 +#[derive(Clone, Debug)]
13 pub enum BuiltinType {
14 Primitive,
15 Function(Option<FunctionId>),