Skip to content

Commit

Permalink
feat: add ConstrainNotEqual instruction
Browse files Browse the repository at this point in the history
  • Loading branch information
TomAFrench committed Jan 11, 2025
1 parent db28cb9 commit 2421b59
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 5 deletions.
20 changes: 20 additions & 0 deletions compiler/noirc_evaluator/src/acir/acir_variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,26 @@ impl<F: AcirField, B: BlackBoxFunctionSolver<F>> AcirContext<F, B> {
Ok(())
}

/// Constrains the `lhs` and `rhs` to be non-equal.
pub(crate) fn assert_neq_var(
&mut self,
lhs: AcirVar,
rhs: AcirVar,
assert_message: Option<AssertionPayload<F>>,
) -> Result<(), RuntimeError> {
let diff_var = self.sub_var(lhs, rhs)?;

let one = self.add_constant(F::one());
let _ = self.inv_var(diff_var, one)?;
if let Some(payload) = assert_message {
self.acir_ir
.assertion_payloads
.insert(self.acir_ir.last_acir_opcode_location(), payload);
}

Ok(())
}

pub(crate) fn vars_to_expressions_or_memory(
&self,
values: &[AcirValue],
Expand Down
41 changes: 41 additions & 0 deletions compiler/noirc_evaluator/src/acir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,47 @@ impl<'a> Context<'a> {

self.acir_context.assert_eq_var(lhs, rhs, assert_payload)?;
}
Instruction::ConstrainNotEqual(lhs, rhs, assert_message) => {
let lhs = self.convert_numeric_value(*lhs, dfg)?;
let rhs = self.convert_numeric_value(*rhs, dfg)?;

let assert_payload = if let Some(error) = assert_message {
match error {
ConstrainError::StaticString(string) => Some(
self.acir_context.generate_assertion_message_payload(string.clone()),
),
ConstrainError::Dynamic(error_selector, is_string_type, values) => {
if let Some(constant_string) = try_to_extract_string_from_error_payload(
*is_string_type,
values,
dfg,
) {
Some(
self.acir_context
.generate_assertion_message_payload(constant_string),
)
} else {
let acir_vars: Vec<_> = values
.iter()
.map(|value| self.convert_value(*value, dfg))
.collect();

let expressions_or_memory =
self.acir_context.vars_to_expressions_or_memory(&acir_vars)?;

Some(AssertionPayload {
error_selector: error_selector.as_u64(),
payload: expressions_or_memory,
})
}
}
}
} else {
None
};

self.acir_context.assert_neq_var(lhs, rhs, assert_payload)?;
}
Instruction::Cast(value_id, _) => {
let acir_var = self.convert_numeric_value(*value_id, dfg)?;
self.define_result_var(dfg, instruction_id, acir_var);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,10 @@ impl<'block> BrilligBlock<'block> {
self.brillig_context.deallocate_single_addr(condition);
}
}
Instruction::ConstrainNotEqual(..) => {
unreachable!("only implemented in ACIR")
}

Instruction::Allocate => {
let result_value = dfg.instruction_results(instruction_id)[0];
let pointer = self.variables.define_single_addr_variable(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ impl DependencyContext {
}
// Check the constrain instruction arguments against those
// involved in Brillig calls, remove covered calls
Instruction::Constrain(value_id1, value_id2, _) => {
Instruction::Constrain(value_id1, value_id2, _)
| Instruction::ConstrainNotEqual(value_id1, value_id2, _) => {
self.clear_constrained(
&[function.dfg.resolve(*value_id1), function.dfg.resolve(*value_id2)],
function,
Expand Down Expand Up @@ -555,6 +556,7 @@ impl Context {
| Instruction::Binary(..)
| Instruction::Cast(..)
| Instruction::Constrain(..)
| Instruction::ConstrainNotEqual(..)
| Instruction::IfElse { .. }
| Instruction::Load { .. }
| Instruction::Not(..)
Expand Down
50 changes: 46 additions & 4 deletions compiler/noirc_evaluator/src/ssa/ir/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,9 @@ pub(crate) enum Instruction {
/// Constrains two values to be equal to one another.
Constrain(ValueId, ValueId, Option<ConstrainError>),

/// Constrains two values to not be equal to one another.
ConstrainNotEqual(ValueId, ValueId, Option<ConstrainError>),

/// Range constrain `value` to `max_bit_size`
RangeCheck { value: ValueId, max_bit_size: u32, assert_message: Option<String> },

Expand Down Expand Up @@ -364,6 +367,7 @@ impl Instruction {
InstructionResultType::Operand(*value)
}
Instruction::Constrain(..)
| Instruction::ConstrainNotEqual(..)
| Instruction::Store { .. }
| Instruction::IncrementRc { .. }
| Instruction::DecrementRc { .. }
Expand Down Expand Up @@ -405,7 +409,7 @@ impl Instruction {
},

// These can fail.
Constrain(..) | RangeCheck { .. } => true,
Constrain(..) | ConstrainNotEqual(..) | RangeCheck { .. } => true,

// This should never be side-effectful

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

View workflow job for this annotation

GitHub Actions / Code

Unknown word (effectful)
MakeArray { .. } | Noop => false,
Expand Down Expand Up @@ -472,7 +476,7 @@ impl Instruction {
},

// We can deduplicate these instructions if we know the predicate is also the same.
Constrain(..) | RangeCheck { .. } => deduplicate_with_predicate,
Constrain(..) | ConstrainNotEqual(..) | RangeCheck { .. } => deduplicate_with_predicate,

// Noop instructions can always be deduplicated, although they're more likely to be
// removed entirely.
Expand Down Expand Up @@ -540,6 +544,7 @@ impl Instruction {
}

Constrain(..)
| ConstrainNotEqual(..)
| EnableSideEffectsIf { .. }
| IncrementRc { .. }
| DecrementRc { .. }
Expand Down Expand Up @@ -610,6 +615,7 @@ impl Instruction {
Instruction::Cast(_, _)
| Instruction::Not(_)
| Instruction::Truncate { .. }
| Instruction::ConstrainNotEqual(..)
| Instruction::Constrain(_, _, _)
| Instruction::RangeCheck { .. }
| Instruction::Allocate
Expand Down Expand Up @@ -656,6 +662,22 @@ impl Instruction {
});
Instruction::Constrain(lhs, rhs, assert_message)
}
Instruction::ConstrainNotEqual(lhs, rhs, assert_message) => {
// Must map the `lhs` and `rhs` first as the value `f` is moved with the closure
let lhs = f(*lhs);
let rhs = f(*rhs);
let assert_message = assert_message.as_ref().map(|error| match error {
ConstrainError::Dynamic(selector, is_string, payload_values) => {
ConstrainError::Dynamic(
*selector,
*is_string,
payload_values.iter().map(|&value| f(value)).collect(),
)
}
_ => error.clone(),
});
Instruction::ConstrainNotEqual(lhs, rhs, assert_message)
}
Instruction::Call { func, arguments } => Instruction::Call {
func: f(*func),
arguments: vecmap(arguments.iter().copied(), f),
Expand Down Expand Up @@ -714,7 +736,8 @@ impl Instruction {
Instruction::Truncate { value, bit_size: _, max_bit_size: _ } => {
*value = f(*value);
}
Instruction::Constrain(lhs, rhs, assert_message) => {
Instruction::Constrain(lhs, rhs, assert_message)
| Instruction::ConstrainNotEqual(lhs, rhs, assert_message) => {
*lhs = f(*lhs);
*rhs = f(*rhs);
if let Some(ConstrainError::Dynamic(_, _, payload_values)) = assert_message {
Expand Down Expand Up @@ -786,7 +809,8 @@ impl Instruction {
| Instruction::Load { address: value } => {
f(*value);
}
Instruction::Constrain(lhs, rhs, assert_error) => {
Instruction::Constrain(lhs, rhs, assert_error)
| Instruction::ConstrainNotEqual(lhs, rhs, assert_error) => {
f(*lhs);
f(*rhs);
if let Some(ConstrainError::Dynamic(_, _, values)) = assert_error.as_ref() {
Expand Down Expand Up @@ -871,13 +895,31 @@ impl Instruction {
}
}
Instruction::Constrain(lhs, rhs, msg) => {
if dfg.get_numeric_constant(*rhs).map_or(false, |constant| constant.is_zero()) {
if let Value::Instruction { instruction, .. } = &dfg[dfg.resolve(*lhs)] {
if let Instruction::Binary(Binary {
lhs,
rhs,
operator: BinaryOp::Eq,
..
}) = &dfg[*instruction]
{
return SimplifiedToInstruction(Instruction::ConstrainNotEqual(
*lhs,
*rhs,
msg.clone(),
));
}
}
}
let constraints = decompose_constrain(*lhs, *rhs, msg, dfg);
if constraints.is_empty() {
Remove
} else {
SimplifiedToInstructionMultiple(constraints)
}
}
Instruction::ConstrainNotEqual(..) => None,
Instruction::ArrayGet { array, index } => {
if let Some(index) = dfg.get_numeric_constant(*index) {
try_optimize_array_get_from_previous_set(dfg, *array, index)
Expand Down
8 changes: 8 additions & 0 deletions compiler/noirc_evaluator/src/ssa/ir/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,14 @@ fn display_instruction_inner(
writeln!(f)
}
}
Instruction::ConstrainNotEqual(lhs, rhs, error) => {
write!(f, "constrain {} != {}", show(*lhs), show(*rhs))?;
if let Some(error) = error {
display_constrain_error(dfg, error, f)
} else {
writeln!(f)
}
}
Instruction::Call { func, arguments } => {
let arguments = value_list(dfg, arguments);
writeln!(f, "call {}({}){}", show(*func), arguments, result_types(dfg, results))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ impl Context {
| Not(_)
| Truncate { .. }
| Constrain(..)
| ConstrainNotEqual(..)
| RangeCheck { .. }
| IfElse { .. }
| IncrementRc { .. }
Expand Down

0 comments on commit 2421b59

Please sign in to comment.