Skip to content

Commit c48ae90

Browse files
authored
chore: pull out cfg simplification changes (#10279)
Please read [contributing guidelines](CONTRIBUTING.md) and remove this line.
1 parent 69bb64f commit c48ae90

File tree

2 files changed

+140
-1
lines changed

2 files changed

+140
-1
lines changed

noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,16 @@ impl<'f> Context<'f> {
447447
};
448448
self.condition_stack.push(cond_context);
449449
self.insert_current_side_effects_enabled();
450+
451+
// We disallow this case as it results in the `else_destination` block
452+
// being inlined before the `then_destination` block due to block deduplication in the work queue.
453+
//
454+
// The `else_destination` block then gets treated as if it were the `then_destination` block
455+
// and has the incorrect condition applied to it.
456+
assert_ne!(
457+
self.branch_ends[if_entry], *then_destination,
458+
"ICE: branches merge inside of `then` branch"
459+
);
450460
vec![self.branch_ends[if_entry], *else_destination, *then_destination]
451461
}
452462

@@ -1526,4 +1536,23 @@ mod test {
15261536
_ => unreachable!("Should have terminator instruction"),
15271537
}
15281538
}
1539+
1540+
#[test]
1541+
#[should_panic = "ICE: branches merge inside of `then` branch"]
1542+
fn panics_if_branches_merge_within_then_branch() {
1543+
//! This is a regression test for https://github.com/noir-lang/noir/issues/6620
1544+
1545+
let src = "
1546+
acir(inline) fn main f0 {
1547+
b0(v0: u1):
1548+
jmpif v0 then: b2, else: b1
1549+
b2():
1550+
return
1551+
b1():
1552+
jmp b2()
1553+
}
1554+
";
1555+
let merged_ssa = Ssa::from_str(src).unwrap();
1556+
let _ = merged_ssa.flatten_cfg();
1557+
}
15291558
}

noir/noir-repo/compiler/noirc_evaluator/src/ssa/opt/simplify_cfg.rs

Lines changed: 111 additions & 1 deletion
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
}

0 commit comments

Comments
 (0)