Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add array_refcount and slice_refcount builtins for debugging #6584

Merged
merged 8 commits into from
Nov 22, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add array_refcount and slice_refcount builtins
jfecher committed Nov 21, 2024
commit 8dc4ca597b0e6c9cf66d1e4d8d9162c14e10f334
7 changes: 7 additions & 0 deletions compiler/noirc_evaluator/src/acir/mod.rs
Original file line number Diff line number Diff line change
@@ -2806,6 +2806,13 @@ impl<'a> Context<'a> {
Intrinsic::FieldLessThan => {
unreachable!("FieldLessThan can only be called in unconstrained")
}
Intrinsic::ArrayRefCount | Intrinsic::SliceRefCount => {
let zero = self.acir_context.add_constant(FieldElement::zero());
michaeljklein marked this conversation as resolved.
Show resolved Hide resolved
Ok(vec![AcirValue::Var(
zero,
AcirType::NumericType(NumericType::Unsigned { bit_size: 32 }),
)])
}
}
}

427 changes: 234 additions & 193 deletions compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -205,16 +205,18 @@ impl Context {
| Intrinsic::IsUnconstrained => {}
Intrinsic::ArrayLen
| Intrinsic::ArrayAsStrUnchecked
| Intrinsic::ArrayRefCount
| Intrinsic::AsField
| Intrinsic::AsSlice
| Intrinsic::BlackBox(..)
| Intrinsic::DerivePedersenGenerators
| Intrinsic::FromField
| Intrinsic::SliceInsert
| Intrinsic::SlicePushBack
| Intrinsic::SlicePushFront
| Intrinsic::SlicePopBack
| Intrinsic::SlicePopFront
| Intrinsic::SliceInsert
| Intrinsic::SliceRefCount
| Intrinsic::SliceRemove
| Intrinsic::StaticAssert
| Intrinsic::StrAsBytes
10 changes: 10 additions & 0 deletions compiler/noirc_evaluator/src/ssa/ir/instruction.rs
Original file line number Diff line number Diff line change
@@ -71,6 +71,8 @@
IsUnconstrained,
DerivePedersenGenerators,
FieldLessThan,
ArrayRefCount,
SliceRefCount,
}

impl std::fmt::Display for Intrinsic {
@@ -100,6 +102,8 @@
Intrinsic::IsUnconstrained => write!(f, "is_unconstrained"),
Intrinsic::DerivePedersenGenerators => write!(f, "derive_pedersen_generators"),
Intrinsic::FieldLessThan => write!(f, "field_less_than"),
Intrinsic::ArrayRefCount => write!(f, "array_refcount"),
Intrinsic::SliceRefCount => write!(f, "slice_refcount"),
}
}
}
@@ -113,6 +117,10 @@
Intrinsic::AssertConstant
| Intrinsic::StaticAssert
| Intrinsic::ApplyRangeConstraint
// Array & slice ref counts are treated as having side effects since they operate
// on hidden variables on otherwise identical array values.
| Intrinsic::ArrayRefCount
| Intrinsic::SliceRefCount
| Intrinsic::AsWitness => true,

// These apply a constraint that the input must fit into a specified number of limbs.
@@ -171,6 +179,8 @@
"is_unconstrained" => Some(Intrinsic::IsUnconstrained),
"derive_pedersen_generators" => Some(Intrinsic::DerivePedersenGenerators),
"field_less_than" => Some(Intrinsic::FieldLessThan),
"array_refcount" => Some(Intrinsic::ArrayRefCount),
"slice_refcount" => Some(Intrinsic::SliceRefCount),

other => BlackBoxFunc::lookup(other).map(Intrinsic::BlackBox),
}
@@ -345,7 +355,7 @@
// We can deduplicate these instructions if we know the predicate is also the same.
Constrain(..) | RangeCheck { .. } => deduplicate_with_predicate,

// This should never be side-effectful

Check warning on line 358 in compiler/noirc_evaluator/src/ssa/ir/instruction.rs

GitHub Actions / Code

Unknown word (effectful)
MakeArray { .. } => true,

// These can have different behavior depending on the EnableSideEffectsIf context.
2 changes: 2 additions & 0 deletions compiler/noirc_evaluator/src/ssa/ir/instruction/call.rs
Original file line number Diff line number Diff line change
@@ -368,6 +368,8 @@ pub(super) fn simplify_call(
SimplifyResult::None
}
}
Intrinsic::ArrayRefCount => SimplifyResult::None,
Intrinsic::SliceRefCount => SimplifyResult::None,
}
}

Original file line number Diff line number Diff line change
@@ -180,6 +180,8 @@ impl Context {
| Intrinsic::AsWitness
| Intrinsic::IsUnconstrained
| Intrinsic::DerivePedersenGenerators
| Intrinsic::ArrayRefCount
| Intrinsic::SliceRefCount
| Intrinsic::FieldLessThan => false,
},

2 changes: 2 additions & 0 deletions compiler/noirc_evaluator/src/ssa/opt/remove_if_else.rs
Original file line number Diff line number Diff line change
@@ -232,6 +232,8 @@ fn slice_capacity_change(
| Intrinsic::DerivePedersenGenerators
| Intrinsic::ToBits(_)
| Intrinsic::ToRadix(_)
| Intrinsic::ArrayRefCount
| Intrinsic::SliceRefCount
| Intrinsic::FieldLessThan => SizeChange::None,
}
}
Original file line number Diff line number Diff line change
@@ -60,6 +60,7 @@ impl<'local, 'context> Interpreter<'local, 'context> {
"apply_range_constraint" => foreign::apply_range_constraint(arguments, location),
"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)),
"as_slice" => as_slice(interner, arguments, location),
"ctstring_eq" => ctstring_eq(arguments, location),
@@ -167,6 +168,7 @@ impl<'local, 'context> Interpreter<'local, 'context> {
"slice_pop_front" => slice_pop_front(interner, arguments, location, call_stack),
"slice_push_back" => slice_push_back(interner, arguments, location),
"slice_push_front" => slice_push_front(interner, arguments, location),
"slice_refcount" => Ok(Value::U32(0)),
"slice_remove" => slice_remove(interner, arguments, location, call_stack),
"str_as_bytes" => str_as_bytes(interner, arguments, location),
"str_as_ctstring" => str_as_ctstring(interner, arguments, location),
14 changes: 14 additions & 0 deletions noir_stdlib/src/mem.nr
Original file line number Diff line number Diff line change
@@ -15,3 +15,17 @@ pub fn zeroed<T>() -> T {}
/// that it is equal to the previous.
#[builtin(checked_transmute)]
pub fn checked_transmute<T, U>(value: T) -> U {}

/// Returns the internal reference count of an array value in unconstrained code.
///
/// Arrays only have reference count in unconstrained code - using this anywhere
/// else will return zero.
#[builtin(array_refcount)]
pub fn array_refcount<T, let N: u32>(array: [T; N]) -> u32 {}

/// Returns the internal reference count of a slice value in unconstrained code.
///
/// Slices only have reference count in unconstrained code - using this anywhere
/// else will return zero.
#[builtin(slice_refcount)]
pub fn slice_refcount<T>(slice: [T]) -> u32 {}
7 changes: 7 additions & 0 deletions test_programs/execution_success/reference_counts/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "reference_counts"
type = "bin"
authors = [""]
compiler_version = ">=0.35.0"

[dependencies]
2 changes: 2 additions & 0 deletions test_programs/execution_success/reference_counts/Prover.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
x = 5
b = true
58 changes: 58 additions & 0 deletions test_programs/execution_success/reference_counts/src/main.nr
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// x = 5
// unconstrained fn main() {
// let mut foo = 7;
// for _ in 0 .. 2{
// for i in 0 .. 10 {
// println(foo);
// }
// assert(foo == 7);
// }
// }
// fn main() {
// let mut acc: Field = 0;
// let array = [[1, 2], [3, 4]];
//
// for i in 0..2/*57*/ {
// for j in 0..2/*57*/ {
// acc += array[i][j];
// }
// }
// assert(acc != 0);
// }
unconstrained fn main() {
let mut array = [0, 1, 2];
assert_refcount(array, 2);

borrow(array, std::mem::array_refcount(array));
borrow_mut(&mut array, std::mem::array_refcount(array));
copy_mut(array, std::mem::array_refcount(array));
}

unconstrained fn borrow(array: [Field; 3], rc_before_call: u32) {
assert_refcount(array, rc_before_call);
println(array[0]);
}

unconstrained fn borrow_mut(array: &mut [Field; 3], rc_before_call: u32) {
assert_refcount(*array, rc_before_call + 0); // issue! this should be +1
array[0] = 5;
println(array[0]);
}

unconstrained fn copy_mut(mut array: [Field; 3], rc_before_call: u32) {
assert_refcount(array, rc_before_call + 1);
array[0] = 6;
println(array[0]);
}

fn assert_refcount(array: [Field; 3], expected: u32) {
let count = std::mem::array_refcount(array);
if std::runtime::is_unconstrained() {
if count != expected {
println(f"actual = {count}, expected = {expected}");
}
assert_eq(count, expected);
} else {
assert_eq(count, 0);
}
}