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): Implement ssa-gen for indexing, cast, constrain, if, unary #1225

Merged
merged 2 commits into from
Apr 26, 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
23 changes: 10 additions & 13 deletions crates/noirc_evaluator/src/ssa_refactor/ir/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,29 +133,27 @@ mod tests {
// Build function of form
// fn func {
// block0(cond: u1):
// jmpif cond(), then: block2, else: block1
// jmpif cond, then: block2, else: block1
// block1():
// jmpif cond(), then: block1, else: block2
// jmpif cond, then: block1, else: block2
// block2():
// return
// return ()
// }
let mut func = Function::new("func".into());
let block0_id = func.entry_block();
let cond = func.dfg.add_block_parameter(block0_id, Type::unsigned(1));
let block1_id = func.dfg.new_block();
let block2_id = func.dfg.new_block();
let block1_id = func.dfg.make_block();
let block2_id = func.dfg.make_block();

func.dfg[block0_id].set_terminator(TerminatorInstruction::JmpIf {
condition: cond,
then_destination: block2_id,
else_destination: block1_id,
arguments: vec![],
});
func.dfg[block1_id].set_terminator(TerminatorInstruction::JmpIf {
condition: cond,
then_destination: block1_id,
else_destination: block2_id,
arguments: vec![],
});
func.dfg[block2_id].set_terminator(TerminatorInstruction::Return { return_values: vec![] });

Expand Down Expand Up @@ -192,15 +190,15 @@ mod tests {
// Modify function to form:
// fn func {
// block0(cond: u1):
// jmpif cond(), then: block1, else: ret_block
// jmpif cond, then: block1, else: ret_block
// block1():
// jmpif cond(), then: block1, else: block2
// jmpif cond, then: block1, else: block2
// block2():
// jmp ret_block
// jmp ret_block()
// ret_block():
// return
// return ()
// }
let ret_block_id = func.dfg.new_block();
let ret_block_id = func.dfg.make_block();
func.dfg[ret_block_id]
.set_terminator(TerminatorInstruction::Return { return_values: vec![] });
func.dfg[block2_id].set_terminator(TerminatorInstruction::Jmp {
Expand All @@ -211,7 +209,6 @@ mod tests {
condition: cond,
then_destination: block1_id,
else_destination: ret_block_id,
arguments: vec![],
});

// Recompute new and changed blocks
Expand Down
4 changes: 2 additions & 2 deletions crates/noirc_evaluator/src/ssa_refactor/ir/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ impl NumericConstant {
Self(value)
}

pub(crate) fn value(&self) -> &FieldElement {
&self.0
pub(crate) fn value(&self) -> FieldElement {
self.0
}
}

Expand Down
24 changes: 21 additions & 3 deletions crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::{
basic_block::{BasicBlock, BasicBlockId},
constant::{NumericConstant, NumericConstantId},
function::Signature,
instruction::{Instruction, InstructionId, InstructionResultType},
instruction::{Instruction, InstructionId, InstructionResultType, TerminatorInstruction},
map::{DenseMap, Id, SecondaryMap, TwoWayMap},
types::Type,
value::{Value, ValueId},
Expand Down Expand Up @@ -75,14 +75,14 @@ impl DataFlowGraph {
/// Creates a new basic block with no parameters.
/// After being created, the block is unreachable in the current function
/// until another block is made to jump to it.
pub(crate) fn new_block(&mut self) -> BasicBlockId {
pub(crate) fn make_block(&mut self) -> BasicBlockId {
self.blocks.insert(BasicBlock::new(Vec::new()))
}

/// Creates a new basic block with the given parameters.
/// After being created, the block is unreachable in the current function
/// until another block is made to jump to it.
pub(crate) fn new_block_with_parameters(
pub(crate) fn make_block_with_parameters(
&mut self,
parameter_types: impl Iterator<Item = Type>,
) -> BasicBlockId {
Expand Down Expand Up @@ -230,6 +230,24 @@ impl DataFlowGraph {
) {
self.blocks[block].insert_instruction(instruction);
}

/// Returns the field element represented by this value if it is a numeric constant.
/// Returns None if the given value is not a numeric constant.
pub(crate) fn get_numeric_constant(&self, value: Id<Value>) -> Option<FieldElement> {
match self.values[value] {
Value::NumericConstant { constant, .. } => Some(self[constant].value()),
_ => None,
}
}

/// Sets the terminator instruction for the given basic block
pub(crate) fn set_block_terminator(
&mut self,
block: BasicBlockId,
terminator: TerminatorInstruction,
) {
self.blocks[block].set_terminator(terminator);
}
}

impl std::ops::Index<InstructionId> for DataFlowGraph {
Expand Down
2 changes: 1 addition & 1 deletion crates/noirc_evaluator/src/ssa_refactor/ir/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl Function {
/// Note that any parameters to the function must be manually added later.
pub(crate) fn new(name: String) -> Self {
let mut dfg = DataFlowGraph::default();
let entry_block = dfg.new_block();
let entry_block = dfg.make_block();
Self { name, source_locations: SecondaryMap::new(), entry_block, dfg }
}

Expand Down
11 changes: 3 additions & 8 deletions crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,14 +150,9 @@ pub(crate) enum TerminatorInstruction {
///
/// Jump If
///
/// If the condition is true: jump to the specified `then_destination` with `arguments`.
/// Otherwise, jump to the specified `else_destination` with `arguments`.
JmpIf {
condition: ValueId,
then_destination: BasicBlockId,
else_destination: BasicBlockId,
arguments: Vec<ValueId>,
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
},
/// If the condition is true: jump to the specified `then_destination`.
/// Otherwise, jump to the specified `else_destination`.
JmpIf { condition: ValueId, then_destination: BasicBlockId, else_destination: BasicBlockId },

/// Unconditional Jump
///
Expand Down
12 changes: 3 additions & 9 deletions crates/noirc_evaluator/src/ssa_refactor/ir/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,11 @@ pub(crate) fn display_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);
Some(TerminatorInstruction::JmpIf { condition, then_destination, else_destination }) => {
writeln!(
f,
" jmpif {}({}) then: {}, else: {}",
condition, args, then_destination, else_destination
" jmpif {} then: {}, else: {}",
condition, then_destination, else_destination
)
}
Some(TerminatorInstruction::Return { return_values }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use acvm::FieldElement;
use crate::ssa_refactor::ir::{
basic_block::BasicBlockId,
function::{Function, FunctionId},
instruction::{Binary, BinaryOp, Instruction},
instruction::{Binary, BinaryOp, Instruction, TerminatorInstruction},
types::Type,
value::ValueId,
};
Expand Down Expand Up @@ -62,15 +62,32 @@ impl<'ssa> FunctionBuilder<'ssa> {
}

/// Insert a numeric constant into the current function
pub(crate) fn numeric_constant(&mut self, value: FieldElement, typ: Type) -> ValueId {
self.current_function.dfg.make_constant(value, typ)
pub(crate) fn numeric_constant(
&mut self,
value: impl Into<FieldElement>,
typ: Type,
) -> ValueId {
self.current_function.dfg.make_constant(value.into(), typ)
}

/// Insert a numeric constant into the current function of type Field
pub(crate) fn field_constant(&mut self, value: impl Into<FieldElement>) -> ValueId {
self.numeric_constant(value.into(), Type::field())
}

pub(crate) fn type_of_value(&self, value: ValueId) -> Type {
self.current_function.dfg.type_of_value(value)
}

pub(crate) fn insert_block(&mut self) -> BasicBlockId {
self.current_function.dfg.make_block()
}

pub(crate) fn add_block_parameter(&mut self, block: BasicBlockId, typ: Type) -> ValueId {
self.current_function.dfg.add_block_parameter(block, typ)
}

/// Inserts a new instruction at the end of the current block and returns its results
fn insert_instruction(
&mut self,
instruction: Instruction,
Expand All @@ -81,23 +98,45 @@ impl<'ssa> FunctionBuilder<'ssa> {
self.current_function.dfg.instruction_results(id)
}

/// Switch to inserting instructions in the given block.
/// Expects the given block to be within the same function. If you want to insert
/// instructions into a new function, call new_function instead.
pub(crate) fn switch_to_block(&mut self, block: BasicBlockId) {
self.current_block = block;
}

/// Insert an allocate instruction at the end of the current block, allocating the
/// given amount of field elements. Returns the result of the allocate instruction,
/// which is always a Reference to the allocated data.
pub(crate) fn insert_allocate(&mut self, size_to_allocate: u32) -> ValueId {
self.insert_instruction(Instruction::Allocate { size: size_to_allocate }, None)[0]
}

/// Insert a Load instruction at the end of the current block, loading from the given address
/// which should point to a previous Allocate instruction. Note that this is limited to loading
/// a single value. Loading multiple values (such as a tuple) will require multiple loads.
/// Insert a Load instruction at the end of the current block, loading from the given offset
/// of the given address which should point to a previous Allocate instruction. Note that
/// this is limited to loading a single value. Loading multiple values (such as a tuple)
/// will require multiple loads.
/// 'offset' is in units of FieldElements here. So loading the fourth FieldElement stored in
/// an array will have an offset of 3.
/// Returns the element that was loaded.
pub(crate) fn insert_load(&mut self, address: ValueId, type_to_load: Type) -> ValueId {
pub(crate) fn insert_load(
&mut self,
mut address: ValueId,
offset: ValueId,
type_to_load: Type,
) -> ValueId {
if let Some(offset) = self.current_function.dfg.get_numeric_constant(offset) {
if !offset.is_zero() {
let offset = self.field_constant(offset);
address = self.insert_binary(address, BinaryOp::Add, offset);
}
};
self.insert_instruction(Instruction::Load { address }, Some(vec![type_to_load]))[0]
}

/// Insert a Store instruction at the end of the current block, storing the given element
/// at the given address. Expects that the address points to a previous Allocate instruction.
/// at the given address. Expects that the address points somewhere
/// within a previous Allocate instruction.
pub(crate) fn insert_store(&mut self, address: ValueId, value: ValueId) {
self.insert_instruction(Instruction::Store { address, value }, None);
}
Expand All @@ -119,4 +158,50 @@ impl<'ssa> FunctionBuilder<'ssa> {
pub(crate) fn insert_not(&mut self, rhs: ValueId) -> ValueId {
self.insert_instruction(Instruction::Not(rhs), None)[0]
}

/// Insert a cast instruction at the end of the current block.
/// Returns the result of the cast instruction.
pub(crate) fn insert_cast(&mut self, value: ValueId, typ: Type) -> ValueId {
self.insert_instruction(Instruction::Cast(value, typ), None)[0]
}

/// Insert a constrain instruction at the end of the current block.
pub(crate) fn insert_constrain(&mut self, boolean: ValueId) {
self.insert_instruction(Instruction::Constrain(boolean), None);
}

/// Terminates the current block with the given terminator instruction
fn terminate_block_with(&mut self, terminator: TerminatorInstruction) {
self.current_function.dfg.set_block_terminator(self.current_block, terminator);
}

/// Terminate the current block with a jmp instruction to jmp to the given
/// block with the given arguments.
pub(crate) fn terminate_with_jmp(
&mut self,
destination: BasicBlockId,
arguments: Vec<ValueId>,
) {
self.terminate_block_with(TerminatorInstruction::Jmp { destination, arguments });
}

/// Terminate the current block with a jmpif instruction to jmp with the given arguments
/// block with the given arguments.
pub(crate) fn terminate_with_jmpif(
&mut self,
condition: ValueId,
then_destination: BasicBlockId,
else_destination: BasicBlockId,
) {
self.terminate_block_with(TerminatorInstruction::JmpIf {
condition,
then_destination,
else_destination,
});
}

/// Terminate the current block with a return instruction
pub(crate) fn terminate_with_return(&mut self, return_values: Vec<ValueId>) {
self.terminate_block_with(TerminatorInstruction::Return { return_values });
}
}
11 changes: 10 additions & 1 deletion crates/noirc_evaluator/src/ssa_refactor/ssa_gen/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ impl<'a> FunctionContext<'a> {
/// Insert a unit constant into the current function if not already
/// present, and return its value
pub(super) fn unit_value(&mut self) -> Values {
self.builder.numeric_constant(0u128.into(), Type::Unit).into()
self.builder.numeric_constant(0u128, Type::Unit).into()
}

/// Insert a binary instruction at the end of the current block.
Expand All @@ -155,6 +155,15 @@ impl<'a> FunctionContext<'a> {
}
result.into()
}

/// Create a const offset of an address for an array load or store
pub(super) fn make_offset(&mut self, mut address: ValueId, offset: u128) -> ValueId {
if offset != 0 {
let offset = self.builder.field_constant(offset);
address = self.builder.insert_binary(address, BinaryOp::Add, offset);
}
address
}
}

/// True if the given operator cannot be encoded directly and needs
Expand Down
Loading