Merge remote-tracking branch 'origin/trunk' into nullable-tags

This commit is contained in:
Folkert 2021-01-17 02:37:57 +01:00
commit 8cd744342b
54 changed files with 1208 additions and 724 deletions

View file

@ -2,14 +2,15 @@ use std::fs::File;
use std::io::prelude::Read;
use std::vec::Vec;
const PATH: &str = env!(
"BUILTINS_BC",
"Env var BUILTINS_BC not found. Is there a problem with the build script?"
);
pub fn get_bytes() -> Vec<u8> {
// In the build script for the builtins module, we compile the builtins bitcode and set
// BUILTINS_BC to the path to the compiled output.
let path: &'static str = env!(
"BUILTINS_BC",
"Env var BUILTINS_BC not found. Is there a problem with the build script?"
);
let mut builtins_bitcode = File::open(path).expect("Unable to find builtins bitcode source");
let mut builtins_bitcode = File::open(PATH).expect("Unable to find builtins bitcode source");
let mut buffer = Vec::new();
builtins_bitcode
.read_to_end(&mut buffer)

View file

@ -291,6 +291,15 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
),
);
// bitwiseXor : Int a, Int a -> Int a
add_type(
Symbol::NUM_BITWISE_XOR,
top_level_function(
vec![int_type(flex(TVAR1)), int_type(flex(TVAR1))],
Box::new(int_type(flex(TVAR1))),
),
);
// rem : Int a, Int a -> Result (Int a) [ DivByZero ]*
add_type(
Symbol::NUM_REM,

View file

@ -294,6 +294,15 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
)
});
// bitwiseAnd : Attr * Int, Attr * Int -> Attr * Int
add_type(Symbol::NUM_BITWISE_XOR, {
let_tvars! { star1, star2, star3, int };
unique_function(
vec![int_type(star1, int), int_type(star2, int)],
int_type(star3, int),
)
});
// divFloat : Float, Float -> Float
add_type(Symbol::NUM_DIV_FLOAT, {
let_tvars! { star1, star2, star3, star4, star5};

View file

@ -117,7 +117,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
NUM_ASIN => num_asin,
NUM_MAX_INT => num_max_int,
NUM_MIN_INT => num_min_int,
NUM_BITWISE_AND => num_bitwise_and
NUM_BITWISE_AND => num_bitwise_and,
NUM_BITWISE_XOR => num_bitwise_xor
}
}
@ -1153,6 +1154,11 @@ fn num_bitwise_and(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumBitwiseAnd)
}
/// Num.bitwiseXor : Int, Int -> Int
fn num_bitwise_xor(symbol: Symbol, var_store: &mut VarStore) -> Def {
num_binop(symbol, var_store, LowLevel::NumBitwiseXor)
}
/// List.isEmpty : List * -> Bool
fn list_is_empty(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();

View file

@ -3599,7 +3599,7 @@ fn run_low_level<'a, 'ctx, 'env>(
build_num_binop(env, parent, lhs_arg, lhs_layout, rhs_arg, rhs_layout, op)
}
NumBitwiseAnd => {
NumBitwiseAnd | NumBitwiseXor => {
debug_assert_eq!(args.len(), 2);
let (lhs_arg, lhs_layout) = load_symbol_and_layout(env, scope, &args[0]);
@ -3953,6 +3953,7 @@ fn build_int_binop<'a, 'ctx, 'env>(
NumDivUnchecked => bd.build_int_signed_div(lhs, rhs, "div_int").into(),
NumPowInt => call_bitcode_fn(env, &[lhs.into(), rhs.into()], &bitcode::NUM_POW_INT),
NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(),
NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(),
_ => {
unreachable!("Unrecognized int binary operation: {:?}", op);
}

View file

@ -757,6 +757,14 @@ mod gen_num {
assert_evals_to!("Num.bitwiseAnd 200 0", 0, i64);
}
#[test]
fn bitwise_xor() {
assert_evals_to!("Num.bitwiseXor 20 20", 0, i64);
assert_evals_to!("Num.bitwiseXor 15 14", 1, i64);
assert_evals_to!("Num.bitwiseXor 7 15", 8, i64);
assert_evals_to!("Num.bitwiseXor 200 0", 200, i64);
}
#[test]
fn lt_i64() {
assert_evals_to!("1 < 2", true, bool);

View file

@ -1901,6 +1901,26 @@ mod gen_primitives {
);
}
#[test]
fn case_or_pattern() {
// the `0` branch body should only be generated once in the future
// it is currently duplicated
assert_evals_to!(
indoc!(
r#"
x : [ Red, Green, Blue ]
x = Red
when x is
Red | Green -> 0
Blue -> 1
"#
),
0,
i64
);
}
#[test]
#[ignore]
fn rosetree_basic() {
@ -1932,4 +1952,29 @@ mod gen_primitives {
bool
);
}
#[test]
fn case_jump() {
// the decision tree will generate a jump to the `1` branch here
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
ConsList a : [ Cons a (ConsList a), Nil ]
x : ConsList I64
x = Nil
main =
when Pair x x is
Pair Nil _ -> 1
Pair _ Nil -> 2
Pair (Cons a _) (Cons b _) -> a + b + 3
"#
),
1,
i64
);
}
}

View file

@ -19,7 +19,7 @@ fn promote_expr_to_module(src: &str) -> String {
pub fn helper<'a>(
arena: &'a bumpalo::Bump,
src: &str,
stdlib: roc_builtins::std::StdLib,
stdlib: &'a roc_builtins::std::StdLib,
leak: bool,
context: &'a inkwell::context::Context,
) -> (&'static str, String, Library) {
@ -295,40 +295,6 @@ pub fn helper<'a>(
(main_fn_name, delayed_errors.join("\n"), lib)
}
// TODO this is almost all code duplication with assert_llvm_evals_to
// the only difference is that this calls uniq_expr instead of can_expr.
// Should extract the common logic into test helpers.
#[macro_export]
macro_rules! assert_opt_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
use bumpalo::Bump;
use inkwell::context::Context;
use roc_gen::run_jit_function;
let arena = Bump::new();
let context = Context::create();
// don't use uniqueness types any more
// let stdlib = roc_builtins::unique::uniq_stdlib();
let stdlib = roc_builtins::std::standard_stdlib();
let (main_fn_name, errors, lib) =
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
let transform = |success| {
let expected = $expected;
let given = $transform(success);
assert_eq!(&given, &expected);
};
run_jit_function!(lib, main_fn_name, $ty, transform, errors)
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
assert_opt_evals_to!($src, $expected, $ty, $transform, true)
};
}
#[macro_export]
macro_rules! assert_llvm_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
@ -337,9 +303,10 @@ macro_rules! assert_llvm_evals_to {
use roc_gen::run_jit_function;
let arena = Bump::new();
let context = Context::create();
let stdlib = roc_builtins::std::standard_stdlib();
// NOTE the stdlib must be in the arena; just taking a reference will segfault
let stdlib = arena.alloc(roc_builtins::std::standard_stdlib());
let (main_fn_name, errors, lib) =
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
@ -377,7 +344,8 @@ macro_rules! assert_evals_to {
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak);
}
{
assert_opt_evals_to!($src, $expected, $ty, $transform, $leak);
// NOTE at the moment, the optimized tests do the same thing
// assert_opt_evals_to!($src, $expected, $ty, $transform, $leak);
}
};
}

View file

@ -47,7 +47,7 @@ pub fn helper<'a>(
arena,
filename,
&module_src,
stdlib,
&stdlib,
src_dir,
exposed_types,
8,

View file

@ -699,7 +699,7 @@ struct State<'a> {
pub root_id: ModuleId,
pub platform_id: Option<ModuleId>,
pub goal_phase: Phase,
pub stdlib: StdLib,
pub stdlib: &'a StdLib,
pub exposed_types: SubsByModule,
pub output_path: Option<&'a str>,
pub platform_path: Option<To<'a>>,
@ -944,7 +944,7 @@ fn enqueue_task<'a>(
pub fn load_and_typecheck(
arena: &Bump,
filename: PathBuf,
stdlib: StdLib,
stdlib: &StdLib,
src_dir: &Path,
exposed_types: SubsByModule,
ptr_bytes: u32,
@ -970,7 +970,7 @@ pub fn load_and_typecheck(
pub fn load_and_monomorphize<'a>(
arena: &'a Bump,
filename: PathBuf,
stdlib: StdLib,
stdlib: &'a StdLib,
src_dir: &Path,
exposed_types: SubsByModule,
ptr_bytes: u32,
@ -997,7 +997,7 @@ pub fn load_and_monomorphize_from_str<'a>(
arena: &'a Bump,
filename: PathBuf,
src: &'a str,
stdlib: StdLib,
stdlib: &'a StdLib,
src_dir: &Path,
exposed_types: SubsByModule,
ptr_bytes: u32,
@ -1146,7 +1146,7 @@ fn load<'a>(
arena: &'a Bump,
//filename: PathBuf,
load_start: LoadStart<'a>,
stdlib: StdLib,
stdlib: &'a StdLib,
src_dir: &Path,
exposed_types: SubsByModule,
goal_phase: Phase,

View file

@ -82,7 +82,7 @@ mod test_load {
roc_load::file::load_and_typecheck(
arena,
full_file_path,
stdlib,
&stdlib,
dir.path(),
exposed_types,
8,
@ -124,7 +124,7 @@ mod test_load {
let loaded = roc_load::file::load_and_typecheck(
&arena,
filename,
roc_builtins::std::standard_stdlib(),
&roc_builtins::std::standard_stdlib(),
src_dir.as_path(),
subs_by_module,
8,
@ -287,7 +287,7 @@ mod test_load {
let loaded = roc_load::file::load_and_typecheck(
&arena,
filename,
roc_builtins::std::standard_stdlib(),
&roc_builtins::std::standard_stdlib(),
src_dir.as_path(),
subs_by_module,
8,

View file

@ -59,6 +59,7 @@ pub enum LowLevel {
NumAcos,
NumAsin,
NumBitwiseAnd,
NumBitwiseXor,
Eq,
NotEq,
And,

View file

@ -823,15 +823,16 @@ define_builtins! {
79 NUM_AT_BINARY32: "@Binary32"
80 NUM_BINARY32: "Binary32" imported
81 NUM_BITWISE_AND: "bitwiseAnd"
82 NUM_SUB_WRAP: "subWrap"
83 NUM_SUB_CHECKED: "subChecked"
84 NUM_MUL_WRAP: "mulWrap"
85 NUM_MUL_CHECKED: "mulChecked"
86 NUM_INT: "Int" imported
87 NUM_FLOAT: "Float" imported
88 NUM_AT_NATURAL: "@Natural"
89 NUM_NATURAL: "Natural" imported
90 NUM_NAT: "Nat" imported
82 NUM_BITWISE_XOR: "bitwiseXor"
83 NUM_SUB_WRAP: "subWrap"
84 NUM_SUB_CHECKED: "subChecked"
85 NUM_MUL_WRAP: "mulWrap"
86 NUM_MUL_CHECKED: "mulChecked"
87 NUM_INT: "Int" imported
88 NUM_FLOAT: "Float" imported
89 NUM_AT_NATURAL: "@Natural"
90 NUM_NATURAL: "Natural" imported
91 NUM_NAT: "Nat" imported
}
2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias

View file

@ -578,9 +578,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
Eq | NotEq | And | Or | NumAdd | NumAddWrap | NumAddChecked | NumSub | NumSubWrap
| NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte | NumLt | NumLte
| NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd => {
arena.alloc_slice_copy(&[irrelevant, irrelevant])
}
| NumCompare | NumDivUnchecked | NumRemUnchecked | NumPow | NumPowInt | NumBitwiseAnd
| NumBitwiseXor => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumRound | NumCeiling | NumFloor
| NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin => {

View file

@ -905,7 +905,10 @@ pub fn optimize_when<'a>(
let decision_tree = compile(patterns);
let decider = tree_to_decider(decision_tree);
let target_counts = count_targets(&decider);
// for each target (branch body), count in how many ways it can be reached
let mut target_counts = bumpalo::vec![in env.arena; 0; indexed_branches.len()];
count_targets(&mut target_counts, &decider);
let mut choices = MutMap::default();
let mut jumps = Vec::new();
@ -913,8 +916,9 @@ pub fn optimize_when<'a>(
for (index, branch) in indexed_branches.into_iter() {
let ((branch_index, choice), opt_jump) = create_choices(&target_counts, index, branch);
if let Some(jump) = opt_jump {
jumps.push(jump);
if let Some((index, body)) = opt_jump {
let id = JoinPointId(env.unique_symbol());
jumps.push((index, id, body));
}
choices.insert(branch_index, choice);
@ -922,7 +926,7 @@ pub fn optimize_when<'a>(
let choice_decider = insert_choices(&choices, decider);
decide_to_branching(
let mut stmt = decide_to_branching(
env,
procs,
layout_cache,
@ -931,7 +935,18 @@ pub fn optimize_when<'a>(
ret_layout,
choice_decider,
&jumps,
)
);
for (_, id, body) in jumps.into_iter() {
stmt = Stmt::Join {
id,
parameters: &[],
continuation: env.arena.alloc(body),
remainder: env.arena.alloc(stmt),
};
}
stmt
}
#[derive(Debug)]
@ -1383,7 +1398,7 @@ fn decide_to_branching<'a>(
cond_layout: Layout<'a>,
ret_layout: Layout<'a>,
decider: Decider<'a, Choice<'a>>,
jumps: &Vec<(u64, Stmt<'a>)>,
jumps: &Vec<(u64, JoinPointId, Stmt<'a>)>,
) -> Stmt<'a> {
use Choice::*;
use Decider::*;
@ -1392,12 +1407,11 @@ fn decide_to_branching<'a>(
match decider {
Leaf(Jump(label)) => {
// we currently inline the jumps: does fewer jumps but produces a larger artifact
let (_, expr) = jumps
.iter()
.find(|(l, _)| l == &label)
let index = jumps
.binary_search_by_key(&label, |ref r| r.0)
.expect("jump not in list of jumps");
expr.clone()
Stmt::Jump(jumps[index].1, &[])
}
Leaf(Inline(expr)) => expr,
Chain {
@ -1674,39 +1688,32 @@ fn to_chain<'a>(
/// If a target appears exactly once in a Decider, the corresponding expression
/// can be inlined. Whether things are inlined or jumps is called a "choice".
fn count_targets(decision_tree: &Decider<u64>) -> MutMap<u64, u64> {
let mut result = MutMap::default();
count_targets_help(decision_tree, &mut result);
result
}
fn count_targets_help(decision_tree: &Decider<u64>, targets: &mut MutMap<u64, u64>) {
fn count_targets(targets: &mut bumpalo::collections::Vec<u64>, initial: &Decider<u64>) {
use Decider::*;
match decision_tree {
Leaf(target) => match targets.get_mut(target) {
None => {
targets.insert(*target, 1);
let mut stack = vec![initial];
while let Some(decision_tree) = stack.pop() {
match decision_tree {
Leaf(target) => {
targets[*target as usize] += 1;
}
Some(current) => {
*current += 1;
Chain {
success, failure, ..
} => {
stack.push(success);
stack.push(failure);
}
},
Chain {
success, failure, ..
} => {
count_targets_help(success, targets);
count_targets_help(failure, targets);
}
FanOut {
tests, fallback, ..
} => {
stack.push(fallback);
FanOut {
tests, fallback, ..
} => {
count_targets_help(fallback, targets);
for (_, decider) in tests {
count_targets_help(decider, targets);
for (_, decider) in tests {
stack.push(decider);
}
}
}
}
@ -1714,11 +1721,11 @@ fn count_targets_help(decision_tree: &Decider<u64>, targets: &mut MutMap<u64, u6
#[allow(clippy::type_complexity)]
fn create_choices<'a>(
target_counts: &MutMap<u64, u64>,
target_counts: &bumpalo::collections::Vec<'a, u64>,
target: u64,
branch: Stmt<'a>,
) -> ((u64, Choice<'a>), Option<(u64, Stmt<'a>)>) {
match target_counts.get(&target) {
match target_counts.get(target as usize) {
None => unreachable!(
"this should never happen: {:?} not in {:?}",
target, target_counts

View file

@ -58,7 +58,7 @@ mod test_mono {
arena,
filename,
&module_src,
stdlib,
&stdlib,
src_dir,
exposed_types,
8,

View file

@ -59,7 +59,7 @@ mod solve_expr {
let result = roc_load::file::load_and_typecheck(
arena,
full_file_path,
stdlib,
&stdlib,
dir.path(),
exposed_types,
8,