diff --git a/Cargo.lock b/Cargo.lock index 0227b1698bf..90260cc3009 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "acir" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcc9e8065170b7d47cf6718e7788c65c01609a86127eacd885a952b16f46b72" +checksum = "cafbc2adec7783509c6e06831ef57e672fc89c963c855a5fe0449c1ebb2822bc" dependencies = [ "acir_field", "brillig_vm", @@ -18,9 +18,9 @@ dependencies = [ [[package]] name = "acir_field" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a43b59e8419f485a2a15ababa828e27fd20c00ba2ce90e78efe165df7d47ac0" +checksum = "6a33855c911b77f0eddd9e0a6b9410238a5ba80a4956dcf335dd65dd4d2303d6" dependencies = [ "ark-bn254", "ark-ff", @@ -32,9 +32,9 @@ dependencies = [ [[package]] name = "acvm" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce0a3b770cf70b29fd41313bb0baf2a920475fd782bd7a37822f5ee1406b2231" +checksum = "0ad7d5020f3cee249e162504deeeb1a83fa2a727e4c6a5277136bdecbe82cf2b" dependencies = [ "acir", "acvm_stdlib", @@ -71,9 +71,9 @@ dependencies = [ [[package]] name = "acvm_stdlib" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9adc16581262b8571551d029a48118c05b5d02371eb2d10af6a633bb670aec" +checksum = "4c9bf39b1e3b486f090ca1c0ef1e7d47fabfdb522af58117d1d86de048ac0571" dependencies = [ "acir", ] @@ -514,9 +514,9 @@ dependencies = [ [[package]] name = "brillig_vm" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f81af1fa12f95a86b2c1a1c15aadc186265853698e3098f5ce9966d1aa64cca" +checksum = "a577e930e991623dd1ec5f9540b20eb601d06ae9d7f66181b81ff699441fc159" dependencies = [ "acir_field", "num-bigint", diff --git a/Cargo.toml b/Cargo.toml index 8459010651c..38231e4bb24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ edition = "2021" rust-version = "1.66" [workspace.dependencies] -acvm = "=0.14.1" +acvm = "=0.14.2" arena = { path = "crates/arena" } fm = { path = "crates/fm" } iter-extended = { path = "crates/iter-extended" } 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/nargo_cli/tests/test_data_ssa_refactor/brillig_field_binary_operations/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_field_binary_operations/Nargo.toml new file mode 100644 index 00000000000..e0b467ce5da --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_field_binary_operations/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_field_binary_operations/Prover.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_field_binary_operations/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_field_binary_operations/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_field_binary_operations/src/main.nr new file mode 100644 index 00000000000..e7b0afccc3e --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_field_binary_operations/src/main.nr @@ -0,0 +1,25 @@ +// Tests arithmetic operations on fields +fn main() { + let x = 4; + let y = 2; + assert((x + y) == add(x, y)); + assert((x - y) == sub(x, y)); + assert((x * y) == mul(x, y)); + assert((x / y) == div(x, y)); +} + +unconstrained fn add(x : Field, y : Field) -> Field { + x + y +} + +unconstrained fn sub(x : Field, y : Field) -> Field { + x - y +} + +unconstrained fn mul(x : Field, y : Field) -> Field { + x * y +} + +unconstrained fn div(x : Field, y : Field) -> Field { + x / y +} \ No newline at end of file diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_identity_function/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_identity_function/Nargo.toml new file mode 100644 index 00000000000..e0b467ce5da --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_identity_function/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_identity_function/Prover.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_identity_function/Prover.toml new file mode 100644 index 00000000000..55cccb955a9 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_identity_function/Prover.toml @@ -0,0 +1,2 @@ +x = "3" + diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_identity_function/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_identity_function/src/main.nr new file mode 100644 index 00000000000..cb6ce1ae2f4 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_identity_function/src/main.nr @@ -0,0 +1,10 @@ +// Tests a very simple program. +// +// The features being tested is the identity function in Brillig +fn main(x : Field) { + assert(x == identity(x)); +} + +unconstrained fn identity(x : Field) -> Field { + x +} \ No newline at end of file diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_integer_binary_operations/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_integer_binary_operations/Nargo.toml new file mode 100644 index 00000000000..e0b467ce5da --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_integer_binary_operations/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_integer_binary_operations/Prover.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_integer_binary_operations/Prover.toml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_integer_binary_operations/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_integer_binary_operations/src/main.nr new file mode 100644 index 00000000000..72f614f1e63 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_integer_binary_operations/src/main.nr @@ -0,0 +1,80 @@ +// Tests arithmetic operations on integers +fn main() { + let x: u32 = 6; + let y: u32 = 2; + + assert((x + y) == add(x, y)); + + assert((x - y) == sub(x, y)); + + assert((x * y) == mul(x, y)); + + assert((x / y) == div(x, y)); + + // TODO SSA => ACIR has some issues with i32 ops + assert(check_signed_div(6, 2, 3)); + + assert(eq(1, 2) == false); + assert(eq(1, 1)); + + assert(lt(x, y) == false); + assert(lt(y, x)); + + assert((x & y) == and(x, y)); + assert((x | y) == or(x, y)); + + // TODO SSA => ACIR has some issues with xor ops + + assert(check_xor(x, y, 4)); + assert((x >> y) == shr(x, y)); + assert((x << y) == shl(x, y)); +} + +unconstrained fn add(x : u32, y : u32) -> u32 { + x + y +} + +unconstrained fn sub(x : u32, y : u32) -> u32 { + x - y +} + +unconstrained fn mul(x : u32, y : u32) -> u32 { + x * y +} + +unconstrained fn div(x : u32, y : u32) -> u32 { + x / y +} + +unconstrained fn check_signed_div(x: i32, y: i32, result: i32) -> bool { + (x / y) == result +} + +unconstrained fn eq(x : u32, y : u32) -> bool { + x == y +} + +unconstrained fn lt(x : u32, y : u32) -> bool { + x < y +} + +unconstrained fn and(x : u32, y : u32) -> u32 { + x & y +} + +unconstrained fn or(x : u32, y : u32) -> u32 { + x | y +} + +unconstrained fn check_xor(x : u32, y : u32, result: u32) -> bool { + (x ^ y) == result +} + +unconstrained fn shr(x : u32, y : u32) -> u32 { + x >> y +} + +unconstrained fn shl(x : u32, y : u32) -> u32 { + x << y +} + diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_loop/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_loop/Nargo.toml new file mode 100644 index 00000000000..e0b467ce5da --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_loop/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_loop/Prover.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_loop/Prover.toml new file mode 100644 index 00000000000..22cd5b7c12f --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_loop/Prover.toml @@ -0,0 +1 @@ +sum = "6" diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_loop/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_loop/src/main.nr new file mode 100644 index 00000000000..7240264b2a8 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_loop/src/main.nr @@ -0,0 +1,14 @@ +// Tests a very simple program. +// +// The features being tested is basic looping on brillig +fn main(sum: u32){ + assert(loop(4) == sum); +} + +unconstrained fn loop(x: u32) -> u32 { + let mut sum = 0; + for i in 0..x { + sum = sum + i; + } + sum +} diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_not/Nargo.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_not/Nargo.toml new file mode 100644 index 00000000000..e0b467ce5da --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_not/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_not/Prover.toml b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_not/Prover.toml new file mode 100644 index 00000000000..a0397e89477 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_not/Prover.toml @@ -0,0 +1,2 @@ +x = "1" +y = "0" diff --git a/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_not/src/main.nr b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_not/src/main.nr new file mode 100644 index 00000000000..bc94810efb9 --- /dev/null +++ b/crates/nargo_cli/tests/test_data_ssa_refactor/brillig_not/src/main.nr @@ -0,0 +1,11 @@ +// Tests a very simple Brillig function. +// +// The features being tested is not instruction on brillig +fn main(x: Field, y : Field) { + assert(false == not_operator(x as bool)); + assert(true == not_operator(y as bool)); +} + +unconstrained fn not_operator(x : bool) -> bool { + !x +} \ 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/binary.rs b/crates/noirc_evaluator/src/brillig/binary.rs new file mode 100644 index 00000000000..93b307e1b70 --- /dev/null +++ b/crates/noirc_evaluator/src/brillig/binary.rs @@ -0,0 +1,106 @@ +use crate::ssa_refactor::ir::{ + instruction::BinaryOp, + types::{NumericType, Type}, +}; +use acvm::acir::brillig_vm::{BinaryFieldOp, BinaryIntOp}; + +/// Type to encapsulate the binary operation types in Brillig +pub(crate) enum BrilligBinaryOp { + Field { op: BinaryFieldOp }, + Integer { op: BinaryIntOp, bit_size: u32 }, +} + +impl BrilligBinaryOp { + /// 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) -> BinaryFieldOp { + 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" + ), + } + } + fn binary_op_to_int_op(op: BinaryOp, is_signed: bool) -> BinaryIntOp { + 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 => todo!("This is not supported by Brillig. It should either be added into Brillig or legalized by the SSA IR"), + 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, + } + } + // If bit size is available then it is a binary integer operation + match bit_size_signedness { + Some((bit_size, is_signed)) => { + let binary_int_op = binary_op_to_int_op(ssa_op, is_signed); + BrilligBinaryOp::Integer { op: binary_int_op, bit_size } + } + None => { + let binary_field_op = binary_op_to_field_op(ssa_op); + BrilligBinaryOp::Field { op: binary_field_op } + } + } + } +} + +/// 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 explicitely 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 + ) + } + } +} diff --git a/crates/noirc_evaluator/src/brillig/brillig_gen.rs b/crates/noirc_evaluator/src/brillig/brillig_gen.rs index 7d862ef1f79..519e341b2be 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_gen.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_gen.rs @@ -1,26 +1,320 @@ -use crate::ssa_refactor::ir::function::Function; +use super::{ + artifact::BrilligArtifact, + binary::{type_of_binary_operation, BrilligBinaryOp}, + memory::BrilligMemory, +}; +use crate::ssa_refactor::ir::{ + basic_block::{BasicBlock, BasicBlockId}, + dfg::DataFlowGraph, + function::Function, + instruction::{Binary, Instruction, InstructionId, TerminatorInstruction}, + post_order::PostOrder, + types::Type, + value::{Value, ValueId}, +}; +use acvm::{ + acir::brillig_vm::{ + BinaryIntOp, Opcode as BrilligOpcode, RegisterIndex, Value as BrilligValue, + }, + FieldElement, +}; +use std::collections::HashMap; -use super::artifact::BrilligArtifact; - -use acvm::acir::brillig_vm::Opcode as BrilligOpcode; #[derive(Default)] /// Generate the compilation artifacts for compiling a function into brillig bytecode. pub(crate) struct BrilligGen { obj: BrilligArtifact, + /// A usize indicating the latest un-used register. + latest_register: usize, + /// Map from SSA values to Register Indices. + ssa_value_to_register: HashMap, + /// Tracks memory allocations + memory: BrilligMemory, } impl BrilligGen { - /// Adds a brillig instruction to the brillig code base + /// Adds a brillig instruction to the brillig byte code fn push_code(&mut self, code: BrilligOpcode) { self.obj.byte_code.push(code); } + /// 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.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 + } + + /// Creates a new register. + fn create_register(&mut self) -> RegisterIndex { + let register = RegisterIndex::from(self.latest_register); + self.latest_register += 1; + register + } + + /// Converts an SSA Basic block into a sequence of Brillig opcodes + fn convert_block(&mut self, block_id: BasicBlockId, dfg: &DataFlowGraph) { + self.obj.add_block_label(block_id); + let block = &dfg[block_id]; + self.convert_block_params(block, dfg); + + for instruction_id in block.instructions() { + self.convert_ssa_instruction(*instruction_id, 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 + /// opcode. + /// + /// 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, return_values: &[ValueId], dfg: &DataFlowGraph) { + for (destination_index, value_id) in return_values.iter().enumerate() { + let return_register = self.convert_ssa_value(*value_id, dfg); + if destination_index > self.latest_register { + self.latest_register = destination_index; + } + self.push_code(BrilligOpcode::Mov { + destination: destination_index.into(), + source: return_register, + }); + } + self.push_code(BrilligOpcode::Stop); + } + + /// 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); + } + _ => { + 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::Allocate => { + let pointer_register = + self.get_or_create_register(dfg.instruction_results(instruction_id)[0]); + self.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.push_code(BrilligOpcode::Store { + destination_pointer: address_register, + source: 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.push_code(BrilligOpcode::Load { + destination: target_register, + source_pointer: address_register, + }); + } + Instruction::Not(value) => { + let result_ids = dfg.instruction_results(instruction_id); + let result_register = self.get_or_create_register(result_ids[0]); + + assert_eq!( + dfg.type_of_value(*value), + Type::bool(), + "not operator can only be applied to boolean values" + ); + + let one = self.make_constant(FieldElement::one()); + let condition = self.convert_ssa_value(*value, dfg); + + // Compile !x as (1 - x) + let opcode = BrilligOpcode::BinaryIntOp { + destination: result_register, + op: BinaryIntOp::Sub, + bit_size: 1, + lhs: one, + rhs: condition, + }; + self.push_code(opcode); + } + _ => todo!("ICE: Instruction not supported {instruction:?}"), + }; + } + + fn allocate_array(&mut self, pointer_register: RegisterIndex, size: u32) { + let array_pointer = self.memory.allocate(size as usize); + self.push_code(BrilligOpcode::Const { + destination: pointer_register, + value: BrilligValue::from(array_pointer), + }); + } + + /// Returns a register which holds the value of a constant + fn make_constant(&mut self, constant: FieldElement) -> RegisterIndex { + let register = self.create_register(); + + let const_opcode = + BrilligOpcode::Const { destination: register, value: BrilligValue::from(constant) }; + self.push_code(const_opcode); + + register + } + + /// 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 = BrilligBinaryOp::convert_ssa_binary_op_to_brillig_binary_op( + binary.operator, + binary_type, + ); + match brillig_binary_op { + BrilligBinaryOp::Field { op } => { + let opcode = BrilligOpcode::BinaryFieldOp { + op, + destination: result_register, + lhs: left, + rhs: right, + }; + self.push_code(opcode); + } + BrilligBinaryOp::Integer { op, bit_size } => { + let opcode = BrilligOpcode::BinaryIntOp { + op, + destination: result_register, + bit_size, + lhs: left, + rhs: right, + }; + self.push_code(opcode); + } + } + } + + /// 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.push_code(BrilligOpcode::Const { + destination: register_index, + value: BrilligValue::from(*constant), + }); + 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(); - // we only support empty functions for now - assert_eq!(func.dfg.num_instructions(), 0); - brillig.push_code(BrilligOpcode::Stop); + + brillig.convert_ssa_function(func); 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); + } + } } diff --git a/crates/noirc_evaluator/src/brillig/memory.rs b/crates/noirc_evaluator/src/brillig/memory.rs new file mode 100644 index 00000000000..099dbfb7704 --- /dev/null +++ b/crates/noirc_evaluator/src/brillig/memory.rs @@ -0,0 +1,15 @@ +/// Simple memory allocator for brillig +/// For now it just tracks a free memory pointer +/// Will probably get smarter in the future +#[derive(Default)] +pub(crate) struct BrilligMemory { + free_mem_pointer: usize, +} + +impl BrilligMemory { + pub(crate) fn allocate(&mut self, size: usize) -> usize { + let start = self.free_mem_pointer; + self.free_mem_pointer += size; + start + } +} diff --git a/crates/noirc_evaluator/src/brillig/mod.rs b/crates/noirc_evaluator/src/brillig/mod.rs index 4c62554615d..5c28619194f 100644 --- a/crates/noirc_evaluator/src/brillig/mod.rs +++ b/crates/noirc_evaluator/src/brillig/mod.rs @@ -3,7 +3,9 @@ use std::collections::HashMap; use self::{artifact::BrilligArtifact, brillig_gen::BrilligGen}; pub(crate) mod artifact; +pub(crate) mod binary; pub(crate) mod brillig_gen; +pub(crate) mod memory; use crate::ssa_refactor::{ ir::function::{Function, FunctionId, RuntimeType}, diff --git a/crates/noirc_evaluator/src/ssa/ssa_gen.rs b/crates/noirc_evaluator/src/ssa/ssa_gen.rs index 016985dcb0f..31817b0bded 100644 --- a/crates/noirc_evaluator/src/ssa/ssa_gen.rs +++ b/crates/noirc_evaluator/src/ssa/ssa_gen.rs @@ -189,6 +189,9 @@ impl IrGenerator { let function_node_id = self.context.get_or_create_opcode_node_id(opcode); Ok(Value::Node(function_node_id)) } + Definition::Oracle(_, _) => { + unimplemented!("oracles not supported by deprecated SSA") + } } } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/acir_variable.rs b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/acir_variable.rs index a6d7ae157d3..935050f1f1a 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/acir_variable.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/acir_variable.rs @@ -1,9 +1,11 @@ +use super::{errors::AcirGenError, generated_acir::GeneratedAcir}; use crate::ssa_refactor::acir_gen::AcirValue; use crate::ssa_refactor::ir::types::Type as SsaType; use crate::ssa_refactor::ir::{instruction::Endian, map::TwoWayMap, types::NumericType}; -use acvm::acir::brillig_vm::Opcode as BrilligOpcode; - -use super::{errors::AcirGenError, generated_acir::GeneratedAcir}; +use acvm::acir::{ + brillig_vm::Opcode as BrilligOpcode, + circuit::brillig::{BrilligInputs, BrilligOutputs}, +}; use acvm::{ acir::{ circuit::opcodes::FunctionInput, @@ -706,8 +708,21 @@ impl AcirContext { self.vars.insert(id, data) } - pub(crate) fn brillig(&mut self, _code: Vec) { - todo!(); + pub(crate) fn brillig( + &mut self, + code: Vec, + inputs: Vec, + output_len: usize, + ) -> Vec { + let b_inputs = + vecmap(inputs, |i| BrilligInputs::Single(self.vars[&i].to_expression().into_owned())); + let outputs = vecmap(0..output_len, |_| self.acir_ir.next_witness_index()); + let outputs_var = + vecmap(&outputs, |witness_index| self.add_data(AcirVarData::Witness(*witness_index))); + let b_outputs = vecmap(outputs, BrilligOutputs::Simple); + self.acir_ir.brillig(code, b_inputs, b_outputs); + + outputs_var } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/generated_acir.rs b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/generated_acir.rs index 5b7963b5f06..084cd050578 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/generated_acir.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/acir_ir/generated_acir.rs @@ -2,7 +2,9 @@ //! program as it is being converted from SSA form. use super::errors::AcirGenError; use acvm::acir::{ + brillig_vm::Opcode as BrilligOpcode, circuit::{ + brillig::{Brillig as AcvmBrillig, BrilligInputs, BrilligOutputs}, directives::{LogInfo, QuotientDirective}, opcodes::{BlackBoxFuncCall, FunctionInput, Opcode as AcirOpcode}, }, @@ -95,19 +97,49 @@ impl GeneratedAcir { } impl GeneratedAcir { - /// Computes lhs mod 2^rhs + /// Computes lhs = 2^{rhs_bit_size} * q + r /// - /// `max_bits` is the upper-bound on the bit_size of the object that `lhs` is representing. - - /// An example; max_bits would be 32, if lhs was representing a u32 at a higher level. + /// For example, if we had a u32: + /// - `rhs` would be `32` + /// - `max_bits` would be the size of `lhs` + /// + /// Take the following code: + /// `` + /// fn main(x : u32) -> u32 { + /// let a = x + x; (L1) + /// let b = a * a; (L2) + /// b + b (L3) + /// } + /// `` + /// + /// Call truncate only on L1: + /// - `rhs` would be `32` + /// - `max_bits` would be `33` due to the addition of two u32s + /// Call truncate only on L2: + /// - `rhs` would be `32` + /// - `max_bits` would be `66` due to the multiplication of two u33s `a` + /// Call truncate only on L3: + /// - `rhs` would be `32` + /// - `max_bits` would be `67` due to the addition of two u66s `b` + /// + /// Truncation is done via the euclidean division formula: + /// + /// a = b * q + r + /// + /// where: + /// - a = `lhs` + /// - b = 2^{max_bits} + /// The prover will supply the quotient and the remainder, where the remainder + /// is the truncated value that we will return since it is enforced to be + /// in the range: 0 <= r < 2^{rhs_bit_size} pub(crate) fn truncate( &mut self, lhs: &Expression, - rhs: u32, + rhs_bit_size: u32, max_bits: u32, ) -> Result { - assert!(max_bits > rhs, "max_bits = {max_bits}, rhs = {rhs}"); - let exp_big = BigUint::from(2_u32).pow(rhs); + assert!(max_bits > rhs_bit_size, "max_bits = {max_bits}, rhs = {rhs_bit_size} -- The caller should ensure that truncation is only called when the value needs to be truncated"); + let exp_big = BigUint::from(2_u32).pow(rhs_bit_size); // 0. Check for constant expression. if let Some(a_c) = lhs.to_const() { @@ -115,37 +147,47 @@ impl GeneratedAcir { a_big %= exp_big; return Ok(Expression::from(FieldElement::from_be_bytes_reduce(&a_big.to_bytes_be()))); } + // Note: This is doing a reduction. However, since the compiler will call + // `max_bits` before it overflows the modulus, this line should never do a reduction. + // + // For example, if the modulus is a 254 bit number. + // `max_bits` will never be 255 since `exp` will be 2^255, which will cause a reduction in the following line. + // TODO: We should change this from `from_be_bytes_reduce` to `from_be_bytes` + // TODO: the latter will return an option that we can unwrap in the compiler let exp = FieldElement::from_be_bytes_reduce(&exp_big.to_bytes_be()); // 1. Generate witnesses a,b,c - let b_witness = self.next_witness_index(); - let c_witness = self.next_witness_index(); + let remainder_witness = self.next_witness_index(); + let quotient_witness = self.next_witness_index(); self.push_opcode(AcirOpcode::Directive(Directive::Quotient(QuotientDirective { a: lhs.clone(), b: Expression::from_field(exp), - q: c_witness, - r: b_witness, + q: quotient_witness, + r: remainder_witness, predicate: None, }))); - self.range_constraint(b_witness, rhs)?; - self.range_constraint(c_witness, max_bits - rhs)?; + // According to the division theorem, the remainder needs to be 0 <= r < 2^{rhs_bit_size} + self.range_constraint(remainder_witness, rhs_bit_size)?; + + // According to the formula above, the quotient should be within the range 0 <= q < 2^{max_bits - rhs} + self.range_constraint(quotient_witness, max_bits - rhs_bit_size)?; - // 2. Add the constraint a = b + 2^{rhs} * c + // 2. Add the constraint a == r + (q * 2^{rhs}) // // 2^{rhs} let mut two_pow_rhs_bits = FieldElement::from(2_i128); - two_pow_rhs_bits = two_pow_rhs_bits.pow(&FieldElement::from(rhs as i128)); + two_pow_rhs_bits = two_pow_rhs_bits.pow(&FieldElement::from(rhs_bit_size as i128)); - let b_arith = Expression::from(b_witness); - let c_arith = Expression::from(c_witness); + let remainder_expr = Expression::from(remainder_witness); + let quotient_expr = Expression::from(quotient_witness); - let res = &b_arith + &(two_pow_rhs_bits * &c_arith); - let my_constraint = &res - lhs; + let res = &remainder_expr + &(two_pow_rhs_bits * "ient_expr); + let euclidean_division = &res - lhs; - self.push_opcode(AcirOpcode::Arithmetic(my_constraint)); + self.push_opcode(AcirOpcode::Arithmetic(euclidean_division)); - Ok(Expression::from(b_witness)) + Ok(Expression::from(remainder_witness)) } /// Calls a black box function and returns the output @@ -264,13 +306,15 @@ impl GeneratedAcir { &mut self, lhs: &Expression, rhs: &Expression, - bit_size: u32, + max_bit_size: u32, predicate: &Expression, ) -> Result<(Witness, Witness), AcirGenError> { let q_witness = self.next_witness_index(); let r_witness = self.next_witness_index(); - let pa = lhs * predicate; + // lhs = rhs * q + r + // + // If predicate is zero, `q_witness` and `r_witness` will be 0 self.push_opcode(AcirOpcode::Directive(Directive::Quotient(QuotientDirective { a: lhs.clone(), b: rhs.clone(), @@ -279,17 +323,25 @@ impl GeneratedAcir { predicate: Some(predicate.clone()), }))); - //r predicate * ( a - b * q - r) == 0 + // When the predicate is 0, the equation always passes. + // When the predicate is 1, the euclidean division needs to be + // true. + let mut rhs_constraint = rhs * &Expression::from(q_witness); + rhs_constraint = &rhs_constraint + r_witness; + rhs_constraint = &rhs_constraint * predicate; + let lhs_constraint = lhs * predicate; + let div_euclidean = &lhs_constraint - &rhs_constraint; self.push_opcode(AcirOpcode::Arithmetic(div_euclidean)); @@ -576,7 +628,7 @@ impl GeneratedAcir { // TODO: perhaps this should be a user error, instead of an assert assert!(max_bits + 1 < FieldElement::max_num_bits()); - // Compute : 2^max_bits + a - b + // Compute : 2^{max_bits} + a - b let mut comparison_evaluation = a - b; let two = FieldElement::from(2_i128); let two_max_bits = two.pow(&FieldElement::from(max_bits as i128)); @@ -586,6 +638,25 @@ impl GeneratedAcir { let r_witness = self.next_witness_index(); // Add constraint : 2^{max_bits} + a - b = q * 2^{max_bits} + r + // + // case: a == b + // + // let k = 0; + // - 2^{max_bits} == q * 2^{max_bits} + r + // - This is only the case when q == 1 and r == 0 (assuming r is bounded to be less than 2^{max_bits}) + // + // case: a > b + // + // let k = a - b; + // - k + 2^{max_bits} == q * 2^{max_bits} + r + // - This is the case when q == 1 and r = k + // + // case: a < b + // + // let k = b - a + // - 2^{max_bits} - k == q * 2^{max_bits} + r + // - This is only the case when q == 0 and r == 2^{max_bits} - k + // let mut expr = Expression::default(); expr.push_addition_term(two_max_bits, q_witness); expr.push_addition_term(FieldElement::one(), r_witness); @@ -613,6 +684,22 @@ impl GeneratedAcir { Ok(q_witness) } + + pub(crate) fn brillig( + &mut self, + code: Vec, + inputs: Vec, + outputs: Vec, + ) { + let opcode = AcirOpcode::Brillig(AcvmBrillig { + inputs, + outputs, + foreign_call_results: Vec::new(), + bytecode: code, + predicate: None, + }); + self.push_opcode(opcode); + } } /// This function will return the number of inputs that a blackbox function 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 537b08ddd93..12c1fde04c9 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs @@ -75,11 +75,6 @@ impl Ssa { impl Context { /// Converts SSA into ACIR fn convert_ssa(mut self, ssa: Ssa, brillig: Brillig, allow_log_ops: bool) -> GeneratedAcir { - assert_eq!( - ssa.functions.len(), - 1, - "expected only a single function to be present with all other functions being inlined." - ); let main_func = ssa.main(); let dfg = &main_func.dfg; let entry_block = &dfg[main_func.entry_block()]; @@ -176,10 +171,18 @@ impl Context { "expected an intrinsic/brillig call, but found {func:?}. All ACIR methods should be inlined" ), RuntimeType::Brillig => { + let inputs:Vec = arguments + .iter() + .flat_map(|arg| self.convert_value(*arg, dfg).flatten()) + .map(|(var, _typ)| var) + .collect(); // Generate the brillig code of the function let code = BrilligArtifact::default().link(&brillig[*id]); - self.acir_context.brillig(code); - // TODO: Set result values + let outputs = self.acir_context.brillig(code, inputs, result_ids.len()); + for (result, output) in result_ids.iter().zip(outputs) { + let result_acir_type = dfg.type_of_value(*result).into(); + self.ssa_values.insert(*result, AcirValue::Var(output, result_acir_type)); + } } } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs index 50584a19a6c..2bb1846c94b 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/function.rs @@ -39,7 +39,7 @@ pub(crate) struct Function { impl Function { /// Creates a new function with an automatically inserted entry block. /// - /// Note that any parameters to the function must be manually added later. + /// Note that any parameters or attributes of the function must be manually added later. pub(crate) fn new(name: String, id: FunctionId) -> Self { let mut dfg = DataFlowGraph::default(); let entry_block = dfg.make_block(); diff --git a/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs index 51ce5601e12..313400e8438 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs @@ -184,6 +184,15 @@ struct Branch { } fn flatten_function_cfg(function: &mut Function) { + // TODO This pass will run forever on a brillig function. + // TODO In particular, analyze will check if the predecessors + // TODO have been processed and push the block to the back of the queue + // TODO This loops forever, if the predecessors are not then processed + // TODO Because it will visit the same block again, pop it out of the queue + // TODO then back into the queue again. + if let crate::ssa_refactor::ir::function::RuntimeType::Brillig = function.runtime() { + return; + } let mut context = Context { cfg: ControlFlowGraph::with_function(function), function, diff --git a/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs b/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs index 0346dcee166..a403ef9b161 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs @@ -257,10 +257,7 @@ impl<'function> PerFunctionContext<'function> { match self.context.builder[id] { Value::Function(id) => Some(id), Value::Intrinsic(_) => None, - _ => { - self.context.failed_to_inline_a_call = true; - None - } + _ => None, } } @@ -330,9 +327,15 @@ impl<'function> PerFunctionContext<'function> { Instruction::Call { func, arguments } => match self.get_function(*func) { Some(function) => match ssa.functions[&function].runtime() { RuntimeType::Acir => self.inline_function(ssa, *id, function, arguments), - RuntimeType::Brillig => self.push_instruction(*id), + RuntimeType::Brillig => { + self.context.failed_to_inline_a_call = true; + self.push_instruction(*id); + } }, - None => self.push_instruction(*id), + None => { + self.context.failed_to_inline_a_call = true; + self.push_instruction(*id); + } }, _ => self.push_instruction(*id), } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs index e6edf3ec930..14b3203128a 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -96,12 +96,12 @@ impl<'a> FunctionContext<'a> { match &ident.definition { ast::Definition::Local(id) => self.lookup(*id).map(|value| value.eval(self).into()), ast::Definition::Function(id) => self.get_or_queue_function(*id), - ast::Definition::Builtin(name) | ast::Definition::LowLevel(name) => { - match self.builder.import_intrinsic(name) { - Some(builtin) => builtin.into(), - None => panic!("No builtin function named '{name}' found"), - } - } + ast::Definition::Builtin(name) + | ast::Definition::LowLevel(name) + | ast::Definition::Oracle(name, _) => match self.builder.import_intrinsic(name) { + Some(builtin) => builtin.into(), + None => panic!("No builtin function named '{name}' found"), + }, } } diff --git a/crates/noirc_frontend/src/ast/function.rs b/crates/noirc_frontend/src/ast/function.rs index 971d42be1bf..de4e4f6f4d2 100644 --- a/crates/noirc_frontend/src/ast/function.rs +++ b/crates/noirc_frontend/src/ast/function.rs @@ -23,6 +23,7 @@ pub enum FunctionKind { LowLevel, Builtin, Normal, + Oracle, } impl NoirFunction { @@ -35,6 +36,9 @@ impl NoirFunction { pub fn low_level(def: FunctionDefinition) -> NoirFunction { NoirFunction { kind: FunctionKind::LowLevel, def } } + pub fn oracle(def: FunctionDefinition) -> NoirFunction { + NoirFunction { kind: FunctionKind::Oracle, def } + } pub fn return_type(&self) -> UnresolvedType { self.def.return_type.clone() @@ -77,6 +81,7 @@ impl From for NoirFunction { Some(Attribute::Builtin(_)) => FunctionKind::Builtin, Some(Attribute::Foreign(_)) => FunctionKind::LowLevel, Some(Attribute::Test) => FunctionKind::Normal, + Some(Attribute::Oracle(_)) => FunctionKind::Oracle, None => FunctionKind::Normal, }; diff --git a/crates/noirc_frontend/src/hir/resolution/resolver.rs b/crates/noirc_frontend/src/hir/resolution/resolver.rs index 5727741b1e2..67d0e2cf2a4 100644 --- a/crates/noirc_frontend/src/hir/resolution/resolver.rs +++ b/crates/noirc_frontend/src/hir/resolution/resolver.rs @@ -307,7 +307,9 @@ impl<'a> Resolver<'a> { let func_meta = self.extract_meta(&func, id); let hir_func = match func.kind { - FunctionKind::Builtin | FunctionKind::LowLevel => HirFunction::empty(), + FunctionKind::Builtin | FunctionKind::LowLevel | FunctionKind::Oracle => { + HirFunction::empty() + } FunctionKind::Normal => { let expr_id = self.intern_block(func.def.body); self.interner.push_expr_location(expr_id, func.def.span, self.file); diff --git a/crates/noirc_frontend/src/hir_def/function.rs b/crates/noirc_frontend/src/hir_def/function.rs index 1f7399e5547..60cb8ea4f85 100644 --- a/crates/noirc_frontend/src/hir_def/function.rs +++ b/crates/noirc_frontend/src/hir_def/function.rs @@ -144,13 +144,13 @@ pub struct FuncMeta { } impl FuncMeta { - /// Builtin and LowLevel functions usually have the return type + /// Builtin, LowLevel and Oracle functions usually have the return type /// declared, however their function bodies will be empty /// So this method tells the type checker to ignore the return /// of the empty function, which is unit pub fn can_ignore_return_type(&self) -> bool { match self.kind { - FunctionKind::LowLevel | FunctionKind::Builtin => true, + FunctionKind::LowLevel | FunctionKind::Builtin | FunctionKind::Oracle => true, FunctionKind::Normal => false, } } diff --git a/crates/noirc_frontend/src/lexer/token.rs b/crates/noirc_frontend/src/lexer/token.rs index 5ffa807a337..3eb5b49914f 100644 --- a/crates/noirc_frontend/src/lexer/token.rs +++ b/crates/noirc_frontend/src/lexer/token.rs @@ -324,6 +324,7 @@ impl IntType { pub enum Attribute { Foreign(String), Builtin(String), + Oracle(String), Test, } @@ -332,6 +333,7 @@ impl fmt::Display for Attribute { match *self { Attribute::Foreign(ref k) => write!(f, "#[foreign({k})]"), Attribute::Builtin(ref k) => write!(f, "#[builtin({k})]"), + Attribute::Oracle(ref k) => write!(f, "#[oracle({k})]"), Attribute::Test => write!(f, "#[test]"), } } @@ -363,6 +365,7 @@ impl Attribute { let tok = match attribute_type { "foreign" => Token::Attribute(Attribute::Foreign(attribute_name.to_string())), "builtin" => Token::Attribute(Attribute::Builtin(attribute_name.to_string())), + "oracle" => Token::Attribute(Attribute::Oracle(attribute_name.to_string())), _ => { return Err(LexerErrorKind::MalformedFuncAttribute { span, found: word.to_owned() }) } @@ -398,6 +401,7 @@ impl AsRef for Attribute { match self { Attribute::Foreign(string) => string, Attribute::Builtin(string) => string, + Attribute::Oracle(string) => string, Attribute::Test => "", } } diff --git a/crates/noirc_frontend/src/monomorphization/ast.rs b/crates/noirc_frontend/src/monomorphization/ast.rs index bad88885749..61541c85fc0 100644 --- a/crates/noirc_frontend/src/monomorphization/ast.rs +++ b/crates/noirc_frontend/src/monomorphization/ast.rs @@ -44,6 +44,8 @@ pub enum Definition { Function(FuncId), Builtin(String), LowLevel(String), + // used as a foreign/externally defined unconstrained function + Oracle(String, FuncId), } /// ID of a local definition, e.g. from a let binding or diff --git a/crates/noirc_frontend/src/monomorphization/mod.rs b/crates/noirc_frontend/src/monomorphization/mod.rs index 3508576669f..0f8fb0ea059 100644 --- a/crates/noirc_frontend/src/monomorphization/mod.rs +++ b/crates/noirc_frontend/src/monomorphization/mod.rs @@ -21,6 +21,7 @@ use crate::{ stmt::{HirAssignStatement, HirLValue, HirLetStatement, HirPattern, HirStatement}, }, node_interner::{self, DefinitionKind, NodeInterner, StmtId}, + token::Attribute, CompTime, FunctionKind, TypeBinding, TypeBindings, }; @@ -152,6 +153,16 @@ impl<'interner> Monomorphizer<'interner> { let id = self.queue_function(id, expr_id, typ); Definition::Function(id) } + + FunctionKind::Oracle => { + let attr = + meta.attributes.expect("Oracle function must have an oracle attribute"); + let id = self.queue_function(id, expr_id, typ); + match attr { + Attribute::Oracle(name) => Definition::Oracle(name, id), + _ => unreachable!("Oracle function must have an oracle attribute"), + } + } } } } diff --git a/crates/noirc_frontend/src/monomorphization/printer.rs b/crates/noirc_frontend/src/monomorphization/printer.rs index 2479f916822..825a800801c 100644 --- a/crates/noirc_frontend/src/monomorphization/printer.rs +++ b/crates/noirc_frontend/src/monomorphization/printer.rs @@ -273,6 +273,7 @@ impl Display for Definition { Definition::Function(id) => write!(f, "f{}", id.0), Definition::Builtin(name) => write!(f, "{name}"), Definition::LowLevel(name) => write!(f, "{name}"), + Definition::Oracle(name, _) => write!(f, "{name}"), } } }