Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions compiler/noirc_evaluator/src/ssa/opt/mem2reg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1833,4 +1833,128 @@ mod tests {
"#;
assert_ssa_does_not_change(src, Ssa::mem2reg);
}

#[test]
fn does_not_remove_store_used_in_if_then() {
let src = "
brillig(inline) fn func f0 {
b0(v0: &mut u1, v1: u1):
v2 = allocate -> &mut u1
store v1 at v2
jmp b1()
b1():
v3 = not v1
v4 = if v1 then v0 else (if v3) v2
v6 = call f0(v4, v1) -> Field
return v6
}
";
assert_ssa_does_not_change(src, Ssa::mem2reg);
}

#[test]
fn block_argument_is_alias_of_block_parameter_1() {
// Here the last load can't be replaced with `Field 0` as v0 and v1 are aliases of one another.
let src = "
brillig(inline) impure fn main f0 {
b0():
v0 = allocate -> &mut Field
store Field 0 at v0
jmp b1(v0)
b1(v1: &mut Field):
store Field 1 at v1
v2 = load v0 -> Field
return v2
}
";
assert_ssa_does_not_change(src, Ssa::mem2reg);
}

#[test]
fn block_argument_is_alias_of_block_parameter_2() {
// Here the last load can't be replaced with `Field 1` as v0 and v1 are aliases of one another.
let src = "
brillig(inline) impure fn main f0 {
b0():
v0 = allocate -> &mut Field
store Field 0 at v0
jmp b1(v0)
b1(v1: &mut Field):
store Field 1 at v1
store Field 2 at v0
v2 = load v1 -> Field
return v2
}
";
assert_ssa_does_not_change(src, Ssa::mem2reg);
}

#[test]
fn nested_alias_in_array() {
let src = "
acir(inline) fn regression_2445_deeper_ref f2 {
b0():
v0 = allocate -> &mut Field
store Field 0 at v0
v2 = allocate -> &mut &mut Field
store v0 at v2
v3 = allocate -> &mut &mut &mut Field
store v2 at v3
v4 = make_array [v3, v3] : [&mut &mut &mut Field; 2]
v5 = allocate -> &mut [&mut &mut &mut Field; 2]
store v4 at v5
v6 = load v5 -> [&mut &mut &mut Field; 2]
v8 = array_get v6, index u32 0 -> &mut &mut &mut Field
v9 = load v8 -> &mut &mut Field
v10 = load v9 -> &mut Field
store Field 1 at v10
v12 = load v5 -> [&mut &mut &mut Field; 2]
v14 = array_get v12, index u32 1 -> &mut &mut &mut Field
v15 = load v14 -> &mut &mut Field
v16 = load v15 -> &mut Field
store Field 2 at v16
v18 = load v0 -> Field
v19 = eq v18, Field 2
constrain v18 == Field 2
v20 = load v3 -> &mut &mut Field
v21 = load v20 -> &mut Field
v22 = load v21 -> Field
v23 = eq v22, Field 2
constrain v22 == Field 2
v24 = load v5 -> [&mut &mut &mut Field; 2]
v25 = array_get v24, index u32 0 -> &mut &mut &mut Field
v26 = load v25 -> &mut &mut Field
v27 = load v26 -> &mut Field
v28 = load v27 -> Field
v29 = eq v28, Field 2
constrain v28 == Field 2
v30 = load v5 -> [&mut &mut &mut Field; 2]
v31 = array_get v30, index u32 1 -> &mut &mut &mut Field
v32 = load v31 -> &mut &mut Field
v33 = load v32 -> &mut Field
v34 = load v33 -> Field
v35 = eq v34, Field 2
constrain v34 == Field 2
return
}
";
let ssa = Ssa::from_str(src).unwrap();
let ssa = ssa.mem2reg();

// We expect the final references to both be resolved to `Field 2` and thus the constrain instructions
// will be trivially true and simplified out.
assert_ssa_snapshot!(ssa, @r"
acir(inline) fn regression_2445_deeper_ref f0 {
b0():
v0 = allocate -> &mut Field
store Field 0 at v0
v2 = allocate -> &mut &mut Field
v3 = allocate -> &mut &mut &mut Field
v4 = make_array [v3, v3] : [&mut &mut &mut Field; 2]
v5 = allocate -> &mut [&mut &mut &mut Field; 2]
store Field 1 at v0
return
}
");
}
}
109 changes: 109 additions & 0 deletions compiler/noirc_evaluator/src/ssa/opt/pure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,4 +512,113 @@ mod test {
assert_eq!(purities[&FunctionId::test_new(0)], Purity::Impure);
assert_eq!(purities[&FunctionId::test_new(1)], Purity::Impure);
}

#[test]
fn mutual_recursion_marks_functions_impure() {
// We want to test that two pure mutually recursive functions do in fact mark each other as impure
let src = r#"
acir(inline) fn main f0 {
b0():
v0 = call f1(u32 4) -> bool
return
}
acir(inline) fn is_even f1 {
b0(v0: u32):
v1 = eq v0, u32 0
jmpif v1 then: b1, else: b2
b1():
jmp b3(u1 1)
b2():
v2 = unchecked_sub v0, u32 1
v3 = call f2(v2) -> bool
jmp b3(v3)
b3(v4: bool):
return v4
}
acir(inline) fn is_odd f2 {
b0(v0: u32):
v1 = eq v0, u32 0
jmpif v1 then: b1, else: b2
b1():
jmp b3(u1 0)
b2():
v2 = unchecked_sub v0, u32 1
v3 = call f1(v2) -> bool
jmp b3(v3)
b3(v4: bool):
return v4
}
"#;

let ssa = Ssa::from_str(src).unwrap();
let ssa = ssa.purity_analysis();

let purities = &ssa.main().dfg.function_purities;
assert_eq!(purities[&FunctionId::test_new(0)], Purity::Impure);
// Impure due to mutual recursion fallback.
assert_eq!(purities[&FunctionId::test_new(1)], Purity::Impure);
assert_eq!(purities[&FunctionId::test_new(2)], Purity::Impure);
}

/// TODO(https://github.com/noir-lang/noir/issues/9444)
#[test]
fn brillig_functions_never_pure() {
let src = "
brillig(inline) fn main f0 {
b0(v0: u1):
call f1()
call f1()
return
}
brillig(inline) fn pure_basic f1 {
b0():
v2 = make_array [Field 0, Field 1] : [Field; 2]
v4 = array_get v2, index u32 1 -> Field
v5 = allocate -> &mut Field
store Field 0 at v5
return
}
";

let ssa = Ssa::from_str(src).unwrap();
let ssa = ssa.purity_analysis();

let purities = &ssa.main().dfg.function_purities;
// PureWithPredicates is the default purity for all Brillig functions.
// So even though `f1` is technically pure it will be marked as PureWithPredicates
assert_eq!(purities[&FunctionId::test_new(0)], Purity::PureWithPredicate);
assert_eq!(purities[&FunctionId::test_new(1)], Purity::PureWithPredicate);
}

#[test]
fn call_to_function_value() {
let src = r#"
acir(inline) fn main f0 {
b0(v0: u32):
v5 = make_array [f1, f2] : [function; 2]
v7 = lt v0, u32 2
constrain v7 == u1 1, "Index out of bounds"
v9 = array_get v5, index v0 -> function
call v9()
return
}
acir(inline) fn lambda f1 {
b0():
return
}
acir(inline) fn lambda f2 {
b0():
return
}"#;

let ssa = Ssa::from_str(src).unwrap();
let ssa = ssa.purity_analysis();

let purities = &ssa.main().dfg.function_purities;
// Even though the functions referenced by the function values are pure
// we assume the worse case for functions containing calls to function values.
assert_eq!(purities[&FunctionId::test_new(0)], Purity::Impure);
assert_eq!(purities[&FunctionId::test_new(1)], Purity::Pure);
assert_eq!(purities[&FunctionId::test_new(1)], Purity::Pure);
}
}
Loading
Loading