Skip to content

Commit

Permalink
chore(ssa refactor): Implement ssa-gen for indexing, cast, constrain,…
Browse files Browse the repository at this point in the history
… if, unary (#1225)

* Implement ssa-gen for if

* Satisfy the clippy gods
  • Loading branch information
jfecher authored Apr 26, 2023
1 parent 3a65f30 commit e551e55
Show file tree
Hide file tree
Showing 12 changed files with 230 additions and 70 deletions.
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>,
},
/// 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

0 comments on commit e551e55

Please sign in to comment.