Skip to content

Commit 9bd7b3b

Browse files
guipublicaakoshh
andauthored
fix: increment ref count when hoisting instructions returning array (#11044)
Co-authored-by: Akosh Farkash <aakoshh@gmail.com>
1 parent 6ac6ec2 commit 9bd7b3b

File tree

1 file changed

+116
-29
lines changed

1 file changed

+116
-29
lines changed

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

Lines changed: 116 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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

797818
impl 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

Comments
 (0)