diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls/Nargo.toml new file mode 100644 index 00000000000..e0b467ce5da --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls/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_calls/Prover.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls/Prover.toml new file mode 100644 index 00000000000..11497a473bc --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls/Prover.toml @@ -0,0 +1 @@ +x = "0" diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls/src/main.nr new file mode 100644 index 00000000000..795fc02c35f --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls/src/main.nr @@ -0,0 +1,45 @@ +// Tests a very simple program. +// +// The features being tested is brillig calls +fn main(x: u32) { + assert(entry_point(x) == 2); + swap_entry_point(x, x + 1); + assert(deep_entry_point(x) == 4); +} + +unconstrained fn inner(x : u32) -> u32 { + x + 1 +} + +unconstrained fn entry_point(x : u32) -> u32 { + inner(x + 1) +} + +unconstrained fn swap(x: u32, y:u32) -> (u32, u32) { + (y, x) +} + +unconstrained fn swap_entry_point(x: u32, y: u32) { + let swapped = swap(x, y); + assert(swapped.0 == y); + assert(swapped.1 == x); + let swapped_twice = swap(swapped.0, swapped.1); + assert(swapped_twice.0 == x); + assert(swapped_twice.1 == y); +} + +unconstrained fn level_3(x : u32) -> u32 { + x + 1 +} + +unconstrained fn level_2(x : u32) -> u32 { + level_3(x + 1) +} + +unconstrained fn level_1(x : u32) -> u32 { + level_2(x + 1) +} + +unconstrained fn deep_entry_point(x : u32) -> u32 { + level_1(x + 1) +} diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls_array/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls_array/Nargo.toml new file mode 100644 index 00000000000..e0b467ce5da --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls_array/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_calls_array/Prover.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls_array/Prover.toml new file mode 100644 index 00000000000..99580ca45bc --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls_array/Prover.toml @@ -0,0 +1 @@ +x = ["1","2","3"] diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls_array/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls_array/src/main.nr new file mode 100644 index 00000000000..ebe37a9b006 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_calls_array/src/main.nr @@ -0,0 +1,16 @@ +// Tests a very simple program. +// +// The features being tested is brillig calls passing arrays around +fn main(x: [u32; 3]) { + assert(entry_point(x) == 9); +} + +unconstrained fn inner(x : [u32; 3]) -> [u32; 3] { + [x[0] + 1, x[1] + 1, x[2] + 1] +} + +unconstrained fn entry_point(x : [u32; 3]) -> u32 { + let y = inner(x); + y[0] + y[1] + y[2] +} + diff --git a/crates/noirc_evaluator/src/brillig/brillig_gen.rs b/crates/noirc_evaluator/src/brillig/brillig_gen.rs index 00d26f6c7fd..034a1a26630 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_gen.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_gen.rs @@ -22,18 +22,15 @@ pub(crate) fn convert_ssa_function(func: &Function) -> BrilligArtifact { let mut function_context = FunctionContext { function_id: func.id(), ssa_value_to_register: HashMap::new() }; - let mut brillig_context = BrilligContext::new(); + let mut brillig_context = BrilligContext::new( + FunctionContext::parameters(func), + FunctionContext::return_values(func), + ); + brillig_context.enter_context(FunctionContext::function_id_to_function_label(func.id())); for block in reverse_post_order { BrilligBlock::compile(&mut function_context, &mut brillig_context, block, &func.dfg); } brillig_context.artifact() } - -/// Creates an entry point artifact, that will be linked with the brillig functions being called -pub(crate) fn create_entry_point_function(num_arguments: usize) -> BrilligArtifact { - let mut brillig_context = BrilligContext::new(); - brillig_context.entry_point_instruction(num_arguments); - brillig_context.artifact() -} diff --git a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 8d74b11332e..1aacc2b6c22 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -1,6 +1,7 @@ use crate::brillig::brillig_ir::{ BrilligBinaryOp, BrilligContext, BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, }; +use crate::ssa_refactor::ir::function::FunctionId; use crate::ssa_refactor::ir::types::CompositeType; use crate::ssa_refactor::ir::{ basic_block::{BasicBlock, BasicBlockId}, @@ -39,7 +40,7 @@ impl<'block> BrilligBlock<'block> { fn convert_block(&mut self, dfg: &DataFlowGraph) { // Add a label for this block - let block_label = self.create_block_label(self.block_id); + let block_label = self.create_block_label_for_current_function(self.block_id); self.brillig_context.enter_context(block_label); // Convert the block parameters @@ -57,9 +58,22 @@ impl<'block> BrilligBlock<'block> { self.convert_ssa_terminator(terminator_instruction, dfg); } - /// Creates a unique global label for a block - fn create_block_label(&self, block_id: BasicBlockId) -> String { - format!("{}-{}", self.function_context.function_id, block_id) + /// Creates a unique global label for a block. + /// + /// This uses the current functions's function ID and the block ID + /// Making the assumption that the block ID passed in belongs to this + /// function. + fn create_block_label_for_current_function(&self, block_id: BasicBlockId) -> String { + Self::create_block_label(self.function_context.function_id, block_id) + } + /// Creates a unique label for a block using the function Id and the block ID. + /// + /// We implicitly assume that the function ID and the block ID is enough + /// for us to create a unique label across functions and blocks. + /// + /// This is so that during linking there are no duplicates or labels being overwritten. + fn create_block_label(function_id: FunctionId, block_id: BasicBlockId) -> String { + format!("{}-{}", function_id, block_id) } /// Converts an SSA terminator instruction into the necessary opcodes. @@ -74,9 +88,13 @@ impl<'block> BrilligBlock<'block> { match terminator_instruction { TerminatorInstruction::JmpIf { condition, then_destination, else_destination } => { let condition = self.convert_ssa_value(*condition, dfg); - self.brillig_context - .jump_if_instruction(condition, self.create_block_label(*then_destination)); - self.brillig_context.jump_instruction(self.create_block_label(*else_destination)); + self.brillig_context.jump_if_instruction( + condition, + self.create_block_label_for_current_function(*then_destination), + ); + self.brillig_context.jump_instruction( + self.create_block_label_for_current_function(*else_destination), + ); } TerminatorInstruction::Jmp { destination, arguments } => { let target = &dfg[*destination]; @@ -85,7 +103,8 @@ impl<'block> BrilligBlock<'block> { let source = self.convert_ssa_value(*src, dfg); self.brillig_context.mov_instruction(destination, source); } - self.brillig_context.jump_instruction(self.create_block_label(*destination)); + self.brillig_context + .jump_instruction(self.create_block_label_for_current_function(*destination)); } TerminatorInstruction::Return { return_values } => { let return_registers: Vec<_> = return_values @@ -106,15 +125,12 @@ impl<'block> BrilligBlock<'block> { _ => unreachable!("ICE: Only Param type values should appear in block parameters"), }; match param_type { - Type::Numeric(_) => { + // Simple parameters and arrays are passed as already filled registers + // In the case of arrays, the values should already be in memory and the register should + // Be a valid pointer to the array. + Type::Numeric(_) | Type::Array(..) => { self.function_context.get_or_create_register(self.brillig_context, *param_id); } - Type::Array(_, size) => { - let pointer_register = self - .function_context - .get_or_create_register(self.brillig_context, *param_id); - self.brillig_context.allocate_fixed_length_array(pointer_register, *size); - } _ => { todo!("ICE: Param type not supported") } @@ -189,6 +205,33 @@ impl<'block> BrilligBlock<'block> { &output_registers, ); } + Value::Function(func_id) => { + let function_arguments: Vec = + vecmap(arguments, |arg| self.convert_ssa_value(*arg, dfg)); + let result_ids = dfg.instruction_results(instruction_id); + + // Create label for the function that will be called + let label_of_function_to_call = + FunctionContext::function_id_to_function_label(*func_id); + + let saved_registers = + self.brillig_context.pre_call_save_registers_prep_args(&function_arguments); + + // Call instruction, which will interpret above registers 0..num args + self.brillig_context.add_external_call_instruction(label_of_function_to_call); + + // Important: resolve after pre_call_save_registers_prep_args + // This ensures we don't save the results to registers unnecessarily. + let result_registers = vecmap(result_ids, |a| { + self.function_context.get_or_create_register(self.brillig_context, *a) + }); + assert!( + !saved_registers.iter().any(|x| result_registers.contains(x)), + "should not save registers used as function results" + ); + self.brillig_context + .post_call_prep_returns_load_registers(&result_registers, &saved_registers); + } _ => { unreachable!("only foreign function calls supported in unconstrained functions") } @@ -502,7 +545,7 @@ fn compute_size_of_composite_type(typ: &CompositeType) -> usize { /// Finds out the size of a given SSA type /// This is needed to store values in memory -fn compute_size_of_type(typ: &Type) -> usize { +pub(crate) fn compute_size_of_type(typ: &Type) -> usize { match typ { Type::Numeric(_) => 1, Type::Array(types, item_count) => compute_size_of_composite_type(types) * item_count, diff --git a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_fn.rs b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_fn.rs index 60de8743373..819f0ae26c7 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_fn.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_fn.rs @@ -3,10 +3,20 @@ use std::collections::HashMap; use acvm::acir::brillig_vm::RegisterIndex; use crate::{ - brillig::brillig_ir::BrilligContext, - ssa_refactor::ir::{function::FunctionId, value::ValueId}, + brillig::brillig_ir::{ + artifact::{BrilligParameter, Label}, + BrilligContext, + }, + ssa_refactor::ir::{ + function::{Function, FunctionId}, + instruction::TerminatorInstruction, + types::Type, + value::ValueId, + }, }; +use super::brillig_block::compute_size_of_type; + pub(crate) struct FunctionContext { pub(crate) function_id: FunctionId, /// Map from SSA values to Register Indices. @@ -38,4 +48,49 @@ impl FunctionContext { register } + + /// Creates a function label from a given SSA function id. + pub(crate) fn function_id_to_function_label(function_id: FunctionId) -> Label { + function_id.to_string() + } + + /// Collects the parameters of a given function + pub(crate) fn parameters(func: &Function) -> Vec { + func.parameters() + .iter() + .map(|&value_id| { + let typ = func.dfg.type_of_value(value_id); + match typ { + Type::Numeric(_) => BrilligParameter::Register, + Type::Array(..) => BrilligParameter::HeapArray(compute_size_of_type(&typ)), + _ => unimplemented!("Unsupported function parameter type {typ:?}"), + } + }) + .collect() + } + + /// Collects the return values of a given function + pub(crate) fn return_values(func: &Function) -> Vec { + let blocks = func.reachable_blocks(); + let mut function_return_values = None; + for block in blocks { + let terminator = func.dfg[block].terminator(); + if let Some(TerminatorInstruction::Return { return_values }) = terminator { + function_return_values = Some(return_values); + break; + } + } + function_return_values + .expect("Expected a return instruction, as block is finished construction") + .iter() + .map(|&value_id| { + let typ = func.dfg.type_of_value(value_id); + match typ { + Type::Numeric(_) => BrilligParameter::Register, + Type::Array(..) => BrilligParameter::HeapArray(compute_size_of_type(&typ)), + _ => unimplemented!("Unsupported return value type {typ:?}"), + } + }) + .collect() + } } diff --git a/crates/noirc_evaluator/src/brillig/brillig_ir.rs b/crates/noirc_evaluator/src/brillig/brillig_ir.rs index 51f447a6777..8362a497104 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_ir.rs @@ -9,7 +9,7 @@ pub(crate) mod debug_show; pub(crate) mod registers; use self::{ - artifact::{BrilligArtifact, UnresolvedJumpLocation}, + artifact::{BrilligArtifact, BrilligParameter, UnresolvedJumpLocation}, registers::BrilligRegistersContext, }; use acvm::{ @@ -40,20 +40,29 @@ pub(crate) const BRILLIG_MEMORY_ADDRESSING_BIT_SIZE: u32 = 64; pub(crate) enum ReservedRegisters { /// This register stores the stack pointer. Allocations must be done after this pointer. StackPointer = 0, - /// Number of reserved registers - Len = 1, } impl ReservedRegisters { + /// The number of reserved registers. + /// + /// This is used to offset the general registers + /// which should not overwrite the special register + const NUM_RESERVED_REGISTERS: usize = 1; + /// Returns the length of the reserved registers pub(crate) fn len() -> usize { - ReservedRegisters::Len as usize + Self::NUM_RESERVED_REGISTERS } /// Returns the stack pointer register. This will get used to allocate memory in runtime. pub(crate) fn stack_pointer() -> RegisterIndex { RegisterIndex::from(ReservedRegisters::StackPointer as usize) } + + /// Returns a user defined (non-reserved) register index. + fn user_register_index(index: usize) -> RegisterIndex { + RegisterIndex::from(index + ReservedRegisters::len()) + } } /// Brillig context object that is used while constructing the @@ -71,26 +80,18 @@ pub(crate) struct BrilligContext { impl BrilligContext { /// Initial context state - pub(crate) fn new() -> BrilligContext { + pub(crate) fn new( + arguments: Vec, + return_parameters: Vec, + ) -> BrilligContext { BrilligContext { - obj: BrilligArtifact::default(), + obj: BrilligArtifact::new(arguments, return_parameters), registers: BrilligRegistersContext::new(), context_label: String::default(), section_label: 0, } } - /// Adds the instructions needed to handle entry point parameters - /// And sets the starting value of the reserved registers - pub(crate) fn entry_point_instruction(&mut self, num_arguments: usize) { - // Translate the inputs by the reserved registers offset - for i in (0..num_arguments).rev() { - self.mov_instruction(self.user_register_index(i), RegisterIndex::from(i)); - } - // Set the initial value of the stack pointer register - self.const_instruction(ReservedRegisters::stack_pointer(), Value::from(0_usize)); - } - /// Adds a brillig instruction to the brillig byte code pub(crate) fn push_opcode(&mut self, opcode: BrilligOpcode) { self.obj.byte_code.push(opcode); @@ -301,11 +302,6 @@ impl BrilligContext { self.obj.add_unresolved_jump(jmp_instruction, destination); } - /// Returns a user defined (non-reserved) register index. - fn user_register_index(&self, index: usize) -> RegisterIndex { - RegisterIndex::from(index + ReservedRegisters::len()) - } - /// Allocates an unused register. pub(crate) fn allocate_register(&mut self) -> RegisterIndex { self.registers.allocate_register() @@ -343,14 +339,41 @@ impl BrilligContext { /// the VM. pub(crate) fn return_instruction(&mut self, return_registers: &[RegisterIndex]) { debug_show::return_instruction(return_registers); + + let mut sources = Vec::with_capacity(return_registers.len()); + let mut destinations = Vec::with_capacity(return_registers.len()); + for (destination_index, return_register) in return_registers.iter().enumerate() { // In case we have fewer return registers than indices to write to, ensure we've allocated this register - self.registers.ensure_register_is_allocated(destination_index.into()); - self.mov_instruction(destination_index.into(), *return_register); + let destination_register = ReservedRegisters::user_register_index(destination_index); + self.registers.ensure_register_is_allocated(destination_register); + sources.push(*return_register); + destinations.push(destination_register); } + self.mov_registers_to_registers_instruction(sources, destinations); self.stop_instruction(); } + /// This function moves values from a set of registers to another set of registers. + /// It first moves all sources to new allocated registers to avoid overwriting. + pub(crate) fn mov_registers_to_registers_instruction( + &mut self, + sources: Vec, + destinations: Vec, + ) { + let new_sources: Vec<_> = sources + .iter() + .map(|source| { + let new_source = self.allocate_register(); + self.mov_instruction(new_source, *source); + new_source + }) + .collect(); + for (new_source, destination) in new_sources.iter().zip(destinations.iter()) { + self.mov_instruction(*destination, *new_source); + } + } + /// Emits a `mov` instruction. /// /// Copies the value at `source` into `destination` @@ -567,6 +590,107 @@ impl BrilligContext { ); self.deallocate_register(zero_register); } + + /// Adds a unresolved external `Call` instruction to the bytecode. + pub(crate) fn add_external_call_instruction(&mut self, func_label: T) { + self.obj.add_unresolved_external_call( + BrilligOpcode::Call { location: 0 }, + func_label.to_string(), + ); + } + + /// Returns the i'th register after the reserved ones + pub(crate) fn register(&self, i: usize) -> RegisterIndex { + RegisterIndex::from(ReservedRegisters::NUM_RESERVED_REGISTERS + i) + } + + /// Saves all of the registers that have been used up until this point. + fn save_all_used_registers(&mut self) -> Vec { + // Save all of the used registers at this point in memory + // because the function call will/may overwrite them. + // + // Note that here it is important that the stack pointer register is at register 0, + // as after the first register save we add to the pointer. + let used_registers: Vec<_> = self.registers.used_registers_iter().collect(); + for register in used_registers.iter() { + self.store_instruction(ReservedRegisters::stack_pointer(), *register); + // Add one to our stack pointer + self.usize_op(ReservedRegisters::stack_pointer(), BinaryIntOp::Add, 1); + } + used_registers + } + + /// Loads all of the registers that have been save by save_all_used_registers. + fn load_all_saved_registers(&mut self, used_registers: &[RegisterIndex]) { + // Load all of the used registers that we saved. + // We do all the reverse operations of save_all_used_registers. + // Iterate our registers in reverse + for register in used_registers.iter().rev() { + // Subtract one from our stack pointer + self.usize_op(ReservedRegisters::stack_pointer(), BinaryIntOp::Sub, 1); + self.load_instruction(*register, ReservedRegisters::stack_pointer()); + } + } + + /// Utility method to perform a binary instruction with a constant value + pub(crate) fn usize_op( + &mut self, + destination: RegisterIndex, + op: BinaryIntOp, + constant: usize, + ) { + let const_register = self.make_constant(Value::from(constant)); + self.binary_instruction( + destination, + const_register, + destination, + BrilligBinaryOp::Integer { op, bit_size: BRILLIG_MEMORY_ADDRESSING_BIT_SIZE }, + ); + // Mark as no longer used for this purpose, frees for reuse + self.deallocate_register(const_register); + } + + // Used before a call instruction. + // Save all the registers we have used to the stack. + // Move argument values to the front of the register indices. + pub(crate) fn pre_call_save_registers_prep_args( + &mut self, + arguments: &[RegisterIndex], + ) -> Vec { + // Save all the registers we have used to the stack. + let saved_registers = self.save_all_used_registers(); + + // Move argument values to the front of the registers + // + // This means that the arguments will be in the first `n` registers after + // the number of reserved registers. + let (sources, destinations) = + arguments.iter().enumerate().map(|(i, argument)| (*argument, self.register(i))).unzip(); + self.mov_registers_to_registers_instruction(sources, destinations); + saved_registers + } + + // Used after a call instruction. + // Move return values to the front of the register indices. + // Load all the registers we have previous saved in save_registers_prep_args. + pub(crate) fn post_call_prep_returns_load_registers( + &mut self, + result_registers: &[RegisterIndex], + saved_registers: &[RegisterIndex], + ) { + // Allocate our result registers and write into them + // We assume the return values of our call are held in 0..num results register indices + for (i, result_register) in result_registers.iter().enumerate() { + self.mov_instruction(*result_register, self.register(i)); + } + + // Restore all the same registers we have, in exact reverse order. + // Note that we have allocated some registers above, which we will not be handling here, + // only restoring registers that were used prior to the call finishing. + // After the call instruction, the stack frame pointer should be back to where we left off, + // so we do our instructions in reverse order. + self.load_all_saved_registers(saved_registers); + } } /// Type to encapsulate the binary operation types in Brillig diff --git a/crates/noirc_evaluator/src/brillig/brillig_ir/artifact.rs b/crates/noirc_evaluator/src/brillig/brillig_ir/artifact.rs index 3c6be408a32..6ccb7cc0b16 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_ir/artifact.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_ir/artifact.rs @@ -1,6 +1,16 @@ -use acvm::acir::brillig_vm::Opcode as BrilligOpcode; +use acvm::acir::brillig_vm::{Opcode as BrilligOpcode, RegisterIndex}; use std::collections::HashMap; +use crate::brillig::brillig_ir::ReservedRegisters; + +/// Represents a parameter or a return value of a function. +#[derive(Debug, Clone)] +pub(crate) enum BrilligParameter { + Register, + // A heap array is filled in memory and a pointer to the first element is passed in the register. + HeapArray(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. @@ -11,6 +21,18 @@ pub(crate) struct BrilligArtifact { unresolved_jumps: Vec<(JumpInstructionPosition, UnresolvedJumpLocation)>, /// A map of labels to their position in byte code. labels: HashMap, + /// Set of labels which are external to the bytecode. + /// + /// This will most commonly contain the labels of functions + /// which are defined in other bytecode, that this bytecode has called. + /// TODO: perhaps we should combine this with the `unresolved_jumps` field + /// TODO: and have an enum which indicates whether the jump is internal or external + unresolved_external_call_labels: Vec<(JumpInstructionPosition, UnresolvedJumpLocation)>, + /// The return values that this function will return. + return_parameters: Vec, + + /// The arguments that this function will take. + arguments: Vec, } /// A pointer to a location in the opcode. @@ -36,19 +58,142 @@ pub(crate) type JumpInstructionPosition = OpcodeLocation; pub(crate) type UnresolvedJumpLocation = Label; impl BrilligArtifact { - /// Link two Brillig artifacts together and resolve all unresolved jump instructions. - pub(crate) fn link(&mut self, obj: &BrilligArtifact) -> Vec { - self.append_artifact(obj); + /// Initialize an artifact with the number of arguments and return parameters + pub(crate) fn new( + arguments: Vec, + return_parameters: Vec, + ) -> BrilligArtifact { + BrilligArtifact { + byte_code: Vec::new(), + unresolved_jumps: Vec::new(), + labels: HashMap::new(), + unresolved_external_call_labels: Vec::new(), + arguments, + return_parameters, + } + } + + /// Creates an entry point artifact wrapping the bytecode of the function provided. + pub(crate) fn to_entry_point_artifact(artifact: &BrilligArtifact) -> BrilligArtifact { + let mut entry_point_artifact = + BrilligArtifact::new(artifact.arguments.clone(), artifact.return_parameters.clone()); + entry_point_artifact.entry_point_instruction(); + + entry_point_artifact.add_unresolved_jumps_and_calls(artifact); + entry_point_artifact.byte_code.extend_from_slice(&artifact.byte_code); + + entry_point_artifact.exit_point_instruction(); + entry_point_artifact + } + + /// Resolves all jumps and generates the final bytecode + pub(crate) fn finish(mut self) -> Vec { self.resolve_jumps(); - self.byte_code.clone() + self.byte_code } - /// Link with an external brillig artifact. + /// Adds the instructions needed to handle entry point parameters + /// + /// And sets the starting value of the reserved registers + fn entry_point_instruction(&mut self) { + // Translate the inputs by the reserved registers offset + for i in (0..self.arguments.len()).rev() { + self.byte_code.push(BrilligOpcode::Mov { + destination: ReservedRegisters::user_register_index(i), + source: RegisterIndex::from(i), + }); + } + + // Calculate the initial value for the stack pointer register + let size_arguments_memory = self + .arguments + .iter() + .map(|arg| match arg { + BrilligParameter::Register => 0, + BrilligParameter::HeapArray(size) => *size, + }) + .sum::(); + + // Set the initial value of the stack pointer register + self.byte_code.push(BrilligOpcode::Const { + destination: ReservedRegisters::stack_pointer(), + value: size_arguments_memory.into(), + }); + } + + /// Adds the instructions needed to handle return parameters + fn exit_point_instruction(&mut self) { + // We want all functions to follow the calling convention of returning + // their results in the first `n` registers. So we modify the bytecode of the + // function to move the return values to the first `n` registers once completed. + // + // Swap the stop opcode with a jump to the exit point section + + let stop_position = + self.byte_code.iter().position(|opcode| matches!(opcode, BrilligOpcode::Stop)); + + let stop_position = stop_position.expect("expected a stop opcode"); + + let exit_section = self.index_of_next_opcode(); + self.byte_code[stop_position] = BrilligOpcode::Jump { location: exit_section }; + + // TODO: this _seems_ like an abstraction leak, we need to know about the reserved + // TODO: registers in order to do this. + // Move the results to registers 0..n + for i in 0..self.return_parameters.len() { + self.push_opcode(BrilligOpcode::Mov { + destination: i.into(), + source: ReservedRegisters::user_register_index(i), + }); + } + self.push_opcode(BrilligOpcode::Stop); + } + + /// Gets the first unresolved function call of this artifact. + pub(crate) fn first_unresolved_function_call(&self) -> Option