Merge pull request #5202 from roc-lang/i5176

Compilation of when expressions with redundant branches and guards
This commit is contained in:
Ayaz 2023-03-25 20:02:22 -05:00 committed by GitHub
commit eadbb4eddd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 2352 additions and 1908 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,8 @@
use super::pattern::{build_list_index_probe, store_pattern, DestructType, ListIndex, Pattern};
use crate::borrow::Ownership;
use crate::ir::{
build_list_index_probe, BranchInfo, Call, CallType, DestructType, Env, Expr, JoinPointId,
ListIndex, Literal, Param, Pattern, Procs, Stmt,
substitute_in_exprs_many, BranchInfo, Call, CallType, CompiledGuardStmt, Env, Expr,
GuardStmtSpec, JoinPointId, Literal, Param, Procs, Stmt,
};
use crate::layout::{
Builtin, InLayout, Layout, LayoutCache, LayoutInterner, TLLayoutInterner, TagIdIntType,
@ -9,6 +10,7 @@ use crate::layout::{
};
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet};
use roc_collections::BumpMap;
use roc_error_macros::internal_error;
use roc_exhaustive::{Ctor, CtorName, ListArity, RenderAs, TagId, Union};
use roc_module::ident::TagName;
@ -42,14 +44,13 @@ fn compile<'a>(
}
#[derive(Clone, Debug, PartialEq)]
pub enum Guard<'a> {
pub(crate) enum Guard<'a> {
NoGuard,
Guard {
/// pattern
pattern: Pattern<'a>,
/// after assigning to symbol, the stmt jumps to this label
id: JoinPointId,
stmt: Stmt<'a>,
/// How to compile the guard statement.
stmt_spec: GuardStmtSpec,
},
}
@ -57,6 +58,10 @@ impl<'a> Guard<'a> {
fn is_none(&self) -> bool {
self == &Guard::NoGuard
}
fn is_some(&self) -> bool {
!self.is_none()
}
}
type Edge<'a> = (GuardedTest<'a>, DecisionTree<'a>);
@ -73,19 +78,19 @@ enum DecisionTree<'a> {
#[derive(Clone, Debug, PartialEq)]
enum GuardedTest<'a> {
// e.g. `_ if True -> ...`
// e.g. `x if True -> ...`
GuardedNoTest {
/// pattern
pattern: Pattern<'a>,
/// after assigning to symbol, the stmt jumps to this label
id: JoinPointId,
/// body
stmt: Stmt<'a>,
/// How to compile the guard body.
stmt_spec: GuardStmtSpec,
},
// e.g. `<pattern> -> ...`
TestNotGuarded {
test: Test<'a>,
},
Placeholder,
// e.g. `_ -> ...` or `x -> ...`
PlaceholderWithGuard,
}
#[derive(Clone, Copy, Debug, PartialEq, Hash)]
@ -188,15 +193,15 @@ impl<'a> Hash for Test<'a> {
impl<'a> Hash for GuardedTest<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
match self {
GuardedTest::GuardedNoTest { id, .. } => {
GuardedTest::GuardedNoTest { stmt_spec, .. } => {
state.write_u8(1);
id.hash(state);
stmt_spec.hash(state);
}
GuardedTest::TestNotGuarded { test } => {
state.write_u8(0);
test.hash(state);
}
GuardedTest::Placeholder => {
GuardedTest::PlaceholderWithGuard => {
state.write_u8(2);
}
}
@ -232,8 +237,8 @@ fn to_decision_tree<'a>(
match first.guard {
Guard::NoGuard => unreachable!(),
Guard::Guard { id, stmt, pattern } => {
let guarded_test = GuardedTest::GuardedNoTest { id, stmt, pattern };
Guard::Guard { pattern, stmt_spec } => {
let guarded_test = GuardedTest::GuardedNoTest { pattern, stmt_spec };
// the guard test does not have a path
let path = vec![];
@ -264,6 +269,7 @@ fn to_decision_tree<'a>(
let path = pick_path(&branches).clone();
let bs = branches.clone();
let (edges, fallback) = gather_edges(interner, branches, &path);
let mut decision_edges: Vec<_> = edges
@ -308,7 +314,7 @@ fn break_out_guard<'a>(
) -> DecisionTree<'a> {
match edges
.iter()
.position(|(t, _)| matches!(t, GuardedTest::Placeholder))
.position(|(t, _)| matches!(t, GuardedTest::PlaceholderWithGuard))
{
None => DecisionTree::Decision {
path,
@ -347,7 +353,7 @@ fn guarded_tests_are_complete(tests: &[GuardedTest]) -> bool {
.all(|t| matches!(t, GuardedTest::TestNotGuarded { .. }));
match tests.last().unwrap() {
GuardedTest::Placeholder => false,
GuardedTest::PlaceholderWithGuard => false,
GuardedTest::GuardedNoTest { .. } => false,
GuardedTest::TestNotGuarded { test } => no_guard && tests_are_complete_help(test, length),
}
@ -687,7 +693,7 @@ fn test_at_path<'a>(
if let Guard::Guard { .. } = &branch.guard {
// no tests for this pattern remain, but we cannot discard it yet
// because it has a guard!
Some(GuardedTest::Placeholder)
Some(GuardedTest::PlaceholderWithGuard)
} else {
None
}
@ -709,10 +715,33 @@ fn edges_for<'a>(
// if we test for a guard, skip all branches until one that has a guard
let it = match test {
GuardedTest::GuardedNoTest { .. } | GuardedTest::Placeholder => {
GuardedTest::GuardedNoTest { .. } => {
let index = branches
.iter()
.position(|b| !b.guard.is_none())
.position(|b| b.guard.is_some())
.expect("if testing for a guard, one branch must have a guard");
branches[index..].iter()
}
GuardedTest::PlaceholderWithGuard => {
// Skip all branches until we hit the one with a placeholder and a guard.
let index = branches
.iter()
.position(|b| {
if b.guard.is_none() {
return false;
}
let (_, pattern) = b
.patterns
.iter()
.find(|(branch_path, _)| branch_path == path)
.expect(
"if testing for a placeholder with guard, must find a branch matching the path",
);
test_for_pattern(pattern).is_none()
})
.expect("if testing for a guard, one branch must have a guard");
branches[index..].iter()
@ -741,7 +770,7 @@ fn to_relevant_branch<'a>(
found_pattern: pattern,
end,
} => match guarded_test {
GuardedTest::Placeholder | GuardedTest::GuardedNoTest { .. } => {
GuardedTest::PlaceholderWithGuard | GuardedTest::GuardedNoTest { .. } => {
// if there is no test, the pattern should not require any
debug_assert!(
matches!(pattern, Pattern::Identifier(_) | Pattern::Underscore,),
@ -1332,15 +1361,15 @@ fn small_branching_factor(branches: &[Branch], path: &[PathInstruction]) -> usiz
relevant_tests.len() + (if !fallbacks { 0 } else { 1 })
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Debug, PartialEq)]
enum Decider<'a, T> {
Leaf(T),
Guarded {
/// after assigning to symbol, the stmt jumps to this label
id: JoinPointId,
stmt: Stmt<'a>,
pattern: Pattern<'a>,
/// The guard expression and how to compile it.
stmt_spec: GuardStmtSpec,
success: Box<Decider<'a, T>>,
failure: Box<Decider<'a, T>>,
},
@ -1364,7 +1393,18 @@ enum Choice<'a> {
type StoresVec<'a> = bumpalo::collections::Vec<'a, (Symbol, InLayout<'a>, Expr<'a>)>;
pub fn optimize_when<'a>(
struct JumpSpec<'a> {
target_index: u64,
id: JoinPointId,
/// Symbols, from the unpacked pattern, to add on when jumping to the target.
jump_pattern_param_symbols: &'a [Symbol],
// Used to construct the joinpoint
join_params: &'a [Param<'a>],
join_body: Stmt<'a>,
}
pub(crate) fn optimize_when<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
@ -1373,11 +1413,11 @@ pub fn optimize_when<'a>(
ret_layout: InLayout<'a>,
opt_branches: bumpalo::collections::Vec<'a, (Pattern<'a>, Guard<'a>, Stmt<'a>)>,
) -> Stmt<'a> {
let (patterns, _indexed_branches) = opt_branches
let (patterns, indexed_branches): (_, Vec<_>) = opt_branches
.into_iter()
.enumerate()
.map(|(index, (pattern, guard, branch))| {
let has_guard = !guard.is_none();
let has_guard = guard.is_some();
(
(guard, pattern.clone(), index as u64),
(index as u64, branch, pattern, has_guard),
@ -1385,8 +1425,6 @@ pub fn optimize_when<'a>(
})
.unzip();
let indexed_branches: Vec<_> = _indexed_branches;
let decision_tree = compile(&layout_cache.interner, patterns);
let decider = tree_to_decider(decision_tree);
@ -1397,19 +1435,84 @@ pub fn optimize_when<'a>(
let mut choices = MutMap::default();
let mut jumps = Vec::new();
for (index, mut branch, pattern, has_guard) in indexed_branches.into_iter() {
// bind the fields referenced in the pattern. For guards this happens separately, so
// the pattern variables are defined when evaluating the guard.
if !has_guard {
branch =
crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch);
for (target, mut branch, pattern, has_guard) in indexed_branches.into_iter() {
let should_inline = {
let target_counts = &target_counts;
match target_counts.get(target as usize) {
None => unreachable!(
"this should never happen: {:?} not in {:?}",
target, target_counts
),
Some(count) => *count == 1,
}
};
let join_params: &'a [Param<'a>];
let jump_pattern_param_symbols: &'a [Symbol];
match (has_guard, should_inline) {
(false, _) => {
// Bind the fields referenced in the pattern.
branch = store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch);
join_params = &[];
jump_pattern_param_symbols = &[];
}
(true, true) => {
// Nothing more to do - the patterns will be bound when the guard is evaluated in
// `decide_to_branching`.
join_params = &[];
jump_pattern_param_symbols = &[];
}
(true, false) => {
// The patterns will be bound when the guard is evaluated, and then we need to get
// them back into the joinpoint here.
//
// So, figure out what symbols the pattern binds, and update the joinpoint
// parameter to take each symbol. Then, when the joinpoint is called, the unpacked
// symbols will be filled in.
//
// Since the joinpoint's parameters will be fresh symbols, the join body also needs
// updating.
let pattern_bindings = pattern.collect_symbols(cond_layout);
let mut parameters_buf = bumpalo::collections::Vec::with_capacity_in(1, env.arena);
let mut pattern_symbols_buf =
bumpalo::collections::Vec::with_capacity_in(1, env.arena);
let mut substitutions = BumpMap::default();
for (pattern_symbol, layout) in pattern_bindings {
let param_symbol = env.unique_symbol();
parameters_buf.push(Param {
symbol: param_symbol,
layout,
ownership: Ownership::Owned,
});
pattern_symbols_buf.push(pattern_symbol);
substitutions.insert(pattern_symbol, param_symbol);
}
join_params = parameters_buf.into_bump_slice();
jump_pattern_param_symbols = pattern_symbols_buf.into_bump_slice();
substitute_in_exprs_many(env.arena, &mut branch, substitutions);
}
}
let ((branch_index, choice), opt_jump) = create_choices(&target_counts, index, branch);
let ((branch_index, choice), opt_jump) = if should_inline {
((target, Choice::Inline(branch)), None)
} else {
((target, Choice::Jump(target)), Some((target, branch)))
};
if let Some((index, body)) = opt_jump {
if let Some((target_index, body)) = opt_jump {
let id = JoinPointId(env.unique_symbol());
jumps.push((index, id, body));
jumps.push(JumpSpec {
target_index,
id,
jump_pattern_param_symbols,
join_params,
join_body: body,
});
}
choices.insert(branch_index, choice);
@ -1428,11 +1531,18 @@ pub fn optimize_when<'a>(
&jumps,
);
for (_, id, body) in jumps.into_iter() {
for JumpSpec {
target_index: _,
id,
jump_pattern_param_symbols: _,
join_params,
join_body,
} in jumps.into_iter()
{
stmt = Stmt::Join {
id,
parameters: &[],
body: env.arena.alloc(body),
parameters: join_params,
body: env.arena.alloc(join_body),
remainder: env.arena.alloc(stmt),
};
}
@ -1929,7 +2039,7 @@ fn decide_to_branching<'a>(
cond_layout: InLayout<'a>,
ret_layout: InLayout<'a>,
decider: Decider<'a, Choice<'a>>,
jumps: &[(u64, JoinPointId, Stmt<'a>)],
jumps: &[JumpSpec<'a>],
) -> Stmt<'a> {
use Choice::*;
use Decider::*;
@ -1939,16 +2049,15 @@ fn decide_to_branching<'a>(
match decider {
Leaf(Jump(label)) => {
let index = jumps
.binary_search_by_key(&label, |r| r.0)
.binary_search_by_key(&label, |r| r.target_index)
.expect("jump not in list of jumps");
Stmt::Jump(jumps[index].1, &[])
Stmt::Jump(jumps[index].id, jumps[index].jump_pattern_param_symbols)
}
Leaf(Inline(expr)) => expr,
Guarded {
id,
stmt,
pattern,
stmt_spec,
success,
failure,
} => {
@ -1994,14 +2103,19 @@ fn decide_to_branching<'a>(
ownership: Ownership::Owned,
};
let CompiledGuardStmt {
join_point_id,
stmt,
} = stmt_spec.generate_guard_and_join(env, procs, layout_cache);
let join = Stmt::Join {
id,
id: join_point_id,
parameters: arena.alloc([param]),
remainder: arena.alloc(stmt),
body: arena.alloc(decide),
remainder: arena.alloc(stmt),
};
crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, join)
store_pattern(env, procs, layout_cache, &pattern, cond_symbol, join)
}
Chain {
test_chain,
@ -2282,15 +2396,17 @@ fn sort_edge_tests_by_priority(edges: &mut [Edge<'_>]) {
edges.sort_by(|(t1, _), (t2, _)| match (t1, t2) {
// Guarded takes priority
(GuardedNoTest { .. }, GuardedNoTest { .. }) => Equal,
(GuardedNoTest { .. }, TestNotGuarded { .. }) | (GuardedNoTest { .. }, Placeholder) => Less,
(GuardedNoTest { .. }, TestNotGuarded { .. })
| (GuardedNoTest { .. }, PlaceholderWithGuard) => 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,
(TestNotGuarded { .. }, PlaceholderWithGuard) => Less,
// Placeholder is always last
(Placeholder, Placeholder) => Equal,
(Placeholder, GuardedNoTest { .. }) | (Placeholder, TestNotGuarded { .. }) => Greater,
(PlaceholderWithGuard, PlaceholderWithGuard) => Equal,
(PlaceholderWithGuard, GuardedNoTest { .. })
| (PlaceholderWithGuard, TestNotGuarded { .. }) => Greater,
});
fn order_tests(t1: &Test, t2: &Test) -> Ordering {
@ -2452,7 +2568,7 @@ fn fanout_decider_help<'a>(
guarded_test: GuardedTest<'a>,
) -> (Test<'a>, Decider<'a, u64>) {
match guarded_test {
GuardedTest::Placeholder | GuardedTest::GuardedNoTest { .. } => {
GuardedTest::PlaceholderWithGuard | GuardedTest::GuardedNoTest { .. } => {
unreachable!("this would not end up in a switch")
}
GuardedTest::TestNotGuarded { test } => {
@ -2469,16 +2585,15 @@ fn chain_decider<'a>(
success_tree: DecisionTree<'a>,
) -> Decider<'a, u64> {
match guarded_test {
GuardedTest::GuardedNoTest { id, stmt, pattern } => {
GuardedTest::GuardedNoTest { pattern, stmt_spec } => {
let failure = Box::new(tree_to_decider(failure_tree));
let success = Box::new(tree_to_decider(success_tree));
Decider::Guarded {
id,
stmt,
pattern,
stmt_spec,
success,
failure: failure.clone(),
failure,
}
}
GuardedTest::TestNotGuarded { test } => {
@ -2489,7 +2604,7 @@ fn chain_decider<'a>(
}
}
GuardedTest::Placeholder => {
GuardedTest::PlaceholderWithGuard => {
// ?
tree_to_decider(success_tree)
}
@ -2572,22 +2687,6 @@ fn count_targets(targets: &mut bumpalo::collections::Vec<u64>, initial: &Decider
}
}
#[allow(clippy::type_complexity)]
fn create_choices<'a>(
target_counts: &bumpalo::collections::Vec<'a, u64>,
target: u64,
branch: Stmt<'a>,
) -> ((u64, Choice<'a>), Option<(u64, Stmt<'a>)>) {
match target_counts.get(target as usize) {
None => unreachable!(
"this should never happen: {:?} not in {:?}",
target, target_counts
),
Some(1) => ((target, Choice::Inline(branch)), None),
Some(_) => ((target, Choice::Jump(target)), Some((target, branch))),
}
}
fn insert_choices<'a>(
choice_dict: &MutMap<u64, Choice<'a>>,
decider: Decider<'a, u64>,
@ -2601,15 +2700,13 @@ fn insert_choices<'a>(
}
Guarded {
id,
stmt,
pattern,
stmt_spec,
success,
failure,
} => Guarded {
id,
stmt,
pattern,
stmt_spec,
success: Box::new(insert_choices(choice_dict, *success)),
failure: Box::new(insert_choices(choice_dict, *failure)),
},

View file

@ -0,0 +1,116 @@
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_can::expr::IntValue;
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_std::RocDec;
use crate::layout::{Builtin, InLayout, Layout, LayoutInterner, TLLayoutInterner};
use super::pattern::Pattern;
#[derive(Debug, Clone, Copy)]
pub enum IntOrFloatValue {
Int(IntValue),
Float(f64),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Literal<'a> {
// Literals
/// stored as raw bytes rather than a number to avoid an alignment bump
Int([u8; 16]),
/// stored as raw bytes rather than a number to avoid an alignment bump
U128([u8; 16]),
Float(f64),
/// stored as raw bytes rather than a number to avoid an alignment bump
Decimal([u8; 16]),
Str(&'a str),
/// Closed tag unions containing exactly two (0-arity) tags compile to Expr::Bool,
/// so they can (at least potentially) be emitted as 1-bit machine bools.
///
/// So [True, False] compiles to this, and so do [A, B] and [Foo, Bar].
/// However, a union like [True, False, Other Int] would not.
Bool(bool),
/// Closed tag unions containing between 3 and 256 tags (all of 0 arity)
/// compile to bytes, e.g. [Blue, Black, Red, Green, White]
Byte(u8),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ListLiteralElement<'a> {
Literal(Literal<'a>),
Symbol(Symbol),
}
impl<'a> ListLiteralElement<'a> {
pub fn to_symbol(&self) -> Option<Symbol> {
match self {
Self::Symbol(s) => Some(*s),
_ => None,
}
}
}
pub enum NumLiteral {
Int([u8; 16], IntWidth),
U128([u8; 16]),
Float(f64, FloatWidth),
Decimal([u8; 16]),
}
impl NumLiteral {
pub fn to_expr_literal(&self) -> Literal<'static> {
match *self {
NumLiteral::Int(n, _) => Literal::Int(n),
NumLiteral::U128(n) => Literal::U128(n),
NumLiteral::Float(n, _) => Literal::Float(n),
NumLiteral::Decimal(n) => Literal::Decimal(n),
}
}
pub fn to_pattern(&self) -> Pattern<'static> {
match *self {
NumLiteral::Int(n, w) => Pattern::IntLiteral(n, w),
NumLiteral::U128(n) => Pattern::IntLiteral(n, IntWidth::U128),
NumLiteral::Float(n, w) => Pattern::FloatLiteral(f64::to_bits(n), w),
NumLiteral::Decimal(n) => Pattern::DecimalLiteral(n),
}
}
}
pub fn make_num_literal<'a>(
interner: &TLLayoutInterner<'a>,
layout: InLayout<'a>,
num_str: &str,
num_value: IntOrFloatValue,
) -> NumLiteral {
match interner.get(layout) {
Layout::Builtin(Builtin::Int(width)) => match num_value {
IntOrFloatValue::Int(IntValue::I128(n)) => NumLiteral::Int(n, width),
IntOrFloatValue::Int(IntValue::U128(n)) => NumLiteral::U128(n),
IntOrFloatValue::Float(..) => {
internal_error!("Float value where int was expected, should have been a type error")
}
},
Layout::Builtin(Builtin::Float(width)) => match num_value {
IntOrFloatValue::Float(n) => NumLiteral::Float(n, width),
IntOrFloatValue::Int(int_value) => match int_value {
IntValue::I128(n) => NumLiteral::Float(i128::from_ne_bytes(n) as f64, width),
IntValue::U128(n) => NumLiteral::Float(u128::from_ne_bytes(n) as f64, width),
},
},
Layout::Builtin(Builtin::Decimal) => {
let dec = match RocDec::from_str(num_str) {
Some(d) => d,
None => internal_error!(
"Invalid decimal for float literal = {}. This should be a type error!",
num_str
),
};
NumLiteral::Decimal(dec.to_ne_bytes())
}
layout => internal_error!(
"Found a non-num layout where a number was expected: {:?}",
layout
),
}
}

File diff suppressed because it is too large Load diff

View file

@ -19,9 +19,4 @@ pub mod low_level;
pub mod reset_reuse;
pub mod tail_recursion;
// Temporary, while we can build up test cases and optimize the exhaustiveness checking.
// For now, following this warning's advice will lead to nasty type inference errors.
//#[allow(clippy::ptr_arg)]
pub mod decision_tree;
pub mod debug;

View file

@ -4312,3 +4312,26 @@ fn function_specialization_information_in_lambda_set_thunk_independent_defs() {
u8
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn when_guard_appears_multiple_times_in_compiled_decision_tree_issue_5176() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
go : U8 -> U8
go = \byte ->
when byte is
15 if Bool.true -> 1
b if Bool.true -> b + 2
_ -> 3
main = go '.'
"#
),
b'.' + 2,
u8
)
}

View file

@ -4,24 +4,24 @@ procedure Bool.1 ():
procedure Test.1 (Test.2):
let Test.5 : I64 = 2i64;
joinpoint Test.10:
let Test.9 : I64 = 0i64;
ret Test.9;
joinpoint Test.8:
let Test.7 : I64 = 0i64;
ret Test.7;
in
let Test.12 : I64 = 2i64;
let Test.13 : Int1 = lowlevel Eq Test.12 Test.5;
if Test.13 then
joinpoint Test.7 Test.11:
if Test.11 then
joinpoint Test.10 Test.9:
if Test.9 then
let Test.6 : I64 = 42i64;
ret Test.6;
else
jump Test.10;
jump Test.8;
in
let Test.8 : Int1 = CallByName Bool.1;
jump Test.7 Test.8;
let Test.11 : Int1 = CallByName Bool.1;
jump Test.10 Test.11;
else
jump Test.10;
jump Test.8;
procedure Test.0 ():
let Test.4 : {} = Struct {};

View file

@ -4,17 +4,17 @@ procedure Bool.11 (#Attr.2, #Attr.3):
procedure Test.1 (Test.3):
let Test.6 : I64 = 10i64;
joinpoint Test.8 Test.12:
if Test.12 then
joinpoint Test.10 Test.9:
if Test.9 then
let Test.7 : I64 = 0i64;
ret Test.7;
else
let Test.11 : I64 = 42i64;
ret Test.11;
let Test.8 : I64 = 42i64;
ret Test.8;
in
let Test.10 : I64 = 5i64;
let Test.9 : Int1 = CallByName Bool.11 Test.6 Test.10;
jump Test.8 Test.9;
let Test.12 : I64 = 5i64;
let Test.11 : Int1 = CallByName Bool.11 Test.6 Test.12;
jump Test.10 Test.11;
procedure Test.0 ():
let Test.5 : {} = Struct {};

View file

@ -77,18 +77,18 @@ procedure Json.160 (Json.570, Json.571):
let Json.161 : List U8 = StructAtIndex 1 Json.505;
inc Json.161;
dec Json.505;
joinpoint Json.548:
let Json.545 : {List U8, List U8} = Struct {Json.162, Json.161};
ret Json.545;
joinpoint Json.546:
let Json.543 : {List U8, List U8} = Struct {Json.162, Json.161};
ret Json.543;
in
let Json.554 : U64 = lowlevel ListLen Json.162;
let Json.555 : U64 = 2i64;
let Json.556 : Int1 = lowlevel NumGte Json.554 Json.555;
if Json.556 then
let Json.547 : U64 = 0i64;
let Json.163 : U8 = lowlevel ListGetUnsafe Json.162 Json.547;
let Json.546 : U64 = 1i64;
let Json.164 : U8 = lowlevel ListGetUnsafe Json.162 Json.546;
let Json.545 : U64 = 0i64;
let Json.163 : U8 = lowlevel ListGetUnsafe Json.162 Json.545;
let Json.544 : U64 = 1i64;
let Json.164 : U8 = lowlevel ListGetUnsafe Json.162 Json.544;
let Json.516 : Int1 = CallByName Json.23 Json.163 Json.164;
if Json.516 then
let Json.523 : U64 = 2i64;
@ -114,19 +114,19 @@ procedure Json.160 (Json.570, Json.571):
if Json.553 then
let Json.550 : U64 = 0i64;
let Json.165 : U8 = lowlevel ListGetUnsafe Json.162 Json.550;
joinpoint Json.543 Json.549:
if Json.549 then
joinpoint Json.548 Json.547:
if Json.547 then
let Json.541 : List U8 = CallByName List.38 Json.162;
let Json.542 : List U8 = CallByName List.4 Json.161 Json.165;
let Json.539 : {List U8, List U8} = Struct {Json.541, Json.542};
jump Json.508 Json.539 Json.159;
else
jump Json.548;
jump Json.546;
in
let Json.544 : Int1 = CallByName Json.305 Json.165;
jump Json.543 Json.544;
let Json.549 : Int1 = CallByName Json.305 Json.165;
jump Json.548 Json.549;
else
jump Json.548;
jump Json.546;
in
jump Json.508 Json.570 Json.571;

View file

@ -51,18 +51,18 @@ procedure Json.160 (Json.570, Json.571):
let Json.161 : List U8 = StructAtIndex 1 Json.505;
inc Json.161;
dec Json.505;
joinpoint Json.548:
let Json.545 : {List U8, List U8} = Struct {Json.162, Json.161};
ret Json.545;
joinpoint Json.546:
let Json.543 : {List U8, List U8} = Struct {Json.162, Json.161};
ret Json.543;
in
let Json.554 : U64 = lowlevel ListLen Json.162;
let Json.555 : U64 = 2i64;
let Json.556 : Int1 = lowlevel NumGte Json.554 Json.555;
if Json.556 then
let Json.547 : U64 = 0i64;
let Json.163 : U8 = lowlevel ListGetUnsafe Json.162 Json.547;
let Json.546 : U64 = 1i64;
let Json.164 : U8 = lowlevel ListGetUnsafe Json.162 Json.546;
let Json.545 : U64 = 0i64;
let Json.163 : U8 = lowlevel ListGetUnsafe Json.162 Json.545;
let Json.544 : U64 = 1i64;
let Json.164 : U8 = lowlevel ListGetUnsafe Json.162 Json.544;
let Json.516 : Int1 = CallByName Json.23 Json.163 Json.164;
if Json.516 then
let Json.523 : U64 = 2i64;
@ -88,19 +88,19 @@ procedure Json.160 (Json.570, Json.571):
if Json.553 then
let Json.550 : U64 = 0i64;
let Json.165 : U8 = lowlevel ListGetUnsafe Json.162 Json.550;
joinpoint Json.543 Json.549:
if Json.549 then
joinpoint Json.548 Json.547:
if Json.547 then
let Json.541 : List U8 = CallByName List.38 Json.162;
let Json.542 : List U8 = CallByName List.4 Json.161 Json.165;
let Json.539 : {List U8, List U8} = Struct {Json.541, Json.542};
jump Json.508 Json.539 Json.159;
else
jump Json.548;
jump Json.546;
in
let Json.544 : Int1 = CallByName Json.305 Json.165;
jump Json.543 Json.544;
let Json.549 : Int1 = CallByName Json.305 Json.165;
jump Json.548 Json.549;
else
jump Json.548;
jump Json.546;
in
jump Json.508 Json.570 Json.571;

View file

@ -0,0 +1,51 @@
procedure Bool.2 ():
let Bool.25 : Int1 = true;
ret Bool.25;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.275 : U8 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.275;
procedure Test.1 (Test.2):
joinpoint Test.12:
let Test.9 : U8 = 3i64;
ret Test.9;
in
joinpoint Test.11 Test.10:
let Test.8 : U8 = 2i64;
let Test.7 : U8 = CallByName Num.19 Test.10 Test.8;
ret Test.7;
in
let Test.22 : U8 = 15i64;
let Test.23 : Int1 = lowlevel Eq Test.22 Test.2;
if Test.23 then
joinpoint Test.17 Test.13:
if Test.13 then
let Test.6 : U8 = 1i64;
ret Test.6;
else
joinpoint Test.15 Test.14:
if Test.14 then
jump Test.11 Test.2;
else
jump Test.12;
in
let Test.16 : Int1 = CallByName Bool.2;
jump Test.15 Test.16;
in
let Test.18 : Int1 = CallByName Bool.2;
jump Test.17 Test.18;
else
joinpoint Test.20 Test.19:
if Test.19 then
jump Test.11 Test.2;
else
jump Test.12;
in
let Test.21 : Int1 = CallByName Bool.2;
jump Test.20 Test.21;
procedure Test.0 ():
let Test.5 : U8 = 46i64;
let Test.4 : U8 = CallByName Test.1 Test.5;
ret Test.4;

View file

@ -2789,3 +2789,21 @@ fn recursive_closure_with_transiently_used_capture() {
"#
)
}
#[mono_test]
fn when_guard_appears_multiple_times_in_compiled_decision_tree_issue_5176() {
indoc!(
r#"
app "test" provides [main] to "./platform"
go : U8 -> U8
go = \byte ->
when byte is
15 if Bool.true -> 1
b if Bool.true -> b + 2
_ -> 3
main = go '.'
"#
)
}