diff --git a/.aztec-sync-commit b/.aztec-sync-commit index f99c7ec7e49..d5285a35a9e 100644 --- a/.aztec-sync-commit +++ b/.aztec-sync-commit @@ -1 +1 @@ -1dfbe7bc3bf3c455d8fb6c8b5fe6a96c1edf7af9 +3fb94c0cd5ffba20a99b97c0088ae5ef357c205d diff --git a/Cargo.lock b/Cargo.lock index dec9cf7ad4f..ee7d1d7d024 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,6 +13,7 @@ dependencies = [ "flate2", "fxhash", "serde", + "serde-big-array", "serde-generate", "serde-reflection", "serde_json", @@ -268,7 +269,7 @@ dependencies = [ "ark-std", "derivative", "hashbrown 0.13.2", - "itertools 0.10.5", + "itertools", "num-traits", "zeroize", ] @@ -285,7 +286,7 @@ dependencies = [ "ark-std", "derivative", "digest", - "itertools 0.10.5", + "itertools", "num-bigint", "num-traits", "paste", @@ -374,15 +375,6 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" -[[package]] -name = "ascii-canvas" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" -dependencies = [ - "term", -] - [[package]] name = "assert_cmd" version = "2.0.12" @@ -1187,7 +1179,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools 0.10.5", + "itertools", "num-traits", "once_cell", "oorandom", @@ -1208,7 +1200,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools 0.10.5", + "itertools", ] [[package]] @@ -1264,12 +1256,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-bigint" version = "0.4.9" @@ -1541,15 +1527,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ena" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" -dependencies = [ - "log", -] - [[package]] name = "encode_unicode" version = "0.3.6" @@ -2388,15 +2365,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.9" @@ -2567,37 +2535,6 @@ dependencies = [ "libc", ] -[[package]] -name = "lalrpop" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" -dependencies = [ - "ascii-canvas", - "bit-set", - "ena", - "itertools 0.11.0", - "lalrpop-util", - "petgraph", - "pico-args", - "regex", - "regex-syntax 0.8.2", - "string_cache", - "term", - "tiny-keccak", - "unicode-xid", - "walkdir", -] - -[[package]] -name = "lalrpop-util" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" -dependencies = [ - "regex-automata 0.4.5", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -2920,12 +2857,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - [[package]] name = "nibble_vec" version = "0.1.0" @@ -3157,8 +3088,6 @@ dependencies = [ "chumsky", "fm", "iter-extended", - "lalrpop", - "lalrpop-util", "noirc_errors", "noirc_printable_type", "petgraph", @@ -3458,12 +3387,6 @@ dependencies = [ "siphasher", ] -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - [[package]] name = "pin-project-lite" version = "0.2.13" @@ -3548,12 +3471,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - [[package]] name = "predicates" version = "2.1.5" @@ -3562,7 +3479,7 @@ checksum = "59230a63c37f3e18569bdb90e4a89cbf5bf8b06fea0b84e65ea10cc4df47addd" dependencies = [ "difflib", "float-cmp", - "itertools 0.10.5", + "itertools", "normalize-line-endings", "predicates-core", "regex", @@ -3576,7 +3493,7 @@ checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" dependencies = [ "anstyle", "difflib", - "itertools 0.10.5", + "itertools", "predicates-core", ] @@ -4381,6 +4298,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde-generate" version = "0.25.1" @@ -4687,19 +4613,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" -[[package]] -name = "string_cache" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" -dependencies = [ - "new_debug_unreachable", - "once_cell", - "parking_lot 0.12.1", - "phf_shared", - "precomputed-hash", -] - [[package]] name = "strsim" version = "0.10.0" @@ -4942,15 +4855,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinytemplate" version = "1.2.1" @@ -5372,9 +5276,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.5.0" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", "winapi-util", diff --git a/acvm-repo/acir/Cargo.toml b/acvm-repo/acir/Cargo.toml index 99095ad3f61..4fae9ea20ff 100644 --- a/acvm-repo/acir/Cargo.toml +++ b/acvm-repo/acir/Cargo.toml @@ -20,6 +20,7 @@ thiserror.workspace = true flate2.workspace = true bincode.workspace = true base64.workspace = true +serde-big-array = "0.5.1" [dev-dependencies] serde_json = "1.0" diff --git a/acvm-repo/acir/codegen/acir.cpp b/acvm-repo/acir/codegen/acir.cpp index 7cd9fbefba0..6c7bd347e5d 100644 --- a/acvm-repo/acir/codegen/acir.cpp +++ b/acvm-repo/acir/codegen/acir.cpp @@ -54,7 +54,7 @@ namespace Program { struct SHA256 { std::vector inputs; - std::vector outputs; + std::array outputs; friend bool operator==(const SHA256&, const SHA256&); std::vector bincodeSerialize() const; @@ -63,7 +63,7 @@ namespace Program { struct Blake2s { std::vector inputs; - std::vector outputs; + std::array outputs; friend bool operator==(const Blake2s&, const Blake2s&); std::vector bincodeSerialize() const; @@ -72,7 +72,7 @@ namespace Program { struct Blake3 { std::vector inputs; - std::vector outputs; + std::array outputs; friend bool operator==(const Blake3&, const Blake3&); std::vector bincodeSerialize() const; @@ -82,7 +82,7 @@ namespace Program { struct SchnorrVerify { Program::FunctionInput public_key_x; Program::FunctionInput public_key_y; - std::vector signature; + std::array signature; std::vector message; Program::Witness output; @@ -112,10 +112,10 @@ namespace Program { }; struct EcdsaSecp256k1 { - std::vector public_key_x; - std::vector public_key_y; - std::vector signature; - std::vector hashed_message; + std::array public_key_x; + std::array public_key_y; + std::array signature; + std::array hashed_message; Program::Witness output; friend bool operator==(const EcdsaSecp256k1&, const EcdsaSecp256k1&); @@ -124,10 +124,10 @@ namespace Program { }; struct EcdsaSecp256r1 { - std::vector public_key_x; - std::vector public_key_y; - std::vector signature; - std::vector hashed_message; + std::array public_key_x; + std::array public_key_y; + std::array signature; + std::array hashed_message; Program::Witness output; friend bool operator==(const EcdsaSecp256r1&, const EcdsaSecp256r1&); @@ -160,7 +160,7 @@ namespace Program { struct Keccak256 { std::vector inputs; Program::FunctionInput var_message_size; - std::vector outputs; + std::array outputs; friend bool operator==(const Keccak256&, const Keccak256&); std::vector bincodeSerialize() const; @@ -168,8 +168,8 @@ namespace Program { }; struct Keccakf1600 { - std::vector inputs; - std::vector outputs; + std::array inputs; + std::array outputs; friend bool operator==(const Keccakf1600&, const Keccakf1600&); std::vector bincodeSerialize() const; @@ -257,9 +257,9 @@ namespace Program { }; struct Sha256Compression { - std::vector inputs; - std::vector hash_values; - std::vector outputs; + std::array inputs; + std::array hash_values; + std::array outputs; friend bool operator==(const Sha256Compression&, const Sha256Compression&); std::vector bincodeSerialize() const; @@ -922,6 +922,9 @@ namespace Program { }; struct Trap { + uint64_t revert_data_offset; + uint64_t revert_data_size; + friend bool operator==(const Trap&, const Trap&); std::vector bincodeSerialize() const; static Trap bincodeDeserialize(std::vector); @@ -1061,6 +1064,17 @@ namespace Program { static MemoryInit bincodeDeserialize(std::vector); }; + struct BrilligCall { + uint32_t id; + std::vector inputs; + std::vector outputs; + std::optional predicate; + + friend bool operator==(const BrilligCall&, const BrilligCall&); + std::vector bincodeSerialize() const; + static BrilligCall bincodeDeserialize(std::vector); + }; + struct Call { uint32_t id; std::vector inputs; @@ -1072,7 +1086,7 @@ namespace Program { static Call bincodeDeserialize(std::vector); }; - std::variant value; + std::variant value; friend bool operator==(const Opcode&, const Opcode&); std::vector bincodeSerialize() const; @@ -1151,8 +1165,17 @@ namespace Program { static Circuit bincodeDeserialize(std::vector); }; + struct BrilligBytecode { + std::vector bytecode; + + friend bool operator==(const BrilligBytecode&, const BrilligBytecode&); + std::vector bincodeSerialize() const; + static BrilligBytecode bincodeDeserialize(std::vector); + }; + struct Program { std::vector functions; + std::vector unconstrained_functions; friend bool operator==(const Program&, const Program&); std::vector bincodeSerialize() const; @@ -4071,6 +4094,48 @@ Program::Brillig serde::Deserializable::deserialize(Deserializ return obj; } +namespace Program { + + inline bool operator==(const BrilligBytecode &lhs, const BrilligBytecode &rhs) { + if (!(lhs.bytecode == rhs.bytecode)) { return false; } + return true; + } + + inline std::vector BrilligBytecode::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline BrilligBytecode BrilligBytecode::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::BrilligBytecode &obj, Serializer &serializer) { + serializer.increase_container_depth(); + serde::Serializable::serialize(obj.bytecode, serializer); + serializer.decrease_container_depth(); +} + +template <> +template +Program::BrilligBytecode serde::Deserializable::deserialize(Deserializer &deserializer) { + deserializer.increase_container_depth(); + Program::BrilligBytecode obj; + obj.bytecode = serde::Deserializable::deserialize(deserializer); + deserializer.decrease_container_depth(); + return obj; +} + namespace Program { inline bool operator==(const BrilligInputs &lhs, const BrilligInputs &rhs) { @@ -4952,6 +5017,8 @@ Program::BrilligOpcode::BlackBox serde::Deserializable template void serde::Serializable::serialize(const Program::BrilligOpcode::Trap &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.revert_data_offset, serializer); + serde::Serializable::serialize(obj.revert_data_size, serializer); } template <> template Program::BrilligOpcode::Trap serde::Deserializable::deserialize(Deserializer &deserializer) { Program::BrilligOpcode::Trap obj; + obj.revert_data_offset = serde::Deserializable::deserialize(deserializer); + obj.revert_data_size = serde::Deserializable::deserialize(deserializer); return obj; } @@ -6118,6 +6189,53 @@ Program::Opcode::MemoryInit serde::Deserializable:: return obj; } +namespace Program { + + inline bool operator==(const Opcode::BrilligCall &lhs, const Opcode::BrilligCall &rhs) { + if (!(lhs.id == rhs.id)) { return false; } + if (!(lhs.inputs == rhs.inputs)) { return false; } + if (!(lhs.outputs == rhs.outputs)) { return false; } + if (!(lhs.predicate == rhs.predicate)) { return false; } + return true; + } + + inline std::vector Opcode::BrilligCall::bincodeSerialize() const { + auto serializer = serde::BincodeSerializer(); + serde::Serializable::serialize(*this, serializer); + return std::move(serializer).bytes(); + } + + inline Opcode::BrilligCall Opcode::BrilligCall::bincodeDeserialize(std::vector input) { + auto deserializer = serde::BincodeDeserializer(input); + auto value = serde::Deserializable::deserialize(deserializer); + if (deserializer.get_buffer_offset() < input.size()) { + throw serde::deserialization_error("Some input bytes were not read"); + } + return value; + } + +} // end of namespace Program + +template <> +template +void serde::Serializable::serialize(const Program::Opcode::BrilligCall &obj, Serializer &serializer) { + serde::Serializable::serialize(obj.id, serializer); + serde::Serializable::serialize(obj.inputs, serializer); + serde::Serializable::serialize(obj.outputs, serializer); + serde::Serializable::serialize(obj.predicate, serializer); +} + +template <> +template +Program::Opcode::BrilligCall serde::Deserializable::deserialize(Deserializer &deserializer) { + Program::Opcode::BrilligCall obj; + obj.id = serde::Deserializable::deserialize(deserializer); + obj.inputs = serde::Deserializable::deserialize(deserializer); + obj.outputs = serde::Deserializable::deserialize(deserializer); + obj.predicate = serde::Deserializable::deserialize(deserializer); + return obj; +} + namespace Program { inline bool operator==(const Opcode::Call &lhs, const Opcode::Call &rhs) { @@ -6290,6 +6408,7 @@ namespace Program { inline bool operator==(const Program &lhs, const Program &rhs) { if (!(lhs.functions == rhs.functions)) { return false; } + if (!(lhs.unconstrained_functions == rhs.unconstrained_functions)) { return false; } return true; } @@ -6315,6 +6434,7 @@ template void serde::Serializable::serialize(const Program::Program &obj, Serializer &serializer) { serializer.increase_container_depth(); serde::Serializable::serialize(obj.functions, serializer); + serde::Serializable::serialize(obj.unconstrained_functions, serializer); serializer.decrease_container_depth(); } @@ -6324,6 +6444,7 @@ Program::Program serde::Deserializable::deserialize(Deserializ deserializer.increase_container_depth(); Program::Program obj; obj.functions = serde::Deserializable::deserialize(deserializer); + obj.unconstrained_functions = serde::Deserializable::deserialize(deserializer); deserializer.decrease_container_depth(); return obj; } diff --git a/acvm-repo/acir/src/circuit/brillig.rs b/acvm-repo/acir/src/circuit/brillig.rs index f394a46ff82..e75d335d52b 100644 --- a/acvm-repo/acir/src/circuit/brillig.rs +++ b/acvm-repo/acir/src/circuit/brillig.rs @@ -29,3 +29,11 @@ pub struct Brillig { /// Predicate of the Brillig execution - indicates if it should be skipped pub predicate: Option, } + +/// This is purely a wrapper struct around a list of Brillig opcode's which represents +/// a full Brillig function to be executed by the Brillig VM. +/// This is stored separately on a program and accessed through a [BrilligPointer]. +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct BrilligBytecode { + pub bytecode: Vec, +} diff --git a/acvm-repo/acir/src/circuit/mod.rs b/acvm-repo/acir/src/circuit/mod.rs index 9c858617acf..188205b124c 100644 --- a/acvm-repo/acir/src/circuit/mod.rs +++ b/acvm-repo/acir/src/circuit/mod.rs @@ -15,6 +15,8 @@ use serde::{de::Error as DeserializationError, Deserialize, Deserializer, Serial use std::collections::BTreeSet; +use self::brillig::BrilligBytecode; + /// Specifies the maximum width of the expressions which will be constrained. /// /// Unbounded Expressions are useful if you are eventually going to pass the ACIR @@ -37,6 +39,7 @@ pub enum ExpressionWidth { #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)] pub struct Program { pub functions: Vec, + pub unconstrained_functions: Vec, } #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Default)] @@ -86,16 +89,6 @@ impl Circuit { } } -#[derive(Debug, Copy, Clone)] -/// The opcode location for a call to a separate ACIR circuit -/// This includes the function index of the caller within a [program][Program] -/// and the index in the callers ACIR to the specific call opcode. -/// This is only resolved and set during circuit execution. -pub struct ResolvedOpcodeLocation { - pub acir_function_index: usize, - pub opcode_location: OpcodeLocation, -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] /// Opcodes are locatable so that callers can /// map opcodes to debug information related to their context. @@ -273,6 +266,10 @@ impl std::fmt::Display for Program { writeln!(f, "func {}", func_index)?; writeln!(f, "{}", function)?; } + for (func_index, function) in self.unconstrained_functions.iter().enumerate() { + writeln!(f, "unconstrained func {}", func_index)?; + writeln!(f, "{:?}", function.bytecode)?; + } Ok(()) } } @@ -324,61 +321,31 @@ mod tests { }) } fn keccakf1600_opcode() -> Opcode { - Opcode::BlackBoxFuncCall(BlackBoxFuncCall::Keccakf1600 { - inputs: vec![ - FunctionInput { witness: Witness(1), num_bits: 64 }, - FunctionInput { witness: Witness(2), num_bits: 64 }, - FunctionInput { witness: Witness(3), num_bits: 64 }, - FunctionInput { witness: Witness(4), num_bits: 64 }, - FunctionInput { witness: Witness(5), num_bits: 64 }, - FunctionInput { witness: Witness(6), num_bits: 64 }, - FunctionInput { witness: Witness(7), num_bits: 64 }, - FunctionInput { witness: Witness(8), num_bits: 64 }, - FunctionInput { witness: Witness(9), num_bits: 64 }, - FunctionInput { witness: Witness(10), num_bits: 64 }, - FunctionInput { witness: Witness(11), num_bits: 64 }, - FunctionInput { witness: Witness(12), num_bits: 64 }, - FunctionInput { witness: Witness(13), num_bits: 64 }, - FunctionInput { witness: Witness(14), num_bits: 64 }, - FunctionInput { witness: Witness(15), num_bits: 64 }, - FunctionInput { witness: Witness(16), num_bits: 64 }, - FunctionInput { witness: Witness(17), num_bits: 64 }, - FunctionInput { witness: Witness(18), num_bits: 64 }, - FunctionInput { witness: Witness(19), num_bits: 64 }, - FunctionInput { witness: Witness(20), num_bits: 64 }, - FunctionInput { witness: Witness(21), num_bits: 64 }, - FunctionInput { witness: Witness(22), num_bits: 64 }, - FunctionInput { witness: Witness(23), num_bits: 64 }, - FunctionInput { witness: Witness(24), num_bits: 64 }, - FunctionInput { witness: Witness(25), num_bits: 64 }, - ], - outputs: vec![ - Witness(26), - Witness(27), - Witness(28), - Witness(29), - Witness(30), - Witness(31), - Witness(32), - Witness(33), - Witness(34), - Witness(35), - Witness(36), - Witness(37), - Witness(38), - Witness(39), - Witness(40), - Witness(41), - Witness(42), - Witness(43), - Witness(44), - Witness(45), - Witness(46), - Witness(47), - Witness(48), - Witness(49), - Witness(50), - ], + let inputs: Box<[FunctionInput; 25]> = Box::new(std::array::from_fn(|i| FunctionInput { + witness: Witness(i as u32 + 1), + num_bits: 8, + })); + let outputs: Box<[Witness; 25]> = Box::new(std::array::from_fn(|i| Witness(i as u32 + 26))); + + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::Keccakf1600 { inputs, outputs }) + } + fn schnorr_verify_opcode() -> Opcode { + let public_key_x = + FunctionInput { witness: Witness(1), num_bits: FieldElement::max_num_bits() }; + let public_key_y = + FunctionInput { witness: Witness(2), num_bits: FieldElement::max_num_bits() }; + let signature: Box<[FunctionInput; 64]> = Box::new(std::array::from_fn(|i| { + FunctionInput { witness: Witness(i as u32 + 3), num_bits: 8 } + })); + let message: Vec = vec![FunctionInput { witness: Witness(67), num_bits: 8 }]; + let output = Witness(68); + + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::SchnorrVerify { + public_key_x, + public_key_y, + signature, + message, + output, }) } @@ -387,14 +354,14 @@ mod tests { let circuit = Circuit { current_witness_index: 5, expression_width: ExpressionWidth::Unbounded, - opcodes: vec![and_opcode(), range_opcode()], + opcodes: vec![and_opcode(), range_opcode(), schnorr_verify_opcode()], private_parameters: BTreeSet::new(), public_parameters: PublicInputs(BTreeSet::from_iter(vec![Witness(2), Witness(12)])), return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(4), Witness(12)])), assert_messages: Default::default(), recursive: false, }; - let program = Program { functions: vec![circuit] }; + let program = Program { functions: vec![circuit], unconstrained_functions: Vec::new() }; fn read_write(program: Program) -> (Program, Program) { let bytes = Program::serialize_program(&program); @@ -420,6 +387,7 @@ mod tests { range_opcode(), and_opcode(), keccakf1600_opcode(), + schnorr_verify_opcode(), ], private_parameters: BTreeSet::new(), public_parameters: PublicInputs(BTreeSet::from_iter(vec![Witness(2)])), @@ -427,7 +395,7 @@ mod tests { assert_messages: Default::default(), recursive: false, }; - let program = Program { functions: vec![circuit] }; + let program = Program { functions: vec![circuit], unconstrained_functions: Vec::new() }; let json = serde_json::to_string_pretty(&program).unwrap(); diff --git a/acvm-repo/acir/src/circuit/opcodes.rs b/acvm-repo/acir/src/circuit/opcodes.rs index d8204132b3e..b0b8e286e0c 100644 --- a/acvm-repo/acir/src/circuit/opcodes.rs +++ b/acvm-repo/acir/src/circuit/opcodes.rs @@ -1,4 +1,7 @@ -use super::{brillig::Brillig, directives::Directive}; +use super::{ + brillig::{Brillig, BrilligInputs, BrilligOutputs}, + directives::Directive, +}; use crate::native_types::{Expression, Witness}; use serde::{Deserialize, Serialize}; @@ -29,6 +32,18 @@ pub enum Opcode { block_id: BlockId, init: Vec, }, + /// Calls to unconstrained functions + BrilligCall { + /// Id for the function being called. It is the responsibility of the executor + /// to fetch the appropriate Brillig bytecode from this id. + id: u32, + /// Inputs to the function call + inputs: Vec, + /// Outputs to the function call + outputs: Vec, + /// Predicate of the Brillig execution - indicates if it should be skipped + predicate: Option, + }, /// Calls to functions represented as a separate circuit. A call opcode allows us /// to build a call stack when executing the outer-most circuit. Call { @@ -99,6 +114,16 @@ impl std::fmt::Display for Opcode { write!(f, "INIT ")?; write!(f, "(id: {}, len: {}) ", block_id.0, init.len()) } + // We keep the display for a BrilligCall and circuit Call separate as they + // are distinct in their functionality and we should maintain this separation for debugging. + Opcode::BrilligCall { id, inputs, outputs, predicate } => { + write!(f, "BRILLIG CALL func {}: ", id)?; + if let Some(pred) = predicate { + writeln!(f, "PREDICATE = {pred}")?; + } + write!(f, "inputs: {:?}, ", inputs)?; + write!(f, "outputs: {:?}", outputs) + } Opcode::Call { id, inputs, outputs, predicate } => { write!(f, "CALL func {}: ", id)?; if let Some(pred) = predicate { diff --git a/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs b/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs index d9089578739..405cd0cef00 100644 --- a/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs +++ b/acvm-repo/acir/src/circuit/opcodes/black_box_function_call.rs @@ -1,6 +1,6 @@ use crate::native_types::Witness; use crate::BlackBoxFunc; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; // Note: Some functions will not use all of the witness // So we need to supply how many bits of the witness is needed @@ -27,20 +27,24 @@ pub enum BlackBoxFuncCall { }, SHA256 { inputs: Vec, - outputs: Vec, + outputs: Box<[Witness; 32]>, }, Blake2s { inputs: Vec, - outputs: Vec, + outputs: Box<[Witness; 32]>, }, Blake3 { inputs: Vec, - outputs: Vec, + outputs: Box<[Witness; 32]>, }, SchnorrVerify { public_key_x: FunctionInput, public_key_y: FunctionInput, - signature: Vec, + #[serde( + serialize_with = "serialize_big_array", + deserialize_with = "deserialize_big_array_into_box" + )] + signature: Box<[FunctionInput; 64]>, message: Vec, output: Witness, }, @@ -55,17 +59,25 @@ pub enum BlackBoxFuncCall { output: Witness, }, EcdsaSecp256k1 { - public_key_x: Vec, - public_key_y: Vec, - signature: Vec, - hashed_message: Vec, + public_key_x: Box<[FunctionInput; 32]>, + public_key_y: Box<[FunctionInput; 32]>, + #[serde( + serialize_with = "serialize_big_array", + deserialize_with = "deserialize_big_array_into_box" + )] + signature: Box<[FunctionInput; 64]>, + hashed_message: Box<[FunctionInput; 32]>, output: Witness, }, EcdsaSecp256r1 { - public_key_x: Vec, - public_key_y: Vec, - signature: Vec, - hashed_message: Vec, + public_key_x: Box<[FunctionInput; 32]>, + public_key_y: Box<[FunctionInput; 32]>, + #[serde( + serialize_with = "serialize_big_array", + deserialize_with = "deserialize_big_array_into_box" + )] + signature: Box<[FunctionInput; 64]>, + hashed_message: Box<[FunctionInput; 32]>, output: Witness, }, FixedBaseScalarMul { @@ -87,11 +99,11 @@ pub enum BlackBoxFuncCall { /// is more than the number of bytes in the input, /// then an error is returned. var_message_size: FunctionInput, - outputs: Vec, + outputs: Box<[Witness; 32]>, }, Keccakf1600 { - inputs: Vec, - outputs: Vec, + inputs: Box<[FunctionInput; 25]>, + outputs: Box<[Witness; 25]>, }, RecursiveAggregation { verification_key: Vec, @@ -154,11 +166,11 @@ pub enum BlackBoxFuncCall { /// * `outputs` - result of the input compressed into 256 bits Sha256Compression { /// 512 bits of the input message, represented by 16 u32s - inputs: Vec, + inputs: Box<[FunctionInput; 16]>, /// Vector of 8 u32s used to compress the input - hash_values: Vec, + hash_values: Box<[FunctionInput; 8]>, /// Output of the compression, represented by 8 u32s - outputs: Vec, + outputs: Box<[Witness; 8]>, }, } @@ -201,13 +213,15 @@ impl BlackBoxFuncCall { BlackBoxFuncCall::SHA256 { inputs, .. } | BlackBoxFuncCall::Blake2s { inputs, .. } | BlackBoxFuncCall::Blake3 { inputs, .. } - | BlackBoxFuncCall::Keccakf1600 { inputs, .. } | BlackBoxFuncCall::PedersenCommitment { inputs, .. } | BlackBoxFuncCall::PedersenHash { inputs, .. } | BlackBoxFuncCall::BigIntFromLeBytes { inputs, .. } | BlackBoxFuncCall::Poseidon2Permutation { inputs, .. } => inputs.to_vec(), + + BlackBoxFuncCall::Keccakf1600 { inputs, .. } => inputs.to_vec(), + BlackBoxFuncCall::Sha256Compression { inputs, hash_values, .. } => { - inputs.iter().chain(hash_values).copied().collect() + inputs.iter().chain(hash_values.as_ref()).copied().collect() } BlackBoxFuncCall::AND { lhs, rhs, .. } | BlackBoxFuncCall::XOR { lhs, rhs, .. } => { vec![*lhs, *rhs] @@ -300,10 +314,14 @@ impl BlackBoxFuncCall { BlackBoxFuncCall::SHA256 { outputs, .. } | BlackBoxFuncCall::Blake2s { outputs, .. } | BlackBoxFuncCall::Blake3 { outputs, .. } - | BlackBoxFuncCall::Keccakf1600 { outputs, .. } - | BlackBoxFuncCall::Keccak256 { outputs, .. } - | BlackBoxFuncCall::Poseidon2Permutation { outputs, .. } - | BlackBoxFuncCall::Sha256Compression { outputs, .. } => outputs.to_vec(), + | BlackBoxFuncCall::Keccak256 { outputs, .. } => outputs.to_vec(), + + BlackBoxFuncCall::Keccakf1600 { outputs, .. } => outputs.to_vec(), + + BlackBoxFuncCall::Sha256Compression { outputs, .. } => outputs.to_vec(), + + BlackBoxFuncCall::Poseidon2Permutation { outputs, .. } => outputs.to_vec(), + BlackBoxFuncCall::AND { output, .. } | BlackBoxFuncCall::XOR { output, .. } | BlackBoxFuncCall::SchnorrVerify { output, .. } @@ -422,3 +440,78 @@ impl std::fmt::Debug for BlackBoxFuncCall { std::fmt::Display::fmt(self, f) } } + +fn serialize_big_array(big_array: &[FunctionInput; 64], s: S) -> Result +where + S: Serializer, +{ + use serde_big_array::BigArray; + + (*big_array).serialize(s) +} + +fn deserialize_big_array_into_box<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + use serde_big_array::BigArray; + + let big_array: [FunctionInput; 64] = BigArray::deserialize(deserializer)?; + Ok(Box::new(big_array)) +} + +#[cfg(test)] +mod tests { + + use crate::{circuit::Opcode, native_types::Witness}; + use acir_field::FieldElement; + + use super::{BlackBoxFuncCall, FunctionInput}; + + fn keccakf1600_opcode() -> Opcode { + let inputs: Box<[FunctionInput; 25]> = Box::new(std::array::from_fn(|i| FunctionInput { + witness: Witness(i as u32 + 1), + num_bits: 8, + })); + let outputs: Box<[Witness; 25]> = Box::new(std::array::from_fn(|i| Witness(i as u32 + 26))); + + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::Keccakf1600 { inputs, outputs }) + } + fn schnorr_verify_opcode() -> Opcode { + let public_key_x = + FunctionInput { witness: Witness(1), num_bits: FieldElement::max_num_bits() }; + let public_key_y = + FunctionInput { witness: Witness(2), num_bits: FieldElement::max_num_bits() }; + let signature: Box<[FunctionInput; 64]> = Box::new(std::array::from_fn(|i| { + FunctionInput { witness: Witness(i as u32 + 3), num_bits: 8 } + })); + let message: Vec = vec![FunctionInput { witness: Witness(67), num_bits: 8 }]; + let output = Witness(68); + + Opcode::BlackBoxFuncCall(BlackBoxFuncCall::SchnorrVerify { + public_key_x, + public_key_y, + signature, + message, + output, + }) + } + + #[test] + fn keccakf1600_serialization_roundtrip() { + let opcode = keccakf1600_opcode(); + let buf = bincode::serialize(&opcode).unwrap(); + let recovered_opcode = bincode::deserialize(&buf).unwrap(); + assert_eq!(opcode, recovered_opcode); + } + + #[test] + fn schnorr_serialization_roundtrip() { + let opcode = schnorr_verify_opcode(); + let buf = bincode::serialize(&opcode).unwrap(); + let recovered_opcode = bincode::deserialize(&buf).unwrap(); + assert_eq!(opcode, recovered_opcode); + } +} diff --git a/acvm-repo/acir/tests/test_program_serialization.rs b/acvm-repo/acir/tests/test_program_serialization.rs index 8b9160ccf6a..fb924a7437d 100644 --- a/acvm-repo/acir/tests/test_program_serialization.rs +++ b/acvm-repo/acir/tests/test_program_serialization.rs @@ -41,17 +41,17 @@ fn addition_circuit() { return_values: PublicInputs([Witness(3)].into()), ..Circuit::default() }; - let program = Program { functions: vec![circuit] }; + let program = Program { functions: vec![circuit], unconstrained_functions: vec![] }; let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 144, 75, 14, 128, 32, 12, 68, 249, 120, 160, 150, - 182, 208, 238, 188, 138, 68, 184, 255, 17, 212, 200, 130, 196, 165, 188, 164, 153, 174, 94, - 38, 227, 221, 203, 118, 159, 119, 95, 226, 200, 125, 36, 252, 3, 253, 66, 87, 152, 92, 4, - 153, 185, 149, 212, 144, 240, 128, 100, 85, 5, 88, 106, 86, 84, 20, 149, 51, 41, 81, 83, - 214, 98, 213, 10, 24, 50, 53, 236, 98, 212, 135, 44, 174, 235, 5, 143, 35, 12, 151, 159, - 126, 55, 109, 28, 231, 145, 47, 245, 105, 191, 143, 133, 1, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 144, 65, 14, 128, 32, 12, 4, 65, 124, 80, 75, 91, + 104, 111, 126, 69, 34, 252, 255, 9, 106, 228, 64, 162, 55, 153, 164, 217, 158, 38, 155, + 245, 238, 97, 189, 206, 187, 55, 161, 231, 214, 19, 254, 129, 126, 162, 107, 25, 92, 4, + 137, 185, 230, 88, 145, 112, 135, 104, 69, 5, 88, 74, 82, 84, 20, 149, 35, 42, 81, 85, 214, + 108, 197, 50, 24, 50, 85, 108, 98, 212, 186, 44, 204, 235, 5, 183, 99, 233, 46, 63, 252, + 110, 216, 56, 184, 15, 78, 146, 74, 173, 20, 141, 1, 0, 0, ]; assert_eq!(bytes, expected_serialization) @@ -72,14 +72,14 @@ fn fixed_base_scalar_mul_circuit() { return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(3), Witness(4)])), ..Circuit::default() }; - let program = Program { functions: vec![circuit] }; + let program = Program { functions: vec![circuit], unconstrained_functions: vec![] }; let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 77, 138, 81, 10, 0, 48, 8, 66, 87, 219, 190, 118, 233, - 29, 61, 43, 3, 5, 121, 34, 207, 86, 231, 162, 198, 157, 124, 228, 71, 157, 220, 232, 161, - 227, 226, 206, 214, 95, 221, 74, 0, 116, 58, 13, 182, 105, 0, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 85, 138, 81, 10, 0, 48, 8, 66, 87, 219, 190, 118, 233, + 29, 61, 35, 3, 19, 228, 137, 60, 91, 149, 139, 26, 119, 242, 145, 31, 117, 114, 163, 135, + 142, 139, 219, 91, 127, 117, 71, 2, 117, 84, 50, 98, 113, 0, 0, 0, ]; assert_eq!(bytes, expected_serialization) @@ -100,14 +100,14 @@ fn pedersen_circuit() { return_values: PublicInputs(BTreeSet::from_iter(vec![Witness(2), Witness(3)])), ..Circuit::default() }; - let program = Program { functions: vec![circuit] }; + let program = Program { functions: vec![circuit], unconstrained_functions: vec![] }; let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 93, 74, 7, 6, 0, 0, 8, 108, 209, 255, 63, 156, 54, 233, - 56, 55, 17, 26, 18, 196, 241, 169, 250, 178, 141, 167, 32, 159, 254, 234, 238, 255, 87, - 112, 52, 63, 63, 101, 105, 0, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 93, 74, 9, 10, 0, 0, 4, 115, 149, 255, 127, 88, 8, 133, + 213, 218, 137, 80, 144, 32, 182, 79, 213, 151, 173, 61, 5, 121, 245, 91, 103, 255, 191, 3, + 7, 16, 26, 112, 158, 113, 0, 0, 0, ]; assert_eq!(bytes, expected_serialization) @@ -119,8 +119,11 @@ fn schnorr_verify_circuit() { FunctionInput { witness: Witness(1), num_bits: FieldElement::max_num_bits() }; let public_key_y = FunctionInput { witness: Witness(2), num_bits: FieldElement::max_num_bits() }; - let signature = - (3..(3 + 64)).map(|i| FunctionInput { witness: Witness(i), num_bits: 8 }).collect(); + let signature: [FunctionInput; 64] = (3..(3 + 64)) + .map(|i| FunctionInput { witness: Witness(i), num_bits: 8 }) + .collect::>() + .try_into() + .unwrap(); let message = ((3 + 64)..(3 + 64 + 10)) .map(|i| FunctionInput { witness: Witness(i), num_bits: 8 }) .collect(); @@ -130,7 +133,7 @@ fn schnorr_verify_circuit() { let schnorr = Opcode::BlackBoxFuncCall(BlackBoxFuncCall::SchnorrVerify { public_key_x, public_key_y, - signature, + signature: Box::new(signature), message, output, }); @@ -142,27 +145,27 @@ fn schnorr_verify_circuit() { return_values: PublicInputs(BTreeSet::from([output])), ..Circuit::default() }; - let program = Program { functions: vec![circuit] }; + let program = Program { functions: vec![circuit], unconstrained_functions: vec![] }; let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 77, 210, 7, 78, 2, 1, 20, 69, 81, 236, 189, 247, 222, - 123, 239, 93, 177, 33, 34, 238, 194, 253, 47, 193, 200, 147, 67, 194, 36, 147, 163, 33, 33, - 228, 191, 219, 82, 168, 63, 63, 181, 183, 197, 223, 177, 147, 191, 181, 183, 149, 69, 159, - 183, 213, 222, 238, 218, 219, 206, 14, 118, 178, 139, 141, 183, 135, 189, 236, 99, 63, 7, - 56, 200, 33, 14, 115, 132, 163, 28, 227, 56, 39, 56, 201, 41, 78, 115, 134, 179, 156, 227, - 60, 23, 184, 200, 37, 46, 115, 133, 171, 92, 227, 58, 55, 184, 201, 45, 110, 115, 135, 187, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 85, 210, 85, 78, 67, 81, 24, 133, 209, 226, 238, 238, + 238, 238, 238, 165, 148, 82, 102, 193, 252, 135, 64, 232, 78, 87, 147, 114, 147, 147, 5, + 47, 132, 252, 251, 107, 41, 212, 191, 159, 218, 107, 241, 115, 236, 228, 111, 237, 181, + 178, 173, 246, 186, 107, 175, 157, 29, 236, 100, 23, 27, 175, 135, 189, 236, 99, 63, 7, 56, + 200, 33, 14, 115, 132, 163, 28, 227, 56, 39, 56, 201, 41, 78, 115, 134, 179, 156, 227, 60, + 23, 184, 200, 37, 46, 115, 133, 171, 92, 227, 58, 55, 184, 201, 45, 110, 115, 135, 187, 220, 227, 62, 15, 120, 200, 35, 30, 243, 132, 167, 60, 227, 57, 47, 120, 201, 43, 94, 243, - 134, 183, 188, 227, 61, 31, 248, 200, 39, 22, 249, 204, 151, 166, 29, 243, 188, 250, 255, - 141, 239, 44, 241, 131, 101, 126, 178, 194, 47, 86, 249, 237, 123, 171, 76, 127, 105, 47, - 189, 165, 181, 116, 150, 198, 26, 125, 245, 248, 45, 233, 41, 45, 165, 163, 52, 148, 126, - 210, 78, 186, 73, 51, 233, 37, 173, 164, 147, 52, 146, 62, 210, 70, 186, 72, 19, 233, 33, - 45, 164, 131, 52, 144, 253, 151, 11, 245, 221, 179, 121, 246, 206, 214, 217, 57, 27, 103, - 223, 109, 187, 238, 218, 115, 223, 142, 135, 246, 59, 182, 219, 169, 189, 206, 237, 116, - 105, 159, 107, 187, 220, 218, 227, 222, 14, 143, 238, 95, 116, 247, 23, 119, 126, 115, 223, - 146, 187, 150, 221, 179, 226, 142, 141, 155, 53, 238, 86, 104, 186, 231, 255, 243, 7, 100, - 141, 232, 192, 233, 3, 0, 0, + 134, 183, 188, 227, 61, 31, 248, 200, 39, 62, 243, 133, 175, 77, 59, 230, 123, 243, 123, + 145, 239, 44, 241, 131, 101, 126, 178, 194, 47, 86, 249, 237, 239, 86, 153, 238, 210, 92, + 122, 75, 107, 233, 44, 141, 53, 250, 234, 241, 191, 164, 167, 180, 148, 142, 210, 80, 250, + 73, 59, 233, 38, 205, 164, 151, 180, 146, 78, 210, 72, 250, 72, 27, 233, 34, 77, 164, 135, + 180, 144, 14, 210, 64, 246, 95, 46, 212, 119, 207, 230, 217, 59, 91, 103, 231, 108, 156, + 125, 183, 237, 186, 107, 207, 125, 59, 30, 218, 239, 216, 110, 167, 246, 58, 183, 211, 165, + 125, 174, 237, 114, 107, 143, 123, 59, 60, 186, 255, 179, 187, 191, 186, 115, 209, 125, 75, + 238, 90, 118, 207, 138, 59, 54, 110, 214, 184, 91, 161, 233, 158, 255, 190, 63, 165, 188, + 93, 151, 233, 3, 0, 0, ]; assert_eq!(bytes, expected_serialization) @@ -206,16 +209,17 @@ fn simple_brillig_foreign_call() { private_parameters: BTreeSet::from([Witness(1), Witness(2)]), ..Circuit::default() }; - let program = Program { functions: vec![circuit] }; + let program = Program { functions: vec![circuit], unconstrained_functions: vec![] }; let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 144, 61, 10, 192, 48, 8, 133, 53, 133, 82, 186, - 245, 38, 233, 13, 122, 153, 14, 93, 58, 132, 144, 227, 135, 252, 41, 56, 36, 46, 201, 7, - 162, 168, 200, 123, 34, 52, 142, 28, 72, 245, 38, 106, 9, 247, 30, 202, 118, 142, 27, 215, - 221, 178, 82, 175, 33, 15, 133, 189, 163, 159, 57, 197, 252, 251, 195, 235, 188, 230, 186, - 16, 65, 255, 12, 239, 92, 131, 89, 149, 198, 77, 3, 10, 9, 119, 8, 198, 242, 152, 1, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 144, 61, 10, 192, 32, 12, 133, 19, 11, 165, 116, + 235, 77, 236, 13, 122, 153, 14, 93, 58, 136, 120, 124, 241, 47, 129, 12, 42, 130, 126, 16, + 18, 146, 16, 222, 11, 66, 225, 136, 129, 84, 111, 162, 150, 112, 239, 161, 172, 231, 184, + 113, 221, 45, 45, 245, 42, 242, 144, 216, 43, 250, 153, 83, 204, 191, 223, 189, 198, 246, + 92, 39, 60, 244, 63, 195, 59, 87, 99, 150, 165, 113, 83, 193, 0, 1, 19, 247, 29, 5, 160, 1, + 0, 0, ]; assert_eq!(bytes, expected_serialization) @@ -306,20 +310,20 @@ fn complex_brillig_foreign_call() { private_parameters: BTreeSet::from([Witness(1), Witness(2), Witness(3)]), ..Circuit::default() }; - let program = Program { functions: vec![circuit] }; + let program = Program { functions: vec![circuit], unconstrained_functions: vec![] }; let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 93, 10, 131, 48, 12, 78, 218, 233, 100, 111, - 187, 193, 96, 59, 64, 231, 9, 188, 139, 248, 166, 232, 163, 167, 23, 11, 126, 197, 24, 250, - 34, 86, 208, 64, 72, 218, 252, 125, 36, 105, 153, 22, 42, 60, 51, 116, 235, 217, 64, 103, - 156, 37, 5, 191, 10, 210, 29, 163, 63, 167, 203, 229, 206, 194, 104, 110, 128, 209, 158, - 128, 49, 236, 195, 69, 231, 157, 114, 46, 73, 251, 103, 35, 239, 231, 225, 57, 243, 156, - 227, 252, 132, 44, 112, 79, 176, 125, 84, 223, 73, 248, 145, 152, 69, 149, 4, 107, 233, - 114, 90, 119, 145, 85, 237, 151, 192, 89, 247, 221, 208, 54, 163, 85, 174, 26, 234, 87, - 232, 63, 101, 103, 21, 55, 169, 216, 73, 72, 249, 5, 197, 234, 132, 123, 179, 35, 247, 155, - 214, 246, 102, 20, 73, 204, 72, 168, 123, 191, 161, 25, 66, 136, 159, 187, 53, 5, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 75, 10, 132, 48, 12, 77, 218, 209, 145, 217, + 205, 13, 6, 198, 3, 84, 79, 224, 93, 196, 157, 162, 75, 79, 47, 22, 124, 197, 16, 186, 17, + 43, 104, 32, 36, 109, 126, 143, 36, 45, 211, 70, 133, 103, 134, 110, 61, 27, 232, 140, 179, + 164, 224, 215, 64, 186, 115, 84, 113, 186, 92, 238, 42, 140, 230, 1, 24, 237, 5, 24, 195, + 62, 220, 116, 222, 41, 231, 146, 180, 127, 54, 242, 126, 94, 158, 51, 207, 57, 206, 111, + 200, 2, 247, 4, 219, 79, 245, 157, 132, 31, 137, 89, 52, 73, 176, 214, 46, 167, 125, 23, + 89, 213, 254, 8, 156, 237, 56, 76, 125, 55, 91, 229, 170, 161, 254, 133, 94, 42, 59, 171, + 184, 69, 197, 46, 66, 202, 47, 40, 86, 39, 220, 155, 3, 185, 191, 180, 183, 55, 163, 72, + 98, 70, 66, 221, 251, 40, 173, 255, 35, 68, 62, 61, 5, 0, 0, ]; assert_eq!(bytes, expected_serialization) @@ -348,16 +352,16 @@ fn memory_op_circuit() { return_values: PublicInputs([Witness(4)].into()), ..Circuit::default() }; - let program = Program { functions: vec![circuit] }; + let program = Program { functions: vec![circuit], unconstrained_functions: vec![] }; let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 81, 201, 13, 0, 32, 8, 147, 195, 125, 112, 3, 247, - 159, 74, 141, 60, 106, 226, 79, 120, 216, 132, 180, 124, 154, 82, 168, 108, 212, 57, 2, - 122, 129, 157, 201, 181, 150, 59, 186, 179, 189, 161, 101, 251, 82, 176, 175, 196, 121, 89, - 118, 185, 246, 91, 185, 26, 125, 187, 64, 80, 134, 29, 195, 31, 79, 24, 2, 250, 167, 252, - 27, 3, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 81, 57, 14, 0, 32, 8, 147, 195, 255, 224, 15, 252, + 255, 171, 212, 200, 208, 129, 77, 24, 108, 66, 90, 150, 166, 20, 106, 23, 125, 143, 128, + 62, 96, 103, 114, 173, 45, 198, 116, 182, 55, 140, 106, 95, 74, 246, 149, 60, 47, 171, 46, + 215, 126, 43, 87, 179, 111, 23, 8, 202, 176, 99, 248, 240, 9, 11, 137, 33, 212, 110, 35, 3, + 0, 0, ]; assert_eq!(bytes, expected_serialization) @@ -450,20 +454,21 @@ fn nested_acir_call_circuit() { ..Circuit::default() }; - let program = Program { functions: vec![main, nested_call, inner_call] }; + let program = + Program { functions: vec![main, nested_call, inner_call], unconstrained_functions: vec![] }; let bytes = Program::serialize_program(&program); let expected_serialization: Vec = vec![ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 205, 146, 97, 10, 195, 32, 12, 133, 163, 66, 207, 147, - 24, 173, 241, 223, 174, 50, 153, 189, 255, 17, 214, 177, 148, 89, 17, 250, 99, 14, 246, - 224, 97, 144, 16, 146, 143, 231, 224, 45, 167, 126, 105, 217, 109, 118, 91, 248, 200, 168, - 225, 248, 191, 106, 114, 208, 233, 104, 188, 233, 139, 223, 137, 108, 51, 139, 113, 13, - 161, 38, 95, 137, 233, 142, 62, 23, 137, 24, 98, 89, 133, 132, 162, 196, 135, 23, 230, 42, - 65, 82, 46, 57, 97, 166, 192, 149, 182, 152, 121, 211, 97, 110, 222, 94, 8, 13, 132, 182, - 54, 48, 144, 235, 8, 254, 10, 22, 76, 132, 101, 231, 237, 229, 23, 189, 213, 54, 119, 15, - 83, 212, 199, 172, 175, 79, 113, 51, 48, 198, 253, 207, 84, 13, 204, 141, 224, 21, 176, - 147, 158, 66, 231, 43, 145, 6, 4, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 205, 146, 65, 10, 3, 33, 12, 69, 163, 46, 230, 58, 137, + 209, 49, 238, 122, 149, 74, 157, 251, 31, 161, 83, 154, 161, 86, 132, 89, 212, 194, 124, + 248, 24, 36, 132, 228, 241, 29, 188, 229, 212, 47, 45, 187, 205, 110, 11, 31, 25, 53, 28, + 255, 103, 77, 14, 58, 29, 141, 55, 125, 241, 55, 145, 109, 102, 49, 174, 33, 212, 228, 43, + 49, 221, 209, 231, 34, 17, 67, 44, 171, 144, 80, 148, 248, 240, 194, 92, 37, 72, 202, 37, + 39, 204, 20, 184, 210, 22, 51, 111, 58, 204, 205, 219, 11, 161, 129, 208, 214, 6, 6, 114, + 29, 193, 127, 193, 130, 137, 176, 236, 188, 189, 252, 162, 183, 218, 230, 238, 97, 138, + 250, 152, 245, 245, 87, 220, 12, 140, 113, 95, 153, 170, 129, 185, 17, 60, 3, 54, 212, 19, + 104, 145, 195, 151, 14, 4, 0, 0, ]; assert_eq!(bytes, expected_serialization); } diff --git a/acvm-repo/acvm/src/compiler/transformers/mod.rs b/acvm-repo/acvm/src/compiler/transformers/mod.rs index 003cd4279a1..d13fac1672a 100644 --- a/acvm-repo/acvm/src/compiler/transformers/mod.rs +++ b/acvm-repo/acvm/src/compiler/transformers/mod.rs @@ -142,6 +142,21 @@ pub(super) fn transform_internal( new_acir_opcode_positions.push(acir_opcode_positions[index]); transformed_opcodes.push(opcode); } + Opcode::BrilligCall { ref outputs, .. } => { + for output in outputs { + match output { + BrilligOutputs::Simple(w) => transformer.mark_solvable(*w), + BrilligOutputs::Array(v) => { + for witness in v { + transformer.mark_solvable(*witness); + } + } + } + } + + new_acir_opcode_positions.push(acir_opcode_positions[index]); + transformed_opcodes.push(opcode); + } Opcode::Call { ref outputs, .. } => { for witness in outputs { transformer.mark_solvable(*witness); diff --git a/acvm-repo/acvm/src/pwg/blackbox/bigint.rs b/acvm-repo/acvm/src/pwg/blackbox/bigint.rs index a47afa5049a..f094bb1ba20 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/bigint.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/bigint.rs @@ -105,7 +105,7 @@ impl BigIntSolver { } BlackBoxFunc::BigIntMul => lhs * rhs, BlackBoxFunc::BigIntDiv => { - lhs * rhs.modpow(&(&modulus - BigUint::from(2_u32)), &modulus) + lhs * rhs.modpow(&(&modulus - BigUint::from(1_u32)), &modulus) } //TODO ensure that modulus is prime _ => unreachable!("ICE - bigint_op must be called for an operation"), }; diff --git a/acvm-repo/acvm/src/pwg/blackbox/hash.rs b/acvm-repo/acvm/src/pwg/blackbox/hash.rs index 24c835a636a..caa09ea8973 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/hash.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/hash.rs @@ -1,7 +1,7 @@ use acir::{ circuit::opcodes::FunctionInput, native_types::{Witness, WitnessMap}, - BlackBoxFunc, FieldElement, + FieldElement, }; use acvm_blackbox_solver::{sha256compression, BlackBoxFunctionSolver, BlackBoxResolutionError}; @@ -14,22 +14,13 @@ pub(super) fn solve_generic_256_hash_opcode( initial_witness: &mut WitnessMap, inputs: &[FunctionInput], var_message_size: Option<&FunctionInput>, - outputs: &[Witness], + outputs: &[Witness; 32], hash_function: fn(data: &[u8]) -> Result<[u8; 32], BlackBoxResolutionError>, - black_box_func: BlackBoxFunc, ) -> Result<(), OpcodeResolutionError> { let message_input = get_hash_input(initial_witness, inputs, var_message_size)?; let digest: [u8; 32] = hash_function(&message_input)?; - let outputs: [Witness; 32] = outputs.try_into().map_err(|_| { - OpcodeResolutionError::BlackBoxFunctionFailed( - black_box_func, - format!("Expected 32 outputs but encountered {}", outputs.len()), - ) - })?; - write_digest_to_outputs(initial_witness, outputs, digest)?; - - Ok(()) + write_digest_to_outputs(initial_witness, outputs, digest) } /// Reads the hash function input from a [`WitnessMap`]. @@ -73,7 +64,7 @@ fn get_hash_input( /// Writes a `digest` to the [`WitnessMap`] at witness indices `outputs`. fn write_digest_to_outputs( initial_witness: &mut WitnessMap, - outputs: [Witness; 32], + outputs: &[Witness; 32], digest: [u8; 32], ) -> Result<(), OpcodeResolutionError> { for (output_witness, value) in outputs.iter().zip(digest.into_iter()) { @@ -87,44 +78,29 @@ fn write_digest_to_outputs( Ok(()) } +fn to_u32_array( + initial_witness: &WitnessMap, + inputs: &[FunctionInput; N], +) -> Result<[u32; N], OpcodeResolutionError> { + let mut result = [0; N]; + for (it, input) in result.iter_mut().zip(inputs) { + let witness_value = witness_to_value(initial_witness, input.witness)?; + *it = witness_value.to_u128() as u32; + } + Ok(result) +} + pub(crate) fn solve_sha_256_permutation_opcode( initial_witness: &mut WitnessMap, - inputs: &[FunctionInput], - hash_values: &[FunctionInput], - outputs: &[Witness], - black_box_func: BlackBoxFunc, + inputs: &[FunctionInput; 16], + hash_values: &[FunctionInput; 8], + outputs: &[Witness; 8], ) -> Result<(), OpcodeResolutionError> { - let mut message = [0; 16]; - if inputs.len() != 16 { - return Err(OpcodeResolutionError::BlackBoxFunctionFailed( - black_box_func, - format!("Expected 16 inputs but encountered {}", &message.len()), - )); - } - for (i, input) in inputs.iter().enumerate() { - let value = witness_to_value(initial_witness, input.witness)?; - message[i] = value.to_u128() as u32; - } - - if hash_values.len() != 8 { - return Err(OpcodeResolutionError::BlackBoxFunctionFailed( - black_box_func, - format!("Expected 8 values but encountered {}", hash_values.len()), - )); - } - let mut state = [0; 8]; - for (i, hash) in hash_values.iter().enumerate() { - let value = witness_to_value(initial_witness, hash.witness)?; - state[i] = value.to_u128() as u32; - } + let message = to_u32_array(initial_witness, inputs)?; + let mut state = to_u32_array(initial_witness, hash_values)?; sha256compression(&mut state, &message); - let outputs: [Witness; 8] = outputs.try_into().map_err(|_| { - OpcodeResolutionError::BlackBoxFunctionFailed( - black_box_func, - format!("Expected 8 outputs but encountered {}", outputs.len()), - ) - })?; + for (output_witness, value) in outputs.iter().zip(state.into_iter()) { insert_value(output_witness, FieldElement::from(value as u128), initial_witness)?; } diff --git a/acvm-repo/acvm/src/pwg/blackbox/mod.rs b/acvm-repo/acvm/src/pwg/blackbox/mod.rs index e7ed402a8eb..26d1a3c8f86 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/mod.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/mod.rs @@ -71,30 +71,16 @@ pub(crate) fn solve( BlackBoxFuncCall::AND { lhs, rhs, output } => and(initial_witness, lhs, rhs, output), BlackBoxFuncCall::XOR { lhs, rhs, output } => xor(initial_witness, lhs, rhs, output), BlackBoxFuncCall::RANGE { input } => solve_range_opcode(initial_witness, input), - BlackBoxFuncCall::SHA256 { inputs, outputs } => solve_generic_256_hash_opcode( - initial_witness, - inputs, - None, - outputs, - sha256, - bb_func.get_black_box_func(), - ), - BlackBoxFuncCall::Blake2s { inputs, outputs } => solve_generic_256_hash_opcode( - initial_witness, - inputs, - None, - outputs, - blake2s, - bb_func.get_black_box_func(), - ), - BlackBoxFuncCall::Blake3 { inputs, outputs } => solve_generic_256_hash_opcode( - initial_witness, - inputs, - None, - outputs, - blake3, - bb_func.get_black_box_func(), - ), + BlackBoxFuncCall::SHA256 { inputs, outputs } => { + solve_generic_256_hash_opcode(initial_witness, inputs, None, outputs, sha256) + } + BlackBoxFuncCall::Blake2s { inputs, outputs } => { + solve_generic_256_hash_opcode(initial_witness, inputs, None, outputs, blake2s) + } + BlackBoxFuncCall::Blake3 { inputs, outputs } => { + solve_generic_256_hash_opcode(initial_witness, inputs, None, outputs, blake3) + } + BlackBoxFuncCall::Keccak256 { inputs, var_message_size, outputs } => { solve_generic_256_hash_opcode( initial_witness, @@ -102,18 +88,17 @@ pub(crate) fn solve( Some(var_message_size), outputs, keccak256, - bb_func.get_black_box_func(), ) } BlackBoxFuncCall::Keccakf1600 { inputs, outputs } => { let mut state = [0; 25]; - for (i, input) in inputs.iter().enumerate() { + for (it, input) in state.iter_mut().zip(inputs.as_ref()) { let witness = input.witness; let num_bits = input.num_bits as usize; assert_eq!(num_bits, 64); let witness_assignment = witness_to_value(initial_witness, witness)?; let lane = witness_assignment.try_to_u64(); - state[i] = lane.unwrap(); + *it = lane.unwrap(); } let output_state = keccakf1600(state)?; for (output_witness, value) in outputs.iter().zip(output_state.into_iter()) { @@ -132,7 +117,7 @@ pub(crate) fn solve( initial_witness, *public_key_x, *public_key_y, - signature, + signature.as_ref(), message, *output, ), @@ -153,7 +138,7 @@ pub(crate) fn solve( public_key_x, public_key_y, signature, - message, + message.as_ref(), *output, ), BlackBoxFuncCall::EcdsaSecp256r1 { @@ -167,7 +152,7 @@ pub(crate) fn solve( public_key_x, public_key_y, signature, - message, + message.as_ref(), *output, ), BlackBoxFuncCall::FixedBaseScalarMul { low, high, outputs } => { @@ -199,13 +184,7 @@ pub(crate) fn solve( bigint_solver.bigint_to_bytes(*input, outputs, initial_witness) } BlackBoxFuncCall::Sha256Compression { inputs, hash_values, outputs } => { - solve_sha_256_permutation_opcode( - initial_witness, - inputs, - hash_values, - outputs, - bb_func.get_black_box_func(), - ) + solve_sha_256_permutation_opcode(initial_witness, inputs, hash_values, outputs) } BlackBoxFuncCall::Poseidon2Permutation { inputs, outputs, len } => { solve_poseidon2_permutation_opcode(backend, initial_witness, inputs, outputs, *len) diff --git a/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs b/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs index 8f0df8378ad..b113c801251 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/signature/ecdsa.rs @@ -7,85 +7,42 @@ use acvm_blackbox_solver::{ecdsa_secp256k1_verify, ecdsa_secp256r1_verify}; use crate::{pwg::insert_value, OpcodeResolutionError}; -use super::to_u8_vec; +use super::{to_u8_array, to_u8_vec}; pub(crate) fn secp256k1_prehashed( initial_witness: &mut WitnessMap, - public_key_x_inputs: &[FunctionInput], - public_key_y_inputs: &[FunctionInput], - signature_inputs: &[FunctionInput], + public_key_x_inputs: &[FunctionInput; 32], + public_key_y_inputs: &[FunctionInput; 32], + signature_inputs: &[FunctionInput; 64], hashed_message_inputs: &[FunctionInput], output: Witness, ) -> Result<(), OpcodeResolutionError> { let hashed_message = to_u8_vec(initial_witness, hashed_message_inputs)?; - // These errors should never be emitted in practice as they would imply malformed ACIR generation. - let pub_key_x: [u8; 32] = - to_u8_vec(initial_witness, public_key_x_inputs)?.try_into().map_err(|_| { - OpcodeResolutionError::BlackBoxFunctionFailed( - acir::BlackBoxFunc::EcdsaSecp256k1, - format!("expected pubkey_x size 32 but received {}", public_key_x_inputs.len()), - ) - })?; - - let pub_key_y: [u8; 32] = - to_u8_vec(initial_witness, public_key_y_inputs)?.try_into().map_err(|_| { - OpcodeResolutionError::BlackBoxFunctionFailed( - acir::BlackBoxFunc::EcdsaSecp256k1, - format!("expected pubkey_y size 32 but received {}", public_key_y_inputs.len()), - ) - })?; - - let signature: [u8; 64] = - to_u8_vec(initial_witness, signature_inputs)?.try_into().map_err(|_| { - OpcodeResolutionError::BlackBoxFunctionFailed( - acir::BlackBoxFunc::EcdsaSecp256k1, - format!("expected signature size 64 but received {}", signature_inputs.len()), - ) - })?; + let pub_key_x: [u8; 32] = to_u8_array(initial_witness, public_key_x_inputs)?; + let pub_key_y: [u8; 32] = to_u8_array(initial_witness, public_key_y_inputs)?; + let signature: [u8; 64] = to_u8_array(initial_witness, signature_inputs)?; let is_valid = ecdsa_secp256k1_verify(&hashed_message, &pub_key_x, &pub_key_y, &signature)?; - insert_value(&output, FieldElement::from(is_valid), initial_witness)?; - Ok(()) + insert_value(&output, FieldElement::from(is_valid), initial_witness) } pub(crate) fn secp256r1_prehashed( initial_witness: &mut WitnessMap, - public_key_x_inputs: &[FunctionInput], - public_key_y_inputs: &[FunctionInput], - signature_inputs: &[FunctionInput], + public_key_x_inputs: &[FunctionInput; 32], + public_key_y_inputs: &[FunctionInput; 32], + signature_inputs: &[FunctionInput; 64], hashed_message_inputs: &[FunctionInput], output: Witness, ) -> Result<(), OpcodeResolutionError> { let hashed_message = to_u8_vec(initial_witness, hashed_message_inputs)?; - let pub_key_x: [u8; 32] = - to_u8_vec(initial_witness, public_key_x_inputs)?.try_into().map_err(|_| { - OpcodeResolutionError::BlackBoxFunctionFailed( - acir::BlackBoxFunc::EcdsaSecp256r1, - format!("expected pubkey_x size 32 but received {}", public_key_x_inputs.len()), - ) - })?; - - let pub_key_y: [u8; 32] = - to_u8_vec(initial_witness, public_key_y_inputs)?.try_into().map_err(|_| { - OpcodeResolutionError::BlackBoxFunctionFailed( - acir::BlackBoxFunc::EcdsaSecp256r1, - format!("expected pubkey_y size 32 but received {}", public_key_y_inputs.len()), - ) - })?; - - let signature: [u8; 64] = - to_u8_vec(initial_witness, signature_inputs)?.try_into().map_err(|_| { - OpcodeResolutionError::BlackBoxFunctionFailed( - acir::BlackBoxFunc::EcdsaSecp256r1, - format!("expected signature size 64 but received {}", signature_inputs.len()), - ) - })?; + let pub_key_x: [u8; 32] = to_u8_array(initial_witness, public_key_x_inputs)?; + let pub_key_y: [u8; 32] = to_u8_array(initial_witness, public_key_y_inputs)?; + let signature: [u8; 64] = to_u8_array(initial_witness, signature_inputs)?; let is_valid = ecdsa_secp256r1_verify(&hashed_message, &pub_key_x, &pub_key_y, &signature)?; - insert_value(&output, FieldElement::from(is_valid), initial_witness)?; - Ok(()) + insert_value(&output, FieldElement::from(is_valid), initial_witness) } diff --git a/acvm-repo/acvm/src/pwg/blackbox/signature/mod.rs b/acvm-repo/acvm/src/pwg/blackbox/signature/mod.rs index 0e28a63ff68..bd223ecd0c9 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/signature/mod.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/signature/mod.rs @@ -2,6 +2,21 @@ use acir::{circuit::opcodes::FunctionInput, native_types::WitnessMap}; use crate::pwg::{witness_to_value, OpcodeResolutionError}; +fn to_u8_array( + initial_witness: &WitnessMap, + inputs: &[FunctionInput; N], +) -> Result<[u8; N], OpcodeResolutionError> { + let mut result = [0; N]; + for (it, input) in result.iter_mut().zip(inputs) { + let witness_value_bytes = witness_to_value(initial_witness, input.witness)?.to_be_bytes(); + let byte = witness_value_bytes + .last() + .expect("Field element must be represented by non-zero amount of bytes"); + *it = *byte; + } + Ok(result) +} + fn to_u8_vec( initial_witness: &WitnessMap, inputs: &[FunctionInput], diff --git a/acvm-repo/acvm/src/pwg/blackbox/signature/schnorr.rs b/acvm-repo/acvm/src/pwg/blackbox/signature/schnorr.rs index 7f5381cee91..3d0216fa217 100644 --- a/acvm-repo/acvm/src/pwg/blackbox/signature/schnorr.rs +++ b/acvm-repo/acvm/src/pwg/blackbox/signature/schnorr.rs @@ -1,4 +1,4 @@ -use super::to_u8_vec; +use super::{to_u8_array, to_u8_vec}; use crate::{ pwg::{insert_value, witness_to_value, OpcodeResolutionError}, BlackBoxFunctionSolver, @@ -15,15 +15,14 @@ pub(crate) fn schnorr_verify( initial_witness: &mut WitnessMap, public_key_x: FunctionInput, public_key_y: FunctionInput, - signature: &[FunctionInput], + signature: &[FunctionInput; 64], message: &[FunctionInput], output: Witness, ) -> Result<(), OpcodeResolutionError> { let public_key_x: &FieldElement = witness_to_value(initial_witness, public_key_x.witness)?; let public_key_y: &FieldElement = witness_to_value(initial_witness, public_key_y.witness)?; - let signature = to_u8_vec(initial_witness, signature)?; - + let signature = to_u8_array(initial_witness, signature)?; let message = to_u8_vec(initial_witness, message)?; let valid_signature = diff --git a/acvm-repo/acvm/src/pwg/brillig.rs b/acvm-repo/acvm/src/pwg/brillig.rs index 81e752d5656..67faf7f5007 100644 --- a/acvm-repo/acvm/src/pwg/brillig.rs +++ b/acvm-repo/acvm/src/pwg/brillig.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use acir::{ - brillig::{ForeignCallParam, ForeignCallResult}, + brillig::{ForeignCallParam, ForeignCallResult, Opcode as BrilligOpcode}, circuit::{ brillig::{Brillig, BrilligInputs, BrilligOutputs}, opcodes::BlockId, @@ -11,7 +11,7 @@ use acir::{ FieldElement, }; use acvm_blackbox_solver::BlackBoxFunctionSolver; -use brillig_vm::{MemoryValue, VMStatus, VM}; +use brillig_vm::{FailureReason, MemoryValue, VMStatus, VM}; use crate::{pwg::OpcodeNotSolvable, OpcodeResolutionError}; @@ -46,9 +46,9 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { /// Assigns the zero value to all outputs of the given [`Brillig`] bytecode. pub(super) fn zero_out_brillig_outputs( initial_witness: &mut WitnessMap, - brillig: &Brillig, + outputs: &[BrilligOutputs], ) -> Result<(), OpcodeResolutionError> { - for output in &brillig.outputs { + for output in outputs { match output { BrilligOutputs::Simple(witness) => { insert_value(witness, FieldElement::zero(), initial_witness)?; @@ -63,6 +63,7 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { Ok(()) } + // TODO: Delete this old method once `Brillig` is deleted /// Constructs a solver for a Brillig block given the bytecode and initial /// witness. pub(crate) fn new( @@ -72,13 +73,45 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { bb_solver: &'b B, acir_index: usize, ) -> Result { + let vm = Self::setup_brillig_vm( + initial_witness, + memory, + &brillig.inputs, + &brillig.bytecode, + bb_solver, + )?; + Ok(Self { vm, acir_index }) + } + + /// Constructs a solver for a Brillig block given the bytecode and initial + /// witness. + pub(crate) fn new_call( + initial_witness: &WitnessMap, + memory: &HashMap, + inputs: &'b [BrilligInputs], + brillig_bytecode: &'b [BrilligOpcode], + bb_solver: &'b B, + acir_index: usize, + ) -> Result { + let vm = + Self::setup_brillig_vm(initial_witness, memory, inputs, brillig_bytecode, bb_solver)?; + Ok(Self { vm, acir_index }) + } + + fn setup_brillig_vm( + initial_witness: &WitnessMap, + memory: &HashMap, + inputs: &[BrilligInputs], + brillig_bytecode: &'b [BrilligOpcode], + bb_solver: &'b B, + ) -> Result, OpcodeResolutionError> { // Set input values let mut calldata: Vec = Vec::new(); // Each input represents an expression or array of expressions to evaluate. // Iterate over each input and evaluate the expression(s) associated with it. // Push the results into memory. // If a certain expression is not solvable, we stall the ACVM and do not proceed with Brillig VM execution. - for input in &brillig.inputs { + for input in inputs { match input { BrilligInputs::Single(expr) => match get_value(expr, initial_witness) { Ok(value) => calldata.push(value), @@ -118,8 +151,8 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { // Instantiate a Brillig VM given the solved calldata // along with the Brillig bytecode. - let vm = VM::new(calldata, &brillig.bytecode, vec![], bb_solver); - Ok(Self { vm, acir_index }) + let vm = VM::new(calldata, brillig_bytecode, vec![], bb_solver); + Ok(vm) } pub fn get_memory(&self) -> &[MemoryValue] { @@ -159,7 +192,31 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { match vm_status { VMStatus::Finished { .. } => Ok(BrilligSolverStatus::Finished), VMStatus::InProgress => Ok(BrilligSolverStatus::InProgress), - VMStatus::Failure { message, call_stack } => { + VMStatus::Failure { reason, call_stack } => { + let message = match reason { + FailureReason::RuntimeError { message } => Some(message), + FailureReason::Trap { revert_data_offset, revert_data_size } => { + // Since noir can only revert with strings currently, we can parse return data as a string + if revert_data_size == 0 { + None + } else { + let memory = self.vm.get_memory(); + let bytes = memory + [revert_data_offset..(revert_data_offset + revert_data_size)] + .iter() + .map(|memory_value| { + memory_value + .try_into() + .expect("Assert message character is not a byte") + }) + .collect(); + Some( + String::from_utf8(bytes) + .expect("Assert message is not valid UTF-8"), + ) + } + } + }; Err(OpcodeResolutionError::BrilligFunctionFailed { message, call_stack: call_stack @@ -180,13 +237,13 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { pub(crate) fn finalize( self, witness: &mut WitnessMap, - brillig: &Brillig, + outputs: &[BrilligOutputs], ) -> Result<(), OpcodeResolutionError> { // Finish the Brillig execution by writing the outputs to the witness map let vm_status = self.vm.get_status(); match vm_status { VMStatus::Finished { return_data_offset, return_data_size } => { - self.write_brillig_outputs(witness, return_data_offset, return_data_size, brillig)?; + self.write_brillig_outputs(witness, return_data_offset, return_data_size, outputs)?; Ok(()) } _ => panic!("Brillig VM has not completed execution"), @@ -198,12 +255,12 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { witness_map: &mut WitnessMap, return_data_offset: usize, return_data_size: usize, - brillig: &Brillig, + outputs: &[BrilligOutputs], ) -> Result<(), OpcodeResolutionError> { // Write VM execution results into the witness map let memory = self.vm.get_memory(); let mut current_ret_data_idx = return_data_offset; - for output in brillig.outputs.iter() { + for output in outputs.iter() { match output { BrilligOutputs::Simple(witness) => { insert_value(witness, memory[current_ret_data_idx].to_field(), witness_map)?; @@ -218,6 +275,7 @@ impl<'b, B: BlackBoxFunctionSolver> BrilligSolver<'b, B> { } } } + assert!( current_ret_data_idx == return_data_offset + return_data_size, "Brillig VM did not write the expected number of return values" diff --git a/acvm-repo/acvm/src/pwg/mod.rs b/acvm-repo/acvm/src/pwg/mod.rs index bb98eda2689..5362af961a7 100644 --- a/acvm-repo/acvm/src/pwg/mod.rs +++ b/acvm-repo/acvm/src/pwg/mod.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use acir::{ brillig::ForeignCallResult, - circuit::{opcodes::BlockId, Opcode, OpcodeLocation}, + circuit::{brillig::BrilligBytecode, opcodes::BlockId, Opcode, OpcodeLocation}, native_types::{Expression, Witness, WitnessMap}, BlackBoxFunc, FieldElement, }; @@ -122,8 +122,8 @@ pub enum OpcodeResolutionError { IndexOutOfBounds { opcode_location: ErrorLocation, index: u32, array_size: u32 }, #[error("Failed to solve blackbox function: {0}, reason: {1}")] BlackBoxFunctionFailed(BlackBoxFunc, String), - #[error("Failed to solve brillig function, reason: {message}")] - BrilligFunctionFailed { message: String, call_stack: Vec }, + #[error("Failed to solve brillig function{}", .message.as_ref().map(|m| format!(", reason: {}", m)).unwrap_or_default())] + BrilligFunctionFailed { message: Option, call_stack: Vec }, #[error("Attempted to call `main` with a `Call` opcode")] AcirMainCallAttempted { opcode_location: ErrorLocation }, #[error("{results_size:?} result values were provided for {outputs_size:?} call output witnesses, most likely due to bad ACIR codegen")] @@ -165,10 +165,18 @@ pub struct ACVM<'a, B: BlackBoxFunctionSolver> { /// Represents the outputs of all ACIR calls during an ACVM process /// List is appended onto by the caller upon reaching a [ACVMStatus::RequiresAcirCall] acir_call_results: Vec>, + + // Each unconstrained function referenced in the program + unconstrained_functions: &'a [BrilligBytecode], } impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { - pub fn new(backend: &'a B, opcodes: &'a [Opcode], initial_witness: WitnessMap) -> Self { + pub fn new( + backend: &'a B, + opcodes: &'a [Opcode], + initial_witness: WitnessMap, + unconstrained_functions: &'a [BrilligBytecode], + ) -> Self { let status = if opcodes.is_empty() { ACVMStatus::Solved } else { ACVMStatus::InProgress }; ACVM { status, @@ -181,6 +189,7 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { brillig_solver: None, acir_call_counter: 0, acir_call_results: Vec::default(), + unconstrained_functions, } } @@ -324,6 +333,10 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { Ok(Some(foreign_call)) => return self.wait_for_foreign_call(foreign_call), res => res.map(|_| ()), }, + Opcode::BrilligCall { .. } => match self.solve_brillig_call_opcode() { + Ok(Some(foreign_call)) => return self.wait_for_foreign_call(foreign_call), + res => res.map(|_| ()), + }, Opcode::Call { .. } => match self.solve_call_opcode() { Ok(Some(input_values)) => return self.wait_for_acir_call(input_values), res => res.map(|_| ()), @@ -378,7 +391,8 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { let witness = &mut self.witness_map; if is_predicate_false(witness, &brillig.predicate)? { - return BrilligSolver::::zero_out_brillig_outputs(witness, brillig).map(|_| None); + return BrilligSolver::::zero_out_brillig_outputs(witness, &brillig.outputs) + .map(|_| None); } // If we're resuming execution after resolving a foreign call then @@ -404,7 +418,51 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { } BrilligSolverStatus::Finished => { // Write execution outputs - solver.finalize(witness, brillig)?; + solver.finalize(witness, &brillig.outputs)?; + Ok(None) + } + } + } + + fn solve_brillig_call_opcode( + &mut self, + ) -> Result, OpcodeResolutionError> { + let Opcode::BrilligCall { id, inputs, outputs, predicate } = + &self.opcodes[self.instruction_pointer] + else { + unreachable!("Not executing a Brillig opcode"); + }; + + let witness = &mut self.witness_map; + if is_predicate_false(witness, predicate)? { + return BrilligSolver::::zero_out_brillig_outputs(witness, outputs).map(|_| None); + } + + // If we're resuming execution after resolving a foreign call then + // there will be a cached `BrilligSolver` to avoid recomputation. + let mut solver: BrilligSolver<'_, B> = match self.brillig_solver.take() { + Some(solver) => solver, + None => BrilligSolver::new_call( + witness, + &self.block_solvers, + inputs, + &self.unconstrained_functions[*id as usize].bytecode, + self.backend, + self.instruction_pointer, + )?, + }; + match solver.solve()? { + BrilligSolverStatus::ForeignCallWait(foreign_call) => { + // Cache the current state of the solver + self.brillig_solver = Some(solver); + Ok(Some(foreign_call)) + } + BrilligSolverStatus::InProgress => { + unreachable!("Brillig solver still in progress") + } + BrilligSolverStatus::Finished => { + // Write execution outputs + solver.finalize(witness, outputs)?; Ok(None) } } @@ -422,7 +480,8 @@ impl<'a, B: BlackBoxFunctionSolver> ACVM<'a, B> { }; if should_skip { - let resolution = BrilligSolver::::zero_out_brillig_outputs(witness, brillig); + let resolution = + BrilligSolver::::zero_out_brillig_outputs(witness, &brillig.outputs); return StepResult::Status(self.handle_opcode_resolution(resolution)); } diff --git a/acvm-repo/acvm/tests/solver.rs b/acvm-repo/acvm/tests/solver.rs index a708db5b030..f009e2c05b8 100644 --- a/acvm-repo/acvm/tests/solver.rs +++ b/acvm-repo/acvm/tests/solver.rs @@ -104,8 +104,9 @@ fn inversion_brillig_oracle_equivalence() { (Witness(2), FieldElement::from(3u128)), ]) .into(); - - let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments); + let unconstrained_functions = vec![]; + let mut acvm = + ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); // use the partial witness generation solver with our acir program let solver_status = acvm.solve(); @@ -241,8 +242,9 @@ fn double_inversion_brillig_oracle() { (Witness(9), FieldElement::from(10u128)), ]) .into(); - - let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments); + let unconstrained_functions = vec![]; + let mut acvm = + ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); // use the partial witness generation solver with our acir program let solver_status = acvm.solve(); @@ -370,8 +372,9 @@ fn oracle_dependent_execution() { let witness_assignments = BTreeMap::from([(w_x, FieldElement::from(2u128)), (w_y, FieldElement::from(2u128))]).into(); - - let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments); + let unconstrained_functions = vec![]; + let mut acvm = + ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); // use the partial witness generation solver with our acir program let solver_status = acvm.solve(); @@ -474,8 +477,9 @@ fn brillig_oracle_predicate() { (Witness(2), FieldElement::from(3u128)), ]) .into(); - - let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments); + let unconstrained_functions = vec![]; + let mut acvm = + ACVM::new(&StubbedBlackBoxSolver, &opcodes, witness_assignments, &unconstrained_functions); let solver_status = acvm.solve(); assert_eq!(solver_status, ACVMStatus::Solved, "should be fully solved"); @@ -509,7 +513,8 @@ fn unsatisfied_opcode_resolved() { values.insert(d, FieldElement::from(2_i128)); let opcodes = vec![Opcode::AssertZero(opcode_a)]; - let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, values); + let unconstrained_functions = vec![]; + let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions); let solver_status = acvm.solve(); assert_eq!( solver_status, @@ -549,7 +554,7 @@ fn unsatisfied_opcode_resolved_brillig() { let jmp_if_opcode = BrilligOpcode::JumpIf { condition: MemoryAddress::from(2), location: location_of_stop }; - let trap_opcode = BrilligOpcode::Trap; + let trap_opcode = BrilligOpcode::Trap { revert_data_offset: 0, revert_data_size: 0 }; let stop_opcode = BrilligOpcode::Stop { return_data_offset: 0, return_data_size: 0 }; let brillig_opcode = Opcode::Brillig(Brillig { @@ -591,13 +596,13 @@ fn unsatisfied_opcode_resolved_brillig() { values.insert(w_result, FieldElement::from(0_i128)); let opcodes = vec![brillig_opcode, Opcode::AssertZero(opcode_a)]; - - let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, values); + let unconstrained_functions = vec![]; + let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, values, &unconstrained_functions); let solver_status = acvm.solve(); assert_eq!( solver_status, ACVMStatus::Failure(OpcodeResolutionError::BrilligFunctionFailed { - message: "explicit trap hit in brillig".to_string(), + message: None, call_stack: vec![OpcodeLocation::Brillig { acir_index: 0, brillig_index: 3 }] }), "The first opcode is not satisfiable, expected an error indicating this" @@ -635,8 +640,9 @@ fn memory_operations() { }); let opcodes = vec![init, read_op, expression]; - - let mut acvm = ACVM::new(&StubbedBlackBoxSolver, &opcodes, initial_witness); + let unconstrained_functions = vec![]; + let mut acvm = + ACVM::new(&StubbedBlackBoxSolver, &opcodes, initial_witness, &unconstrained_functions); let solver_status = acvm.solve(); assert_eq!(solver_status, ACVMStatus::Solved); let witness_map = acvm.finalize(); diff --git a/acvm-repo/acvm_js/build.sh b/acvm-repo/acvm_js/build.sh index 4486a214c9c..58724dee02c 100755 --- a/acvm-repo/acvm_js/build.sh +++ b/acvm-repo/acvm_js/build.sh @@ -25,7 +25,6 @@ function run_if_available { require_command jq require_command cargo require_command wasm-bindgen -require_command wasm-opt self_path=$(dirname "$(readlink -f "$0")") pname=$(cargo read-manifest | jq -r '.name') @@ -49,5 +48,5 @@ BROWSER_WASM=${BROWSER_DIR}/${pname}_bg.wasm run_or_fail cargo build --lib --release --target $TARGET --package ${pname} run_or_fail wasm-bindgen $WASM_BINARY --out-dir $NODE_DIR --typescript --target nodejs run_or_fail wasm-bindgen $WASM_BINARY --out-dir $BROWSER_DIR --typescript --target web -run_or_fail wasm-opt $NODE_WASM -o $NODE_WASM -O -run_or_fail wasm-opt $BROWSER_WASM -o $BROWSER_WASM -O +run_if_available wasm-opt $NODE_WASM -o $NODE_WASM -O +run_if_available wasm-opt $BROWSER_WASM -o $BROWSER_WASM -O diff --git a/acvm-repo/acvm_js/src/execute.rs b/acvm-repo/acvm_js/src/execute.rs index c97b8ea1a66..2fab684467e 100644 --- a/acvm-repo/acvm_js/src/execute.rs +++ b/acvm-repo/acvm_js/src/execute.rs @@ -1,5 +1,6 @@ use std::{future::Future, pin::Pin}; +use acvm::acir::circuit::brillig::BrilligBytecode; use acvm::BlackBoxFunctionSolver; use acvm::{ acir::circuit::{Circuit, Program}, @@ -181,7 +182,12 @@ async fn execute_program_with_native_program_and_return( initial_witness: JsWitnessMap, foreign_call_executor: &ForeignCallHandler, ) -> Result { - let executor = ProgramExecutor::new(&program.functions, &solver.0, foreign_call_executor); + let executor = ProgramExecutor::new( + &program.functions, + &program.unconstrained_functions, + &solver.0, + foreign_call_executor, + ); let witness_stack = executor.execute(initial_witness.into()).await?; Ok(witness_stack) @@ -190,6 +196,8 @@ async fn execute_program_with_native_program_and_return( struct ProgramExecutor<'a, B: BlackBoxFunctionSolver> { functions: &'a [Circuit], + unconstrained_functions: &'a [BrilligBytecode], + blackbox_solver: &'a B, foreign_call_handler: &'a ForeignCallHandler, @@ -198,10 +206,16 @@ struct ProgramExecutor<'a, B: BlackBoxFunctionSolver> { impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { fn new( functions: &'a [Circuit], + unconstrained_functions: &'a [BrilligBytecode], blackbox_solver: &'a B, foreign_call_handler: &'a ForeignCallHandler, ) -> Self { - ProgramExecutor { functions, blackbox_solver, foreign_call_handler } + ProgramExecutor { + functions, + unconstrained_functions, + blackbox_solver, + foreign_call_handler, + } } async fn execute(&self, initial_witness: WitnessMap) -> Result { @@ -220,7 +234,12 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { witness_stack: &'a mut WitnessStack, ) -> Pin> + 'a>> { Box::pin(async { - let mut acvm = ACVM::new(self.blackbox_solver, &circuit.opcodes, initial_witness); + let mut acvm = ACVM::new( + self.blackbox_solver, + &circuit.opcodes, + initial_witness, + self.unconstrained_functions, + ); loop { let solver_status = acvm.solve(); @@ -231,7 +250,7 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { unreachable!("Execution should not stop while in `InProgress` state.") } ACVMStatus::Failure(error) => { - let (assert_message, call_stack) = match &error { + let (assert_message, call_stack): (Option<&str>, _) = match &error { OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Resolved(opcode_location), } @@ -242,12 +261,16 @@ impl<'a, B: BlackBoxFunctionSolver> ProgramExecutor<'a, B> { circuit.get_assert_message(*opcode_location), Some(vec![*opcode_location]), ), - OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => { + OpcodeResolutionError::BrilligFunctionFailed { + call_stack, + message, + } => { + let revert_message = message.as_ref().map(String::as_str); let failing_opcode = call_stack .last() .expect("Brillig error call stacks cannot be empty"); ( - circuit.get_assert_message(*failing_opcode), + revert_message.or(circuit.get_assert_message(*failing_opcode)), Some(call_stack.clone()), ) } diff --git a/acvm-repo/acvm_js/test/shared/addition.ts b/acvm-repo/acvm_js/test/shared/addition.ts index b56a4286878..820a415acf3 100644 --- a/acvm-repo/acvm_js/test/shared/addition.ts +++ b/acvm-repo/acvm_js/test/shared/addition.ts @@ -2,11 +2,11 @@ import { WitnessMap } from '@noir-lang/acvm_js'; // See `addition_circuit` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 144, 75, 14, 128, 32, 12, 68, 249, 120, 160, 150, 182, 208, 238, 188, 138, 68, - 184, 255, 17, 212, 200, 130, 196, 165, 188, 164, 153, 174, 94, 38, 227, 221, 203, 118, 159, 119, 95, 226, 200, 125, - 36, 252, 3, 253, 66, 87, 152, 92, 4, 153, 185, 149, 212, 144, 240, 128, 100, 85, 5, 88, 106, 86, 84, 20, 149, 51, 41, - 81, 83, 214, 98, 213, 10, 24, 50, 53, 236, 98, 212, 135, 44, 174, 235, 5, 143, 35, 12, 151, 159, 126, 55, 109, 28, - 231, 145, 47, 245, 105, 191, 143, 133, 1, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 144, 65, 14, 128, 32, 12, 4, 65, 124, 80, 75, 91, 104, 111, 126, 69, 34, 252, + 255, 9, 106, 228, 64, 162, 55, 153, 164, 217, 158, 38, 155, 245, 238, 97, 189, 206, 187, 55, 161, 231, 214, 19, 254, + 129, 126, 162, 107, 25, 92, 4, 137, 185, 230, 88, 145, 112, 135, 104, 69, 5, 88, 74, 82, 84, 20, 149, 35, 42, 81, 85, + 214, 108, 197, 50, 24, 50, 85, 108, 98, 212, 186, 44, 204, 235, 5, 183, 99, 233, 46, 63, 252, 110, 216, 56, 184, 15, + 78, 146, 74, 173, 20, 141, 1, 0, 0, ]); export const initialWitnessMap: WitnessMap = new Map([ diff --git a/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts b/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts index e074cf1ad38..722bae8e015 100644 --- a/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts +++ b/acvm-repo/acvm_js/test/shared/complex_foreign_call.ts @@ -2,13 +2,13 @@ import { WitnessMap } from '@noir-lang/acvm_js'; // See `complex_brillig_foreign_call` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 93, 10, 131, 48, 12, 78, 218, 233, 100, 111, 187, 193, 96, 59, 64, 231, 9, - 188, 139, 248, 166, 232, 163, 167, 23, 11, 126, 197, 24, 250, 34, 86, 208, 64, 72, 218, 252, 125, 36, 105, 153, 22, - 42, 60, 51, 116, 235, 217, 64, 103, 156, 37, 5, 191, 10, 210, 29, 163, 63, 167, 203, 229, 206, 194, 104, 110, 128, - 209, 158, 128, 49, 236, 195, 69, 231, 157, 114, 46, 73, 251, 103, 35, 239, 231, 225, 57, 243, 156, 227, 252, 132, 44, - 112, 79, 176, 125, 84, 223, 73, 248, 145, 152, 69, 149, 4, 107, 233, 114, 90, 119, 145, 85, 237, 151, 192, 89, 247, - 221, 208, 54, 163, 85, 174, 26, 234, 87, 232, 63, 101, 103, 21, 55, 169, 216, 73, 72, 249, 5, 197, 234, 132, 123, 179, - 35, 247, 155, 214, 246, 102, 20, 73, 204, 72, 168, 123, 191, 161, 25, 66, 136, 159, 187, 53, 5, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 84, 75, 10, 132, 48, 12, 77, 218, 209, 145, 217, 205, 13, 6, 198, 3, 84, 79, + 224, 93, 196, 157, 162, 75, 79, 47, 22, 124, 197, 16, 186, 17, 43, 104, 32, 36, 109, 126, 143, 36, 45, 211, 70, 133, + 103, 134, 110, 61, 27, 232, 140, 179, 164, 224, 215, 64, 186, 115, 84, 113, 186, 92, 238, 42, 140, 230, 1, 24, 237, 5, + 24, 195, 62, 220, 116, 222, 41, 231, 146, 180, 127, 54, 242, 126, 94, 158, 51, 207, 57, 206, 111, 200, 2, 247, 4, 219, + 79, 245, 157, 132, 31, 137, 89, 52, 73, 176, 214, 46, 167, 125, 23, 89, 213, 254, 8, 156, 237, 56, 76, 125, 55, 91, + 229, 170, 161, 254, 133, 94, 42, 59, 171, 184, 69, 197, 46, 66, 202, 47, 40, 86, 39, 220, 155, 3, 185, 191, 180, 183, + 55, 163, 72, 98, 70, 66, 221, 251, 40, 173, 255, 35, 68, 62, 61, 5, 0, 0, ]); export const initialWitnessMap: WitnessMap = new Map([ [1, '0x0000000000000000000000000000000000000000000000000000000000000001'], diff --git a/acvm-repo/acvm_js/test/shared/fixed_base_scalar_mul.ts b/acvm-repo/acvm_js/test/shared/fixed_base_scalar_mul.ts index 5aef521f231..97b5041121a 100644 --- a/acvm-repo/acvm_js/test/shared/fixed_base_scalar_mul.ts +++ b/acvm-repo/acvm_js/test/shared/fixed_base_scalar_mul.ts @@ -1,8 +1,8 @@ // See `fixed_base_scalar_mul_circuit` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 77, 138, 81, 10, 0, 48, 8, 66, 87, 219, 190, 118, 233, 29, 61, 43, 3, 5, 121, 34, - 207, 86, 231, 162, 198, 157, 124, 228, 71, 157, 220, 232, 161, 227, 226, 206, 214, 95, 221, 74, 0, 116, 58, 13, 182, - 105, 0, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 85, 138, 81, 10, 0, 48, 8, 66, 87, 219, 190, 118, 233, 29, 61, 35, 3, 19, 228, 137, + 60, 91, 149, 139, 26, 119, 242, 145, 31, 117, 114, 163, 135, 142, 139, 219, 91, 127, 117, 71, 2, 117, 84, 50, 98, 113, + 0, 0, 0, ]); export const initialWitnessMap = new Map([ [1, '0x0000000000000000000000000000000000000000000000000000000000000001'], diff --git a/acvm-repo/acvm_js/test/shared/foreign_call.ts b/acvm-repo/acvm_js/test/shared/foreign_call.ts index eb14cb2e9f1..0e3d77f62a9 100644 --- a/acvm-repo/acvm_js/test/shared/foreign_call.ts +++ b/acvm-repo/acvm_js/test/shared/foreign_call.ts @@ -2,10 +2,10 @@ import { WitnessMap } from '@noir-lang/acvm_js'; // See `simple_brillig_foreign_call` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 144, 61, 10, 192, 48, 8, 133, 53, 133, 82, 186, 245, 38, 233, 13, 122, 153, - 14, 93, 58, 132, 144, 227, 135, 252, 41, 56, 36, 46, 201, 7, 162, 168, 200, 123, 34, 52, 142, 28, 72, 245, 38, 106, 9, - 247, 30, 202, 118, 142, 27, 215, 221, 178, 82, 175, 33, 15, 133, 189, 163, 159, 57, 197, 252, 251, 195, 235, 188, 230, - 186, 16, 65, 255, 12, 239, 92, 131, 89, 149, 198, 77, 3, 10, 9, 119, 8, 198, 242, 152, 1, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 173, 144, 61, 10, 192, 32, 12, 133, 19, 11, 165, 116, 235, 77, 236, 13, 122, 153, + 14, 93, 58, 136, 120, 124, 241, 47, 129, 12, 42, 130, 126, 16, 18, 146, 16, 222, 11, 66, 225, 136, 129, 84, 111, 162, + 150, 112, 239, 161, 172, 231, 184, 113, 221, 45, 45, 245, 42, 242, 144, 216, 43, 250, 153, 83, 204, 191, 223, 189, + 198, 246, 92, 39, 60, 244, 63, 195, 59, 87, 99, 150, 165, 113, 83, 193, 0, 1, 19, 247, 29, 5, 160, 1, 0, 0, ]); export const initialWitnessMap: WitnessMap = new Map([ [1, '0x0000000000000000000000000000000000000000000000000000000000000005'], diff --git a/acvm-repo/acvm_js/test/shared/memory_op.ts b/acvm-repo/acvm_js/test/shared/memory_op.ts index 1d0e06b3c8a..a69ae443259 100644 --- a/acvm-repo/acvm_js/test/shared/memory_op.ts +++ b/acvm-repo/acvm_js/test/shared/memory_op.ts @@ -1,9 +1,9 @@ // See `memory_op_circuit` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 81, 201, 13, 0, 32, 8, 147, 195, 125, 112, 3, 247, 159, 74, 141, 60, 106, 226, - 79, 120, 216, 132, 180, 124, 154, 82, 168, 108, 212, 57, 2, 122, 129, 157, 201, 181, 150, 59, 186, 179, 189, 161, 101, - 251, 82, 176, 175, 196, 121, 89, 118, 185, 246, 91, 185, 26, 125, 187, 64, 80, 134, 29, 195, 31, 79, 24, 2, 250, 167, - 252, 27, 3, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 213, 81, 57, 14, 0, 32, 8, 147, 195, 255, 224, 15, 252, 255, 171, 212, 200, 208, + 129, 77, 24, 108, 66, 90, 150, 166, 20, 106, 23, 125, 143, 128, 62, 96, 103, 114, 173, 45, 198, 116, 182, 55, 140, + 106, 95, 74, 246, 149, 60, 47, 171, 46, 215, 126, 43, 87, 179, 111, 23, 8, 202, 176, 99, 248, 240, 9, 11, 137, 33, + 212, 110, 35, 3, 0, 0, ]); export const initialWitnessMap = new Map([ diff --git a/acvm-repo/acvm_js/test/shared/nested_acir_call.ts b/acvm-repo/acvm_js/test/shared/nested_acir_call.ts index 1b745ab6a79..4b73d01bb01 100644 --- a/acvm-repo/acvm_js/test/shared/nested_acir_call.ts +++ b/acvm-repo/acvm_js/test/shared/nested_acir_call.ts @@ -2,13 +2,13 @@ import { WitnessMap, StackItem, WitnessStack } from '@noir-lang/acvm_js'; // See `nested_acir_call_circuit` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 205, 146, 97, 10, 195, 32, 12, 133, 163, 66, 207, 147, 24, 173, 241, 223, 174, 50, - 153, 189, 255, 17, 214, 177, 148, 89, 17, 250, 99, 14, 246, 224, 97, 144, 16, 146, 143, 231, 224, 45, 167, 126, 105, - 217, 109, 118, 91, 248, 200, 168, 225, 248, 191, 106, 114, 208, 233, 104, 188, 233, 139, 223, 137, 108, 51, 139, 113, - 13, 161, 38, 95, 137, 233, 142, 62, 23, 137, 24, 98, 89, 133, 132, 162, 196, 135, 23, 230, 42, 65, 82, 46, 57, 97, - 166, 192, 149, 182, 152, 121, 211, 97, 110, 222, 94, 8, 13, 132, 182, 54, 48, 144, 235, 8, 254, 10, 22, 76, 132, 101, - 231, 237, 229, 23, 189, 213, 54, 119, 15, 83, 212, 199, 172, 175, 79, 113, 51, 48, 198, 253, 207, 84, 13, 204, 141, - 224, 21, 176, 147, 158, 66, 231, 43, 145, 6, 4, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 205, 146, 65, 10, 3, 33, 12, 69, 163, 46, 230, 58, 137, 209, 49, 238, 122, 149, 74, + 157, 251, 31, 161, 83, 154, 161, 86, 132, 89, 212, 194, 124, 248, 24, 36, 132, 228, 241, 29, 188, 229, 212, 47, 45, + 187, 205, 110, 11, 31, 25, 53, 28, 255, 103, 77, 14, 58, 29, 141, 55, 125, 241, 55, 145, 109, 102, 49, 174, 33, 212, + 228, 43, 49, 221, 209, 231, 34, 17, 67, 44, 171, 144, 80, 148, 248, 240, 194, 92, 37, 72, 202, 37, 39, 204, 20, 184, + 210, 22, 51, 111, 58, 204, 205, 219, 11, 161, 129, 208, 214, 6, 6, 114, 29, 193, 127, 193, 130, 137, 176, 236, 188, + 189, 252, 162, 183, 218, 230, 238, 97, 138, 250, 152, 245, 245, 87, 220, 12, 140, 113, 95, 153, 170, 129, 185, 17, 60, + 3, 54, 212, 19, 104, 145, 195, 151, 14, 4, 0, 0, ]); export const initialWitnessMap: WitnessMap = new Map([ diff --git a/acvm-repo/acvm_js/test/shared/pedersen.ts b/acvm-repo/acvm_js/test/shared/pedersen.ts index 00d207053d8..e8ddc893d87 100644 --- a/acvm-repo/acvm_js/test/shared/pedersen.ts +++ b/acvm-repo/acvm_js/test/shared/pedersen.ts @@ -1,7 +1,7 @@ // See `pedersen_circuit` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 93, 74, 7, 6, 0, 0, 8, 108, 209, 255, 63, 156, 54, 233, 56, 55, 17, 26, 18, 196, - 241, 169, 250, 178, 141, 167, 32, 159, 254, 234, 238, 255, 87, 112, 52, 63, 63, 101, 105, 0, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 93, 74, 9, 10, 0, 0, 4, 115, 149, 255, 127, 88, 8, 133, 213, 218, 137, 80, 144, 32, + 182, 79, 213, 151, 173, 61, 5, 121, 245, 91, 103, 255, 191, 3, 7, 16, 26, 112, 158, 113, 0, 0, 0, ]); export const initialWitnessMap = new Map([[1, '0x0000000000000000000000000000000000000000000000000000000000000001']]); diff --git a/acvm-repo/acvm_js/test/shared/schnorr_verify.ts b/acvm-repo/acvm_js/test/shared/schnorr_verify.ts index 14c32c615c8..a207aa12b2c 100644 --- a/acvm-repo/acvm_js/test/shared/schnorr_verify.ts +++ b/acvm-repo/acvm_js/test/shared/schnorr_verify.ts @@ -1,17 +1,17 @@ // See `schnorr_verify_circuit` integration test in `acir/tests/test_program_serialization.rs`. export const bytecode = Uint8Array.from([ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 77, 210, 7, 78, 2, 1, 20, 69, 81, 236, 189, 247, 222, 123, 239, 93, 177, 33, 34, - 238, 194, 253, 47, 193, 200, 147, 67, 194, 36, 147, 163, 33, 33, 228, 191, 219, 82, 168, 63, 63, 181, 183, 197, 223, - 177, 147, 191, 181, 183, 149, 69, 159, 183, 213, 222, 238, 218, 219, 206, 14, 118, 178, 139, 141, 183, 135, 189, 236, - 99, 63, 7, 56, 200, 33, 14, 115, 132, 163, 28, 227, 56, 39, 56, 201, 41, 78, 115, 134, 179, 156, 227, 60, 23, 184, - 200, 37, 46, 115, 133, 171, 92, 227, 58, 55, 184, 201, 45, 110, 115, 135, 187, 220, 227, 62, 15, 120, 200, 35, 30, - 243, 132, 167, 60, 227, 57, 47, 120, 201, 43, 94, 243, 134, 183, 188, 227, 61, 31, 248, 200, 39, 22, 249, 204, 151, - 166, 29, 243, 188, 250, 255, 141, 239, 44, 241, 131, 101, 126, 178, 194, 47, 86, 249, 237, 123, 171, 76, 127, 105, 47, - 189, 165, 181, 116, 150, 198, 26, 125, 245, 248, 45, 233, 41, 45, 165, 163, 52, 148, 126, 210, 78, 186, 73, 51, 233, - 37, 173, 164, 147, 52, 146, 62, 210, 70, 186, 72, 19, 233, 33, 45, 164, 131, 52, 144, 253, 151, 11, 245, 221, 179, - 121, 246, 206, 214, 217, 57, 27, 103, 223, 109, 187, 238, 218, 115, 223, 142, 135, 246, 59, 182, 219, 169, 189, 206, - 237, 116, 105, 159, 107, 187, 220, 218, 227, 222, 14, 143, 238, 95, 116, 247, 23, 119, 126, 115, 223, 146, 187, 150, - 221, 179, 226, 142, 141, 155, 53, 238, 86, 104, 186, 231, 255, 243, 7, 100, 141, 232, 192, 233, 3, 0, 0, + 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 85, 210, 85, 78, 67, 81, 24, 133, 209, 226, 238, 238, 238, 238, 238, 165, 148, 82, + 102, 193, 252, 135, 64, 232, 78, 87, 147, 114, 147, 147, 5, 47, 132, 252, 251, 107, 41, 212, 191, 159, 218, 107, 241, + 115, 236, 228, 111, 237, 181, 178, 173, 246, 186, 107, 175, 157, 29, 236, 100, 23, 27, 175, 135, 189, 236, 99, 63, 7, + 56, 200, 33, 14, 115, 132, 163, 28, 227, 56, 39, 56, 201, 41, 78, 115, 134, 179, 156, 227, 60, 23, 184, 200, 37, 46, + 115, 133, 171, 92, 227, 58, 55, 184, 201, 45, 110, 115, 135, 187, 220, 227, 62, 15, 120, 200, 35, 30, 243, 132, 167, + 60, 227, 57, 47, 120, 201, 43, 94, 243, 134, 183, 188, 227, 61, 31, 248, 200, 39, 62, 243, 133, 175, 77, 59, 230, 123, + 243, 123, 145, 239, 44, 241, 131, 101, 126, 178, 194, 47, 86, 249, 237, 239, 86, 153, 238, 210, 92, 122, 75, 107, 233, + 44, 141, 53, 250, 234, 241, 191, 164, 167, 180, 148, 142, 210, 80, 250, 73, 59, 233, 38, 205, 164, 151, 180, 146, 78, + 210, 72, 250, 72, 27, 233, 34, 77, 164, 135, 180, 144, 14, 210, 64, 246, 95, 46, 212, 119, 207, 230, 217, 59, 91, 103, + 231, 108, 156, 125, 183, 237, 186, 107, 207, 125, 59, 30, 218, 239, 216, 110, 167, 246, 58, 183, 211, 165, 125, 174, + 237, 114, 107, 143, 123, 59, 60, 186, 255, 179, 187, 191, 186, 115, 209, 125, 75, 238, 90, 118, 207, 138, 59, 54, 110, + 214, 184, 91, 161, 233, 158, 255, 190, 63, 165, 188, 93, 151, 233, 3, 0, 0, ]); export const initialWitnessMap = new Map([ diff --git a/acvm-repo/blackbox_solver/src/curve_specific_solver.rs b/acvm-repo/blackbox_solver/src/curve_specific_solver.rs index f0ab4561229..fab67467d9a 100644 --- a/acvm-repo/blackbox_solver/src/curve_specific_solver.rs +++ b/acvm-repo/blackbox_solver/src/curve_specific_solver.rs @@ -11,7 +11,7 @@ pub trait BlackBoxFunctionSolver { &self, public_key_x: &FieldElement, public_key_y: &FieldElement, - signature: &[u8], + signature: &[u8; 64], message: &[u8], ) -> Result; fn pedersen_commitment( @@ -59,7 +59,7 @@ impl BlackBoxFunctionSolver for StubbedBlackBoxSolver { &self, _public_key_x: &FieldElement, _public_key_y: &FieldElement, - _signature: &[u8], + _signature: &[u8; 64], _message: &[u8], ) -> Result { Err(Self::fail(BlackBoxFunc::SchnorrVerify)) diff --git a/acvm-repo/bn254_blackbox_solver/src/lib.rs b/acvm-repo/bn254_blackbox_solver/src/lib.rs index 231594170e3..f7c303888e8 100644 --- a/acvm-repo/bn254_blackbox_solver/src/lib.rs +++ b/acvm-repo/bn254_blackbox_solver/src/lib.rs @@ -52,7 +52,7 @@ impl BlackBoxFunctionSolver for Bn254BlackBoxSolver { &self, public_key_x: &FieldElement, public_key_y: &FieldElement, - signature: &[u8], + signature: &[u8; 64], message: &[u8], ) -> Result { let pub_key_bytes: Vec = diff --git a/acvm-repo/brillig/src/opcodes.rs b/acvm-repo/brillig/src/opcodes.rs index d1345351986..468fd88db45 100644 --- a/acvm-repo/brillig/src/opcodes.rs +++ b/acvm-repo/brillig/src/opcodes.rs @@ -177,8 +177,11 @@ pub enum BrilligOpcode { source: MemoryAddress, }, BlackBox(BlackBoxOp), - /// Used to denote execution failure - Trap, + /// Used to denote execution failure, returning data after the offset + Trap { + revert_data_offset: usize, + revert_data_size: usize, + }, /// Stop execution, returning data after the offset Stop { return_data_offset: usize, diff --git a/acvm-repo/brillig_vm/src/black_box.rs b/acvm-repo/brillig_vm/src/black_box.rs index 73981fb0625..2857e27f1af 100644 --- a/acvm-repo/brillig_vm/src/black_box.rs +++ b/acvm-repo/brillig_vm/src/black_box.rs @@ -127,7 +127,8 @@ pub(crate) fn evaluate_black_box( let public_key_x = memory.read(*public_key_x).try_into().unwrap(); let public_key_y = memory.read(*public_key_y).try_into().unwrap(); let message: Vec = to_u8_vec(read_heap_vector(memory, message)); - let signature: Vec = to_u8_vec(read_heap_vector(memory, signature)); + let signature: [u8; 64] = + to_u8_vec(read_heap_vector(memory, signature)).try_into().unwrap(); let verified = solver.schnorr_verify(&public_key_x, &public_key_y, &signature, &message)?; memory.write(*result, verified.into()); diff --git a/acvm-repo/brillig_vm/src/lib.rs b/acvm-repo/brillig_vm/src/lib.rs index 26d5da67576..d83870dc410 100644 --- a/acvm-repo/brillig_vm/src/lib.rs +++ b/acvm-repo/brillig_vm/src/lib.rs @@ -32,6 +32,12 @@ mod memory; /// The error call stack contains the opcode indexes of the call stack at the time of failure, plus the index of the opcode that failed. pub type ErrorCallStack = Vec; +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum FailureReason { + Trap { revert_data_offset: usize, revert_data_size: usize }, + RuntimeError { message: String }, +} + #[derive(Debug, PartialEq, Eq, Clone)] pub enum VMStatus { Finished { @@ -40,7 +46,7 @@ pub enum VMStatus { }, InProgress, Failure { - message: String, + reason: FailureReason, call_stack: ErrorCallStack, }, /// The VM process is not solvable as a [foreign call][Opcode::ForeignCall] has been @@ -138,13 +144,28 @@ impl<'a, B: BlackBoxFunctionSolver> VM<'a, B> { self.status(VMStatus::InProgress); } + fn get_error_stack(&self) -> Vec { + let mut error_stack: Vec<_> = self.call_stack.clone(); + error_stack.push(self.program_counter); + error_stack + } + /// Sets the current status of the VM to `fail`. /// Indicating that the VM encountered a `Trap` Opcode /// or an invalid state. + fn trap(&mut self, revert_data_offset: usize, revert_data_size: usize) -> VMStatus { + self.status(VMStatus::Failure { + call_stack: self.get_error_stack(), + reason: FailureReason::Trap { revert_data_offset, revert_data_size }, + }); + self.status.clone() + } + fn fail(&mut self, message: String) -> VMStatus { - let mut error_stack: Vec<_> = self.call_stack.clone(); - error_stack.push(self.program_counter); - self.status(VMStatus::Failure { call_stack: error_stack, message }); + self.status(VMStatus::Failure { + call_stack: self.get_error_stack(), + reason: FailureReason::RuntimeError { message }, + }); self.status.clone() } @@ -281,7 +302,9 @@ impl<'a, B: BlackBoxFunctionSolver> VM<'a, B> { } self.increment_program_counter() } - Opcode::Trap => self.fail("explicit trap hit in brillig".to_string()), + Opcode::Trap { revert_data_offset, revert_data_size } => { + self.trap(*revert_data_offset, *revert_data_size) + } Opcode::Stop { return_data_offset, return_data_size } => { self.finish(*return_data_offset, *return_data_size) } @@ -684,7 +707,7 @@ mod tests { let jump_opcode = Opcode::Jump { location: 3 }; - let trap_opcode = Opcode::Trap; + let trap_opcode = Opcode::Trap { revert_data_offset: 0, revert_data_size: 0 }; let not_equal_cmp_opcode = Opcode::BinaryFieldOp { op: BinaryFieldOp::Equals, @@ -731,7 +754,7 @@ mod tests { assert_eq!( status, VMStatus::Failure { - message: "explicit trap hit in brillig".to_string(), + reason: FailureReason::Trap { revert_data_offset: 0, revert_data_size: 0 }, call_stack: vec![2] } ); diff --git a/compiler/noirc_driver/src/contract.rs b/compiler/noirc_driver/src/contract.rs index d6c3dc6205d..a33a9b809d3 100644 --- a/compiler/noirc_driver/src/contract.rs +++ b/compiler/noirc_driver/src/contract.rs @@ -53,7 +53,7 @@ pub struct ContractFunction { )] pub bytecode: Program, - pub debug: Vec, + pub debug: DebugInfo, /// Names of the functions in the program. These are used for more informative debugging and benchmarking. pub names: Vec, diff --git a/compiler/noirc_driver/src/lib.rs b/compiler/noirc_driver/src/lib.rs index 8a554879e9f..6fe44780484 100644 --- a/compiler/noirc_driver/src/lib.rs +++ b/compiler/noirc_driver/src/lib.rs @@ -430,8 +430,7 @@ fn compile_contract_inner( } if errors.is_empty() { - let debug_infos: Vec<_> = - functions.iter().flat_map(|function| function.debug.clone()).collect(); + let debug_infos: Vec<_> = functions.iter().map(|function| function.debug.clone()).collect(); let file_map = filter_relevant_files(&debug_infos, &context.file_manager); let out_structs = contract @@ -548,8 +547,13 @@ pub fn compile_no_check( Ok(CompiledProgram { hash, + // TODO(https://github.com/noir-lang/noir/issues/4428) program, - debug, + // TODO(https://github.com/noir-lang/noir/issues/4428) + // Debug info is only relevant for errors at execution time which is not yet supported + // The CompileProgram `debug` field is used in multiple places and is better + // left to be updated once execution of multiple ACIR functions is enabled + debug: debug[0].clone(), abi, file_map, noir_version: NOIR_ARTIFACT_VERSION_STRING.to_string(), diff --git a/compiler/noirc_driver/src/program.rs b/compiler/noirc_driver/src/program.rs index ed7ddb29f59..9ffd2d70dda 100644 --- a/compiler/noirc_driver/src/program.rs +++ b/compiler/noirc_driver/src/program.rs @@ -24,7 +24,7 @@ pub struct CompiledProgram { )] pub program: Program, pub abi: noirc_abi::Abi, - pub debug: Vec, + pub debug: DebugInfo, pub file_map: BTreeMap, pub warnings: Vec, /// Names of the functions in the program. These are used for more informative debugging and benchmarking. diff --git a/compiler/noirc_errors/src/debug_info.rs b/compiler/noirc_errors/src/debug_info.rs index 54e2521e413..09117bdc3b7 100644 --- a/compiler/noirc_errors/src/debug_info.rs +++ b/compiler/noirc_errors/src/debug_info.rs @@ -46,49 +46,6 @@ pub type DebugVariables = BTreeMap; pub type DebugFunctions = BTreeMap; pub type DebugTypes = BTreeMap; -#[derive(Default, Debug, Clone, Deserialize, Serialize)] -pub struct ProgramDebugInfo { - pub debug_infos: Vec, -} - -impl ProgramDebugInfo { - pub fn serialize_compressed_base64_json( - debug_info: &ProgramDebugInfo, - s: S, - ) -> Result - where - S: Serializer, - { - let json_str = serde_json::to_string(debug_info).map_err(S::Error::custom)?; - - let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default()); - encoder.write_all(json_str.as_bytes()).map_err(S::Error::custom)?; - let compressed_data = encoder.finish().map_err(S::Error::custom)?; - - let encoded_b64 = base64::prelude::BASE64_STANDARD.encode(compressed_data); - s.serialize_str(&encoded_b64) - } - - pub fn deserialize_compressed_base64_json<'de, D>( - deserializer: D, - ) -> Result - where - D: Deserializer<'de>, - { - let encoded_b64: String = Deserialize::deserialize(deserializer)?; - - let compressed_data = - base64::prelude::BASE64_STANDARD.decode(encoded_b64).map_err(D::Error::custom)?; - - let mut decoder = DeflateDecoder::new(&compressed_data[..]); - let mut decompressed_data = Vec::new(); - decoder.read_to_end(&mut decompressed_data).map_err(D::Error::custom)?; - - let json_str = String::from_utf8(decompressed_data).map_err(D::Error::custom)?; - serde_json::from_str(&json_str).map_err(D::Error::custom) - } -} - #[serde_as] #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct DebugInfo { @@ -173,4 +130,40 @@ impl DebugInfo { counted_opcodes } + + pub fn serialize_compressed_base64_json( + debug_info: &DebugInfo, + s: S, + ) -> Result + where + S: Serializer, + { + let json_str = serde_json::to_string(debug_info).map_err(S::Error::custom)?; + + let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(json_str.as_bytes()).map_err(S::Error::custom)?; + let compressed_data = encoder.finish().map_err(S::Error::custom)?; + + let encoded_b64 = base64::prelude::BASE64_STANDARD.encode(compressed_data); + s.serialize_str(&encoded_b64) + } + + pub fn deserialize_compressed_base64_json<'de, D>( + deserializer: D, + ) -> Result + where + D: Deserializer<'de>, + { + let encoded_b64: String = Deserialize::deserialize(deserializer)?; + + let compressed_data = + base64::prelude::BASE64_STANDARD.decode(encoded_b64).map_err(D::Error::custom)?; + + let mut decoder = DeflateDecoder::new(&compressed_data[..]); + let mut decompressed_data = Vec::new(); + decoder.read_to_end(&mut decompressed_data).map_err(D::Error::custom)?; + + let json_str = String::from_utf8(decompressed_data).map_err(D::Error::custom)?; + serde_json::from_str(&json_str).map_err(D::Error::custom) + } } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index cf2501ab1c0..22407fc5695 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -5,7 +5,7 @@ use crate::brillig::brillig_ir::{ BrilligBinaryOp, BrilligContext, BRILLIG_MEMORY_ADDRESSING_BIT_SIZE, }; use crate::ssa::ir::dfg::CallStack; -use crate::ssa::ir::instruction::ConstrainError; +use crate::ssa::ir::instruction::{ConstrainError, UserDefinedConstrainError}; use crate::ssa::ir::{ basic_block::{BasicBlock, BasicBlockId}, dfg::DataFlowGraph, @@ -248,10 +248,15 @@ impl<'block> BrilligBlock<'block> { self.convert_ssa_binary(binary, dfg, result_var); } Instruction::Constrain(lhs, rhs, assert_message) => { - let assert_message = if let Some(error) = assert_message { + let (has_revert_data, static_assert_message) = if let Some(error) = assert_message { match error.as_ref() { - ConstrainError::Static(string) => Some(string.clone()), - ConstrainError::Dynamic(call_instruction) => { + ConstrainError::Intrinsic(string) => (false, Some(string.clone())), + ConstrainError::UserDefined(UserDefinedConstrainError::Static(string)) => { + (true, Some(string.clone())) + } + ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( + call_instruction, + )) => { let Instruction::Call { func, arguments } = call_instruction else { unreachable!("expected a call instruction") }; @@ -264,11 +269,11 @@ impl<'block> BrilligBlock<'block> { // Dynamic assert messages are handled in the generated function call. // We then don't need to attach one to the constrain instruction. - None + (false, None) } } } else { - None + (false, None) }; let condition = SingleAddrVariable { @@ -281,8 +286,12 @@ impl<'block> BrilligBlock<'block> { dfg, condition, ); - - self.brillig_context.constrain_instruction(condition, assert_message); + if has_revert_data { + self.brillig_context + .codegen_constrain_with_revert_data(condition, static_assert_message); + } else { + self.brillig_context.codegen_constrain(condition, static_assert_message); + } self.brillig_context.deallocate_single_addr(condition); } Instruction::Allocate => { @@ -670,7 +679,7 @@ impl<'block> BrilligBlock<'block> { BrilligBinaryOp::LessThanEquals, ); - self.brillig_context.constrain_instruction(condition, assert_message.clone()); + self.brillig_context.codegen_constrain(condition, assert_message.clone()); self.brillig_context.deallocate_single_addr(condition); self.brillig_context.deallocate_single_addr(left); self.brillig_context.deallocate_single_addr(right); @@ -802,7 +811,7 @@ impl<'block> BrilligBlock<'block> { ); self.brillig_context - .constrain_instruction(condition, Some("Array index out of bounds".to_owned())); + .codegen_constrain(condition, Some("Array index out of bounds".to_owned())); if should_deallocate_size { self.brillig_context.deallocate_single_addr(size_as_register); @@ -1503,10 +1512,8 @@ impl<'block> BrilligBlock<'block> { condition, BrilligBinaryOp::LessThanEquals, ); - self.brillig_context.constrain_instruction( - condition, - Some("attempt to add with overflow".to_string()), - ); + self.brillig_context + .codegen_constrain(condition, Some("attempt to add with overflow".to_string())); self.brillig_context.deallocate_single_addr(condition); } (BrilligBinaryOp::Sub, false) => { @@ -1519,7 +1526,7 @@ impl<'block> BrilligBlock<'block> { condition, BrilligBinaryOp::LessThanEquals, ); - self.brillig_context.constrain_instruction( + self.brillig_context.codegen_constrain( condition, Some("attempt to subtract with overflow".to_string()), ); @@ -1549,7 +1556,7 @@ impl<'block> BrilligBlock<'block> { BrilligBinaryOp::UnsignedDiv, ); ctx.binary_instruction(division, left, condition, BrilligBinaryOp::Equals); - ctx.constrain_instruction( + ctx.codegen_constrain( condition, Some("attempt to multiply with overflow".to_string()), ); @@ -1778,7 +1785,7 @@ pub(crate) fn type_of_binary_operation(lhs_type: &Type, rhs_type: &Type) -> Type (Type::Numeric(lhs_type), Type::Numeric(rhs_type)) => { assert_eq!( lhs_type, rhs_type, - "lhs and rhs types in a binary operation are always the same" + "lhs and rhs types in a binary operation are always the same but got {lhs_type} and {rhs_type}" ); Type::Numeric(*lhs_type) } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs index e5c731be679..29210dc6f99 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir.rs @@ -140,7 +140,7 @@ pub(crate) mod tests { &self, _public_key_x: &FieldElement, _public_key_y: &FieldElement, - _signature: &[u8], + _signature: &[u8; 64], _message: &[u8], ) -> Result { Ok(true) @@ -262,7 +262,7 @@ pub(crate) mod tests { // uses unresolved jumps which requires a block to be constructed in SSA and // we don't need this for Brillig IR tests context.push_opcode(BrilligOpcode::JumpIf { condition: r_equality, location: 8 }); - context.push_opcode(BrilligOpcode::Trap); + context.push_opcode(BrilligOpcode::Trap { revert_data_offset: 0, revert_data_size: 0 }); context.stop_instruction(); diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs index 8ce15ba4e73..8bd1bfda78f 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/artifact.rs @@ -17,7 +17,7 @@ pub(crate) enum BrilligParameter { /// The result of compiling and linking brillig artifacts. /// This is ready to run bytecode with attached metadata. -#[derive(Debug)] +#[derive(Debug, Default)] pub(crate) struct GeneratedBrillig { pub(crate) byte_code: Vec, pub(crate) locations: BTreeMap, diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs index 116eaa5103f..f8f39f03df4 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/codegen_control_flow.rs @@ -138,4 +138,48 @@ impl BrilligContext { self.enter_section(end_section); } + + /// Emits brillig bytecode to jump to a trap condition if `condition` + /// is false. The trap will include the given message as revert data. + pub(crate) fn codegen_constrain_with_revert_data( + &mut self, + condition: SingleAddrVariable, + assert_message: Option, + ) { + assert!(condition.bit_size == 1); + + self.codegen_if_not(condition.address, |ctx| { + let (revert_data_offset, revert_data_size) = + if let Some(assert_message) = assert_message { + let bytes = assert_message.as_bytes(); + for (i, byte) in bytes.iter().enumerate() { + ctx.const_instruction( + SingleAddrVariable::new(MemoryAddress(i), 8), + (*byte as usize).into(), + ); + } + (0, bytes.len()) + } else { + (0, 0) + }; + ctx.trap_instruction(revert_data_offset, revert_data_size); + }); + } + + /// Emits brillig bytecode to jump to a trap condition if `condition` + /// is false. + pub(crate) fn codegen_constrain( + &mut self, + condition: SingleAddrVariable, + assert_message: Option, + ) { + assert!(condition.bit_size == 1); + + self.codegen_if_not(condition.address, |ctx| { + ctx.trap_instruction(0, 0); + if let Some(assert_message) = assert_message { + ctx.obj.add_assert_message_to_last_opcode(assert_message); + } + }); + } } diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs index 4ca1144b6a4..41a6d1873e4 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/debug_show.rs @@ -113,10 +113,14 @@ impl DebugShow { DebugShow { enable_debug_trace } } - /// Emits brillig bytecode to jump to a trap condition if `condition` - /// is false. - pub(crate) fn constrain_instruction(&self, condition: MemoryAddress) { - debug_println!(self.enable_debug_trace, " ASSERT {} != 0", condition); + /// Emits a `trap` instruction. + pub(crate) fn trap_instruction(&self, revert_data_offset: usize, revert_data_size: usize) { + debug_println!( + self.enable_debug_trace, + " TRAP {}..{}", + revert_data_offset, + revert_data_offset + revert_data_size + ); } /// Emits a `mov` instruction. diff --git a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs index f305eb81b01..901ccc58036 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_ir/instructions.rs @@ -215,29 +215,6 @@ impl BrilligContext { ); } - /// Emits brillig bytecode to jump to a trap condition if `condition` - /// is false. - pub(crate) fn constrain_instruction( - &mut self, - condition: SingleAddrVariable, - assert_message: Option, - ) { - self.debug_show.constrain_instruction(condition.address); - - assert!(condition.bit_size == 1); - - let (next_section, next_label) = self.reserve_next_section_label(); - self.add_unresolved_jump( - BrilligOpcode::JumpIf { condition: condition.address, location: 0 }, - next_label, - ); - self.push_opcode(BrilligOpcode::Trap); - if let Some(assert_message) = assert_message { - self.obj.add_assert_message_to_last_opcode(assert_message); - } - self.enter_section(next_section); - } - /// Adds a unresolved `Jump` to the bytecode. fn add_unresolved_jump( &mut self, @@ -488,6 +465,12 @@ impl BrilligContext { offset, }); } + + pub(super) fn trap_instruction(&mut self, revert_data_offset: usize, revert_data_size: usize) { + self.debug_show.trap_instruction(revert_data_offset, revert_data_size); + + self.push_opcode(BrilligOpcode::Trap { revert_data_offset, revert_data_size }); + } } /// Type to encapsulate the binary operation types in Brillig diff --git a/compiler/noirc_evaluator/src/ssa.rs b/compiler/noirc_evaluator/src/ssa.rs index ce4a9bbe9f1..760340f1a88 100644 --- a/compiler/noirc_evaluator/src/ssa.rs +++ b/compiler/noirc_evaluator/src/ssa.rs @@ -14,7 +14,9 @@ use crate::{ errors::{RuntimeError, SsaReport}, }; use acvm::acir::{ - circuit::{Circuit, ExpressionWidth, Program as AcirProgram, PublicInputs}, + circuit::{ + brillig::BrilligBytecode, Circuit, ExpressionWidth, Program as AcirProgram, PublicInputs, + }, native_types::Witness, }; @@ -35,14 +37,16 @@ pub mod ssa_gen; /// Optimize the given program by converting it into SSA /// form and performing optimizations there. When finished, -/// convert the final SSA into ACIR and return it. +/// convert the final SSA into an ACIR program and return it. +/// An ACIR program is made up of both ACIR functions +/// and Brillig functions for unconstrained execution. pub(crate) fn optimize_into_acir( program: Program, print_passes: bool, print_brillig_trace: bool, force_brillig_output: bool, print_timings: bool, -) -> Result, RuntimeError> { +) -> Result<(Vec, Vec), RuntimeError> { let abi_distinctness = program.return_distinctness; let ssa_gen_span = span!(Level::TRACE, "ssa_generation"); @@ -99,6 +103,18 @@ pub struct SsaProgramArtifact { } impl SsaProgramArtifact { + fn new(unconstrained_functions: Vec) -> Self { + let program = AcirProgram { functions: Vec::default(), unconstrained_functions }; + Self { + program, + debug: Vec::default(), + warnings: Vec::default(), + main_input_witnesses: Vec::default(), + main_return_witnesses: Vec::default(), + names: Vec::default(), + } + } + fn add_circuit(&mut self, mut circuit_artifact: SsaCircuitArtifact, is_main: bool) { self.program.functions.push(circuit_artifact.circuit); self.debug.push(circuit_artifact.debug_info); @@ -130,7 +146,7 @@ pub fn create_program( let func_sigs = program.function_signatures.clone(); let recursive = program.recursive; - let generated_acirs = optimize_into_acir( + let (generated_acirs, generated_brillig) = optimize_into_acir( program, enable_ssa_logging, enable_brillig_logging, @@ -143,7 +159,7 @@ pub fn create_program( "The generated ACIRs should match the supplied function signatures" ); - let mut program_artifact = SsaProgramArtifact::default(); + let mut program_artifact = SsaProgramArtifact::new(generated_brillig); // For setting up the ABI we need separately specify main's input and return witnesses let mut is_main = true; for (acir, func_sig) in generated_acirs.into_iter().zip(func_sigs) { diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs index 775571f4a41..3294eb16695 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/acir_variable.rs @@ -1463,6 +1463,7 @@ impl AcirContext { id } + // TODO: Delete this method once we remove the `Brillig` opcode pub(crate) fn brillig( &mut self, predicate: AcirVar, @@ -1553,6 +1554,105 @@ impl AcirContext { Ok(outputs_var) } + #[allow(clippy::too_many_arguments)] + pub(crate) fn brillig_call( + &mut self, + predicate: AcirVar, + generated_brillig: &GeneratedBrillig, + inputs: Vec, + outputs: Vec, + attempt_execution: bool, + unsafe_return_values: bool, + brillig_function_index: u32, + ) -> Result, RuntimeError> { + let brillig_inputs = try_vecmap(inputs, |i| -> Result<_, InternalError> { + match i { + AcirValue::Var(var, _) => Ok(BrilligInputs::Single(self.var_to_expression(var)?)), + AcirValue::Array(vars) => { + let mut var_expressions: Vec = Vec::new(); + for var in vars { + self.brillig_array_input(&mut var_expressions, var)?; + } + Ok(BrilligInputs::Array(var_expressions)) + } + AcirValue::DynamicArray(AcirDynamicArray { block_id, .. }) => { + Ok(BrilligInputs::MemoryArray(block_id)) + } + } + })?; + + // Optimistically try executing the brillig now, if we can complete execution they just return the results. + // This is a temporary measure pending SSA optimizations being applied to Brillig which would remove constant-input opcodes (See #2066) + // + // We do _not_ want to do this in the situation where the `main` function is unconstrained, as if execution succeeds + // the entire program will be replaced with witness constraints to its outputs. + if attempt_execution { + if let Some(brillig_outputs) = + self.execute_brillig(&generated_brillig.byte_code, &brillig_inputs, &outputs) + { + return Ok(brillig_outputs); + } + } + + // Otherwise we must generate ACIR for it and execute at runtime. + let mut brillig_outputs = Vec::new(); + let outputs_var = vecmap(outputs, |output| match output { + AcirType::NumericType(_) => { + let witness_index = self.acir_ir.next_witness_index(); + brillig_outputs.push(BrilligOutputs::Simple(witness_index)); + let var = self.add_data(AcirVarData::Witness(witness_index)); + AcirValue::Var(var, output.clone()) + } + AcirType::Array(element_types, size) => { + let (acir_value, witnesses) = self.brillig_array_output(&element_types, size); + brillig_outputs.push(BrilligOutputs::Array(witnesses)); + acir_value + } + }); + let predicate = self.var_to_expression(predicate)?; + + self.acir_ir.brillig_call( + Some(predicate), + generated_brillig, + brillig_inputs, + brillig_outputs, + brillig_function_index, + ); + + fn range_constraint_value( + context: &mut AcirContext, + value: &AcirValue, + ) -> Result<(), RuntimeError> { + match value { + AcirValue::Var(var, typ) => { + let numeric_type = match typ { + AcirType::NumericType(numeric_type) => numeric_type, + _ => unreachable!("`AcirValue::Var` may only hold primitive values"), + }; + context.range_constrain_var(*var, numeric_type, None)?; + } + AcirValue::Array(values) => { + for value in values { + range_constraint_value(context, value)?; + } + } + AcirValue::DynamicArray(_) => { + unreachable!("Brillig opcodes cannot return dynamic arrays") + } + } + Ok(()) + } + + // This is a hack to ensure that if we're compiling a brillig entrypoint function then + // we don't also add a number of range constraints. + if !unsafe_return_values { + for output_var in &outputs_var { + range_constraint_value(self, output_var)?; + } + } + Ok(outputs_var) + } + fn brillig_array_input( &mut self, var_expressions: &mut Vec, diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs index ba4e03bff95..999ff2ddb5d 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/acir_ir/generated_acir.rs @@ -167,17 +167,27 @@ impl GeneratedAcir { BlackBoxFuncCall::XOR { lhs: inputs[0][0], rhs: inputs[1][0], output: outputs[0] } } BlackBoxFunc::RANGE => BlackBoxFuncCall::RANGE { input: inputs[0][0] }, - BlackBoxFunc::SHA256 => BlackBoxFuncCall::SHA256 { inputs: inputs[0].clone(), outputs }, - BlackBoxFunc::Blake2s => { - BlackBoxFuncCall::Blake2s { inputs: inputs[0].clone(), outputs } - } - BlackBoxFunc::Blake3 => BlackBoxFuncCall::Blake3 { inputs: inputs[0].clone(), outputs }, + BlackBoxFunc::SHA256 => BlackBoxFuncCall::SHA256 { + inputs: inputs[0].clone(), + outputs: outputs.try_into().expect("Compiler should generate correct size outputs"), + }, + BlackBoxFunc::Blake2s => BlackBoxFuncCall::Blake2s { + inputs: inputs[0].clone(), + outputs: outputs.try_into().expect("Compiler should generate correct size outputs"), + }, + BlackBoxFunc::Blake3 => BlackBoxFuncCall::Blake3 { + inputs: inputs[0].clone(), + outputs: outputs.try_into().expect("Compiler should generate correct size outputs"), + }, BlackBoxFunc::SchnorrVerify => { BlackBoxFuncCall::SchnorrVerify { public_key_x: inputs[0][0], public_key_y: inputs[1][0], // Schnorr signature is an r & s, 32 bytes each - signature: inputs[2].clone(), + signature: inputs[2] + .clone() + .try_into() + .expect("Compiler should generate correct size inputs"), message: inputs[3].clone(), output: outputs[0], } @@ -195,24 +205,48 @@ impl GeneratedAcir { BlackBoxFunc::EcdsaSecp256k1 => { BlackBoxFuncCall::EcdsaSecp256k1 { // 32 bytes for each public key co-ordinate - public_key_x: inputs[0].clone(), - public_key_y: inputs[1].clone(), + public_key_x: inputs[0] + .clone() + .try_into() + .expect("Compiler should generate correct size inputs"), + public_key_y: inputs[1] + .clone() + .try_into() + .expect("Compiler should generate correct size inputs"), // (r,s) are both 32 bytes each, so signature // takes up 64 bytes - signature: inputs[2].clone(), - hashed_message: inputs[3].clone(), + signature: inputs[2] + .clone() + .try_into() + .expect("Compiler should generate correct size inputs"), + hashed_message: inputs[3] + .clone() + .try_into() + .expect("Compiler should generate correct size inputs"), output: outputs[0], } } BlackBoxFunc::EcdsaSecp256r1 => { BlackBoxFuncCall::EcdsaSecp256r1 { // 32 bytes for each public key co-ordinate - public_key_x: inputs[0].clone(), - public_key_y: inputs[1].clone(), + public_key_x: inputs[0] + .clone() + .try_into() + .expect("Compiler should generate correct size inputs"), + public_key_y: inputs[1] + .clone() + .try_into() + .expect("Compiler should generate correct size inputs"), // (r,s) are both 32 bytes each, so signature // takes up 64 bytes - signature: inputs[2].clone(), - hashed_message: inputs[3].clone(), + signature: inputs[2] + .clone() + .try_into() + .expect("Compiler should generate correct size inputs"), + hashed_message: inputs[3] + .clone() + .try_into() + .expect("Compiler should generate correct size inputs"), output: outputs[0], } } @@ -240,11 +274,21 @@ impl GeneratedAcir { } }; - BlackBoxFuncCall::Keccak256 { inputs: inputs[0].clone(), var_message_size, outputs } - } - BlackBoxFunc::Keccakf1600 => { - BlackBoxFuncCall::Keccakf1600 { inputs: inputs[0].clone(), outputs } + BlackBoxFuncCall::Keccak256 { + inputs: inputs[0].clone(), + var_message_size, + outputs: outputs + .try_into() + .expect("Compiler should generate correct size outputs"), + } } + BlackBoxFunc::Keccakf1600 => BlackBoxFuncCall::Keccakf1600 { + inputs: inputs[0] + .clone() + .try_into() + .expect("Compiler should generate correct size inputs"), + outputs: outputs.try_into().expect("Compiler should generate correct size outputs"), + }, BlackBoxFunc::RecursiveAggregation => BlackBoxFuncCall::RecursiveAggregation { verification_key: inputs[0].clone(), proof: inputs[1].clone(), @@ -286,9 +330,15 @@ impl GeneratedAcir { len: constant_inputs[0].to_u128() as u32, }, BlackBoxFunc::Sha256Compression => BlackBoxFuncCall::Sha256Compression { - inputs: inputs[0].clone(), - hash_values: inputs[1].clone(), - outputs, + inputs: inputs[0] + .clone() + .try_into() + .expect("Compiler should generate correct size inputs"), + hash_values: inputs[1] + .clone() + .try_into() + .expect("Compiler should generate correct size inputs"), + outputs: outputs.try_into().expect("Compiler should generate correct size outputs"), }, }; @@ -539,6 +589,7 @@ impl GeneratedAcir { Ok(()) } + // TODO: Delete this method once we remove the `Brillig` opcode pub(crate) fn brillig( &mut self, predicate: Option, @@ -567,6 +618,37 @@ impl GeneratedAcir { } } + pub(crate) fn brillig_call( + &mut self, + predicate: Option, + generated_brillig: &GeneratedBrillig, + inputs: Vec, + outputs: Vec, + brillig_function_index: u32, + ) { + let opcode = + AcirOpcode::BrilligCall { id: brillig_function_index, inputs, outputs, predicate }; + self.push_opcode(opcode); + for (brillig_index, call_stack) in generated_brillig.locations.iter() { + self.locations.insert( + OpcodeLocation::Brillig { + acir_index: self.opcodes.len() - 1, + brillig_index: *brillig_index, + }, + call_stack.clone(), + ); + } + for (brillig_index, message) in generated_brillig.assert_messages.iter() { + self.assert_messages.insert( + OpcodeLocation::Brillig { + acir_index: self.opcodes.len() - 1, + brillig_index: *brillig_index, + }, + message.clone(), + ); + } + } + pub(crate) fn last_acir_opcode_location(&self) -> OpcodeLocation { OpcodeLocation::Acir(self.opcodes.len() - 1) } diff --git a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 9f2cec5c949..8019707644b 100644 --- a/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -1,13 +1,14 @@ //! This file holds the pass to convert from Noir's SSA IR to ACIR. mod acir_ir; -use std::collections::HashSet; +use std::collections::{BTreeMap, HashSet}; use std::fmt::Debug; use self::acir_ir::acir_variable::{AcirContext, AcirType, AcirVar}; use super::function_builder::data_bus::DataBus; use super::ir::dfg::CallStack; -use super::ir::instruction::ConstrainError; +use super::ir::function::FunctionId; +use super::ir::instruction::{ConstrainError, UserDefinedConstrainError}; use super::{ ir::{ dfg::DataFlowGraph, @@ -28,6 +29,7 @@ use crate::errors::{InternalError, InternalWarning, RuntimeError, SsaReport}; use crate::ssa::ir::function::InlineType; pub(crate) use acir_ir::generated_acir::GeneratedAcir; +use acvm::acir::circuit::brillig::BrilligBytecode; use acvm::acir::native_types::Witness; use acvm::acir::BlackBoxFunc; use acvm::{ @@ -39,9 +41,47 @@ use im::Vector; use iter_extended::{try_vecmap, vecmap}; use noirc_frontend::Distinctness; +#[derive(Default)] +struct SharedContext { + /// Final list of Brillig functions which will be part of the final program + /// This is shared across `Context` structs as we want one list of Brillig + /// functions across all ACIR artifacts + generated_brillig: Vec, + + /// Maps SSA function index -> Final generated Brillig artifact index. + /// There can be Brillig functions specified in SSA which do not act as + /// entry points in ACIR (e.g. only called by other Brillig functions) + /// This mapping is necessary to use the correct function pointer for a Brillig call. + brillig_generated_func_pointers: BTreeMap, +} + +impl SharedContext { + fn generated_brillig_pointer(&self, func_id: &FunctionId) -> Option<&u32> { + self.brillig_generated_func_pointers.get(func_id) + } + + fn generated_brillig(&self, func_pointer: usize) -> &GeneratedBrillig { + &self.generated_brillig[func_pointer] + } + + fn insert_generated_brillig( + &mut self, + func_id: FunctionId, + generated_pointer: u32, + code: GeneratedBrillig, + ) { + self.brillig_generated_func_pointers.insert(func_id, generated_pointer); + self.generated_brillig.push(code); + } + + fn new_generated_pointer(&self) -> u32 { + self.generated_brillig.len() as u32 + } +} + /// Context struct for the acir generation pass. /// May be similar to the Evaluator struct in the current SSA IR. -struct Context { +struct Context<'a> { /// Maps SSA values to `AcirVar`. /// /// This is needed so that we only create a single @@ -91,6 +131,9 @@ struct Context { max_block_id: u32, data_bus: DataBus, + + /// Contains state that is generated and also used across ACIR functions + shared_context: &'a mut SharedContext, } #[derive(Clone)] @@ -181,11 +224,12 @@ impl Ssa { self, brillig: &Brillig, abi_distinctness: Distinctness, - ) -> Result, RuntimeError> { + ) -> Result<(Vec, Vec), RuntimeError> { let mut acirs = Vec::new(); // TODO: can we parallelise this? + let mut shared_context = SharedContext::default(); for function in self.functions.values() { - let context = Context::new(); + let context = Context::new(&mut shared_context); if let Some(mut generated_acir) = context.convert_ssa_function(&self, function, brillig)? { @@ -194,6 +238,10 @@ impl Ssa { } } + let brillig = vecmap(shared_context.generated_brillig, |brillig| BrilligBytecode { + bytecode: brillig.byte_code, + }); + // TODO: check whether doing this for a single circuit's return witnesses is correct. // We probably need it for all foldable circuits, as any circuit being folded is essentially an entry point. However, I do not know how that // plays a part when we potentially want not inlined functions normally as part of the compiler. @@ -215,15 +263,15 @@ impl Ssa { .collect(); main_func_acir.return_witnesses = distinct_return_witness; - Ok(acirs) } - Distinctness::DuplicationAllowed => Ok(acirs), + Distinctness::DuplicationAllowed => {} } + Ok((acirs, brillig)) } } -impl Context { - fn new() -> Context { +impl<'a> Context<'a> { + fn new(shared_context: &'a mut SharedContext) -> Context<'a> { let mut acir_context = AcirContext::default(); let current_side_effects_enabled_var = acir_context.add_constant(FieldElement::one()); @@ -237,6 +285,7 @@ impl Context { internal_mem_block_lengths: HashMap::default(), max_block_id: 0, data_bus: DataBus::default(), + shared_context, } } @@ -311,14 +360,18 @@ impl Context { // We specifically do not attempt execution of the brillig code being generated as this can result in it being // replaced with constraints on witnesses to the program outputs. - let output_values = self.acir_context.brillig( + let output_values = self.acir_context.brillig_call( self.current_side_effects_enabled_var, - code, + &code, inputs, outputs, false, true, + // We are guaranteed to have a Brillig function pointer of `0` as main itself is marked as unconstrained + 0, )?; + self.shared_context.insert_generated_brillig(main_func.id(), 0, code); + let output_vars: Vec<_> = output_values .iter() .flat_map(|value| value.clone().flatten()) @@ -472,8 +525,13 @@ impl Context { let assert_message = if let Some(error) = assert_message { match error.as_ref() { - ConstrainError::Static(string) => Some(string.clone()), - ConstrainError::Dynamic(call_instruction) => { + ConstrainError::Intrinsic(string) + | ConstrainError::UserDefined(UserDefinedConstrainError::Static(string)) => { + Some(string.clone()) + } + ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( + call_instruction, + )) => { self.convert_ssa_call(call_instruction, dfg, ssa, brillig, &[])?; None } @@ -571,12 +629,12 @@ impl Context { sum + dfg.try_get_array_length(*result_id).unwrap_or(1) }); - let acir_program_id = ssa - .id_to_index + let acir_function_id = ssa + .entry_point_to_generated_index .get(id) .expect("ICE: should have an associated final index"); let output_vars = self.acir_context.call_acir_function( - *acir_program_id, + *acir_function_id, inputs, output_count, self.current_side_effects_enabled_var, @@ -598,24 +656,50 @@ impl Context { ); } } - let inputs = vecmap(arguments, |arg| self.convert_value(*arg, dfg)); - let arguments = self.gen_brillig_parameters(arguments, dfg); - - let code = self.gen_brillig_for(func, arguments, brillig)?; let outputs: Vec = vecmap(result_ids, |result_id| { dfg.type_of_value(*result_id).into() }); - let output_values = self.acir_context.brillig( - self.current_side_effects_enabled_var, - code, - inputs, - outputs, - true, - false, - )?; + // Check whether we have already generated Brillig for this function + // If we have, re-use the generated code to set-up the Brillig call. + let output_values = if let Some(generated_pointer) = + self.shared_context.generated_brillig_pointer(id) + { + let code = self + .shared_context + .generated_brillig(*generated_pointer as usize); + self.acir_context.brillig_call( + self.current_side_effects_enabled_var, + code, + inputs, + outputs, + true, + false, + *generated_pointer, + )? + } else { + let arguments = self.gen_brillig_parameters(arguments, dfg); + let code = self.gen_brillig_for(func, arguments, brillig)?; + let generated_pointer = + self.shared_context.new_generated_pointer(); + let output_values = self.acir_context.brillig_call( + self.current_side_effects_enabled_var, + &code, + inputs, + outputs, + true, + false, + generated_pointer, + )?; + self.shared_context.insert_generated_brillig( + *id, + generated_pointer, + code, + ); + output_values + }; // Compiler sanity check assert_eq!(result_ids.len(), output_values.len(), "ICE: The number of Brillig output values should match the result ids in SSA"); @@ -2442,19 +2526,27 @@ mod test { }, }; - fn build_basic_foo_with_return(builder: &mut FunctionBuilder, foo_id: FunctionId) { - // acir(fold) fn foo f1 { + fn build_basic_foo_with_return( + builder: &mut FunctionBuilder, + foo_id: FunctionId, + is_brillig_func: bool, + ) { + // fn foo f1 { // b0(v0: Field, v1: Field): // v2 = eq v0, v1 // constrain v2 == u1 0 // return v0 // } - builder.new_function("foo".into(), foo_id, InlineType::Fold); + if is_brillig_func { + builder.new_brillig_function("foo".into(), foo_id); + } else { + builder.new_function("foo".into(), foo_id, InlineType::Fold); + } let foo_v0 = builder.add_parameter(Type::field()); let foo_v1 = builder.add_parameter(Type::field()); let foo_equality_check = builder.insert_binary(foo_v0, BinaryOp::Eq, foo_v1); - let zero = builder.field_constant(0u128); + let zero = builder.numeric_constant(0u128, Type::unsigned(1)); builder.insert_constrain(foo_equality_check, zero, None); builder.terminate_with_return(vec![foo_v0]); } @@ -2488,11 +2580,11 @@ mod test { builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); builder.terminate_with_return(vec![]); - build_basic_foo_with_return(&mut builder, foo_id); + build_basic_foo_with_return(&mut builder, foo_id, false); let ssa = builder.finish(); - let acir_functions = ssa + let (acir_functions, _) = ssa .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) .expect("Should compile manually written SSA into ACIR"); // Expected result: @@ -2584,11 +2676,11 @@ mod test { builder.insert_constrain(main_call1_results[0], main_call2_results[0], None); builder.terminate_with_return(vec![]); - build_basic_foo_with_return(&mut builder, foo_id); + build_basic_foo_with_return(&mut builder, foo_id, false); let ssa = builder.finish(); - let acir_functions = ssa + let (acir_functions, _) = ssa .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) .expect("Should compile manually written SSA into ACIR"); // The expected result should look very similar to the abvoe test expect that the input witnesses of the `Call` @@ -2675,11 +2767,11 @@ mod test { .to_vec(); builder.terminate_with_return(vec![foo_call[0]]); - build_basic_foo_with_return(&mut builder, foo_id); + build_basic_foo_with_return(&mut builder, foo_id, false); let ssa = builder.finish(); - let acir_functions = ssa + let (acir_functions, _) = ssa .into_acir(&Brillig::default(), noirc_frontend::Distinctness::Distinct) .expect("Should compile manually written SSA into ACIR"); @@ -2739,4 +2831,82 @@ mod test { _ => panic!("Expected only Call opcode"), } } + + // Test that given multiple calls to the same brillig function we generate only one bytecode + // and the appropriate Brillig call opcodes are generated + #[test] + fn multiple_brillig_calls_one_bytecode() { + // acir(inline) fn main f0 { + // b0(v0: Field, v1: Field): + // v3 = call f1(v0, v1) + // v4 = call f1(v0, v1) + // v5 = call f1(v0, v1) + // v6 = call f1(v0, v1) + // return + // } + // brillig fn foo f1 { + // b0(v0: Field, v1: Field): + // v2 = eq v0, v1 + // constrain v2 == u1 0 + // return v0 + // } + // brillig fn foo f2 { + // b0(v0: Field, v1: Field): + // v2 = eq v0, v1 + // constrain v2 == u1 0 + // return v0 + // } + let foo_id = Id::test_new(0); + let mut builder = FunctionBuilder::new("main".into(), foo_id); + let main_v0 = builder.add_parameter(Type::field()); + let main_v1 = builder.add_parameter(Type::field()); + + let foo_id = Id::test_new(1); + let foo = builder.import_function(foo_id); + let bar_id = Id::test_new(2); + let bar = builder.import_function(bar_id); + + // Insert multiple calls to the same Brillig function + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + // Interleave a call to a separate Brillig function to make sure that we can call multiple separate Brillig functions + builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.insert_call(foo, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.insert_call(bar, vec![main_v0, main_v1], vec![Type::field()]).to_vec(); + builder.terminate_with_return(vec![]); + + build_basic_foo_with_return(&mut builder, foo_id, true); + build_basic_foo_with_return(&mut builder, bar_id, true); + + let ssa = builder.finish(); + let brillig = ssa.to_brillig(false); + println!("{}", ssa); + + let (acir_functions, brillig_functions) = ssa + .into_acir(&brillig, noirc_frontend::Distinctness::Distinct) + .expect("Should compile manually written SSA into ACIR"); + + assert_eq!(acir_functions.len(), 1, "Should only have a `main` ACIR function"); + assert_eq!( + brillig_functions.len(), + 2, + "Should only have generated a single Brillig function" + ); + + let main_acir = &acir_functions[0]; + let main_opcodes = main_acir.opcodes(); + assert_eq!(main_opcodes.len(), 6, "Should have four calls to f1 and two calls to f2"); + + // We should only have `BrilligCall` opcodes in `main` + for (i, opcode) in main_opcodes.iter().enumerate() { + match opcode { + Opcode::BrilligCall { id, .. } => { + let expected_id = if i == 3 || i == 5 { 1 } else { 0 }; + assert_eq!(*id, expected_id, "Expected an id of {expected_id} but got {id}"); + } + _ => panic!("Expected only Brillig call opcode"), + } + } + } } diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 641d971af3c..04f33d528cd 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -347,9 +347,11 @@ impl Instruction { let lhs = f(*lhs); let rhs = f(*rhs); let assert_message = assert_message.as_ref().map(|error| match error.as_ref() { - ConstrainError::Dynamic(call_instr) => { + ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic(call_instr)) => { let new_instr = call_instr.map_values(f); - Box::new(ConstrainError::Dynamic(new_instr)) + Box::new(ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( + new_instr, + ))) } _ => error.clone(), }); @@ -411,7 +413,10 @@ impl Instruction { f(*lhs); f(*rhs); if let Some(error) = assert_error.as_ref() { - if let ConstrainError::Dynamic(call_instr) = error.as_ref() { + if let ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( + call_instr, + )) = error.as_ref() + { call_instr.for_each_value(f); } } @@ -597,6 +602,14 @@ impl Instruction { #[derive(Debug, PartialEq, Eq, Hash, Clone)] pub(crate) enum ConstrainError { // These are errors which have been hardcoded during SSA gen + Intrinsic(String), + // These are errors issued by the user + UserDefined(UserDefinedConstrainError), +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub(crate) enum UserDefinedConstrainError { + // These are errors which come from static strings specified by a Noir program Static(String), // These are errors which come from runtime expressions specified by a Noir program // We store an `Instruction` as we want this Instruction to be atomic in SSA with @@ -606,7 +619,7 @@ pub(crate) enum ConstrainError { impl From for ConstrainError { fn from(value: String) -> Self { - ConstrainError::Static(value) + ConstrainError::Intrinsic(value) } } diff --git a/compiler/noirc_evaluator/src/ssa/ir/printer.rs b/compiler/noirc_evaluator/src/ssa/ir/printer.rs index fc13ab7307a..d17d2989341 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/printer.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/printer.rs @@ -9,7 +9,10 @@ use iter_extended::vecmap; use super::{ basic_block::BasicBlockId, function::Function, - instruction::{ConstrainError, Instruction, InstructionId, TerminatorInstruction}, + instruction::{ + ConstrainError, Instruction, InstructionId, TerminatorInstruction, + UserDefinedConstrainError, + }, value::ValueId, }; @@ -201,10 +204,11 @@ fn display_constrain_error( f: &mut Formatter, ) -> Result { match error { - ConstrainError::Static(assert_message_string) => { + ConstrainError::Intrinsic(assert_message_string) + | ConstrainError::UserDefined(UserDefinedConstrainError::Static(assert_message_string)) => { writeln!(f, "{assert_message_string:?}") } - ConstrainError::Dynamic(assert_message_call) => { + ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic(assert_message_call)) => { display_instruction_inner(function, assert_message_call, f) } } diff --git a/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs b/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs index ca6527eb0ec..aa0368cc2dd 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/defunctionalize.rs @@ -14,7 +14,7 @@ use crate::ssa::{ ir::{ basic_block::BasicBlockId, function::{Function, FunctionId, Signature}, - instruction::{BinaryOp, ConstrainError, Instruction}, + instruction::{BinaryOp, ConstrainError, Instruction, UserDefinedConstrainError}, types::{NumericType, Type}, value::{Value, ValueId}, }, @@ -93,10 +93,9 @@ impl DefunctionalizationContext { // Constrain instruction potentially hold a call instruction themselves // thus we need to account for them. Instruction::Constrain(_, _, Some(constrain_error)) => { - if let ConstrainError::Dynamic(Instruction::Call { - func: target_func_id, - arguments, - }) = constrain_error.as_ref() + if let ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic( + Instruction::Call { func: target_func_id, arguments }, + )) = constrain_error.as_ref() { (*target_func_id, arguments) } else { @@ -138,9 +137,11 @@ impl DefunctionalizationContext { if let Instruction::Constrain(lhs, rhs, constrain_error_call) = instruction { let new_error_call = if let Some(error) = constrain_error_call { match error.as_ref() { - ConstrainError::Dynamic(_) => { - Some(Box::new(ConstrainError::Dynamic(new_instruction))) - } + ConstrainError::UserDefined( + UserDefinedConstrainError::Dynamic(_), + ) => Some(Box::new(ConstrainError::UserDefined( + UserDefinedConstrainError::Dynamic(new_instruction), + ))), _ => None, } } else { diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index fd1a029af26..59ce4e4f754 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -29,7 +29,9 @@ use super::{ function_builder::data_bus::DataBus, ir::{ function::RuntimeType, - instruction::{BinaryOp, ConstrainError, Instruction, TerminatorInstruction}, + instruction::{ + BinaryOp, ConstrainError, Instruction, TerminatorInstruction, UserDefinedConstrainError, + }, types::Type, value::ValueId, }, @@ -241,7 +243,6 @@ impl<'a> FunctionContext<'a> { Ok(Tree::Branch(vec![string, field_count.into(), fields])) } - ast::Literal::Unit => Ok(Self::unit_value()), } } @@ -708,7 +709,9 @@ impl<'a> FunctionContext<'a> { if let ast::Expression::Literal(ast::Literal::Str(assert_message)) = assert_message_expr.as_ref() { - return Ok(Some(Box::new(ConstrainError::Static(assert_message.to_string())))); + return Ok(Some(Box::new(ConstrainError::UserDefined( + UserDefinedConstrainError::Static(assert_message.to_string()), + )))); } let ast::Expression::Call(call) = assert_message_expr.as_ref() else { @@ -734,7 +737,7 @@ impl<'a> FunctionContext<'a> { } let instr = Instruction::Call { func, arguments }; - Ok(Some(Box::new(ConstrainError::Dynamic(instr)))) + Ok(Some(Box::new(ConstrainError::UserDefined(UserDefinedConstrainError::Dynamic(instr))))) } fn codegen_assign(&mut self, assign: &ast::Assign) -> Result { diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs index 9995c031145..b05a2cbc741 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/program.rs @@ -3,7 +3,7 @@ use std::{collections::BTreeMap, fmt::Display}; use iter_extended::btree_map; use crate::ssa::ir::{ - function::{Function, FunctionId}, + function::{Function, FunctionId, RuntimeType}, map::AtomicCounter, }; @@ -12,7 +12,11 @@ pub(crate) struct Ssa { pub(crate) functions: BTreeMap, pub(crate) main_id: FunctionId, pub(crate) next_id: AtomicCounter, - pub(crate) id_to_index: BTreeMap, + /// Maps SSA entry point function ID -> Final generated ACIR artifact index. + /// There can be functions specified in SSA which do not act as ACIR entry points. + /// This mapping is necessary to use the correct function pointer for an ACIR call, + /// as the final program artifact will be a list of only entry point functions. + pub(crate) entry_point_to_generated_index: BTreeMap, } impl Ssa { @@ -27,9 +31,26 @@ impl Ssa { (f.id(), f) }); - let id_to_index = btree_map(functions.iter().enumerate(), |(i, (id, _))| (*id, i as u32)); + let entry_point_to_generated_index = btree_map( + functions + .iter() + .filter(|(_, func)| { + let runtime = func.runtime(); + match func.runtime() { + RuntimeType::Acir(_) => runtime.is_entry_point() || func.id() == main_id, + RuntimeType::Brillig => false, + } + }) + .enumerate(), + |(i, (id, _))| (*id, i as u32), + ); - Self { functions, main_id, next_id: AtomicCounter::starting_after(max_id), id_to_index } + Self { + functions, + main_id, + next_id: AtomicCounter::starting_after(max_id), + entry_point_to_generated_index, + } } /// Returns the entry-point function of the program diff --git a/compiler/noirc_frontend/Cargo.toml b/compiler/noirc_frontend/Cargo.toml index e39ab2fe106..03b92e15032 100644 --- a/compiler/noirc_frontend/Cargo.toml +++ b/compiler/noirc_frontend/Cargo.toml @@ -24,16 +24,9 @@ small-ord-set = "0.1.3" regex = "1.9.1" tracing.workspace = true petgraph = "0.6" -lalrpop-util = { version = "0.20.2", features = ["lexer"] } [dev-dependencies] base64.workspace = true strum = "0.24" strum_macros = "0.24" tempfile.workspace = true - -[build-dependencies] -lalrpop = "0.20.2" - -[features] -experimental_parser = [] diff --git a/compiler/noirc_frontend/build.rs b/compiler/noirc_frontend/build.rs deleted file mode 100644 index eb896a377ae..00000000000 --- a/compiler/noirc_frontend/build.rs +++ /dev/null @@ -1,28 +0,0 @@ -use std::fs::{read_to_string, File}; -use std::io::Write; - -fn main() { - lalrpop::Configuration::new() - .emit_rerun_directives(true) - .use_cargo_dir_conventions() - .process() - .unwrap(); - - // here, we get a lint error from "extern crate core" so patching that until lalrpop does - // (adding cfg directives appears to be unsupported by lalrpop) - let out_dir = std::env::var("OUT_DIR").unwrap(); - let parser_path = std::path::Path::new(&out_dir).join("noir_parser.rs"); - let content_str = read_to_string(parser_path.clone()).unwrap(); - let mut parser_file = File::create(parser_path).unwrap(); - for line in content_str.lines() { - if line.contains("extern crate core") { - parser_file - .write_all( - format!("{}\n", line.replace("extern crate core", "use core")).as_bytes(), - ) - .unwrap(); - } else { - parser_file.write_all(format!("{}\n", line).as_bytes()).unwrap(); - } - } -} diff --git a/compiler/noirc_frontend/src/ast/mod.rs b/compiler/noirc_frontend/src/ast/mod.rs index 254ec4a7590..4547dc2a176 100644 --- a/compiler/noirc_frontend/src/ast/mod.rs +++ b/compiler/noirc_frontend/src/ast/mod.rs @@ -112,9 +112,6 @@ pub enum UnresolvedTypeData { /*env:*/ Box, ), - // The type of quoted code for metaprogramming - Code, - Unspecified, // This is for when the user declares a variable without specifying it's type Error, } @@ -203,7 +200,6 @@ impl std::fmt::Display for UnresolvedTypeData { } } MutableReference(element) => write!(f, "&mut {element}"), - Code => write!(f, "Code"), Unit => write!(f, "()"), Error => write!(f, "error"), Unspecified => write!(f, "unspecified"), diff --git a/compiler/noirc_frontend/src/hir/comptime/hir_to_ast.rs b/compiler/noirc_frontend/src/hir/comptime/hir_to_ast.rs deleted file mode 100644 index 8ffcbce7d62..00000000000 --- a/compiler/noirc_frontend/src/hir/comptime/hir_to_ast.rs +++ /dev/null @@ -1,350 +0,0 @@ -use iter_extended::vecmap; -use noirc_errors::{Span, Spanned}; - -use crate::ast::{ConstrainStatement, Expression, Statement, StatementKind}; -use crate::hir_def::expr::{HirArrayLiteral, HirExpression, HirIdent}; -use crate::hir_def::stmt::{HirLValue, HirPattern, HirStatement}; -use crate::macros_api::HirLiteral; -use crate::node_interner::{ExprId, NodeInterner, StmtId}; -use crate::{ - ArrayLiteral, AssignStatement, BlockExpression, CallExpression, CastExpression, ConstrainKind, - ConstructorExpression, ExpressionKind, ForLoopStatement, ForRange, Ident, IfExpression, - IndexExpression, InfixExpression, LValue, Lambda, LetStatement, Literal, - MemberAccessExpression, MethodCallExpression, Path, Pattern, PrefixExpression, Type, - UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression, -}; - -// TODO: -// - Full path for idents & types -// - Assert/AssertEq information lost -// - The type name span is lost in constructor patterns & expressions -// - All type spans are lost -// - Type::TypeVariable has no equivalent in the Ast - -impl StmtId { - #[allow(unused)] - fn to_ast(self, interner: &NodeInterner) -> Statement { - let statement = interner.statement(&self); - let span = interner.statement_span(&self); - - let kind = match statement { - HirStatement::Let(let_stmt) => { - let pattern = let_stmt.pattern.into_ast(interner); - let r#type = interner.id_type(let_stmt.expression).to_ast(); - let expression = let_stmt.expression.to_ast(interner); - StatementKind::Let(LetStatement { - pattern, - r#type, - expression, - attributes: Vec::new(), - }) - } - HirStatement::Constrain(constrain) => { - let expr = constrain.0.to_ast(interner); - let message = constrain.2.map(|message| message.to_ast(interner)); - - // TODO: Find difference in usage between Assert & AssertEq - StatementKind::Constrain(ConstrainStatement(expr, message, ConstrainKind::Assert)) - } - HirStatement::Assign(assign) => StatementKind::Assign(AssignStatement { - lvalue: assign.lvalue.into_ast(interner), - expression: assign.expression.to_ast(interner), - }), - HirStatement::For(for_stmt) => StatementKind::For(ForLoopStatement { - identifier: for_stmt.identifier.to_ast(interner), - range: ForRange::Range( - for_stmt.start_range.to_ast(interner), - for_stmt.end_range.to_ast(interner), - ), - block: for_stmt.block.to_ast(interner), - span, - }), - HirStatement::Break => StatementKind::Break, - HirStatement::Continue => StatementKind::Continue, - HirStatement::Expression(expr) => StatementKind::Expression(expr.to_ast(interner)), - HirStatement::Semi(expr) => StatementKind::Semi(expr.to_ast(interner)), - HirStatement::Error => StatementKind::Error, - }; - - Statement { kind, span } - } -} - -impl ExprId { - #[allow(unused)] - fn to_ast(self, interner: &NodeInterner) -> Expression { - let expression = interner.expression(&self); - let span = interner.expr_span(&self); - - let kind = match expression { - HirExpression::Ident(ident) => { - let path = Path::from_ident(ident.to_ast(interner)); - ExpressionKind::Variable(path) - } - HirExpression::Literal(HirLiteral::Array(array)) => { - let array = array.into_ast(interner, span); - ExpressionKind::Literal(Literal::Array(array)) - } - HirExpression::Literal(HirLiteral::Slice(array)) => { - let array = array.into_ast(interner, span); - ExpressionKind::Literal(Literal::Slice(array)) - } - HirExpression::Literal(HirLiteral::Bool(value)) => { - ExpressionKind::Literal(Literal::Bool(value)) - } - HirExpression::Literal(HirLiteral::Integer(value, sign)) => { - ExpressionKind::Literal(Literal::Integer(value, sign)) - } - HirExpression::Literal(HirLiteral::Str(string)) => { - ExpressionKind::Literal(Literal::Str(string)) - } - HirExpression::Literal(HirLiteral::FmtStr(string, _exprs)) => { - // TODO: Is throwing away the exprs here valid? - ExpressionKind::Literal(Literal::FmtStr(string)) - } - HirExpression::Literal(HirLiteral::Unit) => ExpressionKind::Literal(Literal::Unit), - HirExpression::Block(expr) => { - let statements = vecmap(expr.statements, |statement| statement.to_ast(interner)); - ExpressionKind::Block(BlockExpression { statements }) - } - HirExpression::Prefix(prefix) => ExpressionKind::Prefix(Box::new(PrefixExpression { - operator: prefix.operator, - rhs: prefix.rhs.to_ast(interner), - })), - HirExpression::Infix(infix) => ExpressionKind::Infix(Box::new(InfixExpression { - lhs: infix.lhs.to_ast(interner), - operator: Spanned::from(infix.operator.location.span, infix.operator.kind), - rhs: infix.rhs.to_ast(interner), - })), - HirExpression::Index(index) => ExpressionKind::Index(Box::new(IndexExpression { - collection: index.collection.to_ast(interner), - index: index.index.to_ast(interner), - })), - HirExpression::Constructor(constructor) => { - let type_name = constructor.r#type.borrow().name.to_string(); - let type_name = Path::from_single(type_name, span); - let fields = - vecmap(constructor.fields, |(name, expr)| (name, expr.to_ast(interner))); - - ExpressionKind::Constructor(Box::new(ConstructorExpression { type_name, fields })) - } - HirExpression::MemberAccess(access) => { - ExpressionKind::MemberAccess(Box::new(MemberAccessExpression { - lhs: access.lhs.to_ast(interner), - rhs: access.rhs, - })) - } - HirExpression::Call(call) => { - let func = Box::new(call.func.to_ast(interner)); - let arguments = vecmap(call.arguments, |arg| arg.to_ast(interner)); - ExpressionKind::Call(Box::new(CallExpression { func, arguments })) - } - HirExpression::MethodCall(method_call) => { - ExpressionKind::MethodCall(Box::new(MethodCallExpression { - object: method_call.object.to_ast(interner), - method_name: method_call.method, - arguments: vecmap(method_call.arguments, |arg| arg.to_ast(interner)), - })) - } - HirExpression::Cast(cast) => { - let lhs = cast.lhs.to_ast(interner); - let r#type = cast.r#type.to_ast(); - ExpressionKind::Cast(Box::new(CastExpression { lhs, r#type })) - } - HirExpression::If(if_expr) => ExpressionKind::If(Box::new(IfExpression { - condition: if_expr.condition.to_ast(interner), - consequence: if_expr.consequence.to_ast(interner), - alternative: if_expr.alternative.map(|expr| expr.to_ast(interner)), - })), - HirExpression::Tuple(fields) => { - ExpressionKind::Tuple(vecmap(fields, |field| field.to_ast(interner))) - } - HirExpression::Lambda(lambda) => { - let parameters = vecmap(lambda.parameters, |(pattern, typ)| { - (pattern.into_ast(interner), typ.to_ast()) - }); - let return_type = lambda.return_type.to_ast(); - let body = lambda.body.to_ast(interner); - ExpressionKind::Lambda(Box::new(Lambda { parameters, return_type, body })) - } - HirExpression::Quote(block) => ExpressionKind::Quote(block), - HirExpression::Error => ExpressionKind::Error, - }; - - Expression::new(kind, span) - } -} - -impl HirPattern { - fn into_ast(self, interner: &NodeInterner) -> Pattern { - match self { - HirPattern::Identifier(ident) => Pattern::Identifier(ident.to_ast(interner)), - HirPattern::Mutable(pattern, location) => { - let pattern = Box::new(pattern.into_ast(interner)); - Pattern::Mutable(pattern, location.span, false) - } - HirPattern::Tuple(patterns, location) => { - let patterns = vecmap(patterns, |pattern| pattern.into_ast(interner)); - Pattern::Tuple(patterns, location.span) - } - HirPattern::Struct(typ, patterns, location) => { - let patterns = - vecmap(patterns, |(name, pattern)| (name, pattern.into_ast(interner))); - let name = match typ.follow_bindings() { - Type::Struct(struct_def, _) => { - let struct_def = struct_def.borrow(); - struct_def.name.0.contents.clone() - } - // This pass shouldn't error so if the type isn't a struct we just get a string - // representation of any other type and use that. We're relying on name - // resolution to fail later when this Ast is re-converted to Hir. - other => other.to_string(), - }; - // The name span is lost here - let path = Path::from_single(name, location.span); - Pattern::Struct(path, patterns, location.span) - } - } - } -} - -impl HirIdent { - fn to_ast(&self, interner: &NodeInterner) -> Ident { - let name = interner.definition_name(self.id).to_owned(); - Ident(Spanned::from(self.location.span, name)) - } -} - -impl Type { - fn to_ast(&self) -> UnresolvedType { - let typ = match self { - Type::FieldElement => UnresolvedTypeData::FieldElement, - Type::Array(length, element) => { - let length = length.to_type_expression(); - let element = Box::new(element.to_ast()); - UnresolvedTypeData::Array(length, element) - } - Type::Slice(element) => { - let element = Box::new(element.to_ast()); - UnresolvedTypeData::Slice(element) - } - Type::Integer(sign, bit_size) => UnresolvedTypeData::Integer(*sign, *bit_size), - Type::Bool => UnresolvedTypeData::Bool, - Type::String(length) => { - let length = length.to_type_expression(); - UnresolvedTypeData::String(Some(length)) - } - Type::FmtString(length, element) => { - let length = length.to_type_expression(); - let element = Box::new(element.to_ast()); - UnresolvedTypeData::FormatString(length, element) - } - Type::Unit => UnresolvedTypeData::Unit, - Type::Tuple(fields) => { - let fields = vecmap(fields, |field| field.to_ast()); - UnresolvedTypeData::Tuple(fields) - } - Type::Struct(def, generics) => { - let struct_def = def.borrow(); - let generics = vecmap(generics, |generic| generic.to_ast()); - let name = Path::from_ident(struct_def.name.clone()); - UnresolvedTypeData::Named(name, generics, false) - } - Type::Alias(type_def, generics) => { - // Keep the alias name instead of expanding this in case the - // alias' definition was changed - let type_def = type_def.borrow(); - let generics = vecmap(generics, |generic| generic.to_ast()); - let name = Path::from_ident(type_def.name.clone()); - UnresolvedTypeData::Named(name, generics, false) - } - Type::TypeVariable(_, _) => todo!("Convert Type::TypeVariable Hir -> Ast"), - Type::TraitAsType(_, name, generics) => { - let generics = vecmap(generics, |generic| generic.to_ast()); - let name = Path::from_single(name.as_ref().clone(), Span::default()); - UnresolvedTypeData::TraitAsType(name, generics) - } - Type::NamedGeneric(_, name) => { - let name = Path::from_single(name.as_ref().clone(), Span::default()); - UnresolvedTypeData::TraitAsType(name, Vec::new()) - } - Type::Function(args, ret, env) => { - let args = vecmap(args, |arg| arg.to_ast()); - let ret = Box::new(ret.to_ast()); - let env = Box::new(env.to_ast()); - UnresolvedTypeData::Function(args, ret, env) - } - Type::MutableReference(element) => { - let element = Box::new(element.to_ast()); - UnresolvedTypeData::MutableReference(element) - } - // Type::Forall is only for generic functions which don't store a type - // in their Ast so they don't need to call to_ast for their Forall type. - // Since there is no UnresolvedTypeData equivalent for Type::Forall, we use - // this to ignore this case since it shouldn't be needed anyway. - Type::Forall(_, typ) => return typ.to_ast(), - Type::Constant(_) => panic!("Type::Constant where a type was expected: {self:?}"), - Type::Code => UnresolvedTypeData::Code, - Type::Error => UnresolvedTypeData::Error, - }; - - UnresolvedType { typ, span: None } - } - - fn to_type_expression(&self) -> UnresolvedTypeExpression { - let span = Span::default(); - - match self.follow_bindings() { - Type::Constant(length) => UnresolvedTypeExpression::Constant(length, span), - Type::NamedGeneric(_, name) => { - let path = Path::from_single(name.as_ref().clone(), span); - UnresolvedTypeExpression::Variable(path) - } - // TODO: This should be turned into a proper error. - other => panic!("Cannot represent {other:?} as type expression"), - } - } -} - -impl HirLValue { - fn into_ast(self, interner: &NodeInterner) -> LValue { - match self { - HirLValue::Ident(ident, _) => LValue::Ident(ident.to_ast(interner)), - HirLValue::MemberAccess { object, field_name, field_index: _, typ: _, location } => { - let object = Box::new(object.into_ast(interner)); - LValue::MemberAccess { object, field_name, span: location.span } - } - HirLValue::Index { array, index, typ: _, location } => { - let array = Box::new(array.into_ast(interner)); - let index = index.to_ast(interner); - LValue::Index { array, index, span: location.span } - } - HirLValue::Dereference { lvalue, element_type: _, location } => { - let lvalue = Box::new(lvalue.into_ast(interner)); - LValue::Dereference(lvalue, location.span) - } - } - } -} - -impl HirArrayLiteral { - fn into_ast(self, interner: &NodeInterner, span: Span) -> ArrayLiteral { - match self { - HirArrayLiteral::Standard(elements) => { - ArrayLiteral::Standard(vecmap(elements, |element| element.to_ast(interner))) - } - HirArrayLiteral::Repeated { repeated_element, length } => { - let repeated_element = Box::new(repeated_element.to_ast(interner)); - let length = match length { - Type::Constant(length) => { - let literal = Literal::Integer((length as u128).into(), false); - let kind = ExpressionKind::Literal(literal); - Box::new(Expression::new(kind, span)) - } - other => panic!("Cannot convert non-constant type for repeated array literal from Hir -> Ast: {other:?}"), - }; - ArrayLiteral::Repeated { repeated_element, length } - } - } - } -} diff --git a/compiler/noirc_frontend/src/hir/comptime/mod.rs b/compiler/noirc_frontend/src/hir/comptime/mod.rs deleted file mode 100644 index 91621c857cf..00000000000 --- a/compiler/noirc_frontend/src/hir/comptime/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod hir_to_ast; diff --git a/compiler/noirc_frontend/src/hir/mod.rs b/compiler/noirc_frontend/src/hir/mod.rs index b4804739cac..727a6596df1 100644 --- a/compiler/noirc_frontend/src/hir/mod.rs +++ b/compiler/noirc_frontend/src/hir/mod.rs @@ -1,4 +1,3 @@ -pub mod comptime; pub mod def_collector; pub mod def_map; pub mod resolution; diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index 9180201fe17..08b12069d76 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -502,7 +502,6 @@ impl<'a> Resolver<'a> { let fields = self.resolve_type_inner(*fields, new_variables); Type::FmtString(Box::new(resolved_size), Box::new(fields)) } - Code => Type::Code, Unit => Type::Unit, Unspecified => Type::Error, Error => Type::Error, diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index 2d1ebf530e3..265b9e4b5a3 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -2,9 +2,7 @@ use crate::token::{Attribute, DocStyle}; use super::{ errors::LexerErrorKind, - token::{ - token_to_borrowed_token, BorrowedToken, IntType, Keyword, SpannedToken, Token, Tokens, - }, + token::{IntType, Keyword, SpannedToken, Token, Tokens}, }; use acvm::FieldElement; use noirc_errors::{Position, Span}; @@ -23,21 +21,6 @@ pub struct Lexer<'a> { pub type SpannedTokenResult = Result; -pub(crate) fn from_spanned_token_result( - token_result: &SpannedTokenResult, -) -> Result<(usize, BorrowedToken<'_>, usize), LexerErrorKind> { - token_result - .as_ref() - .map(|spanned_token| { - ( - spanned_token.to_span().start() as usize, - token_to_borrowed_token(spanned_token.into()), - spanned_token.to_span().end() as usize, - ) - }) - .map_err(Clone::clone) -} - impl<'a> Lexer<'a> { /// Given a source file of noir code, return all the tokens in the file /// in order, along with any lexing errors that occurred. @@ -111,7 +94,7 @@ impl<'a> Lexer<'a> { fn next_token(&mut self) -> SpannedTokenResult { match self.next_char() { - Some(x) if Self::is_code_whitespace(x) => { + Some(x) if x.is_whitespace() => { let spanned = self.eat_whitespace(x); if self.skip_whitespaces { self.next_token() @@ -577,21 +560,16 @@ impl<'a> Lexer<'a> { } } - fn is_code_whitespace(c: char) -> bool { - c == '\t' || c == '\n' || c == '\r' || c == ' ' - } - /// Skips white space. They are not significant in the source language fn eat_whitespace(&mut self, initial_char: char) -> SpannedToken { let start = self.position; - let whitespace = self.eat_while(initial_char.into(), Self::is_code_whitespace); + let whitespace = self.eat_while(initial_char.into(), |ch| ch.is_whitespace()); SpannedToken::new(Token::Whitespace(whitespace), Span::inclusive(start, self.position)) } } impl<'a> Iterator for Lexer<'a> { type Item = SpannedTokenResult; - fn next(&mut self) -> Option { if self.done { None @@ -600,12 +578,10 @@ impl<'a> Iterator for Lexer<'a> { } } } - #[cfg(test)] mod tests { use super::*; use crate::token::{FunctionAttribute, SecondaryAttribute, TestScope}; - #[test] fn test_single_double_char() { let input = "! != + ( ) { } [ ] | , ; : :: < <= > >= & - -> . .. % / * = == << >>"; diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index 86f26fd1c97..357b1ead593 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -9,105 +9,12 @@ use crate::lexer::errors::LexerErrorKind; /// smallest unit of grammar. A parser may (will) decide to parse /// items differently depending on the Tokens present but will /// never parse the same ordering of identical tokens differently. -#[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)] -pub enum BorrowedToken<'input> { - Ident(&'input str), - Int(FieldElement), - Bool(bool), - Str(&'input str), - /// the u8 is the number of hashes, i.e. r###.. - RawStr(&'input str, u8), - FmtStr(&'input str), - Keyword(Keyword), - IntType(IntType), - Attribute(Attribute), - LineComment(&'input str, Option), - BlockComment(&'input str, Option), - /// < - Less, - /// <= - LessEqual, - /// > - Greater, - /// >= - GreaterEqual, - /// == - Equal, - /// != - NotEqual, - /// + - Plus, - /// - - Minus, - /// * - Star, - /// / - Slash, - /// % - Percent, - /// & - Ampersand, - /// ^ - Caret, - /// << - ShiftLeft, - /// >> - ShiftRight, - /// . - Dot, - /// .. - DoubleDot, - /// ( - LeftParen, - /// ) - RightParen, - /// { - LeftBrace, - /// } - RightBrace, - /// [ - LeftBracket, - /// ] - RightBracket, - /// -> - Arrow, - /// | - Pipe, - /// # - Pound, - /// , - Comma, - /// : - Colon, - /// :: - DoubleColon, - /// ; - Semicolon, - /// ! - Bang, - /// = - Assign, - #[allow(clippy::upper_case_acronyms)] - EOF, - - Whitespace(&'input str), - - /// An invalid character is one that is not in noir's language or grammar. - /// - /// We don't report invalid tokens in the source as errors until parsing to - /// avoid reporting the error twice (once while lexing, again when it is encountered - /// during parsing). Reporting during lexing then removing these from the token stream - /// would not be equivalent as it would change the resulting parse. - Invalid(char), -} - #[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)] pub enum Token { Ident(String), Int(FieldElement), Bool(bool), Str(String), - /// the u8 is the number of hashes, i.e. r###.. RawStr(String, u8), FmtStr(String), Keyword(Keyword), @@ -193,57 +100,6 @@ pub enum Token { Invalid(char), } -pub fn token_to_borrowed_token(token: &Token) -> BorrowedToken<'_> { - match token { - Token::Ident(ref s) => BorrowedToken::Ident(s), - Token::Int(n) => BorrowedToken::Int(*n), - Token::Bool(b) => BorrowedToken::Bool(*b), - Token::Str(ref b) => BorrowedToken::Str(b), - Token::FmtStr(ref b) => BorrowedToken::FmtStr(b), - Token::RawStr(ref b, hashes) => BorrowedToken::RawStr(b, *hashes), - Token::Keyword(k) => BorrowedToken::Keyword(*k), - Token::Attribute(ref a) => BorrowedToken::Attribute(a.clone()), - Token::LineComment(ref s, _style) => BorrowedToken::LineComment(s, *_style), - Token::BlockComment(ref s, _style) => BorrowedToken::BlockComment(s, *_style), - Token::IntType(ref i) => BorrowedToken::IntType(i.clone()), - Token::Less => BorrowedToken::Less, - Token::LessEqual => BorrowedToken::LessEqual, - Token::Greater => BorrowedToken::Greater, - Token::GreaterEqual => BorrowedToken::GreaterEqual, - Token::Equal => BorrowedToken::Equal, - Token::NotEqual => BorrowedToken::NotEqual, - Token::Plus => BorrowedToken::Plus, - Token::Minus => BorrowedToken::Minus, - Token::Star => BorrowedToken::Star, - Token::Slash => BorrowedToken::Slash, - Token::Percent => BorrowedToken::Percent, - Token::Ampersand => BorrowedToken::Ampersand, - Token::Caret => BorrowedToken::Caret, - Token::ShiftLeft => BorrowedToken::ShiftLeft, - Token::ShiftRight => BorrowedToken::ShiftRight, - Token::Dot => BorrowedToken::Dot, - Token::DoubleDot => BorrowedToken::DoubleDot, - Token::LeftParen => BorrowedToken::LeftParen, - Token::RightParen => BorrowedToken::RightParen, - Token::LeftBrace => BorrowedToken::LeftBrace, - Token::RightBrace => BorrowedToken::RightBrace, - Token::LeftBracket => BorrowedToken::LeftBracket, - Token::RightBracket => BorrowedToken::RightBracket, - Token::Arrow => BorrowedToken::Arrow, - Token::Pipe => BorrowedToken::Pipe, - Token::Pound => BorrowedToken::Pound, - Token::Comma => BorrowedToken::Comma, - Token::Colon => BorrowedToken::Colon, - Token::DoubleColon => BorrowedToken::DoubleColon, - Token::Semicolon => BorrowedToken::Semicolon, - Token::Assign => BorrowedToken::Assign, - Token::Bang => BorrowedToken::Bang, - Token::EOF => BorrowedToken::EOF, - Token::Invalid(c) => BorrowedToken::Invalid(*c), - Token::Whitespace(ref s) => BorrowedToken::Whitespace(s), - } -} - #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub enum DocStyle { Outer, @@ -270,12 +126,6 @@ impl From for Token { } } -impl<'a> From<&'a SpannedToken> for &'a Token { - fn from(spt: &'a SpannedToken) -> Self { - &spt.0.contents - } -} - impl SpannedToken { pub fn new(token: Token, span: Span) -> SpannedToken { SpannedToken(Spanned::from(span, token)) diff --git a/compiler/noirc_frontend/src/monomorphization/ast.rs b/compiler/noirc_frontend/src/monomorphization/ast.rs index d9c33d8604e..7d20c2bcfee 100644 --- a/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -92,7 +92,6 @@ pub enum Literal { Slice(ArrayLiteral), Integer(FieldElement, Type, Location), Bool(bool), - Unit, Str(String), FmtStr(String, u64, Box), } diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 4e779244d30..6aa0abce152 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -52,13 +52,14 @@ struct LambdaContext { /// This struct holds the FIFO queue of functions to monomorphize, which is added to /// whenever a new (function, type) combination is encountered. struct Monomorphizer<'interner> { - /// Functions are keyed by their unique ID and expected type so that we can monomorphize - /// a new version of the function for each type. + /// Globals are keyed by their unique ID and expected type so that we can monomorphize + /// a new version of the global for each type. Note that 'global' here means 'globally + /// visible' and thus includes both functions and global variables. /// /// Using nested HashMaps here lets us avoid cloning HirTypes when calling .get() - functions: HashMap>, + globals: HashMap>, - /// Unlike functions, locals are only keyed by their unique ID because they are never + /// Unlike globals, locals are only keyed by their unique ID because they are never /// duplicated during monomorphization. Doing so would allow them to be used polymorphically /// but would also cause them to be re-evaluated which is a performance trap that would /// confuse users. @@ -164,7 +165,7 @@ pub fn monomorphize_debug( impl<'interner> Monomorphizer<'interner> { fn new(interner: &'interner mut NodeInterner, debug_type_tracker: DebugTypeTracker) -> Self { Monomorphizer { - functions: HashMap::new(), + globals: HashMap::new(), locals: HashMap::new(), queue: VecDeque::new(), finished_functions: BTreeMap::new(), @@ -202,7 +203,7 @@ impl<'interner> Monomorphizer<'interner> { trait_method: Option, ) -> Definition { let typ = typ.follow_bindings(); - match self.functions.get(&id).and_then(|inner_map| inner_map.get(&typ)) { + match self.globals.get(&id).and_then(|inner_map| inner_map.get(&typ)) { Some(id) => Definition::Function(*id), None => { // Function has not been monomorphized yet @@ -250,8 +251,8 @@ impl<'interner> Monomorphizer<'interner> { } /// Prerequisite: typ = typ.follow_bindings() - fn define_function(&mut self, id: node_interner::FuncId, typ: HirType, new_id: FuncId) { - self.functions.entry(id).or_default().insert(typ, new_id); + fn define_global(&mut self, id: node_interner::FuncId, typ: HirType, new_id: FuncId) { + self.globals.entry(id).or_default().insert(typ, new_id); } fn compile_main( @@ -785,7 +786,7 @@ impl<'interner> Monomorphizer<'interner> { }) } - /// A local (ie non-function) ident only + /// A local (ie non-global) ident only fn local_ident( &mut self, ident: &HirIdent, @@ -1279,7 +1280,7 @@ impl<'interner> Monomorphizer<'interner> { trait_method: Option, ) -> FuncId { let new_id = self.next_function_id(); - self.define_function(id, function_type.clone(), new_id); + self.define_global(id, function_type.clone(), new_id); let bindings = self.interner.get_instantiation_bindings(expr_id); let bindings = self.follow_bindings(bindings); @@ -1552,7 +1553,9 @@ impl<'interner> Monomorphizer<'interner> { ast::Expression::Literal(ast::Literal::Integer(0_u128.into(), typ, location)) } ast::Type::Bool => ast::Expression::Literal(ast::Literal::Bool(false)), - ast::Type::Unit => ast::Expression::Literal(ast::Literal::Unit), + // There is no unit literal currently. Replace it with 'false' since it should be ignored + // anyway. + ast::Type::Unit => ast::Expression::Literal(ast::Literal::Bool(false)), ast::Type::Array(length, element_type) => { let element = self.zeroed_value_of_type(element_type.as_ref(), location); ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { diff --git a/compiler/noirc_frontend/src/monomorphization/printer.rs b/compiler/noirc_frontend/src/monomorphization/printer.rs index ea8f079cc2f..c253bfe7930 100644 --- a/compiler/noirc_frontend/src/monomorphization/printer.rs +++ b/compiler/noirc_frontend/src/monomorphization/printer.rs @@ -110,9 +110,6 @@ impl AstPrinter { s.fmt(f)?; write!(f, "\"") } - super::ast::Literal::Unit => { - write!(f, "()") - } } } diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 84eb2d77315..ffd760d6d7f 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -917,10 +917,6 @@ impl NodeInterner { self.id_location(expr_id) } - pub fn statement_span(&self, stmt_id: &StmtId) -> Span { - self.id_location(stmt_id).span - } - pub fn get_struct(&self, id: StructId) -> Shared { self.structs[&id].clone() } diff --git a/compiler/noirc_frontend/src/noir_parser.lalrpop b/compiler/noirc_frontend/src/noir_parser.lalrpop deleted file mode 100644 index c8d293fb72f..00000000000 --- a/compiler/noirc_frontend/src/noir_parser.lalrpop +++ /dev/null @@ -1,164 +0,0 @@ -use noirc_errors::Span; - -use crate::lexer::token::BorrowedToken; -use crate::lexer::token as noir_token; -use crate::lexer::errors::LexerErrorKind; -use crate::parser::TopLevelStatement; -use crate::{Ident, Path, PathKind, UseTree, UseTreeKind}; - -use lalrpop_util::ErrorRecovery; - -grammar<'input, 'err>(input: &'input str, errors: &'err mut [ErrorRecovery, &'static str>]); - -extern { - type Location = usize; - - type Error = LexerErrorKind; - - // NOTE: each token needs a terminal defined - enum BorrowedToken<'input> { - string => BorrowedToken::Str(<&'input str>), - ident => BorrowedToken::Ident(<&'input str>), - - // symbols - "<" => BorrowedToken::Less, - "<=" => BorrowedToken::LessEqual, - ">" => BorrowedToken::Greater, - ">=" => BorrowedToken::GreaterEqual, - "==" => BorrowedToken::Equal, - "!=" => BorrowedToken::NotEqual, - "+" => BorrowedToken::Plus, - "-" => BorrowedToken::Minus, - "*" => BorrowedToken::Star, - "/" => BorrowedToken::Slash, - "%" => BorrowedToken::Percent, - "&" => BorrowedToken::Ampersand, - "^" => BorrowedToken::Caret, - "<<" => BorrowedToken::ShiftLeft, - ">>" => BorrowedToken::ShiftRight, - "." => BorrowedToken::Dot, - ".." => BorrowedToken::DoubleDot, - "(" => BorrowedToken::LeftParen, - ")" => BorrowedToken::RightParen, - "{" => BorrowedToken::LeftBrace, - "}" => BorrowedToken::RightBrace, - "[" => BorrowedToken::LeftBracket, - "]" => BorrowedToken::RightBracket, - "->" => BorrowedToken::Arrow, - "|" => BorrowedToken::Pipe, - "#" => BorrowedToken::Pound, - "," => BorrowedToken::Comma, - ":" => BorrowedToken::Colon, - "::" => BorrowedToken::DoubleColon, - ";" => BorrowedToken::Semicolon, - "!" => BorrowedToken::Bang, - "=" => BorrowedToken::Assign, - // keywords - "as" => BorrowedToken::Keyword(noir_token::Keyword::As), - "assert" => BorrowedToken::Keyword(noir_token::Keyword::Assert), - "assert_eq" => BorrowedToken::Keyword(noir_token::Keyword::AssertEq), - "bool" => BorrowedToken::Keyword(noir_token::Keyword::Bool), - "break" => BorrowedToken::Keyword(noir_token::Keyword::Break), - "call_data" => BorrowedToken::Keyword(noir_token::Keyword::CallData), - "char" => BorrowedToken::Keyword(noir_token::Keyword::Char), - "comptime" => BorrowedToken::Keyword(noir_token::Keyword::CompTime), - "constrain" => BorrowedToken::Keyword(noir_token::Keyword::Constrain), - "continue" => BorrowedToken::Keyword(noir_token::Keyword::Continue), - "contract" => BorrowedToken::Keyword(noir_token::Keyword::Contract), - "crate" => BorrowedToken::Keyword(noir_token::Keyword::Crate), - "dep" => BorrowedToken::Keyword(noir_token::Keyword::Dep), - "distinct" => BorrowedToken::Keyword(noir_token::Keyword::Distinct), - "else" => BorrowedToken::Keyword(noir_token::Keyword::Else), - "Field" => BorrowedToken::Keyword(noir_token::Keyword::Field), - "fn" => BorrowedToken::Keyword(noir_token::Keyword::Fn), - "for" => BorrowedToken::Keyword(noir_token::Keyword::For), - "fmtstr" => BorrowedToken::Keyword(noir_token::Keyword::FormatString), - "global" => BorrowedToken::Keyword(noir_token::Keyword::Global), - "if" => BorrowedToken::Keyword(noir_token::Keyword::If), - "impl" => BorrowedToken::Keyword(noir_token::Keyword::Impl), - "in" => BorrowedToken::Keyword(noir_token::Keyword::In), - "let" => BorrowedToken::Keyword(noir_token::Keyword::Let), - "mod" => BorrowedToken::Keyword(noir_token::Keyword::Mod), - "mut" => BorrowedToken::Keyword(noir_token::Keyword::Mut), - "pub" => BorrowedToken::Keyword(noir_token::Keyword::Pub), - "quote" => BorrowedToken::Keyword(noir_token::Keyword::Quote), - "return" => BorrowedToken::Keyword(noir_token::Keyword::Return), - "return_data" => BorrowedToken::Keyword(noir_token::Keyword::ReturnData), - "str" => BorrowedToken::Keyword(noir_token::Keyword::String), - "struct" => BorrowedToken::Keyword(noir_token::Keyword::Struct), - "trait" => BorrowedToken::Keyword(noir_token::Keyword::Trait), - "type" => BorrowedToken::Keyword(noir_token::Keyword::Type), - "unchecked" => BorrowedToken::Keyword(noir_token::Keyword::Unchecked), - "unconstrained" => BorrowedToken::Keyword(noir_token::Keyword::Unconstrained), - "use" => BorrowedToken::Keyword(noir_token::Keyword::Use), - "where" => BorrowedToken::Keyword(noir_token::Keyword::Where), - "while" => BorrowedToken::Keyword(noir_token::Keyword::While), - // bool - "true" => BorrowedToken::Bool(true), - "false" => BorrowedToken::Bool(false), - - r"[\t\r\n ]+" => BorrowedToken::Whitespace(_), - - EOF => BorrowedToken::EOF, - } -} - -pub(crate) TopLevelStatement: TopLevelStatement = { - "use" r"[\t\r\n ]+" ";" EOF => { - TopLevelStatement::Import(use_tree) - } -} - -UseTree: UseTree = { - // path::to::ident as SomeAlias - => { - let ident = prefix.pop(); - let kind = UseTreeKind::Path(ident, alias); - UseTree { prefix, kind } - }, -} - -pub(crate) Path: Path = { - "crate" "::" => { - let kind = PathKind::Crate; - let span = Span::from(lo as u32..hi as u32); - Path { segments, kind, span } - }, - - "dep" "::" => { - let kind = PathKind::Dep; - let span = Span::from(lo as u32..hi as u32); - Path { segments, kind, span } - }, - - => { - segments.insert(0, id); - let kind = PathKind::Plain; - let span = Span::from(lo as u32..hi as u32); - Path { segments, kind, span } - }, -} - -PathSegments: Vec = { - )*> => { - segments - } -} - -Alias: Ident = { - r"[\t\r\n ]+" "as" r"[\t\r\n ]+" => <>, -} - -Ident: Ident = { - => { - let token = noir_token::Token::Ident(i.to_string()); - let span = Span::from(lo as u32..hi as u32); - Ident::from_token(token, span) - }, -} - -Bool: BorrowedToken<'input> = { - "true" => BorrowedToken::Bool(true), - "false" => BorrowedToken::Bool(false), -}; - diff --git a/compiler/noirc_frontend/src/parser/errors.rs b/compiler/noirc_frontend/src/parser/errors.rs index 895d4e07bbd..43a1f96f13f 100644 --- a/compiler/noirc_frontend/src/parser/errors.rs +++ b/compiler/noirc_frontend/src/parser/errors.rs @@ -110,31 +110,25 @@ impl ParserError { impl std::fmt::Display for ParserError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let reason_str: String = if self.reason.is_none() { - "".to_string() - } else { - format!("\nreason: {}", Diagnostic::from(self.clone())) - }; let mut expected = vecmap(&self.expected_tokens, ToString::to_string); expected.append(&mut vecmap(&self.expected_labels, |label| format!("{label}"))); if expected.is_empty() { - write!(f, "Unexpected {} in input{}", self.found, reason_str) + write!(f, "Unexpected {} in input", self.found) } else if expected.len() == 1 { let first = expected.first().unwrap(); let vowel = "aeiou".contains(first.chars().next().unwrap()); write!( f, - "Expected a{} {} but found {}{}", + "Expected a{} {} but found {}", if vowel { "n" } else { "" }, first, - self.found, - reason_str + self.found ) } else { let expected = expected.iter().map(ToString::to_string).collect::>().join(", "); - write!(f, "Unexpected {}, expected one of {}{}", self.found, expected, reason_str) + write!(f, "Unexpected {}, expected one of {}", self.found, expected) } } } diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 1bedf904da2..0a21465fe87 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -35,7 +35,7 @@ use super::{spanned, Item, ItemKind}; use crate::ast::{ Expression, ExpressionKind, LetStatement, StatementKind, UnresolvedType, UnresolvedTypeData, }; -use crate::lexer::{lexer::from_spanned_token_result, Lexer}; +use crate::lexer::Lexer; use crate::parser::{force, ignore_then_commit, statement_recovery}; use crate::token::{Keyword, Token, TokenKind}; use crate::{ @@ -47,7 +47,6 @@ use crate::{ use chumsky::prelude::*; use iter_extended::vecmap; -use lalrpop_util::lalrpop_mod; use noirc_errors::{Span, Spanned}; mod assertion; @@ -60,9 +59,6 @@ mod primitives; mod structs; mod traits; -// synthesized by LALRPOP -lalrpop_mod!(pub noir_parser); - #[cfg(test)] mod test_helpers; @@ -81,79 +77,8 @@ pub fn parse_program(source_program: &str) -> (ParsedModule, Vec) { let (module, mut parsing_errors) = program().parse_recovery_verbose(tokens); parsing_errors.extend(lexing_errors.into_iter().map(Into::into)); - let parsed_module = module.unwrap_or(ParsedModule { items: vec![] }); - - if cfg!(feature = "experimental_parser") { - for parsed_item in &parsed_module.items { - if lalrpop_parser_supports_kind(&parsed_item.kind) { - match &parsed_item.kind { - ItemKind::Import(parsed_use_tree) => { - prototype_parse_use_tree(Some(parsed_use_tree), source_program); - } - // other kinds prevented by lalrpop_parser_supports_kind - _ => unreachable!(), - } - } - } - } - (parsed_module, parsing_errors) -} - -fn prototype_parse_use_tree(expected_use_tree_opt: Option<&UseTree>, input: &str) { - // TODO(https://github.com/noir-lang/noir/issues/4777): currently skipping - // recursive use trees, e.g. "use std::{foo, bar}" - if input.contains('{') { - return; - } - - let mut lexer = Lexer::new(input); - lexer = lexer.skip_whitespaces(false); - let mut errors = Vec::new(); - - // NOTE: this is a hack to get the references working - // => this likely means that we'll want to propagate the <'input> lifetime further into Token - let lexer_result = lexer.collect::>(); - let referenced_lexer_result = lexer_result.iter().map(from_spanned_token_result); - - let calculated = noir_parser::TopLevelStatementParser::new().parse( - input, - &mut errors, - referenced_lexer_result, - ); - - if let Some(expected_use_tree) = expected_use_tree_opt { - assert!( - calculated.is_ok(), - "calculated not Ok(_): {:?}\n\nlexer: {:?}\n\ninput: {:?}", - calculated, - lexer_result, - input - ); - - match calculated.unwrap() { - TopLevelStatement::Import(parsed_use_tree) => { - assert_eq!(expected_use_tree, &parsed_use_tree); - } - unexpected_calculated => { - panic!( - "expected a TopLevelStatement::Import, but found: {:?}", - unexpected_calculated - ) - } - } - } else { - assert!( - calculated.is_err(), - "calculated not Err(_): {:?}\n\nlexer: {:?}\n\ninput: {:?}", - calculated, - lexer_result, - input - ); - } -} -fn lalrpop_parser_supports_kind(kind: &ItemKind) -> bool { - matches!(kind, ItemKind::Import(_)) + (module.unwrap_or(ParsedModule { items: vec![] }), parsing_errors) } /// program: module EOF @@ -1587,53 +1512,33 @@ mod test { #[test] fn parse_use() { - let mut valid_use_statements = vec![ - "use std::hash", - "use std", - "use foo::bar as hello", - "use bar as bar", - "use foo::{}", - "use foo::{bar,}", - "use foo::{bar, hello}", - "use foo::{bar as bar2, hello}", - "use foo::{bar as bar2, hello::{foo}, nested::{foo, bar}}", - "use dep::{std::println, bar::baz}", - ]; - - let mut invalid_use_statements = vec![ - "use std as ;", - "use foobar as as;", - "use hello:: as foo;", - "use foo bar::baz", - "use foo bar::{baz}", - "use foo::{,}", - ]; - - let use_statements = valid_use_statements - .iter_mut() - .map(|valid_str| (valid_str, true)) - .chain(invalid_use_statements.iter_mut().map(|invalid_str| (invalid_str, false))); - - for (use_statement_str, expect_valid) in use_statements { - let mut use_statement_str = use_statement_str.to_string(); - let expected_use_statement = if expect_valid { - let (result_opt, _diagnostics) = - parse_recover(&use_statement(), &use_statement_str); - use_statement_str.push(';'); - match result_opt.unwrap() { - TopLevelStatement::Import(expected_use_statement) => { - Some(expected_use_statement) - } - _ => unreachable!(), - } - } else { - let result = parse_with(&use_statement(), &use_statement_str); - assert!(result.is_err()); - None - }; + parse_all( + use_statement(), + vec![ + "use std::hash", + "use std", + "use foo::bar as hello", + "use bar as bar", + "use foo::{}", + "use foo::{bar,}", + "use foo::{bar, hello}", + "use foo::{bar as bar2, hello}", + "use foo::{bar as bar2, hello::{foo}, nested::{foo, bar}}", + "use dep::{std::println, bar::baz}", + ], + ); - prototype_parse_use_tree(expected_use_statement.as_ref(), &use_statement_str); - } + parse_all_failing( + use_statement(), + vec![ + "use std as ;", + "use foobar as as;", + "use hello:: as foo;", + "use foo bar::baz", + "use foo bar::{baz}", + "use foo::{,}", + ], + ); } #[test] diff --git a/compiler/wasm/src/types/noir_artifact.ts b/compiler/wasm/src/types/noir_artifact.ts index 6ecc3ccd56f..f241b539dc7 100644 --- a/compiler/wasm/src/types/noir_artifact.ts +++ b/compiler/wasm/src/types/noir_artifact.ts @@ -151,16 +151,6 @@ export interface DebugInfo { locations: Record; } -/** - * The debug information for a given program. - */ -export interface ProgramDebugInfo { - /** - * An array that maps to each function of a program. - */ - debug_infos: Array; -} - /** * Maps a file ID to its metadata for debugging purposes. */ diff --git a/compiler/wasm/test/compiler/shared/compile.test.ts b/compiler/wasm/test/compiler/shared/compile.test.ts index f9e37530cbc..52cef14968b 100644 --- a/compiler/wasm/test/compiler/shared/compile.test.ts +++ b/compiler/wasm/test/compiler/shared/compile.test.ts @@ -5,7 +5,6 @@ import { ContractCompilationArtifacts, DebugFileMap, DebugInfo, - ProgramDebugInfo, NoirFunctionEntry, ProgramArtifact, ProgramCompilationArtifacts, @@ -16,7 +15,7 @@ export function shouldCompileProgramIdentically( expect: typeof Expect, timeout = 5000, ) { - it('both nargo and noir_wasm should compile program identically', async () => { + it('both nargo and noir_wasm should compile identically', async () => { // Compile! const { nargoArtifact, noirWasmArtifact } = await compileFn(); @@ -52,7 +51,7 @@ export function shouldCompileContractIdentically( expect: typeof Expect, timeout = 5000, ) { - it('both nargo and noir_wasm should compile contract identically', async () => { + it('both nargo and noir_wasm should compile identically', async () => { // Compile! const { nargoArtifact, noirWasmArtifact } = await compileFn(); @@ -91,7 +90,7 @@ function extractDebugInfos(fns: NoirFunctionEntry[]) { return fns.map((fn) => { const debugSymbols = inflateDebugSymbols(fn.debug_symbols); delete (fn as Partial).debug_symbols; - clearFileIdentifiersProgram(debugSymbols); + clearFileIdentifiers(debugSymbols); return debugSymbols; }); } @@ -114,12 +113,6 @@ function deleteContractDebugMetadata(contract: ContractArtifact) { return [extractDebugInfos(contract.functions), fileMap]; } -function clearFileIdentifiersProgram(debugSymbols: ProgramDebugInfo) { - debugSymbols.debug_infos.map((debug_info) => { - clearFileIdentifiers(debug_info); - }); -} - /** Clears file identifiers from a set of debug symbols. */ function clearFileIdentifiers(debugSymbols: DebugInfo) { for (const loc of Object.values(debugSymbols.locations)) { diff --git a/cspell.json b/cspell.json index bf3040265c2..16de9757fb8 100644 --- a/cspell.json +++ b/cspell.json @@ -113,7 +113,6 @@ "Maddiaa", "mathbb", "memfs", - "memset", "merkle", "metas", "minreq", diff --git a/deny.toml b/deny.toml index db7e53cad24..eff233687e8 100644 --- a/deny.toml +++ b/deny.toml @@ -58,7 +58,7 @@ allow = [ # bitmaps 2.1.0, im 15.1.0 "MPL-2.0", # Boost Software License - "BSL-1.0" + "BSL-1.0", ] # Allow 1 or more licenses on a per-crate basis, so that particular licenses @@ -70,7 +70,6 @@ exceptions = [ { allow = ["CC0-1.0"], name = "more-asserts" }, { allow = ["CC0-1.0"], name = "jsonrpc" }, { allow = ["CC0-1.0"], name = "notify" }, - { allow = ["CC0-1.0"], name = "tiny-keccak" }, { allow = ["MPL-2.0"], name = "sized-chunks" }, { allow = ["MPL-2.0"], name = "webpki-roots" }, diff --git a/docs/docs/reference/debugger/_category_.json b/docs/docs/getting_started/tooling/_category_.json similarity index 53% rename from docs/docs/reference/debugger/_category_.json rename to docs/docs/getting_started/tooling/_category_.json index 27869205ad3..55804c03a71 100644 --- a/docs/docs/reference/debugger/_category_.json +++ b/docs/docs/getting_started/tooling/_category_.json @@ -1,6 +1,6 @@ { - "label": "Debugger", - "position": 1, + "position": 2, + "label": "Tooling", "collapsible": true, "collapsed": true } diff --git a/docs/docs/getting_started/tooling/index.mdx b/docs/docs/getting_started/tooling/index.mdx new file mode 100644 index 00000000000..ac480f3c9f5 --- /dev/null +++ b/docs/docs/getting_started/tooling/index.mdx @@ -0,0 +1,38 @@ +--- +title: Tooling +Description: This section provides information about the various tools and utilities available for Noir development. It covers the Noir playground, IDE tools, Codespaces, and community projects. +Keywords: [Noir, Development, Playground, IDE Tools, Language Service Provider, VS Code Extension, Codespaces, noir-starter, Community Projects, Awesome Noir Repository, Developer Tooling] +--- + +Noir is meant to be easy to develop with. For that reason, a number of utilities have been put together to ease the development process as much as feasible in the zero-knowledge world. + +## Playground + +The Noir playground is an easy way to test small ideas, share snippets, and integrate in other websites. You can access it at [play.noir-lang.org](https://play.noir-lang.org). + +## IDE tools + +When you install Nargo, you're also installing a Language Service Provider (LSP), which can be used by IDEs to provide syntax highlighting, codelens, warnings, and more. + +The easiest way to use these tools is by installing the [Noir VS Code extension](https://marketplace.visualstudio.com/items?itemName=noir-lang.vscode-noir). + +## Codespaces + +Some Noir repos have leveraged Codespaces in order to ease the development process. You can visit the [noir-starter](https://github.com/noir-lang/noir-starter) for an example. + + + +## GitHub Actions + +You can use `noirup` with GitHub Actions for CI/CD and automated testing. It is as simple as +installing `noirup` and running tests in your GitHub Action `yml` file. + +See the +[config file in the Noir repo](https://github.com/TomAFrench/noir-hashes/blob/master/.github/workflows/noir.yml) for an example usage. + +## Community projects + +As an open-source project, Noir has received many contributions over time. Some of them are related with developer tooling, and you can see some of them in [Awesome Noir repository](https://github.com/noir-lang/awesome-noir#dev-tools) diff --git a/docs/docs/tooling/language_server.md b/docs/docs/getting_started/tooling/language_server.md similarity index 100% rename from docs/docs/tooling/language_server.md rename to docs/docs/getting_started/tooling/language_server.md diff --git a/docs/docs/tooling/testing.md b/docs/docs/getting_started/tooling/testing.md similarity index 100% rename from docs/docs/tooling/testing.md rename to docs/docs/getting_started/tooling/testing.md diff --git a/docs/docs/how_to/debugger/_category_.json b/docs/docs/how_to/debugger/_category_.json deleted file mode 100644 index cc2cbb1c253..00000000000 --- a/docs/docs/how_to/debugger/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "label": "Debugging", - "position": 5, - "collapsible": true, - "collapsed": true -} diff --git a/docs/docs/how_to/debugger/debugging_with_the_repl.md b/docs/docs/how_to/debugger/debugging_with_the_repl.md deleted file mode 100644 index 09e5bae68ad..00000000000 --- a/docs/docs/how_to/debugger/debugging_with_the_repl.md +++ /dev/null @@ -1,164 +0,0 @@ ---- -title: Using the REPL Debugger -description: - Step by step guide on how to debug your Noir circuits with the REPL Debugger. -keywords: - [ - Nargo, - Noir CLI, - Noir Debugger, - REPL, - ] -sidebar_position: 1 ---- - -#### Pre-requisites - -In order to use the REPL debugger, first you need to install recent enough versions of Nargo and vscode-noir. - -## Debugging a simple circuit - -Let's debug a simple circuit: - -```rust -fn main(x : Field, y : pub Field) { - assert(x != y); -} -``` - -To start the REPL debugger, using a terminal, go to a Noir circuit's home directory. Then: - -`$ nargo debug` - -You should be seeing this in your terminal: - -``` -[main] Starting debugger -At ~/noir-examples/recursion/circuits/main/src/main.nr:1:9 - 1 -> fn main(x : Field, y : pub Field) { - 2 assert(x != y); - 3 } -> -``` - -The debugger displays the current Noir code location, and it is now waiting for us to drive it. - -Let's first take a look at the available commands. For that we'll use the `help` command. - -``` -> help -Available commands: - - opcodes display ACIR opcodes - into step into to the next opcode - next step until a new source location is reached - out step until a new source location is reached - and the current stack frame is finished - break LOCATION:OpcodeLocation add a breakpoint at an opcode location - over step until a new source location is reached - without diving into function calls - restart restart the debugging session - delete LOCATION:OpcodeLocation delete breakpoint at an opcode location - witness show witness map - witness index:u32 display a single witness from the witness map - witness index:u32 value:String update a witness with the given value - memset index:usize value:String update a memory cell with the given - value - continue continue execution until the end of the - program - vars show variable values available at this point - in execution - stacktrace display the current stack trace - memory show memory (valid when executing unconstrained code) - step step to the next ACIR opcode - -Other commands: - - help Show this help message - quit Quit repl - -``` - -Some commands operate only for unconstrained functions, such as `memory` and `memset`. If you try to use them while execution is paused at an ACIR opcode, the debugger will simply inform you that you are not executing unconstrained code: - -``` -> memory -Unconstrained VM memory not available -> -``` - -Before continuing, we can take a look at the initial witness map: - -``` -> witness -_0 = 1 -_1 = 2 -> -``` - -Cool, since `x==1`, `y==2`, and we want to check that `x != y`, our circuit should succeed. At this point we could intervene and use the witness setter command to change one of the witnesses. Let's set `y=3`, then back to 2, so we don't affect the expected result: - -``` -> witness -_0 = 1 -_1 = 2 -> witness 1 3 -_1 = 3 -> witness -_0 = 1 -_1 = 3 -> witness 1 2 -_1 = 2 -> witness -_0 = 1 -_1 = 2 -> -``` - -Now we can inspect the current state of local variables. For that we use the `vars` command. - -``` -> vars -> -``` - -We currently have no vars in context, since we are at the entry point of the program. Let's use `next` to execute until the next point in the program. - -``` -> vars -> next -At ~/noir-examples/recursion/circuits/main/src/main.nr:1:20 - 1 -> fn main(x : Field, y : pub Field) { - 2 assert(x != y); - 3 } -> vars -x:Field = 0x01 -``` - -As a result of stepping, the variable `x`, whose initial value comes from the witness map, is now in context and returned by `vars`. - -``` -> next - 1 fn main(x : Field, y : pub Field) { - 2 -> assert(x != y); - 3 } -> vars -y:Field = 0x02 -x:Field = 0x01 -``` - -Stepping again we can finally see both variables and their values. And now we can see that the next assertion should succeed. - -Let's continue to the end: - -``` -> continue -(Continuing execution...) -Finished execution -> q -[main] Circuit witness successfully solved -``` - -Upon quitting the debugger after a solved circuit, the resulting circuit witness gets saved, equivalent to what would happen if we had run the same circuit with `nargo execute`. - -We just went through the basics of debugging using Noir REPL debugger. For a comprehensive reference, check out [the reference page](../../reference/debugger/debugger_repl.md). diff --git a/docs/docs/how_to/debugger/debugging_with_vs_code.md b/docs/docs/how_to/debugger/debugging_with_vs_code.md deleted file mode 100644 index a5858c1a5eb..00000000000 --- a/docs/docs/how_to/debugger/debugging_with_vs_code.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: Using the VS Code Debugger -description: - Step by step guide on how to debug your Noir circuits with the VS Code Debugger configuration and features. -keywords: - [ - Nargo, - Noir CLI, - Noir Debugger, - VS Code, - IDE, - ] -sidebar_position: 0 ---- - -This guide will show you how to use VS Code with the vscode-noir extension to debug a Noir project. - -#### Pre-requisites - -- Nargo -- vscode-noir -- A Noir project with a `Nargo.toml`, `Prover.toml` and at least one Noir (`.nr`) containing an entry point function (typically `main`). - -## Running the debugger - -The easiest way to start debugging is to open the file you want to debug, and press `F5`. This will cause the debugger to launch, using your `Prover.toml` file as input. - -You should see something like this: - -![Debugger launched](@site/static/img/debugger/1-started.png) - -Let's inspect the state of the program. For that, we open VS Code's _Debug pane_. Look for this icon: - -![Debug pane icon](@site/static/img/debugger/2-icon.png) - -You will now see two categories of variables: Locals and Witness Map. - -![Debug pane expanded](@site/static/img/debugger/3-debug-pane.png) - -1. **Locals**: variables of your program. At this point in execution this section is empty, but as we step through the code it will get populated by `x`, `result`, `digest`, etc. - -2. **Witness map**: these are initially populated from your project's `Prover.toml` file. In this example, they will be used to populate `x` and `result` at the beginning of the `main` function. - -Most of the time you will probably be focusing mostly on locals, as they represent the high level state of your program. - -You might be interested in inspecting the witness map in case you are trying to solve a really low level issue in the compiler or runtime itself, so this concerns mostly advanced or niche users. - -Let's step through the program, by using the debugger buttons or their corresponding keyboard shortcuts. - -![Debugger buttons](@site/static/img/debugger/4-debugger-buttons.png) - -Now we can see in the variables pane that there's values for `digest`, `result` and `x`. - -![Inspecting locals](@site/static/img/debugger/5-assert.png) - -We can also inspect the values of variables by directly hovering on them on the code. - -![Hover locals](@site/static/img/debugger/6-hover.png) - -Let's set a break point at the `keccak256` function, so we can continue execution up to the point when it's first invoked without having to go one step at a time. - -We just need to click the to the right of the line number 18. Once the breakpoint appears, we can click the `continue` button or use its corresponding keyboard shortcut (`F5` by default). - -![Breakpoint](@site/static/img/debugger/7-break.png) - -Now we are debugging the `keccak256` function, notice the _Call Stack pane_ at the lower right. This lets us inspect the current call stack of our process. - -That covers most of the current debugger functionalities. Check out [the reference](../../reference/debugger/debugger_vscode.md) for more details on how to configure the debugger. \ No newline at end of file diff --git a/docs/docs/how_to/merkle-proof.mdx b/docs/docs/how_to/merkle-proof.mdx index 16c425bed76..003c7019a93 100644 --- a/docs/docs/how_to/merkle-proof.mdx +++ b/docs/docs/how_to/merkle-proof.mdx @@ -5,7 +5,6 @@ description: merkle tree with a specified root, at a given index. keywords: [merkle proof, merkle membership proof, Noir, rust, hash function, Pedersen, sha256, merkle tree] -sidebar_position: 4 --- Let's walk through an example of a merkle membership proof in Noir that proves that a given leaf is diff --git a/docs/docs/noir/concepts/functions.md b/docs/docs/noir/concepts/functions.md index f656cdfd97a..2c9bc33fdfc 100644 --- a/docs/docs/noir/concepts/functions.md +++ b/docs/docs/noir/concepts/functions.md @@ -62,7 +62,7 @@ fn main(x : [Field]) // can't compile, has variable size fn main(....// i think you got it by now ``` -Keep in mind [tests](../../tooling/testing.md) don't differentiate between `main` and any other function. The following snippet passes tests, but won't compile or prove: +Keep in mind [tests](../../getting_started/tooling/testing.md) don't differentiate between `main` and any other function. The following snippet passes tests, but won't compile or prove: ```rust fn main(x : [Field]) { @@ -190,7 +190,7 @@ Supported attributes include: - **deprecated**: mark the function as _deprecated_. Calling the function will generate a warning: `warning: use of deprecated function` - **field**: Used to enable conditional compilation of code depending on the field size. See below for more details - **oracle**: mark the function as _oracle_; meaning it is an external unconstrained function, implemented in noir_js. See [Unconstrained](./unconstrained.md) and [NoirJS](../../reference/NoirJS/noir_js/index.md) for more details. -- **test**: mark the function as unit tests. See [Tests](../../tooling/testing.md) for more details +- **test**: mark the function as unit tests. See [Tests](../../getting_started/tooling/testing.md) for more details ### Field Attribute diff --git a/docs/docs/noir/concepts/oracles.md b/docs/docs/noir/concepts/oracles.md index aa380b5f7b8..2e6a6818d48 100644 --- a/docs/docs/noir/concepts/oracles.md +++ b/docs/docs/noir/concepts/oracles.md @@ -11,12 +11,6 @@ keywords: sidebar_position: 6 --- -:::note - -This is an experimental feature that is not fully documented. If you notice any outdated information or potential improvements to this page, pull request contributions are very welcome: https://github.com/noir-lang/noir - -::: - Noir has support for Oracles via RPC calls. This means Noir will make an RPC call and use the return value for proof generation. Since Oracles are not resolved by Noir, they are [`unconstrained` functions](./unconstrained.md) @@ -27,5 +21,3 @@ You can declare an Oracle through the `#[oracle()]` flag. Example: #[oracle(get_number_sequence)] unconstrained fn get_number_sequence(_size: Field) -> [Field] {} ``` - -The timeout for when using an external RPC oracle resolver can be set with the `NARGO_FOREIGN_CALL_TIMEOUT` environment variable. This timeout is in units of milliseconds. diff --git a/docs/docs/reference/debugger/debugger_known_limitations.md b/docs/docs/reference/debugger/debugger_known_limitations.md deleted file mode 100644 index 936d416ac4b..00000000000 --- a/docs/docs/reference/debugger/debugger_known_limitations.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: Known limitations -description: - An overview of known limitations of the current version of the Noir debugger -keywords: - [ - Nargo, - Noir Debugger, - VS Code, - ] -sidebar_position: 2 ---- - -# Debugger Known Limitations - -There are currently some limits to what the debugger can observe. - -## Mutable references - -The debugger is currently blind to any state mutated via a mutable reference. For example, in: - -``` -let mut x = 1; -let y = &mut x; -*y = 2; -``` - -The update on `x` will not be observed by the debugger. That means, when running `vars` from the debugger REPL, or inspecting the _local variables_ pane in the VS Code debugger, `x` will appear with value 1 despite having executed `*y = 2;`. - -## Variables of type function or mutable references are opaque - -When inspecting variables, any variable of type `Function` or `MutableReference` will render its value as `<>` or `<>`. - -## Debugger instrumentation affects resulting ACIR - -In order to make the state of local variables observable, the debugger compiles Noir circuits interleaving foreign calls that track any mutations to them. While this works (except in the cases described above) and doesn't introduce any behavior changes, it does as a side effect produce bigger bytecode. In particular, when running the command `opcodes` on the REPL debugger, you will notice Unconstrained VM blocks that look like this: - -``` -... -5 BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [], q_c: 2 }), Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(2))], q_c: 0 })] - | outputs=[] - 5.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } - 5.1 | Mov { destination: RegisterIndex(3), source: RegisterIndex(1) } - 5.2 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } - 5.3 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } - 5.4 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } - 5.5 | Mov { destination: RegisterIndex(3), source: RegisterIndex(3) } - 5.6 | Call { location: 8 } - 5.7 | Stop - 5.8 | ForeignCall { function: "__debug_var_assign", destinations: [], inputs: [RegisterIndex(RegisterIndex(2)), RegisterIndex(RegisterIndex(3))] } -... -``` - -If you are interested in debugging/inspecting compiled ACIR without these synthetic changes, you can invoke the REPL debugger with the `--skip-instrumentation` flag or launch the VS Code debugger with the `skipConfiguration` property set to true in its launch configuration. You can find more details about those in the [Debugger REPL reference](debugger_repl.md) and the [VS Code Debugger reference](debugger_vscode.md). - -:::note -Skipping debugger instrumentation means you won't be able to inspect values of local variables. -::: - diff --git a/docs/docs/reference/debugger/debugger_repl.md b/docs/docs/reference/debugger/debugger_repl.md deleted file mode 100644 index 46e2011304e..00000000000 --- a/docs/docs/reference/debugger/debugger_repl.md +++ /dev/null @@ -1,360 +0,0 @@ ---- -title: REPL Debugger -description: - Noir Debugger REPL options and commands. -keywords: - [ - Nargo, - Noir CLI, - Noir Debugger, - REPL, - ] -sidebar_position: 1 ---- - -## Running the REPL debugger - -`nargo debug [OPTIONS] [WITNESS_NAME]` - -Runs the Noir REPL debugger. If a `WITNESS_NAME` is provided the debugger writes the resulting execution witness to a `WITNESS_NAME` file. - -### Options - -| Option | Description | -| --------------------- | ------------------------------------------------------------ | -| `-p, --prover-name ` | The name of the toml file which contains the inputs for the prover [default: Prover]| -| `--package ` | The name of the package to debug | -| `--print-acir` | Display the ACIR for compiled circuit | -| `--deny-warnings` | Treat all warnings as errors | -| `--silence-warnings` | Suppress warnings | -| `-h, --help` | Print help | - -None of these options are required. - -:::note -Since the debugger starts by compiling the target package, all Noir compiler options are also available. Check out the [compiler reference](../nargo_commands.md#nargo-compile) to learn more about the compiler options. -::: - -## REPL commands - -Once the debugger is running, it accepts the following commands. - -#### `help` (h) - -Displays the menu of available commands. - -``` -> help -Available commands: - - opcodes display ACIR opcodes - into step into to the next opcode - next step until a new source location is reached - out step until a new source location is reached - and the current stack frame is finished - break LOCATION:OpcodeLocation add a breakpoint at an opcode location - over step until a new source location is reached - without diving into function calls - restart restart the debugging session - delete LOCATION:OpcodeLocation delete breakpoint at an opcode location - witness show witness map - witness index:u32 display a single witness from the witness map - witness index:u32 value:String update a witness with the given value - memset index:usize value:String update a memory cell with the given - value - continue continue execution until the end of the - program - vars show variable values available at this point - in execution - stacktrace display the current stack trace - memory show memory (valid when executing unconstrained code) value - step step to the next ACIR opcode - -Other commands: - - help Show this help message - quit Quit repl - -``` - -### Stepping through programs - -#### `next` (n) - -Step until the next Noir source code location. While other commands, such as [`into`](#into-i) and [`step`](#step-s), allow for finer grained control of the program's execution at the opcode level, `next` is source code centric. For example: - -``` -3 ... -4 fn main(x: u32) { -5 assert(entry_point(x) == 2); -6 swap_entry_point(x, x + 1); -7 -> assert(deep_entry_point(x) == 4); -8 multiple_values_entry_point(x); -9 } -``` - - -Using `next` here would cause the debugger to jump to the definition of `deep_entry_point` (if available). - -If you want to step over `deep_entry_point` and go straight to line 8, use [the `over` command](#over) instead. - -#### `over` - -Step until the next source code location, without diving into function calls. For example: - -``` -3 ... -4 fn main(x: u32) { -5 assert(entry_point(x) == 2); -6 swap_entry_point(x, x + 1); -7 -> assert(deep_entry_point(x) == 4); -8 multiple_values_entry_point(x); -9 } -``` - - -Using `over` here would cause the debugger to execute until line 8 (`multiple_values_entry_point(x);`). - -If you want to step into `deep_entry_point` instead, use [the `next` command](#next-n). - -#### `out` - -Step until the end of the current function call. For example: - -``` - 3 ... - 4 fn main(x: u32) { - 5 assert(entry_point(x) == 2); - 6 swap_entry_point(x, x + 1); - 7 -> assert(deep_entry_point(x) == 4); - 8 multiple_values_entry_point(x); - 9 } - 10 - 11 unconstrained fn returns_multiple_values(x: u32) -> (u32, u32, u32, u32) { - 12 ... - ... - 55 - 56 unconstrained fn deep_entry_point(x: u32) -> u32 { - 57 -> level_1(x + 1) - 58 } - -``` - -Running `out` here will resume execution until line 8. - -#### `step` (s) - -Skips to the next ACIR code. A compiled Noir program is a sequence of ACIR opcodes. However, an unconstrained VM opcode denotes the start of an unconstrained code block, to be executed by the unconstrained VM. For example (redacted for brevity): - -``` -0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] -1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] - 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } - 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } - 1.2 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } - 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } - 1.4 | Call { location: 7 } - ... - 1.43 | Return -2 EXPR [ (1, _1) -2 ] -``` - -The `->` here shows the debugger paused at an ACIR opcode: `BRILLIG`, at index 1, which denotes an unconstrained code block is about to start. - -Using the `step` command at this point would result in the debugger stopping at ACIR opcode 2, `EXPR`, skipping unconstrained computation steps. - -Use [the `into` command](#into-i) instead if you want to follow unconstrained computation step by step. - -#### `into` (i) - -Steps into the next opcode. A compiled Noir program is a sequence of ACIR opcodes. However, a BRILLIG opcode denotes the start of an unconstrained code block, to be executed by the unconstrained VM. For example (redacted for brevity): - -``` -0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] -1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] - 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } - 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } - 1.2 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } - 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } - 1.4 | Call { location: 7 } - ... - 1.43 | Return -2 EXPR [ (1, _1) -2 ] -``` - -The `->` here shows the debugger paused at an ACIR opcode: `BRILLIG`, at index 1, which denotes an unconstrained code block is about to start. - -Using the `into` command at this point would result in the debugger stopping at opcode 1.0, `Mov ...`, allowing the debugger user to follow unconstrained computation step by step. - -Use [the `step` command](#step-s) instead if you want to skip to the next ACIR code directly. - -#### `continue` (c) - -Continues execution until the next breakpoint, or the end of the program. - -#### `restart` (res) - -Interrupts execution, and restarts a new debugging session from scratch. - -#### `opcodes` (o) - -Display the program's ACIR opcode sequence. For example: - -``` -0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] -1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] - 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } - 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } - 1.2 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } - 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } - 1.4 | Call { location: 7 } - ... - 1.43 | Return -2 EXPR [ (1, _1) -2 ] -``` - -### Breakpoints - -#### `break [Opcode]` (or shorthand `b [Opcode]`) - -Sets a breakpoint on the specified opcode index. To get a list of the program opcode numbers, see [the `opcode` command](#opcodes-o). For example: - -``` -0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] -1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] - 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } - 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } - 1.2 | Const { destination: RegisterIndex(1), value: Value { inner: 0 } } - 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } - 1.4 | Call { location: 7 } - ... - 1.43 | Return -2 EXPR [ (1, _1) -2 ] -``` - -In this example, issuing a `break 1.2` command adds break on opcode 1.2, as denoted by the `*` character: - -``` -0 BLACKBOX::RANGE [(_0, num_bits: 32)] [ ] -1 -> BRILLIG inputs=[Single(Expression { mul_terms: [], linear_combinations: [(1, Witness(0))], q_c: 0 })] outputs=[Simple(Witness(1))] - 1.0 | Mov { destination: RegisterIndex(2), source: RegisterIndex(0) } - 1.1 | Const { destination: RegisterIndex(0), value: Value { inner: 0 } } - 1.2 | * Const { destination: RegisterIndex(1), value: Value { inner: 0 } } - 1.3 | Mov { destination: RegisterIndex(2), source: RegisterIndex(2) } - 1.4 | Call { location: 7 } - ... - 1.43 | Return -2 EXPR [ (1, _1) -2 ] -``` - -Running [the `continue` command](#continue-c) at this point would cause the debugger to execute the program until opcode 1.2. - -#### `delete [Opcode]` (or shorthand `d [Opcode]`) - -Deletes a breakpoint at an opcode location. Usage is analogous to [the `break` command](#). - -### Variable inspection - -#### vars - -Show variable values available at this point in execution. - -:::note -The ability to inspect variable values from the debugger depends on compilation to be run in a special debug instrumentation mode. This instrumentation weaves variable tracing code with the original source code. - -So variable value inspection comes at the expense of making the resulting ACIR bytecode bigger and harder to understand and optimize. - -If you find this compromise unacceptable, you can run the debugger with the flag `--skip-debug-instrumentation`. This will compile your circuit without any additional debug information, so the resulting ACIR bytecode will be identical to the one produced by standard Noir compilation. However, if you opt for this, the `vars` command will not be available while debugging. -::: - - -### Stacktrace - -#### `stacktrace` - -Displays the current stack trace. - - -### Witness map - -#### `witness` (w) - -Show witness map. For example: - -``` -_0 = 0 -_1 = 2 -_2 = 1 -``` - -#### `witness [Witness Index]` - -Display a single witness from the witness map. For example: - -``` -> witness 1 -_1 = 2 -``` - -#### `witness [Witness Index] [New value]` - -Overwrite the given index with a new value. For example: - -``` -> witness 1 3 -_1 = 3 -``` - - -### Unconstrained VM memory - -#### `memory` - -Show unconstrained VM memory state. For example: - -``` -> memory -At opcode 1.13: Store { destination_pointer: RegisterIndex(0), source: RegisterIndex(3) } -... -> registers -0 = 0 -1 = 10 -2 = 0 -3 = 1 -4 = 1 -5 = 2³² -6 = 1 -> into -At opcode 1.14: Const { destination: RegisterIndex(5), value: Value { inner: 1 } } -... -> memory -0 = 1 -> -``` - -In the example above: we start with clean memory, then step through a `Store` opcode which stores the value of register 3 (1) into the memory address stored in register 0 (0). Thus now `memory` shows memory address 0 contains value 1. - -:::note -This command is only functional while the debugger is executing unconstrained code. -::: - -#### `memset [Memory address] [New value]` - -Update a memory cell with the given value. For example: - -``` -> memory -0 = 1 -> memset 0 2 -> memory -0 = 2 -> memset 1 4 -> memory -0 = 2 -1 = 4 -> -``` - -:::note -This command is only functional while the debugger is executing unconstrained code. -::: \ No newline at end of file diff --git a/docs/docs/reference/debugger/debugger_vscode.md b/docs/docs/reference/debugger/debugger_vscode.md deleted file mode 100644 index c027332b3b0..00000000000 --- a/docs/docs/reference/debugger/debugger_vscode.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: VS Code Debugger -description: - VS Code Debugger configuration and features. -keywords: - [ - Nargo, - Noir CLI, - Noir Debugger, - VS Code, - IDE, - ] -sidebar_position: 0 ---- - -# VS Code Noir Debugger Reference - -The Noir debugger enabled by the vscode-noir extension ships with default settings such that the most common scenario should run without any additional configuration steps. - -These defaults can nevertheless be overridden by defining a launch configuration file. This page provides a reference for the properties you can override via a launch configuration file, as well as documenting the Nargo `dap` command, which is a dependency of the VS Code Noir debugger. - - -## Creating and editing launch configuration files - -To create a launch configuration file from VS Code, open the _debug pane_, and click on _create a launch.json file_. - -![Creating a launch configuration file](@site/static/img/debugger/ref1-create-launch.png) - -A `launch.json` file will be created, populated with basic defaults. - -### Noir Debugger launch.json properties - -#### projectFolder - -_String, optional._ - -Absolute path to the Nargo project to debug. By default, it is dynamically determined by looking for the nearest `Nargo.toml` file to the active file at the moment of launching the debugger. - -#### proverName - -_String, optional._ - -Name of the prover input to use. Defaults to `Prover`, which looks for a file named `Prover.toml` at the `projectFolder`. - -#### generateAcir - -_Boolean, optional._ - -If true, generate ACIR opcodes instead of unconstrained opcodes which will be closer to release binaries but less convenient for debugging. Defaults to `false`. - -#### skipInstrumentation - -_Boolean, optional._ - -Skips variables debugging instrumentation of code, making debugging less convenient but the resulting binary smaller and closer to production. Defaults to `false`. - -:::note -Skipping instrumentation causes the debugger to be unable to inspect local variables. -::: - -## `nargo dap [OPTIONS]` - -When run without any option flags, it starts the Nargo Debug Adapter Protocol server, which acts as the debugging backend for the VS Code Noir Debugger. - -All option flags are related to preflight checks. The Debug Adapter Protocol specifies how errors are to be informed from a running DAP server, but it doesn't specify mechanisms to communicate server initialization errors between the DAP server and its client IDE. - -Thus `nargo dap` ships with a _preflight check_ mode. If flag `--preflight-check` and the rest of the `--preflight-*` flags are provided, Nargo will run the same initialization routine except it will not start the DAP server. - -`vscode-noir` will then run `nargo dap` in preflight check mode first before a debugging session starts. If the preflight check ends in error, vscode-noir will present stderr and stdout output from this process through its own Output pane in VS Code. This makes it possible for users to diagnose what pieces of configuration might be wrong or missing in case of initialization errors. - -If the preflight check succeeds, `vscode-noir` proceeds to start the DAP server normally but running `nargo dap` without any additional flags. - -### Options - -| Option | Description | -| --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | -| `--preflight-check` | If present, dap runs in preflight check mode. | -| `--preflight-project-folder ` | Absolute path to the project to debug for preflight check. | -| `--preflight-prover-name ` | Name of prover file to use for preflight check | -| `--preflight-generate-acir` | Optional. If present, compile in ACIR mode while running preflight check. | -| `--preflight-skip-instrumentation` | Optional. If present, compile without introducing debug instrumentation while running preflight check. | -| `-h, --help` | Print help. | diff --git a/docs/docs/tooling/debugger.md b/docs/docs/tooling/debugger.md deleted file mode 100644 index 184c436068f..00000000000 --- a/docs/docs/tooling/debugger.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Debugger -description: Learn about the Noir Debugger, in its REPL or VS Code versions. -keywords: [Nargo, VSCode, Visual Studio Code, REPL, Debugger] -sidebar_position: 2 ---- - -# Noir Debugger - -There are currently two ways of debugging Noir programs: - -1. From VS Code, via the [vscode-noir](https://github.com/noir-lang/vscode-noir) extension. You can install it via the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=noir-lang.vscode-noir). -2. Via the REPL debugger, which ships with Nargo. - -In order to use either version of the debugger, you will need to install recent enough versions of Noir, [Nargo](../getting_started/installation) and vscode-noir: - -- Noir 0.xx -- Nargo 0.xx -- vscode-noir 0.xx - -:::info -At the moment, the debugger supports debugging binary projects, but not contracts. -::: - -We cover the VS Code Noir debugger more in depth in [its VS Code debugger how-to guide](../how_to/debugger/debugging_with_vs_code.md) and [the reference](../reference/debugger/debugger_vscode.md). - -The REPL debugger is discussed at length in [the REPL debugger how-to guide](../how_to/debugger/debugging_with_the_repl.md) and [the reference](../reference/debugger/debugger_repl.md). diff --git a/docs/sidebars.js b/docs/sidebars.js index cf7e852fed5..f1e79ba9ebc 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -65,11 +65,6 @@ export default { label: 'Reference', items: [{ type: 'autogenerated', dirName: 'reference' }], }, - { - type: 'category', - label: 'Tooling', - items: [{ type: 'autogenerated', dirName: 'tooling' }], - }, { type: 'html', value: '
', diff --git a/docs/static/img/debugger/1-started.png b/docs/static/img/debugger/1-started.png deleted file mode 100644 index 6f764d4e601..00000000000 Binary files a/docs/static/img/debugger/1-started.png and /dev/null differ diff --git a/docs/static/img/debugger/2-icon.png b/docs/static/img/debugger/2-icon.png deleted file mode 100644 index 31706670ccb..00000000000 Binary files a/docs/static/img/debugger/2-icon.png and /dev/null differ diff --git a/docs/static/img/debugger/3-debug-pane.png b/docs/static/img/debugger/3-debug-pane.png deleted file mode 100644 index 24c112da96f..00000000000 Binary files a/docs/static/img/debugger/3-debug-pane.png and /dev/null differ diff --git a/docs/static/img/debugger/4-debugger-buttons.png b/docs/static/img/debugger/4-debugger-buttons.png deleted file mode 100644 index 64c1e05be8a..00000000000 Binary files a/docs/static/img/debugger/4-debugger-buttons.png and /dev/null differ diff --git a/docs/static/img/debugger/5-assert.png b/docs/static/img/debugger/5-assert.png deleted file mode 100644 index 0bfed6562af..00000000000 Binary files a/docs/static/img/debugger/5-assert.png and /dev/null differ diff --git a/docs/static/img/debugger/6-hover.png b/docs/static/img/debugger/6-hover.png deleted file mode 100644 index 20579ec461e..00000000000 Binary files a/docs/static/img/debugger/6-hover.png and /dev/null differ diff --git a/docs/static/img/debugger/7-break.png b/docs/static/img/debugger/7-break.png deleted file mode 100644 index aca5121d722..00000000000 Binary files a/docs/static/img/debugger/7-break.png and /dev/null differ diff --git a/docs/static/img/debugger/debugger-intro.gif b/docs/static/img/debugger/debugger-intro.gif deleted file mode 100644 index 06e3b853555..00000000000 Binary files a/docs/static/img/debugger/debugger-intro.gif and /dev/null differ diff --git a/docs/static/img/debugger/ref1-create-launch.png b/docs/static/img/debugger/ref1-create-launch.png deleted file mode 100644 index 0b6cb8b3ec6..00000000000 Binary files a/docs/static/img/debugger/ref1-create-launch.png and /dev/null differ diff --git a/test_programs/execution_failure/fold_dyn_index_fail/Nargo.toml b/test_programs/execution_failure/fold_dyn_index_fail/Nargo.toml deleted file mode 100644 index e49a82cf0fb..00000000000 --- a/test_programs/execution_failure/fold_dyn_index_fail/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "fold_dyn_index_fail" -type = "bin" -authors = [""] -compiler_version = ">=0.26.0" - -[dependencies] \ No newline at end of file diff --git a/test_programs/execution_failure/fold_dyn_index_fail/Prover.toml b/test_programs/execution_failure/fold_dyn_index_fail/Prover.toml deleted file mode 100644 index caf3448c56f..00000000000 --- a/test_programs/execution_failure/fold_dyn_index_fail/Prover.toml +++ /dev/null @@ -1,2 +0,0 @@ -x = [104, 101, 108, 108, 111] -z = "4" diff --git a/test_programs/execution_failure/fold_dyn_index_fail/src/main.nr b/test_programs/execution_failure/fold_dyn_index_fail/src/main.nr deleted file mode 100644 index b12dea630b0..00000000000 --- a/test_programs/execution_failure/fold_dyn_index_fail/src/main.nr +++ /dev/null @@ -1,10 +0,0 @@ -fn main(mut x: [u32; 5], z: Field) { - x[z] = 4; - dynamic_index_check(x, z + 10); -} - -#[fold] -fn dynamic_index_check(x: [u32; 5], idx: Field) { - // Dynamic index is greater than length of the array - assert(x[idx] != 0); -} diff --git a/test_programs/execution_failure/fold_nested_brillig_assert_fail/Nargo.toml b/test_programs/execution_failure/fold_nested_brillig_assert_fail/Nargo.toml deleted file mode 100644 index bb7d5e20dcc..00000000000 --- a/test_programs/execution_failure/fold_nested_brillig_assert_fail/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "fold_nested_brillig_assert_fail" -type = "bin" -authors = [""] -compiler_version = ">=0.26.0" - -[dependencies] \ No newline at end of file diff --git a/test_programs/execution_failure/fold_nested_brillig_assert_fail/Prover.toml b/test_programs/execution_failure/fold_nested_brillig_assert_fail/Prover.toml deleted file mode 100644 index 11497a473bc..00000000000 --- a/test_programs/execution_failure/fold_nested_brillig_assert_fail/Prover.toml +++ /dev/null @@ -1 +0,0 @@ -x = "0" diff --git a/test_programs/execution_failure/fold_nested_brillig_assert_fail/src/main.nr b/test_programs/execution_failure/fold_nested_brillig_assert_fail/src/main.nr deleted file mode 100644 index 0a5038c179b..00000000000 --- a/test_programs/execution_failure/fold_nested_brillig_assert_fail/src/main.nr +++ /dev/null @@ -1,26 +0,0 @@ -// Tests a very simple program. -// -// The features being tested is using assert on brillig that is triggered through nested ACIR calls. -// We want to make sure we get a call stack from the original call in main to the failed assert. -fn main(x: Field) { - assert(1 == fold_conditional_wrapper(x as bool)); -} - -#[fold] -fn fold_conditional_wrapper(x: bool) -> Field { - fold_conditional(x) -} - -#[fold] -fn fold_conditional(x: bool) -> Field { - conditional_wrapper(x) -} - -unconstrained fn conditional_wrapper(x: bool) -> Field { - conditional(x) -} - -unconstrained fn conditional(x: bool) -> Field { - assert(x); - 1 -} diff --git a/test_programs/execution_success/bigint/src/main.nr b/test_programs/execution_success/bigint/src/main.nr index 908cab8accc..db269d63ac0 100644 --- a/test_programs/execution_success/bigint/src/main.nr +++ b/test_programs/execution_success/bigint/src/main.nr @@ -10,11 +10,11 @@ fn main(mut x: [u8; 5], y: [u8; 5]) { assert(a_bytes[i] == x[i]); assert(b_bytes[i] == y[i]); } - //Regression for issue #4578 - let d = a * b; - assert(d / b == a); - big_int_example(x[0], x[1]); + let d = a * b - b; + let d1 = bigint::Secpk1Fq::from_le_bytes(597243850900842442924.to_le_bytes(10)); + assert(d1 == d); + // big_int_example(x[0], x[1]); } // docs:start:big_int_example diff --git a/test_programs/execution_success/fold_after_inlined_calls/Nargo.toml b/test_programs/execution_success/fold_after_inlined_calls/Nargo.toml new file mode 100644 index 00000000000..d23924af083 --- /dev/null +++ b/test_programs/execution_success/fold_after_inlined_calls/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "fold_after_inlined_calls" +type = "bin" +authors = [""] +compiler_version = ">=0.27.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/fold_after_inlined_calls/Prover.toml b/test_programs/execution_success/fold_after_inlined_calls/Prover.toml new file mode 100644 index 00000000000..4dd6b405159 --- /dev/null +++ b/test_programs/execution_success/fold_after_inlined_calls/Prover.toml @@ -0,0 +1 @@ +x = "1" diff --git a/test_programs/execution_success/fold_after_inlined_calls/src/main.nr b/test_programs/execution_success/fold_after_inlined_calls/src/main.nr new file mode 100644 index 00000000000..84c81190b9b --- /dev/null +++ b/test_programs/execution_success/fold_after_inlined_calls/src/main.nr @@ -0,0 +1,14 @@ +fn main(x: u32) { + // We want to call a foldable function after a call to a function that is set to be inlined + assert(increment(x) == x + 1); + foo(x); +} + +#[fold] +fn foo(x: u32) { + assert(x == 1); +} + +fn increment(x: u32) -> u32 { + x + 1 +} diff --git a/test_programs/execution_success/unit_value/Nargo.toml b/test_programs/execution_success/unit_value/Nargo.toml deleted file mode 100644 index f7e3697a7c1..00000000000 --- a/test_programs/execution_success/unit_value/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "short" -type = "bin" -authors = [""] -compiler_version = ">=0.23.0" - -[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/unit_value/src/main.nr b/test_programs/execution_success/unit_value/src/main.nr deleted file mode 100644 index f3844e03cf2..00000000000 --- a/test_programs/execution_success/unit_value/src/main.nr +++ /dev/null @@ -1,7 +0,0 @@ -fn get_transaction() { - dep::std::unsafe::zeroed() -} - -fn main() { - get_transaction(); -} diff --git a/tooling/backend_interface/src/proof_system.rs b/tooling/backend_interface/src/proof_system.rs index 3b47a7ced3a..fa1f82a5722 100644 --- a/tooling/backend_interface/src/proof_system.rs +++ b/tooling/backend_interface/src/proof_system.rs @@ -39,16 +39,16 @@ impl Backend { InfoCommand { crs_path: self.crs_directory() }.run(binary_path) } - /// If we cannot get a valid backend, returns `ExpressionWidth::Bound { width: 3 }`` + /// If we cannot get a valid backend, returns `ExpressionWidth::Bound { width: 4 }`` /// The function also prints a message saying we could not find a backend pub fn get_backend_info_or_default(&self) -> ExpressionWidth { if let Ok(expression_width) = self.get_backend_info() { expression_width } else { warn!( - "No valid backend found, ExpressionWidth defaulting to Bounded with a width of 3" + "No valid backend found, ExpressionWidth defaulting to Bounded with a width of 4" ); - ExpressionWidth::Bounded { width: 3 } + ExpressionWidth::Bounded { width: 4 } } } diff --git a/tooling/backend_interface/src/smart_contract.rs b/tooling/backend_interface/src/smart_contract.rs index f6beeeb09d9..153ab52c83f 100644 --- a/tooling/backend_interface/src/smart_contract.rs +++ b/tooling/backend_interface/src/smart_contract.rs @@ -51,7 +51,7 @@ mod tests { let circuit = Circuit { current_witness_index: 4, - expression_width: ExpressionWidth::Bounded { width: 3 }, + expression_width: ExpressionWidth::Bounded { width: 4 }, opcodes: vec![constraint], private_parameters: BTreeSet::from([Witness(1), Witness(2)]), public_parameters: PublicInputs::default(), @@ -59,7 +59,7 @@ mod tests { assert_messages: Default::default(), recursive: false, }; - let program = Program { functions: vec![circuit] }; + let program = Program { functions: vec![circuit], unconstrained_functions: Vec::new() }; let contract = get_mock_backend()?.eth_contract(&program)?; diff --git a/tooling/backend_interface/test-binaries/mock_backend/src/info_cmd.rs b/tooling/backend_interface/test-binaries/mock_backend/src/info_cmd.rs index fd8cf602125..75a6d323e7b 100644 --- a/tooling/backend_interface/test-binaries/mock_backend/src/info_cmd.rs +++ b/tooling/backend_interface/test-binaries/mock_backend/src/info_cmd.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; const INFO_RESPONSE: &str = r#"{ "language": { "name": "PLONK-CSAT", - "width": 3 + "width": 4 }, "opcodes_supported": ["arithmetic", "directive", "brillig", "memory_init", "memory_op"], "black_box_functions_supported": [ diff --git a/tooling/debugger/src/context.rs b/tooling/debugger/src/context.rs index c8ab544fa70..5639407df54 100644 --- a/tooling/debugger/src/context.rs +++ b/tooling/debugger/src/context.rs @@ -1,4 +1,5 @@ use crate::foreign_calls::DebugForeignCallExecutor; +use acvm::acir::circuit::brillig::BrilligBytecode; use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation}; use acvm::acir::native_types::{Witness, WitnessMap}; use acvm::brillig_vm::brillig::ForeignCallResult; @@ -42,10 +43,17 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { debug_artifact: &'a DebugArtifact, initial_witness: WitnessMap, foreign_call_executor: Box, + unconstrained_functions: &'a [BrilligBytecode], ) -> Self { let source_to_opcodes = build_source_to_opcode_debug_mappings(debug_artifact); Self { - acvm: ACVM::new(blackbox_solver, &circuit.opcodes, initial_witness), + // TODO: need to handle brillig pointer in the debugger + acvm: ACVM::new( + blackbox_solver, + &circuit.opcodes, + initial_witness, + unconstrained_functions, + ), brillig_solver: None, foreign_call_executor, debug_artifact, @@ -331,8 +339,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { self.handle_foreign_call(foreign_call) } Err(err) => DebugCommandResult::Error(NargoError::ExecutionError( - // TODO: debugger does not not handle multiple acir calls - ExecutionError::SolvingError(err, None), + ExecutionError::SolvingError(err), )), } } @@ -375,8 +382,7 @@ impl<'a, B: BlackBoxFunctionSolver> DebugContext<'a, B> { } } ACVMStatus::Failure(error) => DebugCommandResult::Error(NargoError::ExecutionError( - // TODO: debugger does not not handle multiple acir calls - ExecutionError::SolvingError(error, None), + ExecutionError::SolvingError(error), )), ACVMStatus::RequiresForeignCall(_) => { unreachable!("Unexpected pending foreign call resolution"); @@ -632,6 +638,7 @@ fn build_source_to_opcode_debug_mappings( result } +// TODO: update all debugger tests to use unconstrained brillig pointers #[cfg(test)] mod tests { use super::*; @@ -698,12 +705,14 @@ mod tests { let foreign_call_executor = Box::new(DefaultDebugForeignCallExecutor::from_artifact(true, debug_artifact)); + let brillig_funcs = &vec![]; let mut context = DebugContext::new( &StubbedBlackBoxSolver, circuit, debug_artifact, initial_witness, foreign_call_executor, + brillig_funcs, ); assert_eq!(context.get_current_opcode_location(), Some(OpcodeLocation::Acir(0))); @@ -805,12 +814,14 @@ mod tests { let foreign_call_executor = Box::new(DefaultDebugForeignCallExecutor::from_artifact(true, debug_artifact)); + let brillig_funcs = &vec![]; let mut context = DebugContext::new( &StubbedBlackBoxSolver, circuit, debug_artifact, initial_witness, foreign_call_executor, + brillig_funcs, ); // set breakpoint @@ -862,12 +873,14 @@ mod tests { let circuit = Circuit { opcodes, ..Circuit::default() }; let debug_artifact = DebugArtifact { debug_symbols: vec![], file_map: BTreeMap::new(), warnings: vec![] }; + let brillig_funcs = &vec![]; let context = DebugContext::new( &StubbedBlackBoxSolver, &circuit, &debug_artifact, WitnessMap::new(), Box::new(DefaultDebugForeignCallExecutor::new(true)), + brillig_funcs, ); assert_eq!(context.offset_opcode_location(&None, 0), (None, 0)); diff --git a/tooling/debugger/src/dap.rs b/tooling/debugger/src/dap.rs index 8dc3c541540..dc83337a973 100644 --- a/tooling/debugger/src/dap.rs +++ b/tooling/debugger/src/dap.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use std::io::{Read, Write}; use std::str::FromStr; +use acvm::acir::circuit::brillig::BrilligBytecode; use acvm::acir::circuit::{Circuit, OpcodeLocation}; use acvm::acir::native_types::WitnessMap; use acvm::BlackBoxFunctionSolver; @@ -64,6 +65,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { circuit: &'a Circuit, debug_artifact: &'a DebugArtifact, initial_witness: WitnessMap, + unconstrained_functions: &'a [BrilligBytecode], ) -> Self { let context = DebugContext::new( solver, @@ -71,6 +73,7 @@ impl<'a, R: Read, W: Write, B: BlackBoxFunctionSolver> DapSession<'a, R, W, B> { debug_artifact, initial_witness, Box::new(DefaultDebugForeignCallExecutor::from_artifact(true, debug_artifact)), + unconstrained_functions, ); Self { server, @@ -603,7 +606,7 @@ pub fn run_session( initial_witness: WitnessMap, ) -> Result<(), ServerError> { let debug_artifact = DebugArtifact { - debug_symbols: program.debug, + debug_symbols: vec![program.debug], file_map: program.file_map, warnings: program.warnings, }; @@ -613,6 +616,7 @@ pub fn run_session( &program.program.functions[0], &debug_artifact, initial_witness, + &program.program.unconstrained_functions, ); session.run_loop() diff --git a/tooling/debugger/src/lib.rs b/tooling/debugger/src/lib.rs index 4a25e3417a0..a8fc61c893f 100644 --- a/tooling/debugger/src/lib.rs +++ b/tooling/debugger/src/lib.rs @@ -9,6 +9,7 @@ use std::io::{Read, Write}; use ::dap::errors::ServerError; use ::dap::server::Server; +use acvm::acir::circuit::brillig::BrilligBytecode; use acvm::BlackBoxFunctionSolver; use acvm::{acir::circuit::Circuit, acir::native_types::WitnessMap}; @@ -22,8 +23,9 @@ pub fn debug_circuit( circuit: &Circuit, debug_artifact: DebugArtifact, initial_witness: WitnessMap, + unconstrained_functions: &[BrilligBytecode], ) -> Result, NargoError> { - repl::run(blackbox_solver, circuit, &debug_artifact, initial_witness) + repl::run(blackbox_solver, circuit, &debug_artifact, initial_witness, unconstrained_functions) } pub fn run_dap_loop( diff --git a/tooling/debugger/src/repl.rs b/tooling/debugger/src/repl.rs index e30d519b62e..2a92698e5ce 100644 --- a/tooling/debugger/src/repl.rs +++ b/tooling/debugger/src/repl.rs @@ -1,5 +1,6 @@ use crate::context::{DebugCommandResult, DebugContext}; +use acvm::acir::circuit::brillig::BrilligBytecode; use acvm::acir::circuit::{Circuit, Opcode, OpcodeLocation}; use acvm::acir::native_types::{Witness, WitnessMap}; use acvm::{BlackBoxFunctionSolver, FieldElement}; @@ -20,6 +21,7 @@ pub struct ReplDebugger<'a, B: BlackBoxFunctionSolver> { debug_artifact: &'a DebugArtifact, initial_witness: WitnessMap, last_result: DebugCommandResult, + unconstrained_functions: &'a [BrilligBytecode], } impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { @@ -28,6 +30,7 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { circuit: &'a Circuit, debug_artifact: &'a DebugArtifact, initial_witness: WitnessMap, + unconstrained_functions: &'a [BrilligBytecode], ) -> Self { let foreign_call_executor = Box::new(DefaultDebugForeignCallExecutor::from_artifact(true, debug_artifact)); @@ -37,6 +40,7 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { debug_artifact, initial_witness.clone(), foreign_call_executor, + unconstrained_functions, ); let last_result = if context.get_current_opcode_location().is_none() { // handle circuit with no opcodes @@ -44,7 +48,15 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { } else { DebugCommandResult::Ok }; - Self { context, blackbox_solver, circuit, debug_artifact, initial_witness, last_result } + Self { + context, + blackbox_solver, + circuit, + debug_artifact, + initial_witness, + last_result, + unconstrained_functions, + } } pub fn show_current_vm_status(&self) { @@ -271,6 +283,7 @@ impl<'a, B: BlackBoxFunctionSolver> ReplDebugger<'a, B> { self.debug_artifact, self.initial_witness.clone(), foreign_call_executor, + self.unconstrained_functions, ); for opcode_location in breakpoints { self.context.add_breakpoint(opcode_location); @@ -361,9 +374,15 @@ pub fn run( circuit: &Circuit, debug_artifact: &DebugArtifact, initial_witness: WitnessMap, + unconstrained_functions: &[BrilligBytecode], ) -> Result, NargoError> { - let context = - RefCell::new(ReplDebugger::new(blackbox_solver, circuit, debug_artifact, initial_witness)); + let context = RefCell::new(ReplDebugger::new( + blackbox_solver, + circuit, + debug_artifact, + initial_witness, + unconstrained_functions, + )); let ref_context = &context; ref_context.borrow().show_current_vm_status(); diff --git a/tooling/lsp/src/requests/profile_run.rs b/tooling/lsp/src/requests/profile_run.rs index 7d06bc87c85..89719947689 100644 --- a/tooling/lsp/src/requests/profile_run.rs +++ b/tooling/lsp/src/requests/profile_run.rs @@ -84,11 +84,9 @@ fn on_profile_run_request_inner( let compiled_program = nargo::ops::transform_program(compiled_program, expression_width); - for function_debug in compiled_program.debug.iter() { - let span_opcodes = function_debug.count_span_opcodes(); - opcodes_counts.extend(span_opcodes); - } - let debug_artifact: DebugArtifact = compiled_program.into(); + let span_opcodes = compiled_program.debug.count_span_opcodes(); + let debug_artifact: DebugArtifact = compiled_program.clone().into(); + opcodes_counts.extend(span_opcodes); file_map.extend(debug_artifact.file_map); } @@ -96,17 +94,14 @@ fn on_profile_run_request_inner( let compiled_contract = nargo::ops::transform_contract(compiled_contract, expression_width); - let function_debug_info = compiled_contract - .functions - .iter() - .flat_map(|func| &func.debug) - .collect::>(); + let function_debug_info: Vec<_> = + compiled_contract.functions.iter().map(|func| &func.debug).cloned().collect(); + let debug_artifact: DebugArtifact = compiled_contract.into(); + file_map.extend(debug_artifact.file_map); for contract_function_debug in function_debug_info { let span_opcodes = contract_function_debug.count_span_opcodes(); opcodes_counts.extend(span_opcodes); } - let debug_artifact: DebugArtifact = compiled_contract.into(); - file_map.extend(debug_artifact.file_map); } let result = NargoProfileRunResult { file_map, opcodes_counts }; diff --git a/tooling/lsp/src/solver.rs b/tooling/lsp/src/solver.rs index d0acbf1aec5..0fea9b16b54 100644 --- a/tooling/lsp/src/solver.rs +++ b/tooling/lsp/src/solver.rs @@ -10,7 +10,7 @@ impl BlackBoxFunctionSolver for WrapperSolver { &self, public_key_x: &acvm::FieldElement, public_key_y: &acvm::FieldElement, - signature: &[u8], + signature: &[u8; 64], message: &[u8], ) -> Result { self.0.schnorr_verify(public_key_x, public_key_y, signature, message) diff --git a/tooling/nargo/src/artifacts/contract.rs b/tooling/nargo/src/artifacts/contract.rs index 83bb4b94f82..868fb4404fd 100644 --- a/tooling/nargo/src/artifacts/contract.rs +++ b/tooling/nargo/src/artifacts/contract.rs @@ -4,7 +4,7 @@ use noirc_driver::{CompiledContract, CompiledContractOutputs, ContractFunction}; use serde::{Deserialize, Serialize}; use noirc_driver::DebugFile; -use noirc_errors::debug_info::ProgramDebugInfo; +use noirc_errors::debug_info::DebugInfo; use std::collections::{BTreeMap, HashMap}; use fm::FileId; @@ -68,10 +68,10 @@ pub struct ContractFunctionArtifact { pub bytecode: Program, #[serde( - serialize_with = "ProgramDebugInfo::serialize_compressed_base64_json", - deserialize_with = "ProgramDebugInfo::deserialize_compressed_base64_json" + serialize_with = "DebugInfo::serialize_compressed_base64_json", + deserialize_with = "DebugInfo::deserialize_compressed_base64_json" )] - pub debug_symbols: ProgramDebugInfo, + pub debug_symbols: DebugInfo, } impl From for ContractFunctionArtifact { @@ -82,7 +82,7 @@ impl From for ContractFunctionArtifact { custom_attributes: func.custom_attributes, abi: func.abi, bytecode: func.bytecode, - debug_symbols: ProgramDebugInfo { debug_infos: func.debug }, + debug_symbols: func.debug, } } } diff --git a/tooling/nargo/src/artifacts/debug.rs b/tooling/nargo/src/artifacts/debug.rs index 496896468cc..fbdf59805c9 100644 --- a/tooling/nargo/src/artifacts/debug.rs +++ b/tooling/nargo/src/artifacts/debug.rs @@ -121,7 +121,7 @@ impl DebugArtifact { impl From for DebugArtifact { fn from(compiled_program: CompiledProgram) -> Self { DebugArtifact { - debug_symbols: compiled_program.debug, + debug_symbols: vec![compiled_program.debug], file_map: compiled_program.file_map, warnings: compiled_program.warnings, } @@ -133,7 +133,7 @@ impl From for DebugArtifact { let all_functions_debug: Vec = compiled_artifact .functions .into_iter() - .flat_map(|contract_function| contract_function.debug) + .map(|contract_function| contract_function.debug) .collect(); DebugArtifact { diff --git a/tooling/nargo/src/artifacts/program.rs b/tooling/nargo/src/artifacts/program.rs index 67ac9f53ec8..9e660cbd359 100644 --- a/tooling/nargo/src/artifacts/program.rs +++ b/tooling/nargo/src/artifacts/program.rs @@ -5,7 +5,7 @@ use fm::FileId; use noirc_abi::Abi; use noirc_driver::CompiledProgram; use noirc_driver::DebugFile; -use noirc_errors::debug_info::ProgramDebugInfo; +use noirc_errors::debug_info::DebugInfo; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] @@ -27,10 +27,10 @@ pub struct ProgramArtifact { pub bytecode: Program, #[serde( - serialize_with = "ProgramDebugInfo::serialize_compressed_base64_json", - deserialize_with = "ProgramDebugInfo::deserialize_compressed_base64_json" + serialize_with = "DebugInfo::serialize_compressed_base64_json", + deserialize_with = "DebugInfo::deserialize_compressed_base64_json" )] - pub debug_symbols: ProgramDebugInfo, + pub debug_symbols: DebugInfo, /// Map of file Id to the source code so locations in debug info can be mapped to source code they point to. pub file_map: BTreeMap, @@ -45,7 +45,7 @@ impl From for ProgramArtifact { abi: compiled_program.abi, noir_version: compiled_program.noir_version, bytecode: compiled_program.program, - debug_symbols: ProgramDebugInfo { debug_infos: compiled_program.debug }, + debug_symbols: compiled_program.debug, file_map: compiled_program.file_map, names: compiled_program.names, } @@ -59,7 +59,7 @@ impl From for CompiledProgram { abi: program.abi, noir_version: program.noir_version, program: program.bytecode, - debug: program.debug_symbols.debug_infos, + debug: program.debug_symbols, file_map: program.file_map, warnings: vec![], names: program.names, diff --git a/tooling/nargo/src/errors.rs b/tooling/nargo/src/errors.rs index fa44bcc459b..f1e77d47a73 100644 --- a/tooling/nargo/src/errors.rs +++ b/tooling/nargo/src/errors.rs @@ -1,5 +1,5 @@ use acvm::{ - acir::circuit::{OpcodeLocation, ResolvedOpcodeLocation}, + acir::circuit::OpcodeLocation, pwg::{ErrorLocation, OpcodeResolutionError}, }; use noirc_errors::{ @@ -61,13 +61,15 @@ impl NargoError { match execution_error { ExecutionError::AssertionFailed(message, _) => Some(message), - ExecutionError::SolvingError(error, _) => match error { + ExecutionError::SolvingError(error) => match error { OpcodeResolutionError::IndexOutOfBounds { .. } | OpcodeResolutionError::OpcodeNotSolvable(_) | OpcodeResolutionError::UnsatisfiedConstrain { .. } | OpcodeResolutionError::AcirMainCallAttempted { .. } | OpcodeResolutionError::AcirCallOutputsMismatch { .. } => None, - OpcodeResolutionError::BrilligFunctionFailed { message, .. } => Some(message), + OpcodeResolutionError::BrilligFunctionFailed { message, .. } => { + message.as_ref().map(|s| s.as_str()) + } OpcodeResolutionError::BlackBoxFunctionFailed(_, reason) => Some(reason), }, } @@ -77,105 +79,81 @@ impl NargoError { #[derive(Debug, Error)] pub enum ExecutionError { #[error("Failed assertion: '{}'", .0)] - AssertionFailed(String, Vec), + AssertionFailed(String, Vec), - #[error("Failed to solve program: '{}'", .0)] - SolvingError(OpcodeResolutionError, Option>), + #[error(transparent)] + SolvingError(#[from] OpcodeResolutionError), } /// Extracts the opcode locations from a nargo error. fn extract_locations_from_error( error: &ExecutionError, - debug: &[DebugInfo], + debug: &DebugInfo, ) -> Option> { let mut opcode_locations = match error { - ExecutionError::SolvingError( - OpcodeResolutionError::BrilligFunctionFailed { .. }, - acir_call_stack, - ) => acir_call_stack.clone(), - ExecutionError::AssertionFailed(_, call_stack) => Some(call_stack.clone()), - ExecutionError::SolvingError( - OpcodeResolutionError::IndexOutOfBounds { opcode_location: error_location, .. }, - acir_call_stack, - ) - | ExecutionError::SolvingError( - OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: error_location }, - acir_call_stack, - ) => match error_location { + ExecutionError::SolvingError(OpcodeResolutionError::BrilligFunctionFailed { + call_stack, + .. + }) + | ExecutionError::AssertionFailed(_, call_stack) => Some(call_stack.clone()), + ExecutionError::SolvingError(OpcodeResolutionError::IndexOutOfBounds { + opcode_location: error_location, + .. + }) + | ExecutionError::SolvingError(OpcodeResolutionError::UnsatisfiedConstrain { + opcode_location: error_location, + }) => match error_location { ErrorLocation::Unresolved => { unreachable!("Cannot resolve index for unsatisfied constraint") } - ErrorLocation::Resolved(_) => acir_call_stack.clone(), + ErrorLocation::Resolved(opcode_location) => Some(vec![*opcode_location]), }, _ => None, }?; - // Insert the top-level Acir location where the Brillig function failed - for (i, resolved_location) in opcode_locations.iter().enumerate() { - if let ResolvedOpcodeLocation { - acir_function_index, - opcode_location: OpcodeLocation::Brillig { acir_index, .. }, - } = resolved_location - { - let acir_location = ResolvedOpcodeLocation { - acir_function_index: *acir_function_index, - opcode_location: OpcodeLocation::Acir(*acir_index), - }; - - opcode_locations.insert(i, acir_location); - // Go until the first brillig opcode as that means we have the start of a Brillig call stack. - // We have to loop through the opcode locations in case we had ACIR calls - // before the brillig function failure. - break; - } + if let Some(OpcodeLocation::Brillig { acir_index, .. }) = opcode_locations.first() { + opcode_locations.insert(0, OpcodeLocation::Acir(*acir_index)); } Some( opcode_locations .iter() - .flat_map(|resolved_location| { - debug[resolved_location.acir_function_index] - .opcode_location(&resolved_location.opcode_location) - .unwrap_or_default() - }) + .flat_map(|opcode_location| debug.opcode_location(opcode_location).unwrap_or_default()) .collect(), ) } -fn extract_message_from_error(nargo_err: &NargoError) -> String { - match nargo_err { +/// Tries to generate a runtime diagnostic from a nargo error. It will successfully do so if it's a runtime error with a call stack. +pub fn try_to_diagnose_runtime_error( + nargo_err: &NargoError, + debug: &DebugInfo, +) -> Option { + let execution_error = match nargo_err { + NargoError::ExecutionError(execution_error) => execution_error, + _ => return None, + }; + + let source_locations = extract_locations_from_error(execution_error, debug)?; + + // The location of the error itself will be the location at the top + // of the call stack (the last item in the Vec). + let location = source_locations.last()?; + + let message = match nargo_err { NargoError::ExecutionError(ExecutionError::AssertionFailed(message, _)) => { format!("Assertion failed: '{message}'") } NargoError::ExecutionError(ExecutionError::SolvingError( OpcodeResolutionError::IndexOutOfBounds { index, array_size, .. }, - _, )) => { format!("Index out of bounds, array has size {array_size:?}, but index was {index:?}") } NargoError::ExecutionError(ExecutionError::SolvingError( OpcodeResolutionError::UnsatisfiedConstrain { .. }, - _, )) => "Failed constraint".into(), _ => nargo_err.to_string(), - } -} - -/// Tries to generate a runtime diagnostic from a nargo error. It will successfully do so if it's a runtime error with a call stack. -pub fn try_to_diagnose_runtime_error( - nargo_err: &NargoError, - debug: &[DebugInfo], -) -> Option { - let source_locations = match nargo_err { - NargoError::ExecutionError(execution_error) => { - extract_locations_from_error(execution_error, debug)? - } - _ => return None, }; - // The location of the error itself will be the location at the top - // of the call stack (the last item in the Vec). - let location = source_locations.last()?; - let message = extract_message_from_error(nargo_err); + Some( CustomDiagnostic::simple_error(message, String::new(), location.span) .in_file(location.file) diff --git a/tooling/nargo/src/ops/execute.rs b/tooling/nargo/src/ops/execute.rs index 76f6da7c1bc..c2c0208b395 100644 --- a/tooling/nargo/src/ops/execute.rs +++ b/tooling/nargo/src/ops/execute.rs @@ -1,4 +1,5 @@ -use acvm::acir::circuit::{OpcodeLocation, Program, ResolvedOpcodeLocation}; +use acvm::acir::circuit::brillig::BrilligBytecode; +use acvm::acir::circuit::Program; use acvm::acir::native_types::WitnessStack; use acvm::brillig_vm::brillig::ForeignCallResult; use acvm::pwg::{ACVMStatus, ErrorLocation, OpcodeNotSolvable, OpcodeResolutionError, ACVM}; @@ -12,36 +13,30 @@ use super::foreign_calls::{ForeignCallExecutor, NargoForeignCallResult}; struct ProgramExecutor<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> { functions: &'a [Circuit], + + unconstrained_functions: &'a [BrilligBytecode], + // This gets built as we run through the program looking at each function call witness_stack: WitnessStack, blackbox_solver: &'a B, foreign_call_executor: &'a mut F, - - // The Noir compiler codegens per function and call stacks are not shared across ACIR function calls. - // We must rebuild a call stack when executing a program of many circuits. - call_stack: Vec, - - // Tracks the index of the current function we are executing. - // This is used to fetch the function we want to execute - // and to resolve call stack locations across many function calls. - current_function_index: usize, } impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, B, F> { fn new( functions: &'a [Circuit], + unconstrained_functions: &'a [BrilligBytecode], blackbox_solver: &'a B, foreign_call_executor: &'a mut F, ) -> Self { ProgramExecutor { functions, + unconstrained_functions, witness_stack: WitnessStack::default(), blackbox_solver, foreign_call_executor, - call_stack: Vec::default(), - current_function_index: 0, } } @@ -50,9 +45,17 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, } #[tracing::instrument(level = "trace", skip_all)] - fn execute_circuit(&mut self, initial_witness: WitnessMap) -> Result { - let circuit = &self.functions[self.current_function_index]; - let mut acvm = ACVM::new(self.blackbox_solver, &circuit.opcodes, initial_witness); + fn execute_circuit( + &mut self, + circuit: &Circuit, + initial_witness: WitnessMap, + ) -> Result { + let mut acvm = ACVM::new( + self.blackbox_solver, + &circuit.opcodes, + initial_witness, + self.unconstrained_functions, + ); // This message should be resolved by a nargo foreign call only when we have an unsatisfied assertion. let mut assert_message: Option = None; @@ -68,26 +71,9 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, let call_stack = match &error { OpcodeResolutionError::UnsatisfiedConstrain { opcode_location: ErrorLocation::Resolved(opcode_location), - } - | OpcodeResolutionError::IndexOutOfBounds { - opcode_location: ErrorLocation::Resolved(opcode_location), - .. - } => { - let resolved_location = ResolvedOpcodeLocation { - acir_function_index: self.current_function_index, - opcode_location: *opcode_location, - }; - self.call_stack.push(resolved_location); - Some(self.call_stack.clone()) - } + } => Some(vec![*opcode_location]), OpcodeResolutionError::BrilligFunctionFailed { call_stack, .. } => { - let brillig_call_stack = - call_stack.iter().map(|location| ResolvedOpcodeLocation { - acir_function_index: self.current_function_index, - opcode_location: *location, - }); - self.call_stack.extend(brillig_call_stack); - Some(self.call_stack.clone()) + Some(call_stack.clone()) } _ => None, }; @@ -95,29 +81,32 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, return Err(NargoError::ExecutionError(match call_stack { Some(call_stack) => { // First check whether we have a runtime assertion message that should be resolved on an ACVM failure - // If we do not have a runtime assertion message, we should check whether the circuit has any hardcoded - // messages associated with a specific `OpcodeLocation`. + // If we do not have a runtime assertion message, we check wether the error is a brillig error with a user-defined message, + // and finally we should check whether the circuit has any hardcoded messages associated with a specific `OpcodeLocation`. // Otherwise return the provided opcode resolution error. if let Some(assert_message) = assert_message { ExecutionError::AssertionFailed( assert_message.to_owned(), call_stack, ) + } else if let OpcodeResolutionError::BrilligFunctionFailed { + message: Some(message), + .. + } = &error + { + ExecutionError::AssertionFailed(message.to_owned(), call_stack) } else if let Some(assert_message) = circuit.get_assert_message( - call_stack - .last() - .expect("Call stacks should not be empty") - .opcode_location, + *call_stack.last().expect("Call stacks should not be empty"), ) { ExecutionError::AssertionFailed( assert_message.to_owned(), call_stack, ) } else { - ExecutionError::SolvingError(error, Some(call_stack)) + ExecutionError::SolvingError(error) } } - None => ExecutionError::SolvingError(error, None), + None => ExecutionError::SolvingError(error), })); } ACVMStatus::RequiresForeignCall(foreign_call) => { @@ -137,24 +126,10 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, } } ACVMStatus::RequiresAcirCall(call_info) => { - // Store the parent function index whose context we are currently executing - let acir_function_caller = self.current_function_index; - // Add call opcode to the call stack with a reference to the parent function index - self.call_stack.push(ResolvedOpcodeLocation { - acir_function_index: acir_function_caller, - opcode_location: OpcodeLocation::Acir(acvm.instruction_pointer()), - }); - - // Set current function to the circuit we are about to execute - self.current_function_index = call_info.id as usize; - // Execute the ACIR call let acir_to_call = &self.functions[call_info.id as usize]; let initial_witness = call_info.initial_witness; - let call_solved_witness = self.execute_circuit(initial_witness)?; - - // Set tracking index back to the parent function after ACIR call execution - self.current_function_index = acir_function_caller; - + let call_solved_witness = + self.execute_circuit(acir_to_call, initial_witness)?; let mut call_resolved_outputs = Vec::new(); for return_witness_index in acir_to_call.return_values.indices() { if let Some(return_value) = @@ -164,7 +139,6 @@ impl<'a, B: BlackBoxFunctionSolver, F: ForeignCallExecutor> ProgramExecutor<'a, } else { return Err(ExecutionError::SolvingError( OpcodeNotSolvable::MissingAssignment(return_witness_index).into(), - None, // Missing assignment errors do not supply user-facing diagnostics so we do not need to attach a call stack ) .into()); } @@ -186,9 +160,15 @@ pub fn execute_program( blackbox_solver: &B, foreign_call_executor: &mut F, ) -> Result { - let mut executor = - ProgramExecutor::new(&program.functions, blackbox_solver, foreign_call_executor); - let main_witness = executor.execute_circuit(initial_witness)?; + let main = &program.functions[0]; + + let mut executor = ProgramExecutor::new( + &program.functions, + &program.unconstrained_functions, + blackbox_solver, + foreign_call_executor, + ); + let main_witness = executor.execute_circuit(main, initial_witness)?; executor.witness_stack.push(0, main_witness); Ok(executor.finalize()) diff --git a/tooling/nargo/src/ops/foreign_calls.rs b/tooling/nargo/src/ops/foreign_calls.rs index 33767314a37..bc91929e5e7 100644 --- a/tooling/nargo/src/ops/foreign_calls.rs +++ b/tooling/nargo/src/ops/foreign_calls.rs @@ -167,15 +167,8 @@ pub struct DefaultForeignCallExecutor { impl DefaultForeignCallExecutor { pub fn new(show_output: bool, resolver_url: Option<&str>) -> Self { let oracle_resolver = resolver_url.map(|resolver_url| { - let mut transport_builder = + let transport_builder = Builder::new().url(resolver_url).expect("Invalid oracle resolver URL"); - - if let Some(Ok(timeout)) = - std::env::var("NARGO_FOREIGN_CALL_TIMEOUT").ok().map(|timeout| timeout.parse()) - { - let timeout_duration = std::time::Duration::from_millis(timeout); - transport_builder = transport_builder.timeout(timeout_duration); - }; Client::with_transport(transport_builder.build()) }); DefaultForeignCallExecutor { diff --git a/tooling/nargo/src/ops/optimize.rs b/tooling/nargo/src/ops/optimize.rs index a62f4696328..cfaaf27ea98 100644 --- a/tooling/nargo/src/ops/optimize.rs +++ b/tooling/nargo/src/ops/optimize.rs @@ -1,36 +1,25 @@ -use acvm::acir::circuit::Program; use iter_extended::vecmap; use noirc_driver::{CompiledContract, CompiledProgram}; -use noirc_errors::debug_info::DebugInfo; + +/// TODO(https://github.com/noir-lang/noir/issues/4428): Need to update how these passes are run to account for +/// multiple ACIR functions pub fn optimize_program(mut compiled_program: CompiledProgram) -> CompiledProgram { - compiled_program.program = - optimize_program_internal(compiled_program.program, &mut compiled_program.debug); + let (optimized_circuit, location_map) = + acvm::compiler::optimize(std::mem::take(&mut compiled_program.program.functions[0])); + compiled_program.program.functions[0] = optimized_circuit; + compiled_program.debug.update_acir(location_map); compiled_program } pub fn optimize_contract(contract: CompiledContract) -> CompiledContract { let functions = vecmap(contract.functions, |mut func| { - func.bytecode = optimize_program_internal(func.bytecode, &mut func.debug); + let (optimized_bytecode, location_map) = + acvm::compiler::optimize(std::mem::take(&mut func.bytecode.functions[0])); + func.bytecode.functions[0] = optimized_bytecode; + func.debug.update_acir(location_map); func }); CompiledContract { functions, ..contract } } - -fn optimize_program_internal(mut program: Program, debug: &mut [DebugInfo]) -> Program { - let functions = std::mem::take(&mut program.functions); - - let optimized_functions = functions - .into_iter() - .enumerate() - .map(|(i, function)| { - let (optimized_circuit, location_map) = acvm::compiler::optimize(function); - debug[i].update_acir(location_map); - optimized_circuit - }) - .collect::>(); - - program.functions = optimized_functions; - program -} diff --git a/tooling/nargo/src/ops/test.rs b/tooling/nargo/src/ops/test.rs index b216fff827d..45b1a88f99c 100644 --- a/tooling/nargo/src/ops/test.rs +++ b/tooling/nargo/src/ops/test.rs @@ -84,7 +84,7 @@ fn test_status_program_compile_fail(err: CompileError, test_function: &TestFunct /// passed/failed to determine the test status. fn test_status_program_compile_pass( test_function: &TestFunction, - debug: Vec, + debug: DebugInfo, circuit_execution: Result, ) -> TestStatus { let circuit_execution_err = match circuit_execution { diff --git a/tooling/nargo/src/ops/transform.rs b/tooling/nargo/src/ops/transform.rs index b4811bd5780..274286a46e4 100644 --- a/tooling/nargo/src/ops/transform.rs +++ b/tooling/nargo/src/ops/transform.rs @@ -1,17 +1,21 @@ -use acvm::acir::circuit::{ExpressionWidth, Program}; +use acvm::acir::circuit::ExpressionWidth; use iter_extended::vecmap; use noirc_driver::{CompiledContract, CompiledProgram}; -use noirc_errors::debug_info::DebugInfo; + +/// TODO(https://github.com/noir-lang/noir/issues/4428): Need to update how these passes are run to account for +/// multiple ACIR functions pub fn transform_program( mut compiled_program: CompiledProgram, expression_width: ExpressionWidth, ) -> CompiledProgram { - compiled_program.program = transform_program_internal( - compiled_program.program, - &mut compiled_program.debug, + let (optimized_circuit, location_map) = acvm::compiler::compile( + std::mem::take(&mut compiled_program.program.functions[0]), expression_width, ); + + compiled_program.program.functions[0] = optimized_circuit; + compiled_program.debug.update_acir(location_map); compiled_program } @@ -20,33 +24,14 @@ pub fn transform_contract( expression_width: ExpressionWidth, ) -> CompiledContract { let functions = vecmap(contract.functions, |mut func| { - func.bytecode = - transform_program_internal(func.bytecode, &mut func.debug, expression_width); - + let (optimized_bytecode, location_map) = acvm::compiler::compile( + std::mem::take(&mut func.bytecode.functions[0]), + expression_width, + ); + func.bytecode.functions[0] = optimized_bytecode; + func.debug.update_acir(location_map); func }); CompiledContract { functions, ..contract } } - -fn transform_program_internal( - mut program: Program, - debug: &mut [DebugInfo], - expression_width: ExpressionWidth, -) -> Program { - let functions = std::mem::take(&mut program.functions); - - let optimized_functions = functions - .into_iter() - .enumerate() - .map(|(i, function)| { - let (optimized_circuit, location_map) = - acvm::compiler::compile(function, expression_width); - debug[i].update_acir(location_map); - optimized_circuit - }) - .collect::>(); - - program.functions = optimized_functions; - program -} diff --git a/tooling/nargo_cli/src/cli/debug_cmd.rs b/tooling/nargo_cli/src/cli/debug_cmd.rs index 6a3f0094a08..5a9a33c0acd 100644 --- a/tooling/nargo_cli/src/cli/debug_cmd.rs +++ b/tooling/nargo_cli/src/cli/debug_cmd.rs @@ -232,7 +232,7 @@ pub(crate) fn debug_program( let initial_witness = compiled_program.abi.encode(inputs_map, None)?; let debug_artifact = DebugArtifact { - debug_symbols: compiled_program.debug.clone(), + debug_symbols: vec![compiled_program.debug.clone()], file_map: compiled_program.file_map.clone(), warnings: compiled_program.warnings.clone(), }; @@ -242,6 +242,7 @@ pub(crate) fn debug_program( &compiled_program.program.functions[0], debug_artifact, initial_witness, + &compiled_program.program.unconstrained_functions, ) .map_err(CliError::from) } diff --git a/tooling/nargo_cli/src/cli/execute_cmd.rs b/tooling/nargo_cli/src/cli/execute_cmd.rs index a353065491f..697f6d7c1ea 100644 --- a/tooling/nargo_cli/src/cli/execute_cmd.rs +++ b/tooling/nargo_cli/src/cli/execute_cmd.rs @@ -149,7 +149,7 @@ pub(crate) fn execute_program( Ok(solved_witness_stack) => Ok(solved_witness_stack), Err(err) => { let debug_artifact = DebugArtifact { - debug_symbols: compiled_program.debug.clone(), + debug_symbols: vec![compiled_program.debug.clone()], file_map: compiled_program.file_map.clone(), warnings: compiled_program.warnings.clone(), }; diff --git a/tooling/nargo_cli/src/cli/info_cmd.rs b/tooling/nargo_cli/src/cli/info_cmd.rs index 77a84de07a4..e9609687ffb 100644 --- a/tooling/nargo_cli/src/cli/info_cmd.rs +++ b/tooling/nargo_cli/src/cli/info_cmd.rs @@ -97,21 +97,17 @@ pub(crate) fn run( if args.profile_info { for compiled_program in &compiled_programs { + let span_opcodes = compiled_program.debug.count_span_opcodes(); let debug_artifact = DebugArtifact::from(compiled_program.clone()); - for function_debug in compiled_program.debug.iter() { - let span_opcodes = function_debug.count_span_opcodes(); - print_span_opcodes(span_opcodes, &debug_artifact); - } + print_span_opcodes(span_opcodes, &debug_artifact); } for compiled_contract in &compiled_contracts { let debug_artifact = DebugArtifact::from(compiled_contract.clone()); let functions = &compiled_contract.functions; for contract_function in functions { - for function_debug in contract_function.debug.iter() { - let span_opcodes = function_debug.count_span_opcodes(); - print_span_opcodes(span_opcodes, &debug_artifact); - } + let span_opcodes = contract_function.debug.count_span_opcodes(); + print_span_opcodes(span_opcodes, &debug_artifact); } } } @@ -293,8 +289,11 @@ fn count_opcodes_and_gates_in_program( Ok(FunctionInfo { name: compiled_program.names[i].clone(), acir_opcodes: function.opcodes.len(), - circuit_size: backend - .get_exact_circuit_size(&Program { functions: vec![function] })?, + // Unconstrained functions do not matter to a backend circuit count so we pass nothing here + circuit_size: backend.get_exact_circuit_size(&Program { + functions: vec![function], + unconstrained_functions: Vec::new(), + })?, }) }) .collect::>()?; diff --git a/tooling/nargo_fmt/src/rewrite/typ.rs b/tooling/nargo_fmt/src/rewrite/typ.rs index 980d02ee5dc..922337cdb74 100644 --- a/tooling/nargo_fmt/src/rewrite/typ.rs +++ b/tooling/nargo_fmt/src/rewrite/typ.rs @@ -64,7 +64,6 @@ pub(crate) fn rewrite(visitor: &FmtVisitor, _shape: Shape, typ: UnresolvedType) | UnresolvedTypeData::Expression(_) | UnresolvedTypeData::String(_) | UnresolvedTypeData::FormatString(_, _) - | UnresolvedTypeData::Code | UnresolvedTypeData::TraitAsType(_, _) => visitor.slice(typ.span.unwrap()).into(), UnresolvedTypeData::Error => unreachable!(), } diff --git a/tooling/noir_js_backend_barretenberg/package.json b/tooling/noir_js_backend_barretenberg/package.json index 98bfdf1c3a8..438e91ff302 100644 --- a/tooling/noir_js_backend_barretenberg/package.json +++ b/tooling/noir_js_backend_barretenberg/package.json @@ -42,7 +42,7 @@ "lint": "NODE_NO_WARNINGS=1 eslint . --ext .ts --ignore-path ./.eslintignore --max-warnings 0" }, "dependencies": { - "@aztec/bb.js": "0.34.0", + "@aztec/bb.js": "portal:../../../../barretenberg/ts", "@noir-lang/types": "workspace:*", "fflate": "^0.8.0" }, diff --git a/tooling/noirc_abi/src/errors.rs b/tooling/noirc_abi/src/errors.rs index 4209a9e218b..687fecfcc1d 100644 --- a/tooling/noirc_abi/src/errors.rs +++ b/tooling/noirc_abi/src/errors.rs @@ -1,7 +1,4 @@ -use crate::{ - input_parser::{InputTypecheckingError, InputValue}, - AbiType, -}; +use crate::{input_parser::InputValue, AbiParameter, AbiType}; use acvm::acir::native_types::Witness; use thiserror::Error; @@ -41,8 +38,8 @@ impl From for InputParserError { pub enum AbiError { #[error("Received parameters not expected by ABI: {0:?}")] UnexpectedParams(Vec), - #[error("The value passed for parameter `{}` does not match the specified type:\n{0}", .0.path())] - TypeMismatch(#[from] InputTypecheckingError), + #[error("The parameter {} is expected to be a {:?} but found incompatible value {value:?}", .param.name, .param.typ)] + TypeMismatch { param: AbiParameter, value: InputValue }, #[error("ABI expects the parameter `{0}`, but this was not found")] MissingParam(String), #[error( diff --git a/tooling/noirc_abi/src/input_parser/mod.rs b/tooling/noirc_abi/src/input_parser/mod.rs index 024cb06007e..f66e069d487 100644 --- a/tooling/noirc_abi/src/input_parser/mod.rs +++ b/tooling/noirc_abi/src/input_parser/mod.rs @@ -1,7 +1,6 @@ use num_bigint::{BigInt, BigUint}; use num_traits::{Num, Zero}; -use std::collections::{BTreeMap, HashSet}; -use thiserror::Error; +use std::collections::BTreeMap; use acvm::FieldElement; use serde::Serialize; @@ -23,165 +22,63 @@ pub enum InputValue { Struct(BTreeMap), } -#[derive(Debug, Error)] -pub enum InputTypecheckingError { - #[error("Value {value:?} does not fall within range of allowable values for a {typ:?}")] - OutsideOfValidRange { path: String, typ: AbiType, value: InputValue }, - #[error("Type {typ:?} is expected to have length {expected_length} but value {value:?} has length {actual_length}")] - LengthMismatch { - path: String, - typ: AbiType, - value: InputValue, - expected_length: usize, - actual_length: usize, - }, - #[error("Could not find value for required field `{expected_field}`. Found values for fields {found_fields:?}")] - MissingField { path: String, expected_field: String, found_fields: Vec }, - #[error("Additional unexpected field was provided for type {typ:?}. Found field named `{extra_field}`")] - UnexpectedField { path: String, typ: AbiType, extra_field: String }, - #[error("Type {typ:?} and value {value:?} do not match")] - IncompatibleTypes { path: String, typ: AbiType, value: InputValue }, -} - -impl InputTypecheckingError { - pub(crate) fn path(&self) -> &str { - match self { - InputTypecheckingError::OutsideOfValidRange { path, .. } - | InputTypecheckingError::LengthMismatch { path, .. } - | InputTypecheckingError::MissingField { path, .. } - | InputTypecheckingError::UnexpectedField { path, .. } - | InputTypecheckingError::IncompatibleTypes { path, .. } => path, - } - } -} - impl InputValue { /// Checks whether the ABI type matches the InputValue type - pub(crate) fn find_type_mismatch( - &self, - abi_param: &AbiType, - path: String, - ) -> Result<(), InputTypecheckingError> { + /// and also their arity + pub fn matches_abi(&self, abi_param: &AbiType) -> bool { match (self, abi_param) { - (InputValue::Field(_), AbiType::Field) => Ok(()), + (InputValue::Field(_), AbiType::Field) => true, (InputValue::Field(field_element), AbiType::Integer { width, .. }) => { - if field_element.num_bits() <= *width { - Ok(()) - } else { - Err(InputTypecheckingError::OutsideOfValidRange { - path, - typ: abi_param.clone(), - value: self.clone(), - }) - } + field_element.num_bits() <= *width } (InputValue::Field(field_element), AbiType::Boolean) => { - if field_element.is_one() || field_element.is_zero() { - Ok(()) - } else { - Err(InputTypecheckingError::OutsideOfValidRange { - path, - typ: abi_param.clone(), - value: self.clone(), - }) - } + field_element.is_one() || field_element.is_zero() } (InputValue::Vec(array_elements), AbiType::Array { length, typ, .. }) => { if array_elements.len() != *length as usize { - return Err(InputTypecheckingError::LengthMismatch { - path, - typ: abi_param.clone(), - value: self.clone(), - expected_length: *length as usize, - actual_length: array_elements.len(), - }); + return false; } // Check that all of the array's elements' values match the ABI as well. - for (i, element) in array_elements.iter().enumerate() { - let mut path = path.clone(); - path.push_str(&format!("[{i}]")); - - element.find_type_mismatch(typ, path)?; - } - Ok(()) + array_elements.iter().all(|input_value| input_value.matches_abi(typ)) } (InputValue::String(string), AbiType::String { length }) => { - if string.len() == *length as usize { - Ok(()) - } else { - Err(InputTypecheckingError::LengthMismatch { - path, - typ: abi_param.clone(), - value: self.clone(), - actual_length: string.len(), - expected_length: *length as usize, - }) - } + string.len() == *length as usize } (InputValue::Struct(map), AbiType::Struct { fields, .. }) => { - for (field_name, field_type) in fields { - if let Some(value) = map.get(field_name) { - let mut path = path.clone(); - path.push_str(&format!(".{field_name}")); - value.find_type_mismatch(field_type, path)?; - } else { - return Err(InputTypecheckingError::MissingField { - path, - expected_field: field_name.to_string(), - found_fields: map.keys().cloned().collect(), - }); - } + if map.len() != fields.len() { + return false; } - if map.len() > fields.len() { - let expected_fields: HashSet = - fields.iter().map(|(field, _)| field.to_string()).collect(); - let extra_field = map.keys().cloned().find(|key| !expected_fields.contains(key)).expect("`map` is larger than the expected type's `fields` so it must contain an unexpected field"); - return Err(InputTypecheckingError::UnexpectedField { - path, - typ: abi_param.clone(), - extra_field: extra_field.to_string(), - }); - } + let field_types = BTreeMap::from_iter(fields.iter().cloned()); - Ok(()) + // Check that all of the struct's fields' values match the ABI as well. + map.iter().all(|(field_name, field_value)| { + if let Some(field_type) = field_types.get(field_name) { + field_value.matches_abi(field_type) + } else { + false + } + }) } (InputValue::Vec(vec_elements), AbiType::Tuple { fields }) => { if vec_elements.len() != fields.len() { - return Err(InputTypecheckingError::LengthMismatch { - path, - typ: abi_param.clone(), - value: self.clone(), - actual_length: vec_elements.len(), - expected_length: fields.len(), - }); + return false; } - // Check that all of the array's elements' values match the ABI as well. - for (i, (element, expected_typ)) in vec_elements.iter().zip(fields).enumerate() { - let mut path = path.clone(); - path.push_str(&format!(".{i}")); - element.find_type_mismatch(expected_typ, path)?; - } - Ok(()) + + vec_elements + .iter() + .zip(fields) + .all(|(input_value, abi_param)| input_value.matches_abi(abi_param)) } // All other InputValue-AbiType combinations are fundamentally incompatible. - _ => Err(InputTypecheckingError::IncompatibleTypes { - path, - typ: abi_param.clone(), - value: self.clone(), - }), + _ => false, } } - - /// Checks whether the ABI type matches the InputValue type. - pub fn matches_abi(&self, abi_param: &AbiType) -> bool { - self.find_type_mismatch(abi_param, String::new()).is_ok() - } } /// The different formats that are supported when parsing diff --git a/tooling/noirc_abi/src/lib.rs b/tooling/noirc_abi/src/lib.rs index 6ad13500bdd..89a60b0ed26 100644 --- a/tooling/noirc_abi/src/lib.rs +++ b/tooling/noirc_abi/src/lib.rs @@ -307,7 +307,15 @@ impl Abi { .ok_or_else(|| AbiError::MissingParam(param_name.clone()))? .clone(); - value.find_type_mismatch(&expected_type, param_name.clone())?; + if !value.matches_abi(&expected_type) { + let param = self + .parameters + .iter() + .find(|param| param.name == param_name) + .unwrap() + .clone(); + return Err(AbiError::TypeMismatch { param, value }); + } Self::encode_value(value, &expected_type).map(|v| (param_name, v)) }) diff --git a/tooling/noirc_abi_wasm/test/browser/errors.test.ts b/tooling/noirc_abi_wasm/test/browser/errors.test.ts index 0f75ff64a3e..429a2d446a3 100644 --- a/tooling/noirc_abi_wasm/test/browser/errors.test.ts +++ b/tooling/noirc_abi_wasm/test/browser/errors.test.ts @@ -9,7 +9,7 @@ it('errors when an integer input overflows', async () => { const { abi, inputs } = await import('../shared/uint_overflow'); expect(() => abiEncode(abi, inputs)).to.throw( - 'The value passed for parameter `foo` does not match the specified type:\nValue Field(2³⁸) does not fall within range of allowable values for a Integer { sign: Unsigned, width: 32 }', + 'The parameter foo is expected to be a Integer { sign: Unsigned, width: 32 } but found incompatible value Field(2³⁸)', ); }); diff --git a/tooling/noirc_abi_wasm/test/node/errors.test.ts b/tooling/noirc_abi_wasm/test/node/errors.test.ts index fba451b4a8c..0d007e64803 100644 --- a/tooling/noirc_abi_wasm/test/node/errors.test.ts +++ b/tooling/noirc_abi_wasm/test/node/errors.test.ts @@ -5,7 +5,7 @@ it('errors when an integer input overflows', async () => { const { abi, inputs } = await import('../shared/uint_overflow'); expect(() => abiEncode(abi, inputs)).to.throw( - 'The value passed for parameter `foo` does not match the specified type:\nValue Field(2³⁸) does not fall within range of allowable values for a Integer { sign: Unsigned, width: 32 }', + 'The parameter foo is expected to be a Integer { sign: Unsigned, width: 32 } but found incompatible value Field(2³⁸)', ); }); diff --git a/yarn.lock b/yarn.lock index 38e13814929..b45678f5d8b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -221,19 +221,18 @@ __metadata: languageName: node linkType: hard -"@aztec/bb.js@npm:0.34.0": - version: 0.34.0 - resolution: "@aztec/bb.js@npm:0.34.0" +"@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg": + version: 0.0.0-use.local + resolution: "@aztec/bb.js@portal:../../../../barretenberg/ts::locator=%40noir-lang%2Fbackend_barretenberg%40workspace%3Atooling%2Fnoir_js_backend_barretenberg" dependencies: comlink: ^4.4.1 commander: ^10.0.1 debug: ^4.3.4 tslib: ^2.4.0 bin: - bb.js: dest/node/main.js - checksum: 9d07834d81ed19e4d6fd5c1f3b07c565648df1165c30115f020ece9660b2b8599a5ed894a2090410f14020e73dd290484b30b76c9c71e863b8390fa2b7c1b729 + bb.js: ./dest/node/main.js languageName: node - linkType: hard + linkType: soft "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.11, @babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.22.13, @babel/code-frame@npm:^7.23.5, @babel/code-frame@npm:^7.8.3": version: 7.23.5 @@ -4396,7 +4395,7 @@ __metadata: version: 0.0.0-use.local resolution: "@noir-lang/backend_barretenberg@workspace:tooling/noir_js_backend_barretenberg" dependencies: - "@aztec/bb.js": 0.34.0 + "@aztec/bb.js": "portal:../../../../barretenberg/ts" "@noir-lang/types": "workspace:*" "@types/node": ^20.6.2 "@types/prettier": ^3