Skip to content
Merged
Changes from all commits
Commits
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
172 changes: 171 additions & 1 deletion compiler/noirc_evaluator/src/ssa/ir/post_order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,14 @@ impl PostOrder {
mod tests {
use crate::ssa::{
function_builder::FunctionBuilder,
ir::{function::Function, map::Id, post_order::PostOrder, types::Type},
ir::{
basic_block::BasicBlockId,
function::Function,
map::Id,
post_order::PostOrder,
types::{NumericType, Type},
},
ssa_gen::Ssa,
};

#[test]
Expand Down Expand Up @@ -170,4 +177,167 @@ mod tests {
let block_a_id = func.entry_block();
assert_eq!(post_order.0, [block_d_id, block_f_id, block_e_id, block_b_id, block_a_id]);
}

/// Helper to construct a BasicBlockId with a syntax resembling the `b0`
/// syntax used in comments/ssa output.
fn b(id: u32) -> BasicBlockId {
BasicBlockId::test_new(id)
}

/// Documents the somewhat odd behavior from https://github.com/noir-lang/noir/issues/9771
#[test]
fn loop_regression() {
// b0 -> b1 <-> b2
// |
// V
// b3
let mut builder = FunctionBuilder::new("func".into(), Id::test_new(0));
let b0 = builder.current_block();
let b1 = builder.insert_block();
let b2 = builder.insert_block();
let b3 = builder.insert_block();

// This needs to use the FunctionBuilder since the Ssa parser will change the block ids
builder.terminate_with_jmp(b1, Vec::new());

builder.switch_to_block(b1);
let zero = builder.numeric_constant(0u32, NumericType::bool());
builder.terminate_with_jmpif(zero, b2, b3);

builder.switch_to_block(b2);
builder.terminate_with_jmp(b1, Vec::new());

builder.switch_to_block(b3);
builder.terminate_with_return(Vec::new());
let ssa = builder.finish();

let func = ssa.main();
let post_order = PostOrder::with_function(func);

// [3, 2, 1, 0] would be the ideal but we currently get the following:
assert_eq!(post_order.0, [b2, b3, b1, b0]);
}

#[test]
fn simple_if() {
let src = "
acir(inline) fn factorial f1 {
b0(v1: u32):
v2 = lt v1, u32 1
jmpif v2 then: b1, else: b2
b1():
jmp b3(u32 1)
b2():
v4 = sub v1, u32 1
v5 = call f1(v4) -> u32
v6 = mul v1, v5
jmp b3(v6)
b3(v7: u32):
return v7
}";

let ssa = Ssa::from_str(src).unwrap();
let func = ssa.main();
let post_order = PostOrder::with_function(func);
// Ordering of b1, b2 is arbitrary
assert_eq!(post_order.0, [b(3), b(1), b(2), b(0)]);
}

#[test]
fn nested_loop() {
// b0 -> b1 -> b3
// / ^
// V \
// b2 |
// | |
// V |
// b4->b6
// | ^
// V \
// b5->b8
// | ^
// V /
// b7
//
// Expected topological sort:
// [b0, {loop blocks}, b3] (see below for more complete explanation)
let src = "
acir(inline) fn main f0 {
b0(v0: u1):
jmp b1(v0)
b1(v1: u1):
jmpif v1 then: b2, else: b3
b2():
jmp b4(v1)
b3():
return
b4(v2: u1):
jmpif v2 then: b5, else: b6
b5():
jmpif v2 then: b7, else: b8
b6():
jmp b1(v1)
b7():
jmp b8()
b8():
jmp b4(v1)
}
";

// We can break the CFG above into CFG's for each loop where the start
// node is where the loop was entered from. Break all incoming edges
// from this node, and remove outgoing edges that lead out of the loop:
//
// b1
// /
// V
// b2
// |
// V
// b4->b6
// | ^
// V \
// b5->b8
// | ^
// V /
// b7
//
// Here the expected topological sort is more clear:
// [ b1, b2, { loop blocks }, b6 ]
//
// We can do this again for the inner loop starting from `b4`:
//
// b4
// |
// V
// b5->b8
// | ^
// V /
// b7
//
// Where the topological sort for this is unambiguously
// [b4, b5, b7, b8]
//
// Now we can slot this in to the unknown {loop blocks} from the first loop's
// topological sort to get:
//
// [b1, b2, b4, b5, b7, b8, b6]
//
// And finally to the original program to get:
//
// [b0, b1, b2, b4, b5, b7, b8, b6, b3]
//
// And the expected post-order is simply the reverse of this topological ordering:
//
// [b3, b6, b8, b7, b5, b4, b2, b1, b0]
//
// But we currently get:
let expected_post_order = [b(8), b(7), b(5), b(6), b(4), b(2), b(3), b(1), b(0)];

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

let func = ssa.main();
let post_order = PostOrder::with_function(func);
assert_eq!(post_order.0, expected_post_order);
}
}
Loading