When analyzing a Call instruction, whenever an argument that is a reference type is found, we invalidate data related to it:
|
Instruction::Call { .. } => { |
|
// Simple reference (`&mut T` where T has no refs): invalidate that |
|
// address and all its potential aliases. |
|
// Container or nested reference: clear all state. |
|
instruction.for_each_value(|value| { |
|
let value = inserter.resolve(value); |
|
let typ = inserter.function.dfg.type_of_value(value); |
|
let is_simple_ref = matches!(typ.reference_element_type(), Some(inner) if !inner.contains_reference()); |
|
if is_simple_ref { |
|
let dfg = &inserter.function.dfg; |
|
known_values |
|
.retain(|k, _| !may_alias(value, *k, allocations, dfg)); |
|
last_loads |
|
.retain(|k, _| !may_alias(value, *k, allocations, dfg)); |
|
last_stores |
|
.retain(|k, _| !may_alias(value, *k, allocations, dfg)); |
|
} else if typ.contains_reference() { |
|
known_values.clear(); |
|
last_loads.clear(); |
|
last_stores.clear(); |
|
} |
|
}); |
However, this should only be necessary if the argument's type contains any mutable reference. If the type only has immutable references there's no way an alias of that argument will change because of the call.
For example this test should pass but currently doesn't:
#[test]
fn call_with_immutable_reference_does_not_invalidate_cache() {
// A call that only receives an immutable reference cannot write through
// it, so cached values for that address must remain valid after the call.
// Currently the pass treats &T the same as &mut T in the call handler
// (is_simple_ref fires for both), so it incorrectly invalidates the
// cached load and fails to forward v2 to v1.
let src = "
brillig(inline) fn main f0 {
b0(v0: &Field):
v1 = load v0 -> Field
call f1(v0)
v2 = load v0 -> Field
return v2
}
brillig(inline) fn f1 f1 {
b0(v0: &Field):
return
}
";
let ssa = Ssa::from_str(src).unwrap();
let ssa = ssa.load_store_forwarding();
// The call only holds an immutable reference; it cannot modify v0's
// memory. The second load should be forwarded to v1.
assert_ssa_snapshot!(ssa, @r"
brillig(inline) fn main f0 {
b0(v0: &Field):
v1 = load v0 -> Field
call f1(v0)
return v1
}
brillig(inline) fn f1 f1 {
b0(v0: &Field):
return
}
");
}
This is just an optimization, though.
When analyzing a Call instruction, whenever an argument that is a reference type is found, we invalidate data related to it:
noir/compiler/noirc_evaluator/src/ssa/opt/load_store_forwarding.rs
Lines 201 to 222 in 1d77f5a
However, this should only be necessary if the argument's type contains any mutable reference. If the type only has immutable references there's no way an alias of that argument will change because of the call.
For example this test should pass but currently doesn't:
This is just an optimization, though.