Skip to content
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
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions compiler/noirc_evaluator/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ bn254_blackbox_solver.workspace = true
fxhash.workspace = true
iter-extended.workspace = true
thiserror.workspace = true
noirc_printable_type.workspace = true
num-bigint.workspace = true
num-integer.workspace = true
num-traits.workspace = true
Expand Down
8 changes: 5 additions & 3 deletions compiler/noirc_evaluator/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ pub enum RuntimeError {
AssertConstantFailed { call_stack: CallStack },
#[error("The static_assert message is not constant")]
StaticAssertDynamicMessage { call_stack: CallStack },
#[error("Argument is dynamic")]
StaticAssertDynamicPredicate { call_stack: CallStack },
#[error(
"Failed because the predicate is dynamic:\n{message}\nThe predicate must be known at compile time to be evaluated."
)]
StaticAssertDynamicPredicate { message: String, call_stack: CallStack },
#[error("{message}")]
StaticAssertFailed { message: String, call_stack: CallStack },
#[error("Nested slices, i.e. slices within an array or slice, are not supported")]
Expand Down Expand Up @@ -168,7 +170,7 @@ impl RuntimeError {
| RuntimeError::UnknownLoopBound { call_stack }
| RuntimeError::AssertConstantFailed { call_stack }
| RuntimeError::StaticAssertDynamicMessage { call_stack }
| RuntimeError::StaticAssertDynamicPredicate { call_stack }
| RuntimeError::StaticAssertDynamicPredicate { call_stack, .. }
| RuntimeError::StaticAssertFailed { call_stack, .. }
| RuntimeError::IntegerOutOfBounds { call_stack, .. }
| RuntimeError::UnsupportedIntegerSize { call_stack, .. }
Expand Down
2 changes: 1 addition & 1 deletion compiler/noirc_evaluator/src/ssa/ir/dfg/simplify/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ pub(super) fn simplify_call(
}
}
Intrinsic::StaticAssert => {
if arguments.len() != 2 {
if arguments.len() < 2 {
panic!("ICE: static_assert called with wrong number of arguments")
}

Expand Down
67 changes: 52 additions & 15 deletions compiler/noirc_evaluator/src/ssa/opt/assert_constant.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use acvm::{FieldElement, acir::brillig::ForeignCallParam};
use iter_extended::vecmap;
use noirc_printable_type::{PrintableValueDisplay, TryFromParamsError};

use crate::{
errors::RuntimeError,
ssa::{
ir::{
dfg::DataFlowGraph,
function::Function,
instruction::{Instruction, InstructionId, Intrinsic},
value::ValueId,
Expand Down Expand Up @@ -108,27 +113,59 @@ fn evaluate_static_assert(
instruction: InstructionId,
arguments: &[ValueId],
) -> Result<bool, RuntimeError> {
if arguments.len() != 2 {
if arguments.len() < 2 {
panic!("ICE: static_assert called with wrong number of arguments")
}

if !function.dfg.is_constant(arguments[1]) {
let call_stack = function.dfg.get_instruction_call_stack(instruction);
return Err(RuntimeError::StaticAssertDynamicMessage { call_stack });
// To turn the arguments into a string we do the same as we'd do if the arguments
// were passed to the built-in foreign call "print" functions.
let mut foreign_call_params = Vec::with_capacity(arguments.len() - 1);
for arg in arguments.iter().skip(1) {
if !function.dfg.is_constant(*arg) {
let call_stack = function.dfg.get_instruction_call_stack(instruction);
return Err(RuntimeError::StaticAssertDynamicMessage { call_stack });
}
append_foreign_call_param(*arg, &function.dfg, &mut foreign_call_params);
}

if function.dfg.is_constant_true(arguments[0]) {
Ok(false)
return Ok(false);
}

let message = match PrintableValueDisplay::<FieldElement>::try_from_params(&foreign_call_params)
{
Ok(display_values) => display_values.to_string(),
Err(err) => match err {
TryFromParamsError::MissingForeignCallInputs => {
panic!("ICE: missing foreign call inputs")
}
TryFromParamsError::ParsingError(error) => {
panic!("ICE: could not decode printable type {:?}", error)
}
},
};

let call_stack = function.dfg.get_instruction_call_stack(instruction);
if !function.dfg.is_constant(arguments[0]) {
return Err(RuntimeError::StaticAssertDynamicPredicate { message, call_stack });
}

Err(RuntimeError::StaticAssertFailed { message, call_stack })
}

fn append_foreign_call_param(
value: ValueId,
dfg: &DataFlowGraph,
foreign_call_params: &mut Vec<ForeignCallParam<FieldElement>>,
) {
if let Some(field) = dfg.get_numeric_constant(value) {
foreign_call_params.push(ForeignCallParam::Single(field));
} else if let Some((values, _typ)) = dfg.get_array_constant(value) {
let values = vecmap(values, |value| {
dfg.get_numeric_constant(value).expect("ICE: expected constant value")
});
foreign_call_params.push(ForeignCallParam::Array(values));
} else {
let call_stack = function.dfg.get_instruction_call_stack(instruction);
if function.dfg.is_constant(arguments[0]) {
let message = function
.dfg
.get_string(arguments[1])
.expect("Expected second argument to be a string");
Err(RuntimeError::StaticAssertFailed { message, call_stack })
} else {
Err(RuntimeError::StaticAssertDynamicPredicate { call_stack })
}
panic!("ICE: expected constant value");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ fn slice_push_back(
Ok(Value::Slice(values, typ))
}

// static_assert<let N: u32>(predicate: bool, message: str<N>)
// static_assert<let N: u32>(predicate: bool, message: T)
fn static_assert(
interner: &NodeInterner,
arguments: Vec<(Value, Location)>,
Expand All @@ -340,7 +340,7 @@ fn static_assert(
) -> IResult<Value> {
let (predicate, message) = check_two_arguments(arguments, location)?;
let predicate = get_bool(predicate)?;
let message = get_str(interner, message)?;
let message = message.0.display(interner).to_string();

if predicate {
Ok(Value::Unit)
Expand Down
61 changes: 37 additions & 24 deletions compiler/noirc_frontend/src/monomorphization/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1644,6 +1644,15 @@ impl<'interner> Monomorphizer<'interner> {
self.append_printable_type_info(&hir_arguments[1], &mut arguments);
}
}
if let Definition::Builtin(name) = &ident.definition {
if name.as_str() == "static_assert" {
// static_assert can take any type for the `message` argument.
// Here we append printable type info so we can know how to turn that argument
// into a human-readable string.
let typ = self.interner.id_type(call.arguments[1]);
self.append_printable_type_info_for_type(typ, &mut arguments);
}
}
}

let mut block_expressions = vec![];
Expand Down Expand Up @@ -1713,35 +1722,39 @@ impl<'interner> Monomorphizer<'interner> {
match hir_argument {
HirExpression::Ident(ident, _) => {
let typ = self.interner.definition_type(ident.id);
let typ: Type = typ.follow_bindings();
let is_fmt_str = match typ {
// A format string has many different possible types that need to be handled.
// Loop over each element in the format string to fetch each type's relevant metadata
Type::FmtString(_, elements) => {
match *elements {
Type::Tuple(element_types) => {
for typ in element_types {
Self::append_printable_type_info_inner(&typ, arguments);
}
}
_ => unreachable!(
"ICE: format string type should be a tuple but got a {elements}"
),
}
true
}
_ => {
Self::append_printable_type_info_inner(&typ, arguments);
false
}
};
// The caller needs information as to whether it is handling a format string or a single type
arguments.push(ast::Expression::Literal(ast::Literal::Bool(is_fmt_str)));
self.append_printable_type_info_for_type(typ, arguments);
}
_ => unreachable!("logging expr {:?} is not supported", hir_argument),
}
}

fn append_printable_type_info_for_type(&self, typ: Type, arguments: &mut Vec<ast::Expression>) {
let typ: Type = typ.follow_bindings();
let is_fmt_str = match typ {
// A format string has many different possible types that need to be handled.
// Loop over each element in the format string to fetch each type's relevant metadata
Type::FmtString(_, elements) => {
match *elements {
Type::Tuple(element_types) => {
for typ in element_types {
Self::append_printable_type_info_inner(&typ, arguments);
}
}
_ => unreachable!(
"ICE: format string type should be a tuple but got a {elements}"
),
}
true
}
_ => {
Self::append_printable_type_info_inner(&typ, arguments);
false
}
};
// The caller needs information as to whether it is handling a format string or a single type
arguments.push(ast::Expression::Literal(ast::Literal::Bool(is_fmt_str)));
}

fn append_printable_type_info_inner(typ: &Type, arguments: &mut Vec<ast::Expression>) {
// Disallow printing slices and mutable references for consistency,
// since they cannot be passed from ACIR into Brillig
Expand Down
2 changes: 2 additions & 0 deletions compiler/noirc_printable_type/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ workspace = true

[dependencies]
acvm.workspace = true
iter-extended.workspace = true
serde.workspace = true
serde_json.workspace = true

[dev-dependencies]
proptest.workspace = true
Loading
Loading