mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 03:42:17 +00:00
Order list-min-size tests in descending order
Some of the head-constructor tests we generate can be supersets of other tests. Edges must be ordered so that more general tests always happen after their specialized variants. For example, patterns [1, ..] -> ... [2, 1, ..] -> ... may generate the edges ListLen(>=1) -> <rest> ListLen(>=2) -> <rest> but evaluated in exactly this order, the second edge is never reachable. The necessary ordering is ListLen(>=2) -> <rest> ListLen(>=1) -> <rest> Closes #4732
This commit is contained in:
parent
a295e7ac3d
commit
a8693e6102
3 changed files with 324 additions and 31 deletions
|
@ -51,12 +51,14 @@ impl<'a> Guard<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
type Edge<'a> = (GuardedTest<'a>, DecisionTree<'a>);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum DecisionTree<'a> {
|
||||
Match(Label),
|
||||
Decision {
|
||||
path: Vec<PathInstruction>,
|
||||
edges: Vec<(GuardedTest<'a>, DecisionTree<'a>)>,
|
||||
edges: Vec<Edge<'a>>,
|
||||
default: Option<Box<DecisionTree<'a>>>,
|
||||
},
|
||||
}
|
||||
|
@ -1596,6 +1598,7 @@ fn test_to_comparison<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Comparator {
|
||||
Eq,
|
||||
Geq,
|
||||
|
@ -2168,6 +2171,69 @@ fn test_always_succeeds(test: &Test) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
fn sort_edge_tests_by_priority(edges: &mut Vec<Edge<'_>>) {
|
||||
use std::cmp::{Ordering, Ordering::*};
|
||||
use GuardedTest::*;
|
||||
edges.sort_by(|(t1, _), (t2, _)| match (t1, t2) {
|
||||
// Guarded takes priority
|
||||
(GuardedNoTest { .. }, GuardedNoTest { .. }) => Equal,
|
||||
(GuardedNoTest { .. }, TestNotGuarded { .. }) | (GuardedNoTest { .. }, Placeholder) => Less,
|
||||
// Interesting case: what test do we pick?
|
||||
(TestNotGuarded { test: t1 }, TestNotGuarded { test: t2 }) => order_tests(t1, t2),
|
||||
// Otherwise we are between guarded and fall-backs
|
||||
(TestNotGuarded { .. }, GuardedNoTest { .. }) => Greater,
|
||||
(TestNotGuarded { .. }, Placeholder) => Less,
|
||||
// Placeholder is always last
|
||||
(Placeholder, Placeholder) => Equal,
|
||||
(Placeholder, GuardedNoTest { .. }) | (Placeholder, TestNotGuarded { .. }) => Greater,
|
||||
});
|
||||
|
||||
fn order_tests(t1: &Test, t2: &Test) -> Ordering {
|
||||
match (t1, t2) {
|
||||
(
|
||||
Test::IsListLen {
|
||||
bound: bound_l,
|
||||
len: l,
|
||||
},
|
||||
Test::IsListLen {
|
||||
bound: bound_m,
|
||||
len: m,
|
||||
},
|
||||
) => {
|
||||
// List tests can either check for
|
||||
// - exact length (= l)
|
||||
// - a size greater or equal to a given length (>= l)
|
||||
// (>= l) tests can be superset of other tests
|
||||
// - (>= m) where m > l
|
||||
// - (= m)
|
||||
// So, if m > l, we enforce the following order for list tests
|
||||
// (>= m) then (= l) then (>= l)
|
||||
match m.cmp(l) {
|
||||
Less => Less, // (>= m) then (>= l)
|
||||
Greater => Greater,
|
||||
|
||||
Equal => {
|
||||
use ListLenBound::*;
|
||||
match (bound_l, bound_m) {
|
||||
(Exact, AtLeast) => Less, // (= l) then (>= l)
|
||||
(AtLeast, Exact) => Greater,
|
||||
|
||||
(AtLeast, AtLeast) | (Exact, Exact) => Equal,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(Test::IsListLen { .. }, t) | (t, Test::IsListLen { .. }) => internal_error!(
|
||||
"list-length tests should never pair with another test {t:?} at the same level"
|
||||
),
|
||||
// We don't care about anything other than list-length tests, since all other tests
|
||||
// should be disjoint.
|
||||
_ => Equal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tree_to_decider(tree: DecisionTree) -> Decider<u64> {
|
||||
use Decider::*;
|
||||
use DecisionTree::*;
|
||||
|
@ -2179,44 +2245,67 @@ fn tree_to_decider(tree: DecisionTree) -> Decider<u64> {
|
|||
path,
|
||||
mut edges,
|
||||
default,
|
||||
} => match default {
|
||||
None => match edges.len() {
|
||||
0 => panic!("compiler bug, somehow created an empty decision tree"),
|
||||
1 => {
|
||||
let (_, sub_tree) = edges.remove(0);
|
||||
} => {
|
||||
// Some of the head-constructor tests we generate can be supersets of other tests.
|
||||
// Edges must be ordered so that more general tests always happen after their
|
||||
// specialized variants.
|
||||
//
|
||||
// For example, patterns
|
||||
//
|
||||
// [1, ..] -> ...
|
||||
// [2, 1, ..] -> ...
|
||||
//
|
||||
// may generate the edges
|
||||
//
|
||||
// ListLen(>=1) -> <rest>
|
||||
// ListLen(>=2) -> <rest>
|
||||
//
|
||||
// but evaluated in exactly this order, the second edge is never reachable.
|
||||
// The necessary ordering is
|
||||
//
|
||||
// ListLen(>=2) -> <rest>
|
||||
// ListLen(>=1) -> <rest>
|
||||
sort_edge_tests_by_priority(&mut edges);
|
||||
|
||||
tree_to_decider(sub_tree)
|
||||
}
|
||||
2 => {
|
||||
let (_, failure_tree) = edges.remove(1);
|
||||
let (guarded_test, success_tree) = edges.remove(0);
|
||||
match default {
|
||||
None => match edges.len() {
|
||||
0 => panic!("compiler bug, somehow created an empty decision tree"),
|
||||
1 => {
|
||||
let (_, sub_tree) = edges.remove(0);
|
||||
|
||||
chain_decider(path, guarded_test, failure_tree, success_tree)
|
||||
}
|
||||
tree_to_decider(sub_tree)
|
||||
}
|
||||
2 => {
|
||||
let (_, failure_tree) = edges.remove(1);
|
||||
let (guarded_test, success_tree) = edges.remove(0);
|
||||
|
||||
_ => {
|
||||
let fallback = edges.remove(edges.len() - 1).1;
|
||||
chain_decider(path, guarded_test, failure_tree, success_tree)
|
||||
}
|
||||
|
||||
fanout_decider(path, fallback, edges)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let fallback = edges.remove(edges.len() - 1).1;
|
||||
|
||||
Some(last) => match edges.len() {
|
||||
0 => tree_to_decider(*last),
|
||||
1 => {
|
||||
let failure_tree = *last;
|
||||
let (guarded_test, success_tree) = edges.remove(0);
|
||||
fanout_decider(path, fallback, edges)
|
||||
}
|
||||
},
|
||||
|
||||
chain_decider(path, guarded_test, failure_tree, success_tree)
|
||||
}
|
||||
Some(last) => match edges.len() {
|
||||
0 => tree_to_decider(*last),
|
||||
1 => {
|
||||
let failure_tree = *last;
|
||||
let (guarded_test, success_tree) = edges.remove(0);
|
||||
|
||||
_ => {
|
||||
let fallback = *last;
|
||||
chain_decider(path, guarded_test, failure_tree, success_tree)
|
||||
}
|
||||
|
||||
fanout_decider(path, fallback, edges)
|
||||
}
|
||||
},
|
||||
},
|
||||
_ => {
|
||||
let fallback = *last;
|
||||
|
||||
fanout_decider(path, fallback, edges)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
190
crates/compiler/test_mono/generated/issue_4732.txt
Normal file
190
crates/compiler/test_mono/generated/issue_4732.txt
Normal file
|
@ -0,0 +1,190 @@
|
|||
procedure Test.0 ():
|
||||
let Test.1 : List I64 = Array [];
|
||||
joinpoint Test.10:
|
||||
let Test.6 : Str = "Catchall";
|
||||
ret Test.6;
|
||||
in
|
||||
joinpoint Test.9:
|
||||
let Test.4 : Str = "B3";
|
||||
ret Test.4;
|
||||
in
|
||||
joinpoint Test.8:
|
||||
let Test.3 : Str = "B2";
|
||||
ret Test.3;
|
||||
in
|
||||
joinpoint Test.7:
|
||||
let Test.2 : Str = "B1";
|
||||
ret Test.2;
|
||||
in
|
||||
let Test.73 : U64 = lowlevel ListLen Test.1;
|
||||
let Test.74 : U64 = 4i64;
|
||||
let Test.75 : Int1 = lowlevel NumGte Test.73 Test.74;
|
||||
if Test.75 then
|
||||
let Test.11 : U64 = 0i64;
|
||||
let Test.12 : I64 = lowlevel ListGetUnsafe Test.1 Test.11;
|
||||
switch Test.12:
|
||||
case 1:
|
||||
dec Test.1;
|
||||
jump Test.7;
|
||||
|
||||
case 2:
|
||||
let Test.13 : U64 = 1i64;
|
||||
let Test.14 : I64 = lowlevel ListGetUnsafe Test.1 Test.13;
|
||||
dec Test.1;
|
||||
let Test.15 : I64 = 1i64;
|
||||
let Test.16 : Int1 = lowlevel Eq Test.15 Test.14;
|
||||
if Test.16 then
|
||||
jump Test.8;
|
||||
else
|
||||
jump Test.10;
|
||||
|
||||
case 3:
|
||||
joinpoint Test.23:
|
||||
jump Test.10;
|
||||
in
|
||||
let Test.20 : U64 = 2i64;
|
||||
let Test.21 : I64 = lowlevel ListGetUnsafe Test.1 Test.20;
|
||||
let Test.22 : I64 = 1i64;
|
||||
let Test.25 : Int1 = lowlevel Eq Test.22 Test.21;
|
||||
if Test.25 then
|
||||
let Test.17 : U64 = 1i64;
|
||||
let Test.18 : I64 = lowlevel ListGetUnsafe Test.1 Test.17;
|
||||
dec Test.1;
|
||||
let Test.19 : I64 = 2i64;
|
||||
let Test.24 : Int1 = lowlevel Eq Test.19 Test.18;
|
||||
if Test.24 then
|
||||
jump Test.9;
|
||||
else
|
||||
jump Test.23;
|
||||
else
|
||||
dec Test.1;
|
||||
jump Test.23;
|
||||
|
||||
case 4:
|
||||
joinpoint Test.35:
|
||||
jump Test.10;
|
||||
in
|
||||
let Test.32 : U64 = 3i64;
|
||||
let Test.33 : I64 = lowlevel ListGetUnsafe Test.1 Test.32;
|
||||
let Test.34 : I64 = 1i64;
|
||||
let Test.38 : Int1 = lowlevel Eq Test.34 Test.33;
|
||||
if Test.38 then
|
||||
let Test.29 : U64 = 2i64;
|
||||
let Test.30 : I64 = lowlevel ListGetUnsafe Test.1 Test.29;
|
||||
let Test.31 : I64 = 2i64;
|
||||
let Test.37 : Int1 = lowlevel Eq Test.31 Test.30;
|
||||
if Test.37 then
|
||||
let Test.26 : U64 = 1i64;
|
||||
let Test.27 : I64 = lowlevel ListGetUnsafe Test.1 Test.26;
|
||||
dec Test.1;
|
||||
let Test.28 : I64 = 3i64;
|
||||
let Test.36 : Int1 = lowlevel Eq Test.28 Test.27;
|
||||
if Test.36 then
|
||||
let Test.5 : Str = "B4";
|
||||
ret Test.5;
|
||||
else
|
||||
jump Test.35;
|
||||
else
|
||||
dec Test.1;
|
||||
jump Test.35;
|
||||
else
|
||||
dec Test.1;
|
||||
jump Test.35;
|
||||
|
||||
default:
|
||||
dec Test.1;
|
||||
jump Test.10;
|
||||
|
||||
else
|
||||
let Test.70 : U64 = lowlevel ListLen Test.1;
|
||||
let Test.71 : U64 = 3i64;
|
||||
let Test.72 : Int1 = lowlevel NumGte Test.70 Test.71;
|
||||
if Test.72 then
|
||||
let Test.39 : U64 = 0i64;
|
||||
let Test.40 : I64 = lowlevel ListGetUnsafe Test.1 Test.39;
|
||||
switch Test.40:
|
||||
case 1:
|
||||
dec Test.1;
|
||||
jump Test.7;
|
||||
|
||||
case 2:
|
||||
let Test.41 : U64 = 1i64;
|
||||
let Test.42 : I64 = lowlevel ListGetUnsafe Test.1 Test.41;
|
||||
dec Test.1;
|
||||
let Test.43 : I64 = 1i64;
|
||||
let Test.44 : Int1 = lowlevel Eq Test.43 Test.42;
|
||||
if Test.44 then
|
||||
jump Test.8;
|
||||
else
|
||||
jump Test.10;
|
||||
|
||||
case 3:
|
||||
joinpoint Test.51:
|
||||
jump Test.10;
|
||||
in
|
||||
let Test.48 : U64 = 2i64;
|
||||
let Test.49 : I64 = lowlevel ListGetUnsafe Test.1 Test.48;
|
||||
let Test.50 : I64 = 1i64;
|
||||
let Test.53 : Int1 = lowlevel Eq Test.50 Test.49;
|
||||
if Test.53 then
|
||||
let Test.45 : U64 = 1i64;
|
||||
let Test.46 : I64 = lowlevel ListGetUnsafe Test.1 Test.45;
|
||||
dec Test.1;
|
||||
let Test.47 : I64 = 2i64;
|
||||
let Test.52 : Int1 = lowlevel Eq Test.47 Test.46;
|
||||
if Test.52 then
|
||||
jump Test.9;
|
||||
else
|
||||
jump Test.51;
|
||||
else
|
||||
dec Test.1;
|
||||
jump Test.51;
|
||||
|
||||
default:
|
||||
dec Test.1;
|
||||
jump Test.10;
|
||||
|
||||
else
|
||||
let Test.67 : U64 = lowlevel ListLen Test.1;
|
||||
let Test.68 : U64 = 2i64;
|
||||
let Test.69 : Int1 = lowlevel NumGte Test.67 Test.68;
|
||||
if Test.69 then
|
||||
let Test.54 : U64 = 0i64;
|
||||
let Test.55 : I64 = lowlevel ListGetUnsafe Test.1 Test.54;
|
||||
switch Test.55:
|
||||
case 1:
|
||||
dec Test.1;
|
||||
jump Test.7;
|
||||
|
||||
case 2:
|
||||
let Test.56 : U64 = 1i64;
|
||||
let Test.57 : I64 = lowlevel ListGetUnsafe Test.1 Test.56;
|
||||
dec Test.1;
|
||||
let Test.58 : I64 = 1i64;
|
||||
let Test.59 : Int1 = lowlevel Eq Test.58 Test.57;
|
||||
if Test.59 then
|
||||
jump Test.8;
|
||||
else
|
||||
jump Test.10;
|
||||
|
||||
default:
|
||||
dec Test.1;
|
||||
jump Test.10;
|
||||
|
||||
else
|
||||
let Test.64 : U64 = lowlevel ListLen Test.1;
|
||||
let Test.65 : U64 = 1i64;
|
||||
let Test.66 : Int1 = lowlevel NumGte Test.64 Test.65;
|
||||
if Test.66 then
|
||||
let Test.60 : U64 = 0i64;
|
||||
let Test.61 : I64 = lowlevel ListGetUnsafe Test.1 Test.60;
|
||||
dec Test.1;
|
||||
let Test.62 : I64 = 1i64;
|
||||
let Test.63 : Int1 = lowlevel Eq Test.62 Test.61;
|
||||
if Test.63 then
|
||||
jump Test.7;
|
||||
else
|
||||
jump Test.10;
|
||||
else
|
||||
dec Test.1;
|
||||
jump Test.10;
|
|
@ -2220,3 +2220,17 @@ fn lambda_set_with_imported_toplevels_issue_4733() {
|
|||
"###
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn order_list_size_tests_issue_4732() {
|
||||
indoc!(
|
||||
r###"
|
||||
when [] is
|
||||
[1, ..] -> "B1"
|
||||
[2, 1, ..] -> "B2"
|
||||
[3, 2, 1, ..] -> "B3"
|
||||
[4, 3, 2, 1, ..] -> "B4"
|
||||
_ -> "Catchall"
|
||||
"###
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue