diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_cast/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_cast/Nargo.toml new file mode 100644 index 00000000000..e0b467ce5da --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_cast/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_cast/Prover.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_cast/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_cast/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_cast/src/main.nr new file mode 100644 index 00000000000..e258a8f2640 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_cast/src/main.nr @@ -0,0 +1,50 @@ +// Tests a very simple Brillig function. +// +// The features being tested are cast operations on brillig +fn main() { + bool_casts(); + field_casts(); + uint_casts(); + int_casts(); + mixed_casts(); +} + +unconstrained fn bool_casts() { + assert(false == 0 as bool); + assert(true == 1 as bool); + assert(true == 3 as bool); +} + +unconstrained fn field_casts() { + assert(5 as u8 as Field == 5); + assert(16 as u4 as Field == 0); +} + +unconstrained fn uint_casts() { + let x: u32 = 100; + assert(x as u2 == 0); + assert(x as u4 == 4); + assert(x as u6 == 36); + assert(x as u8 == 100); + assert(x as u64 == 100); + assert(x as u126 == 100); +} + +unconstrained fn int_casts() { + let x: i32 = 100; + assert(x as i2 == 0); + assert(x as i4 == 4); + assert(x as i6 == -28 as i6); + assert(x as i8 == 100); + assert(x as i8 == 100); + assert(x as i8 == 100); +} + + +unconstrained fn mixed_casts() { + assert(100 as u32 as i32 as u32 == 100); + assert(13 as u4 as i2 as u32 == 1); + assert(15 as u4 as i2 as u32 == 3); + assert(1 as u8 as bool == true); + assert(true as i8 == 1); +} diff --git a/crates/noirc_evaluator/src/brillig/brillig_gen.rs b/crates/noirc_evaluator/src/brillig/brillig_gen.rs index ffb826acc3d..2cb3045f915 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_gen.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_gen.rs @@ -8,7 +8,10 @@ use crate::ssa_refactor::ir::{ types::{NumericType, Type}, value::{Value, ValueId}, }; -use acvm::acir::brillig_vm::{BinaryFieldOp, BinaryIntOp, RegisterIndex, RegisterValueOrArray}; +use acvm::{ + acir::brillig_vm::{BinaryFieldOp, BinaryIntOp, RegisterIndex, RegisterValueOrArray}, + FieldElement, +}; use iter_extended::vecmap; use std::collections::HashMap; @@ -190,10 +193,60 @@ impl BrilligGen { 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(), + } + } + + // 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.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, diff --git a/crates/noirc_evaluator/src/brillig/brillig_ir.rs b/crates/noirc_evaluator/src/brillig/brillig_ir.rs index b6ce8e22d8e..5dd0ce1bc4b 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_ir.rs @@ -19,6 +19,18 @@ use acvm::{ FieldElement, }; +/// Integer arithmetic in Brillig is limited to 127 bit +/// integers. +/// +/// We could lift this in the future and have Brillig +/// do big integer arithmetic when it exceeds the field size +/// or we could have users re-implement big integer arithmetic +/// in Brillig. +/// Since constrained functions do not have this property, it +/// would mean that unconstrained functions will differ from +/// constrained functions in terms of syntax compatibility. +const BRILLIG_INTEGER_ARITHMETIC_BIT_SIZE: u32 = 127; + /// Brillig context object that is used while constructing the /// Brillig bytecode. #[derive(Default)] @@ -302,6 +314,34 @@ impl BrilligContext { rhs: scratch_register_j, }); } + + /// Emits a modulo instruction against 2**target_bit_size + /// + /// Integer arithmetic in Brillig is currently constrained to 127 bit integers. + /// We restrict the cast operation, so that integer types over 127 bits + /// cannot be created. + pub(crate) fn cast_instruction( + &mut self, + destination: RegisterIndex, + source: RegisterIndex, + target_bit_size: u32, + ) { + assert!( + target_bit_size <= BRILLIG_INTEGER_ARITHMETIC_BIT_SIZE, + "tried to cast to a bit size greater than allowed {target_bit_size}" + ); + + // The brillig VM performs all arithmetic operations modulo 2**bit_size + // So to cast any value to a target bit size we can just issue a no-op arithmetic operation + // With bit size equal to target_bit_size + let zero = self.make_constant(Value::from(FieldElement::zero())); + self.binary_instruction( + source, + zero, + destination, + BrilligBinaryOp::Integer { op: BinaryIntOp::Add, bit_size: target_bit_size }, + ); + } } /// Type to encapsulate the binary operation types in Brillig diff --git a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs index 3dcaf3de7b2..cb5f8a230fe 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs @@ -181,7 +181,7 @@ impl Context { // Generate the brillig code of the function let code = BrilligArtifact::default().link(&brillig[*id]); - let outputs: Vec = vecmap(result_ids, |result_id| dfg.type_of_value(*result_id).into()); + let outputs: Vec = vecmap(result_ids, |result_id| dfg.type_of_value(*result_id).into()); let output_values = self.acir_context.brillig(code, inputs, outputs); // Compiler sanity check