From 08e51d3fd72db5f11d9a1dd35480faa5d2305f65 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Thu, 6 Jul 2023 09:24:47 +0900 Subject: [PATCH] [rust] Pretty-printer for HIR Implements a pretty-printer for the HIR and switches the fixture tests to use this instead of the debug format. It's much more readable now! Note that not all types are properly printed, I only implemented the instructions and terminals used in the example. For others we fall back to the Debug impl so we at least print something. --- .../crates/fixtures/tests/fixtures/simple.js | 2 +- .../crates/fixtures/tests/fixtures_test.rs | 15 +- .../snapshots/fixtures_test__fixtures.snap | 564 +----------------- compiler/forget/crates/hir/src/id_types.rs | 20 + compiler/forget/crates/hir/src/instruction.rs | 15 +- compiler/forget/crates/hir/src/lib.rs | 2 + compiler/forget/crates/hir/src/print.rs | 131 ++++ 7 files changed, 192 insertions(+), 557 deletions(-) create mode 100644 compiler/forget/crates/hir/src/print.rs diff --git a/compiler/forget/crates/fixtures/tests/fixtures/simple.js b/compiler/forget/crates/fixtures/tests/fixtures/simple.js index f4c4c26753..a26662756c 100644 --- a/compiler/forget/crates/fixtures/tests/fixtures/simple.js +++ b/compiler/forget/crates/fixtures/tests/fixtures/simple.js @@ -1,4 +1,4 @@ function test() { - [true, false, null, 1, 3.14, "hello world!"]; + [true, false, null, 1, 3.14, ...["hello world!"]]; return 2; } diff --git a/compiler/forget/crates/fixtures/tests/fixtures_test.rs b/compiler/forget/crates/fixtures/tests/fixtures_test.rs index 5271f09bdc..68b1b4dd17 100644 --- a/compiler/forget/crates/fixtures/tests/fixtures_test.rs +++ b/compiler/forget/crates/fixtures/tests/fixtures_test.rs @@ -2,7 +2,7 @@ use build_hir::build; use bumpalo::Bump; use estree::{ModuleItem, Statement}; use estree_swc::parse; -use hir::{Environment, Registry}; +use hir::{Environment, Print, Registry}; use insta::{assert_snapshot, glob}; #[test] @@ -11,9 +11,9 @@ fn fixtures() { let input = std::fs::read_to_string(path).unwrap(); let ast = parse(&input, path.to_str().unwrap()).unwrap(); - let mut output = Vec::new(); + let mut output = String::new(); - for item in ast.body { + for (ix, item) in ast.body.into_iter().enumerate() { if let ModuleItem::Statement(stmt) = item { if let Statement::FunctionDeclaration(fun) = *stmt { let allocator = Bump::new(); @@ -25,12 +25,15 @@ fn fixtures() { Registry, )); let hir = build(&environment, *fun).unwrap(); - output.push(format!("{hir:#?}")); + + if ix != 0 { + output.push_str("\n\n"); + } + hir.print(&mut output).unwrap(); } } } - let joined = output.join("\n\n"); - assert_snapshot!(format!("Input:\n{input}\n\nOutput:\n{joined}")); + assert_snapshot!(format!("Input:\n{input}\n\nOutput:\n{output}")); }); } diff --git a/compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures.snap b/compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures.snap index ad2940ad98..e4a6506fb7 100644 --- a/compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures.snap +++ b/compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures.snap @@ -1,560 +1,26 @@ --- source: crates/fixtures/tests/fixtures_test.rs -expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{joined}\")" +expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")" input_file: crates/fixtures/tests/fixtures/simple.js --- Input: function test() { - [true, false, null, 1, 3.14, "hello world!"]; + [true, false, null, 1, 3.14, ...["hello world!"]]; return 2; } Output: -Function { - body: HIR { - entry: BlockId( - 0, - ), - blocks: { - BlockId( - 0, - ): BasicBlock { - id: BlockId( - 0, - ), - kind: Block, - instructions: [ - Instruction { - id: InstructionId( - 0, - ), - lvalue: Place { - identifier: Identifier { - id: IdentifierId( - 0, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 0, - ), - ), - }, - }, - }, - effect: None, - }, - value: Primitive( - Primitive { - value: Boolean( - true, - ), - }, - ), - }, - Instruction { - id: InstructionId( - 1, - ), - lvalue: Place { - identifier: Identifier { - id: IdentifierId( - 1, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 1, - ), - ), - }, - }, - }, - effect: None, - }, - value: Primitive( - Primitive { - value: Boolean( - false, - ), - }, - ), - }, - Instruction { - id: InstructionId( - 2, - ), - lvalue: Place { - identifier: Identifier { - id: IdentifierId( - 2, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 2, - ), - ), - }, - }, - }, - effect: None, - }, - value: Primitive( - Primitive { - value: Null, - }, - ), - }, - Instruction { - id: InstructionId( - 3, - ), - lvalue: Place { - identifier: Identifier { - id: IdentifierId( - 3, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 3, - ), - ), - }, - }, - }, - effect: None, - }, - value: Primitive( - Primitive { - value: Number( - Number( - 4607182418800017408, - ), - ), - }, - ), - }, - Instruction { - id: InstructionId( - 4, - ), - lvalue: Place { - identifier: Identifier { - id: IdentifierId( - 4, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 4, - ), - ), - }, - }, - }, - effect: None, - }, - value: Primitive( - Primitive { - value: Number( - Number( - 4614253070214989087, - ), - ), - }, - ), - }, - Instruction { - id: InstructionId( - 5, - ), - lvalue: Place { - identifier: Identifier { - id: IdentifierId( - 5, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 5, - ), - ), - }, - }, - }, - effect: None, - }, - value: Primitive( - Primitive { - value: String( - "hello world!", - ), - }, - ), - }, - Instruction { - id: InstructionId( - 6, - ), - lvalue: Place { - identifier: Identifier { - id: IdentifierId( - 6, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 6, - ), - ), - }, - }, - }, - effect: None, - }, - value: Array( - Array { - elements: [ - Place( - Place { - identifier: Identifier { - id: IdentifierId( - 0, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 0, - ), - ), - }, - }, - }, - effect: None, - }, - ), - Place( - Place { - identifier: Identifier { - id: IdentifierId( - 1, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 1, - ), - ), - }, - }, - }, - effect: None, - }, - ), - Place( - Place { - identifier: Identifier { - id: IdentifierId( - 2, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 2, - ), - ), - }, - }, - }, - effect: None, - }, - ), - Place( - Place { - identifier: Identifier { - id: IdentifierId( - 3, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 3, - ), - ), - }, - }, - }, - effect: None, - }, - ), - Place( - Place { - identifier: Identifier { - id: IdentifierId( - 4, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 4, - ), - ), - }, - }, - }, - effect: None, - }, - ), - Place( - Place { - identifier: Identifier { - id: IdentifierId( - 5, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 5, - ), - ), - }, - }, - }, - effect: None, - }, - ), - ], - }, - ), - }, - Instruction { - id: InstructionId( - 7, - ), - lvalue: Place { - identifier: Identifier { - id: IdentifierId( - 7, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 7, - ), - ), - }, - }, - }, - effect: None, - }, - value: Primitive( - Primitive { - value: Number( - Number( - 4611686018427387904, - ), - ), - }, - ), - }, - ], - terminal: Terminal { - id: InstructionId( - 8, - ), - value: ReturnTerminal( - ReturnTerminal { - value: Place { - identifier: Identifier { - id: IdentifierId( - 7, - ), - name: None, - data: RefCell { - value: IdentifierData { - mutable_range: MutableRange { - start: InstructionId( - 0, - ), - end: InstructionId( - 0, - ), - }, - scope: None, - type_: Var( - TypeVarId( - 7, - ), - ), - }, - }, - }, - effect: None, - }, - }, - ), - }, - predecessors: {}, - }, - }, - }, - is_async: false, - is_generator: false, -} +entry bb0 +bb0 + [0] true + [1] false + [2] null + [3] 1 + [4] 3.14 + [5] "hello world!" + [6] Array [unknown $5] + [7] Array [unknown $0, unknown $1, unknown $2, unknown $3, unknown $4, ...unknown $6] + [8] 2 + [9] Return unknown $8 + diff --git a/compiler/forget/crates/hir/src/id_types.rs b/compiler/forget/crates/hir/src/id_types.rs index 9c3a355d87..49e8f3165b 100644 --- a/compiler/forget/crates/hir/src/id_types.rs +++ b/compiler/forget/crates/hir/src/id_types.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + /// Unique identifier for a basic block. Values are unique only with respect to /// a single top-level function, and may be reused across different top-level /// functions. Notably, ids *are* unique across the basic blocks of a function @@ -12,6 +14,12 @@ impl BlockId { } } +impl Display for BlockId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("bb{}", self.0)) + } +} + /// Unique identifier for a variable within a program. This is used to distinguish /// different instances of a variable with the same name in different scopes, or /// even the same named identifier across reassignments (when in SSA form). @@ -24,6 +32,12 @@ impl IdentifierId { } } +impl Display for IdentifierId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("${}", self.0)) + } +} + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)] pub struct TypeVarId(pub(crate) u32); @@ -42,6 +56,12 @@ impl TypeVarId { #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Hash, Debug)] pub struct InstructionId(pub(crate) u32); +impl Display for InstructionId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("[{}]", self.0)) + } +} + pub struct InstructionIdGenerator(u32); impl InstructionIdGenerator { diff --git a/compiler/forget/crates/hir/src/instruction.rs b/compiler/forget/crates/hir/src/instruction.rs index 683aea1af8..650ba2e10e 100644 --- a/compiler/forget/crates/hir/src/instruction.rs +++ b/compiler/forget/crates/hir/src/instruction.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, fmt::Display, rc::Rc}; use bumpalo::collections::{String, Vec}; @@ -176,6 +176,19 @@ impl Effect { } } +impl Display for Effect { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Effect::Capture => "capture", + Effect::ConditionallyMutate => "mutate?", + Effect::Freeze => "freeze", + Effect::Mutate => "mutate", + Effect::Read => "read", + Effect::Store => "store", + }) + } +} + #[derive(Clone, Debug)] pub struct Identifier<'a> { /// Uniquely identifiers this identifier diff --git a/compiler/forget/crates/hir/src/lib.rs b/compiler/forget/crates/hir/src/lib.rs index 85146bb1d3..9c81353c99 100644 --- a/compiler/forget/crates/hir/src/lib.rs +++ b/compiler/forget/crates/hir/src/lib.rs @@ -4,6 +4,7 @@ mod features; mod function; mod id_types; mod instruction; +mod print; mod registry; mod terminal; mod types; @@ -14,6 +15,7 @@ pub use features::*; pub use function::*; pub use id_types::*; pub use instruction::*; +pub use print::Print; pub use registry::Registry; pub use terminal::*; pub use types::*; diff --git a/compiler/forget/crates/hir/src/print.rs b/compiler/forget/crates/hir/src/print.rs new file mode 100644 index 0000000000..e931c0f832 --- /dev/null +++ b/compiler/forget/crates/hir/src/print.rs @@ -0,0 +1,131 @@ +use std::fmt::{Result, Write}; + +use crate::{ + ArrayElement, BasicBlock, Function, Instruction, InstructionValue, Place, PrimitiveValue, + Terminal, TerminalValue, +}; + +/// Trait for HIR types to describe how they print themselves. +/// Eventually we should add a higher-level abstraction for printing to +/// handle things like indentation and maybe wrapping long lines. The +/// `pretty` crate seems to have a lot of usage but the type signatures +/// are pretty tedious, we can make something much simpler. +pub trait Print { + fn print(&self, out: &mut impl Write) -> Result; +} + +impl<'a> Print for Function<'a> { + fn print(&self, out: &mut impl Write) -> Result { + writeln!(out, "entry {}", self.body.entry)?; + for block in self.body.blocks.values() { + block.print(out)?; + } + Ok(()) + } +} + +impl<'a> Print for BasicBlock<'a> { + fn print(&self, out: &mut impl Write) -> Result { + writeln!(out, "{}", self.id)?; + for instr in &self.instructions { + instr.print(out)?; + } + self.terminal.print(out)?; + Ok(()) + } +} + +impl<'a> Print for Instruction<'a> { + fn print(&self, out: &mut impl Write) -> Result { + write!(out, " {} ", self.id)?; + self.value.print(out)?; + writeln!(out, "")?; + Ok(()) + } +} + +impl<'a> Print for InstructionValue<'a> { + fn print(&self, out: &mut impl Write) -> Result { + match self { + InstructionValue::Array(value) => { + write!(out, "Array [")?; + for (ix, item) in value.elements.iter().enumerate() { + if ix != 0 { + write!(out, ", ")?; + } + item.print(out)?; + } + write!(out, "]")?; + } + InstructionValue::Primitive(value) => { + // Unlike other variants we don't print the variant name ("Primitive") since it's + // obvious + match &value.value { + PrimitiveValue::Boolean(value) => write!(out, "{}", value)?, + PrimitiveValue::Null => write!(out, "null")?, + PrimitiveValue::Number(value) => write!(out, "{}", f64::from(*value))?, + + // TODO: quote the string itself (JS version uses JSON.stringify()) + PrimitiveValue::String(value) => write!(out, "\"{}\"", value.as_str())?, + + PrimitiveValue::Undefined => write!(out, "")?, + }; + } + _ => write!(out, "{:?}", self)?, + } + Ok(()) + } +} + +impl<'a> Print for ArrayElement<'a> { + fn print(&self, out: &mut impl Write) -> Result { + match self { + ArrayElement::Place(place) => place.print(out), + ArrayElement::Spread(place) => { + write!(out, "...")?; + place.print(out)?; + Ok(()) + } + } + } +} + +impl<'a> Print for Place<'a> { + fn print(&self, out: &mut impl Write) -> Result { + write!( + out, + "{} {}{}", + match self.effect { + Some(effect) => format!("{}", effect), + None => "unknown".to_string(), + }, + match &self.identifier.name { + Some(name) => name.to_string(), + None => "".to_string(), + }, + self.identifier.id + ) + } +} + +impl<'a> Print for Terminal<'a> { + fn print(&self, out: &mut impl Write) -> Result { + write!(out, " {} ", self.id)?; + self.value.print(out)?; + writeln!(out, "")?; + Ok(()) + } +} + +impl<'a> Print for TerminalValue<'a> { + fn print(&self, out: &mut impl Write) -> Result { + match self { + TerminalValue::ReturnTerminal(terminal) => { + write!(out, "Return ")?; + terminal.value.print(out)?; + } + _ => write!(out, "{:?}", self)?, + } + Ok(()) + } +}