@@ -428,21 +428,24 @@ impl<'f> LoopInvariantContext<'f> {
428428 if hoist_invariant {
429429 self . inserter . push_instruction ( instruction_id, pre_header, false ) ;
430430
431- // If we are hoisting a MakeArray instruction ,
432- // we need to issue an extra inc_rc in case they are mutated afterward.
431+ // If we are hoisting an instruction which returns a new array ,
432+ // we need to issue an extra inc_rc in case it is mutated afterward.
433433 if insert_rc {
434- let [ result] =
435- self . inserter . function . dfg . instruction_result ( instruction_id) ;
436- let inc_rc = Instruction :: IncrementRc { value : result } ;
437- let call_stack = self
438- . inserter
439- . function
440- . dfg
441- . get_instruction_call_stack_id ( instruction_id) ;
442- self . inserter
443- . function
444- . dfg
445- . insert_instruction_and_results ( inc_rc, * block, None , call_stack) ;
434+ let dfg = & self . inserter . function . dfg ;
435+ let results: Vec < _ > = dfg
436+ . instruction_results ( instruction_id)
437+ . iter ( )
438+ . filter ( |result| dfg. type_of_value ( * * result) . is_array ( ) )
439+ . copied ( )
440+ . collect ( ) ;
441+ let call_stack = dfg. get_instruction_call_stack_id ( instruction_id) ;
442+ for result in results {
443+ let inc_rc = Instruction :: IncrementRc { value : result } ;
444+ self . inserter
445+ . function
446+ . dfg
447+ . insert_instruction_and_results ( inc_rc, * block, None , call_stack) ;
448+ }
446449 }
447450 } else {
448451 let dfg = & self . inserter . function . dfg ;
@@ -685,17 +688,36 @@ impl<'f> LoopInvariantContext<'f> {
685688 return ( false , false ) ;
686689 }
687690
691+ // In Brillig, if the instruction creates a new array, we need to insert an inc_rc
692+ // to handle potential mutations.
693+ let dfg = & self . inserter . function . dfg ;
694+ let returns_array = if dfg. runtime ( ) . is_brillig ( ) {
695+ // Add inc_rc for instructions that create new arrays
696+ match & instruction {
697+ Instruction :: MakeArray { .. } => true ,
698+ Instruction :: Call { .. } => {
699+ let results = dfg. instruction_results ( instruction_id) ;
700+ results. iter ( ) . any ( |r| dfg. type_of_value ( * r) . is_array ( ) )
701+ }
702+ // RefCount for ArrayGet and ArraySet is managed by the ownership analysis.
703+ _ => false ,
704+ }
705+ } else {
706+ false
707+ } ;
708+
688709 // Check if the operation depends only on the outer loop variable, in which case it can be hoisted
689710 // into the pre-header of a nested loop even if the nested loop does not execute.
690711 if self . can_be_hoisted_from_loop_bounds ( loop_context, & instruction) {
691- return ( true , false ) ;
712+ return ( true , returns_array ) ;
692713 }
693714
694- match can_be_hoisted ( & instruction, & self . inserter . function . dfg ) {
695- Yes => ( true , false ) ,
715+ match can_be_hoisted ( & instruction, dfg) {
716+ Yes => ( true , returns_array ) ,
696717 No => ( false , false ) ,
697- WithRefCount => ( true , true ) ,
698- WithPredicate => ( block_context. can_hoist_control_dependent_instruction ( ) , false ) ,
718+ WithPredicate => {
719+ ( block_context. can_hoist_control_dependent_instruction ( ) , returns_array)
720+ }
699721 }
700722 }
701723
@@ -791,7 +813,6 @@ enum CanBeHoistedResult {
791813 Yes ,
792814 No ,
793815 WithPredicate ,
794- WithRefCount ,
795816}
796817
797818impl From < bool > for CanBeHoistedResult {
@@ -867,13 +888,7 @@ fn can_be_hoisted(instruction: &Instruction, dfg: &DataFlowGraph) -> CanBeHoiste
867888 // Arrays can be mutated in unconstrained code so code that handles this case must
868889 // take care to track whether the array was possibly mutated or not before hoisted.
869890 // An ACIR it is always safe to hoist MakeArray.
870- MakeArray { .. } => {
871- if dfg. runtime ( ) . is_acir ( ) {
872- Yes
873- } else {
874- WithRefCount
875- }
876- }
891+ MakeArray { .. } => Yes ,
877892
878893 // These can have different behavior depending on the predicate.
879894 Binary ( _) | ArraySet { .. } | ArrayGet { .. } => {
@@ -2411,8 +2426,8 @@ mod tests {
24112426 assert_ssa_does_not_change ( src, Ssa :: loop_invariant_code_motion) ;
24122427 }
24132428
2414- /// Test that in itself `MakeArray` is only safe to be hoisted in ACIR.
2415- #[ test_case( RuntimeType :: Brillig ( InlineType :: default ( ) ) , CanBeHoistedResult :: WithRefCount ) ]
2429+ /// Test that `MakeArray` can be hoisted in both ACIR and Brillig .
2430+ #[ test_case( RuntimeType :: Brillig ( InlineType :: default ( ) ) , CanBeHoistedResult :: Yes ) ]
24162431 #[ test_case( RuntimeType :: Acir ( InlineType :: default ( ) ) , CanBeHoistedResult :: Yes ) ]
24172432 fn make_array_can_be_hoisted ( runtime : RuntimeType , result : CanBeHoistedResult ) {
24182433 // This is just a stub to create a function with the expected runtime.
@@ -3612,4 +3627,76 @@ mod control_dependence {
36123627 ) ;
36133628 assert_ssa_does_not_change ( & src, Ssa :: loop_invariant_code_motion) ;
36143629 }
3630+
3631+ #[ test]
3632+ /// Checks that hoisting a function call returning an array adds an inc_rc operation
3633+ fn call_func_call_inc_rc ( ) {
3634+ let src = r#"
3635+ brillig(inline) impure fn main f0 {
3636+ b0():
3637+ v2 = make_array [u8 0, u8 0, u8 0, u8 0] : [u8; 4]
3638+ v3 = allocate -> &mut [u8; 4]
3639+ store v2 at v3
3640+ jmp b1(u32 0)
3641+ b1(v0: u32):
3642+ v6 = lt v0, u32 10
3643+ jmpif v6 then: b2, else: b3
3644+ b2():
3645+ v10, v11 = call f1() -> (u8, [u8; 4])
3646+ store v11 at v3
3647+ v13 = unchecked_add v0, u32 1
3648+ jmp b1(v13)
3649+ b3():
3650+ v7 = load v3 -> [u8; 4]
3651+ v9 = call black_box(v7) -> [u8; 4]
3652+ return
3653+ }
3654+ brillig(inline) predicate_pure fn ret_arr f1 {
3655+ b0():
3656+ v4 = make_array [u8 0, u8 1, u8 2, u8 3] : [u8; 4]
3657+ return u8 7, v4
3658+ }
3659+ "# ;
3660+ let ssa = Ssa :: from_str ( src) . unwrap ( ) ;
3661+ let ssa = ssa. loop_invariant_code_motion ( ) ;
3662+ let ssa_string = ssa. to_string ( ) ;
3663+ assert ! ( ssa_string. contains( "inc_rc" ) , "failed on call f1() -> (u8, [u8; 4])" ) ;
3664+ }
3665+
3666+ #[ test]
3667+ /// Validates that the pass does not add an inc_rc operation for ArrayGet
3668+ fn array_get_does_not_add_inc_rc ( ) {
3669+ let src = r#"
3670+ g0 = make_array [Field 1, Field 2] : [Field; 2]
3671+ g1 = make_array [Field 3, Field 4] : [Field; 2]
3672+ g2 = make_array [g0, g1] : [[Field; 2]; 2]
3673+
3674+ brillig(inline) fn main f0 {
3675+ b0():
3676+ v3 = allocate -> &mut Field
3677+ store Field 0 at v3
3678+ jmp b1(u32 0)
3679+ b1(v0: u32):
3680+ v6 = lt v0, u32 2
3681+ jmpif v6 then: b2, else: b3
3682+ b2():
3683+ v8 = array_get g2, index v0 -> [Field; 2]
3684+ v9 = array_get v8, index u32 0 -> Field
3685+ v10 = load v3 -> Field
3686+ v11 = add v10, v9
3687+ store v11 at v3
3688+ v12 = unchecked_add v0, u32 1
3689+ jmp b1(v12)
3690+ b3():
3691+ v7 = load v3 -> Field
3692+ return v7
3693+ }
3694+ "# ;
3695+ let ssa = Ssa :: from_str ( src) . unwrap ( ) ;
3696+ let ssa = ssa. loop_invariant_code_motion ( ) ;
3697+ let ssa_string = ssa. to_string ( ) ;
3698+ // ArrayGet should not add inc_rc since it extracts from an existing array
3699+ // rather than creating a new one
3700+ assert ! ( !ssa_string. contains( "inc_rc" ) , "ArrayGet should not add inc_rc" ) ;
3701+ }
36153702}
0 commit comments