From 1f23d67f0828067f72062ce6ebcaa198ba696be4 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 24 Apr 2023 14:38:37 -0500 Subject: [PATCH] Implement debug printing for the new ssa ir --- crates/noirc_evaluator/src/ssa_refactor/ir.rs | 1 + .../src/ssa_refactor/ir/basic_block.rs | 22 ++++ .../src/ssa_refactor/ir/dfg.rs | 30 ++++- .../src/ssa_refactor/ir/function.rs | 15 ++- .../src/ssa_refactor/ir/instruction.rs | 21 +++- .../src/ssa_refactor/ir/map.rs | 6 + .../src/ssa_refactor/ir/printer.rs | 115 ++++++++++++++++++ .../src/ssa_refactor/ir/types.rs | 21 ++++ .../ssa_builder/function_builder.rs | 8 +- .../src/ssa_refactor/ssa_gen/context.rs | 7 +- .../src/ssa_refactor/ssa_gen/mod.rs | 5 +- 11 files changed, 237 insertions(+), 14 deletions(-) create mode 100644 crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir.rs b/crates/noirc_evaluator/src/ssa_refactor/ir.rs index 96b00293b9..851b86e511 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir.rs @@ -4,5 +4,6 @@ pub(crate) mod dfg; pub(crate) mod function; pub(crate) mod instruction; pub(crate) mod map; +pub(crate) mod printer; pub(crate) mod types; pub(crate) mod value; diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs index 431f164786..13d1b3ca6f 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs @@ -52,7 +52,29 @@ impl BasicBlock { self.instructions.push(instruction); } + pub(crate) fn instructions(&self) -> &[InstructionId] { + &self.instructions + } + pub(crate) fn set_terminator(&mut self, terminator: TerminatorInstruction) { self.terminator = Some(terminator); } + + pub(crate) fn terminator(&self) -> Option<&TerminatorInstruction> { + self.terminator.as_ref() + } + + /// Iterate over all the successors of the currently block, as determined by + /// the blocks jumped to in the terminator instruction. If there is no terminator + /// instruction yet, this will iterate 0 times. + pub(crate) fn successors(&self) -> impl ExactSizeIterator { + match &self.terminator { + Some(TerminatorInstruction::Jmp { destination, .. }) => vec![*destination].into_iter(), + Some(TerminatorInstruction::JmpIf { then_destination, else_destination, .. }) => { + vec![*then_destination, *else_destination].into_iter() + } + Some(TerminatorInstruction::Return { .. }) => vec![].into_iter(), + None => vec![].into_iter(), + } + } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index 567306a6a5..c5ee637ce0 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -1,6 +1,6 @@ use super::{ basic_block::{BasicBlock, BasicBlockId}, - constant::NumericConstant, + constant::{NumericConstant, NumericConstantId}, function::Signature, instruction::{Instruction, InstructionId}, map::{DenseMap, Id, SecondaryMap, TwoWayMap}, @@ -213,6 +213,34 @@ impl DataFlowGraph { } } +impl std::ops::Index for DataFlowGraph { + type Output = Instruction; + fn index(&self, id: InstructionId) -> &Self::Output { + &self.instructions[id] + } +} + +impl std::ops::Index for DataFlowGraph { + type Output = Value; + fn index(&self, id: ValueId) -> &Self::Output { + &self.values[id] + } +} + +impl std::ops::Index for DataFlowGraph { + type Output = NumericConstant; + fn index(&self, id: NumericConstantId) -> &Self::Output { + &self.constants[id] + } +} + +impl std::ops::Index for DataFlowGraph { + type Output = BasicBlock; + fn index(&self, id: BasicBlockId) -> &Self::Output { + &self.blocks[id] + } +} + #[cfg(test)] mod tests { use super::DataFlowGraph; diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs index 1abd6c8536..63cd31142c 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs @@ -18,7 +18,10 @@ pub(crate) struct Function { source_locations: SecondaryMap, /// The first basic block in the function - entry_block: BasicBlockId, + pub(super) entry_block: BasicBlockId, + + /// Name of the function for debugging only + pub(super) name: String, pub(crate) dfg: DataFlowGraph, } @@ -27,10 +30,10 @@ impl Function { /// Creates a new function with an automatically inserted entry block. /// /// Note that any parameters to the function must be manually added later. - pub(crate) fn new() -> Self { + pub(crate) fn new(name: String) -> Self { let mut dfg = DataFlowGraph::default(); let entry_block = dfg.new_block(); - Self { source_locations: SecondaryMap::new(), entry_block, dfg } + Self { name, source_locations: SecondaryMap::new(), entry_block, dfg } } pub(crate) fn entry_block(&self) -> BasicBlockId { @@ -47,6 +50,12 @@ pub(crate) struct Signature { pub(crate) returns: Vec, } +impl std::fmt::Display for Function { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + super::printer::display_function(self, f) + } +} + #[test] fn sign_smoke() { let mut signature = Signature::default(); diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs index 86d59b3ed1..442f1dbd47 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs @@ -15,6 +15,12 @@ pub(crate) type InstructionId = Id; /// of this is println. pub(crate) struct IntrinsicOpcodes; +impl std::fmt::Display for IntrinsicOpcodes { + fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!("intrinsics have no opcodes yet") + } +} + #[derive(Debug, PartialEq, Eq, Hash, Clone)] /// Instructions are used to perform tasks. /// The instructions that the IR is able to specify are listed below. @@ -189,5 +195,18 @@ pub(crate) enum BinaryOp { /// Checks whether two types are equal. /// Returns true if the types were not equal and /// false otherwise. - Ne, + Neq, +} + +impl std::fmt::Display for BinaryOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BinaryOp::Add => write!(f, "add"), + BinaryOp::Sub => write!(f, "sub"), + BinaryOp::Mul => write!(f, "mul"), + BinaryOp::Div => write!(f, "div"), + BinaryOp::Eq => write!(f, "eq"), + BinaryOp::Neq => write!(f, "neq"), + } + } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs index e7e678552a..bb526076e3 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs @@ -69,6 +69,12 @@ impl std::fmt::Debug for Id { } } +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "${}", self.index) + } +} + /// A DenseMap is a Vec wrapper where each element corresponds /// to a unique ID that can be used to access the element. No direct /// access to indices is provided. Since IDs must be stable and correspond diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs new file mode 100644 index 0000000000..1a7737e97b --- /dev/null +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs @@ -0,0 +1,115 @@ +//! This file is for pretty-printing the SSA IR in a human-readable form for debugging. +use std::fmt::{Formatter, Result}; + +use iter_extended::vecmap; + +use super::{ + basic_block::BasicBlockId, + function::Function, + instruction::{Instruction, InstructionId, TerminatorInstruction}, + value::ValueId, +}; + +pub(crate) fn display_function(function: &Function, f: &mut Formatter) -> Result { + writeln!(f, "fn {} {{", function.name)?; + display_block_with_successors(function, function.entry_block, f)?; + write!(f, "}}") +} + +pub(crate) fn display_block_with_successors( + function: &Function, + block_id: BasicBlockId, + f: &mut Formatter, +) -> Result { + display_block(function, block_id, f)?; + + for successor in function.dfg[block_id].successors() { + display_block(function, successor, f)?; + } + Ok(()) +} + +pub(crate) fn display_block( + function: &Function, + block_id: BasicBlockId, + f: &mut Formatter, +) -> Result { + let block = &function.dfg[block_id]; + + writeln!(f, "{}({}):", block_id, value_list(block.parameters()))?; + + for instruction in block.instructions() { + display_instruction(function, *instruction, f)?; + } + + display_terminator(block.terminator(), f) +} + +fn value_list(values: &[ValueId]) -> String { + vecmap(values, ToString::to_string).join(", ") +} + +pub(crate) fn display_terminator( + terminator: Option<&TerminatorInstruction>, + f: &mut Formatter, +) -> Result { + match terminator { + Some(TerminatorInstruction::Jmp { destination, arguments }) => { + writeln!(f, " jmp {}({})", destination, value_list(arguments)) + } + Some(TerminatorInstruction::JmpIf { + condition, + arguments, + then_destination, + else_destination, + }) => { + let args = value_list(arguments); + writeln!( + f, + " jmpif {}({}) then: {}, else: {}", + condition, args, then_destination, else_destination + ) + } + Some(TerminatorInstruction::Return { return_values }) => { + writeln!(f, " return {}", value_list(return_values)) + } + None => writeln!(f, " (no terminator instruction)"), + } +} + +pub(crate) fn display_instruction( + function: &Function, + instruction: InstructionId, + f: &mut Formatter, +) -> Result { + // instructions are always indented within a function + write!(f, " ")?; + + let results = function.dfg.instruction_results(instruction); + if !results.is_empty() { + write!(f, "{} = ", value_list(results))?; + } + + match &function.dfg[instruction] { + Instruction::Binary(binary) => { + writeln!(f, "{} {}, {}", binary.operator, binary.lhs, binary.rhs) + } + Instruction::Cast(value, typ) => writeln!(f, "cast {value} as {typ}"), + Instruction::Not(value) => writeln!(f, "not {value}"), + Instruction::Truncate { value, bit_size, max_bit_size } => { + writeln!(f, "truncate {value} to {bit_size} bits, max_bit_size: {max_bit_size}") + } + Instruction::Constrain(value) => { + writeln!(f, "constrain {value}") + } + Instruction::Call { func, arguments } => { + writeln!(f, "call {func}({})", value_list(arguments)) + } + Instruction::Intrinsic { func, arguments } => { + writeln!(f, "intrinsic {func}({})", value_list(arguments)) + } + Instruction::Allocate { size } => writeln!(f, "alloc {size} fields"), + Instruction::Load { address } => writeln!(f, "load {address}"), + Instruction::Store { address, value } => writeln!(f, "store {value} at {address}"), + } +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs index e1f8e8a74d..888d7d128d 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/types.rs @@ -42,3 +42,24 @@ impl Type { Type::Numeric(NumericType::NativeField) } } + +impl std::fmt::Display for Type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Type::Numeric(numeric) => numeric.fmt(f), + Type::Reference => write!(f, "reference"), + Type::Function => write!(f, "function"), + Type::Unit => write!(f, "unit"), + } + } +} + +impl std::fmt::Display for NumericType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + NumericType::Signed { bit_size } => write!(f, "i{bit_size}"), + NumericType::Unsigned { bit_size } => write!(f, "u{bit_size}"), + NumericType::NativeField => write!(f, "Field"), + } + } +} diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs index 7854c3b14a..9264c0b5c5 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/function_builder.rs @@ -29,8 +29,8 @@ pub(crate) struct FunctionBuilder<'ssa> { } impl<'ssa> FunctionBuilder<'ssa> { - pub(crate) fn new(context: &'ssa SharedBuilderContext) -> Self { - let new_function = Function::new(); + pub(crate) fn new(function_name: String, context: &'ssa SharedBuilderContext) -> Self { + let new_function = Function::new(function_name); let current_block = new_function.entry_block(); Self { @@ -43,8 +43,8 @@ impl<'ssa> FunctionBuilder<'ssa> { } /// Finish the current function and create a new function - pub(crate) fn new_function(&mut self) { - let new_function = Function::new(); + pub(crate) fn new_function(&mut self, name: String) { + let new_function = Function::new(name); let old_function = std::mem::replace(&mut self.current_function, new_function); self.finished_functions.push((self.current_function_id, old_function)); diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs index 58de1dd551..32133feea1 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs @@ -32,22 +32,23 @@ pub(super) struct SharedContext { impl<'a> FunctionContext<'a> { pub(super) fn new( + function_name: String, parameters: &Parameters, shared_context: &'a SharedContext, shared_builder_context: &'a SharedBuilderContext, ) -> Self { let mut this = Self { definitions: HashMap::new(), - builder: FunctionBuilder::new(shared_builder_context), + builder: FunctionBuilder::new(function_name, shared_builder_context), shared_context, }; this.add_parameters_to_scope(parameters); this } - pub(super) fn new_function(&mut self, parameters: &Parameters) { + pub(super) fn new_function(&mut self, name: String, parameters: &Parameters) { self.definitions.clear(); - self.builder.new_function(); + self.builder.new_function(name); self.add_parameters_to_scope(parameters); } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs index d335d912be..2f9c664628 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -19,14 +19,15 @@ pub(crate) fn generate_ssa(program: Program) { let builder_context = SharedBuilderContext::default(); let main = context.program.main(); + let mut function_context = + FunctionContext::new(main.name.clone(), &main.parameters, &context, &builder_context); - let mut function_context = FunctionContext::new(&main.parameters, &context, &builder_context); function_context.codegen_expression(&main.body); while let Some((src_function_id, _new_id)) = context.pop_next_function_in_queue() { let function = &context.program[src_function_id]; // TODO: Need to ensure/assert the new function's id == new_id - function_context.new_function(&function.parameters); + function_context.new_function(function.name.clone(), &function.parameters); function_context.codegen_expression(&function.body); } }