diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_conditional/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_conditional/Nargo.toml new file mode 100644 index 00000000000..e0b467ce5da --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_conditional/Nargo.toml @@ -0,0 +1,5 @@ +[package] +authors = [""] +compiler_version = "0.1" + +[dependencies] \ No newline at end of file diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_conditional/Prover.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_conditional/Prover.toml new file mode 100644 index 00000000000..4dd6b405159 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_conditional/Prover.toml @@ -0,0 +1 @@ +x = "1" diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_conditional/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_conditional/src/main.nr new file mode 100644 index 00000000000..4ddd351ad04 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_conditional/src/main.nr @@ -0,0 +1,14 @@ +// Tests a very simple program. +// +// The features being tested is basic conditonal on brillig +fn main(x: Field) { + assert(4 == conditional(x as bool)); +} + +unconstrained fn conditional(x : bool) -> Field { + if x { + 4 + }else { + 5 + } +} \ No newline at end of file diff --git a/crates/noirc_evaluator/src/brillig/artifact.rs b/crates/noirc_evaluator/src/brillig/artifact.rs index 3152d4a70a1..c3129f0390e 100644 --- a/crates/noirc_evaluator/src/brillig/artifact.rs +++ b/crates/noirc_evaluator/src/brillig/artifact.rs @@ -1,25 +1,98 @@ +use crate::ssa_refactor::ir::basic_block::BasicBlockId; use acvm::acir::brillig_vm::Opcode as BrilligOpcode; +use std::collections::HashMap; + +/// Pointer to a unresolved Jump instruction in +/// the bytecode. +pub(crate) type JumpLabel = usize; + +/// Pointer to a position in the bytecode where a +/// particular basic block starts. +pub(crate) type BlockLabel = usize; #[derive(Default, Debug, Clone)] -/// Artifacts resulting from the compilation of a function into brillig byte code -/// Currently it is just the brillig bytecode of the function +/// Artifacts resulting from the compilation of a function into brillig byte code. +/// Currently it is just the brillig bytecode of the function. pub(crate) struct BrilligArtifact { pub(crate) byte_code: Vec, + /// The set of jumps that need to have their locations + /// resolved. + unresolved_jumps: Vec<(JumpLabel, BasicBlockId)>, + /// A map of the basic blocks to their positions + /// in the bytecode. + blocks: HashMap, } impl BrilligArtifact { - // Link some compiled brillig bytecode with its referenced artifacts + /// Link some compiled brillig bytecode with its referenced artifacts. pub(crate) fn link(&mut self, obj: &BrilligArtifact) -> Vec { self.link_with(obj); + self.resolve_jumps(); self.byte_code.clone() } - // Link with a brillig artifact + /// Link with a brillig artifact fn link_with(&mut self, obj: &BrilligArtifact) { - if obj.byte_code.is_empty() { - panic!("ICE: unresolved symbol"); + let offset = self.code_len(); + for (jump_label, block_id) in &obj.unresolved_jumps { + self.unresolved_jumps.push((jump_label + offset, *block_id)); + } + + for (block_id, block_label) in &obj.blocks { + self.blocks.insert(*block_id, block_label + offset); } self.byte_code.extend_from_slice(&obj.byte_code); } + + /// Adds a unresolved jump to be fixed at the end of bytecode processing. + pub(crate) fn add_unresolved_jump(&mut self, destination: BasicBlockId) { + self.unresolved_jumps.push((self.code_len(), destination)); + } + + /// Adds a label in the bytecode to specify where this block's + /// opcodes will start. + pub(crate) fn add_block_label(&mut self, block: BasicBlockId) { + self.blocks.insert(block, self.code_len()); + } + + /// Number of the opcodes currently in the bytecode + pub(crate) fn code_len(&self) -> usize { + self.byte_code.len() + } + + /// Resolves all of the unresolved jumps in the program. + /// + /// Note: This should only be called once all blocks are processed. + fn resolve_jumps(&mut self) { + for (jump_label, block) in &self.unresolved_jumps { + let jump_instruction = self.byte_code[*jump_label].clone(); + + let actual_block_location = self.blocks[block]; + + match jump_instruction { + BrilligOpcode::Jump { location } => { + assert_eq!(location, 0, "location is not zero, which means that the jump label does not need resolving"); + + self.byte_code[*jump_label] = + BrilligOpcode::Jump { location: actual_block_location }; + } + BrilligOpcode::JumpIfNot { condition, location } => { + assert_eq!(location, 0, "location is not zero, which means that the jump label does not need resolving"); + + self.byte_code[*jump_label] = + BrilligOpcode::JumpIfNot { condition, location: actual_block_location }; + } + BrilligOpcode::JumpIf { condition, location } => { + assert_eq!(location, 0,"location is not zero, which means that the jump label does not need resolving"); + + self.byte_code[*jump_label] = + BrilligOpcode::JumpIf { condition, location: actual_block_location }; + } + _ => unreachable!( + "all jump labels should point to a jump instruction in the bytecode" + ), + } + } + } } diff --git a/crates/noirc_evaluator/src/brillig/brillig_gen.rs b/crates/noirc_evaluator/src/brillig/brillig_gen.rs index 09250714bfc..f57503dd5e5 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_gen.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_gen.rs @@ -1,19 +1,18 @@ -use std::collections::HashMap; - +use super::artifact::BrilligArtifact; use crate::ssa_refactor::ir::{ - basic_block::BasicBlock, + basic_block::{BasicBlock, BasicBlockId}, dfg::DataFlowGraph, function::Function, instruction::{Binary, BinaryOp, Instruction, InstructionId, TerminatorInstruction}, + post_order::PostOrder, types::{NumericType, Type}, value::{Value, ValueId}, }; - -use super::artifact::BrilligArtifact; - use acvm::acir::brillig_vm::{ BinaryFieldOp, BinaryIntOp, Opcode as BrilligOpcode, RegisterIndex, Value as BrilligValue, }; +use std::collections::HashMap; + #[derive(Default)] /// Generate the compilation artifacts for compiling a function into brillig bytecode. pub(crate) struct BrilligGen { @@ -47,14 +46,48 @@ impl BrilligGen { } /// Converts an SSA Basic block into a sequence of Brillig opcodes - fn convert_block(&mut self, block: &BasicBlock, dfg: &DataFlowGraph) { + fn convert_block(&mut self, block_id: BasicBlockId, dfg: &DataFlowGraph) { + self.obj.add_block_label(block_id); + let block = &dfg[dbg!(block_id)]; self.convert_block_params(block, dfg); for instruction_id in block.instructions() { self.convert_ssa_instruction(*instruction_id, dfg); } - self.convert_ssa_return(block, dfg); + // Jump to the next block + let jump = block.terminator().expect("block is expected to be constructed"); + match jump { + TerminatorInstruction::JmpIf { condition, then_destination, else_destination } => { + let condition = self.convert_ssa_value(*condition, dfg); + self.jump_if(condition, *then_destination); + self.jump(*else_destination); + } + TerminatorInstruction::Jmp { destination, arguments } => { + let target = &dfg[*destination]; + for (src, dest) in arguments.iter().zip(target.parameters()) { + let destination = self.convert_ssa_value(*dest, dfg); + let source = self.convert_ssa_value(*src, dfg); + self.push_code(BrilligOpcode::Mov { destination, source }); + } + self.jump(*destination); + } + TerminatorInstruction::Return { return_values } => { + self.convert_ssa_return(return_values, dfg); + } + } + } + + /// Adds a unresolved `Jump` instruction to the bytecode. + fn jump(&mut self, target: BasicBlockId) { + self.obj.add_unresolved_jump(target); + self.push_code(BrilligOpcode::Jump { location: 0 }); + } + + /// Adds a unresolved `JumpIf` instruction to the bytecode. + fn jump_if(&mut self, condition: RegisterIndex, target: BasicBlockId) { + self.obj.add_unresolved_jump(target); + self.push_code(BrilligOpcode::JumpIf { condition, location: 0 }); } /// Converts the SSA return instruction into the necessary BRillig return @@ -63,12 +96,7 @@ impl BrilligGen { /// For Brillig, the return is implicit; The caller will take `N` values from /// the Register starting at register index 0. `N` indicates the number of /// return values expected. - fn convert_ssa_return(&mut self, block: &BasicBlock, dfg: &DataFlowGraph) { - let return_values = match block.terminator().unwrap() { - TerminatorInstruction::Return { return_values } => return_values, - _ => todo!("ICE: Unsupported return"), - }; - + fn convert_ssa_return(&mut self, return_values: &[ValueId], dfg: &DataFlowGraph) { // Check if the program returns the `Unit/None` type. // This type signifies that the program returns nothing. let is_return_unit_type = @@ -171,7 +199,7 @@ impl BrilligGen { Value::Param { .. } | Value::Instruction { .. } => { // All block parameters and instruction results should have already been // converted to registers so we fetch from the cache. - self.ssa_value_to_register[&value_id] + self.get_or_create_register(value_id) } Value::NumericConstant { constant, .. } => { let register_index = self.get_or_create_register(value_id); @@ -193,14 +221,27 @@ impl BrilligGen { pub(crate) fn compile(func: &Function) -> BrilligArtifact { let mut brillig = BrilligGen::default(); - let dfg = &func.dfg; - - brillig.convert_block(&dfg[func.entry_block()], dfg); + brillig.convert_ssa_function(func); brillig.push_code(BrilligOpcode::Stop); brillig.obj } + + /// Converting an SSA function into Brillig bytecode. + /// + /// TODO: Change this to use `dfg.basic_blocks_iter` which will return an + /// TODO iterator of all of the basic blocks. + /// TODO(Jake): what order is this ^ + fn convert_ssa_function(&mut self, func: &Function) { + let mut reverse_post_order = Vec::new(); + reverse_post_order.extend_from_slice(PostOrder::with_function(func).as_slice()); + reverse_post_order.reverse(); + + for block in reverse_post_order { + self.convert_block(block, &func.dfg); + } + } } /// Type to encapsulate the binary operation types in Brillig