Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(ssa refactor): Add debug printing for the new ssa ir #1211

Merged
merged 1 commit into from
Apr 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
22 changes: 22 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ir/basic_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item = BasicBlockId> {
jfecher marked this conversation as resolved.
Show resolved Hide resolved
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(),
}
}
}
30 changes: 29 additions & 1 deletion crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -213,6 +213,34 @@ impl DataFlowGraph {
}
}

impl std::ops::Index<InstructionId> for DataFlowGraph {
type Output = Instruction;
fn index(&self, id: InstructionId) -> &Self::Output {
&self.instructions[id]
}
}

impl std::ops::Index<ValueId> for DataFlowGraph {
type Output = Value;
fn index(&self, id: ValueId) -> &Self::Output {
&self.values[id]
}
}

impl std::ops::Index<NumericConstantId> for DataFlowGraph {
type Output = NumericConstant;
fn index(&self, id: NumericConstantId) -> &Self::Output {
&self.constants[id]
}
}

impl std::ops::Index<BasicBlockId> for DataFlowGraph {
type Output = BasicBlock;
fn index(&self, id: BasicBlockId) -> &Self::Output {
&self.blocks[id]
}
}

#[cfg(test)]
mod tests {
use super::DataFlowGraph;
Expand Down
15 changes: 12 additions & 3 deletions crates/noirc_evaluator/src/ssa_refactor/ir/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ pub(crate) struct Function {
source_locations: SecondaryMap<Instruction, Location>,

/// 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,
}
Expand All @@ -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 {
Expand All @@ -47,6 +50,12 @@ pub(crate) struct Signature {
pub(crate) returns: Vec<Type>,
}

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();
Expand Down
21 changes: 20 additions & 1 deletion crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ pub(crate) type InstructionId = Id<Instruction>;
/// 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.
Expand Down Expand Up @@ -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"),
}
}
}
6 changes: 6 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ir/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ impl<T> std::fmt::Debug for Id<T> {
}
}

impl<T> std::fmt::Display for Id<T> {
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
Expand Down
115 changes: 115 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs
Original file line number Diff line number Diff line change
@@ -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}"),
}
}
21 changes: 21 additions & 0 deletions crates/noirc_evaluator/src/ssa_refactor/ir/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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));
Expand Down
7 changes: 4 additions & 3 deletions crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
5 changes: 3 additions & 2 deletions crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down