From c671b1ecb0f39e13e87d5b65c6468f9142f599e9 Mon Sep 17 00:00:00 2001 From: "Michael J. Klein" <lambdamichael@gmail.com> Date: Tue, 7 Jan 2025 16:56:28 -0500 Subject: [PATCH 01/18] change two assertions on generics to static assertions, wip finding static_assert locations for field/mod.nr --- noir_stdlib/src/collections/bounded_vec.nr | 5 +++-- noir_stdlib/src/field/mod.nr | 13 +++++++++++-- noir_stdlib/src/meta/ctstring.nr | 3 ++- noir_stdlib/src/uint128.nr | 6 ++++-- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr index 7aed5e6a0e4..4b8820df8c8 100644 --- a/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir_stdlib/src/collections/bounded_vec.nr @@ -1,4 +1,4 @@ -use crate::{cmp::Eq, convert::From}; +use crate::{cmp::Eq, convert::From, static_assert}; /// A `BoundedVec<T, MaxLen>` is a growable storage similar to a `Vec<T>` except that it /// is bounded with a maximum possible length. Unlike `Vec`, `BoundedVec` is not implemented @@ -339,7 +339,7 @@ impl<T, let MaxLen: u32> BoundedVec<T, MaxLen> { /// let bounded_vec: BoundedVec<Field, 10> = BoundedVec::from_array([1, 2, 3]) /// ``` pub fn from_array<let Len: u32>(array: [T; Len]) -> Self { - assert(Len <= MaxLen, "from array out of bounds"); + static_assert(Len <= MaxLen, "from array out of bounds"); let mut vec: BoundedVec<T, MaxLen> = BoundedVec::new(); vec.extend_from_array(array); vec @@ -612,6 +612,7 @@ mod bounded_vec_tests { assert_eq(bounded_vec.get(2), 3); } + // TODO: works with 'shoul_fail': does 'should_fail_with' support 'static_assert'? #[test(should_fail_with = "from array out of bounds")] fn max_len_lower_then_array_len() { let _: BoundedVec<Field, 2> = BoundedVec::from_array([0; 3]); diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr index 7e5ad97d64f..abbc34854c0 100644 --- a/noir_stdlib/src/field/mod.nr +++ b/noir_stdlib/src/field/mod.nr @@ -1,5 +1,5 @@ pub mod bn254; -use crate::runtime::is_unconstrained; +use crate::{runtime::is_unconstrained, static_assert}; use bn254::lt as bn254_lt; impl Field { @@ -10,13 +10,14 @@ impl Field { // docs:start:assert_max_bit_size pub fn assert_max_bit_size<let BIT_SIZE: u32>(self) { // docs:end:assert_max_bit_size - assert(BIT_SIZE < modulus_num_bits() as u32); + static_assert(BIT_SIZE < modulus_num_bits() as u32, "BIT_SIZE must be less than modulus_num_bits"); self.__assert_max_bit_size(BIT_SIZE); } #[builtin(apply_range_constraint)] fn __assert_max_bit_size(self, bit_size: u32) {} + // TODO: to_le_bits -> to_le_bits_unsafe /// Decomposes `self` into its little endian bit decomposition as a `[u1; N]` array. /// This slice will be zero padded should not all bits be necessary to represent `self`. /// @@ -33,6 +34,7 @@ impl Field { pub fn to_le_bits<let N: u32>(self: Self) -> [u1; N] {} // docs:end:to_le_bits + // TODO: to_be_bits -> to_be_bits_unsafe /// Decomposes `self` into its big endian bit decomposition as a `[u1; N]` array. /// This array will be zero padded should not all bits be necessary to represent `self`. /// @@ -49,6 +51,7 @@ impl Field { pub fn to_be_bits<let N: u32>(self: Self) -> [u1; N] {} // docs:end:to_be_bits + // TODO: static_assert for N /// Decomposes `self` into its little endian byte decomposition as a `[u8;N]` array /// This array will be zero padded should not all bytes be necessary to represent `self`. /// @@ -82,6 +85,7 @@ impl Field { bytes } + // TODO: static_assert for N /// Decomposes `self` into its big endian byte decomposition as a `[u8;N]` array of length required to represent the field modulus /// This array will be zero padded should not all bytes be necessary to represent `self`. /// @@ -135,10 +139,13 @@ impl Field { } // docs:end:to_be_radix + // TODO: static_assert for N // `_radix` must be less than 256 #[builtin(to_le_radix)] fn __to_le_radix<let N: u32>(self, radix: u32) -> [u8; N] {} + // TODO: static_assert for N + // TODO: is this also true? "`_radix` must be less than 256" #[builtin(to_be_radix)] fn __to_be_radix<let N: u32>(self, radix: u32) -> [u8; N] {} @@ -169,6 +176,7 @@ impl Field { } } + // TODO: static_assert for N? /// Convert a little endian byte array to a field element. /// If the provided byte array overflows the field modulus then the Field will silently wrap around. pub fn from_le_bytes<let N: u32>(bytes: [u8; N]) -> Field { @@ -182,6 +190,7 @@ impl Field { result } + // TODO: static_assert for N? /// Convert a big endian byte array to a field element. /// If the provided byte array overflows the field modulus then the Field will silently wrap around. pub fn from_be_bytes<let N: u32>(bytes: [u8; N]) -> Field { diff --git a/noir_stdlib/src/meta/ctstring.nr b/noir_stdlib/src/meta/ctstring.nr index b414b3418d9..8297af7dd24 100644 --- a/noir_stdlib/src/meta/ctstring.nr +++ b/noir_stdlib/src/meta/ctstring.nr @@ -7,7 +7,8 @@ impl CtString { "".as_ctstring() } - // Bug: using &mut self as the object results in this method not being found + // TODO(https://github.com/noir-lang/noir/issues/6980): Bug: using &mut self + // as the object results in this method not being found // docs:start:append_str pub comptime fn append_str<let N: u32>(self, s: str<N>) -> Self { // docs:end:append_str diff --git a/noir_stdlib/src/uint128.nr b/noir_stdlib/src/uint128.nr index fab8cacc055..6176ff032cf 100644 --- a/noir_stdlib/src/uint128.nr +++ b/noir_stdlib/src/uint128.nr @@ -1,5 +1,6 @@ use crate::cmp::{Eq, Ord, Ordering}; use crate::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Not, Rem, Shl, Shr, Sub}; +use crate::static_assert; use super::convert::AsPrimitive; global pow64: Field = 18446744073709551616; //2^64; @@ -67,11 +68,12 @@ impl U128 { } pub fn from_hex<let N: u32>(hex: str<N>) -> U128 { - let N = N as u32; + // TODO cleanup if working + // let N = N as u32; let bytes = hex.as_bytes(); // string must starts with "0x" assert((bytes[0] == 48) & (bytes[1] == 120), "Invalid hexadecimal string"); - assert(N < 35, "Input does not fit into a U128"); + static_assert(N < 35, "Input does not fit into a U128"); let mut lo = 0; let mut hi = 0; From e835376b3ecf6a77270c8252c99fa7f2e7b20a39 Mon Sep 17 00:00:00 2001 From: "Michael J. Klein" <lambdamichael@gmail.com> Date: Thu, 9 Jan 2025 00:51:48 -0500 Subject: [PATCH 02/18] rename ToRadix -> ToRadixUnsafe, ToBits -> ToBitsUnsafe, add size checks for numeric generics in field/mod.nr, fixed printing in test_transform_program_is_idempotent --- compiler/noirc_evaluator/src/acir/mod.rs | 4 +- .../src/brillig/brillig_gen/brillig_block.rs | 4 +- .../check_for_underconstrained_values.rs | 8 +- .../src/ssa/function_builder/mod.rs | 2 +- .../noirc_evaluator/src/ssa/ir/instruction.rs | 28 +++---- .../src/ssa/ir/instruction/call.rs | 14 ++-- .../src/ssa/opt/constant_folding.rs | 16 ++-- .../src/ssa/opt/flatten_cfg.rs | 2 +- .../src/ssa/opt/remove_bit_shifts.rs | 2 +- .../src/ssa/opt/remove_enable_side_effects.rs | 4 +- .../src/ssa/opt/remove_if_else.rs | 4 +- .../src/hir/comptime/interpreter/builtin.rs | 16 ++-- noir_stdlib/src/collections/bounded_vec.nr | 4 +- noir_stdlib/src/field/mod.nr | 76 +++++++++++++++---- tooling/nargo_cli/src/cli/compile_cmd.rs | 8 +- tooling/nargo_cli/src/cli/test_cmd.rs | 2 +- .../nargo_cli/src/cli/test_cmd/formatters.rs | 2 +- 17 files changed, 124 insertions(+), 72 deletions(-) diff --git a/compiler/noirc_evaluator/src/acir/mod.rs b/compiler/noirc_evaluator/src/acir/mod.rs index 7ecc4d28032..b1244aea1bd 100644 --- a/compiler/noirc_evaluator/src/acir/mod.rs +++ b/compiler/noirc_evaluator/src/acir/mod.rs @@ -2145,7 +2145,7 @@ impl<'a> Context<'a> { Intrinsic::ApplyRangeConstraint => { unreachable!("ICE: `Intrinsic::ApplyRangeConstraint` calls should be transformed into an `Instruction::RangeCheck`"); } - Intrinsic::ToRadix(endian) => { + Intrinsic::ToRadixUnsafe(endian) => { let field = self.convert_value(arguments[0], dfg).into_var()?; let radix = self.convert_value(arguments[1], dfg).into_var()?; @@ -2164,7 +2164,7 @@ impl<'a> Context<'a> { ) .map(|array| vec![array]) } - Intrinsic::ToBits(endian) => { + Intrinsic::ToBitsUnsafe(endian) => { let field = self.convert_value(arguments[0], dfg).into_var()?; let Type::Array(result_type, array_length) = dfg.type_of_value(result_ids[0]) 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 31c99bf433e..08199844158 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -496,7 +496,7 @@ impl<'block> BrilligBlock<'block> { arguments, ); } - Intrinsic::ToBits(endianness) => { + Intrinsic::ToBitsUnsafe(endianness) => { let results = dfg.instruction_results(instruction_id); let source = self.convert_ssa_single_addr_value(arguments[0], dfg); @@ -526,7 +526,7 @@ impl<'block> BrilligBlock<'block> { self.brillig_context.deallocate_single_addr(two); } - Intrinsic::ToRadix(endianness) => { + Intrinsic::ToRadixUnsafe(endianness) => { let results = dfg.instruction_results(instruction_id); let source = self.convert_ssa_single_addr_value(arguments[0], dfg); diff --git a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs index d61dd27d02a..fc5b6cf9575 100644 --- a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs +++ b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs @@ -307,8 +307,8 @@ impl DependencyContext { | Intrinsic::SliceRemove | Intrinsic::StaticAssert | Intrinsic::StrAsBytes - | Intrinsic::ToBits(..) - | Intrinsic::ToRadix(..) + | Intrinsic::ToBitsUnsafe(..) + | Intrinsic::ToRadixUnsafe(..) | Intrinsic::FieldLessThan => { // Record all the function arguments as parents of the results self.update_children(&arguments, &results); @@ -586,8 +586,8 @@ impl Context { | Intrinsic::SliceRemove | Intrinsic::StaticAssert | Intrinsic::StrAsBytes - | Intrinsic::ToBits(..) - | Intrinsic::ToRadix(..) + | Intrinsic::ToBitsUnsafe(..) + | Intrinsic::ToRadixUnsafe(..) | Intrinsic::FieldLessThan => { self.value_sets.push(instruction_arguments_and_results); } diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index d08d5339237..1e060e1757b 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -566,7 +566,7 @@ mod tests { let one = builder.numeric_constant(FieldElement::one(), NumericType::bool()); let zero = builder.numeric_constant(FieldElement::zero(), NumericType::bool()); - let to_bits_id = builder.import_intrinsic_id(Intrinsic::ToBits(Endian::Little)); + let to_bits_id = builder.import_intrinsic_id(Intrinsic::ToBitsUnsafe(Endian::Little)); let input = builder.field_constant(FieldElement::from(7_u128)); let length = builder.field_constant(FieldElement::from(8_u128)); let result_types = vec![Type::Array(Arc::new(vec![Type::bool()]), 8)]; diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index aadd35040cf..cdc3c91d4bd 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -61,8 +61,8 @@ pub(crate) enum Intrinsic { SliceRemove, ApplyRangeConstraint, StrAsBytes, - ToBits(Endian), - ToRadix(Endian), + ToBitsUnsafe(Endian), + ToRadixUnsafe(Endian), BlackBox(BlackBoxFunc), Hint(Hint), AsWitness, @@ -89,10 +89,10 @@ impl std::fmt::Display for Intrinsic { Intrinsic::SliceRemove => write!(f, "slice_remove"), Intrinsic::StrAsBytes => write!(f, "str_as_bytes"), Intrinsic::ApplyRangeConstraint => write!(f, "apply_range_constraint"), - Intrinsic::ToBits(Endian::Big) => write!(f, "to_be_bits"), - Intrinsic::ToBits(Endian::Little) => write!(f, "to_le_bits"), - Intrinsic::ToRadix(Endian::Big) => write!(f, "to_be_radix"), - Intrinsic::ToRadix(Endian::Little) => write!(f, "to_le_radix"), + Intrinsic::ToBitsUnsafe(Endian::Big) => write!(f, "to_be_bits_unsafe"), + Intrinsic::ToBitsUnsafe(Endian::Little) => write!(f, "to_le_bits_unsafe"), + Intrinsic::ToRadixUnsafe(Endian::Big) => write!(f, "to_be_radix_unsafe"), + Intrinsic::ToRadixUnsafe(Endian::Little) => write!(f, "to_le_radix_unsafe"), Intrinsic::BlackBox(function) => write!(f, "{function}"), Intrinsic::Hint(Hint::BlackBox) => write!(f, "black_box"), Intrinsic::AsWitness => write!(f, "as_witness"), @@ -124,7 +124,7 @@ impl Intrinsic { | Intrinsic::AsWitness => true, // These apply a constraint that the input must fit into a specified number of limbs. - Intrinsic::ToBits(_) | Intrinsic::ToRadix(_) => true, + Intrinsic::ToBitsUnsafe(_) | Intrinsic::ToRadixUnsafe(_) => true, // These imply a check that the slice is non-empty and should fail otherwise. Intrinsic::SlicePopBack | Intrinsic::SlicePopFront | Intrinsic::SliceRemove => true, @@ -164,8 +164,8 @@ impl Intrinsic { // directly depend on the corresponding `enable_side_effect` instruction any more. // However, to conform with the expectations of `Instruction::can_be_deduplicated` and // `constant_folding` we only use this information if the caller shows interest in it. - Intrinsic::ToBits(_) - | Intrinsic::ToRadix(_) + Intrinsic::ToBitsUnsafe(_) + | Intrinsic::ToRadixUnsafe(_) | Intrinsic::BlackBox( BlackBoxFunc::MultiScalarMul | BlackBoxFunc::EmbeddedCurveAdd @@ -203,10 +203,10 @@ impl Intrinsic { "slice_insert" => Some(Intrinsic::SliceInsert), "slice_remove" => Some(Intrinsic::SliceRemove), "str_as_bytes" => Some(Intrinsic::StrAsBytes), - "to_le_radix" => Some(Intrinsic::ToRadix(Endian::Little)), - "to_be_radix" => Some(Intrinsic::ToRadix(Endian::Big)), - "to_le_bits" => Some(Intrinsic::ToBits(Endian::Little)), - "to_be_bits" => Some(Intrinsic::ToBits(Endian::Big)), + "to_le_radix_unsafe" => Some(Intrinsic::ToRadixUnsafe(Endian::Little)), + "to_be_radix_unsafe" => Some(Intrinsic::ToRadixUnsafe(Endian::Big)), + "to_le_bits_unsafe" => Some(Intrinsic::ToBitsUnsafe(Endian::Little)), + "to_be_bits_unsafe" => Some(Intrinsic::ToBitsUnsafe(Endian::Big)), "as_witness" => Some(Intrinsic::AsWitness), "is_unconstrained" => Some(Intrinsic::IsUnconstrained), "derive_pedersen_generators" => Some(Intrinsic::DerivePedersenGenerators), @@ -220,7 +220,7 @@ impl Intrinsic { } } -/// The endian-ness of bits when encoding values as bits in e.g. ToBits or ToRadix +/// The endian-ness of bits when encoding values as bits in e.g. ToBitsUnsafe or ToRadixUnsafe #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub(crate) enum Endian { Big, diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 951761e041e..74601f891e3 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -52,16 +52,16 @@ pub(super) fn simplify_call( arguments.iter().map(|value_id| dfg.get_numeric_constant(*value_id)).collect(); let simplified_result = match intrinsic { - Intrinsic::ToBits(endian) => { + Intrinsic::ToBitsUnsafe(endian) => { // TODO: simplify to a range constraint if `limb_count == 1` if let (Some(constant_args), Some(return_type)) = (constant_args, return_type.clone()) { let field = constant_args[0]; let limb_count = if let Type::Array(_, array_len) = return_type { array_len } else { - unreachable!("ICE: Intrinsic::ToRadix return type must be array") + unreachable!("ICE: Intrinsic::ToRadixUnsafe return type must be array") }; - constant_to_radix(endian, field, 2, limb_count, |values| { + constant_to_radix_unsafe(endian, field, 2, limb_count, |values| { make_constant_array( dfg, values.into_iter(), @@ -74,7 +74,7 @@ pub(super) fn simplify_call( SimplifyResult::None } } - Intrinsic::ToRadix(endian) => { + Intrinsic::ToRadixUnsafe(endian) => { // TODO: simplify to a range constraint if `limb_count == 1` if let (Some(constant_args), Some(return_type)) = (constant_args, return_type.clone()) { let field = constant_args[0]; @@ -82,9 +82,9 @@ pub(super) fn simplify_call( let limb_count = if let Type::Array(_, array_len) = return_type { array_len } else { - unreachable!("ICE: Intrinsic::ToRadix return type must be array") + unreachable!("ICE: Intrinsic::ToRadixUnsafe return type must be array") }; - constant_to_radix(endian, field, radix, limb_count, |values| { + constant_to_radix_unsafe(endian, field, radix, limb_count, |values| { make_constant_array( dfg, values.into_iter(), @@ -620,7 +620,7 @@ fn make_array( } /// Returns a slice (represented by a tuple (len, slice)) of constants corresponding to the limbs of the radix decomposition. -fn constant_to_radix( +fn constant_to_radix_unsafe( endian: Endian, field: FieldElement, radix: u32, diff --git a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs index 8dca867fc6d..5222dbc821b 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs @@ -1558,15 +1558,15 @@ mod test { brillig(inline) fn main f0 { b0(v0: Field, v1: Field, v2: u1): v4 = call is_unconstrained() -> u1 - v7 = call to_be_radix(v0, u32 256) -> [u8; 1] // `a.to_be_radix(256)`; + v7 = call to_be_radix_unsafe(v0, u32 256) -> [u8; 1] // `a.to_be_radix_unsafe(256)`; inc_rc v7 - v8 = call to_be_radix(v0, u32 256) -> [u8; 1] // duplicate load of `a` + v8 = call to_be_radix_unsafe(v0, u32 256) -> [u8; 1] // duplicate load of `a` inc_rc v8 - v9 = cast v2 as Field // `if c { a.to_be_radix(256) }` - v10 = mul v0, v9 // attaching `c` to `a` - v11 = call to_be_radix(v10, u32 256) -> [u8; 1] // calling `to_radix(c * a)` + v9 = cast v2 as Field // `if c { a.to_be_radix_unsafe(256) }` + v10 = mul v0, v9 // attaching `c` to `a` + v11 = call to_be_radix_unsafe(v10, u32 256) -> [u8; 1] // calling `to_radix(c * a)` inc_rc v11 - enable_side_effects v2 // side effect var for `c` shifted down by removal + enable_side_effects v2 // side effect var for `c` shifted down by removal return } "; @@ -1575,12 +1575,12 @@ mod test { brillig(inline) fn main f0 { b0(v0: Field, v1: Field, v2: u1): v4 = call is_unconstrained() -> u1 - v7 = call to_be_radix(v0, u32 256) -> [u8; 1] + v7 = call to_be_radix_unsafe(v0, u32 256) -> [u8; 1] inc_rc v7 inc_rc v7 v8 = cast v2 as Field v9 = mul v0, v8 - v10 = call to_be_radix(v9, u32 256) -> [u8; 1] + v10 = call to_be_radix_unsafe(v9, u32 256) -> [u8; 1] inc_rc v10 enable_side_effects v2 return diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index 748867c7409..6a6cb7aa24a 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -712,7 +712,7 @@ impl<'f> Context<'f> { } Instruction::Call { func, mut arguments } => match self.inserter.function.dfg[func] { - Value::Intrinsic(Intrinsic::ToBits(_) | Intrinsic::ToRadix(_)) => { + Value::Intrinsic(Intrinsic::ToBitsUnsafe(_) | Intrinsic::ToRadixUnsafe(_)) => { let field = arguments[0]; let argument_type = self.inserter.function.dfg.type_of_value(field); diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs index 61d53cbd960..831e7cef926 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs @@ -190,7 +190,7 @@ impl Context<'_> { fn pow(&mut self, lhs: ValueId, rhs: ValueId) -> ValueId { let typ = self.function.dfg.type_of_value(rhs); if let Type::Numeric(NumericType::Unsigned { bit_size }) = typ { - let to_bits = self.function.dfg.import_intrinsic(Intrinsic::ToBits(Endian::Little)); + let to_bits = self.function.dfg.import_intrinsic(Intrinsic::ToBitsUnsafe(Endian::Little)); let result_types = vec![Type::Array(Arc::new(vec![Type::bool()]), bit_size)]; let rhs_bits = self.insert_call(to_bits, vec![rhs], result_types); diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs index a22232ba49a..5c7317749f4 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs @@ -173,8 +173,8 @@ impl Context { | Intrinsic::StaticAssert | Intrinsic::ApplyRangeConstraint | Intrinsic::StrAsBytes - | Intrinsic::ToBits(_) - | Intrinsic::ToRadix(_) + | Intrinsic::ToBitsUnsafe(_) + | Intrinsic::ToRadixUnsafe(_) | Intrinsic::BlackBox(_) | Intrinsic::Hint(Hint::BlackBox) | Intrinsic::AsSlice diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs index 8dde79a3c60..160b9b54526 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs @@ -234,8 +234,8 @@ fn slice_capacity_change( | Intrinsic::AsWitness | Intrinsic::IsUnconstrained | Intrinsic::DerivePedersenGenerators - | Intrinsic::ToBits(_) - | Intrinsic::ToRadix(_) + | Intrinsic::ToBitsUnsafe(_) + | Intrinsic::ToRadixUnsafe(_) | Intrinsic::ArrayRefCount | Intrinsic::SliceRefCount | Intrinsic::FieldLessThan => SizeChange::None, diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 8a9fcf12e16..34dc742867a 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -190,8 +190,8 @@ impl<'local, 'context> Interpreter<'local, 'context> { "struct_def_module" => struct_def_module(self, arguments, location), "struct_def_name" => struct_def_name(interner, arguments, location), "struct_def_set_fields" => struct_def_set_fields(interner, arguments, location), - "to_be_radix" => to_be_radix(arguments, return_type, location), - "to_le_radix" => to_le_radix(arguments, return_type, location), + "to_be_radix_unsafe" => to_be_radix_unsafe(arguments, return_type, location), + "to_le_radix_unsafe" => to_le_radix_unsafe(arguments, return_type, location), "trait_constraint_eq" => trait_constraint_eq(arguments, location), "trait_constraint_hash" => trait_constraint_hash(arguments, location), "trait_def_as_trait_constraint" => { @@ -776,22 +776,22 @@ fn quoted_tokens(arguments: Vec<(Value, Location)>, location: Location) -> IResu )) } -fn to_be_radix( +fn to_be_radix_unsafe( arguments: Vec<(Value, Location)>, return_type: Type, location: Location, ) -> IResult<Value> { - let le_radix_limbs = to_le_radix(arguments, return_type, location)?; + let le_radix_limbs = to_le_radix_unsafe(arguments, return_type, location)?; let Value::Array(limbs, typ) = le_radix_limbs else { - unreachable!("`to_le_radix` should always return an array"); + unreachable!("`to_le_radix_unsafe` should always return an array"); }; let be_radix_limbs = limbs.into_iter().rev().collect(); Ok(Value::Array(be_radix_limbs, typ)) } -fn to_le_radix( +fn to_le_radix_unsafe( arguments: Vec<(Value, Location)>, return_type: Type, location: Location, @@ -815,7 +815,7 @@ fn to_le_radix( }; // Decompose the integer into its radix digits in little endian form. - let decomposed_integer = compute_to_radix_le(value, radix); + let decomposed_integer = compute_to_radix_le_unsafe(value, radix); let decomposed_integer = vecmap(0..limb_count.to_u128() as usize, |i| match decomposed_integer.get(i) { Some(digit) => Value::U8(*digit), @@ -825,7 +825,7 @@ fn to_le_radix( Ok(Value::Array(decomposed_integer.into(), result_type)) } -fn compute_to_radix_le(field: FieldElement, radix: u32) -> Vec<u8> { +fn compute_to_radix_le_unsafe(field: FieldElement, radix: u32) -> Vec<u8> { let bit_size = u32::BITS - (radix - 1).leading_zeros(); let radix_big = BigUint::from(radix); assert_eq!(BigUint::from(2u128).pow(bit_size), radix_big, "ICE: Radix must be a power of 2"); diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr index 4b8820df8c8..a6760f4071d 100644 --- a/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir_stdlib/src/collections/bounded_vec.nr @@ -612,8 +612,8 @@ mod bounded_vec_tests { assert_eq(bounded_vec.get(2), 3); } - // TODO: works with 'shoul_fail': does 'should_fail_with' support 'static_assert'? - #[test(should_fail_with = "from array out of bounds")] + // TODO(https://github.com/noir-lang/noir/issues/6995): #[test(should_fail_with = "from array out of bounds")] + #[test(should_fail_with = "Argument is false")] fn max_len_lower_then_array_len() { let _: BoundedVec<Field, 2> = BoundedVec::from_array([0; 3]); } diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr index abbc34854c0..b6429d78a09 100644 --- a/noir_stdlib/src/field/mod.nr +++ b/noir_stdlib/src/field/mod.nr @@ -17,7 +17,6 @@ impl Field { #[builtin(apply_range_constraint)] fn __assert_max_bit_size(self, bit_size: u32) {} - // TODO: to_le_bits -> to_le_bits_unsafe /// Decomposes `self` into its little endian bit decomposition as a `[u1; N]` array. /// This slice will be zero padded should not all bits be necessary to represent `self`. /// @@ -29,12 +28,28 @@ impl Field { /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus /// (e.g. 254 for the BN254 field) allow for multiple bit decompositions. This is due to how the `Field` will /// wrap around due to overflow when verifying the decomposition. - #[builtin(to_le_bits)] + #[builtin(to_le_bits_unsafe)] + // docs:start:to_le_bits_unsafe + pub fn to_le_bits_unsafe<let N: u32>(self: Self) -> [u1; N] {} + // docs:end:to_le_bits_unsafe + + /// Decomposes `self` into its little endian bit decomposition as a `[u1; N]` array. + /// This slice will be zero padded should not all bits be necessary to represent `self`. + /// + /// # Failures + /// Causes a constraint failure for `Field` values exceeding `2^N` as the resulting slice will not + /// be able to represent the original `Field`. + /// + /// # Safety + /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus + /// (e.g. 254 for the BN254 field) cause a compile-time error. // docs:start:to_le_bits - pub fn to_le_bits<let N: u32>(self: Self) -> [u1; N] {} + pub fn to_le_bits<let N: u32>(self: Self) -> [u1; N] { + static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits"); + self.to_le_bits_unsafe() + } // docs:end:to_le_bits - // TODO: to_be_bits -> to_be_bits_unsafe /// Decomposes `self` into its big endian bit decomposition as a `[u1; N]` array. /// This array will be zero padded should not all bits be necessary to represent `self`. /// @@ -46,12 +61,28 @@ impl Field { /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus /// (e.g. 254 for the BN254 field) allow for multiple bit decompositions. This is due to how the `Field` will /// wrap around due to overflow when verifying the decomposition. - #[builtin(to_be_bits)] + #[builtin(to_be_bits_unsafe)] + // docs:start:to_be_bits_unsafe + pub fn to_be_bits_unsafe<let N: u32>(self: Self) -> [u1; N] {} + // docs:end:to_be_bits_unsafe + + /// Decomposes `self` into its big endian bit decomposition as a `[u1; N]` array. + /// This array will be zero padded should not all bits be necessary to represent `self`. + /// + /// # Failures + /// Causes a constraint failure for `Field` values exceeding `2^N` as the resulting slice will not + /// be able to represent the original `Field`. + /// + /// # Safety + /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus + /// (e.g. 254 for the BN254 field) cause a compile-time error. // docs:start:to_be_bits - pub fn to_be_bits<let N: u32>(self: Self) -> [u1; N] {} + pub fn to_be_bits<let N: u32>(self: Self) -> [u1; N] { + static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits"); + self.to_be_bits_unsafe() + } // docs:end:to_be_bits - // TODO: static_assert for N /// Decomposes `self` into its little endian byte decomposition as a `[u8;N]` array /// This array will be zero padded should not all bytes be necessary to represent `self`. /// @@ -64,6 +95,7 @@ impl Field { // docs:start:to_le_bytes pub fn to_le_bytes<let N: u32>(self: Self) -> [u8; N] { // docs:end:to_le_bytes + static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()"); // Compute the byte decomposition let bytes = self.to_le_radix(256); @@ -85,7 +117,6 @@ impl Field { bytes } - // TODO: static_assert for N /// Decomposes `self` into its big endian byte decomposition as a `[u8;N]` array of length required to represent the field modulus /// This array will be zero padded should not all bytes be necessary to represent `self`. /// @@ -98,6 +129,7 @@ impl Field { // docs:start:to_be_bytes pub fn to_be_bytes<let N: u32>(self: Self) -> [u8; N] { // docs:end:to_be_bytes + static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()"); // Compute the byte decomposition let bytes = self.to_be_radix(256); @@ -139,15 +171,27 @@ impl Field { } // docs:end:to_be_radix - // TODO: static_assert for N // `_radix` must be less than 256 - #[builtin(to_le_radix)] - fn __to_le_radix<let N: u32>(self, radix: u32) -> [u8; N] {} + #[builtin(to_le_radix_unsafe)] + fn __to_le_radix_unsafe<let N: u32>(self, radix: u32) -> [u8; N] {} - // TODO: static_assert for N - // TODO: is this also true? "`_radix` must be less than 256" - #[builtin(to_be_radix)] - fn __to_be_radix<let N: u32>(self, radix: u32) -> [u8; N] {} + // `_radix` must be less than 256 + fn __to_le_radix<let N: u32>(self, radix: u32) -> [u8; N] { + static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits"); + // TODO(https://github.com/noir-lang/noir/issues/6964): assert(radix < 256, "`_radix` must be less than 256"); + self.__to_le_radix_unsafe(radix) + } + + // `_radix` must be less than 256 + #[builtin(to_be_radix_unsafe)] + fn __to_be_radix_unsafe<let N: u32>(self, radix: u32) -> [u8; N] {} + + // `_radix` must be less than 256 + fn __to_be_radix<let N: u32>(self, radix: u32) -> [u8; N] { + static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits"); + // TODO(https://github.com/noir-lang/noir/issues/6964): assert(radix < 256, "`_radix` must be less than 256"); + self.__to_be_radix_unsafe(radix) + } // Returns self to the power of the given exponent value. // Caution: we assume the exponent fits into 32 bits @@ -180,6 +224,7 @@ impl Field { /// Convert a little endian byte array to a field element. /// If the provided byte array overflows the field modulus then the Field will silently wrap around. pub fn from_le_bytes<let N: u32>(bytes: [u8; N]) -> Field { + static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()"); let mut v = 1; let mut result = 0; @@ -194,6 +239,7 @@ impl Field { /// Convert a big endian byte array to a field element. /// If the provided byte array overflows the field modulus then the Field will silently wrap around. pub fn from_be_bytes<let N: u32>(bytes: [u8; N]) -> Field { + static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()"); let mut v = 1; let mut result = 0; diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index 0af05703c9a..8d95f9917cd 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -335,6 +335,7 @@ mod tests { use noirc_driver::{CompileOptions, CrateName}; use crate::cli::compile_cmd::{get_target_width, parse_workspace, read_workspace}; + use crate::cli::test_cmd::formatters::diagnostic_to_string; /// Try to find the directory that Cargo sets when it is running; /// otherwise fallback to assuming the CWD is the root of the repository @@ -414,7 +415,12 @@ mod tests { &CompileOptions::default(), None, ) - .expect("failed to compile"); + .unwrap_or_else(|err| { + let error_string: String = err.iter().map(|diagnostic| { + format!("{}\n---\n", diagnostic_to_string(diagnostic, &file_manager)) + }).collect(); + panic!("Failed to compile:\n\n{}", error_string) + }); let width = get_target_width(package.expression_width, None); diff --git a/tooling/nargo_cli/src/cli/test_cmd.rs b/tooling/nargo_cli/src/cli/test_cmd.rs index 43f1c306d88..f7fe7715d19 100644 --- a/tooling/nargo_cli/src/cli/test_cmd.rs +++ b/tooling/nargo_cli/src/cli/test_cmd.rs @@ -26,7 +26,7 @@ use crate::{cli::check_cmd::check_crate_and_report_errors, errors::CliError}; use super::{NargoConfig, PackageOptions}; -mod formatters; +pub(crate) mod formatters; /// Run the tests for this program #[derive(Debug, Clone, Args)] diff --git a/tooling/nargo_cli/src/cli/test_cmd/formatters.rs b/tooling/nargo_cli/src/cli/test_cmd/formatters.rs index 75cf14ba120..7651d663c19 100644 --- a/tooling/nargo_cli/src/cli/test_cmd/formatters.rs +++ b/tooling/nargo_cli/src/cli/test_cmd/formatters.rs @@ -514,7 +514,7 @@ fn package_start(package_name: &str, test_count: usize) -> std::io::Result<()> { Ok(()) } -fn diagnostic_to_string(file_diagnostic: &FileDiagnostic, file_manager: &FileManager) -> String { +pub(crate) fn diagnostic_to_string(file_diagnostic: &FileDiagnostic, file_manager: &FileManager) -> String { let file_map = file_manager.as_file_map(); let custom_diagnostic = &file_diagnostic.diagnostic; From 0896b0879161d62c7c253a6f00cf46b71e2879c1 Mon Sep 17 00:00:00 2001 From: "Michael J. Klein" <lambdamichael@gmail.com> Date: Thu, 9 Jan 2025 11:59:27 -0500 Subject: [PATCH 03/18] wip implementing comptime static_assert, add tests for comptime static_assert, ensure dynamic vars rejected by static_assert --- .../src/hir/comptime/interpreter/builtin.rs | 22 ++++++++++++++++++- .../src/tests/metaprogramming.rs | 21 ++++++++++++++++++ noir_stdlib/src/field/mod.nr | 2 -- .../comptime_static_assert_failure/Nargo.toml | 6 +++++ .../src/main.nr | 13 +++++++++++ .../comptime_static_assert/Nargo.toml | 6 +++++ .../comptime_static_assert/src/main.nr | 19 ++++++++++++++++ tooling/nargo_cli/src/cli/compile_cmd.rs | 1 + 8 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 test_programs/compile_failure/comptime_static_assert_failure/Nargo.toml create mode 100644 test_programs/compile_failure/comptime_static_assert_failure/src/main.nr create mode 100644 test_programs/compile_success_empty/comptime_static_assert/Nargo.toml create mode 100644 test_programs/compile_success_empty/comptime_static_assert/src/main.nr diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 34dc742867a..0084e4dcdfe 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -66,7 +66,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { "array_as_str_unchecked" => array_as_str_unchecked(interner, arguments, location), "array_len" => array_len(interner, arguments, location), "array_refcount" => Ok(Value::U32(0)), - "assert_constant" => Ok(Value::Bool(true)), + "assert_constant" => Ok(Value::Unit), "as_slice" => as_slice(interner, arguments, location), "ctstring_eq" => ctstring_eq(arguments, location), "ctstring_hash" => ctstring_hash(arguments, location), @@ -175,6 +175,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { "slice_push_front" => slice_push_front(interner, arguments, location), "slice_refcount" => Ok(Value::U32(0)), "slice_remove" => slice_remove(interner, arguments, location, call_stack), + "static_assert" => static_assert(interner, arguments, location, call_stack), "str_as_bytes" => str_as_bytes(interner, arguments, location), "str_as_ctstring" => str_as_ctstring(interner, arguments, location), "struct_def_add_attribute" => struct_def_add_attribute(interner, arguments, location), @@ -320,6 +321,25 @@ fn slice_push_back( Ok(Value::Slice(values, typ)) } +// static_assert<let N: u32>(predicate: bool, message: str<N>) +fn static_assert( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, + call_stack: &im::Vector<Location>, +) -> IResult<Value> { + let (predicate, message) = check_two_arguments(arguments, location)?; + let predicate = get_bool(predicate)?; + let message = get_str(interner, message)?; + + "TODO need to evaluate static_assert's predicate more?!"; + if predicate { + Ok(Value::Unit) + } else { + failing_constraint((*message).clone(), location, call_stack) + } +} + fn str_as_bytes( interner: &NodeInterner, arguments: Vec<(Value, Location)>, diff --git a/compiler/noirc_frontend/src/tests/metaprogramming.rs b/compiler/noirc_frontend/src/tests/metaprogramming.rs index 8256744e18f..6142271c502 100644 --- a/compiler/noirc_frontend/src/tests/metaprogramming.rs +++ b/compiler/noirc_frontend/src/tests/metaprogramming.rs @@ -3,6 +3,7 @@ use noirc_errors::Spanned; use crate::{ ast::Ident, hir::{ + comptime::InterpreterError, def_collector::{ dc_crate::CompilationError, errors::{DefCollectorErrorKind, DuplicateType}, @@ -26,6 +27,26 @@ fn comptime_let() { assert_eq!(errors.len(), 0); } +#[test] +fn comptime_code_rejects_dynamic_variable() { + let src = r#"fn main(x: Field) { + comptime let my_var = (x - x) + 2; + assert_eq(my_var, 2); + }"#; + let errors = get_program_errors(src); + + // TODO cleanup + dbg!(&errors); + + assert_eq!(errors.len(), 1); + match &errors[0].0 { + CompilationError::InterpreterError(InterpreterError::NonComptimeVarReferenced { name, .. }) => { + assert_eq!(name, "x"); + } + _ => panic!("expected an InterpreterError"), + } +} + #[test] fn comptime_type_in_runtime_code() { let source = "pub fn foo(_f: FunctionDefinition) {}"; diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr index b6429d78a09..8f6177e858e 100644 --- a/noir_stdlib/src/field/mod.nr +++ b/noir_stdlib/src/field/mod.nr @@ -220,7 +220,6 @@ impl Field { } } - // TODO: static_assert for N? /// Convert a little endian byte array to a field element. /// If the provided byte array overflows the field modulus then the Field will silently wrap around. pub fn from_le_bytes<let N: u32>(bytes: [u8; N]) -> Field { @@ -235,7 +234,6 @@ impl Field { result } - // TODO: static_assert for N? /// Convert a big endian byte array to a field element. /// If the provided byte array overflows the field modulus then the Field will silently wrap around. pub fn from_be_bytes<let N: u32>(bytes: [u8; N]) -> Field { diff --git a/test_programs/compile_failure/comptime_static_assert_failure/Nargo.toml b/test_programs/compile_failure/comptime_static_assert_failure/Nargo.toml new file mode 100644 index 00000000000..006fd9f7ffe --- /dev/null +++ b/test_programs/compile_failure/comptime_static_assert_failure/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "comptime_static_assert_failure" +type = "bin" +authors = [""] + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/comptime_static_assert_failure/src/main.nr b/test_programs/compile_failure/comptime_static_assert_failure/src/main.nr new file mode 100644 index 00000000000..fcd757f4c94 --- /dev/null +++ b/test_programs/compile_failure/comptime_static_assert_failure/src/main.nr @@ -0,0 +1,13 @@ +use std::static_assert; + +comptime fn foo(x: Field) -> bool { + static_assert(x == 4, "x != 4"); + x == 4 +} + +fn main() { + comptime { + static_assert(foo(3), "expected message"); + } +} + diff --git a/test_programs/compile_success_empty/comptime_static_assert/Nargo.toml b/test_programs/compile_success_empty/comptime_static_assert/Nargo.toml new file mode 100644 index 00000000000..4c969fe7a79 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_static_assert/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "comptime_static_assert" +type = "bin" +authors = [""] + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_empty/comptime_static_assert/src/main.nr b/test_programs/compile_success_empty/comptime_static_assert/src/main.nr new file mode 100644 index 00000000000..2ddbba7b0de --- /dev/null +++ b/test_programs/compile_success_empty/comptime_static_assert/src/main.nr @@ -0,0 +1,19 @@ +use std::static_assert; + +comptime fn foo(x: Field) -> bool { + static_assert(x == 4, "x != 4"); + x == 4 +} + +global C: bool = { + let out = foo(2 + 2); + static_assert(out, "foo did not pass in C"); + out +}; + +fn main() { + comptime { + static_assert(foo(4), "foo did not pass in main"); + static_assert(C, "C did not pass") + } +} diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index 8d95f9917cd..b33df08bd04 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -415,6 +415,7 @@ mod tests { &CompileOptions::default(), None, ) + // TODO: collect all errors and only error at the end! (i.e. don't fail fast) .unwrap_or_else(|err| { let error_string: String = err.iter().map(|diagnostic| { format!("{}\n---\n", diagnostic_to_string(diagnostic, &file_manager)) From 89a025327a46b373677d993e53cd78cc38cea46f Mon Sep 17 00:00:00 2001 From: Michael Klein <lambdamichael@gmail.com> Date: Thu, 9 Jan 2025 14:14:46 -0500 Subject: [PATCH 04/18] rename after merge --- .../noirc_frontend/src/hir/comptime/interpreter/builtin.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 111269023a4..024e5fc3e65 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -805,7 +805,7 @@ fn to_be_bits_unsafe( ) -> IResult<Value> { let value = check_one_argument(arguments, location)?; let radix = (Value::U32(2), value.1); - to_be_radix(vec![value, radix], return_type, location) + to_be_radix_unsafe(vec![value, radix], return_type, location) } fn to_le_bits_unsafe( @@ -815,7 +815,7 @@ fn to_le_bits_unsafe( ) -> IResult<Value> { let value = check_one_argument(arguments, location)?; let radix = (Value::U32(2), value.1); - to_le_radix(vec![value, radix], return_type, location) + to_le_radix_unsafe(vec![value, radix], return_type, location) } fn to_be_radix_unsafe( From 3cc4f7d84e16d163ae31a0a9ff847f7dc304d681 Mon Sep 17 00:00:00 2001 From: Michael Klein <lambdamichael@gmail.com> Date: Thu, 9 Jan 2025 15:56:36 -0500 Subject: [PATCH 05/18] debug failing tests by updating '<' -> '<=' where needed, cargo clippy/fmt --- .../noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs | 3 ++- .../src/hir/comptime/interpreter/builtin.rs | 7 +++++-- compiler/noirc_frontend/src/tests/metaprogramming.rs | 5 ++++- noir_stdlib/src/field/mod.nr | 8 ++++---- tooling/nargo_cli/src/cli/compile_cmd.rs | 9 ++++++--- tooling/nargo_cli/src/cli/test_cmd/formatters.rs | 5 ++++- 6 files changed, 25 insertions(+), 12 deletions(-) diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs index b5d1868db1e..e24e4246d67 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs @@ -193,7 +193,8 @@ impl Context<'_> { fn pow(&mut self, lhs: ValueId, rhs: ValueId) -> ValueId { let typ = self.function.dfg.type_of_value(rhs); if let Type::Numeric(NumericType::Unsigned { bit_size }) = typ { - let to_bits = self.function.dfg.import_intrinsic(Intrinsic::ToBitsUnsafe(Endian::Little)); + let to_bits = + self.function.dfg.import_intrinsic(Intrinsic::ToBitsUnsafe(Endian::Little)); let result_types = vec![Type::Array(Arc::new(vec![Type::bool()]), bit_size)]; let rhs_bits = self.insert_call(to_bits, vec![rhs], result_types); diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 024e5fc3e65..e4998ee3c48 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -334,11 +334,14 @@ fn static_assert( let predicate = get_bool(predicate)?; let message = get_str(interner, message)?; - "TODO need to evaluate static_assert's predicate more?!"; if predicate { Ok(Value::Unit) } else { - failing_constraint((*message).clone(), location, call_stack) + failing_constraint( + format!("static_assert failed: {}", message).clone(), + location, + call_stack, + ) } } diff --git a/compiler/noirc_frontend/src/tests/metaprogramming.rs b/compiler/noirc_frontend/src/tests/metaprogramming.rs index 6142271c502..1efbb4efcd7 100644 --- a/compiler/noirc_frontend/src/tests/metaprogramming.rs +++ b/compiler/noirc_frontend/src/tests/metaprogramming.rs @@ -40,7 +40,10 @@ fn comptime_code_rejects_dynamic_variable() { assert_eq!(errors.len(), 1); match &errors[0].0 { - CompilationError::InterpreterError(InterpreterError::NonComptimeVarReferenced { name, .. }) => { + CompilationError::InterpreterError(InterpreterError::NonComptimeVarReferenced { + name, + .. + }) => { assert_eq!(name, "x"); } _ => panic!("expected an InterpreterError"), diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr index 1c3a484a379..0aa19942846 100644 --- a/noir_stdlib/src/field/mod.nr +++ b/noir_stdlib/src/field/mod.nr @@ -45,7 +45,7 @@ impl Field { /// (e.g. 254 for the BN254 field) cause a compile-time error. // docs:start:to_le_bits pub fn to_le_bits<let N: u32>(self: Self) -> [u1; N] { - static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits"); + static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits"); self.to_le_bits_unsafe() } // docs:end:to_le_bits @@ -78,7 +78,7 @@ impl Field { /// (e.g. 254 for the BN254 field) cause a compile-time error. // docs:start:to_be_bits pub fn to_be_bits<let N: u32>(self: Self) -> [u1; N] { - static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits"); + static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits"); self.to_be_bits_unsafe() } // docs:end:to_be_bits @@ -177,7 +177,7 @@ impl Field { // `_radix` must be less than 256 fn __to_le_radix<let N: u32>(self, radix: u32) -> [u8; N] { - static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits"); + static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits"); // TODO(https://github.com/noir-lang/noir/issues/6964): assert(radix < 256, "`_radix` must be less than 256"); self.__to_le_radix_unsafe(radix) } @@ -188,7 +188,7 @@ impl Field { // `_radix` must be less than 256 fn __to_be_radix<let N: u32>(self, radix: u32) -> [u8; N] { - static_assert(N < modulus_num_bits() as u32, "N must be less than modulus_num_bits"); + static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits"); // TODO(https://github.com/noir-lang/noir/issues/6964): assert(radix < 256, "`_radix` must be less than 256"); self.__to_be_radix_unsafe(radix) } diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index b33df08bd04..fb46752115c 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -417,9 +417,12 @@ mod tests { ) // TODO: collect all errors and only error at the end! (i.e. don't fail fast) .unwrap_or_else(|err| { - let error_string: String = err.iter().map(|diagnostic| { - format!("{}\n---\n", diagnostic_to_string(diagnostic, &file_manager)) - }).collect(); + let error_string: String = err + .iter() + .map(|diagnostic| { + format!("{}\n---\n", diagnostic_to_string(diagnostic, &file_manager)) + }) + .collect(); panic!("Failed to compile:\n\n{}", error_string) }); diff --git a/tooling/nargo_cli/src/cli/test_cmd/formatters.rs b/tooling/nargo_cli/src/cli/test_cmd/formatters.rs index 7651d663c19..bc4621c92ea 100644 --- a/tooling/nargo_cli/src/cli/test_cmd/formatters.rs +++ b/tooling/nargo_cli/src/cli/test_cmd/formatters.rs @@ -514,7 +514,10 @@ fn package_start(package_name: &str, test_count: usize) -> std::io::Result<()> { Ok(()) } -pub(crate) fn diagnostic_to_string(file_diagnostic: &FileDiagnostic, file_manager: &FileManager) -> String { +pub(crate) fn diagnostic_to_string( + file_diagnostic: &FileDiagnostic, + file_manager: &FileManager, +) -> String { let file_map = file_manager.as_file_map(); let custom_diagnostic = &file_diagnostic.diagnostic; From 75eb48ca0e90e71ca0613b8c27021a3fa6dfb5fc Mon Sep 17 00:00:00 2001 From: "Michael J. Klein" <lambdamichael@gmail.com> Date: Tue, 14 Jan 2025 14:05:13 -0500 Subject: [PATCH 06/18] reverting To(Bits|Radix)Unsafe changes, wip testing 'self.range_constraint(input_expr_witness, bit_size)?;' for to_radix --- .../src/acir/generated_acir.rs | 5 ++ compiler/noirc_evaluator/src/acir/mod.rs | 4 +- .../src/brillig/brillig_gen/brillig_block.rs | 4 +- .../check_for_underconstrained_values.rs | 8 +-- .../src/ssa/function_builder/mod.rs | 2 +- .../noirc_evaluator/src/ssa/ir/instruction.rs | 28 ++++---- .../src/ssa/ir/instruction/call.rs | 14 ++-- .../src/ssa/opt/constant_folding.rs | 14 ++-- .../src/ssa/opt/flatten_cfg.rs | 2 +- .../src/ssa/opt/remove_bit_shifts.rs | 3 +- .../src/ssa/opt/remove_enable_side_effects.rs | 4 +- .../src/ssa/opt/remove_if_else.rs | 4 +- .../src/hir/comptime/interpreter/builtin.rs | 28 ++++---- noir_stdlib/src/field/mod.nr | 64 +++---------------- 14 files changed, 70 insertions(+), 114 deletions(-) diff --git a/compiler/noirc_evaluator/src/acir/generated_acir.rs b/compiler/noirc_evaluator/src/acir/generated_acir.rs index 14ceac62461..ee938a77dd9 100644 --- a/compiler/noirc_evaluator/src/acir/generated_acir.rs +++ b/compiler/noirc_evaluator/src/acir/generated_acir.rs @@ -368,12 +368,17 @@ impl<F: AcirField> GeneratedAcir<F> { "ICE: Radix must be a power of 2" ); + // TODO: is this range_constraint useful? + let input_expr_witness = self.create_witness_for_expression(input_expr); + self.range_constraint(input_expr_witness, bit_size)?; + let limb_witnesses = self.brillig_to_radix(input_expr, radix, limb_count); let mut composed_limbs = Expression::default(); let mut radix_pow = BigUint::from(1u128); for limb_witness in &limb_witnesses { + // TODO: do we need both range_constraint's? self.range_constraint(*limb_witness, bit_size)?; composed_limbs = composed_limbs.add_mul( diff --git a/compiler/noirc_evaluator/src/acir/mod.rs b/compiler/noirc_evaluator/src/acir/mod.rs index 55553d77adb..a250189d3f1 100644 --- a/compiler/noirc_evaluator/src/acir/mod.rs +++ b/compiler/noirc_evaluator/src/acir/mod.rs @@ -2148,7 +2148,7 @@ impl<'a> Context<'a> { Intrinsic::ApplyRangeConstraint => { unreachable!("ICE: `Intrinsic::ApplyRangeConstraint` calls should be transformed into an `Instruction::RangeCheck`"); } - Intrinsic::ToRadixUnsafe(endian) => { + Intrinsic::ToRadix(endian) => { let field = self.convert_value(arguments[0], dfg).into_var()?; let radix = self.convert_value(arguments[1], dfg).into_var()?; @@ -2167,7 +2167,7 @@ impl<'a> Context<'a> { ) .map(|array| vec![array]) } - Intrinsic::ToBitsUnsafe(endian) => { + Intrinsic::ToBits(endian) => { let field = self.convert_value(arguments[0], dfg).into_var()?; let Type::Array(result_type, array_length) = dfg.type_of_value(result_ids[0]) 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 575a2229807..e9bc6b127f7 100644 --- a/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -496,7 +496,7 @@ impl<'block> BrilligBlock<'block> { arguments, ); } - Intrinsic::ToBitsUnsafe(endianness) => { + Intrinsic::ToBits(endianness) => { let results = dfg.instruction_results(instruction_id); let source = self.convert_ssa_single_addr_value(arguments[0], dfg); @@ -526,7 +526,7 @@ impl<'block> BrilligBlock<'block> { self.brillig_context.deallocate_single_addr(two); } - Intrinsic::ToRadixUnsafe(endianness) => { + Intrinsic::ToRadix(endianness) => { let results = dfg.instruction_results(instruction_id); let source = self.convert_ssa_single_addr_value(arguments[0], dfg); diff --git a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs index 90921e9f706..80dde5e27f3 100644 --- a/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs +++ b/compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs @@ -307,8 +307,8 @@ impl DependencyContext { | Intrinsic::SliceRemove | Intrinsic::StaticAssert | Intrinsic::StrAsBytes - | Intrinsic::ToBitsUnsafe(..) - | Intrinsic::ToRadixUnsafe(..) + | Intrinsic::ToBits(..) + | Intrinsic::ToRadix(..) | Intrinsic::FieldLessThan => { // Record all the function arguments as parents of the results self.update_children(&arguments, &results); @@ -587,8 +587,8 @@ impl Context { | Intrinsic::SliceRemove | Intrinsic::StaticAssert | Intrinsic::StrAsBytes - | Intrinsic::ToBitsUnsafe(..) - | Intrinsic::ToRadixUnsafe(..) + | Intrinsic::ToBits(..) + | Intrinsic::ToRadix(..) | Intrinsic::FieldLessThan => { self.value_sets.push(instruction_arguments_and_results); } diff --git a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs index be6532c2579..3d7e0b06d5b 100644 --- a/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/function_builder/mod.rs @@ -558,7 +558,7 @@ mod tests { let one = builder.numeric_constant(FieldElement::one(), NumericType::bool()); let zero = builder.numeric_constant(FieldElement::zero(), NumericType::bool()); - let to_bits_id = builder.import_intrinsic_id(Intrinsic::ToBitsUnsafe(Endian::Little)); + let to_bits_id = builder.import_intrinsic_id(Intrinsic::ToBits(Endian::Little)); let input = builder.field_constant(FieldElement::from(7_u128)); let length = builder.field_constant(FieldElement::from(8_u128)); let result_types = vec![Type::Array(Arc::new(vec![Type::bool()]), 8)]; diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index c63f280a9ca..786c3671d38 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -61,8 +61,8 @@ pub(crate) enum Intrinsic { SliceRemove, ApplyRangeConstraint, StrAsBytes, - ToBitsUnsafe(Endian), - ToRadixUnsafe(Endian), + ToBits(Endian), + ToRadix(Endian), BlackBox(BlackBoxFunc), Hint(Hint), AsWitness, @@ -89,10 +89,10 @@ impl std::fmt::Display for Intrinsic { Intrinsic::SliceRemove => write!(f, "slice_remove"), Intrinsic::StrAsBytes => write!(f, "str_as_bytes"), Intrinsic::ApplyRangeConstraint => write!(f, "apply_range_constraint"), - Intrinsic::ToBitsUnsafe(Endian::Big) => write!(f, "to_be_bits_unsafe"), - Intrinsic::ToBitsUnsafe(Endian::Little) => write!(f, "to_le_bits_unsafe"), - Intrinsic::ToRadixUnsafe(Endian::Big) => write!(f, "to_be_radix_unsafe"), - Intrinsic::ToRadixUnsafe(Endian::Little) => write!(f, "to_le_radix_unsafe"), + Intrinsic::ToBits(Endian::Big) => write!(f, "to_be_bits"), + Intrinsic::ToBits(Endian::Little) => write!(f, "to_le_bits"), + Intrinsic::ToRadix(Endian::Big) => write!(f, "to_be_radix"), + Intrinsic::ToRadix(Endian::Little) => write!(f, "to_le_radix"), Intrinsic::BlackBox(function) => write!(f, "{function}"), Intrinsic::Hint(Hint::BlackBox) => write!(f, "black_box"), Intrinsic::AsWitness => write!(f, "as_witness"), @@ -124,7 +124,7 @@ impl Intrinsic { | Intrinsic::AsWitness => true, // These apply a constraint that the input must fit into a specified number of limbs. - Intrinsic::ToBitsUnsafe(_) | Intrinsic::ToRadixUnsafe(_) => true, + Intrinsic::ToBits(_) | Intrinsic::ToRadix(_) => true, // These imply a check that the slice is non-empty and should fail otherwise. Intrinsic::SlicePopBack | Intrinsic::SlicePopFront | Intrinsic::SliceRemove => true, @@ -164,8 +164,8 @@ impl Intrinsic { // directly depend on the corresponding `enable_side_effect` instruction any more. // However, to conform with the expectations of `Instruction::can_be_deduplicated` and // `constant_folding` we only use this information if the caller shows interest in it. - Intrinsic::ToBitsUnsafe(_) - | Intrinsic::ToRadixUnsafe(_) + Intrinsic::ToBits(_) + | Intrinsic::ToRadix(_) | Intrinsic::BlackBox( BlackBoxFunc::MultiScalarMul | BlackBoxFunc::EmbeddedCurveAdd @@ -203,10 +203,10 @@ impl Intrinsic { "slice_insert" => Some(Intrinsic::SliceInsert), "slice_remove" => Some(Intrinsic::SliceRemove), "str_as_bytes" => Some(Intrinsic::StrAsBytes), - "to_le_radix_unsafe" => Some(Intrinsic::ToRadixUnsafe(Endian::Little)), - "to_be_radix_unsafe" => Some(Intrinsic::ToRadixUnsafe(Endian::Big)), - "to_le_bits_unsafe" => Some(Intrinsic::ToBitsUnsafe(Endian::Little)), - "to_be_bits_unsafe" => Some(Intrinsic::ToBitsUnsafe(Endian::Big)), + "to_le_radix" => Some(Intrinsic::ToRadix(Endian::Little)), + "to_be_radix" => Some(Intrinsic::ToRadix(Endian::Big)), + "to_le_bits" => Some(Intrinsic::ToBits(Endian::Little)), + "to_be_bits" => Some(Intrinsic::ToBits(Endian::Big)), "as_witness" => Some(Intrinsic::AsWitness), "is_unconstrained" => Some(Intrinsic::IsUnconstrained), "derive_pedersen_generators" => Some(Intrinsic::DerivePedersenGenerators), @@ -220,7 +220,7 @@ impl Intrinsic { } } -/// The endian-ness of bits when encoding values as bits in e.g. ToBitsUnsafe or ToRadixUnsafe +/// The endian-ness of bits when encoding values as bits in e.g. ToBits or ToRadix #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub(crate) enum Endian { Big, diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 0efab65138c..992c633ffcd 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -52,16 +52,16 @@ pub(super) fn simplify_call( arguments.iter().map(|value_id| dfg.get_numeric_constant(*value_id)).collect(); let simplified_result = match intrinsic { - Intrinsic::ToBitsUnsafe(endian) => { + Intrinsic::ToBits(endian) => { // TODO: simplify to a range constraint if `limb_count == 1` if let (Some(constant_args), Some(return_type)) = (constant_args, return_type.clone()) { let field = constant_args[0]; let limb_count = if let Type::Array(_, array_len) = return_type { array_len } else { - unreachable!("ICE: Intrinsic::ToRadixUnsafe return type must be array") + unreachable!("ICE: Intrinsic::ToRadix return type must be array") }; - constant_to_radix_unsafe(endian, field, 2, limb_count, |values| { + constant_to_radix(endian, field, 2, limb_count, |values| { make_constant_array( dfg, values.into_iter(), @@ -74,7 +74,7 @@ pub(super) fn simplify_call( SimplifyResult::None } } - Intrinsic::ToRadixUnsafe(endian) => { + Intrinsic::ToRadix(endian) => { // TODO: simplify to a range constraint if `limb_count == 1` if let (Some(constant_args), Some(return_type)) = (constant_args, return_type.clone()) { let field = constant_args[0]; @@ -82,9 +82,9 @@ pub(super) fn simplify_call( let limb_count = if let Type::Array(_, array_len) = return_type { array_len } else { - unreachable!("ICE: Intrinsic::ToRadixUnsafe return type must be array") + unreachable!("ICE: Intrinsic::ToRadix return type must be array") }; - constant_to_radix_unsafe(endian, field, radix, limb_count, |values| { + constant_to_radix(endian, field, radix, limb_count, |values| { make_constant_array( dfg, values.into_iter(), @@ -641,7 +641,7 @@ fn make_array( } /// Returns a slice (represented by a tuple (len, slice)) of constants corresponding to the limbs of the radix decomposition. -fn constant_to_radix_unsafe( +fn constant_to_radix( endian: Endian, field: FieldElement, radix: u32, diff --git a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs index 4b00bbd05d2..5b75055c09a 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/constant_folding.rs @@ -1558,13 +1558,13 @@ mod test { // After EnableSideEffectsIf removal: brillig(inline) fn main f0 { b0(v0: Field, v1: Field, v2: u1): - v7 = call to_be_radix_unsafe(v0, u32 256) -> [u8; 1] // `a.to_be_radix_unsafe(256)`; + v7 = call to_be_radix(v0, u32 256) -> [u8; 1] // `a.to_be_radix(256)`; inc_rc v7 - v8 = call to_be_radix_unsafe(v0, u32 256) -> [u8; 1] // duplicate load of `a` + v8 = call to_be_radix(v0, u32 256) -> [u8; 1] // duplicate load of `a` inc_rc v8 - v9 = cast v2 as Field // `if c { a.to_be_radix_unsafe(256) }` - v10 = mul v0, v9 // attaching `c` to `a` - v11 = call to_be_radix_unsafe(v10, u32 256) -> [u8; 1] // calling `to_radix(c * a)` + v9 = cast v2 as Field // `if c { a.to_be_radix(256) }` + v10 = mul v0, v9 // attaching `c` to `a` + v11 = call to_be_radix(v10, u32 256) -> [u8; 1] // calling `to_radix(c * a)` inc_rc v11 return } @@ -1573,12 +1573,12 @@ mod test { let expected = " brillig(inline) fn main f0 { b0(v0: Field, v1: Field, v2: u1): - v5 = call to_be_radix_unsafe(v0, u32 256) -> [u8; 1] + v5 = call to_be_radix(v0, u32 256) -> [u8; 1] inc_rc v5 inc_rc v5 v6 = cast v2 as Field v7 = mul v0, v6 - v8 = call to_be_radix_unsafe(v7, u32 256) -> [u8; 1] + v8 = call to_be_radix(v7, u32 256) -> [u8; 1] inc_rc v8 return } diff --git a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs index d4276b4a214..76f8495c009 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs @@ -697,7 +697,7 @@ impl<'f> Context<'f> { } Instruction::Call { func, mut arguments } => match self.inserter.function.dfg[func] { - Value::Intrinsic(Intrinsic::ToBitsUnsafe(_) | Intrinsic::ToRadixUnsafe(_)) => { + Value::Intrinsic(Intrinsic::ToBits(_) | Intrinsic::ToRadix(_)) => { let field = arguments[0]; let casted_condition = self.cast_condition_to_value_type(condition, field, call_stack); diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs index 11439ed964f..e36be71aeea 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_bit_shifts.rs @@ -217,8 +217,7 @@ impl Context<'_> { fn pow(&mut self, lhs: ValueId, rhs: ValueId) -> ValueId { let typ = self.function.dfg.type_of_value(rhs); if let Type::Numeric(NumericType::Unsigned { bit_size }) = typ { - let to_bits = - self.function.dfg.import_intrinsic(Intrinsic::ToBitsUnsafe(Endian::Little)); + let to_bits = self.function.dfg.import_intrinsic(Intrinsic::ToBits(Endian::Little)); let result_types = vec![Type::Array(Arc::new(vec![Type::bool()]), bit_size)]; let rhs_bits = self.insert_call(to_bits, vec![rhs], result_types); diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs index 52e0c1cd68a..ca9b75643bc 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs @@ -173,8 +173,8 @@ impl Context { | Intrinsic::StaticAssert | Intrinsic::ApplyRangeConstraint | Intrinsic::StrAsBytes - | Intrinsic::ToBitsUnsafe(_) - | Intrinsic::ToRadixUnsafe(_) + | Intrinsic::ToBits(_) + | Intrinsic::ToRadix(_) | Intrinsic::BlackBox(_) | Intrinsic::Hint(Hint::BlackBox) | Intrinsic::AsSlice diff --git a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs index 160b9b54526..8dde79a3c60 100644 --- a/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs +++ b/compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs @@ -234,8 +234,8 @@ fn slice_capacity_change( | Intrinsic::AsWitness | Intrinsic::IsUnconstrained | Intrinsic::DerivePedersenGenerators - | Intrinsic::ToBitsUnsafe(_) - | Intrinsic::ToRadixUnsafe(_) + | Intrinsic::ToBits(_) + | Intrinsic::ToRadix(_) | Intrinsic::ArrayRefCount | Intrinsic::SliceRefCount | Intrinsic::FieldLessThan => SizeChange::None, diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 13eadad3bd9..83ef4077fb5 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -191,10 +191,10 @@ impl<'local, 'context> Interpreter<'local, 'context> { "struct_def_module" => struct_def_module(self, arguments, location), "struct_def_name" => struct_def_name(interner, arguments, location), "struct_def_set_fields" => struct_def_set_fields(interner, arguments, location), - "to_be_radix_unsafe" => to_be_radix_unsafe(arguments, return_type, location), - "to_le_radix_unsafe" => to_le_radix_unsafe(arguments, return_type, location), - "to_be_bits_unsafe" => to_be_bits_unsafe(arguments, return_type, location), - "to_le_bits_unsafe" => to_le_bits_unsafe(arguments, return_type, location), + "to_be_radix" => to_be_radix(arguments, return_type, location), + "to_le_radix" => to_le_radix(arguments, return_type, location), + "to_be_bits" => to_be_bits(arguments, return_type, location), + "to_le_bits" => to_le_bits(arguments, return_type, location), "trait_constraint_eq" => trait_constraint_eq(arguments, location), "trait_constraint_hash" => trait_constraint_hash(arguments, location), "trait_def_as_trait_constraint" => { @@ -801,42 +801,42 @@ fn quoted_tokens(arguments: Vec<(Value, Location)>, location: Location) -> IResu )) } -fn to_be_bits_unsafe( +fn to_be_bits( arguments: Vec<(Value, Location)>, return_type: Type, location: Location, ) -> IResult<Value> { let value = check_one_argument(arguments, location)?; let radix = (Value::U32(2), value.1); - to_be_radix_unsafe(vec![value, radix], return_type, location) + to_be_radix(vec![value, radix], return_type, location) } -fn to_le_bits_unsafe( +fn to_le_bits( arguments: Vec<(Value, Location)>, return_type: Type, location: Location, ) -> IResult<Value> { let value = check_one_argument(arguments, location)?; let radix = (Value::U32(2), value.1); - to_le_radix_unsafe(vec![value, radix], return_type, location) + to_le_radix(vec![value, radix], return_type, location) } -fn to_be_radix_unsafe( +fn to_be_radix( arguments: Vec<(Value, Location)>, return_type: Type, location: Location, ) -> IResult<Value> { - let le_radix_limbs = to_le_radix_unsafe(arguments, return_type, location)?; + let le_radix_limbs = to_le_radix(arguments, return_type, location)?; let Value::Array(limbs, typ) = le_radix_limbs else { - unreachable!("`to_le_radix_unsafe` should always return an array"); + unreachable!("`to_le_radix` should always return an array"); }; let be_radix_limbs = limbs.into_iter().rev().collect(); Ok(Value::Array(be_radix_limbs, typ)) } -fn to_le_radix_unsafe( +fn to_le_radix( arguments: Vec<(Value, Location)>, return_type: Type, location: Location, @@ -863,7 +863,7 @@ fn to_le_radix_unsafe( *element_type == Type::Integer(Signedness::Unsigned, IntegerBitSize::One); // Decompose the integer into its radix digits in little endian form. - let decomposed_integer = compute_to_radix_le_unsafe(value, radix); + let decomposed_integer = compute_to_radix_le(value, radix); let decomposed_integer = vecmap(0..limb_count.to_u128() as usize, |i| { let digit = match decomposed_integer.get(i) { Some(digit) => *digit, @@ -885,7 +885,7 @@ fn to_le_radix_unsafe( Ok(Value::Array(decomposed_integer.into(), result_type)) } -fn compute_to_radix_le_unsafe(field: FieldElement, radix: u32) -> Vec<u8> { +fn compute_to_radix_le(field: FieldElement, radix: u32) -> Vec<u8> { let bit_size = u32::BITS - (radix - 1).leading_zeros(); let radix_big = BigUint::from(radix); assert_eq!(BigUint::from(2u128).pow(bit_size), radix_big, "ICE: Radix must be a power of 2"); diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr index 0aa19942846..92aae5cbc56 100644 --- a/noir_stdlib/src/field/mod.nr +++ b/noir_stdlib/src/field/mod.nr @@ -28,26 +28,9 @@ impl Field { /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus /// (e.g. 254 for the BN254 field) allow for multiple bit decompositions. This is due to how the `Field` will /// wrap around due to overflow when verifying the decomposition. - #[builtin(to_le_bits_unsafe)] - // docs:start:to_le_bits_unsafe - pub fn to_le_bits_unsafe<let N: u32>(self: Self) -> [u1; N] {} - // docs:end:to_le_bits_unsafe - - /// Decomposes `self` into its little endian bit decomposition as a `[u1; N]` array. - /// This slice will be zero padded should not all bits be necessary to represent `self`. - /// - /// # Failures - /// Causes a constraint failure for `Field` values exceeding `2^N` as the resulting slice will not - /// be able to represent the original `Field`. - /// - /// # Safety - /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus - /// (e.g. 254 for the BN254 field) cause a compile-time error. + #[builtin(to_le_bits)] // docs:start:to_le_bits - pub fn to_le_bits<let N: u32>(self: Self) -> [u1; N] { - static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits"); - self.to_le_bits_unsafe() - } + pub fn to_le_bits<let N: u32>(self: Self) -> [u1; N] {} // docs:end:to_le_bits /// Decomposes `self` into its big endian bit decomposition as a `[u1; N]` array. @@ -61,26 +44,9 @@ impl Field { /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus /// (e.g. 254 for the BN254 field) allow for multiple bit decompositions. This is due to how the `Field` will /// wrap around due to overflow when verifying the decomposition. - #[builtin(to_be_bits_unsafe)] - // docs:start:to_be_bits_unsafe - pub fn to_be_bits_unsafe<let N: u32>(self: Self) -> [u1; N] {} - // docs:end:to_be_bits_unsafe - - /// Decomposes `self` into its big endian bit decomposition as a `[u1; N]` array. - /// This array will be zero padded should not all bits be necessary to represent `self`. - /// - /// # Failures - /// Causes a constraint failure for `Field` values exceeding `2^N` as the resulting slice will not - /// be able to represent the original `Field`. - /// - /// # Safety - /// Values of `N` equal to or greater than the number of bits necessary to represent the `Field` modulus - /// (e.g. 254 for the BN254 field) cause a compile-time error. + #[builtin(to_be_bits)] // docs:start:to_be_bits - pub fn to_be_bits<let N: u32>(self: Self) -> [u1; N] { - static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits"); - self.to_be_bits_unsafe() - } + pub fn to_be_bits<let N: u32>(self: Self) -> [u1; N] {} // docs:end:to_be_bits /// Decomposes `self` into its little endian byte decomposition as a `[u8;N]` array @@ -172,26 +138,12 @@ impl Field { // docs:end:to_be_radix // `_radix` must be less than 256 - #[builtin(to_le_radix_unsafe)] - fn __to_le_radix_unsafe<let N: u32>(self, radix: u32) -> [u8; N] {} - - // `_radix` must be less than 256 - fn __to_le_radix<let N: u32>(self, radix: u32) -> [u8; N] { - static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits"); - // TODO(https://github.com/noir-lang/noir/issues/6964): assert(radix < 256, "`_radix` must be less than 256"); - self.__to_le_radix_unsafe(radix) - } + #[builtin(to_le_radix)] + fn __to_le_radix<let N: u32>(self, radix: u32) -> [u8; N] {} // `_radix` must be less than 256 - #[builtin(to_be_radix_unsafe)] - fn __to_be_radix_unsafe<let N: u32>(self, radix: u32) -> [u8; N] {} - - // `_radix` must be less than 256 - fn __to_be_radix<let N: u32>(self, radix: u32) -> [u8; N] { - static_assert(N <= modulus_num_bits() as u32, "N must be less than or equal to modulus_num_bits"); - // TODO(https://github.com/noir-lang/noir/issues/6964): assert(radix < 256, "`_radix` must be less than 256"); - self.__to_be_radix_unsafe(radix) - } + #[builtin(to_be_radix)] + fn __to_be_radix<let N: u32>(self, radix: u32) -> [u8; N] {} // Returns self to the power of the given exponent value. // Caution: we assume the exponent fits into 32 bits From fbc93ca8dd0667d06d3e8e1b1859f4e8bda67fe0 Mon Sep 17 00:00:00 2001 From: "Michael J. Klein" <lambdamichael@gmail.com> Date: Tue, 14 Jan 2025 14:19:14 -0500 Subject: [PATCH 07/18] cargo clippy --- tooling/nargo_cli/src/cli/compile_cmd.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index fb46752115c..0c7ad5a1857 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -325,6 +325,7 @@ pub(crate) fn get_target_width( #[cfg(test)] mod tests { use std::{ + fmt::Write, path::{Path, PathBuf}, str::FromStr, }; @@ -419,10 +420,10 @@ mod tests { .unwrap_or_else(|err| { let error_string: String = err .iter() - .map(|diagnostic| { - format!("{}\n---\n", diagnostic_to_string(diagnostic, &file_manager)) - }) - .collect(); + .fold(String::new(), |mut output, diagnostic| { + let _ = write!(output, "{}\n---\n", diagnostic_to_string(diagnostic, &file_manager)); + output + }); panic!("Failed to compile:\n\n{}", error_string) }); From 713d9b771bba846dbef68b09fc9b27ff6dd15e42 Mon Sep 17 00:00:00 2001 From: Michael Klein <lambdamichael@gmail.com> Date: Wed, 15 Jan 2025 14:36:39 -0500 Subject: [PATCH 08/18] update test fixed by recent PR, remove broken wip range constraint, cargo clippy/fmt --- compiler/noirc_evaluator/src/acir/generated_acir.rs | 6 ------ noir_stdlib/src/collections/bounded_vec.nr | 3 +-- tooling/nargo_cli/src/cli/compile_cmd.rs | 11 +++++++---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/compiler/noirc_evaluator/src/acir/generated_acir.rs b/compiler/noirc_evaluator/src/acir/generated_acir.rs index ee938a77dd9..21c8bfe7d5c 100644 --- a/compiler/noirc_evaluator/src/acir/generated_acir.rs +++ b/compiler/noirc_evaluator/src/acir/generated_acir.rs @@ -367,18 +367,12 @@ impl<F: AcirField> GeneratedAcir<F> { radix_big, "ICE: Radix must be a power of 2" ); - - // TODO: is this range_constraint useful? - let input_expr_witness = self.create_witness_for_expression(input_expr); - self.range_constraint(input_expr_witness, bit_size)?; - let limb_witnesses = self.brillig_to_radix(input_expr, radix, limb_count); let mut composed_limbs = Expression::default(); let mut radix_pow = BigUint::from(1u128); for limb_witness in &limb_witnesses { - // TODO: do we need both range_constraint's? self.range_constraint(*limb_witness, bit_size)?; composed_limbs = composed_limbs.add_mul( diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr index a6760f4071d..8624233d398 100644 --- a/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir_stdlib/src/collections/bounded_vec.nr @@ -612,8 +612,7 @@ mod bounded_vec_tests { assert_eq(bounded_vec.get(2), 3); } - // TODO(https://github.com/noir-lang/noir/issues/6995): #[test(should_fail_with = "from array out of bounds")] - #[test(should_fail_with = "Argument is false")] + #[test(should_fail_with = "from array out of bounds")] fn max_len_lower_then_array_len() { let _: BoundedVec<Field, 2> = BoundedVec::from_array([0; 3]); } diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index 0c7ad5a1857..35303866a72 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -418,10 +418,13 @@ mod tests { ) // TODO: collect all errors and only error at the end! (i.e. don't fail fast) .unwrap_or_else(|err| { - let error_string: String = err - .iter() - .fold(String::new(), |mut output, diagnostic| { - let _ = write!(output, "{}\n---\n", diagnostic_to_string(diagnostic, &file_manager)); + let error_string: String = + err.iter().fold(String::new(), |mut output, diagnostic| { + let _ = write!( + output, + "{}\n---\n", + diagnostic_to_string(diagnostic, &file_manager) + ); output }); panic!("Failed to compile:\n\n{}", error_string) From 68c306040b46a9f9277bcc835c7cf33d9df1dd79 Mon Sep 17 00:00:00 2001 From: Michael Klein <lambdamichael@gmail.com> Date: Wed, 15 Jan 2025 16:24:34 -0500 Subject: [PATCH 09/18] nargo fmt --- noir_stdlib/src/field/mod.nr | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr index 92aae5cbc56..957c90ace25 100644 --- a/noir_stdlib/src/field/mod.nr +++ b/noir_stdlib/src/field/mod.nr @@ -10,7 +10,10 @@ impl Field { // docs:start:assert_max_bit_size pub fn assert_max_bit_size<let BIT_SIZE: u32>(self) { // docs:end:assert_max_bit_size - static_assert(BIT_SIZE < modulus_num_bits() as u32, "BIT_SIZE must be less than modulus_num_bits"); + static_assert( + BIT_SIZE < modulus_num_bits() as u32, + "BIT_SIZE must be less than modulus_num_bits", + ); self.__assert_max_bit_size(BIT_SIZE); } @@ -61,7 +64,10 @@ impl Field { // docs:start:to_le_bytes pub fn to_le_bytes<let N: u32>(self: Self) -> [u8; N] { // docs:end:to_le_bytes - static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()"); + static_assert( + N <= modulus_le_bytes().len(), + "N must be less than or equal to modulus_le_bytes().len()", + ); // Compute the byte decomposition let bytes = self.to_le_radix(256); @@ -95,7 +101,10 @@ impl Field { // docs:start:to_be_bytes pub fn to_be_bytes<let N: u32>(self: Self) -> [u8; N] { // docs:end:to_be_bytes - static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()"); + static_assert( + N <= modulus_le_bytes().len(), + "N must be less than or equal to modulus_le_bytes().len()", + ); // Compute the byte decomposition let bytes = self.to_be_radix(256); @@ -175,7 +184,10 @@ impl Field { /// Convert a little endian byte array to a field element. /// If the provided byte array overflows the field modulus then the Field will silently wrap around. pub fn from_le_bytes<let N: u32>(bytes: [u8; N]) -> Field { - static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()"); + static_assert( + N <= modulus_le_bytes().len(), + "N must be less than or equal to modulus_le_bytes().len()", + ); let mut v = 1; let mut result = 0; @@ -189,7 +201,10 @@ impl Field { /// Convert a big endian byte array to a field element. /// If the provided byte array overflows the field modulus then the Field will silently wrap around. pub fn from_be_bytes<let N: u32>(bytes: [u8; N]) -> Field { - static_assert(N <= modulus_le_bytes().len(), "N must be less than or equal to modulus_le_bytes().len()"); + static_assert( + N <= modulus_le_bytes().len(), + "N must be less than or equal to modulus_le_bytes().len()", + ); let mut v = 1; let mut result = 0; From b4c8c364386d3a39322aa9f5cbad339b7e9dfe6d Mon Sep 17 00:00:00 2001 From: Michael J Klein <michaeljklein@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:18:10 -0500 Subject: [PATCH 10/18] Update noir_stdlib/src/uint128.nr --- noir_stdlib/src/uint128.nr | 1 - 1 file changed, 1 deletion(-) diff --git a/noir_stdlib/src/uint128.nr b/noir_stdlib/src/uint128.nr index 4bb869df6ee..c1f4fd62715 100644 --- a/noir_stdlib/src/uint128.nr +++ b/noir_stdlib/src/uint128.nr @@ -68,7 +68,6 @@ impl U128 { } pub fn from_hex<let N: u32>(hex: str<N>) -> U128 { - // TODO cleanup if working // let N = N as u32; let bytes = hex.as_bytes(); // string must starts with "0x" From ba79d2f8931a9f0c44efa2f88db271637a6097f2 Mon Sep 17 00:00:00 2001 From: Michael J Klein <michaeljklein@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:18:16 -0500 Subject: [PATCH 11/18] Update noir_stdlib/src/uint128.nr --- noir_stdlib/src/uint128.nr | 1 - 1 file changed, 1 deletion(-) diff --git a/noir_stdlib/src/uint128.nr b/noir_stdlib/src/uint128.nr index c1f4fd62715..446ed4fb092 100644 --- a/noir_stdlib/src/uint128.nr +++ b/noir_stdlib/src/uint128.nr @@ -68,7 +68,6 @@ impl U128 { } pub fn from_hex<let N: u32>(hex: str<N>) -> U128 { - // let N = N as u32; let bytes = hex.as_bytes(); // string must starts with "0x" assert((bytes[0] == 48) & (bytes[1] == 120), "Invalid hexadecimal string"); From 5b4c5d45775ac47f7482aab426a2f71fde987ec8 Mon Sep 17 00:00:00 2001 From: Michael J Klein <michaeljklein@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:18:34 -0500 Subject: [PATCH 12/18] Update compiler/noirc_frontend/src/tests/metaprogramming.rs --- compiler/noirc_frontend/src/tests/metaprogramming.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/noirc_frontend/src/tests/metaprogramming.rs b/compiler/noirc_frontend/src/tests/metaprogramming.rs index 1efbb4efcd7..65c32edc5a2 100644 --- a/compiler/noirc_frontend/src/tests/metaprogramming.rs +++ b/compiler/noirc_frontend/src/tests/metaprogramming.rs @@ -36,7 +36,6 @@ fn comptime_code_rejects_dynamic_variable() { let errors = get_program_errors(src); // TODO cleanup - dbg!(&errors); assert_eq!(errors.len(), 1); match &errors[0].0 { From c7c3e8fe78e27f188b46db00139a3024da542329 Mon Sep 17 00:00:00 2001 From: Michael J Klein <michaeljklein@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:18:45 -0500 Subject: [PATCH 13/18] Update tooling/nargo_cli/src/cli/compile_cmd.rs --- tooling/nargo_cli/src/cli/compile_cmd.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index 35303866a72..3c55d8c2164 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -416,7 +416,6 @@ mod tests { &CompileOptions::default(), None, ) - // TODO: collect all errors and only error at the end! (i.e. don't fail fast) .unwrap_or_else(|err| { let error_string: String = err.iter().fold(String::new(), |mut output, diagnostic| { From 8214dad7ad11f9497f2a25acf6370fa4cd8467c2 Mon Sep 17 00:00:00 2001 From: Michael J Klein <michaeljklein@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:19:01 -0500 Subject: [PATCH 14/18] Update compiler/noirc_frontend/src/tests/metaprogramming.rs --- compiler/noirc_frontend/src/tests/metaprogramming.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/noirc_frontend/src/tests/metaprogramming.rs b/compiler/noirc_frontend/src/tests/metaprogramming.rs index 65c32edc5a2..93426c1348c 100644 --- a/compiler/noirc_frontend/src/tests/metaprogramming.rs +++ b/compiler/noirc_frontend/src/tests/metaprogramming.rs @@ -35,7 +35,6 @@ fn comptime_code_rejects_dynamic_variable() { }"#; let errors = get_program_errors(src); - // TODO cleanup assert_eq!(errors.len(), 1); match &errors[0].0 { From d1536d5ca96a3dbd5cabff5c616e19ac97817879 Mon Sep 17 00:00:00 2001 From: Michael Klein <lambdamichael@gmail.com> Date: Tue, 21 Jan 2025 12:57:49 -0500 Subject: [PATCH 15/18] cargo fmt --- compiler/noirc_frontend/src/tests/metaprogramming.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/noirc_frontend/src/tests/metaprogramming.rs b/compiler/noirc_frontend/src/tests/metaprogramming.rs index 93426c1348c..b42342fa47d 100644 --- a/compiler/noirc_frontend/src/tests/metaprogramming.rs +++ b/compiler/noirc_frontend/src/tests/metaprogramming.rs @@ -35,7 +35,6 @@ fn comptime_code_rejects_dynamic_variable() { }"#; let errors = get_program_errors(src); - assert_eq!(errors.len(), 1); match &errors[0].0 { CompilationError::InterpreterError(InterpreterError::NonComptimeVarReferenced { From 8a1e7d27935aa9c904e13ae72efa1b213ebec72d Mon Sep 17 00:00:00 2001 From: Michael Klein <lambdamichael@gmail.com> Date: Tue, 21 Jan 2025 13:39:45 -0500 Subject: [PATCH 16/18] nargo fmt --- noir_stdlib/src/collections/bounded_vec.nr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noir_stdlib/src/collections/bounded_vec.nr b/noir_stdlib/src/collections/bounded_vec.nr index 6c4772f6d55..c030544e791 100644 --- a/noir_stdlib/src/collections/bounded_vec.nr +++ b/noir_stdlib/src/collections/bounded_vec.nr @@ -1,4 +1,4 @@ -use crate::{cmp::Eq, convert::From, static_assert, runtime::is_unconstrained}; +use crate::{cmp::Eq, convert::From, runtime::is_unconstrained, static_assert}; /// A `BoundedVec<T, MaxLen>` is a growable storage similar to a `Vec<T>` except that it /// is bounded with a maximum possible length. Unlike `Vec`, `BoundedVec` is not implemented From 7d442d8afcc493cb0cd03e07e2218c558797df27 Mon Sep 17 00:00:00 2001 From: Michael Klein <lambdamichael@gmail.com> Date: Wed, 22 Jan 2025 12:43:45 -0500 Subject: [PATCH 17/18] add static_assert's to to_le_radix, add noir stdlib and compile failure tests, wip debugging test failures, cleanup diagnostic printing --- noir_stdlib/src/field/mod.nr | 45 ++++++++++++++++--- .../compile_failure/to_le_radix_1/Nargo.toml | 6 +++ .../compile_failure/to_le_radix_1/src/main.nr | 5 +++ .../to_le_radix_257/Nargo.toml | 6 +++ .../to_le_radix_257/src/main.nr | 5 +++ .../compile_failure/to_le_radix_3/Nargo.toml | 6 +++ .../compile_failure/to_le_radix_3/src/main.nr | 5 +++ tooling/nargo_cli/src/cli/compile_cmd.rs | 15 ++----- 8 files changed, 75 insertions(+), 18 deletions(-) create mode 100644 test_programs/compile_failure/to_le_radix_1/Nargo.toml create mode 100644 test_programs/compile_failure/to_le_radix_1/src/main.nr create mode 100644 test_programs/compile_failure/to_le_radix_257/Nargo.toml create mode 100644 test_programs/compile_failure/to_le_radix_257/src/main.nr create mode 100644 test_programs/compile_failure/to_le_radix_3/Nargo.toml create mode 100644 test_programs/compile_failure/to_le_radix_3/src/main.nr diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr index 957c90ace25..0317b3d7552 100644 --- a/noir_stdlib/src/field/mod.nr +++ b/noir_stdlib/src/field/mod.nr @@ -129,9 +129,23 @@ impl Field { // docs:start:to_le_radix pub fn to_le_radix<let N: u32>(self: Self, radix: u32) -> [u8; N] { // Brillig does not need an immediate radix - if !crate::runtime::is_unconstrained() { - crate::assert_constant(radix); - } + // if !crate::runtime::is_unconstrained() { + static_assert(false, "hi!"); + static_assert(radix == 256, "radix must be 256"); + + static_assert( + 1 < radix, + "radix must be greater than 1", + ); + static_assert( + radix <= 256, + "radix must be less than or equal to 256", + ); + static_assert( + radix & (radix - 1) == 0, + "radix must be a power of 2", + ); + // } self.__to_le_radix(radix) } // docs:end:to_le_radix @@ -201,10 +215,6 @@ impl Field { /// Convert a big endian byte array to a field element. /// If the provided byte array overflows the field modulus then the Field will silently wrap around. pub fn from_be_bytes<let N: u32>(bytes: [u8; N]) -> Field { - static_assert( - N <= modulus_le_bytes().len(), - "N must be less than or equal to modulus_le_bytes().len()", - ); let mut v = 1; let mut result = 0; @@ -342,6 +352,27 @@ mod tests { } // docs:end:to_le_radix_example + #[test] + fn test_to_le_radix_1() { + assert(!crate::runtime::is_unconstrained(), "expected this test to be run constrained"); + let field = 2; + let _: [u8; 8] = field.to_le_radix(1); + } + + #[test] + fn test_to_le_radix_3() { + assert(!crate::runtime::is_unconstrained(), "expected this test to be run constrained"); + let field = 2; + let _: [u8; 8] = field.to_le_radix(3); + } + + #[test] + fn test_to_le_radix_257() { + assert(!crate::runtime::is_unconstrained(), "expected this test to be run constrained"); + let field = 2; + let _: [u8; 8] = field.to_le_radix(257); + } + #[test] unconstrained fn test_field_less_than() { assert(field_less_than(0, 1)); diff --git a/test_programs/compile_failure/to_le_radix_1/Nargo.toml b/test_programs/compile_failure/to_le_radix_1/Nargo.toml new file mode 100644 index 00000000000..d1683bad4c2 --- /dev/null +++ b/test_programs/compile_failure/to_le_radix_1/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "to_le_radix_1" +type = "bin" +authors = [""] + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/to_le_radix_1/src/main.nr b/test_programs/compile_failure/to_le_radix_1/src/main.nr new file mode 100644 index 00000000000..9772451d569 --- /dev/null +++ b/test_programs/compile_failure/to_le_radix_1/src/main.nr @@ -0,0 +1,5 @@ +fn main() { + assert(!std::runtime::is_unconstrained(), "expected this test to be run constrained"); + let field = 2; + let _: [u8; 8] = field.to_le_radix(1); +} diff --git a/test_programs/compile_failure/to_le_radix_257/Nargo.toml b/test_programs/compile_failure/to_le_radix_257/Nargo.toml new file mode 100644 index 00000000000..ae1eae8ae61 --- /dev/null +++ b/test_programs/compile_failure/to_le_radix_257/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "to_le_radix_257" +type = "bin" +authors = [""] + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/to_le_radix_257/src/main.nr b/test_programs/compile_failure/to_le_radix_257/src/main.nr new file mode 100644 index 00000000000..a399809ec8f --- /dev/null +++ b/test_programs/compile_failure/to_le_radix_257/src/main.nr @@ -0,0 +1,5 @@ +fn main() { + assert(!std::runtime::is_unconstrained(), "expected this test to be run constrained"); + let field = 2; + let _: [u8; 8] = field.to_le_radix(512); +} diff --git a/test_programs/compile_failure/to_le_radix_3/Nargo.toml b/test_programs/compile_failure/to_le_radix_3/Nargo.toml new file mode 100644 index 00000000000..7b1d862806b --- /dev/null +++ b/test_programs/compile_failure/to_le_radix_3/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "to_le_radix_3" +type = "bin" +authors = [""] + +[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/to_le_radix_3/src/main.nr b/test_programs/compile_failure/to_le_radix_3/src/main.nr new file mode 100644 index 00000000000..365732fd861 --- /dev/null +++ b/test_programs/compile_failure/to_le_radix_3/src/main.nr @@ -0,0 +1,5 @@ +fn main() { + assert(!std::runtime::is_unconstrained(), "expected this test to be run constrained"); + let field = 2; + let _: [u8; 8] = field.to_le_radix(3); +} diff --git a/tooling/nargo_cli/src/cli/compile_cmd.rs b/tooling/nargo_cli/src/cli/compile_cmd.rs index 3c55d8c2164..8a4b991a234 100644 --- a/tooling/nargo_cli/src/cli/compile_cmd.rs +++ b/tooling/nargo_cli/src/cli/compile_cmd.rs @@ -325,7 +325,6 @@ pub(crate) fn get_target_width( #[cfg(test)] mod tests { use std::{ - fmt::Write, path::{Path, PathBuf}, str::FromStr, }; @@ -417,16 +416,10 @@ mod tests { None, ) .unwrap_or_else(|err| { - let error_string: String = - err.iter().fold(String::new(), |mut output, diagnostic| { - let _ = write!( - output, - "{}\n---\n", - diagnostic_to_string(diagnostic, &file_manager) - ); - output - }); - panic!("Failed to compile:\n\n{}", error_string) + for diagnostic in err { + println!("{}", diagnostic_to_string(&diagnostic, &file_manager)); + } + panic!("Failed to compile") }); let width = get_target_width(package.expression_width, None); From 99710bd5a43754f17ea32ea3ed642673179fe38b Mon Sep 17 00:00:00 2001 From: Michael Klein <lambdamichael@gmail.com> Date: Wed, 22 Jan 2025 15:33:52 -0500 Subject: [PATCH 18/18] remove compile_failure tests now that stdlib tests are working, remove debug 'static_assert's and only check radix when not is_unconstrained, simplify to nothing when invalid radix, check radix range when generating ACIR, add brillig-specific to_radix tests, add handling for unconstrained mode for acir to_radix tests --- .../src/acir/generated_acir.rs | 6 ++ .../src/ssa/ir/instruction/call.rs | 7 +- noir_stdlib/src/field/mod.nr | 93 +++++++++++++------ .../compile_failure/to_le_radix_1/Nargo.toml | 6 -- .../compile_failure/to_le_radix_1/src/main.nr | 5 - .../to_le_radix_257/Nargo.toml | 6 -- .../to_le_radix_257/src/main.nr | 5 - .../compile_failure/to_le_radix_3/Nargo.toml | 6 -- .../compile_failure/to_le_radix_3/src/main.nr | 5 - 9 files changed, 77 insertions(+), 62 deletions(-) delete mode 100644 test_programs/compile_failure/to_le_radix_1/Nargo.toml delete mode 100644 test_programs/compile_failure/to_le_radix_1/src/main.nr delete mode 100644 test_programs/compile_failure/to_le_radix_257/Nargo.toml delete mode 100644 test_programs/compile_failure/to_le_radix_257/src/main.nr delete mode 100644 test_programs/compile_failure/to_le_radix_3/Nargo.toml delete mode 100644 test_programs/compile_failure/to_le_radix_3/src/main.nr diff --git a/compiler/noirc_evaluator/src/acir/generated_acir.rs b/compiler/noirc_evaluator/src/acir/generated_acir.rs index 21c8bfe7d5c..8b29888a7ad 100644 --- a/compiler/noirc_evaluator/src/acir/generated_acir.rs +++ b/compiler/noirc_evaluator/src/acir/generated_acir.rs @@ -362,6 +362,12 @@ impl<F: AcirField> GeneratedAcir<F> { bit_size: u32, ) -> Result<Vec<Witness>, RuntimeError> { let radix_big = BigUint::from(radix); + let radix_range = BigUint::from(2u128)..=BigUint::from(256u128); + assert!( + radix_range.contains(&radix_big), + "ICE: Radix must be in the range 2..=256, but found: {:?}", + radix + ); assert_eq!( BigUint::from(2u128).pow(bit_size), radix_big, diff --git a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs index 992c633ffcd..6ee7aa0192c 100644 --- a/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs +++ b/compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs @@ -650,7 +650,12 @@ fn constant_to_radix( ) -> SimplifyResult { let bit_size = u32::BITS - (radix - 1).leading_zeros(); let radix_big = BigUint::from(radix); - assert_eq!(BigUint::from(2u128).pow(bit_size), radix_big, "ICE: Radix must be a power of 2"); + let radix_range = BigUint::from(2u128)..=BigUint::from(256u128); + if !radix_range.contains(&radix_big) || BigUint::from(2u128).pow(bit_size) != radix_big { + // NOTE: expect an error to be thrown later in + // acir::generated_acir::radix_le_decompose + return SimplifyResult::None; + } let big_integer = BigUint::from_bytes_be(&field.to_be_bytes()); // Decompose the integer into its radix digits in little endian form. diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr index 0317b3d7552..255236477ff 100644 --- a/noir_stdlib/src/field/mod.nr +++ b/noir_stdlib/src/field/mod.nr @@ -129,23 +129,11 @@ impl Field { // docs:start:to_le_radix pub fn to_le_radix<let N: u32>(self: Self, radix: u32) -> [u8; N] { // Brillig does not need an immediate radix - // if !crate::runtime::is_unconstrained() { - static_assert(false, "hi!"); - static_assert(radix == 256, "radix must be 256"); - - static_assert( - 1 < radix, - "radix must be greater than 1", - ); - static_assert( - radix <= 256, - "radix must be less than or equal to 256", - ); - static_assert( - radix & (radix - 1) == 0, - "radix must be a power of 2", - ); - // } + if !crate::runtime::is_unconstrained() { + static_assert(1 < radix, "radix must be greater than 1"); + static_assert(radix <= 256, "radix must be less than or equal to 256"); + static_assert(radix & (radix - 1) == 0, "radix must be a power of 2"); + } self.__to_le_radix(radix) } // docs:end:to_le_radix @@ -292,6 +280,7 @@ fn lt_fallback(x: Field, y: Field) -> bool { } mod tests { + use crate::{panic::panic, runtime}; use super::field_less_than; #[test] @@ -352,25 +341,73 @@ mod tests { } // docs:end:to_le_radix_example - #[test] + #[test(should_fail_with = "radix must be greater than 1")] fn test_to_le_radix_1() { - assert(!crate::runtime::is_unconstrained(), "expected this test to be run constrained"); - let field = 2; - let _: [u8; 8] = field.to_le_radix(1); + // this test should only fail in constrained mode + if !runtime::is_unconstrained() { + let field = 2; + let _: [u8; 8] = field.to_le_radix(1); + } else { + panic(f"radix must be greater than 1"); + } } #[test] + fn test_to_le_radix_brillig_1() { + // this test should only fail in constrained mode + if runtime::is_unconstrained() { + let field = 1; + let out: [u8; 8] = field.to_le_radix(1); + crate::println(out); + let expected = [0; 8]; + assert(out == expected, "unexpected result"); + } + } + + #[test(should_fail_with = "radix must be a power of 2")] fn test_to_le_radix_3() { - assert(!crate::runtime::is_unconstrained(), "expected this test to be run constrained"); - let field = 2; - let _: [u8; 8] = field.to_le_radix(3); + // this test should only fail in constrained mode + if !runtime::is_unconstrained() { + let field = 2; + let _: [u8; 8] = field.to_le_radix(3); + } else { + panic(f"radix must be a power of 2"); + } } #[test] - fn test_to_le_radix_257() { - assert(!crate::runtime::is_unconstrained(), "expected this test to be run constrained"); - let field = 2; - let _: [u8; 8] = field.to_le_radix(257); + fn test_to_le_radix_brillig_3() { + // this test should only fail in constrained mode + if runtime::is_unconstrained() { + let field = 1; + let out: [u8; 8] = field.to_le_radix(3); + let mut expected = [0; 8]; + expected[0] = 1; + assert(out == expected, "unexpected result"); + } + } + + #[test(should_fail_with = "radix must be less than or equal to 256")] + fn test_to_le_radix_512() { + // this test should only fail in constrained mode + if !runtime::is_unconstrained() { + let field = 2; + let _: [u8; 8] = field.to_le_radix(512); + } else { + panic(f"radix must be less than or equal to 256") + } + } + + #[test] + fn test_to_le_radix_brillig_512() { + // this test should only fail in constrained mode + if runtime::is_unconstrained() { + let field = 1; + let out: [u8; 8] = field.to_le_radix(512); + let mut expected = [0; 8]; + expected[0] = 1; + assert(out == expected, "unexpected result"); + } } #[test] diff --git a/test_programs/compile_failure/to_le_radix_1/Nargo.toml b/test_programs/compile_failure/to_le_radix_1/Nargo.toml deleted file mode 100644 index d1683bad4c2..00000000000 --- a/test_programs/compile_failure/to_le_radix_1/Nargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "to_le_radix_1" -type = "bin" -authors = [""] - -[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/to_le_radix_1/src/main.nr b/test_programs/compile_failure/to_le_radix_1/src/main.nr deleted file mode 100644 index 9772451d569..00000000000 --- a/test_programs/compile_failure/to_le_radix_1/src/main.nr +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - assert(!std::runtime::is_unconstrained(), "expected this test to be run constrained"); - let field = 2; - let _: [u8; 8] = field.to_le_radix(1); -} diff --git a/test_programs/compile_failure/to_le_radix_257/Nargo.toml b/test_programs/compile_failure/to_le_radix_257/Nargo.toml deleted file mode 100644 index ae1eae8ae61..00000000000 --- a/test_programs/compile_failure/to_le_radix_257/Nargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "to_le_radix_257" -type = "bin" -authors = [""] - -[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/to_le_radix_257/src/main.nr b/test_programs/compile_failure/to_le_radix_257/src/main.nr deleted file mode 100644 index a399809ec8f..00000000000 --- a/test_programs/compile_failure/to_le_radix_257/src/main.nr +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - assert(!std::runtime::is_unconstrained(), "expected this test to be run constrained"); - let field = 2; - let _: [u8; 8] = field.to_le_radix(512); -} diff --git a/test_programs/compile_failure/to_le_radix_3/Nargo.toml b/test_programs/compile_failure/to_le_radix_3/Nargo.toml deleted file mode 100644 index 7b1d862806b..00000000000 --- a/test_programs/compile_failure/to_le_radix_3/Nargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "to_le_radix_3" -type = "bin" -authors = [""] - -[dependencies] \ No newline at end of file diff --git a/test_programs/compile_failure/to_le_radix_3/src/main.nr b/test_programs/compile_failure/to_le_radix_3/src/main.nr deleted file mode 100644 index 365732fd861..00000000000 --- a/test_programs/compile_failure/to_le_radix_3/src/main.nr +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - assert(!std::runtime::is_unconstrained(), "expected this test to be run constrained"); - let field = 2; - let _: [u8; 8] = field.to_le_radix(3); -}