From e8a2f8be9d0323dbc94e8f6adaba1b90ea4ee32c Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Wed, 17 Jul 2024 18:50:08 +0100 Subject: [PATCH] feat: add comptime support for `modulus_*` compiler builtins (#5530) # Description ## Problem\* Resolves ## Summary\* This PR changes the function `std::compat::is_bn254` to run in a `comptime` context. To do this I've had to implement a few compiler builtins within the interpreter. I've had to mangle `std::compat::is_bn254` a bit to get around restrictions on when a function can be called in a `comptime` context however. This works around around the loop unrolling issue experienced in #4535. ## Additional Context ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [ ] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- .../src/hir/comptime/interpreter/builtin.rs | 70 +++++++++++++++++++ .../noirc_frontend/src/hir/comptime/value.rs | 9 +++ noir_stdlib/src/compat.nr | 17 ++++- noir_stdlib/src/field/mod.nr | 12 ++-- .../comptime_slice_equality/Nargo.toml | 7 ++ .../comptime_slice_equality/src/main.nr | 5 ++ 6 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 test_programs/execution_success/comptime_slice_equality/Nargo.toml create mode 100644 test_programs/execution_success/comptime_slice_equality/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 eea5e2747e5..017ea360562 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -3,6 +3,7 @@ use std::{ rc::Rc, }; +use acvm::{AcirField, FieldElement}; use chumsky::Parser; use noirc_errors::Location; @@ -25,6 +26,11 @@ pub(super) fn call_builtin( "array_len" => array_len(interner, arguments, location), "as_slice" => as_slice(interner, arguments, location), "is_unconstrained" => Ok(Value::Bool(true)), + "modulus_be_bits" => modulus_be_bits(interner, arguments, location), + "modulus_be_bytes" => modulus_be_bytes(interner, arguments, location), + "modulus_le_bits" => modulus_le_bits(interner, arguments, location), + "modulus_le_bytes" => modulus_le_bytes(interner, arguments, location), + "modulus_num_bits" => modulus_num_bits(interner, arguments, location), "slice_insert" => slice_insert(interner, arguments, location), "slice_pop_back" => slice_pop_back(interner, arguments, location), "slice_pop_front" => slice_pop_front(interner, arguments, location), @@ -386,3 +392,67 @@ fn trait_constraint_eq( Ok(Value::Bool(constraint_a == constraint_b)) } + +fn modulus_be_bits( + _interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(0, &arguments, location)?; + + let bits = FieldElement::modulus().to_radix_be(2); + let bits_vector = bits.into_iter().map(|bit| Value::U1(bit != 0)).collect(); + + let int_type = Type::Integer(crate::ast::Signedness::Unsigned, IntegerBitSize::One); + let typ = Type::Slice(Box::new(int_type)); + Ok(Value::Slice(bits_vector, typ)) +} + +fn modulus_be_bytes( + _interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(0, &arguments, location)?; + + let bytes = FieldElement::modulus().to_bytes_be(); + let bytes_vector = bytes.into_iter().map(Value::U8).collect(); + + let int_type = Type::Integer(crate::ast::Signedness::Unsigned, IntegerBitSize::Eight); + let typ = Type::Slice(Box::new(int_type)); + Ok(Value::Slice(bytes_vector, typ)) +} + +fn modulus_le_bits( + interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let Value::Slice(bits, typ) = modulus_be_bits(interner, arguments, location)? else { + unreachable!("modulus_be_bits must return slice") + }; + let reversed_bits = bits.into_iter().rev().collect(); + Ok(Value::Slice(reversed_bits, typ)) +} + +fn modulus_le_bytes( + interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let Value::Slice(bytes, typ) = modulus_be_bytes(interner, arguments, location)? else { + unreachable!("modulus_be_bytes must return slice") + }; + let reversed_bytes = bytes.into_iter().rev().collect(); + Ok(Value::Slice(reversed_bytes, typ)) +} + +fn modulus_num_bits( + _interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + check_argument_count(0, &arguments, location)?; + let bits = FieldElement::max_num_bits().into(); + Ok(Value::U64(bits)) +} diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index 9eeb323d664..0cbf4662f6a 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -32,6 +32,7 @@ pub enum Value { I16(i16), I32(i32), I64(i64), + U1(bool), U8(u8), U16(u16), U32(u32), @@ -62,6 +63,7 @@ impl Value { Value::I16(_) => Type::Integer(Signedness::Signed, IntegerBitSize::Sixteen), Value::I32(_) => Type::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo), Value::I64(_) => Type::Integer(Signedness::Signed, IntegerBitSize::SixtyFour), + Value::U1(_) => Type::Integer(Signedness::Unsigned, IntegerBitSize::One), Value::U8(_) => Type::Integer(Signedness::Unsigned, IntegerBitSize::Eight), Value::U16(_) => Type::Integer(Signedness::Unsigned, IntegerBitSize::Sixteen), Value::U32(_) => Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo), @@ -124,6 +126,9 @@ impl Value { let value = (value as u128).into(); ExpressionKind::Literal(Literal::Integer(value, negative)) } + Value::U1(value) => { + ExpressionKind::Literal(Literal::Integer((value as u128).into(), false)) + } Value::U8(value) => { ExpressionKind::Literal(Literal::Integer((value as u128).into(), false)) } @@ -249,6 +254,9 @@ impl Value { let value = (value as u128).into(); HirExpression::Literal(HirLiteral::Integer(value, negative)) } + Value::U1(value) => { + HirExpression::Literal(HirLiteral::Integer((value as u128).into(), false)) + } Value::U8(value) => { HirExpression::Literal(HirLiteral::Integer((value as u128).into(), false)) } @@ -385,6 +393,7 @@ impl Display for Value { Value::I16(value) => write!(f, "{value}"), Value::I32(value) => write!(f, "{value}"), Value::I64(value) => write!(f, "{value}"), + Value::U1(value) => write!(f, "{value}"), Value::U8(value) => write!(f, "{value}"), Value::U16(value) => write!(f, "{value}"), Value::U32(value) => write!(f, "{value}"), diff --git a/noir_stdlib/src/compat.nr b/noir_stdlib/src/compat.nr index 06da8150767..11f29b3caa0 100644 --- a/noir_stdlib/src/compat.nr +++ b/noir_stdlib/src/compat.nr @@ -1,7 +1,20 @@ -global BN254_MODULUS_BE_BYTES: [u8] = &[ +comptime global BN254_MODULUS_BE_BYTES: [u8] = &[ 48, 100, 78, 114, 225, 49, 160, 41, 184, 80, 69, 182, 129, 129, 88, 93, 40, 51, 232, 72, 121, 185, 112, 145, 67, 225, 245, 147, 240, 0, 0, 1 ]; pub fn is_bn254() -> bool { - crate::field::modulus_be_bytes() == BN254_MODULUS_BE_BYTES + comptime { + // We can't use the `Eq` trait here due to limitations on calling non-comptime functions + // defined within the same crate. + let mut eq = true; + + let modulus_be_bytes = crate::field::modulus_be_bytes(); + // We can't do `BN254_MODULUS_BE_BYTES.len()` due to limitations on calling non-comptime functions. + assert_eq(crate::field::modulus_num_bits(), 254); + for i in 0..32 { + eq &= modulus_be_bytes[i] == BN254_MODULUS_BE_BYTES[i]; + } + + eq + } } diff --git a/noir_stdlib/src/field/mod.nr b/noir_stdlib/src/field/mod.nr index b876bcc967b..1d290ec7453 100644 --- a/noir_stdlib/src/field/mod.nr +++ b/noir_stdlib/src/field/mod.nr @@ -84,19 +84,21 @@ impl Field { } #[builtin(modulus_num_bits)] -pub fn modulus_num_bits() -> u64 {} +pub comptime fn modulus_num_bits() -> u64 {} #[builtin(modulus_be_bits)] -pub fn modulus_be_bits() -> [u1] {} +pub comptime fn modulus_be_bits() -> [u1] {} #[builtin(modulus_le_bits)] -pub fn modulus_le_bits() -> [u1] {} +pub comptime fn modulus_le_bits() -> [u1] {} #[builtin(modulus_be_bytes)] -pub fn modulus_be_bytes() -> [u8] {} +pub comptime fn modulus_be_bytes() -> [u8] {} #[builtin(modulus_le_bytes)] -pub fn modulus_le_bytes() -> [u8] {} +pub comptime fn modulus_le_bytes() -> [u8] {} + + // Convert a 32 byte array to a field element by modding pub fn bytes32_to_field(bytes32: [u8; 32]) -> Field { // Convert it to a field element diff --git a/test_programs/execution_success/comptime_slice_equality/Nargo.toml b/test_programs/execution_success/comptime_slice_equality/Nargo.toml new file mode 100644 index 00000000000..72700d87d8b --- /dev/null +++ b/test_programs/execution_success/comptime_slice_equality/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_slice_equality" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/test_programs/execution_success/comptime_slice_equality/src/main.nr b/test_programs/execution_success/comptime_slice_equality/src/main.nr new file mode 100644 index 00000000000..d5f8afd41e6 --- /dev/null +++ b/test_programs/execution_success/comptime_slice_equality/src/main.nr @@ -0,0 +1,5 @@ +fn main() { + comptime { + assert_eq(&[1], &[1]); + } +} \ No newline at end of file