@@ -18,7 +18,8 @@ use crate::ssa::{
18
18
basic_block:: BasicBlockId ,
19
19
cfg:: ControlFlowGraph ,
20
20
function:: { Function , RuntimeType } ,
21
- instruction:: TerminatorInstruction ,
21
+ instruction:: { Instruction , TerminatorInstruction } ,
22
+ value:: Value ,
22
23
} ,
23
24
ssa_gen:: Ssa ,
24
25
} ;
@@ -31,6 +32,7 @@ impl Ssa {
31
32
/// 4. Removing any blocks which have no instructions other than a single terminating jmp.
32
33
/// 5. Replacing any jmpifs with constant conditions with jmps. If this causes the block to have
33
34
/// 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.
34
36
///
35
37
/// Currently, 1 is unimplemented.
36
38
#[ tracing:: instrument( level = "trace" , skip( self ) ) ]
@@ -55,6 +57,8 @@ impl Function {
55
57
stack. extend ( self . dfg [ block] . successors ( ) . filter ( |block| !visited. contains ( block) ) ) ;
56
58
}
57
59
60
+ check_for_negated_jmpif_condition ( self , block, & mut cfg) ;
61
+
58
62
// This call is before try_inline_into_predecessor so that if it succeeds in changing a
59
63
// jmpif into a jmp, the block may then be inlined entirely into its predecessor in try_inline_into_predecessor.
60
64
check_for_constant_jmpif ( self , block, & mut cfg) ;
@@ -184,6 +188,55 @@ fn check_for_double_jmp(function: &mut Function, block: BasicBlockId, cfg: &mut
184
188
cfg. recompute_block ( function, block) ;
185
189
}
186
190
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
+
187
240
/// If the given block has block parameters, replace them with the jump arguments from the predecessor.
188
241
///
189
242
/// Currently, if this function is needed, `try_inline_into_predecessor` will also always apply,
@@ -246,6 +299,8 @@ mod test {
246
299
map:: Id ,
247
300
types:: Type ,
248
301
} ,
302
+ opt:: assert_normalized_ssa_equals,
303
+ Ssa ,
249
304
} ;
250
305
use acvm:: acir:: AcirField ;
251
306
@@ -359,4 +414,59 @@ mod test {
359
414
other => panic ! ( "Unexpected terminator {other:?}" ) ,
360
415
}
361
416
}
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
+ }
362
472
}
0 commit comments