diff --git a/crates/noirc_evaluator/src/brillig/brillig_gen.rs b/crates/noirc_evaluator/src/brillig/brillig_gen.rs index 2cb3045f915..39e73eff7bb 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_gen.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_gen.rs @@ -1,426 +1,32 @@ -use super::brillig_ir::{artifact::BrilligArtifact, BrilligBinaryOp, BrilligContext}; -use crate::ssa_refactor::ir::{ - basic_block::{BasicBlock, BasicBlockId}, - dfg::DataFlowGraph, - function::Function, - instruction::{Binary, BinaryOp, Instruction, InstructionId, TerminatorInstruction}, - post_order::PostOrder, - types::{NumericType, Type}, - value::{Value, ValueId}, -}; -use acvm::{ - acir::brillig_vm::{BinaryFieldOp, BinaryIntOp, RegisterIndex, RegisterValueOrArray}, - FieldElement, -}; -use iter_extended::vecmap; -use std::collections::HashMap; - -#[derive(Default)] -/// Generate the compilation artifacts for compiling a function into brillig bytecode. -pub(crate) struct BrilligGen { - /// Context for creating brillig opcodes - context: BrilligContext, - /// Map from SSA values to Register Indices. - ssa_value_to_register: HashMap, -} - -impl BrilligGen { - /// Gets a `RegisterIndex` for a `ValueId`, if one already exists - /// or creates a new `RegisterIndex` using the latest available - /// free register. - fn get_or_create_register(&mut self, value: ValueId) -> RegisterIndex { - if let Some(register_index) = self.ssa_value_to_register.get(&value) { - return *register_index; - } - - let register = self.context.create_register(); - - // Cache the `ValueId` so that if we call it again, it will - // return the register that has just been created. - // - // WARNING: This assumes that a register has not been - // modified. If a MOV instruction has overwritten the value - // at a register, then this cache will be invalid. - self.ssa_value_to_register.insert(value, register); - - register - } - - /// Converts an SSA Basic block into a sequence of Brillig opcodes - fn convert_block(&mut self, block_id: BasicBlockId, dfg: &DataFlowGraph) { - // Add a label for this block - self.context.add_label_to_next_opcode(block_id); - - // Convert the block parameters - let block = &dfg[block_id]; - self.convert_block_params(block, dfg); - - // Convert all of the instructions int the block - for instruction_id in block.instructions() { - self.convert_ssa_instruction(*instruction_id, dfg); - } - - // Process the block's terminator instruction - let terminator_instruction = - block.terminator().expect("block is expected to be constructed"); - self.convert_ssa_terminator(terminator_instruction, dfg); - } - - /// Converts an SSA terminator instruction into the necessary opcodes. - /// - /// TODO: document why the TerminatorInstruction::Return includes a stop instruction - /// TODO along with the `Self::compile` - fn convert_ssa_terminator( - &mut self, - terminator_instruction: &TerminatorInstruction, - dfg: &DataFlowGraph, - ) { - match terminator_instruction { - TerminatorInstruction::JmpIf { condition, then_destination, else_destination } => { - let condition = self.convert_ssa_value(*condition, dfg); - self.context.jump_if_instruction(condition, then_destination); - self.context.jump_instruction(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.context.mov_instruction(destination, source); - } - self.context.jump_instruction(destination); - } - TerminatorInstruction::Return { return_values } => { - let return_registers: Vec<_> = return_values - .iter() - .map(|value_id| self.convert_ssa_value(*value_id, dfg)) - .collect(); - self.context.return_instruction(&return_registers); - } - } - } - - /// Converts SSA Block parameters into Brillig Registers. - fn convert_block_params(&mut self, block: &BasicBlock, dfg: &DataFlowGraph) { - for param_id in block.parameters() { - let value = &dfg[*param_id]; - let param_type = match value { - Value::Param { typ, .. } => typ, - _ => unreachable!("ICE: Only Param type values should appear in block parameters"), - }; - - match param_type { - Type::Numeric(_) => { - self.get_or_create_register(*param_id); - } - Type::Array(_, size) => { - let pointer_register = self.get_or_create_register(*param_id); - self.context.allocate_array(pointer_register, *size as u32); - } - _ => { - todo!("ICE: Param type not supported") - } - } - } - } - - /// Converts an SSA instruction into a sequence of Brillig opcodes. - fn convert_ssa_instruction(&mut self, instruction_id: InstructionId, dfg: &DataFlowGraph) { - let instruction = &dfg[instruction_id]; - - match instruction { - Instruction::Binary(binary) => { - let result_ids = dfg.instruction_results(instruction_id); - let result_register = self.get_or_create_register(result_ids[0]); - self.convert_ssa_binary(binary, dfg, result_register); - } - Instruction::Constrain(value) => { - let condition = self.convert_ssa_value(*value, dfg); - self.context.constrain_instruction(condition); - } - Instruction::Allocate => { - let pointer_register = - self.get_or_create_register(dfg.instruction_results(instruction_id)[0]); - self.context.allocate_array(pointer_register, 1); - } - Instruction::Store { address, value } => { - let address_register = self.convert_ssa_value(*address, dfg); - let value_register = self.convert_ssa_value(*value, dfg); - self.context.store_instruction(address_register, value_register); - } - Instruction::Load { address } => { - let target_register = - self.get_or_create_register(dfg.instruction_results(instruction_id)[0]); - let address_register = self.convert_ssa_value(*address, dfg); - self.context.load_instruction(target_register, address_register); - } - Instruction::Not(value) => { - assert_eq!( - dfg.type_of_value(*value), - Type::bool(), - "not operator can only be applied to boolean values" - ); - let condition = self.convert_ssa_value(*value, dfg); - let result_ids = dfg.instruction_results(instruction_id); - let result_register = self.get_or_create_register(result_ids[0]); - - self.context.not_instruction(condition, result_register); - } - Instruction::Call { func, arguments } => match &dfg[*func] { - Value::ForeignFunction(func_name) => { - let result_ids = dfg.instruction_results(instruction_id); - - let input_registers = vecmap(arguments, |value_id| { - self.convert_ssa_value_to_register_value_or_array(*value_id, dfg) - }); - let output_registers = vecmap(result_ids, |value_id| { - self.convert_ssa_value_to_register_value_or_array(*value_id, dfg) - }); - - self.context.foreign_call_instruction( - func_name.to_owned(), - &input_registers, - &output_registers, - ); - } - _ => { - unreachable!("only foreign function calls supported in unconstrained functions") - } - }, - Instruction::Truncate { value, .. } => { - let result_ids = dfg.instruction_results(instruction_id); - let destination = self.get_or_create_register(result_ids[0]); - let source = self.convert_ssa_value(*value, dfg); - self.context.truncate_instruction(destination, source); - } - Instruction::Cast(value, target_type) => { - let result_ids = dfg.instruction_results(instruction_id); - let destination = self.get_or_create_register(result_ids[0]); - let source = self.convert_ssa_value(*value, dfg); - self.convert_cast(destination, source, target_type, &dfg.type_of_value(*value)); - } - _ => todo!("ICE: Instruction not supported {instruction:?}"), - }; - } - - /// Converts an SSA cast to a sequence of Brillig opcodes. - /// Casting is only necessary when shrinking the bit size of a numeric value. - fn convert_cast( - &mut self, - destination: RegisterIndex, - source: RegisterIndex, - target_type: &Type, - source_type: &Type, - ) { - fn numeric_to_bit_size(typ: &NumericType) -> u32 { - match typ { - NumericType::Signed { bit_size } | NumericType::Unsigned { bit_size } => *bit_size, - NumericType::NativeField => FieldElement::max_num_bits(), - } - } +pub(crate) mod brillig_block; +pub(crate) mod brillig_fn; - // Casting is only valid for numeric types - // This should be checked by the frontend, so we panic if this is the case - let (source_numeric_type, target_numeric_type) = match (source_type, target_type) { - (Type::Numeric(source_numeric_type), Type::Numeric(target_numeric_type)) => { - (source_numeric_type, target_numeric_type) - } - _ => unimplemented!("The cast operation is only valid for integers."), - }; +use crate::ssa_refactor::ir::{function::Function, post_order::PostOrder}; - let source_bit_size = numeric_to_bit_size(source_numeric_type); - let target_bit_size = numeric_to_bit_size(target_numeric_type); - - // Casting from a larger bit size to a smaller bit size (narrowing cast) - // requires a cast instruction. - // If its a widening cast, ie casting from a smaller bit size to a larger bit size - // we simply put a mov instruction as a no-op - // - // Field elements by construction always have the largest bit size - // This means that casting to a Field element, will always be a widening cast - // and therefore a no-op. Conversely, casting from a Field element - // will always be a narrowing cast and therefore a cast instruction - if source_bit_size > target_bit_size { - self.context.cast_instruction(destination, source, target_bit_size); - } else { - self.context.mov_instruction(destination, source); - } - } - - /// Converts the Binary instruction into a sequence of Brillig opcodes. - fn convert_ssa_binary( - &mut self, - binary: &Binary, - dfg: &DataFlowGraph, - result_register: RegisterIndex, - ) { - let binary_type = - type_of_binary_operation(dfg[binary.lhs].get_type(), dfg[binary.rhs].get_type()); - - let left = self.convert_ssa_value(binary.lhs, dfg); - let right = self.convert_ssa_value(binary.rhs, dfg); - - let brillig_binary_op = - convert_ssa_binary_op_to_brillig_binary_op(binary.operator, binary_type); - - self.context.binary_instruction(left, right, result_register, brillig_binary_op); - } - - /// Converts an SSA `ValueId` into a `RegisterIndex`. - fn convert_ssa_value(&mut self, value_id: ValueId, dfg: &DataFlowGraph) -> RegisterIndex { - let value = &dfg[value_id]; - let register = match value { - Value::Param { .. } | Value::Instruction { .. } => { - // All block parameters and instruction results should have already been - // converted to registers so we fetch from the cache. - self.get_or_create_register(value_id) - } - Value::NumericConstant { constant, .. } => { - let register_index = self.get_or_create_register(value_id); - - self.context.const_instruction(register_index, (*constant).into()); - register_index - } - _ => { - todo!("ICE: Should have been in cache {value:?}") - } - }; - register - } - - /// Compiles an SSA function into a Brillig artifact which - /// contains a sequence of SSA opcodes. - pub(crate) fn compile(func: &Function) -> BrilligArtifact { - let mut brillig = BrilligGen::default(); - - brillig.convert_ssa_function(func); - - brillig.context.artifact() - } - - /// 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); - } - } - - fn convert_ssa_value_to_register_value_or_array( - &mut self, - value_id: ValueId, - dfg: &DataFlowGraph, - ) -> RegisterValueOrArray { - let register_index = self.convert_ssa_value(value_id, dfg); - let typ = dfg[value_id].get_type(); - match typ { - Type::Numeric(_) => RegisterValueOrArray::RegisterIndex(register_index), - Type::Array(_, size) => RegisterValueOrArray::HeapArray(register_index, size), - _ => { - unreachable!("type not supported for conversion into brillig register") - } - } - } -} +use std::collections::HashMap; -/// Returns the type of the operation considering the types of the operands -/// TODO: SSA issues binary operations between fields and integers. -/// This probably should be explicitly casted in SSA to avoid having to coerce at this level. -pub(crate) fn type_of_binary_operation(lhs_type: Type, rhs_type: Type) -> Type { - match (lhs_type, rhs_type) { - // If either side is a Field constant then, we coerce into the type - // of the other operand - (Type::Numeric(NumericType::NativeField), typ) - | (typ, Type::Numeric(NumericType::NativeField)) => typ, - // If both sides are numeric type, then we expect their types to be - // the same. - (Type::Numeric(lhs_type), Type::Numeric(rhs_type)) => { - assert_eq!( - lhs_type, rhs_type, - "lhs and rhs types in a binary operation are always the same" - ); - Type::Numeric(lhs_type) - } - (lhs_type, rhs_type) => { - unreachable!( - "ICE: Binary operation between types {:?} and {:?} is not allowed", - lhs_type, rhs_type - ) - } - } -} +use self::{brillig_block::BrilligBlock, brillig_fn::FunctionContext}; -/// Convert an SSA binary operation into: -/// - Brillig Binary Integer Op, if it is a integer type -/// - Brillig Binary Field Op, if it is a field type -pub(crate) fn convert_ssa_binary_op_to_brillig_binary_op( - ssa_op: BinaryOp, - typ: Type, -) -> BrilligBinaryOp { - // First get the bit size and whether its a signed integer, if it is a numeric type - // if it is not,then we return None, indicating that - // it is a Field. - let bit_size_signedness = match typ { - Type::Numeric(numeric_type) => match numeric_type { - NumericType::Signed { bit_size } => Some((bit_size, true)), - NumericType::Unsigned { bit_size } => Some((bit_size, false)), - NumericType::NativeField => None, - }, - _ => unreachable!("only numeric types are allowed in binary operations. References are handled separately"), - }; +use super::brillig_ir::{artifact::BrilligArtifact, BrilligContext}; - fn binary_op_to_field_op(op: BinaryOp) -> BrilligBinaryOp { - let operation = match op { - BinaryOp::Add => BinaryFieldOp::Add, - BinaryOp::Sub => BinaryFieldOp::Sub, - BinaryOp::Mul => BinaryFieldOp::Mul, - BinaryOp::Div => BinaryFieldOp::Div, - BinaryOp::Eq => BinaryFieldOp::Equals, - _ => unreachable!( - "Field type cannot be used with {op}. This should have been caught by the frontend" - ), - }; +/// 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 ^ +pub(crate) fn convert_ssa_function(func: &Function) -> BrilligArtifact { + let mut reverse_post_order = Vec::new(); + reverse_post_order.extend_from_slice(PostOrder::with_function(func).as_slice()); + reverse_post_order.reverse(); - BrilligBinaryOp::Field { op: operation } - } + let mut function_context = + FunctionContext { function_id: func.id(), ssa_value_to_register: HashMap::new() }; - fn binary_op_to_int_op(op: BinaryOp, bit_size: u32, is_signed: bool) -> BrilligBinaryOp { - let operation = match op { - BinaryOp::Add => BinaryIntOp::Add, - BinaryOp::Sub => BinaryIntOp::Sub, - BinaryOp::Mul => BinaryIntOp::Mul, - BinaryOp::Div => { - if is_signed { - BinaryIntOp::SignedDiv - } else { - BinaryIntOp::UnsignedDiv - } - } - BinaryOp::Mod => { - return BrilligBinaryOp::Modulo { is_signed_integer: is_signed, bit_size } - } - BinaryOp::Eq => BinaryIntOp::Equals, - BinaryOp::Lt => BinaryIntOp::LessThan, - BinaryOp::And => BinaryIntOp::And, - BinaryOp::Or => BinaryIntOp::Or, - BinaryOp::Xor => BinaryIntOp::Xor, - BinaryOp::Shl => BinaryIntOp::Shl, - BinaryOp::Shr => BinaryIntOp::Shr, - }; + let mut brillig_context = BrilligContext::default(); - BrilligBinaryOp::Integer { op: operation, bit_size } + for block in reverse_post_order { + BrilligBlock::compile(&mut function_context, &mut brillig_context, block, &func.dfg); } - // If bit size is available then it is a binary integer operation - match bit_size_signedness { - Some((bit_size, is_signed)) => binary_op_to_int_op(ssa_op, bit_size, is_signed), - None => binary_op_to_field_op(ssa_op), - } + 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 new file mode 100644 index 00000000000..ab07bc98d52 --- /dev/null +++ b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -0,0 +1,409 @@ +use crate::brillig::brillig_ir::{BrilligBinaryOp, BrilligContext}; +use crate::ssa_refactor::ir::{ + basic_block::{BasicBlock, BasicBlockId}, + dfg::DataFlowGraph, + instruction::{Binary, BinaryOp, Instruction, InstructionId, TerminatorInstruction}, + types::{NumericType, Type}, + value::{Value, ValueId}, +}; +use acvm::acir::brillig_vm::{BinaryFieldOp, BinaryIntOp, RegisterIndex, RegisterValueOrArray}; +use acvm::FieldElement; +use iter_extended::vecmap; + +use super::brillig_fn::FunctionContext; + +/// Generate the compilation artifacts for compiling a function into brillig bytecode. +pub(crate) struct BrilligBlock<'block> { + function_context: &'block mut FunctionContext, + /// The basic block that is being converted + block_id: BasicBlockId, + /// Context for creating brillig opcodes + brillig_context: &'block mut BrilligContext, +} + +impl<'block> BrilligBlock<'block> { + /// Converts an SSA Basic block into a sequence of Brillig opcodes + pub(crate) fn compile( + function_context: &'block mut FunctionContext, + brillig_context: &'block mut BrilligContext, + block_id: BasicBlockId, + dfg: &DataFlowGraph, + ) { + let mut brillig_block = BrilligBlock { function_context, block_id, brillig_context }; + + brillig_block.convert_block(dfg); + } + + fn convert_block(&mut self, dfg: &DataFlowGraph) { + // Add a label for this block + let block_label = self.create_block_label(self.block_id); + self.brillig_context.enter_context(block_label); + + // Convert the block parameters + let block = &dfg[self.block_id]; + self.convert_block_params(block, dfg); + + // Convert all of the instructions into the block + for instruction_id in block.instructions() { + self.convert_ssa_instruction(*instruction_id, dfg); + } + + // Process the block's terminator instruction + let terminator_instruction = + block.terminator().expect("block is expected to be constructed"); + 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) + } + + /// Converts an SSA terminator instruction into the necessary opcodes. + /// + /// TODO: document why the TerminatorInstruction::Return includes a stop instruction + /// TODO along with the `Self::compile` + fn convert_ssa_terminator( + &mut self, + terminator_instruction: &TerminatorInstruction, + dfg: &DataFlowGraph, + ) { + 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)); + } + 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.brillig_context.mov_instruction(destination, source); + } + self.brillig_context.jump_instruction(self.create_block_label(*destination)); + } + TerminatorInstruction::Return { return_values } => { + let return_registers: Vec<_> = return_values + .iter() + .map(|value_id| self.convert_ssa_value(*value_id, dfg)) + .collect(); + self.brillig_context.return_instruction(&return_registers); + } + } + } + + /// Converts SSA Block parameters into Brillig Registers. + fn convert_block_params(&mut self, block: &BasicBlock, dfg: &DataFlowGraph) { + for param_id in block.parameters() { + let value = &dfg[*param_id]; + let param_type = match value { + Value::Param { typ, .. } => typ, + _ => unreachable!("ICE: Only Param type values should appear in block parameters"), + }; + match param_type { + Type::Numeric(_) => { + 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_array(pointer_register, *size as u32); + } + _ => { + todo!("ICE: Param type not supported") + } + } + } + } + + /// Converts an SSA instruction into a sequence of Brillig opcodes. + fn convert_ssa_instruction(&mut self, instruction_id: InstructionId, dfg: &DataFlowGraph) { + let instruction = &dfg[instruction_id]; + + match instruction { + Instruction::Binary(binary) => { + let result_ids = dfg.instruction_results(instruction_id); + let result_register = self + .function_context + .get_or_create_register(self.brillig_context, result_ids[0]); + self.convert_ssa_binary(binary, dfg, result_register); + } + Instruction::Constrain(value) => { + let condition = self.convert_ssa_value(*value, dfg); + self.brillig_context.constrain_instruction(condition); + } + Instruction::Allocate => { + let pointer_register = self.function_context.get_or_create_register( + self.brillig_context, + dfg.instruction_results(instruction_id)[0], + ); + self.brillig_context.allocate_array(pointer_register, 1); + } + Instruction::Store { address, value } => { + let address_register = self.convert_ssa_value(*address, dfg); + let value_register = self.convert_ssa_value(*value, dfg); + self.brillig_context.store_instruction(address_register, value_register); + } + Instruction::Load { address } => { + let target_register = self.function_context.get_or_create_register( + self.brillig_context, + dfg.instruction_results(instruction_id)[0], + ); + let address_register = self.convert_ssa_value(*address, dfg); + self.brillig_context.load_instruction(target_register, address_register); + } + Instruction::Not(value) => { + assert_eq!( + dfg.type_of_value(*value), + Type::bool(), + "not operator can only be applied to boolean values" + ); + let condition = self.convert_ssa_value(*value, dfg); + let result_ids = dfg.instruction_results(instruction_id); + let result_register = self + .function_context + .get_or_create_register(self.brillig_context, result_ids[0]); + + self.brillig_context.not_instruction(condition, result_register); + } + Instruction::Call { func, arguments } => match &dfg[*func] { + Value::ForeignFunction(func_name) => { + let result_ids = dfg.instruction_results(instruction_id); + + let input_registers = vecmap(arguments, |value_id| { + self.convert_ssa_value_to_register_value_or_array(*value_id, dfg) + }); + let output_registers = vecmap(result_ids, |value_id| { + self.convert_ssa_value_to_register_value_or_array(*value_id, dfg) + }); + + self.brillig_context.foreign_call_instruction( + func_name.to_owned(), + &input_registers, + &output_registers, + ); + } + _ => { + unreachable!("only foreign function calls supported in unconstrained functions") + } + }, + Instruction::Truncate { value, .. } => { + let result_ids = dfg.instruction_results(instruction_id); + let destination = self + .function_context + .get_or_create_register(self.brillig_context, result_ids[0]); + let source = self.convert_ssa_value(*value, dfg); + self.brillig_context.truncate_instruction(destination, source); + } + Instruction::Cast(value, target_type) => { + let result_ids = dfg.instruction_results(instruction_id); + let destination = self + .function_context + .get_or_create_register(self.brillig_context, result_ids[0]); + let source = self.convert_ssa_value(*value, dfg); + self.convert_cast(destination, source, target_type, &dfg.type_of_value(*value)); + } + _ => todo!("ICE: Instruction not supported {instruction:?}"), + }; + } + + /// Converts an SSA cast to a sequence of Brillig opcodes. + /// Casting is only necessary when shrinking the bit size of a numeric value. + fn convert_cast( + &mut self, + destination: RegisterIndex, + source: RegisterIndex, + target_type: &Type, + source_type: &Type, + ) { + fn numeric_to_bit_size(typ: &NumericType) -> u32 { + match typ { + NumericType::Signed { bit_size } | NumericType::Unsigned { bit_size } => *bit_size, + NumericType::NativeField => FieldElement::max_num_bits(), + } + } + // Casting is only valid for numeric types + // This should be checked by the frontend, so we panic if this is the case + let (source_numeric_type, target_numeric_type) = match (source_type, target_type) { + (Type::Numeric(source_numeric_type), Type::Numeric(target_numeric_type)) => { + (source_numeric_type, target_numeric_type) + } + _ => unimplemented!("The cast operation is only valid for integers."), + }; + let source_bit_size = numeric_to_bit_size(source_numeric_type); + let target_bit_size = numeric_to_bit_size(target_numeric_type); + // Casting from a larger bit size to a smaller bit size (narrowing cast) + // requires a cast instruction. + // If its a widening cast, ie casting from a smaller bit size to a larger bit size + // we simply put a mov instruction as a no-op + // + // Field elements by construction always have the largest bit size + // This means that casting to a Field element, will always be a widening cast + // and therefore a no-op. Conversely, casting from a Field element + // will always be a narrowing cast and therefore a cast instruction + if source_bit_size > target_bit_size { + self.brillig_context.cast_instruction(destination, source, target_bit_size); + } else { + self.brillig_context.mov_instruction(destination, source); + } + } + + /// Converts the Binary instruction into a sequence of Brillig opcodes. + fn convert_ssa_binary( + &mut self, + binary: &Binary, + dfg: &DataFlowGraph, + result_register: RegisterIndex, + ) { + let binary_type = + type_of_binary_operation(dfg[binary.lhs].get_type(), dfg[binary.rhs].get_type()); + + let left = self.convert_ssa_value(binary.lhs, dfg); + let right = self.convert_ssa_value(binary.rhs, dfg); + + let brillig_binary_op = + convert_ssa_binary_op_to_brillig_binary_op(binary.operator, binary_type); + + self.brillig_context.binary_instruction(left, right, result_register, brillig_binary_op); + } + + /// Converts an SSA `ValueId` into a `RegisterIndex`. + fn convert_ssa_value(&mut self, value_id: ValueId, dfg: &DataFlowGraph) -> RegisterIndex { + let value = &dfg[value_id]; + + let register = match value { + Value::Param { .. } | Value::Instruction { .. } => { + // All block parameters and instruction results should have already been + // converted to registers so we fetch from the cache. + self.function_context.get_or_create_register(self.brillig_context, value_id) + } + Value::NumericConstant { constant, .. } => { + let register_index = self + .function_context + .get_or_create_register(self.brillig_context, value_id); + + self.brillig_context.const_instruction(register_index, (*constant).into()); + register_index + } + _ => { + todo!("ICE: Should have been in cache {value:?}") + } + }; + register + } + + fn convert_ssa_value_to_register_value_or_array( + &mut self, + value_id: ValueId, + dfg: &DataFlowGraph, + ) -> RegisterValueOrArray { + let register_index = self.convert_ssa_value(value_id, dfg); + let typ = dfg[value_id].get_type(); + match typ { + Type::Numeric(_) => RegisterValueOrArray::RegisterIndex(register_index), + Type::Array(_, size) => RegisterValueOrArray::HeapArray(register_index, size), + _ => { + unreachable!("type not supported for conversion into brillig register") + } + } + } +} + +/// Returns the type of the operation considering the types of the operands +/// TODO: SSA issues binary operations between fields and integers. +/// This probably should be explicitly casted in SSA to avoid having to coerce at this level. +pub(crate) fn type_of_binary_operation(lhs_type: Type, rhs_type: Type) -> Type { + match (lhs_type, rhs_type) { + // If either side is a Field constant then, we coerce into the type + // of the other operand + (Type::Numeric(NumericType::NativeField), typ) + | (typ, Type::Numeric(NumericType::NativeField)) => typ, + // If both sides are numeric type, then we expect their types to be + // the same. + (Type::Numeric(lhs_type), Type::Numeric(rhs_type)) => { + assert_eq!( + lhs_type, rhs_type, + "lhs and rhs types in a binary operation are always the same" + ); + Type::Numeric(lhs_type) + } + (lhs_type, rhs_type) => { + unreachable!( + "ICE: Binary operation between types {:?} and {:?} is not allowed", + lhs_type, rhs_type + ) + } + } +} + +/// Convert an SSA binary operation into: +/// - Brillig Binary Integer Op, if it is a integer type +/// - Brillig Binary Field Op, if it is a field type +pub(crate) fn convert_ssa_binary_op_to_brillig_binary_op( + ssa_op: BinaryOp, + typ: Type, +) -> BrilligBinaryOp { + // First get the bit size and whether its a signed integer, if it is a numeric type + // if it is not,then we return None, indicating that + // it is a Field. + let bit_size_signedness = match typ { + Type::Numeric(numeric_type) => match numeric_type { + NumericType::Signed { bit_size } => Some((bit_size, true)), + NumericType::Unsigned { bit_size } => Some((bit_size, false)), + NumericType::NativeField => None, + }, + _ => unreachable!("only numeric types are allowed in binary operations. References are handled separately"), + }; + + fn binary_op_to_field_op(op: BinaryOp) -> BrilligBinaryOp { + let operation = match op { + BinaryOp::Add => BinaryFieldOp::Add, + BinaryOp::Sub => BinaryFieldOp::Sub, + BinaryOp::Mul => BinaryFieldOp::Mul, + BinaryOp::Div => BinaryFieldOp::Div, + BinaryOp::Eq => BinaryFieldOp::Equals, + _ => unreachable!( + "Field type cannot be used with {op}. This should have been caught by the frontend" + ), + }; + + BrilligBinaryOp::Field { op: operation } + } + + fn binary_op_to_int_op(op: BinaryOp, bit_size: u32, is_signed: bool) -> BrilligBinaryOp { + let operation = match op { + BinaryOp::Add => BinaryIntOp::Add, + BinaryOp::Sub => BinaryIntOp::Sub, + BinaryOp::Mul => BinaryIntOp::Mul, + BinaryOp::Div => { + if is_signed { + BinaryIntOp::SignedDiv + } else { + BinaryIntOp::UnsignedDiv + } + } + BinaryOp::Mod => { + return BrilligBinaryOp::Modulo { is_signed_integer: is_signed, bit_size } + } + BinaryOp::Eq => BinaryIntOp::Equals, + BinaryOp::Lt => BinaryIntOp::LessThan, + BinaryOp::And => BinaryIntOp::And, + BinaryOp::Or => BinaryIntOp::Or, + BinaryOp::Xor => BinaryIntOp::Xor, + BinaryOp::Shl => BinaryIntOp::Shl, + BinaryOp::Shr => BinaryIntOp::Shr, + }; + + BrilligBinaryOp::Integer { op: operation, bit_size } + } + + // If bit size is available then it is a binary integer operation + match bit_size_signedness { + Some((bit_size, is_signed)) => binary_op_to_int_op(ssa_op, bit_size, is_signed), + None => binary_op_to_field_op(ssa_op), + } +} diff --git a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_fn.rs b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_fn.rs new file mode 100644 index 00000000000..76b660d1c5b --- /dev/null +++ b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_fn.rs @@ -0,0 +1,41 @@ +use std::collections::HashMap; + +use acvm::acir::brillig_vm::RegisterIndex; + +use crate::{ + brillig::brillig_ir::BrilligContext, + ssa_refactor::ir::{function::FunctionId, value::ValueId}, +}; + +pub(crate) struct FunctionContext { + pub(crate) function_id: FunctionId, + /// Map from SSA values to Register Indices. + pub(crate) ssa_value_to_register: HashMap, +} + +impl FunctionContext { + /// Gets a `RegisterIndex` for a `ValueId`, if one already exists + /// or creates a new `RegisterIndex` using the latest available + /// free register. + pub(crate) fn get_or_create_register( + &mut self, + brillig_context: &mut BrilligContext, + value: ValueId, + ) -> RegisterIndex { + if let Some(register_index) = self.ssa_value_to_register.get(&value) { + return *register_index; + } + + let register = brillig_context.create_register(); + + // Cache the `ValueId` so that if we call it again, it will + // return the register that has just been created. + // + // WARNING: This assumes that a register has not been + // modified. If a MOV instruction has overwritten the value + // at a register, then this cache will be invalid. + self.ssa_value_to_register.insert(value, register); + + register + } +} diff --git a/crates/noirc_evaluator/src/brillig/brillig_ir.rs b/crates/noirc_evaluator/src/brillig/brillig_ir.rs index 5dd0ce1bc4b..cc9b207e9f2 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_ir.rs @@ -40,6 +40,11 @@ pub(crate) struct BrilligContext { latest_register: usize, /// Tracks memory allocations memory: BrilligMemory, + /// Context label, must be unique with respect to the function + /// being linked. + context_label: String, + /// Section label, used to separate sections of code + section_label: usize, } impl BrilligContext { @@ -64,16 +69,41 @@ impl BrilligContext { } /// Adds a label to the next opcode - pub(crate) fn add_label_to_next_opcode(&mut self, label: T) { + pub(crate) fn enter_context(&mut self, label: T) { + self.context_label = label.to_string(); + self.section_label = 0; + // Add a context label to the next opcode self.obj.add_label_at_position(label.to_string(), self.obj.index_of_next_opcode()); + // Add a section label to the next opcode + self.obj + .add_label_at_position(self.current_section_label(), self.obj.index_of_next_opcode()); + } + + /// Increments the section label and adds a section label to the next opcode + fn enter_next_section(&mut self) { + self.section_label += 1; + self.obj + .add_label_at_position(self.current_section_label(), self.obj.index_of_next_opcode()); + } + + /// Internal function used to compute the section labels + fn compute_section_label(&self, section: usize) -> String { + format!("{}-{}", self.context_label, section) + } + + /// Returns the next section label + fn next_section_label(&self) -> String { + self.compute_section_label(self.section_label + 1) + } + + /// Returns the current section label + fn current_section_label(&self) -> String { + self.compute_section_label(self.section_label) } /// Adds a unresolved `Jump` instruction to the bytecode. pub(crate) fn jump_instruction(&mut self, target_label: T) { - self.add_unresolved_jump( - BrilligOpcode::Jump { location: 0 }, - UnresolvedJumpLocation::Label(target_label.to_string()), - ); + self.add_unresolved_jump(BrilligOpcode::Jump { location: 0 }, target_label.to_string()); } /// Adds a unresolved `JumpIf` instruction to the bytecode. @@ -84,7 +114,7 @@ impl BrilligContext { ) { self.add_unresolved_jump( BrilligOpcode::JumpIf { condition, location: 0 }, - UnresolvedJumpLocation::Label(target_label.to_string()), + target_label.to_string(), ); } @@ -109,12 +139,12 @@ impl BrilligContext { /// Emits brillig bytecode to jump to a trap condition if `condition` /// is false. pub(crate) fn constrain_instruction(&mut self, condition: RegisterIndex) { - // Jump to the relative location after the trap self.add_unresolved_jump( BrilligOpcode::JumpIf { condition, location: 0 }, - UnresolvedJumpLocation::Relative(2), + self.next_section_label(), ); self.push_opcode(BrilligOpcode::Trap); + self.enter_next_section(); } /// Processes a return instruction. diff --git a/crates/noirc_evaluator/src/brillig/brillig_ir/artifact.rs b/crates/noirc_evaluator/src/brillig/brillig_ir/artifact.rs index 8d0020ea00f..3c6be408a32 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_ir/artifact.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_ir/artifact.rs @@ -26,7 +26,6 @@ pub(crate) type JumpInstructionPosition = OpcodeLocation; /// When constructing the bytecode, there may be instructions /// which require one to jump to a specific region of code (function) -/// or a position relative to the current instruction. /// /// The position of a function cannot always be known /// at this point in time, so Jumps are unresolved @@ -34,17 +33,7 @@ pub(crate) type JumpInstructionPosition = OpcodeLocation; /// `Label` is used as the jump location and once all of the bytecode /// has been processed, the jumps are resolved using a map from Labels /// to their position in the bytecode. -/// -/// Sometimes the jump destination may be relative to the jump instruction. -/// Since the absolute position in the bytecode cannot be known until -/// all internal and external functions have been linked, jumps of this -/// nature cannot be fully resolved while building the bytecode either. -/// We add relative jumps into the `Relative` variant of this enum. -#[derive(Debug, Clone)] -pub(crate) enum UnresolvedJumpLocation { - Label(String), - Relative(i32), -} +pub(crate) type UnresolvedJumpLocation = Label; impl BrilligArtifact { /// Link two Brillig artifacts together and resolve all unresolved jump instructions. @@ -66,7 +55,8 @@ impl BrilligArtifact { } for (label_id, position_in_bytecode) in &obj.labels { - self.labels.insert(label_id.clone(), position_in_bytecode + offset); + let old_value = self.labels.insert(label_id.clone(), position_in_bytecode + offset); + assert!(old_value.is_none(), "overwriting label {label_id} {old_value:?}"); } self.byte_code.extend_from_slice(&obj.byte_code); @@ -126,12 +116,7 @@ impl BrilligArtifact { /// linkage with other bytecode has happened. fn resolve_jumps(&mut self) { for (location_of_jump, unresolved_location) in &self.unresolved_jumps { - let resolved_location = match unresolved_location { - UnresolvedJumpLocation::Label(label) => self.labels[label], - UnresolvedJumpLocation::Relative(offset) => { - (offset + *location_of_jump as i32) as usize - } - }; + let resolved_location = self.labels[unresolved_location]; let jump_instruction = self.byte_code[*location_of_jump].clone(); match jump_instruction { diff --git a/crates/noirc_evaluator/src/brillig/mod.rs b/crates/noirc_evaluator/src/brillig/mod.rs index 4e8ea271f92..19d750a4544 100644 --- a/crates/noirc_evaluator/src/brillig/mod.rs +++ b/crates/noirc_evaluator/src/brillig/mod.rs @@ -1,7 +1,7 @@ pub(crate) mod brillig_gen; pub(crate) mod brillig_ir; -use self::{brillig_gen::BrilligGen, brillig_ir::artifact::BrilligArtifact}; +use self::{brillig_gen::convert_ssa_function, brillig_ir::artifact::BrilligArtifact}; use crate::ssa_refactor::{ ir::function::{Function, FunctionId, RuntimeType}, ssa_gen::Ssa, @@ -19,7 +19,7 @@ pub struct Brillig { impl Brillig { /// Compiles a function into brillig and store the compilation artifacts pub(crate) fn compile(&mut self, func: &Function) { - let obj = BrilligGen::compile(func); + let obj = convert_ssa_function(func); self.ssa_function_to_brillig.insert(func.id(), obj); } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs index b931c061b22..e7f9d812de3 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/map.rs @@ -99,6 +99,12 @@ impl std::fmt::Display for Id { } } +impl std::fmt::Display for Id { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "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