Skip to content

Commit c8f2651

Browse files
authored
Merge 72892b2 into 7d7b9c9
2 parents 7d7b9c9 + 72892b2 commit c8f2651

File tree

4 files changed

+127
-1
lines changed

4 files changed

+127
-1
lines changed

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

+111-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ use crate::ssa::{
1818
basic_block::BasicBlockId,
1919
cfg::ControlFlowGraph,
2020
function::{Function, RuntimeType},
21-
instruction::TerminatorInstruction,
21+
instruction::{Instruction, TerminatorInstruction},
22+
value::Value,
2223
},
2324
ssa_gen::Ssa,
2425
};
@@ -31,6 +32,7 @@ impl Ssa {
3132
/// 4. Removing any blocks which have no instructions other than a single terminating jmp.
3233
/// 5. Replacing any jmpifs with constant conditions with jmps. If this causes the block to have
3334
/// only 1 successor then (2) also will be applied.
35+
/// 6. Replacing any jmpifs with a negated condition with a jmpif with a un-negated condition and reversed branches.
3436
///
3537
/// Currently, 1 is unimplemented.
3638
#[tracing::instrument(level = "trace", skip(self))]
@@ -55,6 +57,8 @@ impl Function {
5557
stack.extend(self.dfg[block].successors().filter(|block| !visited.contains(block)));
5658
}
5759

60+
check_for_negated_jmpif_condition(self, block, &mut cfg);
61+
5862
// This call is before try_inline_into_predecessor so that if it succeeds in changing a
5963
// jmpif into a jmp, the block may then be inlined entirely into its predecessor in try_inline_into_predecessor.
6064
check_for_constant_jmpif(self, block, &mut cfg);
@@ -184,6 +188,55 @@ fn check_for_double_jmp(function: &mut Function, block: BasicBlockId, cfg: &mut
184188
cfg.recompute_block(function, block);
185189
}
186190

191+
/// Optimize a jmpif on a negated condition by swapping the branches.
192+
fn check_for_negated_jmpif_condition(
193+
function: &mut Function,
194+
block: BasicBlockId,
195+
cfg: &mut ControlFlowGraph,
196+
) {
197+
if matches!(function.runtime(), RuntimeType::Acir(_)) {
198+
// Swapping the `then` and `else` branches of a `JmpIf` within an ACIR function
199+
// can result in the situation where the branches merge together again in the `then` block, e.g.
200+
//
201+
// acir(inline) fn main f0 {
202+
// b0(v0: u1):
203+
// jmpif v0 then: b2, else: b1
204+
// b2():
205+
// return
206+
// b1():
207+
// jmp b2()
208+
// }
209+
//
210+
// This breaks the `flatten_cfg` pass as it assumes that merges only happen in
211+
// the `else` block or a 3rd block.
212+
//
213+
// See: https://github.com/noir-lang/noir/pull/5891#issuecomment-2500219428
214+
return;
215+
}
216+
217+
if let Some(TerminatorInstruction::JmpIf {
218+
condition,
219+
then_destination,
220+
else_destination,
221+
call_stack,
222+
}) = function.dfg[block].terminator()
223+
{
224+
if let Value::Instruction { instruction, .. } = function.dfg[*condition] {
225+
if let Instruction::Not(negated_condition) = function.dfg[instruction] {
226+
let call_stack = call_stack.clone();
227+
let jmpif = TerminatorInstruction::JmpIf {
228+
condition: negated_condition,
229+
then_destination: *else_destination,
230+
else_destination: *then_destination,
231+
call_stack,
232+
};
233+
function.dfg[block].set_terminator(jmpif);
234+
cfg.recompute_block(function, block);
235+
}
236+
}
237+
}
238+
}
239+
187240
/// If the given block has block parameters, replace them with the jump arguments from the predecessor.
188241
///
189242
/// Currently, if this function is needed, `try_inline_into_predecessor` will also always apply,
@@ -246,6 +299,8 @@ mod test {
246299
map::Id,
247300
types::Type,
248301
},
302+
opt::assert_normalized_ssa_equals,
303+
Ssa,
249304
};
250305
use acvm::acir::AcirField;
251306

@@ -359,4 +414,59 @@ mod test {
359414
other => panic!("Unexpected terminator {other:?}"),
360415
}
361416
}
417+
418+
#[test]
419+
fn swap_negated_jmpif_branches_in_brillig() {
420+
let src = "
421+
brillig(inline) fn main f0 {
422+
b0(v0: u1):
423+
v1 = allocate -> &mut Field
424+
store Field 0 at v1
425+
v3 = not v0
426+
jmpif v3 then: b1, else: b2
427+
b1():
428+
store Field 2 at v1
429+
jmp b2()
430+
b2():
431+
v5 = load v1 -> Field
432+
v6 = eq v5, Field 2
433+
constrain v5 == Field 2
434+
return
435+
}";
436+
let ssa = Ssa::from_str(src).unwrap();
437+
438+
let expected = "
439+
brillig(inline) fn main f0 {
440+
b0(v0: u1):
441+
v1 = allocate -> &mut Field
442+
store Field 0 at v1
443+
v3 = not v0
444+
jmpif v0 then: b2, else: b1
445+
b2():
446+
v5 = load v1 -> Field
447+
v6 = eq v5, Field 2
448+
constrain v5 == Field 2
449+
return
450+
b1():
451+
store Field 2 at v1
452+
jmp b2()
453+
}";
454+
assert_normalized_ssa_equals(ssa.simplify_cfg(), expected);
455+
}
456+
457+
#[test]
458+
fn does_not_swap_negated_jmpif_branches_in_acir() {
459+
let src = "
460+
acir(inline) fn main f0 {
461+
b0(v0: u1):
462+
v1 = not v0
463+
jmpif v1 then: b1, else: b2
464+
b1():
465+
jmp b2()
466+
b2():
467+
return
468+
}";
469+
let ssa = Ssa::from_str(src).unwrap();
470+
assert_normalized_ssa_equals(ssa.simplify_cfg(), src);
471+
}
362472
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "negated_jmpif_condition"
3+
type = "bin"
4+
authors = [""]
5+
6+
[dependencies]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
x = "2"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
fn main(mut x: Field) {
2+
let mut q = 0;
3+
4+
if x != 10 {
5+
q = 2;
6+
}
7+
8+
assert(q == 2);
9+
}

0 commit comments

Comments
 (0)