From a16d48a6a96adf49bc9bc787e63e13cb7d654aad Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 21 Mar 2020 22:27:36 +0100 Subject: [PATCH] code gen for simple guards Guards cannot use variables bound in the pattern yet --- compiler/gen/src/crane/build.rs | 13 +- compiler/gen/tests/test_gen.rs | 60 +++ compiler/mono/src/decision_tree.rs | 702 +++++++++++++++++------------ compiler/mono/src/expr.rs | 8 +- 4 files changed, 490 insertions(+), 293 deletions(-) diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs index ab17c4b4f0..4727f9e65e 100644 --- a/compiler/gen/src/crane/build.rs +++ b/compiler/gen/src/crane/build.rs @@ -771,13 +771,24 @@ fn call_by_name<'a, B: Backend>( .ins() .load(env.ptr_sized_int(), MemFlags::new(), list_ptr, offset) } - Symbol::INT_EQ_I64 | Symbol::INT_EQ_I8 | Symbol::INT_EQ_I1 => { + Symbol::INT_EQ_I64 | Symbol::INT_EQ_I8 => { debug_assert!(args.len() == 2); let a = build_arg(&args[0], env, scope, module, builder, procs); let b = build_arg(&args[1], env, scope, module, builder, procs); builder.ins().icmp(IntCC::Equal, a, b) } + Symbol::INT_EQ_I1 => { + debug_assert!(args.len() == 2); + let a = build_arg(&args[0], env, scope, module, builder, procs); + let b = build_arg(&args[1], env, scope, module, builder, procs); + + // integer comparisons don't work for booleans, and a custom xand gives errors. + let p = builder.ins().bint(types::I8, a); + let q = builder.ins().bint(types::I8, b); + + builder.ins().icmp(IntCC::Equal, p, q) + } Symbol::FLOAT_EQ => { debug_assert!(args.len() == 2); let a = build_arg(&args[0], env, scope, module, builder, procs); diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs index adae9d07fc..0546180c82 100644 --- a/compiler/gen/tests/test_gen.rs +++ b/compiler/gen/tests/test_gen.rs @@ -1495,6 +1495,66 @@ mod test_gen { ); } + #[test] + fn or_pattern() { + assert_evals_to!( + indoc!( + r#" + when 2 is + 1 | 2 -> 42 + _ -> 1 + "# + ), + 42, + i64 + ); + } + + #[test] + fn if_guard_pattern_false() { + assert_evals_to!( + indoc!( + r#" + when 2 is + 2 if False -> 0 + _ -> 42 + "# + ), + 42, + i64 + ); + } + + #[test] + fn if_guard_pattern_true() { + assert_evals_to!( + indoc!( + r#" + when 2 is + 2 if True -> 42 + _ -> 0 + "# + ), + 42, + i64 + ); + } + + // #[test] + // fn if_guard_exhaustiveness() { + // assert_evals_to!( + // indoc!( + // r#" + // when 2 is + // _ if False -> 0 + // _ -> 42 + // "# + // ), + // 42, + // i64 + // ); + // } + // #[test] // fn linked_list_empty() { // assert_evals_to!( diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index 2de2149d03..739cd36f04 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -19,12 +19,12 @@ type Label = u64; /// some normal branches and gives out a decision tree that has "labels" at all /// the leafs and a dictionary that maps these "labels" to the code that should /// run. -pub fn compile(raw_branches: Vec<(Pattern<'_>, u64)>) -> DecisionTree { +pub fn compile<'a>(raw_branches: Vec<(Option>, Pattern<'a>, u64)>) -> DecisionTree<'a> { let formatted = raw_branches .into_iter() - .map(|(pattern, index)| Branch { + .map(|(guard, pattern, index)| Branch { goal: index, - patterns: vec![(Path::Empty, pattern)], + patterns: vec![(Path::Empty, guard, pattern)], }) .collect(); @@ -41,7 +41,7 @@ pub enum DecisionTree<'a> { }, } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq)] pub enum Test<'a> { IsCtor { tag_id: u8, @@ -58,6 +58,8 @@ pub enum Test<'a> { tag_id: u8, num_alts: usize, }, + // A pattern that always succeeds (like `_`) can still have a guard + Guarded(Option>>, Expr<'a>), } use std::hash::{Hash, Hasher}; impl<'a> Hash for Test<'a> { @@ -89,7 +91,14 @@ impl<'a> Hash for Test<'a> { IsByte { tag_id, num_alts } => { state.write_u8(5); tag_id.hash(state); - num_alts.hash(state) + num_alts.hash(state); + } + Guarded(None, _) => { + state.write_u8(6); + } + Guarded(Some(nested), _) => { + state.write_u8(7); + nested.hash(state); } } } @@ -111,7 +120,7 @@ pub enum Path { #[derive(Clone, Debug, PartialEq)] struct Branch<'a> { goal: Label, - patterns: Vec<(Path, Pattern<'a>)>, + patterns: Vec<(Path, Option>, Pattern<'a>)>, } fn to_decision_tree(raw_branches: Vec) -> DecisionTree { @@ -163,6 +172,7 @@ fn is_complete(tests: &[Test]) -> bool { Test::IsInt(_) => false, Test::IsFloat(_) => false, Test::IsStr(_) => false, + Test::Guarded(_, _) => false, }, } } @@ -179,20 +189,28 @@ fn flatten_patterns(branch: Branch) -> Branch { } } -fn flatten<'a>(path_pattern: (Path, Pattern<'a>), path_patterns: &mut Vec<(Path, Pattern<'a>)>) { - match &path_pattern.1 { +fn flatten<'a>( + path_pattern: (Path, Option>, Pattern<'a>), + path_patterns: &mut Vec<(Path, Option>, Pattern<'a>)>, +) { + match &path_pattern.2 { Pattern::AppliedTag { union, arguments, tag_id, .. } => { + // TODO do we need to check that guard.is_none() here? if union.alternatives.len() == 1 { let path = path_pattern.0; // Theory: unbox doesn't have any value for us, because one-element tag unions // don't store the tag anyway. if arguments.len() == 1 { - path_patterns.push((Path::Unbox(Box::new(path)), path_pattern.1.clone())); + path_patterns.push(( + Path::Unbox(Box::new(path)), + path_pattern.1.clone(), + path_pattern.2.clone(), + )); } else { for (index, (arg_pattern, _)) in arguments.iter().enumerate() { flatten( @@ -202,6 +220,8 @@ fn flatten<'a>(path_pattern: (Path, Pattern<'a>), path_patterns: &mut Vec<(Path, tag_id: *tag_id, path: Box::new(path.clone()), }, + // same guard here? + path_pattern.1.clone(), arg_pattern.clone(), ), path_patterns, @@ -227,7 +247,11 @@ fn flatten<'a>(path_pattern: (Path, Pattern<'a>), path_patterns: &mut Vec<(Path, /// us something like ("x" => value.0.0) fn check_for_match(branches: &Vec) -> Option