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 assert_constant #2242

Merged
merged 4 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "assert_constant_fail"
type = "bin"
authors = [""]
compiler_version = "0.9.0"

[dependencies]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use dep::std::assert_constant;

fn main(x: Field) {
foo(5, x);
}

fn foo(constant: Field, non_constant: Field) {
assert_constant(constant);
assert_constant(non_constant);
}
5 changes: 4 additions & 1 deletion crates/noirc_evaluator/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub enum RuntimeError {
UnsupportedIntegerSize { num_bits: u32, max_num_bits: u32, location: Option<Location> },
#[error("Could not determine loop bound at compile-time")]
UnknownLoopBound { location: Option<Location> },
#[error("Argument is not constant")]
AssertConstantFailed { location: Option<Location> },
}

#[derive(Debug, PartialEq, Eq, Clone, Error)]
Expand Down Expand Up @@ -69,7 +71,8 @@ impl RuntimeError {
| RuntimeError::InvalidRangeConstraint { location, .. }
| RuntimeError::TypeConversion { location, .. }
| RuntimeError::UnInitialized { location, .. }
| RuntimeError::UnknownLoopBound { location, .. }
| RuntimeError::UnknownLoopBound { location }
| RuntimeError::AssertConstantFailed { location }
| RuntimeError::UnsupportedIntegerSize { location, .. } => *location,
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/noirc_evaluator/src/ssa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ pub(crate) fn optimize_into_acir(
ssa = ssa
.inline_functions()
.print(print_ssa_passes, "After Inlining:")
.evaluate_assert_constant()?
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
.unroll_loops()?
.print(print_ssa_passes, "After Unrolling:")
.simplify_cfg()
Expand Down
5 changes: 5 additions & 0 deletions crates/noirc_evaluator/src/ssa/ir/basic_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ impl BasicBlock {
&mut self.instructions
}

/// Take the instructions in this block, replacing it with an empty Vec
pub(crate) fn take_instructions(&mut self) -> Vec<InstructionId> {
std::mem::take(&mut self.instructions)
}

/// Sets the terminator instruction of this block.
///
/// A properly-constructed block will always terminate with a TerminatorInstruction -
Expand Down
11 changes: 7 additions & 4 deletions crates/noirc_evaluator/src/ssa/ir/dfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ impl DataFlowGraph {
/// source block.
pub(crate) fn inline_block(&mut self, source: BasicBlockId, destination: BasicBlockId) {
let source = &mut self.blocks[source];
let mut instructions = std::mem::take(source.instructions_mut());
let mut instructions = source.take_instructions();
let terminator = source.take_terminator();

let destination = &mut self.blocks[destination];
Expand All @@ -417,6 +417,11 @@ impl DataFlowGraph {
_ => None,
}
}

/// True if the given ValueId refers to a constant value
pub(crate) fn is_constant(&self, argument: ValueId) -> bool {
!matches!(&self[self.resolve(argument)], Value::Instruction { .. } | Value::Param { .. })
}
}

impl std::ops::Index<InstructionId> for DataFlowGraph {
Expand Down Expand Up @@ -483,9 +488,7 @@ impl<'dfg> InsertInstructionResult<'dfg> {
InsertInstructionResult::Results(results) => Cow::Borrowed(results),
InsertInstructionResult::SimplifiedTo(result) => Cow::Owned(vec![result]),
InsertInstructionResult::SimplifiedToMultiple(results) => Cow::Owned(results),
InsertInstructionResult::InstructionRemoved => {
panic!("InsertInstructionResult::results called on a removed instruction")
}
InsertInstructionResult::InstructionRemoved => Cow::Owned(vec![]),
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
3 changes: 3 additions & 0 deletions crates/noirc_evaluator/src/ssa/ir/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub(crate) type InstructionId = Id<Instruction>;
pub(crate) enum Intrinsic {
Sort,
ArrayLen,
AssertConstant,
SlicePushBack,
SlicePushFront,
SlicePopBack,
Expand All @@ -52,6 +53,7 @@ impl std::fmt::Display for Intrinsic {
Intrinsic::Println => write!(f, "println"),
Intrinsic::Sort => write!(f, "arraysort"),
Intrinsic::ArrayLen => write!(f, "array_len"),
Intrinsic::AssertConstant => write!(f, "assert_constant"),
Intrinsic::SlicePushBack => write!(f, "slice_push_back"),
Intrinsic::SlicePushFront => write!(f, "slice_push_front"),
Intrinsic::SlicePopBack => write!(f, "slice_pop_back"),
Expand All @@ -75,6 +77,7 @@ impl Intrinsic {
"println" => Some(Intrinsic::Println),
"arraysort" => Some(Intrinsic::Sort),
"array_len" => Some(Intrinsic::ArrayLen),
"assert_constant" => Some(Intrinsic::AssertConstant),
"slice_push_back" => Some(Intrinsic::SlicePushBack),
"slice_push_front" => Some(Intrinsic::SlicePushFront),
"slice_pop_back" => Some(Intrinsic::SlicePopBack),
Expand Down
7 changes: 7 additions & 0 deletions crates/noirc_evaluator/src/ssa/ir/instruction/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,13 @@ pub(super) fn simplify_call(
SimplifyResult::None
}
}
Intrinsic::AssertConstant => {
if arguments.iter().all(|argument| dfg.is_constant(*argument)) {
SimplifyResult::Remove
} else {
SimplifyResult::None
}
}
Intrinsic::BlackBox(bb_func) => simplify_black_box_func(bb_func, arguments, dfg),
Intrinsic::Sort => simplify_sort(dfg, arguments),
Intrinsic::Println => SimplifyResult::None,
Expand Down
83 changes: 83 additions & 0 deletions crates/noirc_evaluator/src/ssa/opt/assert_constant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use crate::{
errors::RuntimeError,
ssa::{
ir::{
function::Function,
instruction::{Instruction, InstructionId, Intrinsic},
value::ValueId,
},
ssa_gen::Ssa,
},
};

impl Ssa {
/// A simple SSA pass to go through each instruction and evaluate each call
/// to `assert_constant`, issuing an error if any arguments to the function are
/// not constants.
///
/// Note that this pass must be placed directly before loop unrolling to be
/// useful. Any optimization passes between this and loop unrolling will cause
/// the constants that this pass sees to be potentially different than the constants
/// seen by loop unrolling. Furthermore, this pass cannot be a part of loop unrolling
/// since we must go through every instruction to find all references to `assert_constant`
/// while loop unrolling only touches blocks with loops in them.
pub(crate) fn evaluate_assert_constant(mut self) -> Result<Ssa, RuntimeError> {
for function in self.functions.values_mut() {
for block in function.reachable_blocks() {
// Unfortunately we can't just use instructions.retain(...) here since
// check_instruction can also return an error
let instructions = function.dfg[block].take_instructions();
let mut filtered_instructions = Vec::with_capacity(instructions.len());

for instruction in instructions {
if check_instruction(function, instruction)? {
filtered_instructions.push(instruction);
}
}

*function.dfg[block].instructions_mut() = filtered_instructions;
}
}
Ok(self)
}
}

/// During the loop unrolling pass we also evaluate calls to `assert_constant`.
/// This is done in this pass because loop unrolling is the only pass that will error
/// if a value (the loop bounds) are not known constants.
///
/// This returns Ok(true) if the given instruction should be kept in the block and
/// Ok(false) if it should be removed.
fn check_instruction(
function: &mut Function,
instruction: InstructionId,
) -> Result<bool, RuntimeError> {
let assert_constant_id = function.dfg.import_intrinsic(Intrinsic::AssertConstant);
match &function.dfg[instruction] {
Instruction::Call { func, arguments } => {
if *func == assert_constant_id {
evaluate_assert_constant(function, instruction, arguments)
} else {
Ok(true)
}
}
_ => Ok(true),
}
}

/// Evaluate a call to `assert_constant`, returning an error if any of the elements are not
/// constants. If all of the elements are constants, Ok(false) is returned. This signifies a
/// success but also that the instruction need not be reinserted into the block being unrolled
/// since it has already been evaluated.
fn evaluate_assert_constant(
function: &Function,
instruction: InstructionId,
arguments: &[ValueId],
) -> Result<bool, RuntimeError> {
if arguments.iter().all(|arg| function.dfg.is_constant(*arg)) {
Ok(false)
} else {
let location = function.dfg.get_location(&instruction);
Err(RuntimeError::AssertConstantFailed { location })
}
}
2 changes: 1 addition & 1 deletion crates/noirc_evaluator/src/ssa/opt/constant_folding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ struct Context {

impl Context {
fn fold_constants_in_block(&mut self, function: &mut Function, block: BasicBlockId) {
let instructions = std::mem::take(function.dfg[block].instructions_mut());
let instructions = function.dfg[block].take_instructions();

for instruction in instructions {
self.push_instruction(function, block, instruction);
Expand Down
1 change: 1 addition & 0 deletions crates/noirc_evaluator/src/ssa/opt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! simpler form until the IR only has a single function remaining with 1 block within it.
//! Generally, these passes are also expected to minimize the final amount of instructions.
mod array_use;
mod assert_constant;
mod constant_folding;
mod defunctionalize;
mod die;
Expand Down
5 changes: 5 additions & 0 deletions noir_stdlib/src/lib.nr
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@ unconstrained fn println<T>(input: T) {

#[foreign(recursive_aggregation)]
fn verify_proof<N>(_verification_key : [Field], _proof : [Field], _public_inputs : [Field], _key_hash : Field, _input_aggregation_object : [Field; N]) -> [Field; N] {}

// Asserts that the given value is known at compile-time.
// Useful for debugging for-loop bounds.
#[builtin(assert_constant)]
fn assert_constant<T>(_x: T) {}