mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 06:55:15 +00:00
Merge pull request #5202 from roc-lang/i5176
Compilation of when expressions with redundant branches and guards
This commit is contained in:
commit
eadbb4eddd
12 changed files with 2352 additions and 1908 deletions
File diff suppressed because it is too large
Load diff
|
@ -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)),
|
||||
},
|
116
crates/compiler/mono/src/ir/literal.rs
Normal file
116
crates/compiler/mono/src/ir/literal.rs
Normal 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
|
||||
),
|
||||
}
|
||||
}
|
1815
crates/compiler/mono/src/ir/pattern.rs
Normal file
1815
crates/compiler/mono/src/ir/pattern.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 {};
|
||||
|
|
|
@ -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 {};
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
|
@ -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 '.'
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue