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

[rust] New data model for HIR

This change is motivated by starting to explore porting EnterSSA to Rust. It's a good medium complexity pass and quickly demonstrates why a direct port of our existing data model and algorithms won't work so well. For examples just these first lines at the top of the transform create multiple references to the function body/blocks: https://github.com/facebook/react-forget/blob/58da89888eabde17ede649d348b307ee01fc15e1/forget/packages/babel-plugin-react-forget/src/SSA/EnterSSA.ts#L230-L231 But more generally it's always felt wrong that instructions have an LValue that isn't really used. So here i'm exploring making operands a newtype index into a single instructions array (shared for the entire function), and using different types for identifier references in SetLocal and LoadLocal. Incidentally this also declutters the printed HIR quite a bit. I'm not going to land this until i actually finish enter_ssa() and some other passes. We definitely need to balance fidelity to the existing code (to facilitate porting) with using idiomatic Rust (to facilitate porting in the sense of not fighting the borrow checker).

Joe Savona committed Jul 11, 2023 at 16:59 UTC e9d2822e92792c0f6a96864b9cf19a86275876f6
22 files changed +417 -236
compiler/forget/Cargo.lock
+10
@@ -638,6 +638,16 @@ dependencies = [
638 "serde",
639 ]
640
641 +[[package]]
642 +name = "hir-ssa"
643 +version = "0.1.0"
644 +dependencies = [
645 + "bumpalo",
646 + "hir",
647 + "miette 5.9.0",
648 + "thiserror",
649 +]
650 +
651 [[package]]
652 name = "iana-time-zone"
653 version = "0.1.57"
compiler/forget/Cargo.toml
+1
@@ -4,6 +4,7 @@ members = [
4 "crates/build-hir",
5 "crates/fixtures",
6 "crates/hir",
7 + "crates/hir-ssa",
8 "crates/swc-demo",
9 "crates/estree",
10 "crates/estree-codegen",
compiler/forget/crates/build-hir/src/build.rs
+94 -73
@@ -6,8 +6,8 @@ use estree::{
6 };
7 use hir::{
8 ArrayElement, BlockKind, BranchTerminal, Environment, ForTerminal, Function, GotoKind,
9 - Identifier, InstructionKind, InstructionValue, LValue, LoadGlobal, LoadLocal, Place,
10 - PrimitiveValue, TerminalValue,
9 + Identifier, IdentifierOperand, InstrIx, InstructionKind, InstructionValue, LValue, LoadGlobal,
10 + LoadLocal, Operand, PrimitiveValue, TerminalValue,
11 };
12
13 use crate::{
@@ -33,7 +33,7 @@ pub fn build<'a>(
33 lower_block_statement(environment, &mut builder, *body, None)?
34 }
35 Some(estree::FunctionBody::Expression(body)) => {
36 - lower_expression_to_temporary(environment, &mut builder, body)?;
36 + lower_expression(environment, &mut builder, body)?;
37 }
38 None => {
39 return Err(BuildDiagnostic::new(
@@ -56,7 +56,10 @@ pub fn build<'a>(
56 );
57 builder.terminate(
58 TerminalValue::Return(hir::ReturnTerminal {
59 - value: implicit_return_value,
59 + value: Operand {
60 + ix: implicit_return_value,
61 + effect: None,
62 + },
63 }),
64 hir::BlockKind::Block,
65 );
@@ -114,8 +117,8 @@ fn lower_statement<'a>(
117 );
118 }
119 Statement::ReturnStatement(stmt) => {
117 - let value = match stmt.argument {
118 - Some(argument) => lower_expression_to_temporary(env, builder, argument)?,
120 + let ix = match stmt.argument {
121 + Some(argument) => lower_expression(env, builder, argument)?,
122 None => lower_value_to_temporary(
123 env,
124 builder,
@@ -125,12 +128,14 @@ fn lower_statement<'a>(
128 ),
129 };
130 builder.terminate(
128 - TerminalValue::Return(hir::ReturnTerminal { value }),
131 + TerminalValue::Return(hir::ReturnTerminal {
132 + value: Operand { ix, effect: None },
133 + }),
134 BlockKind::Block,
135 );
136 }
137 Statement::ExpressionStatement(stmt) => {
133 - lower_expression_to_temporary(env, builder, stmt.expression)?;
138 + lower_expression(env, builder, stmt.expression)?;
139 }
140 Statement::EmptyStatement(_) => {
141 // no-op
@@ -149,7 +154,7 @@ fn lower_statement<'a>(
154 };
155 for declaration in stmt.declarations {
156 if let Some(init) = declaration.init {
152 - let value = lower_expression_to_temporary(env, builder, init)?;
157 + let value = lower_expression(env, builder, init)?;
158 lower_assignment(
159 env,
160 builder,
@@ -171,15 +176,17 @@ fn lower_statement<'a>(
176 ));
177 }
178 };
174 - let place = Place {
175 - effect: None,
176 - identifier,
177 - };
179 lower_value_to_temporary(
180 env,
181 builder,
182 InstructionValue::DeclareLocal(hir::DeclareLocal {
182 - lvalue: LValue { place, kind },
183 + lvalue: LValue {
184 + identifier: IdentifierOperand {
185 + identifier,
186 + effect: None,
187 + },
188 + kind,
189 + },
190 }),
191 );
192 }
@@ -216,9 +223,12 @@ fn lower_statement<'a>(
223 }))
224 })?;
225
219 - let test = lower_expression_to_temporary(env, builder, test)?;
226 + let test = lower_expression(env, builder, test)?;
227 let terminal = TerminalValue::If(hir::IfTerminal {
221 - test,
228 + test: Operand {
229 + ix: test,
230 + effect: None,
231 + },
232 consequent: consequent_block,
233 alternate: alternate_block,
234 fallthrough: Some(fallthrough_block.id),
@@ -259,7 +269,7 @@ fn lower_statement<'a>(
269 let update_block = update
270 .map(|update| {
271 builder.enter(BlockKind::Loop, |builder| {
262 - lower_expression_to_temporary(env, builder, update)?;
272 + lower_expression(env, builder, update)?;
273 Ok(TerminalValue::Goto(hir::GotoTerminal {
274 block: test_block.id,
275 kind: GotoKind::Break,
@@ -293,9 +303,12 @@ fn lower_statement<'a>(
303 builder.terminate_with_fallthrough(terminal, test_block);
304
305 if let Some(test) = test {
296 - let test_value = lower_expression_to_temporary(env, builder, test)?;
306 + let test_value = lower_expression(env, builder, test)?;
307 let terminal = TerminalValue::Branch(BranchTerminal {
298 - test: test_value,
308 + test: Operand {
309 + ix: test_value,
310 + effect: None,
311 + },
312 consequent: body_block,
313 alternate: fallthrough_block.id,
314 });
@@ -313,16 +326,6 @@ fn lower_statement<'a>(
326 Ok(())
327 }
328
316 -/// Shortcut for lowering an expression and saving the result to a temporary
317 -fn lower_expression_to_temporary<'a>(
318 - env: &'a Environment<'a>,
319 - builder: &mut Builder<'a>,
320 - expr: Expression,
321 -) -> Result<Place<'a>, BuildDiagnostic> {
322 - let value = lower_expression(env, builder, expr)?;
323 - Ok(lower_value_to_temporary(env, builder, value))
324 -}
325 -
329 /// Converts an ESTree Expression into an HIR InstructionValue. Note that while only a single
330 /// InstructionValue is returned, this function is recursive and may cause multiple instructions
331 /// to be emitted, possibly across multiple basic blocks (in the case of expressions with control
@@ -331,14 +334,14 @@ fn lower_expression<'a>(
334 env: &'a Environment<'a>,
335 builder: &mut Builder<'a>,
336 expr: Expression,
334 -) -> Result<InstructionValue<'a>, BuildDiagnostic> {
335 - Ok(match expr {
337 +) -> Result<InstrIx, BuildDiagnostic> {
338 + let value = match expr {
339 Expression::Identifier(expr) => {
340 // TODO: handle unbound variables
341 let binding = builder.resolve_binding(&expr)?;
342 match binding {
343 Binding::Local(identifier) => {
341 - let place = Place {
344 + let place = IdentifierOperand {
345 effect: None,
346 identifier,
347 };
@@ -357,15 +360,17 @@ fn lower_expression<'a>(
360 for expr in expr.elements {
361 let element = match expr {
362 Some(estree::ExpressionOrSpread::SpreadElement(expr)) => {
360 - Some(ArrayElement::Spread(lower_expression_to_temporary(
361 - env,
362 - builder,
363 - expr.argument,
364 - )?))
363 + Some(ArrayElement::Spread(Operand {
364 + ix: lower_expression(env, builder, expr.argument)?,
365 + effect: None,
366 + }))
367 + }
368 + Some(estree::ExpressionOrSpread::Expression(expr)) => {
369 + Some(ArrayElement::Place(Operand {
370 + ix: lower_expression(env, builder, expr)?,
371 + effect: None,
372 + }))
373 }
366 - Some(estree::ExpressionOrSpread::Expression(expr)) => Some(
367 - ArrayElement::Place(lower_expression_to_temporary(env, builder, expr)?),
368 - ),
374 None => None,
375 };
376 elements.push(element);
@@ -375,8 +380,14 @@ fn lower_expression<'a>(
380
381 Expression::AssignmentExpression(expr) => match expr.operator {
382 estree::AssignmentOperator::Equals => {
378 - let right = lower_expression_to_temporary(env, builder, expr.right)?;
379 - lower_assignment(env, builder, InstructionKind::Reassign, expr.left, right)?
383 + let right = lower_expression(env, builder, expr.right)?;
384 + return Ok(lower_assignment(
385 + env,
386 + builder,
387 + InstructionKind::Reassign,
388 + expr.left,
389 + right,
390 + )?);
391 }
392 _ => todo!("lower assignment expr {:#?}", expr),
393 },
@@ -388,17 +399,24 @@ fn lower_expression<'a>(
399 right,
400 ..
401 } = *expr;
391 - let left = lower_expression_to_temporary(env, builder, left)?;
392 - let right = lower_expression_to_temporary(env, builder, right)?;
402 + let left = lower_expression(env, builder, left)?;
403 + let right = lower_expression(env, builder, right)?;
404 InstructionValue::Binary(hir::Binary {
394 - left,
405 + left: Operand {
406 + ix: left,
407 + effect: None,
408 + },
409 operator,
396 - right,
410 + right: Operand {
411 + ix: right,
412 + effect: None,
413 + },
414 })
415 }
416
417 _ => todo!("Lower expr {expr:#?}"),
401 - })
418 + };
419 + Ok(builder.push(value))
420 }
421
422 fn lower_assignment<'a>(
@@ -406,21 +424,23 @@ fn lower_assignment<'a>(
424 builder: &mut Builder<'a>,
425 kind: InstructionKind,
426 lvalue: AssignmentTarget,
409 - value: Place<'a>,
410 -) -> Result<InstructionValue<'a>, BuildDiagnostic> {
427 + value: InstrIx,
428 +) -> Result<InstrIx, BuildDiagnostic> {
429 Ok(match lvalue {
430 AssignmentTarget::Pattern(lvalue) => match lvalue {
431 Pattern::Identifier(lvalue) => {
414 - let place = lower_identifier_for_assignment(env, builder, kind, *lvalue)?;
415 - let temporary = lower_value_to_temporary(
432 + let identifier = lower_identifier_for_assignment(env, builder, kind, *lvalue)?;
433 + lower_value_to_temporary(
434 env,
435 builder,
436 InstructionValue::StoreLocal(hir::StoreLocal {
419 - lvalue: LValue { place, kind },
420 - value,
437 + lvalue: LValue { identifier, kind },
438 + value: Operand {
439 + ix: value,
440 + effect: None,
441 + },
442 }),
422 - );
423 - InstructionValue::LoadLocal(LoadLocal { place: temporary })
443 + )
444 }
445 _ => todo!("lower assignment pattern for {:#?}", lvalue),
446 },
@@ -433,7 +453,7 @@ fn lower_identifier_for_assignment<'a>(
453 builder: &mut Builder<'a>,
454 _kind: InstructionKind,
455 identifier: estree::Identifier,
436 -) -> Result<Place<'a>, BuildDiagnostic> {
456 +) -> Result<IdentifierOperand<'a>, BuildDiagnostic> {
457 let binding = builder.resolve_binding(&identifier)?;
458 match binding {
459 Binding::Module(..) | Binding::Global => Err(BuildDiagnostic::new(
@@ -441,7 +461,7 @@ fn lower_identifier_for_assignment<'a>(
461 ErrorSeverity::InvalidReact,
462 identifier.range,
463 )),
444 - Binding::Local(id) => Ok(Place {
464 + Binding::Local(id) => Ok(IdentifierOperand {
465 identifier: id,
466 effect: None,
467 }),
@@ -457,26 +477,27 @@ fn lower_value_to_temporary<'a>(
477 env: &'a Environment<'a>,
478 builder: &mut Builder<'a>,
479 value: InstructionValue<'a>,
460 -) -> Place<'a> {
461 - if let InstructionValue::LoadLocal(LoadLocal {
462 - place:
463 - place @ Place {
464 - identifier: Identifier { name: None, .. },
465 - ..
466 - },
467 - }) = value
468 - {
469 - return place;
470 - }
471 - let place = build_temporary_place(env, builder);
472 - builder.push(place.clone(), value);
473 - return place;
480 +) -> InstrIx {
481 + // if let InstructionValue::LoadLocal(LoadLocal {
482 + // place:
483 + // place @ Operand {
484 + // identifier: Identifier { name: None, .. },
485 + // ..
486 + // },
487 + // }) = value
488 + // {
489 + // return place;
490 + // }
491 + builder.push(value)
492 }
493
494 /// Constructs a temporary Identifier and Place wrapper, which can be used as an Instruction lvalue
495 /// or other places where a temporary target is required
478 -fn build_temporary_place<'a>(_env: &'a Environment<'a>, builder: &mut Builder<'a>) -> Place<'a> {
479 - Place {
496 +fn build_temporary_place<'a>(
497 + _env: &'a Environment<'a>,
498 + builder: &mut Builder<'a>,
499 +) -> IdentifierOperand<'a> {
500 + IdentifierOperand {
501 identifier: builder.make_temporary(),
502 effect: None,
503 }
compiler/forget/crates/build-hir/src/builder.rs
+51 -30
@@ -1,9 +1,13 @@
1 -use bumpalo::collections::{String, Vec};
1 +use bumpalo::{
2 + boxed::Box,
3 + collections::{String, Vec},
4 +};
5 use std::{cell::RefCell, collections::HashSet, rc::Rc};
6
7 use hir::{
5 - BasicBlock, BlockId, BlockKind, Environment, GotoKind, Identifier, IdentifierData, Instruction,
6 - InstructionIdGenerator, InstructionValue, Place, Terminal, TerminalValue, Type, HIR,
8 + BasicBlock, BlockId, BlockKind, Environment, GotoKind, Identifier, IdentifierData, InstrIx,
9 + Instruction, InstructionIdGenerator, InstructionValue, Operand, Terminal, TerminalValue, Type,
10 + HIR,
11 };
12 use indexmap::IndexMap;
13
@@ -22,7 +26,9 @@ pub(crate) struct Builder<'a> {
26 #[allow(dead_code)]
27 environment: &'a Environment<'a>,
28
25 - completed: IndexMap<BlockId, BasicBlock<'a>>,
29 + completed: IndexMap<BlockId, Box<'a, BasicBlock<'a>>>,
30 +
31 + instructions: Vec<'a, Instruction<'a>>,
32
33 entry: BlockId,
34
@@ -36,7 +42,7 @@ pub(crate) struct Builder<'a> {
42 pub(crate) struct WipBlock<'a> {
43 pub id: BlockId,
44 pub kind: BlockKind,
39 - pub instructions: Vec<'a, Instruction<'a>>,
45 + pub instructions: Vec<'a, InstrIx>,
46 }
47
48 pub(crate) enum Binding<'a> {
@@ -94,6 +100,7 @@ impl<'a> Builder<'a> {
100 Self {
101 environment,
102 completed: Default::default(),
103 + instructions: Vec::new_in(&environment.allocator),
104 entry,
105 wip: current,
106 id_gen: InstructionIdGenerator::new(),
@@ -110,6 +117,7 @@ impl<'a> Builder<'a> {
117 let mut hir = HIR {
118 entry: self.entry,
119 blocks: self.completed,
120 + instructions: self.instructions,
121 };
122
123 reverse_postorder_blocks(&mut hir);
@@ -123,13 +131,15 @@ impl<'a> Builder<'a> {
131 }
132
133 /// Adds a new instruction to the end of the work in progress block
126 - pub(crate) fn push(&mut self, lvalue: Place<'a>, value: InstructionValue<'a>) {
134 + pub(crate) fn push(&mut self, value: InstructionValue<'a>) -> InstrIx {
135 let instr = Instruction {
136 id: self.id_gen.next(),
129 - lvalue,
137 value,
138 };
132 - self.wip.instructions.push(instr);
139 + let ix = InstrIx::new(self.instructions.len() as u32);
140 + self.instructions.push(instr);
141 + self.wip.instructions.push(ix);
142 + ix
143 }
144
145 /// Terminates the work in progress block with the given terminal, and starts a new
@@ -151,16 +161,19 @@ impl<'a> Builder<'a> {
161 let prev_wip = std::mem::replace(&mut self.wip, fallthrough);
162 self.completed.insert(
163 prev_wip.id,
154 - BasicBlock {
155 - id: prev_wip.id,
156 - kind: prev_wip.kind,
157 - instructions: prev_wip.instructions,
158 - terminal: Terminal {
159 - id: self.id_gen.next(),
160 - value: terminal,
164 + Box::new_in(
165 + BasicBlock {
166 + id: prev_wip.id,
167 + kind: prev_wip.kind,
168 + instructions: prev_wip.instructions,
169 + terminal: Terminal {
170 + id: self.id_gen.next(),
171 + value: terminal,
172 + },
173 + predecessors: Default::default(),
174 },
162 - predecessors: Default::default(),
163 - },
175 + &self.environment.allocator,
176 + ),
177 );
178 }
179
@@ -203,16 +216,19 @@ impl<'a> Builder<'a> {
216 let completed = std::mem::replace(&mut self.wip, current);
217 self.completed.insert(
218 completed.id,
206 - BasicBlock {
207 - id: completed.id,
208 - kind: completed.kind,
209 - instructions: completed.instructions,
210 - terminal: Terminal {
211 - id: self.id_gen.next(),
212 - value: terminal,
219 + Box::new_in(
220 + BasicBlock {
221 + id: completed.id,
222 + kind: completed.kind,
223 + instructions: completed.instructions,
224 + terminal: Terminal {
225 + id: self.id_gen.next(),
226 + value: terminal,
227 + },
228 + predecessors: Default::default(),
229 },
214 - predecessors: Default::default(),
215 - },
230 + &self.environment.allocator,
231 + ),
232 );
233 result
234 }
@@ -377,6 +393,9 @@ fn reverse_postorder_blocks<'a>(hir: &mut HIR<'a>) {
393 visit(terminal.block, hir, visited, postorder);
394 }
395 TerminalValue::Return(..) => { /* no-op */ }
396 + TerminalValue::Unsupported(..) => {
397 + panic!("Unexpected unsupported terminal")
398 + }
399 }
400 postorder.push(block_id);
401 }
@@ -446,15 +465,17 @@ fn remove_unreachable_do_while_statements<'a>(hir: &mut HIR<'a>) {
465 fn mark_instruction_ids<'a>(hir: &mut HIR<'a>) -> Result<(), BuildDiagnostic> {
466 let mut id_gen = InstructionIdGenerator::new();
467 let mut visited = HashSet::<(usize, usize)>::new();
449 - for (block_ix, block) in hir.blocks.values_mut().enumerate() {
450 - for (instr_ix, instr) in block.instructions.iter_mut().enumerate() {
451 - invariant(visited.insert((block_ix, instr_ix)), || {
468 + for (ii, block) in hir.blocks.values_mut().enumerate() {
469 + let block_id = block.id;
470 + for (jj, instr_ix) in block.instructions.iter_mut().enumerate() {
471 + invariant(visited.insert((ii, jj)), || {
472 BuildDiagnostic::new(
453 - DiagnosticError::BlockVisitedTwice { block: block.id },
473 + DiagnosticError::BlockVisitedTwice { block: block_id },
474 ErrorSeverity::Invariant,
475 None,
476 )
477 })?;
478 + let instr = &mut hir.instructions[usize::from(*instr_ix)];
479 instr.id = id_gen.next();
480 }
481 block.terminal.id = id_gen.next();
compiler/forget/crates/build-hir/src/error.rs
+1 -1
@@ -119,7 +119,7 @@ impl BuildDiagnostic {
119 /// with the diagnostic produced by the provided callback
120 pub fn invariant<F>(cond: bool, f: F) -> Result<(), BuildDiagnostic>
121 where
122 - F: FnOnce() -> BuildDiagnostic,
122 + F: Fn() -> BuildDiagnostic,
123 {
124 if cond {
125 Ok(())
compiler/forget/crates/fixtures/tests/fixtures_test.rs
+1 -1
@@ -32,7 +32,7 @@ fn fixtures() {
32 }
33 match build(&environment, *fun) {
34 Ok(hir) => {
35 - hir.print(&mut output).unwrap();
35 + hir.print(&hir.body, &mut output).unwrap();
36 }
37 Err(error) => {
38 write!(&mut output, "{}", error,).unwrap();
compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@for-statement.js.snap
+18 -18
@@ -16,31 +16,31 @@ function foo() {
16 Output:
17 entry bb0
18 bb0
19 - [0] unknown $0 = 0
20 - [1] unknown $2 = StoreLocal Let unknown x$1 = unknown $0
19 + [0] #0 = 0
20 + [1] #1 = StoreLocal Let unknown x$0 = unknown #0
21 [2] For init=bb3 test=bb1 update=bb4 body=bb5 fallthrough=bb2
22 bb3
23 - [3] unknown $3 = 0
24 - [4] unknown $5 = StoreLocal Let unknown i$4 = unknown $3
23 + [3] #2 = 0
24 + [4] #3 = StoreLocal Let unknown i$1 = unknown #2
25 [5] Goto bb1
26 bb1
27 - [6] unknown $14 = LoadLocal unknown i$4
28 - [7] unknown $15 = 10
29 - [8] unknown $16 = Binary unknown $14 < unknown $15
30 - [9] Branch unknown $16 consequent=bb5 alternate=bb2
27 + [6] #12 = LoadLocal unknown i$1
28 + [7] #13 = 10
29 + [8] #14 = Binary unknown #12 < unknown #13
30 + [9] Branch unknown #14 consequent=bb5 alternate=bb2
31 bb5
32 - [10] unknown $10 = LoadLocal unknown x$1
33 - [11] unknown $11 = LoadLocal unknown i$4
34 - [12] unknown $12 = Binary unknown $10 + unknown $11
35 - [13] unknown $13 = StoreLocal Reassign unknown x$1 = unknown $12
32 + [10] #8 = LoadLocal unknown x$0
33 + [11] #9 = LoadLocal unknown i$1
34 + [12] #10 = Binary unknown #8 + unknown #9
35 + [13] #11 = StoreLocal Reassign unknown x$0 = unknown #10
36 [14] Goto bb4
37 bb4
38 - [15] unknown $6 = LoadLocal unknown i$4
39 - [16] unknown $7 = 1
40 - [17] unknown $8 = Binary unknown $6 + unknown $7
41 - [18] unknown $9 = StoreLocal Reassign unknown i$4 = unknown $8
38 + [15] #4 = LoadLocal unknown i$1
39 + [16] #5 = 1
40 + [17] #6 = Binary unknown #4 + unknown #5
41 + [18] #7 = StoreLocal Reassign unknown i$1 = unknown #6
42 [19] Goto bb1
43 bb2
44 - [20] unknown $17 = LoadLocal unknown x$1
45 - [21] Return unknown $17
44 + [20] #15 = LoadLocal unknown x$0
45 + [21] Return unknown #15
46
compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@identifiers.js.snap
+11 -11
@@ -25,15 +25,15 @@ function id(x) {
25 Output:
26 entry bb0
27 bb0
28 - [0] unknown $0 = LoadGlobal Math
29 - [1] unknown $2 = LoadGlobal id
30 - [2] unknown $3 = true
31 - [3] unknown $5 = StoreLocal Let unknown y$4 = unknown $3
32 - [4] unknown $6 = false
33 - [5] unknown $7 = StoreLocal Reassign unknown y$4 = unknown $6
34 - [6] unknown $8 = LoadLocal unknown y$4
35 - [7] unknown $10 = DeclareLocal Let unknown z$9
36 - [8] unknown $11 = LoadLocal unknown z$9
37 - [9] unknown $13 = LoadLocal unknown x$12
38 - [10] Return unknown $13
28 + [0] #0 = LoadGlobal Math
29 + [1] #1 = LoadGlobal id
30 + [2] #2 = true
31 + [3] #3 = StoreLocal Let unknown y$1 = unknown #2
32 + [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
38 + [10] Return unknown #9
39
compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@if-statement.js.snap
+8 -8
@@ -17,16 +17,16 @@ function foo(a, b, c, d) {
17 Output:
18 entry bb0
19 bb0
20 - [0] unknown $5 = LoadLocal unknown a$4
21 - [1] If unknown $5 consequent=bb2 alternate=bb4 fallthrough=bb1
20 + [0] #2 = LoadLocal unknown a$2
21 + [1] If unknown #2 consequent=bb2 alternate=bb4 fallthrough=bb1
22 bb2
23 - [2] unknown $1 = LoadLocal unknown b$0
24 - [3] Return unknown $1
23 + [2] #0 = LoadLocal unknown b$0
24 + [3] Return unknown #0
25 bb4
26 - [4] unknown $3 = LoadLocal unknown c$2
26 + [4] #1 = LoadLocal unknown c$1
27 [5] Goto bb1
28 bb1
29 - [6] unknown $7 = LoadLocal unknown d$6
30 - [7] unknown $8 = <undefined>
31 - [8] Return unknown $8
29 + [6] #3 = LoadLocal unknown d$3
30 + [7] #4 = <undefined>
31 + [8] Return unknown #4
32
compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@simple.js.snap
+10 -10
@@ -13,14 +13,14 @@ function test() {
13 Output:
14 entry bb0
15 bb0
16 - [0] unknown $0 = true
17 - [1] unknown $1 = false
18 - [2] unknown $2 = null
19 - [3] unknown $3 = 1
20 - [4] unknown $4 = 3.14
21 - [5] unknown $5 = "hello world!"
22 - [6] unknown $6 = Array [unknown $5]
23 - [7] unknown $7 = Array [unknown $0, unknown $1, unknown $2, unknown $3, unknown $4, ...unknown $6]
24 - [8] unknown $8 = 2
25 - [9] Return unknown $8
16 + [0] #0 = true
17 + [1] #1 = false
18 + [2] #2 = null
19 + [3] #3 = 1
20 + [4] #4 = 3.14
21 + [5] #5 = "hello world!"
22 + [6] #6 = Array [unknown #5]
23 + [7] #7 = Array [unknown #0, unknown #1, unknown #2, unknown #3, unknown #4, ...unknown #6]
24 + [8] #8 = 2
25 + [9] Return unknown #8
26
compiler/forget/crates/hir-ssa/Cargo.toml new
+12
@@ -0,0 +1,12 @@
1 +[package]
2 +name = "hir-ssa"
3 +version = "0.1.0"
4 +edition = "2021"
5 +
6 +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 +
8 +[dependencies]
9 +hir = { path = "../hir" }
10 +bumpalo = "3.13.0"
11 +miette = { version = "5.9.0" }
12 +thiserror = "1.0.41"
\ No newline at end of file
compiler/forget/crates/hir-ssa/README.md new
+3
@@ -0,0 +1,3 @@
1 +# hir-ssa
2 +
3 +This crate handles conversion to/from SSA and normal form. This includes a pass to eliminate redundant phi nodes in SSA form.
\ No newline at end of file
compiler/forget/crates/hir-ssa/src/enter.rs new
+38
@@ -0,0 +1,38 @@
1 +use std::collections::HashSet;
2 +
3 +use hir::{BlockId, Environment, Function, HIR};
4 +use thiserror::Error;
5 +
6 +#[derive(Error, Debug)]
7 +#[error("Error constructing SSA form")]
8 +pub struct SSAError;
9 +
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)
13 +}
14 +
15 +struct Builder<'a> {
16 + env: &'a Environment<'a>,
17 +}
18 +
19 +impl<'a> Builder<'a> {
20 + fn new(env: &'a Environment<'a>) -> Self {
21 + Self { env }
22 + }
23 +}
24 +
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);
35 + }
36 + }
37 + Ok(())
38 +}
compiler/forget/crates/hir-ssa/src/leave.rs new
+3
@@ -0,0 +1,3 @@
1 +use hir::{Environment, HIR};
2 +
3 +pub fn leave_ssa<'a>(env: &'a Environment, hir: &mut HIR<'a>) {}
compiler/forget/crates/hir-ssa/src/lib.rs new
+7
@@ -0,0 +1,7 @@
1 +mod enter;
2 +mod leave;
3 +mod minimize_phis;
4 +
5 +pub use enter::enter_ssa;
6 +pub use leave::leave_ssa;
7 +pub use minimize_phis::minimize_phis;
compiler/forget/crates/hir-ssa/src/minimize_phis.rs new
+3
@@ -0,0 +1,3 @@
1 +use hir::{Environment, HIR};
2 +
3 +pub fn minimize_phis<'a>(env: &'a Environment, hir: &mut HIR<'a>) {}
compiler/forget/crates/hir/src/basic_block.rs
+2 -2
@@ -1,6 +1,6 @@
1 use std::collections::HashSet;
2
3 -use crate::{id_types::BlockId, Instruction, Terminal};
3 +use crate::{id_types::BlockId, InstrIx, Terminal};
4
5 /// Represents a sequence of instructions that will always[1] execute
6 /// consecutively. Concretely, a block may have zero or more instructions
@@ -20,7 +20,7 @@ pub struct BasicBlock<'a> {
20 pub kind: BlockKind,
21
22 /// The ordered instructions in this block
23 - pub instructions: bumpalo::collections::Vec<'a, Instruction<'a>>,
23 + pub instructions: bumpalo::collections::Vec<'a, InstrIx>,
24
25 /// The terminal instruction for the block
26 pub terminal: Terminal<'a>,
compiler/forget/crates/hir/src/function.rs
+8 -4
@@ -1,9 +1,11 @@
1 +use std::cell::Cell;
2 +
3 +use bumpalo::{boxed::Box, collections::Vec};
4 use indexmap::IndexMap;
5
3 -use crate::{BasicBlock, BlockId};
6 +use crate::{BasicBlock, BlockId, Instruction};
7
8 /// Represents either a React function or a function expression
6 -#[derive(Debug)]
9 pub struct Function<'a> {
10 pub body: HIR<'a>,
11 pub is_async: bool,
@@ -14,14 +16,16 @@ pub struct Function<'a> {
16 /// Blocks are stored in reverse postorder (predecessors before successors)
17 /// so that compiler passes can complete forward data flow analysis in a
18 /// single pass over the CFG in the case where there are no loops.
17 -#[derive(Debug)]
19 pub struct HIR<'a> {
20 /// The id of the first block
21 pub entry: BlockId,
22
23 /// Blocks are stored in a map for easy retrieval by their id,
24 /// but the blocks are in reverse postorder
24 - pub blocks: IndexMap<BlockId, BasicBlock<'a>>,
25 + pub blocks: IndexMap<BlockId, Box<'a, BasicBlock<'a>>>,
26 +
27 + /// All instructions for the block. This may contain unused items,
28 + pub instructions: Vec<'a, Instruction<'a>>,
29 }
30
31 impl<'a> HIR<'a> {
compiler/forget/crates/hir/src/id_types.rs
+21
@@ -87,3 +87,24 @@ pub struct FunctionId(pub(crate) u32);
87 /// Uniquely identifiers a builtin object type in the type registry
88 #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
89 pub struct ObjectId(pub(crate) u32);
90 +
91 +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)]
92 +pub struct InstrIx(pub(crate) u32);
93 +
94 +impl InstrIx {
95 + pub fn new(value: u32) -> Self {
96 + Self(value)
97 + }
98 +}
99 +
100 +impl From<InstrIx> for usize {
101 + fn from(value: InstrIx) -> Self {
102 + value.0 as usize
103 + }
104 +}
105 +
106 +impl Display for InstrIx {
107 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 + f.write_fmt(format_args!("#{}", self.0))
109 + }
110 +}
compiler/forget/crates/hir/src/instruction.rs
+22 -17
@@ -3,12 +3,11 @@ use std::{cell::RefCell, fmt::Display, rc::Rc};
3 use bumpalo::collections::{String, Vec};
4 use estree::BinaryOperator;
5
6 -use crate::{IdentifierId, InstructionId, ScopeId, Type};
6 +use crate::{IdentifierId, InstrIx, InstructionId, ScopeId, Type};
7
8 #[derive(Debug)]
9 pub struct Instruction<'a> {
10 pub id: InstructionId,
11 - pub lvalue: Place<'a>,
11 pub value: InstructionValue<'a>,
12 }
13
@@ -16,7 +15,7 @@ pub struct Instruction<'a> {
15 pub enum InstructionValue<'a> {
16 Array(Array<'a>),
17 // Await(Await<'a>),
19 - Binary(Binary<'a>),
18 + Binary(Binary),
19 // Call(Call<'a>),
20 // ComputedDelete(ComputedDelete<'a>),
21 // ComputedLoad(ComputedLoad<'a>),
@@ -28,7 +27,7 @@ pub enum InstructionValue<'a> {
27 // Function(Function<'a>),
28 // JsxFragment(JsxFragment<'a>),
29 // JsxText(JsxText<'a>),
31 - LoadContext(LoadContext<'a>),
30 + LoadContext(LoadContext),
31 LoadGlobal(LoadGlobal<'a>),
32 LoadLocal(LoadLocal<'a>),
33 // MethodCall(MethodCall<'a>),
@@ -51,20 +50,20 @@ pub enum InstructionValue<'a> {
50
51 #[derive(Debug)]
52 pub struct Array<'a> {
54 - pub elements: Vec<'a, Option<ArrayElement<'a>>>,
53 + pub elements: Vec<'a, Option<ArrayElement>>,
54 }
55
56 #[derive(Debug)]
58 -pub enum ArrayElement<'a> {
59 - Place(Place<'a>),
60 - Spread(Place<'a>),
57 +pub enum ArrayElement {
58 + Place(Operand),
59 + Spread(Operand),
60 }
61
62 #[derive(Debug)]
64 -pub struct Binary<'a> {
65 - pub left: Place<'a>,
63 +pub struct Binary {
64 + pub left: Operand,
65 pub operator: BinaryOperator,
67 - pub right: Place<'a>,
66 + pub right: Operand,
67 }
68
69 #[derive(Debug)]
@@ -100,12 +99,12 @@ impl From<Number> for f64 {
99
100 #[derive(Debug)]
101 pub struct LoadLocal<'a> {
103 - pub place: Place<'a>,
102 + pub place: IdentifierOperand<'a>,
103 }
104
105 #[derive(Debug)]
107 -pub struct LoadContext<'a> {
108 - pub place: Place<'a>,
106 +pub struct LoadContext {
107 + pub place: Operand,
108 }
109
110 #[derive(Debug)]
@@ -126,18 +125,24 @@ pub struct DeclareContext<'a> {
125 #[derive(Debug)]
126 pub struct StoreLocal<'a> {
127 pub lvalue: LValue<'a>,
129 - pub value: Place<'a>,
128 + pub value: Operand,
129 }
130
131 #[derive(Clone, Debug)]
133 -pub struct Place<'a> {
132 +pub struct Operand {
133 + pub ix: InstrIx,
134 + pub effect: Option<Effect>,
135 +}
136 +
137 +#[derive(Clone, Debug)]
138 +pub struct IdentifierOperand<'a> {
139 pub identifier: Identifier<'a>,
140 pub effect: Option<Effect>,
141 }
142
143 #[derive(Debug)]
144 pub struct LValue<'a> {
140 - pub place: Place<'a>,
145 + pub identifier: IdentifierOperand<'a>,
146 pub kind: InstructionKind,
147 }
148
compiler/forget/crates/hir/src/print.rs
+75 -50
@@ -1,8 +1,8 @@
1 use std::fmt::{Result, Write};
2
3 use crate::{
4 - ArrayElement, BasicBlock, Function, Instruction, InstructionValue, LValue, Place,
5 - PrimitiveValue, Terminal, TerminalValue,
4 + ArrayElement, BasicBlock, Function, Identifier, IdentifierOperand, Instruction,
5 + InstructionValue, LValue, Operand, PrimitiveValue, Terminal, TerminalValue, HIR,
6 };
7
8 /// Trait for HIR types to describe how they print themselves.
@@ -10,44 +10,45 @@ use crate::{
10 /// handle things like indentation and maybe wrapping long lines. The
11 /// `pretty` crate seems to have a lot of usage but the type signatures
12 /// are pretty tedious, we can make something much simpler.
13 -pub trait Print {
14 - fn print(&self, out: &mut impl Write) -> Result;
13 +pub trait Print<'a> {
14 + fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result;
15 }
16
17 -impl<'a> Print for Function<'a> {
18 - fn print(&self, out: &mut impl Write) -> Result {
17 +impl<'a> Print<'a> for Function<'a> {
18 + fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
19 writeln!(out, "entry {}", self.body.entry)?;
20 - for block in self.body.blocks.values() {
21 - block.print(out)?;
20 + for (_, block) in self.body.blocks.iter() {
21 + block.print(hir, out)?;
22 }
23 Ok(())
24 }
25 }
26
27 -impl<'a> Print for BasicBlock<'a> {
28 - fn print(&self, out: &mut impl Write) -> Result {
27 +impl<'a> Print<'a> for BasicBlock<'a> {
28 + fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
29 writeln!(out, "{}", self.id)?;
30 - for instr in &self.instructions {
31 - instr.print(out)?;
30 + for ix in &self.instructions {
31 + let instr = &hir.instructions[usize::from(*ix)];
32 + write!(out, " {} {} = ", instr.id, ix)?;
33 + instr.value.print(hir, out)?;
34 + writeln!(out, "")?;
35 }
33 - self.terminal.print(out)?;
36 + self.terminal.print(hir, out)?;
37 Ok(())
38 }
39 }
40
38 -impl<'a> Print for Instruction<'a> {
39 - fn print(&self, out: &mut impl Write) -> Result {
41 +impl<'a> Print<'a> for Instruction<'a> {
42 + fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
43 write!(out, " {} ", self.id)?;
41 - self.lvalue.print(out)?;
42 - write!(out, " = ")?;
43 - self.value.print(out)?;
44 + self.value.print(hir, out)?;
45 writeln!(out, "")?;
46 Ok(())
47 }
48 }
49
49 -impl<'a> Print for InstructionValue<'a> {
50 - fn print(&self, out: &mut impl Write) -> Result {
50 +impl<'a> Print<'a> for InstructionValue<'a> {
51 + fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
52 match self {
53 InstructionValue::Array(value) => {
54 write!(out, "Array [")?;
@@ -56,7 +57,7 @@ impl<'a> Print for InstructionValue<'a> {
57 write!(out, ", ")?;
58 }
59 if let Some(item) = item {
59 - item.print(out)?;
60 + item.print(hir, out)?;
61 } else {
62 write!(out, "<elision>")?;
63 }
@@ -68,7 +69,7 @@ impl<'a> Print for InstructionValue<'a> {
69 }
70 InstructionValue::LoadLocal(value) => {
71 write!(out, "LoadLocal ")?;
71 - value.place.print(out)?;
72 + value.place.print(hir, out)?;
73 }
74 InstructionValue::Primitive(value) => {
75 // Unlike other variants we don't print the variant name ("Primitive") since it's
@@ -86,19 +87,19 @@ impl<'a> Print for InstructionValue<'a> {
87 }
88 InstructionValue::StoreLocal(value) => {
89 write!(out, "StoreLocal ")?;
89 - value.lvalue.print(out)?;
90 + value.lvalue.print(hir, out)?;
91 write!(out, " = ")?;
91 - value.value.print(out)?;
92 + value.value.print(hir, out)?;
93 }
94 InstructionValue::DeclareLocal(value) => {
95 write!(out, "DeclareLocal ")?;
95 - value.lvalue.print(out)?;
96 + value.lvalue.print(hir, out)?;
97 }
98 InstructionValue::Binary(value) => {
99 write!(out, "Binary ")?;
99 - value.left.print(out)?;
100 + value.left.print(hir, out)?;
101 write!(out, " {} ", value.operator)?;
101 - value.right.print(out)?;
102 + value.right.print(hir, out)?;
103 }
104 _ => write!(out, "{:?}", self)?,
105 }
@@ -106,66 +107,90 @@ impl<'a> Print for InstructionValue<'a> {
107 }
108 }
109
109 -impl<'a> Print for ArrayElement<'a> {
110 - fn print(&self, out: &mut impl Write) -> Result {
110 +impl<'a> Print<'a> for ArrayElement {
111 + fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
112 match self {
112 - ArrayElement::Place(place) => place.print(out),
113 + ArrayElement::Place(place) => place.print(hir, out),
114 ArrayElement::Spread(place) => {
115 write!(out, "...")?;
115 - place.print(out)?;
116 + place.print(hir, out)?;
117 Ok(())
118 }
119 }
120 }
121 }
122
122 -impl<'a> Print for Place<'a> {
123 - fn print(&self, out: &mut impl Write) -> Result {
123 +impl<'a> Print<'a> for Operand {
124 + fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
125 write!(
126 out,
126 - "{} {}{}",
127 + "{} {}",
128 match self.effect {
129 Some(effect) => format!("{}", effect),
130 None => "unknown".to_string(),
131 },
131 - match &self.identifier.name {
132 - Some(name) => name.to_string(),
133 - None => "".to_string(),
134 - },
135 - self.identifier.id
132 + self.ix
133 )
134 }
135 }
136
140 -impl<'a> Print for LValue<'a> {
141 - fn print(&self, out: &mut impl Write) -> Result {
137 +impl<'a> Print<'a> for LValue<'a> {
138 + fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
139 write!(out, "{} ", self.kind)?;
143 - self.place.print(out)
140 + self.identifier.print(hir, out)
141 + }
142 +}
143 +
144 +impl<'a> Print<'a> for IdentifierOperand<'a> {
145 + fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
146 + write!(
147 + out,
148 + "{} ",
149 + match self.effect {
150 + Some(effect) => format!("{}", effect),
151 + None => "unknown".to_string(),
152 + },
153 + )?;
154 + self.identifier.print(hir, out)
155 + }
156 +}
157 +
158 +impl<'a> Print<'a> for Identifier<'a> {
159 + fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
160 + write!(
161 + out,
162 + "{}{}",
163 + match &self.name {
164 + Some(name) => name.to_string(),
165 + None => "".to_string(),
166 + },
167 + self.id
168 + )
169 }
170 }
171
147 -impl<'a> Print for Terminal<'a> {
148 - fn print(&self, out: &mut impl Write) -> Result {
172 +impl<'a> Print<'a> for Terminal<'a> {
173 + fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
174 write!(out, " {} ", self.id)?;
150 - self.value.print(out)?;
175 + self.value.print(hir, out)?;
176 writeln!(out, "")?;
177 Ok(())
178 }
179 }
180
156 -impl<'a> Print for TerminalValue<'a> {
157 - fn print(&self, out: &mut impl Write) -> Result {
181 +impl<'a> Print<'a> for TerminalValue<'a> {
182 + fn print(&self, hir: &HIR<'a>, out: &mut impl Write) -> Result {
183 match self {
184 TerminalValue::Return(terminal) => {
185 write!(out, "Return ")?;
161 - terminal.value.print(out)?;
186 + terminal.value.print(hir, out)?;
187 }
188 TerminalValue::Goto(terminal) => {
189 write!(out, "Goto {}", terminal.block)?;
190 }
191 TerminalValue::If(terminal) => {
192 write!(out, "If ")?;
168 - terminal.test.print(out)?;
193 + terminal.test.print(hir, out)?;
194 write!(
195 out,
196 " consequent={} alternate={} fallthrough={}",
@@ -179,7 +204,7 @@ impl<'a> Print for TerminalValue<'a> {
204 }
205 TerminalValue::Branch(terminal) => {
206 write!(out, "Branch ")?;
182 - terminal.test.print(out)?;
207 + terminal.test.print(hir, out)?;
208 write!(
209 out,
210 " consequent={} alternate={}",
compiler/forget/crates/hir/src/terminal.rs
+18 -11
@@ -1,4 +1,4 @@
1 -use crate::{instruction::Place, BlockId, InstructionId};
1 +use crate::{instruction::Operand, BlockId, InstructionId};
2
3 /// Terminals represent statements or expressions that affect control flow,
4 /// such as for-of, if-else, return, logical (??), ternaries (?:), etc.
@@ -10,21 +10,21 @@ pub struct Terminal<'a> {
10
11 #[derive(Debug)]
12 pub enum TerminalValue<'a> {
13 - Branch(BranchTerminal<'a>),
13 + Branch(BranchTerminal),
14 DoWhile(DoWhileTerminal),
15 // ForOf(ForOfTerminal),
16 For(ForTerminal),
17 Goto(GotoTerminal),
18 - If(IfTerminal<'a>),
18 + If(IfTerminal),
19 // Label(LabelTerminal),
20 // Logical(LogicalTerminal),
21 // Optional(OptionalTerminal),
22 - Return(ReturnTerminal<'a>),
22 + Return(ReturnTerminal),
23 // Sequence(SequenceTerminal),
24 // Switch(SwitchTerminal),
25 // Ternary(TernaryTerminal),
26 // Throw(ThrowTerminal),
27 - // Unsupported(UnsupportedTerminal),
27 + Unsupported(UnsupportedTerminal<'a>),
28 // While(WhileTerminal),
29 }
30
@@ -47,6 +47,7 @@ impl<'a> TerminalValue<'a> {
47 let _: BlockId = *fallthrough;
48 }
49 Self::Branch(_) | Self::Goto(_) | Self::Return(_) => {}
50 + Self::Unsupported(_) => panic!("Unexpected unsupported terminal"),
51 }
52 }
53
@@ -70,13 +71,19 @@ impl<'a> TerminalValue<'a> {
71 Self::Return(_) => {
72 vec![]
73 }
74 + Self::Unsupported(_) => panic!("Unexpected unsupported terminal"),
75 }
76 }
77 }
78
79 #[derive(Debug)]
78 -pub struct BranchTerminal<'a> {
79 - pub test: Place<'a>,
80 +pub struct UnsupportedTerminal<'a> {
81 + phantom: std::marker::PhantomData<&'a ()>
82 +}
83 +
84 +#[derive(Debug)]
85 +pub struct BranchTerminal {
86 + pub test: Operand,
87 pub consequent: BlockId,
88 pub alternate: BlockId,
89 }
@@ -101,16 +108,16 @@ pub struct DoWhileTerminal {
108 }
109
110 #[derive(Debug)]
104 -pub struct IfTerminal<'a> {
105 - pub test: Place<'a>,
111 +pub struct IfTerminal {
112 + pub test: Operand,
113 pub consequent: BlockId,
114 pub alternate: BlockId,
115 pub fallthrough: Option<BlockId>,
116 }
117
118 #[derive(Debug)]
112 -pub struct ReturnTerminal<'a> {
113 - pub value: Place<'a>,
119 +pub struct ReturnTerminal {
120 + pub value: Operand,
121 }
122
123 #[derive(Debug)]