From 6039c99fa3b6300e3d57b423d6a685b7ca68ac06 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 5 Jul 2024 10:58:41 -0500 Subject: [PATCH 1/4] Add remaining slice methods --- .../src/hir/comptime/interpreter/builtin.rs | 137 ++++++++++++++++-- .../comptime_slice_methods/Nargo.toml | 7 + .../comptime_slice_methods/src/main.nr | 26 ++++ 3 files changed, 156 insertions(+), 14 deletions(-) create mode 100644 test_programs/compile_success_empty/comptime_slice_methods/Nargo.toml create mode 100644 test_programs/compile_success_empty/comptime_slice_methods/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 89c0b1d438e..399d9905269 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -3,8 +3,9 @@ use std::rc::Rc; use noirc_errors::Location; use crate::{ + ast::IntegerBitSize, hir::comptime::{errors::IResult, InterpreterError, Value}, - macros_api::NodeInterner, + macros_api::{NodeInterner, Signedness}, token::{SpannedToken, Token, Tokens}, QuotedType, Type, }; @@ -18,10 +19,16 @@ pub(super) fn call_builtin( match name { "array_len" => array_len(interner, arguments, location), "as_slice" => as_slice(interner, arguments, location), + "is_unconstrained" => Ok(Value::Bool(true)), + "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), "slice_push_back" => slice_push_back(interner, arguments, location), + "slice_push_front" => slice_push_front(interner, arguments, location), + "slice_remove" => slice_remove(interner, arguments, location), "struct_def_as_type" => struct_def_as_type(interner, arguments, location), - "struct_def_generics" => struct_def_generics(interner, arguments, location), "struct_def_fields" => struct_def_fields(interner, arguments, location), + "struct_def_generics" => struct_def_generics(interner, arguments, location), _ => { let item = format!("Comptime evaluation for builtin function {name}"); Err(InterpreterError::Unimplemented { item, location }) @@ -42,6 +49,36 @@ fn check_argument_count( } } +fn failing_constraint(message: impl Into, location: Location) -> IResult { + let message = Some(Value::String(Rc::new(message.into()))); + Err(InterpreterError::FailingConstraint { message, location }) +} + +fn get_slice( + interner: &NodeInterner, + value: Value, + location: Location, +) -> IResult<(im::Vector, Type)> { + match value { + Value::Slice(values, typ) => Ok((values, typ)), + value => { + let type_var = Box::new(interner.next_type_variable()); + let expected = Type::Slice(type_var); + Err(InterpreterError::TypeMismatch { expected, value, location }) + } + } +} + +fn get_u32(value: Value, location: Location) -> IResult { + match value { + Value::U32(value) => Ok(value), + value => { + let expected = Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo); + Err(InterpreterError::TypeMismatch { expected, value, location }) + } + } +} + fn array_len( interner: &NodeInterner, mut arguments: Vec<(Value, Location)>, @@ -85,18 +122,9 @@ fn slice_push_back( check_argument_count(2, &arguments, location)?; let (element, _) = arguments.pop().unwrap(); - let (slice, _) = arguments.pop().unwrap(); - match slice { - Value::Slice(mut values, typ) => { - values.push_back(element); - Ok(Value::Slice(values, typ)) - } - value => { - let type_var = Box::new(interner.next_type_variable()); - let expected = Type::Slice(type_var); - Err(InterpreterError::TypeMismatch { expected, value, location }) - } - } + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + values.push_back(element); + Ok(Value::Slice(values, typ)) } /// fn as_type(self) -> Quoted @@ -198,3 +226,84 @@ fn struct_def_fields( ]))); Ok(Value::Slice(fields, typ)) } + +fn slice_remove( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(2, &arguments, location)?; + + let index = get_u32(arguments.pop().unwrap().0, location)? as usize; + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + + if values.is_empty() { + return failing_constraint("slice_remove called on empty slice", location); + } + + if index >= values.len() { + let message = format!( + "slice_remove: index {index} is out of bounds for a slice of length {}", + values.len() + ); + return failing_constraint(message, location); + } + + let element = values.remove(index); + Ok(Value::Tuple(vec![Value::Slice(values, typ), element])) +} + +fn slice_push_front( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(2, &arguments, location)?; + + let (element, _) = arguments.pop().unwrap(); + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + values.push_front(element); + Ok(Value::Slice(values, typ)) +} + +fn slice_pop_front( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(1, &arguments, location)?; + + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + match values.pop_front() { + Some(element) => Ok(Value::Tuple(vec![element, Value::Slice(values, typ)])), + None => failing_constraint("slice_pop_front called on empty slice", location), + } +} + +fn slice_pop_back( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(1, &arguments, location)?; + + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + match values.pop_back() { + Some(element) => Ok(Value::Tuple(vec![Value::Slice(values, typ), element])), + None => failing_constraint("slice_pop_back called on empty slice", location), + } +} + +fn slice_insert( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(3, &arguments, location)?; + + let (element, _) = arguments.pop().unwrap(); + let index = get_u32(arguments.pop().unwrap().0, location)?; + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + values.insert(index as usize, element); + Ok(Value::Slice(values, typ)) +} diff --git a/test_programs/compile_success_empty/comptime_slice_methods/Nargo.toml b/test_programs/compile_success_empty/comptime_slice_methods/Nargo.toml new file mode 100644 index 00000000000..8ff281cf9e3 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_slice_methods/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_slice_methods" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] diff --git a/test_programs/compile_success_empty/comptime_slice_methods/src/main.nr b/test_programs/compile_success_empty/comptime_slice_methods/src/main.nr new file mode 100644 index 00000000000..8341669a0a4 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_slice_methods/src/main.nr @@ -0,0 +1,26 @@ +fn main() { + comptime { + // Comptime scopes are counted as unconstrained + assert(std::runtime::is_unconstrained()); + + let slice = &[2]; + let slice = slice.push_back(3); + let slice = slice.push_front(1); + assert_eq(slice, &[1, 2, 3]); + + let slice = slice.insert(1, 10); + assert_eq(slice, &[1, 10, 2, 3]); + + let (slice, ten) = slice.remove(1); + assert_eq(slice, &[1, 2, 3]); + assert_eq(ten, 10); + + let (one, slice) = slice.pop_front(); + assert_eq(slice, &[2, 3]); + assert_eq(one, 1); + + let (slice, three) = slice.pop_back(); + assert_eq(slice, &[2]); + assert_eq(three, 3); + } +} From 6abf4034521ede8d2c0de9a3fedb5baa67deccce Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 5 Jul 2024 11:51:38 -0500 Subject: [PATCH 2/4] Format test --- .../compile_success_empty/comptime_slice_methods/src/main.nr | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test_programs/compile_success_empty/comptime_slice_methods/src/main.nr b/test_programs/compile_success_empty/comptime_slice_methods/src/main.nr index 8341669a0a4..b76e8d70b83 100644 --- a/test_programs/compile_success_empty/comptime_slice_methods/src/main.nr +++ b/test_programs/compile_success_empty/comptime_slice_methods/src/main.nr @@ -1,5 +1,6 @@ fn main() { - comptime { + comptime + { // Comptime scopes are counted as unconstrained assert(std::runtime::is_unconstrained()); From b4eed93720c60ba6c6d64d4a6baa84d5f74fb57f Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 5 Jul 2024 11:52:40 -0500 Subject: [PATCH 3/4] Exclude new test from legacy tests --- tooling/nargo_cli/build.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tooling/nargo_cli/build.rs b/tooling/nargo_cli/build.rs index 0fbdaaba0b4..faac228cb63 100644 --- a/tooling/nargo_cli/build.rs +++ b/tooling/nargo_cli/build.rs @@ -61,13 +61,14 @@ const IGNORED_BRILLIG_TESTS: [&str; 11] = [ /// Certain features are only available in the elaborator. /// We skip these tests for non-elaborator code since they are not /// expected to work there. This can be removed once the old code is removed. -const IGNORED_NEW_FEATURE_TESTS: [&str; 6] = [ +const IGNORED_NEW_FEATURE_TESTS: [&str; 7] = [ "macros", "wildcard_type", "type_definition_annotation", "numeric_generics_explicit", "derive_impl", "comptime_traits", + "comptime_slice_methods", ]; fn read_test_cases( From 4285c1063ee20113a74fb71b66d0e63f95451952 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Fri, 5 Jul 2024 13:04:37 -0500 Subject: [PATCH 4/4] Add is_unconstrained docs --- docs/docs/noir/standard_library/is_unconstrained.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/docs/noir/standard_library/is_unconstrained.md b/docs/docs/noir/standard_library/is_unconstrained.md index bb157e719dc..51bb1bda8f1 100644 --- a/docs/docs/noir/standard_library/is_unconstrained.md +++ b/docs/docs/noir/standard_library/is_unconstrained.md @@ -56,4 +56,14 @@ pub fn external_interface(){ ``` -The is_unconstrained result is resolved at compile time, so in unconstrained contexts the compiler removes the else branch, and in constrained contexts the compiler removes the if branch, reducing the amount of compute necessary to run external_interface. \ No newline at end of file +The is_unconstrained result is resolved at compile time, so in unconstrained contexts the compiler removes the else branch, and in constrained contexts the compiler removes the if branch, reducing the amount of compute necessary to run external_interface. + +Note that using `is_unconstrained` in a `comptime` context will also return `true`: + +``` +fn main() { + comptime { + assert(is_unconstrained()); + } +} +```