diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index 35ff97f55e3..68a604df378 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -3038,7 +3038,7 @@ dependencies = [ "num-bigint", "num-traits", "proptest", - "proptest-derive 0.4.0", + "proptest-derive", "serde", "serde_json", "strum", @@ -3161,8 +3161,6 @@ dependencies = [ "num-bigint", "num-traits", "petgraph", - "proptest", - "proptest-derive 0.5.0", "rangemap", "regex", "rustc-hash", @@ -3727,17 +3725,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "proptest-derive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "quick-error" version = "1.2.3" diff --git a/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml b/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml index 5bd6c1e3d10..aac07339dd6 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_evaluator/Cargo.toml @@ -20,6 +20,7 @@ fxhash.workspace = true iter-extended.workspace = true thiserror.workspace = true num-bigint = "0.4" +num-traits.workspace = true im.workspace = true serde.workspace = true serde_json.workspace = true @@ -31,6 +32,7 @@ cfg-if.workspace = true [dev-dependencies] proptest.workspace = true +similar-asserts.workspace = true [features] bn254 = ["noirc_frontend/bn254"] diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs index fc2746a7b53..d5529c00d6c 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_directive.rs @@ -73,8 +73,6 @@ pub(crate) fn directive_invert() -> GeneratedBrillig { return_data: HeapVector { pointer: zero_usize, size: one_usize }, }, ], - error_types: Default::default(), - locations: Default::default(), name: "directive_invert".to_string(), ..Default::default() } @@ -144,8 +142,6 @@ pub(crate) fn directive_quotient() -> GeneratedBrillig { }, }, ], - error_types: Default::default(), - locations: Default::default(), name: "directive_integer_quotient".to_string(), ..Default::default() } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs index 77661fba3a1..9512a60e699 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs @@ -6,7 +6,7 @@ use acvm::{ AcirField, }; -use crate::ErrorType; +use crate::ssa::ir::instruction::ErrorType; use super::{ artifact::BrilligParameter, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/mod.rs index 1678268d343..2fa51f3db59 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/brillig/brillig_ir/procedures/mod.rs @@ -18,6 +18,7 @@ use noirc_errors::debug_info::ProcedureDebugId; use prepare_vector_insert::compile_prepare_vector_insert_procedure; use prepare_vector_push::compile_prepare_vector_push_procedure; use revert_with_string::compile_revert_with_string_procedure; +use serde::{Deserialize, Serialize}; use vector_copy::compile_vector_copy_procedure; use vector_pop_back::compile_vector_pop_back_procedure; use vector_pop_front::compile_vector_pop_front_procedure; @@ -34,7 +35,7 @@ use super::{ /// Procedures are a set of complex operations that are common in the noir language. /// Extracting them to reusable procedures allows us to reduce the size of the generated Brillig. /// Procedures receive their arguments on scratch space to avoid stack dumping&restoring. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Deserialize, Serialize)] pub enum ProcedureId { ArrayCopy, ArrayReverse, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/lib.rs b/noir/noir-repo/compiler/noirc_evaluator/src/lib.rs index 3fddd9da056..b2b4762723f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/lib.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/lib.rs @@ -14,3 +14,21 @@ pub mod brillig; pub use ssa::create_program; pub use ssa::ir::instruction::ErrorType; + +/// Trims leading whitespace from each line of the input string, according to +/// how much leading whitespace there is on the first non-empty line. +#[cfg(test)] +pub(crate) fn trim_leading_whitespace_from_lines(src: &str) -> String { + let mut lines = src.trim_end().lines(); + let mut first_line = lines.next().unwrap(); + while first_line.is_empty() { + first_line = lines.next().unwrap(); + } + let indent = first_line.len() - first_line.trim_start().len(); + let mut result = first_line.trim_start().to_string(); + for line in lines { + result.push('\n'); + result.push_str(&line[indent..]); + } + result +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs index 67d7ad70cbc..78151d18b61 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa.rs @@ -43,6 +43,7 @@ mod checks; pub(super) mod function_builder; pub mod ir; mod opt; +mod parser; pub mod ssa_gen; pub struct SsaEvaluatorOptions { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index b32d6d11f03..ecf7561321f 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -1009,13 +1009,13 @@ impl<'a> Context<'a> { }; entry_point.link_with(artifact); // Insert the range of opcode locations occupied by a procedure - if let Some(procedure_id) = artifact.procedure.clone() { + if let Some(procedure_id) = &artifact.procedure { let num_opcodes = entry_point.byte_code.len(); let previous_num_opcodes = entry_point.byte_code.len() - artifact.byte_code.len(); // We subtract one as to keep the range inclusive on both ends entry_point .procedure_locations - .insert(procedure_id, (previous_num_opcodes, num_opcodes - 1)); + .insert(procedure_id.clone(), (previous_num_opcodes, num_opcodes - 1)); } } // Generate the final bytecode diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index 7b39f7041e4..63a9453a430 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -328,6 +328,16 @@ impl FunctionBuilder { .first() } + pub(crate) fn insert_mutable_array_set( + &mut self, + array: ValueId, + index: ValueId, + value: ValueId, + ) -> ValueId { + self.insert_instruction(Instruction::ArraySet { array, index, value, mutable: true }, None) + .first() + } + /// Insert an instruction to increment an array's reference count. This only has an effect /// in unconstrained code where arrays are reference counted and copy on write. pub(crate) fn insert_inc_rc(&mut self, value: ValueId) { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs index d79916a9e11..2be9ffa9afa 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/dfg.rs @@ -74,8 +74,9 @@ pub(crate) struct DataFlowGraph { blocks: DenseMap, /// Debugging information about which `ValueId`s have had their underlying `Value` substituted - /// for that of another. This information is purely used for printing the SSA, and has no - /// material effect on the SSA itself. + /// for that of another. In theory this information is purely used for printing the SSA, + /// and has no material effect on the SSA itself, however in practice the IDs can get out of + /// sync and may need this resolution before they can be compared. #[serde(skip)] replaced_value_ids: HashMap, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs index 03262be0a06..72708c8ca89 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction/binary.rs @@ -281,7 +281,6 @@ impl Binary { let zero = dfg.make_constant(FieldElement::zero(), operand_type); return SimplifyResult::SimplifiedTo(zero); } - // `two_pow_rhs` is limited to be at most `2 ^ {operand_bitsize - 1}` so it fits in `operand_type`. let two_pow_rhs = FieldElement::from(2u128).pow(&rhs_const); let two_pow_rhs = dfg.make_constant(two_pow_rhs, operand_type); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs index 93d69f255c3..3bbe14f866a 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -69,9 +69,16 @@ fn value(function: &Function, id: ValueId) -> String { } Value::Function(id) => id.to_string(), Value::Intrinsic(intrinsic) => intrinsic.to_string(), - Value::Array { array, .. } => { + Value::Array { array, typ } => { let elements = vecmap(array, |element| value(function, *element)); - format!("[{}]", elements.join(", ")) + let element_types = &typ.clone().element_types(); + let element_types_str = + element_types.iter().map(|typ| typ.to_string()).collect::>().join(", "); + if element_types.len() == 1 { + format!("[{}] of {}", elements.join(", "), element_types_str) + } else { + format!("[{}] of ({})", elements.join(", "), element_types_str) + } } Value::Param { .. } | Value::Instruction { .. } | Value::ForeignFunction(_) => { id.to_string() @@ -119,7 +126,11 @@ pub(crate) fn display_terminator( ) } Some(TerminatorInstruction::Return { return_values, .. }) => { - writeln!(f, " return {}", value_list(function, return_values)) + if return_values.is_empty() { + writeln!(f, " return") + } else { + writeln!(f, " return {}", value_list(function, return_values)) + } } None => writeln!(f, " (no terminator instruction)"), } @@ -139,12 +150,13 @@ pub(crate) fn display_instruction( write!(f, "{} = ", value_list(function, results))?; } - display_instruction_inner(function, &function.dfg[instruction], f) + display_instruction_inner(function, &function.dfg[instruction], results, f) } fn display_instruction_inner( function: &Function, instruction: &Instruction, + results: &[ValueId], f: &mut Formatter, ) -> Result { let show = |id| value(function, id); @@ -168,10 +180,15 @@ fn display_instruction_inner( } } Instruction::Call { func, arguments } => { - writeln!(f, "call {}({})", show(*func), value_list(function, arguments)) + let arguments = value_list(function, arguments); + writeln!(f, "call {}({}){}", show(*func), arguments, result_types(function, results)) + } + Instruction::Allocate => { + writeln!(f, "allocate{}", result_types(function, results)) + } + Instruction::Load { address } => { + writeln!(f, "load {}{}", show(*address), result_types(function, results)) } - Instruction::Allocate => writeln!(f, "allocate"), - Instruction::Load { address } => writeln!(f, "load {}", show(*address)), Instruction::Store { address, value } => { writeln!(f, "store {} at {}", show(*value), show(*address)) } @@ -179,7 +196,13 @@ fn display_instruction_inner( writeln!(f, "enable_side_effects {}", show(*condition)) } Instruction::ArrayGet { array, index } => { - writeln!(f, "array_get {}, index {}", show(*array), show(*index)) + writeln!( + f, + "array_get {}, index {}{}", + show(*array), + show(*index), + result_types(function, results) + ) } Instruction::ArraySet { array, index, value, mutable } => { let array = show(*array); @@ -210,6 +233,17 @@ fn display_instruction_inner( } } +fn result_types(function: &Function, results: &[ValueId]) -> String { + let types = vecmap(results, |result| function.dfg.type_of_value(*result).to_string()); + if types.is_empty() { + String::new() + } else if types.len() == 1 { + format!(" -> {}", types[0]) + } else { + format!(" -> ({})", types.join(", ")) + } +} + /// Tries to extract a constant string from an error payload. pub(crate) fn try_to_extract_string_from_error_payload( is_string_type: bool, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/types.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/types.rs index b7ee37ba17a..203356de8e6 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/types.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/types.rs @@ -218,7 +218,11 @@ impl std::fmt::Display for Type { Type::Reference(element) => write!(f, "&mut {element}"), Type::Array(element, length) => { let elements = vecmap(element.iter(), |element| element.to_string()); - write!(f, "[{}; {length}]", elements.join(", ")) + if elements.len() == 1 { + write!(f, "[{}; {length}]", elements.join(", ")) + } else { + write!(f, "[({}); {length}]", elements.join(", ")) + } } Type::Slice(element) => { let elements = vecmap(element.iter(), |element| element.to_string()); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/array_set.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/array_set.rs index 7035345436e..ed3accae15a 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/array_set.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/array_set.rs @@ -186,135 +186,47 @@ fn make_mutable( #[cfg(test)] mod tests { - use std::sync::Arc; - - use im::vector; - use noirc_frontend::monomorphization::ast::InlineType; - - use crate::ssa::{ - function_builder::FunctionBuilder, - ir::{ - function::RuntimeType, - instruction::{BinaryOp, Instruction}, - map::Id, - types::Type, - }, - }; + use crate::ssa::{opt::assert_normalized_ssa_equals, Ssa}; #[test] fn array_set_in_loop_with_conditional_clone() { // We want to make sure that we do not mark a single array set mutable which is loaded // from and cloned in a loop. If the array is inadvertently marked mutable, and is cloned in a previous iteration // of the loop, its clone will also be altered. - // - // brillig fn main f0 { - // b0(): - // v3 = allocate - // store [[Field 0, Field 0, Field 0, Field 0, Field 0], [Field 0, Field 0, Field 0, Field 0, Field 0]] at v3 - // v4 = allocate - // store [[Field 0, Field 0, Field 0, Field 0, Field 0], [Field 0, Field 0, Field 0, Field 0, Field 0]] at v4 - // jmp b1(u32 0) - // b1(v6: u32): - // v8 = lt v6, u32 5 - // jmpif v8 then: b3, else: b2 - // b3(): - // v9 = eq v6, u32 5 - // jmpif v9 then: b4, else: b5 - // b4(): - // v10 = load v3 - // store v10 at v4 - // jmp b5() - // b5(): - // v11 = load v3 - // v13 = array_get v11, index Field 0 - // v14 = array_set v13, index v6, value Field 20 - // v15 = array_set v11, index v6, value v14 - // store v15 at v3 - // v17 = add v6, u32 1 - // jmp b1(v17) - // b2(): - // return - // } - let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id); - builder.set_runtime(RuntimeType::Brillig(InlineType::default())); - - let array_type = Type::Array(Arc::new(vec![Type::field()]), 5); - let zero = builder.field_constant(0u128); - let array_constant = - builder.array_constant(vector![zero, zero, zero, zero, zero], array_type.clone()); - let nested_array_type = Type::Array(Arc::new(vec![array_type.clone()]), 2); - let nested_array_constant = builder - .array_constant(vector![array_constant, array_constant], nested_array_type.clone()); - - let v3 = builder.insert_allocate(array_type.clone()); - - builder.insert_store(v3, nested_array_constant); - - let v4 = builder.insert_allocate(array_type.clone()); - builder.insert_store(v4, nested_array_constant); - - let b1 = builder.insert_block(); - let zero_u32 = builder.numeric_constant(0u128, Type::unsigned(32)); - builder.terminate_with_jmp(b1, vec![zero_u32]); - - // Loop header - builder.switch_to_block(b1); - let v5 = builder.add_block_parameter(b1, Type::unsigned(32)); - let five = builder.numeric_constant(5u128, Type::unsigned(32)); - let v8 = builder.insert_binary(v5, BinaryOp::Lt, five); - - let b2 = builder.insert_block(); - let b3 = builder.insert_block(); - let b4 = builder.insert_block(); - let b5 = builder.insert_block(); - builder.terminate_with_jmpif(v8, b3, b2); - - // Loop body - // b3 is the if statement conditional - builder.switch_to_block(b3); - let two = builder.numeric_constant(5u128, Type::unsigned(32)); - let v9 = builder.insert_binary(v5, BinaryOp::Eq, two); - builder.terminate_with_jmpif(v9, b4, b5); - - // b4 is the rest of the loop after the if statement - builder.switch_to_block(b4); - let v10 = builder.insert_load(v3, nested_array_type.clone()); - builder.insert_store(v4, v10); - builder.terminate_with_jmp(b5, vec![]); - - builder.switch_to_block(b5); - let v11 = builder.insert_load(v3, nested_array_type.clone()); - let twenty = builder.field_constant(20u128); - let v13 = builder.insert_array_get(v11, zero, array_type.clone()); - let v14 = builder.insert_array_set(v13, v5, twenty); - let v15 = builder.insert_array_set(v11, v5, v14); - - builder.insert_store(v3, v15); - let one = builder.numeric_constant(1u128, Type::unsigned(32)); - let v17 = builder.insert_binary(v5, BinaryOp::Add, one); - builder.terminate_with_jmp(b1, vec![v17]); - - builder.switch_to_block(b2); - builder.terminate_with_return(vec![]); + let src = " + brillig(inline) fn main f0 { + b0(): + v1 = allocate -> &mut [Field; 5] + store [[Field 0, Field 0, Field 0, Field 0, Field 0] of Field, [Field 0, Field 0, Field 0, Field 0, Field 0] of Field] of [Field; 5] at v1 + v6 = allocate -> &mut [Field; 5] + store [[Field 0, Field 0, Field 0, Field 0, Field 0] of Field, [Field 0, Field 0, Field 0, Field 0, Field 0] of Field] of [Field; 5] at v6 + jmp b1(u32 0) + b1(v0: u32): + v12 = lt v0, u32 5 + jmpif v12 then: b3, else: b2 + b3(): + v13 = eq v0, u32 5 + jmpif v13 then: b4, else: b5 + b4(): + v14 = load v1 -> [[Field; 5]; 2] + store v14 at v6 + jmp b5() + b5(): + v15 = load v1 -> [[Field; 5]; 2] + v16 = array_get v15, index Field 0 -> [Field; 5] + v18 = array_set v16, index v0, value Field 20 + v19 = array_set v15, index v0, value v18 + store v19 at v1 + v21 = add v0, u32 1 + jmp b1(v21) + b2(): + return + } + "; + let ssa = Ssa::from_str(src).unwrap(); - let ssa = builder.finish(); // We expect the same result as above let ssa = ssa.array_set_optimization(); - - let main = ssa.main(); - assert_eq!(main.reachable_blocks().len(), 6); - - let array_set_instructions = main.dfg[b5] - .instructions() - .iter() - .filter(|instruction| matches!(&main.dfg[**instruction], Instruction::ArraySet { .. })) - .collect::>(); - - assert_eq!(array_set_instructions.len(), 2); - if let Instruction::ArraySet { mutable, .. } = &main.dfg[*array_set_instructions[0]] { - // The single array set should not be marked mutable - assert!(!mutable); - } + assert_normalized_ssa_equals(ssa, src); } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs index 59917e8589b..76705dcc9db 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/as_slice_length.rs @@ -75,3 +75,34 @@ fn replace_known_slice_lengths( func.dfg.set_value_from_id(original_slice_length, known_length); }); } + +#[cfg(test)] +mod test { + use crate::ssa::opt::assert_normalized_ssa_equals; + + use super::Ssa; + + #[test] + fn as_slice_length_optimization() { + // In this code we expect `return v2` to be replaced with `return u32 3` because + // that's the length of the v0 array. + let src = " + acir(inline) fn main f0 { + b0(v0: [Field; 3]): + v2, v3 = call as_slice(v0) -> (u32, [Field]) + return v2 + } + "; + let ssa = Ssa::from_str(src).unwrap(); + + let expected = " + acir(inline) fn main f0 { + b0(v0: [Field; 3]): + v2, v3 = call as_slice(v0) -> (u32, [Field]) + return u32 3 + } + "; + let ssa = ssa.as_slice_optimization(); + assert_normalized_ssa_equals(ssa, expected); + } +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs index 3b86ded4a87..b5ab688cb2c 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs @@ -317,531 +317,305 @@ mod test { use crate::ssa::{ function_builder::FunctionBuilder, - ir::{ - instruction::{Binary, BinaryOp, Instruction, TerminatorInstruction}, - map::Id, - types::Type, - value::{Value, ValueId}, - }, + ir::{map::Id, types::Type}, + opt::assert_normalized_ssa_equals, + Ssa, }; - use acvm::{acir::AcirField, FieldElement}; #[test] fn simple_constant_fold() { - // fn main f0 { - // b0(v0: Field): - // v1 = add v0, Field 1 - // v2 = mul v1, Field 3 - // return v2 - // } - // // After constructing this IR, we set the value of v0 to 2. // The expected return afterwards should be 9. - let main_id = Id::test_new(0); - - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id); - let v0 = builder.add_parameter(Type::field()); - - let one = builder.field_constant(1u128); - let two = builder.field_constant(2u128); - let three = builder.field_constant(3u128); - - let v1 = builder.insert_binary(v0, BinaryOp::Add, one); - let v2 = builder.insert_binary(v1, BinaryOp::Mul, three); - builder.terminate_with_return(vec![v2]); - - let mut ssa = builder.finish(); + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + v1 = add v0, Field 1 + v2 = mul v1, Field 3 + return v2 + } + "; + let mut ssa = Ssa::from_str(src).unwrap(); let main = ssa.main_mut(); + let instructions = main.dfg[main.entry_block()].instructions(); assert_eq!(instructions.len(), 2); // The final return is not counted - // Expected output: - // - // fn main f0 { - // b0(Field 2: Field): - // return Field 9 - // } + let v0 = main.parameters()[0]; + let two = main.dfg.make_constant(2_u128.into(), Type::field()); + main.dfg.set_value_from_id(v0, two); - let ssa = ssa.fold_constants(); - let main = ssa.main(); - let block = &main.dfg[main.entry_block()]; - assert_eq!(block.instructions().len(), 0); - - match block.terminator() { - Some(TerminatorInstruction::Return { return_values, .. }) => { - let value = main - .dfg - .get_numeric_constant(return_values[0]) - .expect("Expected constant 9") - .to_u128(); - assert_eq!(value, 9); + let expected = " + acir(inline) fn main f0 { + b0(v0: Field): + return Field 9 } - _ => unreachable!("b0 should have a return terminator"), - } + "; + let ssa = ssa.fold_constants(); + assert_normalized_ssa_equals(ssa, expected); } #[test] fn redundant_truncation() { - // fn main f0 { - // b0(v0: u16, v1: u16): - // v2 = div v0, v1 - // v3 = truncate v2 to 8 bits, max_bit_size: 16 - // return v3 - // } - // // After constructing this IR, we set the value of v1 to 2^8. // The expected return afterwards should be v2. - let main_id = Id::test_new(0); - - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id); - let v0 = builder.add_parameter(Type::unsigned(16)); - let v1 = builder.add_parameter(Type::unsigned(16)); - - // Note that this constant guarantees that `v0/constant < 2^8`. We then do not need to truncate the result. - let constant = 2_u128.pow(8); - let constant = builder.numeric_constant(constant, Type::field()); - - let v2 = builder.insert_binary(v0, BinaryOp::Div, v1); - let v3 = builder.insert_truncate(v2, 8, 16); - builder.terminate_with_return(vec![v3]); - - let mut ssa = builder.finish(); + let src = " + acir(inline) fn main f0 { + b0(v0: u16, v1: u16): + v2 = div v0, v1 + v3 = truncate v2 to 8 bits, max_bit_size: 16 + return v3 + } + "; + let mut ssa = Ssa::from_str(src).unwrap(); let main = ssa.main_mut(); + let instructions = main.dfg[main.entry_block()].instructions(); assert_eq!(instructions.len(), 2); // The final return is not counted - // Expected output: - // - // fn main f0 { - // b0(Field 2: Field): - // return Field 9 - // } - main.dfg.set_value_from_id(v1, constant); + let v1 = main.parameters()[1]; - let ssa = ssa.fold_constants(); - let main = ssa.main(); + // Note that this constant guarantees that `v0/constant < 2^8`. We then do not need to truncate the result. + let constant = 2_u128.pow(8); + let constant = main.dfg.make_constant(constant.into(), Type::unsigned(16)); - println!("{ssa}"); + main.dfg.set_value_from_id(v1, constant); - let instructions = main.dfg[main.entry_block()].instructions(); - assert_eq!(instructions.len(), 1); - let instruction = &main.dfg[instructions[0]]; + let expected = " + acir(inline) fn main f0 { + b0(v0: u16, v1: u16): + v3 = div v0, u16 256 + return v3 + } + "; - assert_eq!( - instruction, - &Instruction::Binary(Binary { lhs: v0, operator: BinaryOp::Div, rhs: constant }) - ); + let ssa = ssa.fold_constants(); + assert_normalized_ssa_equals(ssa, expected); } #[test] fn non_redundant_truncation() { - // fn main f0 { - // b0(v0: u16, v1: u16): - // v2 = div v0, v1 - // v3 = truncate v2 to 8 bits, max_bit_size: 16 - // return v3 - // } - // // After constructing this IR, we set the value of v1 to 2^8 - 1. // This should not result in the truncation being removed. - let main_id = Id::test_new(0); + let src = " + acir(inline) fn main f0 { + b0(v0: u16, v1: u16): + v2 = div v0, v1 + v3 = truncate v2 to 8 bits, max_bit_size: 16 + return v3 + } + "; + let mut ssa = Ssa::from_str(src).unwrap(); + let main = ssa.main_mut(); - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id); - let v0 = builder.add_parameter(Type::unsigned(16)); - let v1 = builder.add_parameter(Type::unsigned(16)); + let instructions = main.dfg[main.entry_block()].instructions(); + assert_eq!(instructions.len(), 2); // The final return is not counted + + let v1 = main.parameters()[1]; // Note that this constant does not guarantee that `v0/constant < 2^8`. We must then truncate the result. let constant = 2_u128.pow(8) - 1; - let constant = builder.numeric_constant(constant, Type::field()); - - let v2 = builder.insert_binary(v0, BinaryOp::Div, v1); - let v3 = builder.insert_truncate(v2, 8, 16); - builder.terminate_with_return(vec![v3]); - - let mut ssa = builder.finish(); - let main = ssa.main_mut(); - let instructions = main.dfg[main.entry_block()].instructions(); - assert_eq!(instructions.len(), 2); // The final return is not counted + let constant = main.dfg.make_constant(constant.into(), Type::unsigned(16)); - // Expected output: - // - // fn main f0 { - // b0(v0: u16, Field 255: Field): - // v6 = div v0, Field 255 - // v7 = truncate v6 to 8 bits, max_bit_size: 16 - // return v7 - // } main.dfg.set_value_from_id(v1, constant); - let ssa = ssa.fold_constants(); - let main = ssa.main(); - - let instructions = main.dfg[main.entry_block()].instructions(); - assert_eq!(instructions.len(), 2); + let expected = " + acir(inline) fn main f0 { + b0(v0: u16, v1: u16): + v3 = div v0, u16 255 + v4 = truncate v3 to 8 bits, max_bit_size: 16 + return v4 + } + "; - assert_eq!( - &main.dfg[instructions[0]], - &Instruction::Binary(Binary { lhs: v0, operator: BinaryOp::Div, rhs: constant }) - ); - assert_eq!( - &main.dfg[instructions[1]], - &Instruction::Truncate { value: ValueId::test_new(6), bit_size: 8, max_bit_size: 16 } - ); + let ssa = ssa.fold_constants(); + assert_normalized_ssa_equals(ssa, expected); } #[test] fn arrays_elements_are_updated() { - // fn main f0 { - // b0(v0: Field): - // v1 = add v0, Field 1 - // return [v1] - // } - // // After constructing this IR, we run constant folding with no expected benefit, but to // ensure that all new values ids are correctly propagated. - let main_id = Id::test_new(0); - - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id); - let v0 = builder.add_parameter(Type::field()); - let one = builder.field_constant(1u128); - let v1 = builder.insert_binary(v0, BinaryOp::Add, one); - - let array_type = Type::Array(Arc::new(vec![Type::field()]), 1); - let arr = builder.current_function.dfg.make_array(vec![v1].into(), array_type); - builder.terminate_with_return(vec![arr]); - - let ssa = builder.finish().fold_constants(); - let main = ssa.main(); - let entry_block_id = main.entry_block(); - let entry_block = &main.dfg[entry_block_id]; - assert_eq!(entry_block.instructions().len(), 1); - let new_add_instr = entry_block.instructions().first().unwrap(); - let new_add_instr_result = main.dfg.instruction_results(*new_add_instr)[0]; - assert_ne!(new_add_instr_result, v1); - - let return_value_id = match entry_block.unwrap_terminator() { - TerminatorInstruction::Return { return_values, .. } => return_values[0], - _ => unreachable!("Should have terminator instruction"), - }; - let return_element = match &main.dfg[return_value_id] { - Value::Array { array, .. } => array[0], - _ => unreachable!("Return type should be array"), - }; - // The return element is expected to refer to the new add instruction result. - assert_eq!(main.dfg.resolve(new_add_instr_result), main.dfg.resolve(return_element)); + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + v2 = add v0, Field 1 + return [v2] of Field + } + "; + let ssa = Ssa::from_str(src).unwrap(); + let ssa = ssa.fold_constants(); + assert_normalized_ssa_equals(ssa, src); } #[test] fn instruction_deduplication() { - // fn main f0 { - // b0(v0: u16): - // v1 = cast v0 as u32 - // v2 = cast v0 as u32 - // constrain v1 v2 - // } - // // After constructing this IR, we run constant folding which should replace the second cast // with a reference to the results to the first. This then allows us to optimize away // the constrain instruction as both inputs are known to be equal. // // The first cast instruction is retained and will be removed in the dead instruction elimination pass. - let main_id = Id::test_new(0); - - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id); - let v0 = builder.add_parameter(Type::unsigned(16)); - - let v1 = builder.insert_cast(v0, Type::unsigned(32)); - let v2 = builder.insert_cast(v0, Type::unsigned(32)); - builder.insert_constrain(v1, v2, None); - - let mut ssa = builder.finish(); - let main = ssa.main_mut(); - let instructions = main.dfg[main.entry_block()].instructions(); - assert_eq!(instructions.len(), 3); - - // Expected output: - // - // fn main f0 { - // b0(v0: u16): - // v1 = cast v0 as u32 - // } + let src = " + acir(inline) fn main f0 { + b0(v0: u16): + v1 = cast v0 as u32 + v2 = cast v0 as u32 + constrain v1 == v2 + return + } + "; + let expected = " + acir(inline) fn main f0 { + b0(v0: u16): + v1 = cast v0 as u32 + return + } + "; + let ssa = Ssa::from_str(src).unwrap(); let ssa = ssa.fold_constants(); - let main = ssa.main(); - let instructions = main.dfg[main.entry_block()].instructions(); - - assert_eq!(instructions.len(), 1); - let instruction = &main.dfg[instructions[0]]; - - assert_eq!(instruction, &Instruction::Cast(v0, Type::unsigned(32))); + assert_normalized_ssa_equals(ssa, expected); } #[test] fn constant_index_array_access_deduplication() { - // fn main f0 { - // b0(v0: [Field; 4], v1: u32, v2: bool, v3: bool): - // enable_side_effects v2 - // v4 = array_get v0 u32 0 - // v5 = array_get v0 v1 - // enable_side_effects v3 - // v6 = array_get v0 u32 0 - // v7 = array_get v0 v1 - // constrain v4 v6 - // } - // // After constructing this IR, we run constant folding which should replace the second constant-index array get // with a reference to the results to the first. This then allows us to optimize away // the constrain instruction as both inputs are known to be equal. - // - let main_id = Id::test_new(0); - - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id); - - let v0 = builder.add_parameter(Type::Array(Arc::new(vec![Type::field()]), 4)); - let v1 = builder.add_parameter(Type::unsigned(32)); - let v2 = builder.add_parameter(Type::unsigned(1)); - let v3 = builder.add_parameter(Type::unsigned(1)); - - let zero = builder.numeric_constant(FieldElement::zero(), Type::length_type()); - - builder.insert_enable_side_effects_if(v2); - let v4 = builder.insert_array_get(v0, zero, Type::field()); - let _v5 = builder.insert_array_get(v0, v1, Type::field()); - - builder.insert_enable_side_effects_if(v3); - let v6 = builder.insert_array_get(v0, zero, Type::field()); - let _v7 = builder.insert_array_get(v0, v1, Type::field()); - - builder.insert_constrain(v4, v6, None); - - let ssa = builder.finish(); - - println!("{ssa}"); - - let main = ssa.main(); - let instructions = main.dfg[main.entry_block()].instructions(); - assert_eq!(instructions.len(), 7); + let src = " + acir(inline) fn main f0 { + b0(v0: [Field; 4], v1: u32, v2: bool, v3: bool): + enable_side_effects v2 + v4 = array_get v0, index u32 0 -> Field + v5 = array_get v0, index v1 -> Field + enable_side_effects v3 + v6 = array_get v0, index u32 0 -> Field + v7 = array_get v0, index v1 -> Field + constrain v4 == v6 + return + } + "; + let expected = " + acir(inline) fn main f0 { + b0(v0: [Field; 4], v1: u32, v2: u1, v3: u1): + enable_side_effects v2 + v5 = array_get v0, index u32 0 -> Field + v6 = array_get v0, index v1 -> Field + enable_side_effects v3 + v7 = array_get v0, index v1 -> Field + return + } + "; - // Expected output: - // - // fn main f0 { - // b0(v0: [Field; 4], v1: u32, v2: bool, v3: bool): - // enable_side_effects v2 - // v10 = array_get v0 u32 0 - // v11 = array_get v0 v1 - // enable_side_effects v3 - // v12 = array_get v0 v1 - // } + let ssa = Ssa::from_str(src).unwrap(); let ssa = ssa.fold_constants(); - - println!("{ssa}"); - - let main = ssa.main(); - let instructions = main.dfg[main.entry_block()].instructions(); - - assert_eq!(instructions.len(), 5); + assert_normalized_ssa_equals(ssa, expected); } #[test] fn constraint_decomposition() { - // fn main f0 { - // b0(v0: u1, v1: u1, v2: u1): - // v3 = mul v0 v1 - // v4 = not v2 - // v5 = mul v3 v4 - // constrain v4 u1 1 - // } - // // When constructing this IR, we should automatically decompose the constraint to be in terms of `v0`, `v1` and `v2`. // // The mul instructions are retained and will be removed in the dead instruction elimination pass. - let main_id = Id::test_new(0); - - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id); - let v0 = builder.add_parameter(Type::bool()); - let v1 = builder.add_parameter(Type::bool()); - let v2 = builder.add_parameter(Type::bool()); - - let v3 = builder.insert_binary(v0, BinaryOp::Mul, v1); - let v4 = builder.insert_not(v2); - let v5 = builder.insert_binary(v3, BinaryOp::Mul, v4); - - // This constraint is automatically decomposed when it is inserted. - let v_true = builder.numeric_constant(true, Type::bool()); - builder.insert_constrain(v5, v_true, None); - - let v_false = builder.numeric_constant(false, Type::bool()); - - // Expected output: - // - // fn main f0 { - // b0(v0: u1, v1: u1, v2: u1): - // v3 = mul v0 v1 - // v4 = not v2 - // v5 = mul v3 v4 - // constrain v0 u1 1 - // constrain v1 u1 1 - // constrain v2 u1 0 - // } - - let ssa = builder.finish(); - let main = ssa.main(); - let instructions = main.dfg[main.entry_block()].instructions(); - - assert_eq!(instructions.len(), 6); - - assert_eq!( - main.dfg[instructions[0]], - Instruction::Binary(Binary { lhs: v0, operator: BinaryOp::Mul, rhs: v1 }) - ); - assert_eq!(main.dfg[instructions[1]], Instruction::Not(v2)); - assert_eq!( - main.dfg[instructions[2]], - Instruction::Binary(Binary { lhs: v3, operator: BinaryOp::Mul, rhs: v4 }) - ); - assert_eq!(main.dfg[instructions[3]], Instruction::Constrain(v0, v_true, None)); - assert_eq!(main.dfg[instructions[4]], Instruction::Constrain(v1, v_true, None)); - assert_eq!(main.dfg[instructions[5]], Instruction::Constrain(v2, v_false, None)); + let src = " + acir(inline) fn main f0 { + b0(v0: u1, v1: u1, v2: u1): + v3 = mul v0, v1 + v4 = not v2 + v5 = mul v3, v4 + constrain v5 == u1 1 + return + } + "; + let ssa = Ssa::from_str(src).unwrap(); + + let expected = " + acir(inline) fn main f0 { + b0(v0: u1, v1: u1, v2: u1): + v3 = mul v0, v1 + v4 = not v2 + v5 = mul v3, v4 + constrain v0 == u1 1 + constrain v1 == u1 1 + constrain v2 == u1 0 + return + } + "; + assert_normalized_ssa_equals(ssa, expected); } // Regression for #4600 #[test] fn array_get_regression() { - // fn main f0 { - // b0(v0: u1, v1: u64): - // enable_side_effects_if v0 - // v2 = array_get [Field 0, Field 1], index v1 - // v3 = not v0 - // enable_side_effects_if v3 - // v4 = array_get [Field 0, Field 1], index v1 - // } - // // We want to make sure after constant folding both array_gets remain since they are // under different enable_side_effects_if contexts and thus one may be disabled while // the other is not. If one is removed, it is possible e.g. v4 is replaced with v2 which // is disabled (only gets from index 0) and thus returns the wrong result. - let main_id = Id::test_new(0); - - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id); - let v0 = builder.add_parameter(Type::bool()); - let v1 = builder.add_parameter(Type::unsigned(64)); - - builder.insert_enable_side_effects_if(v0); - - let zero = builder.field_constant(0u128); - let one = builder.field_constant(1u128); - - let typ = Type::Array(Arc::new(vec![Type::field()]), 2); - let array = builder.array_constant(vec![zero, one].into(), typ); - - let _v2 = builder.insert_array_get(array, v1, Type::field()); - let v3 = builder.insert_not(v0); - - builder.insert_enable_side_effects_if(v3); - let _v4 = builder.insert_array_get(array, v1, Type::field()); + let src = " + acir(inline) fn main f0 { + b0(v0: u1, v1: u64): + enable_side_effects v0 + v5 = array_get [Field 0, Field 1] of Field, index v1 -> Field + v6 = not v0 + enable_side_effects v6 + v8 = array_get [Field 0, Field 1] of Field, index v1 -> Field + return + } + "; + let ssa = Ssa::from_str(src).unwrap(); // Expected output is unchanged - let ssa = builder.finish(); - let main = ssa.main(); - let instructions = main.dfg[main.entry_block()].instructions(); - let starting_instruction_count = instructions.len(); - assert_eq!(starting_instruction_count, 5); - let ssa = ssa.fold_constants(); - let main = ssa.main(); - let instructions = main.dfg[main.entry_block()].instructions(); - let ending_instruction_count = instructions.len(); - assert_eq!(starting_instruction_count, ending_instruction_count); + assert_normalized_ssa_equals(ssa, src); } #[test] fn deduplicate_instructions_with_predicates() { - // fn main f0 { - // b0(v0: bool, v1: bool, v2: [u32; 2]): - // enable_side_effects v0 - // v3 = array_get v2, index u32 0 - // v4 = array_set v2, index u32 1, value: u32 2 - // v5 = array_get v4, index u32 0 - // constrain_eq v3, v5 - // enable_side_effects v1 - // v6 = array_get v2, index u32 0 - // v7 = array_set v2, index u32 1, value: u32 2 - // v8 = array_get v7, index u32 0 - // constrain_eq v6, v8 - // enable_side_effects v0 - // v9 = array_get v2, index u32 0 - // v10 = array_set v2, index u32 1, value: u32 2 - // v11 = array_get v10, index u32 0 - // constrain_eq v9, v11 - // } - let main_id = Id::test_new(0); - - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id); - - let v0 = builder.add_parameter(Type::bool()); - let v1 = builder.add_parameter(Type::bool()); - let v2 = builder.add_parameter(Type::Array(Arc::new(vec![Type::field()]), 2)); - - let zero = builder.numeric_constant(0u128, Type::length_type()); - let one = builder.numeric_constant(1u128, Type::length_type()); - let two = builder.numeric_constant(2u128, Type::length_type()); - - builder.insert_enable_side_effects_if(v0); - let v3 = builder.insert_array_get(v2, zero, Type::length_type()); - let v4 = builder.insert_array_set(v2, one, two); - let v5 = builder.insert_array_get(v4, zero, Type::length_type()); - builder.insert_constrain(v3, v5, None); - - builder.insert_enable_side_effects_if(v1); - let v6 = builder.insert_array_get(v2, zero, Type::length_type()); - let v7 = builder.insert_array_set(v2, one, two); - let v8 = builder.insert_array_get(v7, zero, Type::length_type()); - builder.insert_constrain(v6, v8, None); - - // We expect all these instructions after the 'enable side effects' instruction to be removed. - builder.insert_enable_side_effects_if(v0); - let v9 = builder.insert_array_get(v2, zero, Type::length_type()); - let v10 = builder.insert_array_set(v2, one, two); - let v11 = builder.insert_array_get(v10, zero, Type::length_type()); - builder.insert_constrain(v9, v11, None); - - let ssa = builder.finish(); - println!("{ssa}"); + let src = " + acir(inline) fn main f0 { + b0(v0: u1, v1: u1, v2: [Field; 2]): + enable_side_effects v0 + v6 = array_get v2, index u32 0 -> u32 + v7 = array_set v2, index u32 1, value u32 2 + v8 = array_get v7, index u32 0 -> u32 + constrain v6 == v8 + enable_side_effects v1 + v9 = array_get v2, index u32 0 -> u32 + v10 = array_set v2, index u32 1, value u32 2 + v11 = array_get v10, index u32 0 -> u32 + constrain v9 == v11 + enable_side_effects v0 + v12 = array_get v2, index u32 0 -> u32 + v13 = array_set v2, index u32 1, value u32 2 + v14 = array_get v13, index u32 0 -> u32 + constrain v12 == v14 + return + } + "; + let ssa = Ssa::from_str(src).unwrap(); let main = ssa.main(); let instructions = main.dfg[main.entry_block()].instructions(); assert_eq!(instructions.len(), 15); - // Expected output: - // - // fn main f0 { - // b0(v0: bool, v1: bool, v2: [Field; 2]): - // enable_side_effects v0 - // v3 = array_get v2, index Field 0 - // v4 = array_set v2, index Field 1, value: Field 2 - // v5 = array_get v4, index Field 0 - // constrain_eq v3, v5 - // enable_side_effects v1 - // v7 = array_set v2, index Field 1, value: Field 2 - // v8 = array_get v7, index Field 0 - // constrain_eq v3, v8 - // enable_side_effects v0 - // } - let ssa = ssa.fold_constants_using_constraints(); - println!("{ssa}"); + let expected = " + acir(inline) fn main f0 { + b0(v0: u1, v1: u1, v2: [Field; 2]): + enable_side_effects v0 + v4 = array_get v2, index u32 0 -> u32 + v7 = array_set v2, index u32 1, value u32 2 + v8 = array_get v7, index u32 0 -> u32 + constrain v4 == v8 + enable_side_effects v1 + v9 = array_set v2, index u32 1, value u32 2 + v10 = array_get v9, index u32 0 -> u32 + constrain v4 == v10 + enable_side_effects v0 + return + } + "; - let main = ssa.main(); - let instructions = main.dfg[main.entry_block()].instructions(); - assert_eq!(instructions.len(), 10); + let ssa = ssa.fold_constants_using_constraints(); + assert_normalized_ssa_equals(ssa, expected); } // This test currently fails. It being fixed will address the issue https://github.com/noir-lang/noir/issues/5756 @@ -878,7 +652,8 @@ mod test { let _v10 = builder.insert_call(keccakf1600, vec![array1], vec![typ.clone()]); let _v11 = builder.insert_call(keccakf1600, vec![array2], vec![typ.clone()]); - let ssa = builder.finish(); + let mut ssa = builder.finish(); + ssa.normalize_ids(); println!("{ssa}"); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs index beca7c41e5c..57af27e8dcd 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/die.rs @@ -622,210 +622,118 @@ mod test { use crate::ssa::{ function_builder::FunctionBuilder, - ir::{ - instruction::{BinaryOp, Instruction, Intrinsic}, - map::Id, - types::Type, - }, + ir::{instruction::Instruction, map::Id, types::Type}, + opt::assert_normalized_ssa_equals, + Ssa, }; #[test] fn dead_instruction_elimination() { - // fn main f0 { - // b0(v0: Field): - // v1 = add v0, Field 1 - // v2 = add v0, Field 2 - // jmp b1(v2) - // b1(v3: Field): - // v4 = allocate 1 field - // v5 = load v4 - // v6 = allocate 1 field - // store Field 1 in v6 - // v7 = load v6 - // v8 = add v7, Field 1 - // v9 = add v7, Field 2 - // v10 = add v7, Field 3 - // v11 = add v10, v10 - // call assert_constant(v8) - // return v9 - // } - let main_id = Id::test_new(0); - - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id); - let v0 = builder.add_parameter(Type::field()); - let b1 = builder.insert_block(); - - let one = builder.field_constant(1u128); - let two = builder.field_constant(2u128); - let three = builder.field_constant(3u128); - - let _v1 = builder.insert_binary(v0, BinaryOp::Add, one); - let v2 = builder.insert_binary(v0, BinaryOp::Add, two); - builder.terminate_with_jmp(b1, vec![v2]); - - builder.switch_to_block(b1); - let _v3 = builder.add_block_parameter(b1, Type::field()); - - let v4 = builder.insert_allocate(Type::field()); - let _v5 = builder.insert_load(v4, Type::field()); - - let v6 = builder.insert_allocate(Type::field()); - builder.insert_store(v6, one); - let v7 = builder.insert_load(v6, Type::field()); - let v8 = builder.insert_binary(v7, BinaryOp::Add, one); - let v9 = builder.insert_binary(v7, BinaryOp::Add, two); - let v10 = builder.insert_binary(v7, BinaryOp::Add, three); - let _v11 = builder.insert_binary(v10, BinaryOp::Add, v10); - - let assert_constant_id = builder.import_intrinsic_id(Intrinsic::AssertConstant); - builder.insert_call(assert_constant_id, vec![v8], vec![]); - builder.terminate_with_return(vec![v9]); - - let ssa = builder.finish(); - let main = ssa.main(); - - // The instruction count never includes the terminator instruction - assert_eq!(main.dfg[main.entry_block()].instructions().len(), 2); - assert_eq!(main.dfg[b1].instructions().len(), 10); - - // Expected output: - // - // fn main f0 { - // b0(v0: Field): - // v2 = add v0, Field 2 - // jmp b1(v2) - // b1(v3: Field): - // v6 = allocate 1 field - // store Field 1 in v6 - // v7 = load v6 - // v8 = add v7, Field 1 - // v9 = add v7, Field 2 - // call assert_constant(v8) - // return v9 - // } + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + v3 = add v0, Field 1 + v5 = add v0, Field 2 + jmp b1(v5) + b1(v1: Field): + v6 = allocate -> &mut Field + v7 = load v6 -> Field + v8 = allocate -> &mut Field + store Field 1 at v8 + v9 = load v8 -> Field + v10 = add v9, Field 1 + v11 = add v9, Field 2 + v13 = add v9, Field 3 + v14 = add v13, v13 + call assert_constant(v10) + return v11 + } + "; + let ssa = Ssa::from_str(src).unwrap(); + + let expected = " + acir(inline) fn main f0 { + b0(v0: Field): + v3 = add v0, Field 2 + jmp b1(v3) + b1(v1: Field): + v4 = allocate -> &mut Field + store Field 1 at v4 + v6 = load v4 -> Field + v7 = add v6, Field 1 + v8 = add v6, Field 2 + call assert_constant(v7) + return v8 + } + "; let ssa = ssa.dead_instruction_elimination(); - let main = ssa.main(); - - assert_eq!(main.dfg[main.entry_block()].instructions().len(), 1); - assert_eq!(main.dfg[b1].instructions().len(), 6); + assert_normalized_ssa_equals(ssa, expected); } #[test] fn as_witness_die() { - // fn main f0 { - // b0(v0: Field): - // v1 = add v0, Field 1 - // v2 = add v0, Field 2 - // call as_witness(v2) - // return v1 - // } - let main_id = Id::test_new(0); - - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id); - let v0 = builder.add_parameter(Type::field()); - - let one = builder.field_constant(1u128); - let two = builder.field_constant(2u128); - - let v1 = builder.insert_binary(v0, BinaryOp::Add, one); - let v2 = builder.insert_binary(v0, BinaryOp::Add, two); - let as_witness = builder.import_intrinsic("as_witness").unwrap(); - builder.insert_call(as_witness, vec![v2], Vec::new()); - builder.terminate_with_return(vec![v1]); - - let ssa = builder.finish(); - let main = ssa.main(); - - // The instruction count never includes the terminator instruction - assert_eq!(main.dfg[main.entry_block()].instructions().len(), 3); - - // Expected output: - // - // acir(inline) fn main f0 { - // b0(v0: Field): - // v3 = add v0, Field 1 - // return v3 - // } + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + v2 = add v0, Field 1 + v4 = add v0, Field 2 + call as_witness(v4) + return v2 + } + "; + let ssa = Ssa::from_str(src).unwrap(); + + let expected = " + acir(inline) fn main f0 { + b0(v0: Field): + v2 = add v0, Field 1 + return v2 + } + "; let ssa = ssa.dead_instruction_elimination(); - let main = ssa.main(); - - assert_eq!(main.dfg[main.entry_block()].instructions().len(), 1); + assert_normalized_ssa_equals(ssa, expected); } #[test] fn remove_useless_paired_rcs_even_when_used() { - // acir(inline) fn main f0 { - // b0(v0: [Field; 2]): - // inc_rc v0 - // v2 = array_get v0, index u32 0 - // dec_rc v0 - // return v2 - // } - let main_id = Id::test_new(0); - - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id); - let v0 = builder.add_parameter(Type::Array(Arc::new(vec![Type::field()]), 2)); - builder.increment_array_reference_count(v0); - let zero = builder.numeric_constant(0u128, Type::unsigned(32)); - let v1 = builder.insert_array_get(v0, zero, Type::field()); - builder.decrement_array_reference_count(v0); - builder.terminate_with_return(vec![v1]); - - let ssa = builder.finish(); - let main = ssa.main(); - - // The instruction count never includes the terminator instruction - assert_eq!(main.dfg[main.entry_block()].instructions().len(), 3); - - // Expected output: - // - // acir(inline) fn main f0 { - // b0(v0: [Field; 2]): - // v2 = array_get v0, index u32 0 - // return v2 - // } + let src = " + acir(inline) fn main f0 { + b0(v0: [Field; 2]): + inc_rc v0 + v2 = array_get v0, index u32 0 -> Field + dec_rc v0 + return v2 + } + "; + let ssa = Ssa::from_str(src).unwrap(); + + let expected = " + acir(inline) fn main f0 { + b0(v0: [Field; 2]): + v2 = array_get v0, index u32 0 -> Field + return v2 + } + "; let ssa = ssa.dead_instruction_elimination(); - let main = ssa.main(); - - let instructions = main.dfg[main.entry_block()].instructions(); - assert_eq!(instructions.len(), 1); - assert!(matches!(&main.dfg[instructions[0]], Instruction::ArrayGet { .. })); + assert_normalized_ssa_equals(ssa, expected); } #[test] fn keep_paired_rcs_with_array_set() { - // acir(inline) fn main f0 { - // b0(v0: [Field; 2]): - // inc_rc v0 - // v2 = array_set v0, index u32 0, value u32 0 - // dec_rc v0 - // return v2 - // } - let main_id = Id::test_new(0); - - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id); - let v0 = builder.add_parameter(Type::Array(Arc::new(vec![Type::field()]), 2)); - builder.increment_array_reference_count(v0); - let zero = builder.numeric_constant(0u128, Type::unsigned(32)); - let v1 = builder.insert_array_set(v0, zero, zero); - builder.decrement_array_reference_count(v0); - builder.terminate_with_return(vec![v1]); - - let ssa = builder.finish(); - let main = ssa.main(); - - // The instruction count never includes the terminator instruction - assert_eq!(main.dfg[main.entry_block()].instructions().len(), 3); + let src = " + acir(inline) fn main f0 { + b0(v0: [Field; 2]): + inc_rc v0 + v2 = array_set v0, index u32 0, value u32 0 + dec_rc v0 + return v2 + } + "; + let ssa = Ssa::from_str(src).unwrap(); // We expect the output to be unchanged let ssa = ssa.dead_instruction_elimination(); - let main = ssa.main(); - - assert_eq!(main.dfg[main.entry_block()].instructions().len(), 3); + assert_normalized_ssa_equals(ssa, src); } #[test] @@ -940,47 +848,31 @@ mod test { #[test] fn remove_inc_rcs_that_are_never_mutably_borrowed() { - // acir(inline) fn main f0 { - // b0(v0: [Field; 2]): - // inc_rc v0 - // inc_rc v0 - // inc_rc v0 - // v2 = array_get v0, index u32 0 - // inc_rc v0 - // return v2 - // } - let main_id = Id::test_new(0); - - // Compiling main - let mut builder = FunctionBuilder::new("main".into(), main_id); - let v0 = builder.add_parameter(Type::Array(Arc::new(vec![Type::field()]), 2)); - builder.increment_array_reference_count(v0); - builder.increment_array_reference_count(v0); - builder.increment_array_reference_count(v0); - - let zero = builder.numeric_constant(0u128, Type::unsigned(32)); - let v2 = builder.insert_array_get(v0, zero, Type::field()); - builder.increment_array_reference_count(v0); - builder.terminate_with_return(vec![v2]); - - let ssa = builder.finish(); + let src = " + acir(inline) fn main f0 { + b0(v0: [Field; 2]): + inc_rc v0 + inc_rc v0 + inc_rc v0 + v2 = array_get v0, index u32 0 -> Field + inc_rc v0 + return v2 + } + "; + let ssa = Ssa::from_str(src).unwrap(); let main = ssa.main(); // The instruction count never includes the terminator instruction assert_eq!(main.dfg[main.entry_block()].instructions().len(), 5); - // Expected output: - // - // acir(inline) fn main f0 { - // b0(v0: [Field; 2]): - // v2 = array_get v0, index u32 0 - // return v2 - // } + let expected = " + acir(inline) fn main f0 { + b0(v0: [Field; 2]): + v2 = array_get v0, index u32 0 -> Field + return v2 + } + "; let ssa = ssa.dead_instruction_elimination(); - let main = ssa.main(); - - let instructions = main.dfg[main.entry_block()].instructions(); - assert_eq!(instructions.len(), 1); - assert!(matches!(&main.dfg[instructions[0]], Instruction::ArrayGet { .. })); + assert_normalized_ssa_equals(ssa, expected); } } diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 984f639df00..db2d96aac81 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -930,237 +930,156 @@ mod test { types::Type, value::{Value, ValueId}, }, + opt::assert_normalized_ssa_equals, + Ssa, }; #[test] fn basic_jmpif() { - // fn main f0 { - // b0(v0: b1): - // jmpif v0, then: b1, else: b2 - // b1(): - // jmp b3(Field 3) - // b2(): - // jmp b3(Field 4) - // b3(v1: Field): - // return v1 - // } - let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id); - - let b1 = builder.insert_block(); - let b2 = builder.insert_block(); - let b3 = builder.insert_block(); - - let v0 = builder.add_parameter(Type::bool()); - let v1 = builder.add_block_parameter(b3, Type::field()); - - let three = builder.field_constant(3u128); - let four = builder.field_constant(4u128); - - builder.terminate_with_jmpif(v0, b1, b2); - - builder.switch_to_block(b1); - builder.terminate_with_jmp(b3, vec![three]); - - builder.switch_to_block(b2); - builder.terminate_with_jmp(b3, vec![four]); - - builder.switch_to_block(b3); - builder.terminate_with_return(vec![v1]); - - let ssa = builder.finish(); + let src = " + acir(inline) fn main f0 { + b0(v0: u1): + jmpif v0 then: b1, else: b2 + b1(): + jmp b3(Field 3) + b3(v1: Field): + return v1 + b2(): + jmp b3(Field 4) + } + "; + let ssa = Ssa::from_str(src).unwrap(); assert_eq!(ssa.main().reachable_blocks().len(), 4); - // Expected output: - // fn main f0 { - // b0(v0: u1): - // enable_side_effects v0 - // v5 = not v0 - // enable_side_effects v5 - // enable_side_effects u1 1 - // v7 = mul v0, Field 3 - // v8 = mul v5, Field 4 - // v9 = add v7, v8 - // return v9 - // } + let expected = " + acir(inline) fn main f0 { + b0(v0: u1): + enable_side_effects v0 + v1 = not v0 + enable_side_effects u1 1 + v3 = cast v0 as Field + v4 = cast v1 as Field + v6 = mul v3, Field 3 + v8 = mul v4, Field 4 + v9 = add v6, v8 + return v9 + } + "; + let ssa = ssa.flatten_cfg(); - assert_eq!(ssa.main().reachable_blocks().len(), 1); + assert_normalized_ssa_equals(ssa, expected); } #[test] fn modify_constrain() { - // fn main f0 { - // b0(v0: u1, v1: u1): - // jmpif v0, then: b1, else: b2 - // b1(): - // constrain v1 - // jmp b2() - // b2(): - // return - // } - let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id); - - let b1 = builder.insert_block(); - let b2 = builder.insert_block(); - - let v0 = builder.add_parameter(Type::bool()); - let v1 = builder.add_parameter(Type::bool()); - let v_true = builder.numeric_constant(true, Type::bool()); - - builder.terminate_with_jmpif(v0, b1, b2); - - builder.switch_to_block(b1); - builder.insert_constrain(v1, v_true, None); - builder.terminate_with_jmp(b2, vec![]); - - builder.switch_to_block(b2); - builder.terminate_with_return(vec![]); - - let ssa = builder.finish(); + let src = " + acir(inline) fn main f0 { + b0(v0: u1, v1: u1): + jmpif v0 then: b1, else: b2 + b1(): + constrain v1 == u1 1 + jmp b2() + b2(): + return + } + "; + let ssa = Ssa::from_str(src).unwrap(); assert_eq!(ssa.main().reachable_blocks().len(), 3); - // Expected output: - // fn main f0 { - // b0(v0: u1, v1: u1): - // enable_side_effects v0 - // v3 = mul v1, v0 - // v4 = eq v3, v0 - // constrain v4 - // v5 = not v0 - // enable_side_effects v5 - // enable_side_effects u1 1 - // return - // } + let expected = " + acir(inline) fn main f0 { + b0(v0: u1, v1: u1): + enable_side_effects v0 + v2 = mul v1, v0 + constrain v2 == v0 + v3 = not v0 + enable_side_effects u1 1 + return + } + "; let ssa = ssa.flatten_cfg(); assert_eq!(ssa.main().reachable_blocks().len(), 1); + assert_normalized_ssa_equals(ssa, expected); } #[test] fn merge_stores() { - // fn main f0 { - // b0(v0: u1, v1: &mut Field): - // jmpif v0, then: b1, else: b2 - // b1(): - // store v1, Field 5 - // jmp b2() - // b2(): - // return - // } - let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id); - - let b1 = builder.insert_block(); - let b2 = builder.insert_block(); - - let v0 = builder.add_parameter(Type::bool()); - let v1 = builder.add_parameter(Type::Reference(Arc::new(Type::field()))); - - builder.terminate_with_jmpif(v0, b1, b2); - - builder.switch_to_block(b1); - let five = builder.field_constant(5u128); - builder.insert_store(v1, five); - builder.terminate_with_jmp(b2, vec![]); - - builder.switch_to_block(b2); - builder.terminate_with_return(vec![]); - - let ssa = builder.finish(); - - // Expected output: - // fn main f0 { - // b0(v0: u1, v1: reference): - // enable_side_effects v0 - // v4 = load v1 - // store Field 5 at v1 - // v5 = not v0 - // store v4 at v1 - // enable_side_effects u1 1 - // v6 = cast v0 as Field - // v7 = cast v5 as Field - // v8 = mul v6, Field 5 - // v9 = mul v7, v4 - // v10 = add v8, v9 - // store v10 at v1 - // return - // } + let src = " + acir(inline) fn main f0 { + b0(v0: u1, v1: &mut Field): + jmpif v0 then: b1, else: b2 + b1(): + store Field 5 at v1 + jmp b2() + b2(): + return + } + "; + let ssa = Ssa::from_str(src).unwrap(); + + let expected = " + acir(inline) fn main f0 { + b0(v0: u1, v1: &mut Field): + enable_side_effects v0 + v2 = load v1 -> Field + store Field 5 at v1 + v4 = not v0 + store v2 at v1 + enable_side_effects u1 1 + v6 = cast v0 as Field + v7 = cast v4 as Field + v8 = mul v6, Field 5 + v9 = mul v7, v2 + v10 = add v8, v9 + store v10 at v1 + return + } + "; let ssa = ssa.flatten_cfg(); - let main = ssa.main(); - - assert_eq!(main.reachable_blocks().len(), 1); - - let store_count = count_instruction(main, |ins| matches!(ins, Instruction::Store { .. })); - assert_eq!(store_count, 3); + assert_normalized_ssa_equals(ssa, expected); } #[test] fn merge_stores_with_else_block() { - // fn main f0 { - // b0(v0: u1, v1: ref): - // jmpif v0, then: b1, else: b2 - // b1(): - // store Field 5 in v1 - // jmp b3() - // b2(): - // store Field 6 in v1 - // jmp b3() - // b3(): - // return - // } - let main_id = Id::test_new(0); - let mut builder = FunctionBuilder::new("main".into(), main_id); - - let b1 = builder.insert_block(); - let b2 = builder.insert_block(); - let b3 = builder.insert_block(); - - let v0 = builder.add_parameter(Type::bool()); - let v1 = builder.add_parameter(Type::Reference(Arc::new(Type::field()))); - - builder.terminate_with_jmpif(v0, b1, b2); - - builder.switch_to_block(b1); - let five = builder.field_constant(5u128); - builder.insert_store(v1, five); - builder.terminate_with_jmp(b3, vec![]); - - builder.switch_to_block(b2); - let six = builder.field_constant(6u128); - builder.insert_store(v1, six); - builder.terminate_with_jmp(b3, vec![]); - - builder.switch_to_block(b3); - builder.terminate_with_return(vec![]); - - let ssa = builder.finish(); - - // Expected output: - // fn main f0 { - // b0(v0: u1, v1: reference): - // enable_side_effects v0 - // v5 = load v1 - // store Field 5 at v1 - // v6 = not v0 - // store v5 at v1 - // enable_side_effects v6 - // v8 = load v1 - // store Field 6 at v1 - // enable_side_effects u1 1 - // v9 = cast v0 as Field - // v10 = cast v6 as Field - // v11 = mul v9, Field 5 - // v12 = mul v10, Field 6 - // v13 = add v11, v12 - // store v13 at v1 - // return - // } + let src = " + acir(inline) fn main f0 { + b0(v0: u1, v1: &mut Field): + jmpif v0 then: b1, else: b2 + b1(): + store Field 5 at v1 + jmp b3() + b2(): + store Field 6 at v1 + jmp b3() + b3(): + return + } + "; + let ssa = Ssa::from_str(src).unwrap(); + + let expected = " + acir(inline) fn main f0 { + b0(v0: u1, v1: &mut Field): + enable_side_effects v0 + v2 = load v1 -> Field + store Field 5 at v1 + v4 = not v0 + store v2 at v1 + enable_side_effects v4 + v5 = load v1 -> Field + store Field 6 at v1 + enable_side_effects u1 1 + v8 = cast v0 as Field + v9 = cast v4 as Field + v10 = mul v8, Field 5 + v11 = mul v9, Field 6 + v12 = add v10, v11 + store v12 at v1 + return + } + "; let ssa = ssa.flatten_cfg(); - let main = ssa.main(); - assert_eq!(main.reachable_blocks().len(), 1); - - let store_count = count_instruction(main, |ins| matches!(ins, Instruction::Store { .. })); - assert_eq!(store_count, 4); + assert_normalized_ssa_equals(ssa, expected); } fn count_instruction(function: &Function, f: impl Fn(&Instruction) -> bool) -> usize { @@ -1445,40 +1364,29 @@ mod test { // Very simplified derived regression test for #1792 // Tests that it does not simplify to a true constraint an always-false constraint // The original function is replaced by the following: - // fn main f1 { - // b0(): - // jmpif u1 0 then: b1, else: b2 - // b1(): - // jmp b2() - // b2(): - // constrain u1 0 // was incorrectly removed - // return - // } - let main_id = Id::test_new(1); - let mut builder = FunctionBuilder::new("main".into(), main_id); - - builder.insert_block(); // entry - - let b1 = builder.insert_block(); - let b2 = builder.insert_block(); - let v_true = builder.numeric_constant(true, Type::bool()); - let v_false = builder.numeric_constant(false, Type::bool()); - builder.terminate_with_jmpif(v_false, b1, b2); - - builder.switch_to_block(b1); - builder.terminate_with_jmp(b2, vec![]); - - builder.switch_to_block(b2); - builder.insert_constrain(v_false, v_true, None); // should not be removed - builder.terminate_with_return(vec![]); - - let ssa = builder.finish().flatten_cfg(); - let main = ssa.main(); - - // Assert we have not incorrectly removed a constraint: - use Instruction::Constrain; - let constrain_count = count_instruction(main, |ins| matches!(ins, Constrain(..))); - assert_eq!(constrain_count, 1); + let src = " + acir(inline) fn main f1 { + b0(): + jmpif u1 0 then: b1, else: b2 + b1(): + jmp b2() + b2(): + constrain u1 0 == u1 1 // was incorrectly removed + return + } + "; + let ssa = Ssa::from_str(src).unwrap(); + + let expected = " + acir(inline) fn main f0 { + b0(): + enable_side_effects u1 1 + constrain u1 0 == u1 1 + return + } + "; + let ssa = ssa.flatten_cfg(); + assert_normalized_ssa_equals(ssa, expected); } #[test] diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs index 9e3dd3e5a30..95a95157114 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs @@ -904,7 +904,7 @@ mod tests { let mut builder = FunctionBuilder::new("main".into(), main_id); let v0 = builder.insert_allocate(Type::field()); - let zero = builder.numeric_constant(0u128, Type::field()); + let zero = builder.field_constant(0u128); builder.insert_store(v0, zero); let v2 = builder.insert_allocate(Type::field()); @@ -928,9 +928,9 @@ mod tests { // Loop body builder.switch_to_block(b2); let v5 = builder.insert_load(v2, v2_type.clone()); - let two = builder.numeric_constant(2u128, Type::field()); + let two = builder.field_constant(2u128); builder.insert_store(v5, two); - let one = builder.numeric_constant(1u128, Type::field()); + let one = builder.field_constant(1u128); let v3_plus_one = builder.insert_binary(v3, BinaryOp::Add, one); builder.terminate_with_jmp(b1, vec![v3_plus_one]); diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mod.rs index f3dbd58fa69..5576b494570 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/mod.rs @@ -3,6 +3,7 @@ //! Each pass is generally expected to mutate the SSA IR into a gradually //! simpler form until the IR only has a single function remaining with 1 block within it. //! Generally, these passes are also expected to minimize the final amount of instructions. + mod array_set; mod as_slice_length; mod assert_constant; @@ -21,3 +22,29 @@ mod resolve_is_unconstrained; mod runtime_separation; mod simplify_cfg; mod unrolling; + +/// Asserts that the given SSA, after normalizing its IDs and printing it, +/// is equal to the expected strings. Normalization is done so the IDs don't +/// shift depending on whether temporary intermediate values were created. +#[cfg(test)] +pub(crate) fn assert_normalized_ssa_equals(mut ssa: super::Ssa, expected: &str) { + // First check if `expected` is valid SSA by parsing it, otherwise + // the comparison will always fail but it won't be clear that it's because + // expected is not valid. + if let Err(err) = Ssa::from_str(expected) { + panic!("`expected` argument of `assert_ssa_equals` is not valid SSA:\n{:?}", err); + } + + use crate::{ssa::Ssa, trim_leading_whitespace_from_lines}; + + ssa.normalize_ids(); + + let ssa = ssa.to_string(); + let ssa = trim_leading_whitespace_from_lines(&ssa); + let expected = trim_leading_whitespace_from_lines(expected); + + if ssa != expected { + println!("Got:\n~~~\n{}\n~~~\nExpected:\n~~~\n{}\n~~~", ssa, expected); + similar_asserts::assert_eq!(ssa, expected); + } +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser.rs new file mode 100644 index 00000000000..717d2691b2f --- /dev/null +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser.rs @@ -0,0 +1,902 @@ +use std::{ + fmt::{self, Debug, Formatter}, + sync::Arc, +}; + +use super::{ + ir::{instruction::BinaryOp, types::Type}, + Ssa, +}; + +use acvm::{AcirField, FieldElement}; +use ast::{ + Identifier, ParsedBlock, ParsedFunction, ParsedInstruction, ParsedParameter, ParsedSsa, + ParsedValue, +}; +use lexer::{Lexer, LexerError}; +use noirc_errors::Span; +use noirc_frontend::{monomorphization::ast::InlineType, token::IntType}; +use thiserror::Error; +use token::{Keyword, SpannedToken, Token}; + +use crate::ssa::{ir::function::RuntimeType, parser::ast::ParsedTerminator}; + +mod ast; +mod into_ssa; +mod lexer; +mod tests; +mod token; + +impl Ssa { + pub(crate) fn from_str(src: &str) -> Result { + let mut parser = + Parser::new(src).map_err(|err| SsaErrorWithSource::parse_error(err, src))?; + let parsed_ssa = + parser.parse_ssa().map_err(|err| SsaErrorWithSource::parse_error(err, src))?; + parsed_ssa.into_ssa().map_err(|error| SsaErrorWithSource { src: src.to_string(), error }) + } +} + +pub(crate) struct SsaErrorWithSource { + src: String, + error: SsaError, +} + +impl SsaErrorWithSource { + fn parse_error(error: ParserError, src: &str) -> Self { + Self { src: src.to_string(), error: SsaError::ParserError(error) } + } +} + +impl Debug for SsaErrorWithSource { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let span = self.error.span(); + + let mut byte: usize = 0; + for line in self.src.lines() { + let has_error = + byte <= span.start() as usize && span.end() as usize <= byte + line.len(); + if has_error { + writeln!(f)?; + } + + writeln!(f, "{}", line)?; + + if has_error { + let offset = span.start() as usize - byte; + write!(f, "{}", " ".repeat(offset))?; + writeln!(f, "{}", "^".repeat((span.end() - span.start()) as usize))?; + write!(f, "{}", " ".repeat(offset))?; + writeln!(f, "{}", self.error)?; + writeln!(f)?; + } + + byte += line.len() + 1; // "+ 1" for the newline + } + Ok(()) + } +} + +#[derive(Debug, Error)] +pub(crate) enum SsaError { + #[error("{0}")] + ParserError(ParserError), + #[error("Unknown variable '{0}'")] + UnknownVariable(Identifier), + #[error("Unknown block '{0}'")] + UnknownBlock(Identifier), + #[error("Unknown function '{0}'")] + UnknownFunction(Identifier), + #[error("Mismatched return values")] + MismatchedReturnValues { returns: Vec, expected: usize }, + #[error("Variable '{0}' already defined")] + VariableAlreadyDefined(Identifier), +} + +impl SsaError { + fn span(&self) -> Span { + match self { + SsaError::ParserError(parser_error) => parser_error.span(), + SsaError::UnknownVariable(identifier) + | SsaError::UnknownBlock(identifier) + | SsaError::VariableAlreadyDefined(identifier) + | SsaError::UnknownFunction(identifier) => identifier.span, + SsaError::MismatchedReturnValues { returns, expected: _ } => returns[0].span, + } + } +} + +type ParseResult = Result; + +pub(crate) struct Parser<'a> { + lexer: Lexer<'a>, + token: SpannedToken, +} + +impl<'a> Parser<'a> { + pub(crate) fn new(source: &'a str) -> ParseResult { + let lexer = Lexer::new(source); + let mut parser = Self { lexer, token: eof_spanned_token() }; + parser.token = parser.read_token_internal()?; + Ok(parser) + } + + pub(crate) fn parse_ssa(&mut self) -> ParseResult { + let mut functions = Vec::new(); + while !self.at(Token::Eof) { + let function = self.parse_function()?; + functions.push(function); + } + Ok(ParsedSsa { functions }) + } + + fn parse_function(&mut self) -> ParseResult { + let runtime_type = self.parse_runtime_type()?; + self.eat_or_error(Token::Keyword(Keyword::Fn))?; + + let external_name = self.eat_ident_or_error()?; + let internal_name = self.eat_ident_or_error()?; + + self.eat_or_error(Token::LeftBrace)?; + + let blocks = self.parse_blocks()?; + + self.eat_or_error(Token::RightBrace)?; + + Ok(ParsedFunction { runtime_type, external_name, internal_name, blocks }) + } + + fn parse_runtime_type(&mut self) -> ParseResult { + let acir = if self.eat_keyword(Keyword::Acir)? { + true + } else if self.eat_keyword(Keyword::Brillig)? { + false + } else { + return self.expected_one_of_tokens(&[ + Token::Keyword(Keyword::Acir), + Token::Keyword(Keyword::Brillig), + ]); + }; + + self.eat_or_error(Token::LeftParen)?; + let inline_type = self.parse_inline_type()?; + self.eat_or_error(Token::RightParen)?; + + if acir { + Ok(RuntimeType::Acir(inline_type)) + } else { + Ok(RuntimeType::Brillig(inline_type)) + } + } + + fn parse_inline_type(&mut self) -> ParseResult { + if self.eat_keyword(Keyword::Inline)? { + Ok(InlineType::Inline) + } else if self.eat_keyword(Keyword::InlineAlways)? { + Ok(InlineType::InlineAlways) + } else if self.eat_keyword(Keyword::Fold)? { + Ok(InlineType::Fold) + } else if self.eat_keyword(Keyword::NoPredicates)? { + Ok(InlineType::NoPredicates) + } else { + self.expected_one_of_tokens(&[ + Token::Keyword(Keyword::Inline), + Token::Keyword(Keyword::InlineAlways), + Token::Keyword(Keyword::Fold), + Token::Keyword(Keyword::NoPredicates), + ]) + } + } + + fn parse_blocks(&mut self) -> ParseResult> { + let mut blocks = Vec::new(); + while !self.at(Token::RightBrace) { + let block = self.parse_block()?; + blocks.push(block); + } + Ok(blocks) + } + + fn parse_block(&mut self) -> ParseResult { + let name = self.eat_ident_or_error()?; + self.eat_or_error(Token::LeftParen)?; + + let mut parameters = Vec::new(); + while !self.at(Token::RightParen) { + parameters.push(self.parse_parameter()?); + if !self.eat(Token::Comma)? { + break; + } + } + + self.eat_or_error(Token::RightParen)?; + self.eat_or_error(Token::Colon)?; + + let instructions = self.parse_instructions()?; + let terminator = self.parse_terminator()?; + Ok(ParsedBlock { name, parameters, instructions, terminator }) + } + + fn parse_parameter(&mut self) -> ParseResult { + let identifier = self.eat_identifier_or_error()?; + self.eat_or_error(Token::Colon)?; + let typ = self.parse_type()?; + Ok(ParsedParameter { identifier, typ }) + } + + fn parse_instructions(&mut self) -> ParseResult> { + let mut instructions = Vec::new(); + while let Some(instruction) = self.parse_instruction()? { + instructions.push(instruction); + } + Ok(instructions) + } + + fn parse_instruction(&mut self) -> ParseResult> { + if let Some(instruction) = self.parse_call()? { + return Ok(Some(instruction)); + } + + if let Some(instruction) = self.parse_constrain()? { + return Ok(Some(instruction)); + } + + if let Some(instruction) = self.parse_decrement_rc()? { + return Ok(Some(instruction)); + } + + if let Some(instruction) = self.parse_enable_side_effects()? { + return Ok(Some(instruction)); + } + + if let Some(instruction) = self.parse_increment_rc()? { + return Ok(Some(instruction)); + } + + if let Some(instruction) = self.parse_range_check()? { + return Ok(Some(instruction)); + } + + if let Some(instruction) = self.parse_store()? { + return Ok(Some(instruction)); + } + + if let Some(target) = self.eat_identifier()? { + return Ok(Some(self.parse_assignment(target)?)); + } + + Ok(None) + } + + fn eat_binary_op(&mut self) -> ParseResult> { + let op = match self.token.token() { + Token::Keyword(Keyword::Add) => BinaryOp::Add, + Token::Keyword(Keyword::Sub) => BinaryOp::Sub, + Token::Keyword(Keyword::Mul) => BinaryOp::Mul, + Token::Keyword(Keyword::Div) => BinaryOp::Div, + Token::Keyword(Keyword::Eq) => BinaryOp::Eq, + Token::Keyword(Keyword::Mod) => BinaryOp::Mod, + Token::Keyword(Keyword::Lt) => BinaryOp::Lt, + Token::Keyword(Keyword::And) => BinaryOp::And, + Token::Keyword(Keyword::Or) => BinaryOp::Or, + Token::Keyword(Keyword::Xor) => BinaryOp::Xor, + Token::Keyword(Keyword::Shl) => BinaryOp::Shl, + Token::Keyword(Keyword::Shr) => BinaryOp::Shr, + _ => return Ok(None), + }; + + self.bump()?; + + Ok(Some(op)) + } + + fn parse_call(&mut self) -> ParseResult> { + if !self.eat_keyword(Keyword::Call)? { + return Ok(None); + } + + let function = self.eat_identifier_or_error()?; + let arguments = self.parse_arguments()?; + Ok(Some(ParsedInstruction::Call { targets: vec![], function, arguments, types: vec![] })) + } + + fn parse_constrain(&mut self) -> ParseResult> { + if !self.eat_keyword(Keyword::Constrain)? { + return Ok(None); + } + + let lhs = self.parse_value_or_error()?; + self.eat_or_error(Token::Equal)?; + let rhs = self.parse_value_or_error()?; + Ok(Some(ParsedInstruction::Constrain { lhs, rhs })) + } + + fn parse_decrement_rc(&mut self) -> ParseResult> { + if !self.eat_keyword(Keyword::DecRc)? { + return Ok(None); + } + + let value = self.parse_value_or_error()?; + Ok(Some(ParsedInstruction::DecrementRc { value })) + } + + fn parse_enable_side_effects(&mut self) -> ParseResult> { + if !self.eat_keyword(Keyword::EnableSideEffects)? { + return Ok(None); + } + + let condition = self.parse_value_or_error()?; + Ok(Some(ParsedInstruction::EnableSideEffectsIf { condition })) + } + + fn parse_increment_rc(&mut self) -> ParseResult> { + if !self.eat_keyword(Keyword::IncRc)? { + return Ok(None); + } + + let value = self.parse_value_or_error()?; + Ok(Some(ParsedInstruction::IncrementRc { value })) + } + + fn parse_range_check(&mut self) -> ParseResult> { + if !self.eat_keyword(Keyword::RangeCheck)? { + return Ok(None); + } + + let value = self.parse_value_or_error()?; + self.eat_or_error(Token::Keyword(Keyword::To))?; + let max_bit_size = self.eat_int_or_error()?.to_u128() as u32; + self.eat_or_error(Token::Keyword(Keyword::Bits))?; + Ok(Some(ParsedInstruction::RangeCheck { value, max_bit_size })) + } + + fn parse_store(&mut self) -> ParseResult> { + if !self.eat_keyword(Keyword::Store)? { + return Ok(None); + } + + let value = self.parse_value_or_error()?; + self.eat_or_error(Token::Keyword(Keyword::At))?; + let address = self.parse_value_or_error()?; + Ok(Some(ParsedInstruction::Store { address, value })) + } + + fn parse_assignment(&mut self, target: Identifier) -> ParseResult { + let mut targets = vec![target]; + + while self.eat(Token::Comma)? { + let target = self.eat_identifier_or_error()?; + targets.push(target); + } + + self.eat_or_error(Token::Assign)?; + + if self.eat_keyword(Keyword::Call)? { + let function = self.eat_identifier_or_error()?; + let arguments = self.parse_arguments()?; + self.eat_or_error(Token::Arrow)?; + let types = self.parse_types()?; + return Ok(ParsedInstruction::Call { targets, function, arguments, types }); + } + + if targets.len() > 1 { + return Err(ParserError::MultipleReturnValuesOnlyAllowedForCall { + second_target: targets[1].clone(), + }); + } + + let target = targets.remove(0); + + if self.eat_keyword(Keyword::Allocate)? { + self.eat_or_error(Token::Arrow)?; + let typ = self.parse_mutable_reference_type_or_error()?; + return Ok(ParsedInstruction::Allocate { target, typ }); + } + + if self.eat_keyword(Keyword::ArrayGet)? { + let array = self.parse_value_or_error()?; + self.eat_or_error(Token::Comma)?; + self.eat_or_error(Token::Keyword(Keyword::Index))?; + let index = self.parse_value_or_error()?; + self.eat_or_error(Token::Arrow)?; + let element_type = self.parse_type()?; + return Ok(ParsedInstruction::ArrayGet { target, element_type, array, index }); + } + + if self.eat_keyword(Keyword::ArraySet)? { + let mutable = self.eat_keyword(Keyword::Mut)?; + let array = self.parse_value_or_error()?; + self.eat_or_error(Token::Comma)?; + self.eat_or_error(Token::Keyword(Keyword::Index))?; + let index = self.parse_value_or_error()?; + self.eat_or_error(Token::Comma)?; + self.eat_or_error(Token::Keyword(Keyword::Value))?; + let value = self.parse_value_or_error()?; + return Ok(ParsedInstruction::ArraySet { target, array, index, value, mutable }); + } + + if self.eat_keyword(Keyword::Cast)? { + let lhs = self.parse_value_or_error()?; + self.eat_or_error(Token::Keyword(Keyword::As))?; + let typ = self.parse_type()?; + return Ok(ParsedInstruction::Cast { target, lhs, typ }); + } + + if self.eat_keyword(Keyword::Load)? { + let value = self.parse_value_or_error()?; + self.eat_or_error(Token::Arrow)?; + let typ = self.parse_type()?; + return Ok(ParsedInstruction::Load { target, value, typ }); + } + + if self.eat_keyword(Keyword::Not)? { + let value = self.parse_value_or_error()?; + return Ok(ParsedInstruction::Not { target, value }); + } + + if self.eat_keyword(Keyword::Truncate)? { + let value = self.parse_value_or_error()?; + self.eat_or_error(Token::Keyword(Keyword::To))?; + let bit_size = self.eat_int_or_error()?.to_u128() as u32; + self.eat_or_error(Token::Keyword(Keyword::Bits))?; + self.eat_or_error(Token::Comma)?; + self.eat_or_error(Token::Keyword(Keyword::MaxBitSize))?; + self.eat_or_error(Token::Colon)?; + let max_bit_size = self.eat_int_or_error()?.to_u128() as u32; + return Ok(ParsedInstruction::Truncate { target, value, bit_size, max_bit_size }); + } + + if let Some(op) = self.eat_binary_op()? { + let lhs = self.parse_value_or_error()?; + self.eat_or_error(Token::Comma)?; + let rhs = self.parse_value_or_error()?; + return Ok(ParsedInstruction::BinaryOp { target, lhs, op, rhs }); + } + + self.expected_instruction_or_terminator() + } + + fn parse_terminator(&mut self) -> ParseResult { + if let Some(terminator) = self.parse_return()? { + return Ok(terminator); + } + + if let Some(terminator) = self.parse_jmp()? { + return Ok(terminator); + } + + if let Some(terminator) = self.parse_jmpif()? { + return Ok(terminator); + } + + self.expected_instruction_or_terminator() + } + + fn parse_return(&mut self) -> ParseResult> { + // Before advancing to the next token (after a potential return keyword), + // we check if a newline follows. This is because if we have this: + // + // return + // b1(): + // ... + // + // then unless we check for a newline we can't know if the return + // returns `b1` or not (we could check if a parentheses comes next, but + // that would require a look-ahead and, for the purpose of the SSA parser, + // it's just simpler to check if a newline follows) + let newline_follows = self.newline_follows(); + + if !self.eat_keyword(Keyword::Return)? { + return Ok(None); + } + + let values = + if newline_follows { Vec::new() } else { self.parse_comma_separated_values()? }; + Ok(Some(ParsedTerminator::Return(values))) + } + + fn parse_jmp(&mut self) -> ParseResult> { + if !self.eat_keyword(Keyword::Jmp)? { + return Ok(None); + } + + let destination = self.eat_identifier_or_error()?; + let arguments = self.parse_arguments()?; + Ok(Some(ParsedTerminator::Jmp { destination, arguments })) + } + + fn parse_jmpif(&mut self) -> ParseResult> { + if !self.eat_keyword(Keyword::Jmpif)? { + return Ok(None); + } + + let condition = self.parse_value_or_error()?; + self.eat_or_error(Token::Keyword(Keyword::Then))?; + self.eat_or_error(Token::Colon)?; + let then_block = self.eat_identifier_or_error()?; + self.eat_or_error(Token::Comma)?; + self.eat_or_error(Token::Keyword(Keyword::Else))?; + self.eat_or_error(Token::Colon)?; + let else_block = self.eat_identifier_or_error()?; + + Ok(Some(ParsedTerminator::Jmpif { condition, then_block, else_block })) + } + + fn parse_arguments(&mut self) -> ParseResult> { + self.eat_or_error(Token::LeftParen)?; + let arguments = self.parse_comma_separated_values()?; + self.eat_or_error(Token::RightParen)?; + Ok(arguments) + } + + fn parse_comma_separated_values(&mut self) -> ParseResult> { + let mut values = Vec::new(); + while let Some(value) = self.parse_value()? { + values.push(value); + if !self.eat(Token::Comma)? { + break; + } + } + Ok(values) + } + + fn parse_value_or_error(&mut self) -> ParseResult { + if let Some(value) = self.parse_value()? { + Ok(value) + } else { + self.expected_value() + } + } + + fn parse_value(&mut self) -> ParseResult> { + if let Some(value) = self.parse_field_value()? { + return Ok(Some(value)); + } + + if let Some(value) = self.parse_int_value()? { + return Ok(Some(value)); + } + + if let Some(value) = self.parse_array_value()? { + return Ok(Some(value)); + } + + if let Some(identifier) = self.eat_identifier()? { + return Ok(Some(ParsedValue::Variable(identifier))); + } + + Ok(None) + } + + fn parse_field_value(&mut self) -> ParseResult> { + if self.eat_keyword(Keyword::Field)? { + let constant = self.eat_int_or_error()?; + Ok(Some(ParsedValue::NumericConstant { constant, typ: Type::field() })) + } else { + Ok(None) + } + } + + fn parse_int_value(&mut self) -> ParseResult> { + if let Some(int_type) = self.eat_int_type()? { + let constant = self.eat_int_or_error()?; + let typ = match int_type { + IntType::Unsigned(bit_size) => Type::unsigned(bit_size), + IntType::Signed(bit_size) => Type::signed(bit_size), + }; + Ok(Some(ParsedValue::NumericConstant { constant, typ })) + } else { + Ok(None) + } + } + + fn parse_array_value(&mut self) -> ParseResult> { + if self.eat(Token::LeftBracket)? { + let values = self.parse_comma_separated_values()?; + self.eat_or_error(Token::RightBracket)?; + self.eat_or_error(Token::Keyword(Keyword::Of))?; + let types = self.parse_types()?; + let types_len = types.len(); + let values_len = values.len(); + Ok(Some(ParsedValue::Array { + typ: Type::Array(Arc::new(types), values_len / types_len), + values, + })) + } else { + Ok(None) + } + } + + fn parse_types(&mut self) -> ParseResult> { + if self.eat(Token::LeftParen)? { + let types = self.parse_comma_separated_types()?; + self.eat_or_error(Token::RightParen)?; + Ok(types) + } else { + Ok(vec![self.parse_type()?]) + } + } + + fn parse_comma_separated_types(&mut self) -> ParseResult> { + let mut types = Vec::new(); + loop { + let typ = self.parse_type()?; + types.push(typ); + if !self.eat(Token::Comma)? { + break; + } + } + Ok(types) + } + + fn parse_type(&mut self) -> ParseResult { + if self.eat_keyword(Keyword::Bool)? { + return Ok(Type::bool()); + } + + if self.eat_keyword(Keyword::Field)? { + return Ok(Type::field()); + } + + if let Some(int_type) = self.eat_int_type()? { + return Ok(match int_type { + IntType::Unsigned(bit_size) => Type::unsigned(bit_size), + IntType::Signed(bit_size) => Type::signed(bit_size), + }); + } + + if self.eat(Token::LeftBracket)? { + let element_types = self.parse_types()?; + if self.eat(Token::Semicolon)? { + let length = self.eat_int_or_error()?; + self.eat_or_error(Token::RightBracket)?; + return Ok(Type::Array(Arc::new(element_types), length.to_u128() as usize)); + } else { + self.eat_or_error(Token::RightBracket)?; + return Ok(Type::Slice(Arc::new(element_types))); + } + } + + if let Some(typ) = self.parse_mutable_reference_type()? { + return Ok(Type::Reference(Arc::new(typ))); + } + + self.expected_type() + } + + /// Parses `&mut Type`, returns `Type` if `&mut` was found, errors otherwise. + fn parse_mutable_reference_type_or_error(&mut self) -> ParseResult { + if let Some(typ) = self.parse_mutable_reference_type()? { + Ok(typ) + } else { + self.expected_token(Token::Ampersand) + } + } + + /// Parses `&mut Type`, returns `Some(Type)` if `&mut` was found, `None` otherwise. + fn parse_mutable_reference_type(&mut self) -> ParseResult> { + if !self.eat(Token::Ampersand)? { + return Ok(None); + } + + self.eat_or_error(Token::Keyword(Keyword::Mut))?; + let typ = self.parse_type()?; + Ok(Some(typ)) + } + + fn eat_identifier_or_error(&mut self) -> ParseResult { + if let Some(identifier) = self.eat_identifier()? { + Ok(identifier) + } else { + self.expected_identifier() + } + } + + fn eat_identifier(&mut self) -> ParseResult> { + let span = self.token.to_span(); + if let Some(name) = self.eat_ident()? { + Ok(Some(Identifier::new(name, span))) + } else { + Ok(None) + } + } + + fn eat_keyword(&mut self, keyword: Keyword) -> ParseResult { + if let Token::Keyword(kw) = self.token.token() { + if *kw == keyword { + self.bump()?; + Ok(true) + } else { + Ok(false) + } + } else { + Ok(false) + } + } + + fn eat_ident(&mut self) -> ParseResult> { + if !matches!(self.token.token(), Token::Ident(..)) { + return Ok(None); + } + + let token = self.bump()?; + match token.into_token() { + Token::Ident(ident) => Ok(Some(ident)), + _ => unreachable!(), + } + } + + fn eat_ident_or_error(&mut self) -> ParseResult { + if let Some(ident) = self.eat_ident()? { + Ok(ident) + } else { + self.expected_identifier() + } + } + + fn eat_int(&mut self) -> ParseResult> { + if matches!(self.token.token(), Token::Int(..)) { + let token = self.bump()?; + match token.into_token() { + Token::Int(int) => Ok(Some(int)), + _ => unreachable!(), + } + } else { + Ok(None) + } + } + + fn eat_int_or_error(&mut self) -> ParseResult { + if let Some(int) = self.eat_int()? { + Ok(int) + } else { + self.expected_int() + } + } + + fn eat_int_type(&mut self) -> ParseResult> { + let is_int_type = matches!(self.token.token(), Token::IntType(..)); + if is_int_type { + let token = self.bump()?; + match token.into_token() { + Token::IntType(int_type) => Ok(Some(int_type)), + _ => unreachable!(), + } + } else { + Ok(None) + } + } + + fn eat(&mut self, token: Token) -> ParseResult { + if self.token.token() == &token { + self.bump()?; + Ok(true) + } else { + Ok(false) + } + } + + fn eat_or_error(&mut self, token: Token) -> ParseResult<()> { + if self.eat(token.clone())? { + Ok(()) + } else { + self.expected_token(token) + } + } + + fn at(&self, token: Token) -> bool { + self.token.token() == &token + } + + fn at_keyword(&self, keyword: Keyword) -> bool { + self.at(Token::Keyword(keyword)) + } + + fn newline_follows(&self) -> bool { + self.lexer.newline_follows() + } + + fn bump(&mut self) -> ParseResult { + let token = self.read_token_internal()?; + Ok(std::mem::replace(&mut self.token, token)) + } + + fn read_token_internal(&mut self) -> ParseResult { + self.lexer.next_token().map_err(ParserError::LexerError) + } + + fn expected_instruction_or_terminator(&mut self) -> ParseResult { + Err(ParserError::ExpectedInstructionOrTerminator { + found: self.token.token().clone(), + span: self.token.to_span(), + }) + } + + fn expected_identifier(&mut self) -> ParseResult { + Err(ParserError::ExpectedIdentifier { + found: self.token.token().clone(), + span: self.token.to_span(), + }) + } + + fn expected_int(&mut self) -> ParseResult { + Err(ParserError::ExpectedInt { + found: self.token.token().clone(), + span: self.token.to_span(), + }) + } + + fn expected_type(&mut self) -> ParseResult { + Err(ParserError::ExpectedType { + found: self.token.token().clone(), + span: self.token.to_span(), + }) + } + + fn expected_value(&mut self) -> ParseResult { + Err(ParserError::ExpectedValue { + found: self.token.token().clone(), + span: self.token.to_span(), + }) + } + + fn expected_token(&mut self, token: Token) -> ParseResult { + Err(ParserError::ExpectedToken { + token, + found: self.token.token().clone(), + span: self.token.to_span(), + }) + } + + fn expected_one_of_tokens(&mut self, tokens: &[Token]) -> ParseResult { + Err(ParserError::ExpectedOneOfTokens { + tokens: tokens.to_vec(), + found: self.token.token().clone(), + span: self.token.to_span(), + }) + } +} + +#[derive(Debug, Error)] +pub(crate) enum ParserError { + #[error("{0}")] + LexerError(LexerError), + #[error("Expected '{token}', found '{found}'")] + ExpectedToken { token: Token, found: Token, span: Span }, + #[error("Expected one of {tokens:?}, found {found}")] + ExpectedOneOfTokens { tokens: Vec, found: Token, span: Span }, + #[error("Expected an identifier, found '{found}'")] + ExpectedIdentifier { found: Token, span: Span }, + #[error("Expected an int, found '{found}'")] + ExpectedInt { found: Token, span: Span }, + #[error("Expected a type, found '{found}'")] + ExpectedType { found: Token, span: Span }, + #[error("Expected an instruction or terminator, found '{found}'")] + ExpectedInstructionOrTerminator { found: Token, span: Span }, + #[error("Expected a value, found '{found}'")] + ExpectedValue { found: Token, span: Span }, + #[error("Multiple return values only allowed for call")] + MultipleReturnValuesOnlyAllowedForCall { second_target: Identifier }, +} + +impl ParserError { + fn span(&self) -> Span { + match self { + ParserError::LexerError(err) => err.span(), + ParserError::ExpectedToken { span, .. } + | ParserError::ExpectedOneOfTokens { span, .. } + | ParserError::ExpectedIdentifier { span, .. } + | ParserError::ExpectedInt { span, .. } + | ParserError::ExpectedType { span, .. } + | ParserError::ExpectedInstructionOrTerminator { span, .. } + | ParserError::ExpectedValue { span, .. } => *span, + ParserError::MultipleReturnValuesOnlyAllowedForCall { second_target, .. } => { + second_target.span + } + } + } +} + +fn eof_spanned_token() -> SpannedToken { + SpannedToken::new(Token::Eof, Default::default()) +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/ast.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/ast.rs new file mode 100644 index 00000000000..f8fe8c68a98 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/ast.rs @@ -0,0 +1,139 @@ +use std::fmt::{self, Display, Formatter}; + +use acvm::FieldElement; +use noirc_errors::Span; + +use crate::ssa::ir::{function::RuntimeType, instruction::BinaryOp, types::Type}; + +#[derive(Debug)] +pub(crate) struct ParsedSsa { + pub(crate) functions: Vec, +} + +#[derive(Debug)] +pub(crate) struct ParsedFunction { + pub(crate) runtime_type: RuntimeType, + pub(crate) external_name: String, + pub(crate) internal_name: String, + pub(crate) blocks: Vec, +} + +#[derive(Debug)] +pub(crate) struct ParsedBlock { + pub(crate) name: String, + pub(crate) parameters: Vec, + pub(crate) instructions: Vec, + pub(crate) terminator: ParsedTerminator, +} + +#[derive(Debug)] +pub(crate) struct ParsedParameter { + pub(crate) identifier: Identifier, + pub(crate) typ: Type, +} + +#[derive(Debug, Clone)] +pub(crate) struct Identifier { + pub(crate) name: String, + pub(crate) span: Span, +} + +impl Identifier { + pub(crate) fn new(name: String, span: Span) -> Self { + Self { name, span } + } +} + +impl Display for Identifier { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +#[derive(Debug)] +pub(crate) enum ParsedInstruction { + Allocate { + target: Identifier, + typ: Type, + }, + ArrayGet { + target: Identifier, + element_type: Type, + array: ParsedValue, + index: ParsedValue, + }, + ArraySet { + target: Identifier, + array: ParsedValue, + index: ParsedValue, + value: ParsedValue, + mutable: bool, + }, + BinaryOp { + target: Identifier, + lhs: ParsedValue, + op: BinaryOp, + rhs: ParsedValue, + }, + Call { + targets: Vec, + function: Identifier, + arguments: Vec, + types: Vec, + }, + Cast { + target: Identifier, + lhs: ParsedValue, + typ: Type, + }, + Constrain { + lhs: ParsedValue, + rhs: ParsedValue, + }, + DecrementRc { + value: ParsedValue, + }, + EnableSideEffectsIf { + condition: ParsedValue, + }, + IncrementRc { + value: ParsedValue, + }, + Load { + target: Identifier, + value: ParsedValue, + typ: Type, + }, + Not { + target: Identifier, + value: ParsedValue, + }, + RangeCheck { + value: ParsedValue, + max_bit_size: u32, + }, + Store { + value: ParsedValue, + address: ParsedValue, + }, + Truncate { + target: Identifier, + value: ParsedValue, + bit_size: u32, + max_bit_size: u32, + }, +} + +#[derive(Debug)] +pub(crate) enum ParsedTerminator { + Jmp { destination: Identifier, arguments: Vec }, + Jmpif { condition: ParsedValue, then_block: Identifier, else_block: Identifier }, + Return(Vec), +} + +#[derive(Debug)] +pub(crate) enum ParsedValue { + NumericConstant { constant: FieldElement, typ: Type }, + Array { values: Vec, typ: Type }, + Variable(Identifier), +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs new file mode 100644 index 00000000000..2a94a4fd1eb --- /dev/null +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/into_ssa.rs @@ -0,0 +1,317 @@ +use std::collections::HashMap; + +use im::Vector; + +use crate::ssa::{ + function_builder::FunctionBuilder, + ir::{basic_block::BasicBlockId, function::FunctionId, value::ValueId}, +}; + +use super::{ + Identifier, ParsedBlock, ParsedFunction, ParsedInstruction, ParsedSsa, ParsedTerminator, + ParsedValue, RuntimeType, Ssa, SsaError, +}; + +impl ParsedSsa { + pub(crate) fn into_ssa(self) -> Result { + Translator::translate(self) + } +} + +struct Translator { + builder: FunctionBuilder, + + /// Maps function names to their IDs + functions: HashMap, + + /// Maps block names to their IDs + blocks: HashMap>, + + /// Maps variable names to their IDs + variables: HashMap>, +} + +impl Translator { + fn translate(mut parsed_ssa: ParsedSsa) -> Result { + let mut translator = Self::new(&mut parsed_ssa)?; + + // Note that the `new` call above removed the main function, + // so all we are left with are non-main functions. + for function in parsed_ssa.functions { + translator.translate_non_main_function(function)?; + } + + Ok(translator.finish()) + } + + fn new(parsed_ssa: &mut ParsedSsa) -> Result { + // A FunctionBuilder must be created with a main Function, so here wer remove it + // from the parsed SSA to avoid adding it twice later on. + let main_function = parsed_ssa.functions.remove(0); + let main_id = FunctionId::new(0); + let mut builder = FunctionBuilder::new(main_function.external_name.clone(), main_id); + builder.set_runtime(main_function.runtime_type); + + // Map function names to their IDs so calls can be resolved + let mut function_id_counter = 1; + let mut functions = HashMap::new(); + for function in &parsed_ssa.functions { + let function_id = FunctionId::new(function_id_counter); + function_id_counter += 1; + + functions.insert(function.internal_name.clone(), function_id); + } + + let mut translator = + Self { builder, functions, variables: HashMap::new(), blocks: HashMap::new() }; + translator.translate_function_body(main_function)?; + + Ok(translator) + } + + fn translate_non_main_function(&mut self, function: ParsedFunction) -> Result<(), SsaError> { + let function_id = self.functions[&function.internal_name]; + let external_name = function.external_name.clone(); + + match function.runtime_type { + RuntimeType::Acir(inline_type) => { + self.builder.new_function(external_name, function_id, inline_type); + } + RuntimeType::Brillig(inline_type) => { + self.builder.new_brillig_function(external_name, function_id, inline_type); + } + } + + self.translate_function_body(function) + } + + fn translate_function_body(&mut self, function: ParsedFunction) -> Result<(), SsaError> { + // First define all blocks so that they are known (a block might jump to a block that comes next) + for (index, block) in function.blocks.iter().enumerate() { + // The first block is the entry block and it was automatically created by the builder + let block_id = if index == 0 { + self.builder.current_function.entry_block() + } else { + self.builder.insert_block() + }; + let entry = self.blocks.entry(self.current_function_id()).or_default(); + entry.insert(block.name.clone(), block_id); + } + + for block in function.blocks { + self.translate_block(block)?; + } + + Ok(()) + } + + fn translate_block(&mut self, block: ParsedBlock) -> Result<(), SsaError> { + let block_id = self.blocks[&self.current_function_id()][&block.name]; + self.builder.switch_to_block(block_id); + + for parameter in block.parameters { + let parameter_value_id = self.builder.add_block_parameter(block_id, parameter.typ); + self.define_variable(parameter.identifier, parameter_value_id)?; + } + + for instruction in block.instructions { + self.translate_instruction(instruction)?; + } + + match block.terminator { + ParsedTerminator::Jmp { destination, arguments } => { + let block_id = self.lookup_block(destination)?; + let arguments = self.translate_values(arguments)?; + self.builder.terminate_with_jmp(block_id, arguments); + } + ParsedTerminator::Jmpif { condition, then_block, else_block } => { + let condition = self.translate_value(condition)?; + let then_destination = self.lookup_block(then_block)?; + let else_destination = self.lookup_block(else_block)?; + self.builder.terminate_with_jmpif(condition, then_destination, else_destination); + } + ParsedTerminator::Return(values) => { + let return_values = self.translate_values(values)?; + self.builder.terminate_with_return(return_values); + } + } + + Ok(()) + } + + fn translate_instruction(&mut self, instruction: ParsedInstruction) -> Result<(), SsaError> { + match instruction { + ParsedInstruction::Allocate { target, typ } => { + let value_id = self.builder.insert_allocate(typ); + self.define_variable(target, value_id)?; + } + ParsedInstruction::ArrayGet { target, element_type, array, index } => { + let array = self.translate_value(array)?; + let index = self.translate_value(index)?; + let value_id = self.builder.insert_array_get(array, index, element_type); + self.define_variable(target, value_id)?; + } + ParsedInstruction::ArraySet { target, array, index, value, mutable } => { + let array = self.translate_value(array)?; + let index = self.translate_value(index)?; + let value = self.translate_value(value)?; + let value_id = if mutable { + self.builder.insert_mutable_array_set(array, index, value) + } else { + self.builder.insert_array_set(array, index, value) + }; + self.define_variable(target, value_id)?; + } + ParsedInstruction::BinaryOp { target, lhs, op, rhs } => { + let lhs = self.translate_value(lhs)?; + let rhs = self.translate_value(rhs)?; + let value_id = self.builder.insert_binary(lhs, op, rhs); + self.define_variable(target, value_id)?; + } + ParsedInstruction::Call { targets, function, arguments, types } => { + let function_id = if let Some(id) = self.builder.import_intrinsic(&function.name) { + id + } else { + let function_id = self.lookup_function(function)?; + self.builder.import_function(function_id) + }; + + let arguments = self.translate_values(arguments)?; + + let value_ids = self.builder.insert_call(function_id, arguments, types).to_vec(); + + if value_ids.len() != targets.len() { + return Err(SsaError::MismatchedReturnValues { + returns: targets, + expected: value_ids.len(), + }); + } + + for (target, value_id) in targets.into_iter().zip(value_ids.into_iter()) { + self.define_variable(target, value_id)?; + } + } + ParsedInstruction::Cast { target, lhs, typ } => { + let lhs = self.translate_value(lhs)?; + let value_id = self.builder.insert_cast(lhs, typ); + self.define_variable(target, value_id)?; + } + ParsedInstruction::Constrain { lhs, rhs } => { + let lhs = self.translate_value(lhs)?; + let rhs = self.translate_value(rhs)?; + self.builder.insert_constrain(lhs, rhs, None); + } + ParsedInstruction::DecrementRc { value } => { + let value = self.translate_value(value)?; + self.builder.decrement_array_reference_count(value); + } + ParsedInstruction::EnableSideEffectsIf { condition } => { + let condition = self.translate_value(condition)?; + self.builder.insert_enable_side_effects_if(condition); + } + ParsedInstruction::IncrementRc { value } => { + let value = self.translate_value(value)?; + self.builder.increment_array_reference_count(value); + } + ParsedInstruction::Load { target, value, typ } => { + let value = self.translate_value(value)?; + let value_id = self.builder.insert_load(value, typ); + self.define_variable(target, value_id)?; + } + ParsedInstruction::Not { target, value } => { + let value = self.translate_value(value)?; + let value_id = self.builder.insert_not(value); + self.define_variable(target, value_id)?; + } + ParsedInstruction::RangeCheck { value, max_bit_size } => { + let value = self.translate_value(value)?; + self.builder.insert_range_check(value, max_bit_size, None); + } + ParsedInstruction::Store { value, address } => { + let value = self.translate_value(value)?; + let address = self.translate_value(address)?; + self.builder.insert_store(address, value); + } + ParsedInstruction::Truncate { target, value, bit_size, max_bit_size } => { + let value = self.translate_value(value)?; + let value_id = self.builder.insert_truncate(value, bit_size, max_bit_size); + self.define_variable(target, value_id)?; + } + } + + Ok(()) + } + + fn translate_values(&mut self, values: Vec) -> Result, SsaError> { + let mut translated_values = Vec::with_capacity(values.len()); + for value in values { + translated_values.push(self.translate_value(value)?); + } + Ok(translated_values) + } + + fn translate_value(&mut self, value: ParsedValue) -> Result { + match value { + ParsedValue::NumericConstant { constant, typ } => { + Ok(self.builder.numeric_constant(constant, typ)) + } + ParsedValue::Array { values, typ } => { + let mut translated_values = Vector::new(); + for value in values { + translated_values.push_back(self.translate_value(value)?); + } + Ok(self.builder.array_constant(translated_values, typ)) + } + ParsedValue::Variable(identifier) => self.lookup_variable(identifier), + } + } + + fn define_variable( + &mut self, + identifier: Identifier, + value_id: ValueId, + ) -> Result<(), SsaError> { + if let Some(vars) = self.variables.get(&self.current_function_id()) { + if vars.contains_key(&identifier.name) { + return Err(SsaError::VariableAlreadyDefined(identifier)); + } + } + + let entry = self.variables.entry(self.current_function_id()).or_default(); + entry.insert(identifier.name, value_id); + + Ok(()) + } + + fn lookup_variable(&mut self, identifier: Identifier) -> Result { + if let Some(value_id) = self.variables[&self.current_function_id()].get(&identifier.name) { + Ok(*value_id) + } else { + Err(SsaError::UnknownVariable(identifier)) + } + } + + fn lookup_block(&mut self, identifier: Identifier) -> Result { + if let Some(block_id) = self.blocks[&self.current_function_id()].get(&identifier.name) { + Ok(*block_id) + } else { + Err(SsaError::UnknownBlock(identifier)) + } + } + + fn lookup_function(&mut self, identifier: Identifier) -> Result { + if let Some(function_id) = self.functions.get(&identifier.name) { + Ok(*function_id) + } else { + Err(SsaError::UnknownFunction(identifier)) + } + } + + fn finish(self) -> Ssa { + self.builder.finish() + } + + fn current_function_id(&self) -> FunctionId { + self.builder.current_function.id() + } +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/lexer.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/lexer.rs new file mode 100644 index 00000000000..ac4c3b77205 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/lexer.rs @@ -0,0 +1,259 @@ +use std::str::{CharIndices, FromStr}; + +use acvm::{AcirField, FieldElement}; +use noirc_errors::{Position, Span}; +use noirc_frontend::token::IntType; +use num_bigint::BigInt; +use num_traits::{Num, One}; +use thiserror::Error; + +use super::token::{Keyword, SpannedToken, Token}; + +pub(crate) struct Lexer<'a> { + chars: CharIndices<'a>, + position: Position, + done: bool, + max_integer: BigInt, +} + +impl<'a> Lexer<'a> { + pub(crate) fn new(source: &'a str) -> Self { + Lexer { + chars: source.char_indices(), + position: 0, + done: false, + max_integer: BigInt::from_biguint(num_bigint::Sign::Plus, FieldElement::modulus()) + - BigInt::one(), + } + } + + pub(crate) fn next_token(&mut self) -> SpannedTokenResult { + match self.next_char() { + Some(char) if char.is_ascii_whitespace() => { + while let Some(char) = self.peek_char() { + if char.is_ascii_whitespace() { + self.next_char(); + } else { + break; + } + } + self.next_token() + } + Some('/') if self.peek_char() == Some('/') => { + while let Some(char) = self.next_char() { + if char == '\n' { + break; + } + } + self.next_token() + } + Some('=') if self.peek_char() == Some('=') => self.double_char_token(Token::Equal), + Some('=') => self.single_char_token(Token::Assign), + Some(',') => self.single_char_token(Token::Comma), + Some(':') => self.single_char_token(Token::Colon), + Some(';') => self.single_char_token(Token::Semicolon), + Some('(') => self.single_char_token(Token::LeftParen), + Some(')') => self.single_char_token(Token::RightParen), + Some('{') => self.single_char_token(Token::LeftBrace), + Some('}') => self.single_char_token(Token::RightBrace), + Some('[') => self.single_char_token(Token::LeftBracket), + Some(']') => self.single_char_token(Token::RightBracket), + Some('&') => self.single_char_token(Token::Ampersand), + Some('-') if self.peek_char() == Some('>') => self.double_char_token(Token::Arrow), + Some(ch) if ch.is_ascii_alphanumeric() || ch == '_' => self.eat_alpha_numeric(ch), + Some(char) => Err(LexerError::UnexpectedCharacter { + char, + span: Span::single_char(self.position), + }), + None => { + self.done = true; + Ok(Token::Eof.into_single_span(self.position)) + } + } + } + + fn eat_alpha_numeric(&mut self, initial_char: char) -> SpannedTokenResult { + match initial_char { + 'A'..='Z' | 'a'..='z' | '_' => Ok(self.eat_word(initial_char)?), + '0'..='9' => self.eat_digit(initial_char), + _ => Err(LexerError::UnexpectedCharacter { + char: initial_char, + span: Span::single_char(self.position), + }), + } + } + + fn eat_word(&mut self, initial_char: char) -> SpannedTokenResult { + let (start, word, end) = self.lex_word(initial_char); + self.lookup_word_token(word, start, end) + } + + fn lex_word(&mut self, initial_char: char) -> (Position, String, Position) { + let start = self.position; + let word = self.eat_while(Some(initial_char), |ch| { + ch.is_ascii_alphabetic() || ch.is_numeric() || ch == '_' + }); + (start, word, self.position) + } + + fn lookup_word_token( + &self, + word: String, + start: Position, + end: Position, + ) -> SpannedTokenResult { + // Check if word either an identifier or a keyword + if let Some(keyword_token) = Keyword::lookup_keyword(&word) { + return Ok(keyword_token.into_span(start, end)); + } + + // Check if word an int type + // if no error occurred, then it is either a valid integer type or it is not an int type + let parsed_token = IntType::lookup_int_type(&word); + + // Check if it is an int type + if let Some(int_type) = parsed_token { + return Ok(Token::IntType(int_type).into_span(start, end)); + } + + // Else it is just an identifier + let ident_token = Token::Ident(word); + Ok(ident_token.into_span(start, end)) + } + + fn eat_digit(&mut self, initial_char: char) -> SpannedTokenResult { + let start = self.position; + + let integer_str = self.eat_while(Some(initial_char), |ch| { + ch.is_ascii_digit() | ch.is_ascii_hexdigit() | (ch == 'x') | (ch == '_') + }); + + let end = self.position; + + // We want to enforce some simple rules about usage of underscores: + // 1. Underscores cannot appear at the end of a integer literal. e.g. 0x123_. + // 2. There cannot be more than one underscore consecutively, e.g. 0x5__5, 5__5. + // + // We're not concerned with an underscore at the beginning of a decimal literal + // such as `_5` as this would be lexed into an ident rather than an integer literal. + let invalid_underscore_location = integer_str.ends_with('_'); + let consecutive_underscores = integer_str.contains("__"); + if invalid_underscore_location || consecutive_underscores { + return Err(LexerError::InvalidIntegerLiteral { + span: Span::inclusive(start, end), + found: integer_str, + }); + } + + // Underscores needs to be stripped out before the literal can be converted to a `FieldElement. + let integer_str = integer_str.replace('_', ""); + + let bigint_result = match integer_str.strip_prefix("0x") { + Some(integer_str) => BigInt::from_str_radix(integer_str, 16), + None => BigInt::from_str(&integer_str), + }; + + let integer = match bigint_result { + Ok(bigint) => { + if bigint > self.max_integer { + return Err(LexerError::IntegerLiteralTooLarge { + span: Span::inclusive(start, end), + limit: self.max_integer.to_string(), + }); + } + let big_uint = bigint.magnitude(); + FieldElement::from_be_bytes_reduce(&big_uint.to_bytes_be()) + } + Err(_) => { + return Err(LexerError::InvalidIntegerLiteral { + span: Span::inclusive(start, end), + found: integer_str, + }) + } + }; + + let integer_token = Token::Int(integer); + Ok(integer_token.into_span(start, end)) + } + + fn eat_while bool>( + &mut self, + initial_char: Option, + predicate: F, + ) -> String { + // This function is only called when we want to continue consuming a character of the same type. + // For example, we see a digit and we want to consume the whole integer + // Therefore, the current character which triggered this function will need to be appended + let mut word = String::new(); + if let Some(init_char) = initial_char { + word.push(init_char); + } + + // Keep checking that we are not at the EOF + while let Some(peek_char) = self.peek_char() { + // Then check for the predicate, if predicate matches append char and increment the cursor + // If not, return word. The next character will be analyzed on the next iteration of next_token, + // Which will increment the cursor + if !predicate(peek_char) { + return word; + } + word.push(peek_char); + + // If we arrive at this point, then the char has been added to the word and we should increment the cursor + self.next_char(); + } + + word + } + + fn single_char_token(&self, token: Token) -> SpannedTokenResult { + Ok(token.into_single_span(self.position)) + } + + fn double_char_token(&mut self, token: Token) -> SpannedTokenResult { + let start_position = self.position; + self.next_char(); + Ok(token.into_span(start_position, self.position)) + } + + fn next_char(&mut self) -> Option { + let (position, ch) = self.chars.next()?; + self.position = position as u32; + Some(ch) + } + + fn peek_char(&self) -> Option { + self.chars.clone().next().map(|(_, ch)| ch) + } + + fn is_code_whitespace(c: char) -> bool { + c.is_ascii_whitespace() + } + + pub(crate) fn newline_follows(&self) -> bool { + let chars = self.chars.clone(); + chars.take_while(|(_, char)| char.is_ascii_whitespace()).any(|(_, char)| char == '\n') + } +} + +type SpannedTokenResult = Result; + +#[derive(Debug, Error)] +pub(crate) enum LexerError { + #[error("Unexpected character: {char:?}")] + UnexpectedCharacter { char: char, span: Span }, + #[error("Invalid integer literal")] + InvalidIntegerLiteral { span: Span, found: String }, + #[error("Integer literal too large")] + IntegerLiteralTooLarge { span: Span, limit: String }, +} + +impl LexerError { + pub(crate) fn span(&self) -> Span { + match self { + LexerError::UnexpectedCharacter { span, .. } + | LexerError::InvalidIntegerLiteral { span, .. } + | LexerError::IntegerLiteralTooLarge { span, .. } => *span, + } + } +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/tests.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/tests.rs new file mode 100644 index 00000000000..3ed6be57b5e --- /dev/null +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/tests.rs @@ -0,0 +1,427 @@ +#![cfg(test)] + +use crate::{ + ssa::{opt::assert_normalized_ssa_equals, Ssa}, + trim_leading_whitespace_from_lines, +}; + +fn assert_ssa_roundtrip(src: &str) { + let ssa = Ssa::from_str(src).unwrap(); + let ssa = ssa.to_string(); + let ssa = trim_leading_whitespace_from_lines(&ssa); + let src = trim_leading_whitespace_from_lines(src); + if ssa != src { + println!("Expected:\n~~~\n{}\n~~~\nGot:\n~~~\n{}\n~~~", src, ssa); + similar_asserts::assert_eq!(ssa, src); + } +} + +#[test] +fn test_empty_acir_function() { + let src = " + acir(inline) fn main f0 { + b0(): + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_empty_brillig_function() { + let src = " + brillig(inline) fn main f0 { + b0(): + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_return_integer() { + for typ in ["u1", "u8", "u16", "u32", "u64", "i1", "i8", "i16", "i32", "i64", "Field"] { + let src = format!( + " + acir(inline) fn main f0 {{ + b0(): + return {typ} 1 + }} + " + ); + assert_ssa_roundtrip(&src); + } +} + +#[test] +fn test_return_array() { + let src = " + acir(inline) fn main f0 { + b0(): + return [Field 1] of Field + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_return_empty_array() { + let src = " + acir(inline) fn main f0 { + b0(): + return [] of Field + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_return_composite_array() { + let src = " + acir(inline) fn main f0 { + b0(): + return [Field 1, Field 2] of (Field, Field) + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_block_parameters() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field, v1: Field): + return v0, v1 + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_multiple_blocks_and_jmp() { + let src = " + acir(inline) fn main f0 { + b0(): + jmp b1(Field 1) + b1(v1: Field): + return v1 + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_jmpif() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + jmpif v0 then: b1, else: b2 + b1(): + return + b2(): + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_call() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + v2 = call f1(v0) -> Field + return v2 + } + acir(inline) fn foo f1 { + b0(v0: Field): + return v0 + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_call_multiple_return_values() { + let src = " + acir(inline) fn main f0 { + b0(): + v1, v2 = call f1() -> ([Field; 3], [Field; 1]) + return v1 + } + acir(inline) fn foo f1 { + b0(): + return [Field 1, Field 2, Field 3] of Field, [Field 4] of Field + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_call_no_return_value() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + call f1(v0) + return + } + acir(inline) fn foo f1 { + b0(v0: Field): + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_call_intrinsic() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + call assert_constant(v0) + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_cast() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + v1 = cast v0 as i32 + return v1 + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_constrain() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + constrain v0 == Field 1 + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_enable_side_effects() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + enable_side_effects v0 + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_array_get() { + let src = " + acir(inline) fn main f0 { + b0(v0: [Field; 3]): + v2 = array_get v0, index Field 0 -> Field + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_array_set() { + let src = " + acir(inline) fn main f0 { + b0(v0: [Field; 3]): + v3 = array_set v0, index Field 0, value Field 1 + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_mutable_array_set() { + let src = " + acir(inline) fn main f0 { + b0(v0: [Field; 3]): + v3 = array_set mut v0, index Field 0, value Field 1 + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_array_get_set_bug() { + let src = " + acir(inline) fn main f0 { + b0(v0: [u32; 3]): + v3 = array_set v0, index u32 1, value u32 2 + v5 = array_get v3, index u32 0 -> u32 + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_binary() { + for op in ["add", "sub", "mul", "div", "eq", "mod", "lt", "and", "or", "xor", "shl", "shr"] { + let src = format!( + " + acir(inline) fn main f0 {{ + b0(v0: Field, v1: Field): + v2 = {op} v0, v1 + return + }} + " + ); + assert_ssa_roundtrip(&src); + } +} + +#[test] +fn test_truncate() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + v1 = truncate v0 to 8 bits, max_bit_size: 16 + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_not() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + v1 = not v0 + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_range_check() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + range_check v0 to 8 bits + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_allocate() { + let src = " + acir(inline) fn main f0 { + b0(): + v0 = allocate -> &mut [Field; 3] + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_load() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + v1 = load v0 -> Field + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_store() { + let src = " + acir(inline) fn main f0 { + b0(v0: Field): + store Field 1 at v0 + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_inc_rc() { + let src = " + acir(inline) fn main f0 { + b0(v0: [Field; 3]): + inc_rc v0 + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_dec_rc() { + let src = " + acir(inline) fn main f0 { + b0(v0: [Field; 3]): + dec_rc v0 + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_mutable_reference_type() { + let src = " + acir(inline) fn main f0 { + b0(v0: &mut Field): + return + } + "; + assert_ssa_roundtrip(src); +} + +#[test] +fn test_parses_with_comments() { + let src = " + // This is a comment + acir(inline) fn main f0 { + b0(v0: &mut Field): // This is a block + return // Returns nothing + } + "; + + let expected = " + acir(inline) fn main f0 { + b0(v0: &mut Field): + return + } + "; + + let ssa = Ssa::from_str(src).unwrap(); + assert_normalized_ssa_equals(ssa, expected); +} + +#[test] +fn test_slice() { + let src = " + acir(inline) fn main f0 { + b0(v0: [Field; 3]): + v2, v3 = call as_slice(v0) -> (u32, [Field]) + return + } + "; + assert_ssa_roundtrip(src); +} diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/token.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/token.rs new file mode 100644 index 00000000000..41c4f9ca164 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/parser/token.rs @@ -0,0 +1,269 @@ +use std::fmt::Display; + +use acvm::FieldElement; +use noirc_errors::{Position, Span, Spanned}; +use noirc_frontend::token::IntType; + +#[derive(Debug)] +pub(crate) struct SpannedToken(Spanned); + +impl SpannedToken { + pub(crate) fn new(token: Token, span: Span) -> SpannedToken { + SpannedToken(Spanned::from(span, token)) + } + + pub(crate) fn to_span(&self) -> Span { + self.0.span() + } + + pub(crate) fn token(&self) -> &Token { + &self.0.contents + } + + pub(crate) fn into_token(self) -> Token { + self.0.contents + } +} + +#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub(crate) enum Token { + Ident(String), + Int(FieldElement), + Keyword(Keyword), + IntType(IntType), + /// = + Assign, + /// ( + LeftParen, + /// ) + RightParen, + /// { + LeftBrace, + /// } + RightBrace, + /// [ + LeftBracket, + /// ] + RightBracket, + /// , + Comma, + /// : + Colon, + /// ; + Semicolon, + /// -> + Arrow, + /// == + Equal, + /// & + Ampersand, + Eof, +} + +impl Token { + pub(super) fn into_single_span(self, position: Position) -> SpannedToken { + self.into_span(position, position) + } + + pub(super) fn into_span(self, start: Position, end: Position) -> SpannedToken { + SpannedToken(Spanned::from_position(start, end, self)) + } +} + +impl Display for Token { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Token::Ident(ident) => write!(f, "{}", ident), + Token::Int(int) => write!(f, "{}", int), + Token::Keyword(keyword) => write!(f, "{}", keyword), + Token::IntType(int_type) => write!(f, "{}", int_type), + Token::Assign => write!(f, "="), + Token::LeftParen => write!(f, "("), + Token::RightParen => write!(f, ")"), + Token::LeftBrace => write!(f, "{{"), + Token::RightBrace => write!(f, "}}"), + Token::LeftBracket => write!(f, "["), + Token::RightBracket => write!(f, "]"), + Token::Comma => write!(f, ","), + Token::Colon => write!(f, ":"), + Token::Semicolon => write!(f, ";"), + Token::Arrow => write!(f, "->"), + Token::Equal => write!(f, "=="), + Token::Ampersand => write!(f, "&"), + Token::Eof => write!(f, "(end of stream)"), + } + } +} + +impl std::fmt::Debug for Token { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self) + } +} + +#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub(crate) enum Keyword { + Acir, + Add, + Allocate, + And, + ArrayGet, + ArraySet, + As, + At, + Bits, + Bool, + Brillig, + Call, + Cast, + Constrain, + DecRc, + Div, + Inline, + InlineAlways, + Else, + EnableSideEffects, + Eq, + Field, + Fold, + Fn, + IncRc, + Index, + Jmp, + Jmpif, + Load, + Lt, + MaxBitSize, + Mod, + Mul, + Mut, + NoPredicates, + Not, + Of, + Or, + RangeCheck, + Return, + Shl, + Shr, + Store, + Sub, + Then, + To, + Truncate, + Value, + Xor, +} + +impl Keyword { + pub(crate) fn lookup_keyword(word: &str) -> Option { + let keyword = match word { + "acir" => Keyword::Acir, + "add" => Keyword::Add, + "allocate" => Keyword::Allocate, + "and" => Keyword::And, + "array_get" => Keyword::ArrayGet, + "array_set" => Keyword::ArraySet, + "as" => Keyword::As, + "at" => Keyword::At, + "bits" => Keyword::Bits, + "bool" => Keyword::Bool, + "brillig" => Keyword::Brillig, + "call" => Keyword::Call, + "cast" => Keyword::Cast, + "constrain" => Keyword::Constrain, + "dec_rc" => Keyword::DecRc, + "div" => Keyword::Div, + "else" => Keyword::Else, + "enable_side_effects" => Keyword::EnableSideEffects, + "eq" => Keyword::Eq, + "inline" => Keyword::Inline, + "inline_always" => Keyword::InlineAlways, + "Field" => Keyword::Field, + "fold" => Keyword::Fold, + "fn" => Keyword::Fn, + "inc_rc" => Keyword::IncRc, + "index" => Keyword::Index, + "jmp" => Keyword::Jmp, + "jmpif" => Keyword::Jmpif, + "load" => Keyword::Load, + "lt" => Keyword::Lt, + "max_bit_size" => Keyword::MaxBitSize, + "mod" => Keyword::Mod, + "mul" => Keyword::Mul, + "mut" => Keyword::Mut, + "no_predicates" => Keyword::NoPredicates, + "not" => Keyword::Not, + "of" => Keyword::Of, + "or" => Keyword::Or, + "range_check" => Keyword::RangeCheck, + "return" => Keyword::Return, + "shl" => Keyword::Shl, + "shr" => Keyword::Shr, + "store" => Keyword::Store, + "sub" => Keyword::Sub, + "then" => Keyword::Then, + "to" => Keyword::To, + "truncate" => Keyword::Truncate, + "value" => Keyword::Value, + "xor" => Keyword::Xor, + _ => return None, + }; + Some(Token::Keyword(keyword)) + } +} + +impl Display for Keyword { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Keyword::Acir => write!(f, "acir"), + Keyword::Add => write!(f, "add"), + Keyword::Allocate => write!(f, "allocate"), + Keyword::And => write!(f, "and"), + Keyword::ArrayGet => write!(f, "array_get"), + Keyword::ArraySet => write!(f, "array_set"), + Keyword::As => write!(f, "as"), + Keyword::At => write!(f, "at"), + Keyword::Bits => write!(f, "bits"), + Keyword::Bool => write!(f, "bool"), + Keyword::Brillig => write!(f, "brillig"), + Keyword::Call => write!(f, "call"), + Keyword::Cast => write!(f, "cast"), + Keyword::Constrain => write!(f, "constrain"), + Keyword::DecRc => write!(f, "dec_rc"), + Keyword::Div => write!(f, "div"), + Keyword::Else => write!(f, "else"), + Keyword::EnableSideEffects => write!(f, "enable_side_effects"), + Keyword::Eq => write!(f, "eq"), + Keyword::Field => write!(f, "Field"), + Keyword::Fold => write!(f, "fold"), + Keyword::Fn => write!(f, "fn"), + Keyword::IncRc => write!(f, "inc_rc"), + Keyword::Index => write!(f, "index"), + Keyword::Inline => write!(f, "inline"), + Keyword::InlineAlways => write!(f, "inline_always"), + Keyword::Jmp => write!(f, "jmp"), + Keyword::Jmpif => write!(f, "jmpif"), + Keyword::Load => write!(f, "load"), + Keyword::Lt => write!(f, "lt"), + Keyword::MaxBitSize => write!(f, "max_bit_size"), + Keyword::Mod => write!(f, "mod"), + Keyword::Mul => write!(f, "mul"), + Keyword::Mut => write!(f, "mut"), + Keyword::NoPredicates => write!(f, "no_predicates"), + Keyword::Not => write!(f, "not"), + Keyword::Of => write!(f, "of"), + Keyword::Or => write!(f, "or"), + Keyword::RangeCheck => write!(f, "range_check"), + Keyword::Return => write!(f, "return"), + Keyword::Shl => write!(f, "shl"), + Keyword::Shr => write!(f, "shr"), + Keyword::Store => write!(f, "store"), + Keyword::Sub => write!(f, "sub"), + Keyword::Then => write!(f, "then"), + Keyword::To => write!(f, "to"), + Keyword::Truncate => write!(f, "truncate"), + Keyword::Value => write!(f, "value"), + Keyword::Xor => write!(f, "xor"), + } + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs index 91ae544ddf0..5c592c84022 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/lexer.rs @@ -371,11 +371,11 @@ impl<'a> Lexer<'a> { // Check if word an int type // if no error occurred, then it is either a valid integer type or it is not an int type - let parsed_token = IntType::lookup_int_type(&word)?; + let parsed_token = IntType::lookup_int_type(&word); // Check if it is an int type - if let Some(int_type_token) = parsed_token { - return Ok(int_type_token.into_span(start, end)); + if let Some(int_type) = parsed_token { + return Ok(Token::IntType(int_type).into_span(start, end)); } // Else it is just an identifier diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs index 11001d27768..1f34af8313d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs @@ -585,7 +585,7 @@ impl IntType { // XXX: Result // Is not the best API. We could split this into two functions. One that checks if the // word is a integer, which only returns an Option - pub(crate) fn lookup_int_type(word: &str) -> Result, LexerErrorKind> { + pub fn lookup_int_type(word: &str) -> Option { // Check if the first string is a 'u' or 'i' let is_signed = if word.starts_with('i') { @@ -593,20 +593,20 @@ impl IntType { } else if word.starts_with('u') { false } else { - return Ok(None); + return None; }; // Word start with 'u' or 'i'. Check if the latter is an integer let str_as_u32 = match word[1..].parse::() { Ok(str_as_u32) => str_as_u32, - Err(_) => return Ok(None), + Err(_) => return None, }; if is_signed { - Ok(Some(Token::IntType(IntType::Signed(str_as_u32)))) + Some(IntType::Signed(str_as_u32)) } else { - Ok(Some(Token::IntType(IntType::Unsigned(str_as_u32)))) + Some(IntType::Unsigned(str_as_u32)) } } }