Skip to content

Commit 51180b9

Browse files
authored
feat: add ConstrainNotEqual instruction (#7032)
1 parent 48e613e commit 51180b9

10 files changed

Lines changed: 183 additions & 5 deletions

File tree

compiler/noirc_evaluator/src/acir/acir_variable.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,29 @@ impl<F: AcirField, B: BlackBoxFunctionSolver<F>> AcirContext<F, B> {
541541
Ok(())
542542
}
543543

544+
/// Constrains the `lhs` and `rhs` to be non-equal.
545+
///
546+
/// This is done by asserting the existence of an inverse for the value `lhs - rhs`.
547+
/// The constraint `(lhs - rhs) * inverse == 1` will only be satisfiable if `lhs` and `rhs` are non-equal.
548+
pub(crate) fn assert_neq_var(
549+
&mut self,
550+
lhs: AcirVar,
551+
rhs: AcirVar,
552+
assert_message: Option<AssertionPayload<F>>,
553+
) -> Result<(), RuntimeError> {
554+
let diff_var = self.sub_var(lhs, rhs)?;
555+
556+
let one = self.add_constant(F::one());
557+
let _ = self.inv_var(diff_var, one)?;
558+
if let Some(payload) = assert_message {
559+
self.acir_ir
560+
.assertion_payloads
561+
.insert(self.acir_ir.last_acir_opcode_location(), payload);
562+
}
563+
564+
Ok(())
565+
}
566+
544567
pub(crate) fn vars_to_expressions_or_memory(
545568
&self,
546569
values: &[AcirValue],

compiler/noirc_evaluator/src/acir/mod.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,47 @@ impl<'a> Context<'a> {
723723

724724
self.acir_context.assert_eq_var(lhs, rhs, assert_payload)?;
725725
}
726+
Instruction::ConstrainNotEqual(lhs, rhs, assert_message) => {
727+
let lhs = self.convert_numeric_value(*lhs, dfg)?;
728+
let rhs = self.convert_numeric_value(*rhs, dfg)?;
729+
730+
let assert_payload = if let Some(error) = assert_message {
731+
match error {
732+
ConstrainError::StaticString(string) => Some(
733+
self.acir_context.generate_assertion_message_payload(string.clone()),
734+
),
735+
ConstrainError::Dynamic(error_selector, is_string_type, values) => {
736+
if let Some(constant_string) = try_to_extract_string_from_error_payload(
737+
*is_string_type,
738+
values,
739+
dfg,
740+
) {
741+
Some(
742+
self.acir_context
743+
.generate_assertion_message_payload(constant_string),
744+
)
745+
} else {
746+
let acir_vars: Vec<_> = values
747+
.iter()
748+
.map(|value| self.convert_value(*value, dfg))
749+
.collect();
750+
751+
let expressions_or_memory =
752+
self.acir_context.vars_to_expressions_or_memory(&acir_vars)?;
753+
754+
Some(AssertionPayload {
755+
error_selector: error_selector.as_u64(),
756+
payload: expressions_or_memory,
757+
})
758+
}
759+
}
760+
}
761+
} else {
762+
None
763+
};
764+
765+
self.acir_context.assert_neq_var(lhs, rhs, assert_payload)?;
766+
}
726767
Instruction::Cast(value_id, _) => {
727768
let acir_var = self.convert_numeric_value(*value_id, dfg)?;
728769
self.define_result_var(dfg, instruction_id, acir_var);

compiler/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,10 @@ impl<'block> BrilligBlock<'block> {
279279
self.brillig_context.deallocate_single_addr(condition);
280280
}
281281
}
282+
Instruction::ConstrainNotEqual(..) => {
283+
unreachable!("only implemented in ACIR")
284+
}
285+
282286
Instruction::Allocate => {
283287
let result_value = dfg.instruction_results(instruction_id)[0];
284288
let pointer = self.variables.define_single_addr_variable(

compiler/noirc_evaluator/src/ssa.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ fn optimize_all(builder: SsaBuilder, options: &SsaEvaluatorOptions) -> Result<Ss
186186
.run_pass(Ssa::fold_constants, "Constant Folding")
187187
.run_pass(Ssa::remove_enable_side_effects, "EnableSideEffectsIf removal")
188188
.run_pass(Ssa::fold_constants_using_constraints, "Constraint Folding")
189+
.run_pass(Ssa::make_constrain_not_equal_instructions, "Adding constrain not equal")
189190
.run_pass(Ssa::dead_instruction_elimination, "Dead Instruction Elimination (1st)")
190191
.run_pass(Ssa::simplify_cfg, "Simplifying:")
191192
.run_pass(Ssa::array_set_optimization, "Array Set Optimizations")

compiler/noirc_evaluator/src/ssa/checks/check_for_underconstrained_values.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,8 @@ impl DependencyContext {
267267
}
268268
// Check the constrain instruction arguments against those
269269
// involved in Brillig calls, remove covered calls
270-
Instruction::Constrain(value_id1, value_id2, _) => {
270+
Instruction::Constrain(value_id1, value_id2, _)
271+
| Instruction::ConstrainNotEqual(value_id1, value_id2, _) => {
271272
self.clear_constrained(
272273
&[function.dfg.resolve(*value_id1), function.dfg.resolve(*value_id2)],
273274
function,
@@ -555,6 +556,7 @@ impl Context {
555556
| Instruction::Binary(..)
556557
| Instruction::Cast(..)
557558
| Instruction::Constrain(..)
559+
| Instruction::ConstrainNotEqual(..)
558560
| Instruction::IfElse { .. }
559561
| Instruction::Load { .. }
560562
| Instruction::Not(..)

compiler/noirc_evaluator/src/ssa/ir/instruction.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,9 @@ pub(crate) enum Instruction {
256256
/// Constrains two values to be equal to one another.
257257
Constrain(ValueId, ValueId, Option<ConstrainError>),
258258

259+
/// Constrains two values to not be equal to one another.
260+
ConstrainNotEqual(ValueId, ValueId, Option<ConstrainError>),
261+
259262
/// Range constrain `value` to `max_bit_size`
260263
RangeCheck { value: ValueId, max_bit_size: u32, assert_message: Option<String> },
261264

@@ -364,6 +367,7 @@ impl Instruction {
364367
InstructionResultType::Operand(*value)
365368
}
366369
Instruction::Constrain(..)
370+
| Instruction::ConstrainNotEqual(..)
367371
| Instruction::Store { .. }
368372
| Instruction::IncrementRc { .. }
369373
| Instruction::DecrementRc { .. }
@@ -405,7 +409,7 @@ impl Instruction {
405409
},
406410

407411
// These can fail.
408-
Constrain(..) | RangeCheck { .. } => true,
412+
Constrain(..) | ConstrainNotEqual(..) | RangeCheck { .. } => true,
409413

410414
// This should never be side-effectful
411415
MakeArray { .. } | Noop => false,
@@ -472,7 +476,7 @@ impl Instruction {
472476
},
473477

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

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

542546
Constrain(..)
547+
| ConstrainNotEqual(..)
543548
| EnableSideEffectsIf { .. }
544549
| IncrementRc { .. }
545550
| DecrementRc { .. }
@@ -610,6 +615,7 @@ impl Instruction {
610615
Instruction::Cast(_, _)
611616
| Instruction::Not(_)
612617
| Instruction::Truncate { .. }
618+
| Instruction::ConstrainNotEqual(..)
613619
| Instruction::Constrain(_, _, _)
614620
| Instruction::RangeCheck { .. }
615621
| Instruction::Allocate
@@ -656,6 +662,22 @@ impl Instruction {
656662
});
657663
Instruction::Constrain(lhs, rhs, assert_message)
658664
}
665+
Instruction::ConstrainNotEqual(lhs, rhs, assert_message) => {
666+
// Must map the `lhs` and `rhs` first as the value `f` is moved with the closure
667+
let lhs = f(*lhs);
668+
let rhs = f(*rhs);
669+
let assert_message = assert_message.as_ref().map(|error| match error {
670+
ConstrainError::Dynamic(selector, is_string, payload_values) => {
671+
ConstrainError::Dynamic(
672+
*selector,
673+
*is_string,
674+
payload_values.iter().map(|&value| f(value)).collect(),
675+
)
676+
}
677+
_ => error.clone(),
678+
});
679+
Instruction::ConstrainNotEqual(lhs, rhs, assert_message)
680+
}
659681
Instruction::Call { func, arguments } => Instruction::Call {
660682
func: f(*func),
661683
arguments: vecmap(arguments.iter().copied(), f),
@@ -714,7 +736,8 @@ impl Instruction {
714736
Instruction::Truncate { value, bit_size: _, max_bit_size: _ } => {
715737
*value = f(*value);
716738
}
717-
Instruction::Constrain(lhs, rhs, assert_message) => {
739+
Instruction::Constrain(lhs, rhs, assert_message)
740+
| Instruction::ConstrainNotEqual(lhs, rhs, assert_message) => {
718741
*lhs = f(*lhs);
719742
*rhs = f(*rhs);
720743
if let Some(ConstrainError::Dynamic(_, _, payload_values)) = assert_message {
@@ -786,7 +809,8 @@ impl Instruction {
786809
| Instruction::Load { address: value } => {
787810
f(*value);
788811
}
789-
Instruction::Constrain(lhs, rhs, assert_error) => {
812+
Instruction::Constrain(lhs, rhs, assert_error)
813+
| Instruction::ConstrainNotEqual(lhs, rhs, assert_error) => {
790814
f(*lhs);
791815
f(*rhs);
792816
if let Some(ConstrainError::Dynamic(_, _, values)) = assert_error.as_ref() {
@@ -878,6 +902,7 @@ impl Instruction {
878902
SimplifiedToInstructionMultiple(constraints)
879903
}
880904
}
905+
Instruction::ConstrainNotEqual(..) => None,
881906
Instruction::ArrayGet { array, index } => {
882907
if let Some(index) = dfg.get_numeric_constant(*index) {
883908
try_optimize_array_get_from_previous_set(dfg, *array, index)

compiler/noirc_evaluator/src/ssa/ir/printer.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,14 @@ fn display_instruction_inner(
198198
writeln!(f)
199199
}
200200
}
201+
Instruction::ConstrainNotEqual(lhs, rhs, error) => {
202+
write!(f, "constrain {} != {}", show(*lhs), show(*rhs))?;
203+
if let Some(error) = error {
204+
display_constrain_error(dfg, error, f)
205+
} else {
206+
writeln!(f)
207+
}
208+
}
201209
Instruction::Call { func, arguments } => {
202210
let arguments = value_list(dfg, arguments);
203211
writeln!(f, "call {}({}){}", show(*func), arguments, result_types(dfg, results))
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use acvm::AcirField;
2+
3+
use crate::ssa::{
4+
ir::{
5+
function::Function,
6+
instruction::{Binary, BinaryOp, Instruction},
7+
value::Value,
8+
},
9+
ssa_gen::Ssa,
10+
};
11+
12+
impl Ssa {
13+
/// A simple SSA pass to go through each [`Instruction::Constrain`], determine whether it's asserting
14+
/// two values are not equal, and if so replace it with a [`Instruction::ConstrainNotEqual`].
15+
///
16+
/// Note that this pass must be placed after CFG flattening as the flattening pass cannot
17+
/// handle this instruction.
18+
#[tracing::instrument(level = "trace", skip(self))]
19+
pub(crate) fn make_constrain_not_equal_instructions(mut self) -> Ssa {
20+
for function in self.functions.values_mut() {
21+
function.make_constrain_not_equal();
22+
}
23+
self
24+
}
25+
}
26+
27+
impl Function {
28+
pub(crate) fn make_constrain_not_equal(&mut self) {
29+
if !self.runtime().is_acir() {
30+
return;
31+
}
32+
33+
for block in self.reachable_blocks() {
34+
let instructions = self.dfg[block].instructions().to_vec();
35+
36+
for instruction in instructions {
37+
let constrain_ne: Instruction = match &self.dfg[instruction] {
38+
Instruction::Constrain(lhs, rhs, msg) => {
39+
if self
40+
.dfg
41+
.get_numeric_constant(*rhs)
42+
.map_or(false, |constant| constant.is_zero())
43+
{
44+
if let Value::Instruction { instruction, .. } =
45+
&self.dfg[self.dfg.resolve(*lhs)]
46+
{
47+
if let Instruction::Binary(Binary {
48+
lhs,
49+
rhs,
50+
operator: BinaryOp::Eq,
51+
..
52+
}) = self.dfg[*instruction]
53+
{
54+
Instruction::ConstrainNotEqual(lhs, rhs, msg.clone())
55+
} else {
56+
continue;
57+
}
58+
} else {
59+
continue;
60+
}
61+
} else {
62+
continue;
63+
}
64+
}
65+
_ => continue,
66+
};
67+
68+
self.dfg[instruction] = constrain_ne;
69+
}
70+
}
71+
}
72+
}

compiler/noirc_evaluator/src/ssa/opt/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub(crate) mod flatten_cfg;
1414
mod hint;
1515
mod inlining;
1616
mod loop_invariant;
17+
mod make_constrain_not_equal;
1718
mod mem2reg;
1819
mod normalize_value_ids;
1920
mod rc;

compiler/noirc_evaluator/src/ssa/opt/remove_enable_side_effects.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ impl Context {
143143
| Not(_)
144144
| Truncate { .. }
145145
| Constrain(..)
146+
| ConstrainNotEqual(..)
146147
| RangeCheck { .. }
147148
| IfElse { .. }
148149
| IncrementRc { .. }

0 commit comments

Comments
 (0)