@@ -353,13 +353,23 @@ impl<'f> LoopInvariantContext<'f> {
353353
354354 let can_be_hoisted = can_be_hoisted ( & instruction, self . inserter . function , false )
355355 || matches ! ( instruction, MakeArray { .. } )
356- || ( can_be_hoisted ( & instruction, self . inserter . function , true )
357- && !self . current_block_control_dependent )
356+ || self . can_be_hoisted_with_control_dependence ( & instruction, self . inserter . function )
358357 || self . can_be_hoisted_from_loop_bounds ( & instruction) ;
359358
360359 is_loop_invariant && can_be_hoisted
361360 }
362361
362+ /// Check [can_be_hoisted] with extra control dependence information that
363+ /// lives within the context of this pass.
364+ fn can_be_hoisted_with_control_dependence (
365+ & self ,
366+ instruction : & Instruction ,
367+ function : & Function ,
368+ ) -> bool {
369+ can_be_hoisted ( instruction, function, true )
370+ && self . can_hoist_control_dependent_instruction ( )
371+ }
372+
363373 /// Keep track of a loop induction variable and respective upper bound.
364374 /// In the case of a nested loop, this will be used by later loops to determine
365375 /// whether they have operations reliant upon the maximum induction variable.
@@ -403,24 +413,29 @@ impl<'f> LoopInvariantContext<'f> {
403413 }
404414 Binary ( binary) => self . can_evaluate_binary_op ( binary) ,
405415 Constrain ( ..) | ConstrainNotEqual ( ..) | RangeCheck { .. } => {
406- // If we know the loop will be executed we can still only hoist if we are in a non control dependent block.
407- !self . current_block_control_dependent && self . does_loop_body_execute ( )
416+ self . can_hoist_control_dependent_instruction ( )
408417 }
409418 Call { func, .. } => {
410419 let purity = match self . inserter . function . dfg [ * func] {
411420 Value :: Intrinsic ( intrinsic) => Some ( intrinsic. purity ( ) ) ,
412421 Value :: Function ( id) => self . inserter . function . dfg . purity_of ( id) ,
413422 _ => None ,
414423 } ;
415- // If we know the loop will be executed we can still only hoist if we are in a non control dependent block.
416424 matches ! ( purity, Some ( Purity :: PureWithPredicate ) )
417- && !self . current_block_control_dependent
418- && self . does_loop_body_execute ( )
425+ && self . can_hoist_control_dependent_instruction ( )
419426 }
420427 _ => false ,
421428 }
422429 }
423430
431+ /// A control dependent instruction (e.g. constrain or division) has more strict conditions for hoisting.
432+ /// This function check the following conditions:
433+ /// - The current block is non control dependent
434+ /// - The loop body is guaranteed to be executed
435+ fn can_hoist_control_dependent_instruction ( & self ) -> bool {
436+ !self . current_block_control_dependent && self . does_loop_body_execute ( )
437+ }
438+
424439 /// Determine whether the loop body is guaranteed to execute.
425440 /// We know a loop body will execute if we have constant loop bounds where the upper bound
426441 /// is greater than the lower bound.
@@ -819,10 +834,18 @@ impl<'f> LoopInvariantContext<'f> {
819834/// and its predicate, rather than just the instruction. Setting this means instructions that
820835/// rely on predicates can be hoisted as well.
821836///
837+ /// # Preconditions
822838/// Certain instructions can be hoisted because they implicitly depend on a predicate.
823839/// However, to avoid tight coupling between passes, we make the hoisting
824840/// conditional on whether the caller wants the predicate to be taken into account or not.
825841///
842+ /// Even if we know the predicate is the same for an instruction's block and a loop's header block,
843+ /// the caller of this methods needs to be careful as a loop may still never be executed.
844+ /// This is because an loop with dynamic bounds may never execute its loop body.
845+ /// If the instruction were to trigger a failure, our program may fail inadvertently.
846+ /// If we know a loop's upper bound is greater than its lower bound we can hoist these instructions,
847+ /// but it is left to the caller of this method to account for this case.
848+ ///
826849/// This differs from `can_be_deduplicated` as that method assumes there is a matching instruction
827850/// with the same inputs. Hoisting is for lone instructions, meaning a mislabeled hoist could cause
828851/// unexpected failures if the instruction was never meant to be executed.
@@ -850,19 +873,13 @@ fn can_be_hoisted(
850873 } ;
851874 match purity {
852875 Some ( Purity :: Pure ) => true ,
853- Some ( Purity :: PureWithPredicate ) => false ,
876+ Some ( Purity :: PureWithPredicate ) => hoist_with_predicate ,
854877 Some ( Purity :: Impure ) => false ,
855878 None => false ,
856879 }
857880 }
858881
859- // We cannot hoist these instructions, even if we know the predicate is the same.
860- // This is because an loop with dynamic bounds may never execute its loop body.
861- // If the instruction were to trigger a failure, our program may fail inadvertently.
862- // If we know a loop's upper bound is greater than its lower bound we can hoist these instructions,
863- // but we do not want to assume that the caller of this method has accounted
864- // for this case. Thus, we block hoisting on these instructions.
865- Constrain ( ..) | ConstrainNotEqual ( ..) | RangeCheck { .. } => false ,
882+ Constrain ( ..) | ConstrainNotEqual ( ..) | RangeCheck { .. } => hoist_with_predicate,
866883
867884 // Noop instructions can always be hoisted, although they're more likely to be
868885 // removed entirely.
@@ -1723,8 +1740,8 @@ mod control_dependence {
17231740 v3 = lt v2, u32 0
17241741 jmpif v3 then: loop_body, else: exit
17251742 loop_body():
1726- v6 = mul v0, v1
1727- v7 = mul v6, v0
1743+ v6 = unchecked_mul v0, v1
1744+ v7 = unchecked_mul v6, v0
17281745 constrain v7 == u32 12
17291746 v10 = unchecked_add v2, u32 1
17301747 jmp loop(v10)
@@ -1744,8 +1761,8 @@ mod control_dependence {
17441761 let expected = "
17451762 brillig(inline) fn main f0 {
17461763 entry(v0: u32, v1: u32):
1747- v3 = mul v0, v1
1748- v4 = mul v3, v0
1764+ v3 = unchecked_mul v0, v1
1765+ v4 = unchecked_mul v3, v0
17491766 jmp loop(u32 0)
17501767 loop(v2: u32):
17511768 jmpif u1 0 then: loop_body, else: exit
@@ -1773,8 +1790,8 @@ mod control_dependence {
17731790 v3 = lt v2, u32 1
17741791 jmpif v3 then: loop_body, else: exit
17751792 loop_body():
1776- v6 = mul v0, v1
1777- v7 = mul v6, v0
1793+ v6 = unchecked_mul v0, v1
1794+ v7 = unchecked_mul v6, v0
17781795 constrain v7 == u32 12
17791796 v10 = unchecked_add v2, u32 1
17801797 jmp loop(v10)
@@ -1793,8 +1810,8 @@ mod control_dependence {
17931810 let expected = "
17941811 brillig(inline) fn main f0 {
17951812 entry(v0: u32, v1: u32):
1796- v3 = mul v0, v1
1797- v4 = mul v3, v0
1813+ v3 = unchecked_mul v0, v1
1814+ v4 = unchecked_mul v3, v0
17981815 jmp loop(u32 1)
17991816 loop(v2: u32):
18001817 v7 = eq v2, u32 0
@@ -1823,8 +1840,8 @@ mod control_dependence {
18231840 v3 = lt v2, v1
18241841 jmpif v3 then: loop_body, else: exit
18251842 loop_body():
1826- v6 = mul v0, v1
1827- v7 = mul v6, v0
1843+ v6 = unchecked_mul v0, v1
1844+ v7 = unchecked_mul v6, v0
18281845 constrain v7 == u32 12
18291846 v10 = unchecked_add v2, u32 1
18301847 jmp loop(v10)
@@ -1844,8 +1861,8 @@ mod control_dependence {
18441861 let expected = "
18451862 brillig(inline) fn main f0 {
18461863 entry(v0: u32, v1: u32):
1847- v3 = mul v0, v1
1848- v4 = mul v3, v0
1864+ v3 = unchecked_mul v0, v1
1865+ v4 = unchecked_mul v3, v0
18491866 jmp loop(u32 0)
18501867 loop(v2: u32):
18511868 v6 = lt v2, v1
@@ -1876,8 +1893,8 @@ mod control_dependence {
18761893 v3 = lt v2, v1
18771894 jmpif v3 then: loop_body, else: exit
18781895 loop_body():
1879- v6 = mul v0, v1
1880- v7 = mul v6, v0
1896+ v6 = unchecked_mul v0, v1
1897+ v7 = unchecked_mul v6, v0
18811898 call f1()
18821899 v10 = unchecked_add v2, u32 1
18831900 jmp loop(v10)
@@ -1898,8 +1915,8 @@ mod control_dependence {
18981915 assert_ssa_snapshot ! ( ssa, @r"
18991916 brillig(inline) predicate_pure fn main f0 {
19001917 b0(v0: u32, v1: u32):
1901- v3 = mul v0, v1
1902- v4 = mul v3, v0
1918+ v3 = unchecked_mul v0, v1
1919+ v4 = unchecked_mul v3, v0
19031920 jmp b1(u32 0)
19041921 b1(v2: u32):
19051922 v6 = lt v2, v1
@@ -2203,7 +2220,6 @@ mod control_dependence {
22032220 " ;
22042221
22052222 let ssa = Ssa :: from_str ( src) . unwrap ( ) ;
2206-
22072223 let ssa = ssa. loop_invariant_code_motion ( ) ;
22082224
22092225 assert_ssa_snapshot ! ( ssa, @r"
@@ -2222,4 +2238,31 @@ mod control_dependence {
22222238 }
22232239 " ) ;
22242240 }
2241+
2242+ #[ test]
2243+ fn do_not_hoist_non_control_dependent_div_in_non_executed_loop ( ) {
2244+ let src = "
2245+ brillig(inline) fn main f0 {
2246+ b0(v0: u32):
2247+ v1 = allocate -> &mut Field
2248+ store Field 0 at v1
2249+ jmp b1()
2250+ b1():
2251+ v3 = load v1 -> Field
2252+ v4 = eq v3, Field 0
2253+ jmpif v4 then: b2, else: b3
2254+ b2():
2255+ return
2256+ b3():
2257+ v6 = div u32 5, v0
2258+ jmp b1()
2259+ }
2260+ " ;
2261+
2262+ let ssa = Ssa :: from_str ( src) . unwrap ( ) ;
2263+ let ssa = ssa. loop_invariant_code_motion ( ) ;
2264+
2265+ // We expect the SSA to be unchanged
2266+ assert_normalized_ssa_equals ( ssa, src) ;
2267+ }
22252268}
0 commit comments