Merge pull request #440 from rtfeldman/refcount

Introduce refcounting
This commit is contained in:
Richard Feldman 2020-08-10 21:25:27 -04:00 committed by GitHub
commit ddfe16b1b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 8181 additions and 1632 deletions

1
Cargo.lock generated
View file

@ -2250,6 +2250,7 @@ dependencies = [
"roc_solve",
"roc_types",
"roc_unify",
"ven_pretty",
]
[[package]]

View file

@ -1,7 +1,6 @@
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
use inkwell::passes::PassManager;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_builtins::unique::uniq_stdlib;
@ -19,7 +18,7 @@ use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_mono::expr::Procs;
use roc_mono::ir::Procs;
use roc_mono::layout::{Layout, LayoutCache};
use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before;
@ -209,13 +208,9 @@ pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<(String, S
}
let context = Context::create();
let module = roc_gen::llvm::build::module_from_builtins(&context, "app");
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "app"));
let builder = context.create_builder();
let fpm = PassManager::create(&module);
roc_gen::llvm::build::add_passes(&fpm, opt_level);
fpm.initialize();
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
// pretty-print the expr type string for later.
name_all_type_vars(var, &mut subs);
@ -243,8 +238,9 @@ pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<(String, S
builder: &builder,
context: &context,
interns,
module: arena.alloc(module),
module,
ptr_bytes,
leak: false,
};
let mut procs = Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
@ -252,7 +248,7 @@ pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<(String, S
// Populate Procs and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new();
let mut mono_env = roc_mono::expr::Env {
let mut mono_env = roc_mono::ir::Env {
arena: &arena,
subs: &mut subs,
problems: &mut mono_problems,
@ -260,7 +256,9 @@ pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<(String, S
ident_ids: &mut ident_ids,
};
let main_body = roc_mono::expr::Expr::new(&mut mono_env, loc_expr.value, &mut procs);
let main_body = roc_mono::ir::Stmt::new(&mut mono_env, loc_expr.value, &mut procs);
let main_body =
roc_mono::inc_dec::visit_declaration(mono_env.arena, mono_env.arena.alloc(main_body));
let mut headers = {
let num_headers = match &procs.pending_specializations {
Some(map) => map.len(),
@ -270,7 +268,7 @@ pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<(String, S
Vec::with_capacity(num_headers)
};
let mut layout_cache = LayoutCache::default();
let mut procs = roc_mono::expr::specialize_all(&mut mono_env, procs, &mut layout_cache);
let mut procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
assert_eq!(
procs.runtime_errors,
@ -285,8 +283,10 @@ pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<(String, S
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
let mut gen_scope = roc_gen::llvm::build::Scope::default();
for ((symbol, layout), proc) in procs.specialized.drain() {
use roc_mono::expr::InProgressProc::*;
use roc_mono::ir::InProgressProc::*;
match proc {
InProgress => {
@ -331,10 +331,10 @@ pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<(String, S
builder.position_at_end(basic_block);
let ret = roc_gen::llvm::build::build_expr(
let ret = roc_gen::llvm::build::build_exp_stmt(
&env,
&mut layout_ids,
&ImMap::default(),
&mut gen_scope,
main_fn,
&main_body,
);
@ -350,6 +350,8 @@ pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<(String, S
panic!("Main function {} failed LLVM verification. Uncomment things near this error message for more details.", main_fn_name);
}
mpm.run_on(module);
// Verify the module
if let Err(errors) = env.module.verify() {
panic!("Errors defining module: {:?}", errors);
@ -367,7 +369,9 @@ pub fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<(String, S
.ok_or(format!("Unable to JIT compile `{}`", main_fn_name))
.expect("errored");
Ok((format!("{}", main.call()), expr_type_str))
let result = main.call();
let output = format!("{}", result);
Ok((output, expr_type_str))
}
}

View file

@ -101,4 +101,17 @@ mod cli_run {
assert!(&out.stdout.ends_with("Hello, World!\n"));
assert!(out.status.success());
}
#[test]
fn run_quicksort() {
let out = run_roc(&[
"run",
example_file("quicksort", "Quicksort.roc").to_str().unwrap(),
"--optimize",
]);
assert_eq!(&out.stderr, "");
assert!(&out.stdout.ends_with("[4, 7, 19, 21]\n"));
assert!(out.status.success());
}
}

View file

@ -1,10 +1,8 @@
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::module::Linkage;
use inkwell::passes::PassManager;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_collections::all::ImMap;
use roc_gen::layout_id::LayoutIds;
use roc_gen::llvm::build::{
build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel,
@ -12,7 +10,7 @@ use roc_gen::llvm::build::{
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_load::file::LoadedModule;
use roc_module::symbol::Symbol;
use roc_mono::expr::{Env, Expr, PartialProc, Procs};
use roc_mono::ir::{Env, PartialProc, Procs, Stmt};
use roc_mono::layout::{Layout, LayoutCache};
use inkwell::targets::{
@ -128,13 +126,9 @@ pub fn gen(
// Generate the binary
let context = Context::create();
let module = module_from_builtins(&context, "app");
let module = arena.alloc(module_from_builtins(&context, "app"));
let builder = context.create_builder();
let fpm = PassManager::create(&module);
roc_gen::llvm::build::add_passes(&fpm, opt_level);
fpm.initialize();
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
// Compute main_fn_type before moving subs to Env
let layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
@ -155,8 +149,9 @@ pub fn gen(
builder: &builder,
context: &context,
interns: loaded.interns,
module: arena.alloc(module),
module,
ptr_bytes,
leak: false,
};
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = LayoutIds::default();
@ -202,7 +197,9 @@ pub fn gen(
let proc = PartialProc {
annotation: def.expr_var,
// This is a 0-arity thunk, so it has no arguments.
pattern_symbols: &[],
pattern_symbols: bumpalo::collections::Vec::new_in(
mono_env.arena,
),
body,
};
@ -226,7 +223,9 @@ pub fn gen(
}
// Populate Procs further and get the low-level Expr from the canonical Expr
let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs);
let main_body = Stmt::new(&mut mono_env, loc_expr.value, &mut procs);
let main_body =
roc_mono::inc_dec::visit_declaration(mono_env.arena, mono_env.arena.alloc(main_body));
let mut headers = {
let num_headers = match &procs.pending_specializations {
Some(map) => map.len(),
@ -235,7 +234,7 @@ pub fn gen(
Vec::with_capacity(num_headers)
};
let mut procs = roc_mono::expr::specialize_all(&mut mono_env, procs, &mut layout_cache);
let mut procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
assert_eq!(
procs.runtime_errors,
@ -251,7 +250,7 @@ pub fn gen(
// We have to do this in a separate pass first,
// because their bodies may reference each other.
for ((symbol, layout), proc) in procs.specialized.drain() {
use roc_mono::expr::InProgressProc::*;
use roc_mono::ir::InProgressProc::*;
match proc {
InProgress => {
@ -296,10 +295,10 @@ pub fn gen(
builder.position_at_end(basic_block);
let ret = roc_gen::llvm::build::build_expr(
let ret = roc_gen::llvm::build::build_exp_stmt(
&env,
&mut layout_ids,
&ImMap::default(),
&mut roc_gen::llvm::build::Scope::default(),
main_fn,
&main_body,
);
@ -315,6 +314,8 @@ pub fn gen(
panic!("Function {} failed LLVM verification.", main_fn_name);
}
mpm.run_on(module);
// Verify the module
if let Err(errors) = env.module.verify() {
panic!("😱 LLVM errors when defining module: {:?}", errors);

View file

@ -58,6 +58,7 @@ pub enum Expr {
Str(Box<str>),
BlockStr(Box<str>),
List {
list_var: Variable, // required for uniqueness of the list
elem_var: Variable,
loc_elems: Vec<Located<Expr>>,
},
@ -256,6 +257,7 @@ pub fn canonicalize_expr<'a>(
if loc_elems.is_empty() {
(
List {
list_var: var_store.fresh(),
elem_var: var_store.fresh(),
loc_elems: Vec::new(),
},
@ -283,6 +285,7 @@ pub fn canonicalize_expr<'a>(
(
List {
list_var: var_store.fresh(),
elem_var: var_store.fresh(),
loc_elems: can_elems,
},
@ -1052,6 +1055,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
| other @ RunLowLevel { .. } => other,
List {
list_var,
elem_var,
loc_elems,
} => {
@ -1067,6 +1071,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
}
List {
list_var,
elem_var,
loc_elems: new_elems,
}

View file

@ -77,9 +77,14 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
}
RecordDestructure { destructs, .. } => {
for destruct in destructs {
// when a record field has a pattern guard, only symbols in the guard are introduced
if let DestructType::Guard(_, subpattern) = &destruct.value.typ {
symbols_from_pattern_help(&subpattern.value, symbols);
} else {
symbols.push(destruct.value.symbol);
}
}
}
NumLiteral(_, _)
| IntLiteral(_)

View file

@ -203,7 +203,7 @@ pub fn constrain_expr(
List {
elem_var,
loc_elems,
..
list_var: _unused,
} => {
if loc_elems.is_empty() {
exists(

View file

@ -639,6 +639,7 @@ pub fn constrain_expr(
exists(vars, And(arg_cons))
}
List {
list_var,
elem_var,
loc_elems,
} => {
@ -676,9 +677,12 @@ pub fn constrain_expr(
}
let inferred = list_type(Bool::variable(uniq_var), entry_type);
constraints.push(Eq(inferred, expected, Category::List, region));
constraints.push(Eq(inferred, expected.clone(), Category::List, region));
exists(vec![*elem_var, uniq_var], And(constraints))
let stored = Type::Variable(*list_var);
constraints.push(Eq(stored, expected, Category::Storage, region));
exists(vec![*elem_var, *list_var, uniq_var], And(constraints))
}
}
Var(symbol) => {

File diff suppressed because it is too large Load diff

View file

@ -112,12 +112,31 @@ pub fn basic_type_from_layout<'ctx>(
let ptr_size = std::mem::size_of::<i64>();
let union_size = layout.stack_size(ptr_size as u32);
let array_type = context
.i8_type()
.array_type(union_size)
.as_basic_type_enum();
// The memory layout of Union is a bit tricky.
// We have tags with different memory layouts, that are part of the same type.
// For llvm, all tags must have the same memory layout.
//
// So, we convert all tags to a layout of bytes of some size.
// It turns out that encoding to i64 for as many elements as possible is
// a nice optimization, the remainder is encoded as bytes.
context.struct_type(&[array_type], false).into()
let num_i64 = union_size / 8;
let num_i8 = union_size % 8;
let i64_array_type = context.i64_type().array_type(num_i64).as_basic_type_enum();
if num_i8 == 0 {
// the object fits perfectly in some number of i64's
// (i.e. the size is a multiple of 8 bytes)
context.struct_type(&[i64_array_type], false).into()
} else {
// there are some trailing bytes at the end
let i8_array_type = context.i8_type().array_type(num_i8).as_basic_type_enum();
context
.struct_type(&[i64_array_type, i8_array_type], false)
.into()
}
}
Builtin(builtin) => match builtin {
@ -137,7 +156,7 @@ pub fn basic_type_from_layout<'ctx>(
.as_basic_type_enum(),
Map(_, _) | EmptyMap => panic!("TODO layout_to_basic_type for Builtin::Map"),
Set(_) | EmptySet => panic!("TODO layout_to_basic_type for Builtin::Set"),
List(_) => collection(context, ptr_bytes).into(),
List(_, _) => collection(context, ptr_bytes).into(),
EmptyList => BasicTypeEnum::StructType(collection(context, ptr_bytes)),
},
}

View file

@ -13,25 +13,18 @@ mod helpers;
#[cfg(test)]
mod gen_list {
use crate::helpers::{can_expr, infer_expr, uniq_expr, with_larger_debug_stack, CanExprOut};
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
use inkwell::passes::PassManager;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_collections::all::ImMap;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::expr::{Expr, Procs};
use roc_mono::layout::Layout;
use roc_types::subs::Subs;
use crate::helpers::with_larger_debug_stack;
#[test]
fn empty_list_literal() {
assert_evals_to!("[]", &[], &'static [i64]);
}
#[test]
fn int_singleton_list_literal() {
assert_evals_to!("[1]", &[1], &'static [i64]);
}
#[test]
fn int_list_literal() {
assert_evals_to!("[ 12, 9, 6, 3 ]", &[12, 9, 6, 3], &'static [i64]);
@ -272,7 +265,9 @@ mod gen_list {
assert_evals_to!(
&format!("List.concat {} {}", slice_str1, slice_str2),
expected_slice,
&'static [i64]
&'static [i64],
|x| x,
true
);
}
@ -312,6 +307,13 @@ mod gen_list {
assert_concat_worked(2, 3);
assert_concat_worked(3, 3);
assert_concat_worked(4, 4);
}
#[test]
fn list_concat_large() {
// these values produce mono ASTs so large that
// it can cause a stack overflow. This has been solved
// for current code, but may become a problem again in the future.
assert_concat_worked(150, 150);
assert_concat_worked(129, 350);
assert_concat_worked(350, 129);
@ -634,6 +636,120 @@ mod gen_list {
);
}
#[test]
fn gen_swap() {
assert_evals_to!(
indoc!(
r#"
swap : Int, Int, List a -> List a
swap = \i, j, list ->
when Pair (List.get list i) (List.get list j) is
Pair (Ok atI) (Ok atJ) ->
list
|> List.set i atJ
|> List.set j atI
_ ->
[]
swap 0 1 [ 1, 2 ]
"#
),
&[2, 1],
&'static [i64]
);
}
// #[test]
// fn gen_partition() {
// assert_evals_to!(
// indoc!(
// r#"
// swap : Int, Int, List a -> List a
// swap = \i, j, list ->
// when Pair (List.get list i) (List.get list j) is
// Pair (Ok atI) (Ok atJ) ->
// list
// |> List.set i atJ
// |> List.set j atI
//
// _ ->
// []
// partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
// partition = \low, high, initialList ->
// when List.get initialList high is
// Ok pivot ->
// when partitionHelp (low - 1) low initialList high pivot is
// Pair newI newList ->
// Pair (newI + 1) (swap (newI + 1) high newList)
//
// Err _ ->
// Pair (low - 1) initialList
//
//
// partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ]
// partitionHelp = \i, j, list, high, pivot ->
// if j < high then
// when List.get list j is
// Ok value ->
// if value <= pivot then
// partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot
// else
// partitionHelp i (j + 1) list high pivot
//
// Err _ ->
// Pair i list
// else
// Pair i list
//
// # when partition 0 0 [ 1,2,3,4,5 ] is
// # Pair list _ -> list
// [ 1,3 ]
// "#
// ),
// &[2, 1],
// &'static [i64]
// );
// }
// #[test]
// fn gen_partition() {
// assert_evals_to!(
// indoc!(
// r#"
// swap : Int, Int, List a -> List a
// swap = \i, j, list ->
// when Pair (List.get list i) (List.get list j) is
// Pair (Ok atI) (Ok atJ) ->
// list
// |> List.set i atJ
// |> List.set j atI
//
// _ ->
// []
// partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
// partition = \low, high, initialList ->
// when List.get initialList high is
// Ok pivot ->
// when partitionHelp (low - 1) low initialList high pivot is
// Pair newI newList ->
// Pair (newI + 1) (swap (newI + 1) high newList)
//
// Err _ ->
// Pair (low - 1) initialList
//
//
// partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ]
//
// # when partition 0 0 [ 1,2,3,4,5 ] is
// # Pair list _ -> list
// [ 1,3 ]
// "#
// ),
// &[2, 1],
// &'static [i64]
// );
// }
#[test]
fn gen_quicksort() {
with_larger_debug_stack(|| {
@ -642,7 +758,8 @@ mod gen_list {
r#"
quicksort : List (Num a) -> List (Num a)
quicksort = \list ->
quicksortHelp list 0 (List.len list - 1)
n = List.len list
quicksortHelp list 0 (n - 1)
quicksortHelp : List (Num a), Int, Int -> List (Num a)
@ -680,7 +797,7 @@ mod gen_list {
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ]
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
@ -695,14 +812,257 @@ mod gen_list {
else
Pair i list
quicksort [ 7, 4, 21, 19 ]
"#
),
&[4, 7, 19, 21],
&'static [i64]
&'static [i64],
|x| x,
true
);
})
}
// #[test]
// fn foobar2() {
// with_larger_debug_stack(|| {
// assert_evals_to!(
// indoc!(
// r#"
// quicksort : List (Num a) -> List (Num a)
// quicksort = \list ->
// quicksortHelp list 0 (List.len list - 1)
//
//
// quicksortHelp : List (Num a), Int, Int -> List (Num a)
// quicksortHelp = \list, low, high ->
// if low < high then
// when partition low high list is
// Pair partitionIndex partitioned ->
// partitioned
// |> quicksortHelp low (partitionIndex - 1)
// |> quicksortHelp (partitionIndex + 1) high
// else
// list
//
//
// swap : Int, Int, List a -> List a
// swap = \i, j, list ->
// when Pair (List.get list i) (List.get list j) is
// Pair (Ok atI) (Ok atJ) ->
// list
// |> List.set i atJ
// |> List.set j atI
//
// _ ->
// []
//
// partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
// partition = \low, high, initialList ->
// when List.get initialList high is
// Ok pivot ->
// when partitionHelp (low - 1) low initialList high pivot is
// Pair newI newList ->
// Pair (newI + 1) (swap (newI + 1) high newList)
//
// Err _ ->
// Pair (low - 1) initialList
//
//
// partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ]
// partitionHelp = \i, j, list, high, pivot ->
// # if j < high then
// if False then
// when List.get list j is
// Ok value ->
// if value <= pivot then
// partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot
// else
// partitionHelp i (j + 1) list high pivot
//
// Err _ ->
// Pair i list
// else
// Pair i list
//
//
//
// quicksort [ 7, 4, 21, 19 ]
// "#
// ),
// &[19, 7, 4, 21],
// &'static [i64],
// |x| x,
// true
// );
// })
// }
// #[test]
// fn foobar() {
// with_larger_debug_stack(|| {
// assert_evals_to!(
// indoc!(
// r#"
// quicksort : List (Num a) -> List (Num a)
// quicksort = \list ->
// quicksortHelp list 0 (List.len list - 1)
//
//
// quicksortHelp : List (Num a), Int, Int -> List (Num a)
// quicksortHelp = \list, low, high ->
// if low < high then
// when partition low high list is
// Pair partitionIndex partitioned ->
// partitioned
// |> quicksortHelp low (partitionIndex - 1)
// |> quicksortHelp (partitionIndex + 1) high
// else
// list
//
//
// swap : Int, Int, List a -> List a
// swap = \i, j, list ->
// when Pair (List.get list i) (List.get list j) is
// Pair (Ok atI) (Ok atJ) ->
// list
// |> List.set i atJ
// |> List.set j atI
//
// _ ->
// []
//
// partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
// partition = \low, high, initialList ->
// when List.get initialList high is
// Ok pivot ->
// when partitionHelp (low - 1) low initialList high pivot is
// Pair newI newList ->
// Pair (newI + 1) (swap (newI + 1) high newList)
//
// Err _ ->
// Pair (low - 1) initialList
//
//
// partitionHelp : Int, Int, List (Num a), Int, Int -> [ Pair Int (List (Num a)) ]
// partitionHelp = \i, j, list, high, pivot ->
// if j < high then
// when List.get list j is
// Ok value ->
// if value <= pivot then
// partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot
// else
// partitionHelp i (j + 1) list high pivot
//
// Err _ ->
// Pair i list
// else
// Pair i list
//
//
//
// when List.first (quicksort [0x1]) is
// _ -> 4
// "#
// ),
// 4,
// i64,
// |x| x,
// false
// );
// })
// }
#[test]
fn empty_list_increment_decrement() {
assert_evals_to!(
indoc!(
r#"
x : List Int
x = []
List.len x + List.len x
"#
),
0,
i64
);
}
#[test]
fn list_literal_increment_decrement() {
assert_evals_to!(
indoc!(
r#"
x : List Int
x = [1,2,3]
List.len x + List.len x
"#
),
6,
i64
);
}
#[test]
fn list_pass_to_function() {
assert_evals_to!(
indoc!(
r#"
x : List Int
x = [1,2,3]
id : List Int -> List Int
id = \y -> y
id x
"#
),
&[1, 2, 3],
&'static [i64],
|x| x,
true
);
}
#[test]
fn list_pass_to_set() {
assert_evals_to!(
indoc!(
r#"
x : List Int
x = [1,2,3]
id : List Int -> List Int
id = \y -> List.set y 0 0
id x
"#
),
&[0, 2, 3],
&'static [i64],
|x| x,
true
);
}
#[test]
fn list_wrap_in_tag() {
assert_evals_to!(
indoc!(
r#"
id : List Int -> [ Pair (List Int) Int ]
id = \y -> Pair y 4
when id [1,2,3] is
Pair v _ -> v
"#
),
&[1, 2, 3],
&'static [i64],
|x| x,
true
);
}
}

View file

@ -13,19 +13,15 @@ mod helpers;
#[cfg(test)]
mod gen_num {
use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut};
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
/*
use inkwell::passes::PassManager;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_collections::all::ImMap;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::expr::{Expr, Procs};
use roc_mono::layout::Layout;
use roc_types::subs::Subs;
*/
#[test]
fn f64_sqrt() {
@ -44,7 +40,7 @@ mod gen_num {
}
#[test]
fn f64_round() {
fn f64_round_old() {
assert_evals_to!("Num.round 3.6", 4, i64);
}
@ -68,6 +64,26 @@ mod gen_num {
#[test]
fn gen_if_fn() {
assert_evals_to!(
indoc!(
r#"
limitedNegate = \num ->
x =
if num == 1 then
-1
else if num == -1 then
1
else
num
x
limitedNegate 1
"#
),
-1,
i64
);
assert_evals_to!(
indoc!(
r#"
@ -462,7 +478,7 @@ mod gen_num {
}
#[test]
fn if_guard_bind_variable() {
fn if_guard_bind_variable_false() {
assert_evals_to!(
indoc!(
r#"
@ -474,7 +490,10 @@ mod gen_num {
42,
i64
);
}
#[test]
fn if_guard_bind_variable_true() {
assert_evals_to!(
indoc!(
r#"

View file

@ -13,19 +13,6 @@ mod helpers;
#[cfg(test)]
mod gen_primitives {
use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut};
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
use inkwell::passes::PassManager;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_collections::all::ImMap;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::expr::{Expr, Procs};
use roc_mono::layout::Layout;
use roc_types::subs::Subs;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

View file

@ -13,20 +13,6 @@ mod helpers;
#[cfg(test)]
mod gen_records {
use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut};
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
use inkwell::passes::PassManager;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_collections::all::ImMap;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::expr::{Expr, Procs};
use roc_mono::layout::Layout;
use roc_types::subs::Subs;
#[test]
fn basic_record() {
assert_evals_to!(
@ -201,7 +187,10 @@ mod gen_records {
5,
i64
);
}
#[test]
fn when_on_record_with_guard_pattern() {
assert_evals_to!(
indoc!(
r#"
@ -212,7 +201,10 @@ mod gen_records {
5,
i64
);
}
#[test]
fn let_with_record_pattern() {
assert_evals_to!(
indoc!(
r#"
@ -391,4 +383,20 @@ mod gen_records {
bool
);
}
#[test]
fn return_record() {
assert_evals_to!(
indoc!(
r#"
x = 4
y = 3
{ x, y }
"#
),
(4, 3),
(i64, i64)
);
}
}

View file

@ -13,19 +13,23 @@ mod helpers;
#[cfg(test)]
mod gen_tags {
use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut};
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
use inkwell::passes::PassManager;
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_collections::all::ImMap;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::expr::{Expr, Procs};
use roc_mono::layout::Layout;
use roc_types::subs::Subs;
#[test]
fn applied_tag_nothing_ir() {
assert_evals_to!(
indoc!(
r#"
Maybe a : [ Just a, Nothing ]
x : Maybe Int
x = Nothing
0x1
"#
),
1,
i64
);
}
#[test]
fn applied_tag_nothing() {
@ -63,6 +67,24 @@ mod gen_tags {
);
}
#[test]
fn applied_tag_just_ir() {
assert_evals_to!(
indoc!(
r#"
Maybe a : [ Just a, Nothing ]
y : Maybe Int
y = Just 0x4
0x1
"#
),
1,
i64
);
}
#[test]
fn applied_tag_just_unit() {
assert_evals_to!(
@ -621,4 +643,77 @@ mod gen_tags {
i64
);
}
#[test]
fn join_point_if() {
assert_evals_to!(
indoc!(
r#"
x =
if True then 1 else 2
x
"#
),
1,
i64
);
}
#[test]
fn join_point_when() {
assert_evals_to!(
indoc!(
r#"
x : [ Red, White, Blue ]
x = Blue
y =
when x is
Red -> 1
White -> 2
Blue -> 3.1
y
"#
),
3.1,
f64
);
}
#[test]
fn join_point_with_cond_expr() {
assert_evals_to!(
indoc!(
r#"
y =
when 1 + 2 is
3 -> 3
1 -> 1
_ -> 0
y
"#
),
3,
i64
);
assert_evals_to!(
indoc!(
r#"
y =
if 1 + 2 > 0 then
3
else
0
y
"#
),
3,
i64
);
}
}

View file

@ -1,11 +1,34 @@
#[macro_export]
macro_rules! assert_llvm_evals_to {
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
use roc_types::subs::Subs;
pub fn helper_without_uniqueness<'a>(
arena: &'a bumpalo::Bump,
src: &str,
leak: bool,
context: &'a inkwell::context::Context,
) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) {
use crate::helpers::{can_expr, infer_expr, CanExprOut};
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_gen::llvm::build::Scope;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::layout::Layout;
let target = target_lexicon::Triple::host();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let arena = Bump::new();
let CanExprOut { loc_expr, var_store, var, constraint, home, interns, problems, .. } = can_expr($src);
let errors = problems.into_iter().filter(|problem| {
let CanExprOut {
loc_expr,
var_store,
var,
constraint,
home,
interns,
problems,
..
} = can_expr(src);
let errors = problems
.into_iter()
.filter(|problem| {
use roc_problem::can::Problem::*;
// Ignore "unused" problems
@ -13,7 +36,8 @@ macro_rules! assert_llvm_evals_to {
UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) => false,
_ => true,
}
}).collect::<Vec<roc_problem::can::Problem>>();
})
.collect::<Vec<roc_problem::can::Problem>>();
assert_eq!(errors, Vec::new(), "Encountered errors: {:?}", errors);
@ -21,50 +45,57 @@ macro_rules! assert_llvm_evals_to {
let mut unify_problems = Vec::new();
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
assert_eq!(unify_problems, Vec::new(), "Encountered type mismatches: {:?}", unify_problems);
assert_eq!(
unify_problems,
Vec::new(),
"Encountered type mismatches: {:?}",
unify_problems
);
let context = Context::create();
let module = roc_gen::llvm::build::module_from_builtins(&context, "app");
let module = roc_gen::llvm::build::module_from_builtins(context, "app");
let builder = context.create_builder();
let opt_level = if cfg!(debug_assertions) {
roc_gen::llvm::build::OptLevel::Normal
} else {
roc_gen::llvm::build::OptLevel::Optimize
};
let fpm = PassManager::create(&module);
roc_gen::llvm::build::add_passes(&fpm, opt_level);
fpm.initialize();
let module = arena.alloc(module);
let (module_pass, function_pass) =
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
// Compute main_fn_type before moving subs to Env
let layout = Layout::new(&arena, content, &subs)
.unwrap_or_else(|err| panic!("Code gen error in NON-OPTIMIZED test: could not convert to layout. Err was {:?}", err));
let execution_engine =
module
let layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
panic!(
"Code gen error in NON-OPTIMIZED test: could not convert to layout. Err was {:?}",
err
)
});
let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
let main_fn_type = basic_type_from_layout(&arena, &context, &layout, ptr_bytes)
.fn_type(&[], false);
let main_fn_type =
basic_type_from_layout(&arena, context, &layout, ptr_bytes).fn_type(&[], false);
let main_fn_name = "$Test.main";
// Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env {
arena: &arena,
builder: &builder,
context: &context,
context: context,
interns,
module: arena.alloc(module),
ptr_bytes
module,
ptr_bytes,
leak: leak,
};
let mut procs = Procs::default();
let mut procs = roc_mono::ir::Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
// Populate Procs and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new();
let mut mono_env = roc_mono::expr::Env {
let mut mono_env = roc_mono::ir::Env {
arena: &arena,
subs: &mut subs,
problems: &mut mono_problems,
@ -72,19 +103,25 @@ macro_rules! assert_llvm_evals_to {
ident_ids: &mut ident_ids,
};
let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs);
let main_body = roc_mono::ir::Stmt::new(&mut mono_env, loc_expr.value, &mut procs);
let main_body =
roc_mono::inc_dec::visit_declaration(mono_env.arena, mono_env.arena.alloc(main_body));
let mut headers = {
let num_headers = match &procs.pending_specializations {
Some(map) => map.len(),
None => 0
None => 0,
};
Vec::with_capacity(num_headers)
};
let mut layout_cache = roc_mono::layout::LayoutCache::default();
let mut procs = roc_mono::expr::specialize_all(&mut mono_env, procs, &mut layout_cache);
let procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
assert_eq!(procs.runtime_errors, roc_collections::all::MutMap::default());
assert_eq!(
procs.runtime_errors,
roc_collections::all::MutMap::default()
);
// Put this module's ident_ids back in the interns, so we can use them in env.
// This must happen *after* building the headers, because otherwise there's
@ -94,28 +131,20 @@ macro_rules! assert_llvm_evals_to {
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
for ((symbol, layout), proc) in procs.specialized.drain() {
use roc_mono::expr::InProgressProc::*;
match proc {
InProgress => {
panic!("A specialization was still marked InProgress after monomorphization had completed: {:?} with layout {:?}", symbol, layout);
}
Done(proc) => {
for ((symbol, layout), proc) in procs.get_specialized_procs(env.arena).drain() {
let (fn_val, arg_basic_types) =
build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val, arg_basic_types));
}
}
}
// Build each proc using its header info.
for (proc, fn_val, arg_basic_types) in headers {
build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types);
if fn_val.verify(true) {
fpm.run_on(&fn_val);
function_pass.run_on(&fn_val);
} else {
eprintln!(
"\n\nFunction {:?} failed LLVM verification in NON-OPTIMIZED build. Its content was:\n", fn_val.get_name().to_str().unwrap()
@ -131,7 +160,8 @@ macro_rules! assert_llvm_evals_to {
// Add main to the module.
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
let cc = roc_gen::llvm::build::get_call_conventions(target.default_calling_convention().unwrap());
let cc =
roc_gen::llvm::build::get_call_conventions(target.default_calling_convention().unwrap());
main_fn.set_call_conventions(cc);
@ -140,10 +170,10 @@ macro_rules! assert_llvm_evals_to {
builder.position_at_end(basic_block);
let ret = roc_gen::llvm::build::build_expr(
let ret = roc_gen::llvm::build::build_exp_stmt(
&env,
&mut layout_ids,
&ImMap::default(),
&mut Scope::default(),
main_fn,
&main_body,
);
@ -154,11 +184,13 @@ macro_rules! assert_llvm_evals_to {
// env.module.print_to_stderr();
if main_fn.verify(true) {
fpm.run_on(&main_fn);
function_pass.run_on(&main_fn);
} else {
panic!("Main function {} failed LLVM verification in NON-OPTIMIZED build. Uncomment things nearby to see more details.", main_fn_name);
}
module_pass.run_on(env.module);
// Verify the module
if let Err(errors) = env.module.verify() {
panic!("Errors defining module: {:?}", errors);
@ -167,29 +199,30 @@ macro_rules! assert_llvm_evals_to {
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
unsafe {
let main: JitFunction<unsafe extern "C" fn() -> $ty> = execution_engine
.get_function(main_fn_name)
.ok()
.ok_or(format!("Unable to JIT compile `{}`", main_fn_name))
.expect("errored");
assert_eq!($transform(main.call()), $expected);
}
};
(main_fn_name, execution_engine.clone())
}
// 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) => {
let arena = Bump::new();
pub fn helper_with_uniqueness<'a>(
arena: &'a bumpalo::Bump,
src: &str,
leak: bool,
context: &'a inkwell::context::Context,
) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) {
use crate::helpers::{infer_expr, uniq_expr};
use inkwell::types::BasicType;
use inkwell::OptimizationLevel;
use roc_gen::llvm::build::Scope;
use roc_gen::llvm::build::{build_proc, build_proc_header};
use roc_gen::llvm::convert::basic_type_from_layout;
use roc_mono::layout::Layout;
let target = target_lexicon::Triple::host();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let (loc_expr, _output, problems, subs, var, constraint, home, interns) = uniq_expr($src);
let errors = problems.into_iter().filter(|problem| {
let (loc_expr, _output, problems, subs, var, constraint, home, interns) = uniq_expr(src);
let errors = problems
.into_iter()
.filter(|problem| {
use roc_problem::can::Problem::*;
// Ignore "unused" problems
@ -197,78 +230,89 @@ macro_rules! assert_opt_evals_to {
UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) => false,
_ => true,
}
}).collect::<Vec<roc_problem::can::Problem>>();
})
.collect::<Vec<roc_problem::can::Problem>>();
assert_eq!(errors, Vec::new(), "Encountered errors: {:?}", errors);
let mut unify_problems = Vec::new();
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
assert_eq!(unify_problems, Vec::new(), "Encountered one or more type mismatches: {:?}", unify_problems);
assert_eq!(
unify_problems,
Vec::new(),
"Encountered one or more type mismatches: {:?}",
unify_problems
);
let context = Context::create();
let module = roc_gen::llvm::build::module_from_builtins(&context, "app");
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(context, "app"));
let builder = context.create_builder();
let opt_level = if cfg!(debug_assertions) {
roc_gen::llvm::build::OptLevel::Normal
} else {
roc_gen::llvm::build::OptLevel::Optimize
};
let fpm = PassManager::create(&module);
roc_gen::llvm::build::add_passes(&fpm, opt_level);
fpm.initialize();
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
// Compute main_fn_type before moving subs to Env
let layout = Layout::new(&arena, content, &subs)
.unwrap_or_else(|err| panic!("Code gen error in OPTIMIZED test: could not convert to layout. Err was {:?}", err));
let layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
panic!(
"Code gen error in OPTIMIZED test: could not convert to layout. Err was {:?}",
err
)
});
let execution_engine =
module
let execution_engine = module
.create_jit_execution_engine(OptimizationLevel::None)
.expect("Error creating JIT execution engine for test");
let main_fn_type = basic_type_from_layout(&arena, &context, &layout, ptr_bytes)
.fn_type(&[], false);
let main_fn_type = basic_type_from_layout(&arena, context, &layout, ptr_bytes)
.fn_type(&[], false)
.clone();
let main_fn_name = "$Test.main";
// Compile and add all the Procs before adding main
let mut env = roc_gen::llvm::build::Env {
arena: &arena,
builder: &builder,
context: &context,
context: context,
interns,
module: arena.alloc(module),
ptr_bytes
module,
ptr_bytes,
leak: leak,
};
let mut procs = Procs::default();
let mut procs = roc_mono::ir::Procs::default();
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
// Populate Procs and get the low-level Expr from the canonical Expr
let mut mono_problems = Vec::new();
let mut mono_env = roc_mono::expr::Env {
let mut mono_env = roc_mono::ir::Env {
arena: &arena,
subs: &mut subs,
problems: &mut mono_problems,
home,
ident_ids: &mut ident_ids,
};
let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs);
let main_body = roc_mono::ir::Stmt::new(&mut mono_env, loc_expr.value, &mut procs);
let main_body =
roc_mono::inc_dec::visit_declaration(mono_env.arena, mono_env.arena.alloc(main_body));
let mut headers = {
let num_headers = match &procs.pending_specializations {
Some(map) => map.len(),
None => 0
None => 0,
};
Vec::with_capacity(num_headers)
};
let mut layout_cache = roc_mono::layout::LayoutCache::default();
let mut procs = roc_mono::expr::specialize_all(&mut mono_env, procs, &mut layout_cache);
let procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
assert_eq!(procs.runtime_errors, roc_collections::all::MutMap::default());
assert_eq!(
procs.runtime_errors,
roc_collections::all::MutMap::default()
);
// Put this module's ident_ids back in the interns, so we can use them in env.
// This must happen *after* building the headers, because otherwise there's
@ -278,21 +322,12 @@ macro_rules! assert_opt_evals_to {
// Add all the Proc headers to the module.
// We have to do this in a separate pass first,
// because their bodies may reference each other.
for ((symbol, layout), proc) in procs.specialized.drain() {
use roc_mono::expr::InProgressProc::*;
match proc {
InProgress => {
panic!("A specialization was still marked InProgress after monomorphization had completed: {:?} with layout {:?}", symbol, layout);
}
Done(proc) => {
for ((symbol, layout), proc) in procs.get_specialized_procs(env.arena).drain() {
let (fn_val, arg_basic_types) =
build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
headers.push((proc, fn_val, arg_basic_types));
}
}
}
// Build each proc using its header info.
for (proc, fn_val, arg_basic_types) in headers {
@ -302,7 +337,8 @@ macro_rules! assert_opt_evals_to {
fpm.run_on(&fn_val);
} else {
eprintln!(
"\n\nFunction {:?} failed LLVM verification in OPTIMIZED build. Its content was:\n", fn_val.get_name().to_str().unwrap()
"\n\nFunction {:?} failed LLVM verification in OPTIMIZED build. Its content was:\n",
fn_val.get_name().to_str().unwrap()
);
fn_val.print_to_stderr();
@ -315,7 +351,8 @@ macro_rules! assert_opt_evals_to {
// Add main to the module.
let main_fn = env.module.add_function(main_fn_name, main_fn_type, None);
let cc = roc_gen::llvm::build::get_call_conventions(target.default_calling_convention().unwrap());
let cc =
roc_gen::llvm::build::get_call_conventions(target.default_calling_convention().unwrap());
main_fn.set_call_conventions(cc);
@ -324,16 +361,18 @@ macro_rules! assert_opt_evals_to {
builder.position_at_end(basic_block);
let ret = roc_gen::llvm::build::build_expr(
let ret = roc_gen::llvm::build::build_exp_stmt(
&env,
&mut layout_ids,
&ImMap::default(),
&mut Scope::default(),
main_fn,
&main_body,
);
builder.build_return(Some(&ret));
// you're in the version with uniqueness!
// Uncomment this to see the module's un-optimized LLVM instruction output:
// env.module.print_to_stderr();
@ -343,6 +382,8 @@ macro_rules! assert_opt_evals_to {
panic!("main function {} failed LLVM verification in OPTIMIZED build. Uncomment nearby statements to see more details.", main_fn_name);
}
mpm.run_on(module);
// Verify the module
if let Err(errors) = env.module.verify() {
panic!("Errors defining module: {:?}", errors);
@ -351,6 +392,26 @@ macro_rules! assert_opt_evals_to {
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
(main_fn_name, execution_engine)
}
// 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 inkwell::execution_engine::JitFunction;
let arena = Bump::new();
let context = Context::create();
let (main_fn_name, execution_engine) =
$crate::helpers::eval::helper_with_uniqueness(&arena, $src, $leak, &context);
unsafe {
let main: JitFunction<unsafe extern "C" fn() -> $ty> = execution_engine
.get_function(main_fn_name)
@ -361,6 +422,40 @@ macro_rules! assert_opt_evals_to {
assert_eq!($transform(main.call()), $expected);
}
};
($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) => {
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::execution_engine::JitFunction;
let arena = Bump::new();
let context = Context::create();
let (main_fn_name, execution_engine) =
$crate::helpers::eval::helper_without_uniqueness(&arena, $src, $leak, &context);
unsafe {
let main: JitFunction<unsafe extern "C" fn() -> $ty> = execution_engine
.get_function(main_fn_name)
.ok()
.ok_or(format!("Unable to JIT compile `{}`", main_fn_name))
.expect("errored");
assert_eq!($transform(main.call()), $expected);
}
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
assert_llvm_evals_to!($src, $expected, $ty, $transform, true);
};
}
#[macro_export]
@ -386,4 +481,13 @@ macro_rules! assert_evals_to {
assert_opt_evals_to!($src, $expected, $ty, $transform);
}
};
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
// Same as above, except with an additional transformation argument.
{
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak);
}
{
assert_opt_evals_to!($src, $expected, $ty, $transform, $leak);
}
};
}

View file

@ -1,5 +1,5 @@
app Quicksort
provides [ swap, partition, quicksort ]
provides [ swap, partition, partitionHelp, quicksort ]
imports []
quicksort : List (Num a), Int, Int -> List (Num a)
@ -27,23 +27,25 @@ partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
go = \i, j, list ->
if j < high then
when List.get list j is
Ok value ->
if value <= pivot then
go (i + 1) (j + 1) (swap (i + 1) j list)
else
go i (j + 1) list
Err _ ->
Pair i list
else
Pair i list
when go (low - 1) low initialList is
when partitionHelp (low - 1) low initialList high pivot is
Pair newI newList ->
Pair (newI + 1) (swap (newI + 1) high newList)
Err _ ->
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
Ok value ->
if value <= pivot then
partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot
else
partitionHelp i (j + 1) list high pivot
Err _ ->
Pair i list
else
Pair i list

View file

@ -27,23 +27,25 @@ partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
go = \i, j, list ->
if j < high then
when List.get list j is
Ok value ->
if value <= pivot then
go (i + 1) (j + 1) (swap (i + 1) j list)
else
go i (j + 1) list
Err _ ->
Pair i list
else
Pair i list
when go (low - 1) low initialList is
when partitionHelp (low - 1) low initialList high pivot is
Pair newI newList ->
Pair (newI + 1) (swap (newI + 1) high newList)
Err _ ->
Pair (low - 1) initialList
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
Ok value ->
if value <= pivot then
partitionHelp (i + 1) (j + 1) (swap (i + 1) j list) high pivot
else
partitionHelp i (j + 1) list high pivot
Err _ ->
Pair i list
else
Pair i list

View file

@ -214,6 +214,7 @@ mod test_load {
hashmap! {
"swap" => "Int, Int, List a -> List a",
"partition" => "Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]",
"partitionHelp" => "Int, Int, List (Num a), Int, Num a -> [ Pair Int (List (Num a)) ]",
"quicksort" => "List (Num a), Int, Int -> List (Num a)",
},
);
@ -229,6 +230,7 @@ mod test_load {
hashmap! {
"swap" => "Int, Int, List a -> List a",
"partition" => "Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]",
"partitionHelp" => "Int, Int, List (Num a), Int, Num a -> [ Pair Int (List (Num a)) ]",
"quicksort" => "List (Num a), Int, Int -> List (Num a)",
},
);

View file

@ -233,6 +233,8 @@ mod test_uniq_load {
hashmap! {
"swap" => "Attr * (Attr * Int, Attr * Int, Attr * (List (Attr Shared a)) -> Attr * (List (Attr Shared a)))",
"partition" => "Attr * (Attr Shared Int, Attr Shared Int, Attr b (List (Attr Shared (Num (Attr Shared a)))) -> Attr * [ Pair (Attr * Int) (Attr b (List (Attr Shared (Num (Attr Shared a))))) ])",
"partitionHelp" => "Attr Shared (Attr b Int, Attr Shared Int, Attr c (List (Attr Shared (Num (Attr Shared a)))), Attr Shared Int, Attr Shared (Num (Attr Shared a)) -> Attr * [ Pair (Attr b Int) (Attr c (List (Attr Shared (Num (Attr Shared a))))) ])",
"quicksort" => "Attr Shared (Attr b (List (Attr Shared (Num (Attr Shared a)))), Attr Shared Int, Attr Shared Int -> Attr b (List (Attr Shared (Num (Attr Shared a)))))",
},
);

View file

@ -146,6 +146,17 @@ impl fmt::Debug for Symbol {
}
}
impl fmt::Display for Symbol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let module_id = self.module_id();
let ident_id = self.ident_id();
match ident_id {
IdentId(value) => write!(f, "{:?}.{:?}", module_id, value),
}
}
}
fn fallback_debug_fmt(symbol: Symbol, f: &mut fmt::Formatter) -> fmt::Result {
let module_id = symbol.module_id();
let ident_id = symbol.ident_id();

View file

@ -13,6 +13,7 @@ roc_types = { path = "../types" }
roc_can = { path = "../can" }
roc_unify = { path = "../unify" }
roc_problem = { path = "../problem" }
ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.2", features = ["collections"] }
[dev-dependencies]

View file

@ -1,7 +1,6 @@
use crate::expr::{DestructType, Env, Expr, Pattern};
use crate::layout::{Builtin, Layout};
use crate::pattern::{Ctor, RenderAs, TagId, Union};
use bumpalo::Bump;
use crate::exhaustive::{Ctor, RenderAs, TagId, Union};
use crate::ir::{DestructType, Env, Expr, JoinPointId, Literal, Param, Pattern, Procs, Stmt};
use crate::layout::{Builtin, Layout, LayoutCache};
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::TagName;
use roc_module::low_level::LowLevel;
@ -31,8 +30,12 @@ pub fn compile<'a>(raw_branches: Vec<(Guard<'a>, Pattern<'a>, u64)>) -> Decision
pub enum Guard<'a> {
NoGuard,
Guard {
stores: &'a [(Symbol, Layout<'a>, Expr<'a>)],
expr: Expr<'a>,
/// Symbol that stores a boolean
/// when true this branch is picked, otherwise skipped
symbol: Symbol,
/// after assigning to symbol, the stmt jumps to this label
id: JoinPointId,
stmt: Stmt<'a>,
},
}
@ -57,7 +60,7 @@ pub enum Test<'a> {
IsCtor {
tag_id: u8,
tag_name: TagName,
union: crate::pattern::Union,
union: crate::exhaustive::Union,
arguments: Vec<(Pattern<'a>, Layout<'a>)>,
},
IsInt(i64),
@ -72,8 +75,12 @@ pub enum Test<'a> {
// A pattern that always succeeds (like `_`) can still have a guard
Guarded {
opt_test: Option<Box<Test<'a>>>,
stores: &'a [(Symbol, Layout<'a>, Expr<'a>)],
expr: Expr<'a>,
/// Symbol that stores a boolean
/// when true this branch is picked, otherwise skipped
symbol: Symbol,
/// after assigning to symbol, the stmt jumps to this label
id: JoinPointId,
stmt: Stmt<'a>,
},
}
use std::hash::{Hash, Hasher};
@ -355,11 +362,12 @@ fn test_at_path<'a>(selected_path: &Path, branch: &Branch<'a>, all_tests: &mut V
None => {}
Some((_, guard, pattern)) => {
let guarded = |test| {
if let Guard::Guard { stores, expr } = guard {
if let Guard::Guard { symbol, id, stmt } = guard {
Guarded {
opt_test: Some(Box::new(test)),
stores,
expr: expr.clone(),
stmt: stmt.clone(),
symbol: *symbol,
id: *id,
}
} else {
test
@ -369,11 +377,12 @@ fn test_at_path<'a>(selected_path: &Path, branch: &Branch<'a>, all_tests: &mut V
match pattern {
// TODO use guard!
Identifier(_) | Underscore | Shadowed(_, _) | UnsupportedPattern(_) => {
if let Guard::Guard { stores, expr } = guard {
if let Guard::Guard { symbol, id, stmt } = guard {
all_tests.push(Guarded {
opt_test: None,
stores,
expr: expr.clone(),
stmt: stmt.clone(),
symbol: *symbol,
id: *id,
});
}
}
@ -577,6 +586,8 @@ fn to_relevant_branch_help<'a>(
start.push((Path::Unbox(Box::new(path.clone())), guard, arg.0));
start.extend(end);
}
} else if union.alternatives.len() == 1 {
todo!("this should need a special index, right?")
} else {
let sub_positions =
arguments
@ -855,31 +866,30 @@ enum Decider<'a, T> {
#[derive(Clone, Debug, PartialEq)]
enum Choice<'a> {
Inline(Stores<'a>, Expr<'a>),
Inline(Stmt<'a>),
Jump(Label),
}
type Stores<'a> = &'a [(Symbol, Layout<'a>, Expr<'a>)];
type StoresVec<'a> = bumpalo::collections::Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>;
pub fn optimize_when<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
cond_symbol: Symbol,
cond_layout: Layout<'a>,
ret_layout: Layout<'a>,
opt_branches: Vec<(Pattern<'a>, Guard<'a>, Stores<'a>, Expr<'a>)>,
) -> Expr<'a> {
opt_branches: bumpalo::collections::Vec<'a, (Pattern<'a>, Guard<'a>, Stmt<'a>)>,
) -> Stmt<'a> {
let (patterns, _indexed_branches) = opt_branches
.into_iter()
.enumerate()
.map(|(index, (pattern, guard, stores, branch))| {
(
(guard, pattern, index as u64),
(index as u64, stores, branch),
)
.map(|(index, (pattern, guard, branch))| {
((guard, pattern, index as u64), (index as u64, branch))
})
.unzip();
let indexed_branches: Vec<(u64, Stores<'a>, Expr<'a>)> = _indexed_branches;
let indexed_branches: Vec<(u64, Stmt<'a>)> = _indexed_branches;
let decision_tree = compile(patterns);
let decider = tree_to_decider(decision_tree);
@ -888,9 +898,8 @@ pub fn optimize_when<'a>(
let mut choices = MutMap::default();
let mut jumps = Vec::new();
for (index, stores, branch) in indexed_branches.into_iter() {
let ((branch_index, choice), opt_jump) =
create_choices(&target_counts, index, stores, branch);
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);
@ -901,16 +910,16 @@ pub fn optimize_when<'a>(
let choice_decider = insert_choices(&choices, decider);
let (stores, expr) = decide_to_branching(
decide_to_branching(
env,
procs,
layout_cache,
cond_symbol,
cond_layout,
ret_layout,
choice_decider,
&jumps,
);
Expr::Store(stores, env.arena.alloc(expr))
)
}
fn path_to_expr<'a>(
@ -918,29 +927,36 @@ fn path_to_expr<'a>(
symbol: Symbol,
path: &Path,
layout: &Layout<'a>,
) -> Expr<'a> {
path_to_expr_help(env, symbol, path, layout.clone()).0
) -> (StoresVec<'a>, Symbol) {
let (symbol, stores, _) = path_to_expr_help2(env, symbol, path, layout.clone());
(stores, symbol)
}
fn path_to_expr_help<'a>(
fn path_to_expr_help2<'a>(
env: &mut Env<'a, '_>,
symbol: Symbol,
path: &Path,
layout: Layout<'a>,
) -> (Expr<'a>, Layout<'a>) {
mut symbol: Symbol,
mut path: &Path,
mut layout: Layout<'a>,
) -> (Symbol, StoresVec<'a>, Layout<'a>) {
let mut stores = bumpalo::collections::Vec::new_in(env.arena);
loop {
match path {
Path::Unbox(unboxed) => path_to_expr_help(env, symbol, unboxed, layout),
Path::Empty => (Expr::Load(symbol), layout),
Path::Unbox(unboxed) => {
path = unboxed;
}
Path::Empty => break,
Path::Index {
index,
tag_id,
path: nested,
} => {
let (outer_expr, outer_layout) = path_to_expr_help(env, symbol, nested, layout);
let (is_unwrapped, field_layouts) = match outer_layout {
Layout::Union(layouts) => (layouts.is_empty(), layouts[*tag_id as usize].to_vec()),
let (is_unwrapped, field_layouts) = match layout.clone() {
Layout::Union(layouts) => {
(layouts.is_empty(), layouts[*tag_id as usize].to_vec())
}
Layout::Struct(layouts) => (true, layouts.to_vec()),
other => (true, vec![other]),
};
@ -952,23 +968,29 @@ fn path_to_expr_help<'a>(
let inner_expr = Expr::AccessAtIndex {
index: *index,
field_layouts: env.arena.alloc(field_layouts),
expr: env.arena.alloc(outer_expr),
structure: symbol,
is_unwrapped,
};
(inner_expr, inner_layout)
symbol = env.unique_symbol();
stores.push((symbol, inner_layout.clone(), inner_expr));
layout = inner_layout;
path = nested;
}
}
}
(symbol, stores, layout)
}
fn test_to_equality<'a>(
env: &mut Env<'a, '_>,
cond_symbol: Symbol,
cond_layout: &Layout<'a>,
path: &Path,
test: Test<'a>,
tests: &mut Vec<(Expr<'a>, Expr<'a>, Layout<'a>)>,
) {
) -> (StoresVec<'a>, Symbol, Symbol, Layout<'a>) {
match test {
Test::IsCtor {
tag_id,
@ -980,7 +1002,7 @@ fn test_to_equality<'a>(
// (e.g. record pattern guard matches)
debug_assert!(union.alternatives.len() > 1);
let lhs = Expr::Int(tag_id as i64);
let lhs = Expr::Literal(Literal::Int(tag_id as i64));
let mut field_layouts =
bumpalo::collections::Vec::with_capacity_in(arguments.len(), env.arena);
@ -995,89 +1017,131 @@ fn test_to_equality<'a>(
let rhs = Expr::AccessAtIndex {
index: 0,
field_layouts: field_layouts.into_bump_slice(),
expr: env.arena.alloc(Expr::Load(cond_symbol)),
structure: cond_symbol,
is_unwrapped: union.alternatives.len() == 1,
};
tests.push((lhs, rhs, Layout::Builtin(Builtin::Int64)));
let lhs_symbol = env.unique_symbol();
let rhs_symbol = env.unique_symbol();
let mut stores = bumpalo::collections::Vec::with_capacity_in(2, env.arena);
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int64), lhs));
stores.push((rhs_symbol, Layout::Builtin(Builtin::Int64), rhs));
(
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Int64),
)
}
Test::IsInt(test_int) => {
let lhs = Expr::Int(test_int);
let rhs = path_to_expr(env, cond_symbol, &path, &cond_layout);
let lhs = Expr::Literal(Literal::Int(test_int));
let lhs_symbol = env.unique_symbol();
let (mut stores, rhs_symbol) = path_to_expr(env, cond_symbol, &path, &cond_layout);
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int64), lhs));
tests.push((lhs, rhs, Layout::Builtin(Builtin::Int64)));
(
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Int64),
)
}
Test::IsFloat(test_int) => {
// TODO maybe we can actually use i64 comparison here?
let test_float = f64::from_bits(test_int as u64);
let lhs = Expr::Float(test_float);
let rhs = path_to_expr(env, cond_symbol, &path, &cond_layout);
let lhs = Expr::Literal(Literal::Float(test_float));
let lhs_symbol = env.unique_symbol();
let (mut stores, rhs_symbol) = path_to_expr(env, cond_symbol, &path, &cond_layout);
stores.push((lhs_symbol, Layout::Builtin(Builtin::Float64), lhs));
tests.push((lhs, rhs, Layout::Builtin(Builtin::Float64)));
(
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Float64),
)
}
Test::IsByte {
tag_id: test_byte, ..
} => {
let lhs = Expr::Byte(test_byte);
let rhs = path_to_expr(env, cond_symbol, &path, &cond_layout);
let lhs = Expr::Literal(Literal::Byte(test_byte));
let lhs_symbol = env.unique_symbol();
let (mut stores, rhs_symbol) = path_to_expr(env, cond_symbol, &path, &cond_layout);
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int8), lhs));
tests.push((lhs, rhs, Layout::Builtin(Builtin::Int8)));
(
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Int8),
)
}
Test::IsBit(test_bit) => {
let lhs = Expr::Bool(test_bit);
let rhs = path_to_expr(env, cond_symbol, &path, &cond_layout);
let lhs = Expr::Literal(Literal::Bool(test_bit));
let lhs_symbol = env.unique_symbol();
let (mut stores, rhs_symbol) = path_to_expr(env, cond_symbol, &path, &cond_layout);
stores.push((lhs_symbol, Layout::Builtin(Builtin::Int1), lhs));
tests.push((lhs, rhs, Layout::Builtin(Builtin::Int1)));
(
stores,
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Int1),
)
}
Test::IsStr(test_str) => {
let lhs = Expr::Str(env.arena.alloc(test_str));
let rhs = path_to_expr(env, cond_symbol, &path, &cond_layout);
let lhs = Expr::Literal(Literal::Str(env.arena.alloc(test_str)));
let lhs_symbol = env.unique_symbol();
let (mut stores, rhs_symbol) = path_to_expr(env, cond_symbol, &path, &cond_layout);
tests.push((lhs, rhs, Layout::Builtin(Builtin::Str)));
}
stores.push((lhs_symbol, Layout::Builtin(Builtin::Str), lhs));
Test::Guarded {
opt_test,
(
stores,
expr,
} => {
if let Some(nested) = opt_test {
test_to_equality(env, cond_symbol, cond_layout, path, *nested, tests);
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Str),
)
}
let lhs = Expr::Bool(true);
let rhs = Expr::Store(stores, env.arena.alloc(expr));
tests.push((lhs, rhs, Layout::Builtin(Builtin::Int1)));
}
Test::Guarded { .. } => unreachable!("should be handled elsewhere"),
}
}
// TODO procs and layout are currently unused, but potentially required
// for defining optional fields?
// if not, do remove
#[allow(clippy::too_many_arguments)]
fn decide_to_branching<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
cond_symbol: Symbol,
cond_layout: Layout<'a>,
ret_layout: Layout<'a>,
decider: Decider<'a, Choice<'a>>,
jumps: &Vec<(u64, Stores<'a>, Expr<'a>)>,
) -> (Stores<'a>, Expr<'a>) {
jumps: &Vec<(u64, Stmt<'a>)>,
) -> Stmt<'a> {
use Choice::*;
use Decider::*;
match decider {
Leaf(Jump(label)) => {
// we currently inline the jumps: does fewer jumps but produces a larger artifact
let (_, stores, expr) = jumps
let (_, expr) = jumps
.iter()
.find(|(l, _, _)| l == &label)
.find(|(l, _)| l == &label)
.expect("jump not in list of jumps");
(stores, expr.clone())
expr.clone()
}
Leaf(Inline(stores, expr)) => (stores, expr),
Leaf(Inline(expr)) => expr,
Chain {
test_chain,
success,
@ -1085,14 +1149,10 @@ fn decide_to_branching<'a>(
} => {
// generate a switch based on the test chain
let mut tests = Vec::with_capacity(test_chain.len());
for (path, test) in test_chain {
test_to_equality(env, cond_symbol, &cond_layout, &path, test, &mut tests);
}
let (pass_stores, pass_expr) = decide_to_branching(
let pass_expr = decide_to_branching(
env,
procs,
layout_cache,
cond_symbol,
cond_layout.clone(),
ret_layout.clone(),
@ -1100,8 +1160,10 @@ fn decide_to_branching<'a>(
jumps,
);
let (fail_stores, fail_expr) = decide_to_branching(
let fail_expr = decide_to_branching(
env,
procs,
layout_cache,
cond_symbol,
cond_layout.clone(),
ret_layout.clone(),
@ -1109,30 +1171,148 @@ fn decide_to_branching<'a>(
jumps,
);
let fail = (fail_stores, &*env.arena.alloc(fail_expr));
let pass = (pass_stores, &*env.arena.alloc(pass_expr));
let fail = &*env.arena.alloc(fail_expr);
let pass = &*env.arena.alloc(pass_expr);
let condition = boolean_all(env.arena, tests);
let branching_symbol = env.unique_symbol();
let branching_layout = Layout::Builtin(Builtin::Int1);
let branch_symbol = env.unique_symbol();
let stores = [(branch_symbol, Layout::Builtin(Builtin::Int1), condition)];
let cond_layout = Layout::Builtin(Builtin::Int1);
(
env.arena.alloc(stores),
Expr::Store(
&[],
env.arena.alloc(Expr::Cond {
let mut cond = Stmt::Cond {
cond_symbol,
branch_symbol,
cond_layout,
cond_layout: cond_layout.clone(),
branching_symbol,
branching_layout,
pass,
fail,
ret_layout,
}),
),
)
};
let true_symbol = env.unique_symbol();
let mut tests = Vec::with_capacity(test_chain.len());
let mut guard = None;
// Assumption: there is at most 1 guard, and it is the outer layer.
for (path, test) in test_chain {
match test {
Test::Guarded {
opt_test,
id,
symbol,
stmt,
} => {
if let Some(nested) = opt_test {
tests.push(test_to_equality(
env,
cond_symbol,
&cond_layout,
&path,
*nested,
));
}
// let (stores, rhs_symbol) = path_to_expr(env, cond_symbol, &path, &cond_layout);
guard = Some((symbol, id, stmt));
}
_ => tests.push(test_to_equality(
env,
cond_symbol,
&cond_layout,
&path,
test,
)),
}
}
let mut current_symbol = branching_symbol;
// TODO There must be some way to remove this iterator/loop
let nr = (tests.len() as i64) - 1 + (guard.is_some() as i64);
let accum_symbols = std::iter::once(true_symbol)
.chain((0..nr).map(|_| env.unique_symbol()))
.rev()
.collect::<Vec<_>>();
let mut accum_it = accum_symbols.into_iter();
// the guard is the final thing that we check, so needs to be layered on first!
if let Some((_, id, stmt)) = guard {
let accum = accum_it.next().unwrap();
let test_symbol = env.unique_symbol();
let and_expr =
Expr::RunLowLevel(LowLevel::And, env.arena.alloc([test_symbol, accum]));
// write to the branching symbol
cond = Stmt::Let(
current_symbol,
and_expr,
Layout::Builtin(Builtin::Int1),
env.arena.alloc(cond),
);
// calculate the guard value
let param = Param {
symbol: test_symbol,
layout: Layout::Builtin(Builtin::Int1),
borrow: false,
};
cond = Stmt::Join {
id,
parameters: env.arena.alloc([param]),
remainder: env.arena.alloc(stmt),
continuation: env.arena.alloc(cond),
};
// load all the variables (the guard might need them);
current_symbol = accum;
}
for ((new_stores, lhs, rhs, _layout), accum) in tests.into_iter().rev().zip(accum_it) {
let test_symbol = env.unique_symbol();
let test = Expr::RunLowLevel(
LowLevel::Eq,
bumpalo::vec![in env.arena; lhs, rhs].into_bump_slice(),
);
let and_expr =
Expr::RunLowLevel(LowLevel::And, env.arena.alloc([test_symbol, accum]));
// write to the branching symbol
cond = Stmt::Let(
current_symbol,
and_expr,
Layout::Builtin(Builtin::Int1),
env.arena.alloc(cond),
);
// write to the test symbol
cond = Stmt::Let(
test_symbol,
test,
Layout::Builtin(Builtin::Int1),
env.arena.alloc(cond),
);
for (symbol, layout, expr) in new_stores.into_iter() {
cond = Stmt::Let(symbol, expr, layout, env.arena.alloc(cond));
}
current_symbol = accum;
}
cond = Stmt::Let(
true_symbol,
Expr::Literal(Literal::Bool(true)),
Layout::Builtin(Builtin::Int1),
env.arena.alloc(cond),
);
cond
}
FanOut {
path,
@ -1141,23 +1321,28 @@ fn decide_to_branching<'a>(
} => {
// the cond_layout can change in the process. E.g. if the cond is a Tag, we actually
// switch on the tag discriminant (currently an i64 value)
let (cond, cond_layout) = path_to_expr_help(env, cond_symbol, &path, cond_layout);
// NOTE the tag discriminant is not actually loaded, `cond` can point to a tag
let (cond, cond_stores_vec, cond_layout) =
path_to_expr_help2(env, cond_symbol, &path, cond_layout);
let (default_stores, default_expr) = decide_to_branching(
let default_branch = decide_to_branching(
env,
procs,
layout_cache,
cond_symbol,
cond_layout.clone(),
ret_layout.clone(),
*fallback,
jumps,
);
let default_branch = (default_stores, &*env.arena.alloc(default_expr));
let mut branches = bumpalo::collections::Vec::with_capacity_in(tests.len(), env.arena);
for (test, decider) in tests {
let (stores, branch) = decide_to_branching(
let branch = decide_to_branching(
env,
procs,
layout_cache,
cond_symbol,
cond_layout.clone(),
ret_layout.clone(),
@ -1174,24 +1359,28 @@ fn decide_to_branching<'a>(
other => todo!("other {:?}", other),
};
branches.push((tag, stores, branch));
branches.push((tag, branch));
}
let mut switch = Stmt::Switch {
cond_layout,
cond_symbol: cond,
branches: branches.into_bump_slice(),
default_branch: env.arena.alloc(default_branch),
ret_layout,
};
for (symbol, layout, expr) in cond_stores_vec.into_iter() {
switch = Stmt::Let(symbol, expr, layout, env.arena.alloc(switch));
}
// make a jump table based on the tests
(
&[],
Expr::Switch {
cond: env.arena.alloc(cond),
cond_layout,
branches: branches.into_bump_slice(),
default_branch,
ret_layout,
},
)
switch
}
}
}
/*
fn boolean_all<'a>(arena: &'a Bump, tests: Vec<(Expr<'a>, Expr<'a>, Layout<'a>)>) -> Expr<'a> {
let mut expr = Expr::Bool(true);
@ -1212,6 +1401,7 @@ fn boolean_all<'a>(arena: &'a Bump, tests: Vec<(Expr<'a>, Expr<'a>, Layout<'a>)>
expr
}
*/
/// TREE TO DECIDER
///
@ -1385,19 +1575,15 @@ fn count_targets_help(decision_tree: &Decider<u64>, targets: &mut MutMap<u64, u6
fn create_choices<'a>(
target_counts: &MutMap<u64, u64>,
target: u64,
stores: Stores<'a>,
branch: Expr<'a>,
) -> ((u64, Choice<'a>), Option<(u64, Stores<'a>, Expr<'a>)>) {
branch: Stmt<'a>,
) -> ((u64, Choice<'a>), Option<(u64, Stmt<'a>)>) {
match target_counts.get(&target) {
None => unreachable!(
"this should never happen: {:?} not in {:?}",
target, target_counts
),
Some(1) => ((target, Choice::Inline(stores, branch)), None),
Some(_) => (
(target, Choice::Jump(target)),
Some((target, stores, branch)),
),
Some(1) => ((target, Choice::Inline(branch)), None),
Some(_) => ((target, Choice::Jump(target)), Some((target, branch))),
}
}

View file

@ -1,4 +1,4 @@
use crate::expr::DestructType;
use crate::ir::DestructType;
use roc_collections::all::{Index, MutMap};
use roc_module::ident::{Lowercase, TagName};
use roc_region::all::{Located, Region};
@ -44,8 +44,8 @@ pub enum Literal {
Str(Box<str>),
}
fn simplify<'a>(pattern: &crate::expr::Pattern<'a>) -> Pattern {
use crate::expr::Pattern::*;
fn simplify<'a>(pattern: &crate::ir::Pattern<'a>) -> Pattern {
use crate::ir::Pattern::*;
match pattern {
IntLiteral(v) => Literal(Literal::Int(*v)),
@ -137,7 +137,7 @@ pub enum Guard {
pub fn check<'a>(
region: Region,
patterns: &[(Located<crate::expr::Pattern<'a>>, Guard)],
patterns: &[(Located<crate::ir::Pattern<'a>>, Guard)],
context: Context,
) -> Result<(), Vec<Error>> {
let mut errors = Vec::new();
@ -153,7 +153,7 @@ pub fn check<'a>(
pub fn check_patterns<'a>(
region: Region,
context: Context,
patterns: &[(Located<crate::expr::Pattern<'a>>, Guard)],
patterns: &[(Located<crate::ir::Pattern<'a>>, Guard)],
errors: &mut Vec<Error>,
) {
match to_nonredundant_rows(region, patterns) {
@ -286,7 +286,7 @@ fn recover_ctor(
/// INVARIANT: Produces a list of rows where (forall row. length row == 1)
fn to_nonredundant_rows<'a>(
overall_region: Region,
patterns: &[(Located<crate::expr::Pattern<'a>>, Guard)],
patterns: &[(Located<crate::ir::Pattern<'a>>, Guard)],
) -> Result<Vec<Vec<Pattern>>, Error> {
let mut checked_rows = Vec::with_capacity(patterns.len());

View file

@ -0,0 +1,901 @@
use crate::ir::{Expr, JoinPointId, Param, Proc, Stmt};
use crate::layout::Layout;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::Symbol;
pub fn free_variables(stmt: &Stmt<'_>) -> MutSet<Symbol> {
let (mut occuring, bound) = occuring_variables(stmt);
for ref s in bound {
occuring.remove(s);
}
occuring
}
pub fn occuring_variables(stmt: &Stmt<'_>) -> (MutSet<Symbol>, MutSet<Symbol>) {
let mut stack = std::vec![stmt];
let mut result = MutSet::default();
let mut bound_variables = MutSet::default();
while let Some(stmt) = stack.pop() {
use Stmt::*;
match stmt {
Let(symbol, expr, _, cont) => {
occuring_variables_expr(expr, &mut result);
result.insert(*symbol);
bound_variables.insert(*symbol);
stack.push(cont);
}
Ret(symbol) => {
result.insert(*symbol);
}
Inc(symbol, cont) | Dec(symbol, cont) => {
result.insert(*symbol);
stack.push(cont);
}
Jump(_, arguments) => {
result.extend(arguments.iter().copied());
}
Join {
parameters,
continuation,
remainder,
..
} => {
result.extend(parameters.iter().map(|p| p.symbol));
stack.push(continuation);
stack.push(remainder);
}
Switch {
cond_symbol,
branches,
default_branch,
..
} => {
result.insert(*cond_symbol);
stack.extend(branches.iter().map(|(_, s)| s));
stack.push(default_branch);
}
Cond {
cond_symbol,
branching_symbol,
pass,
fail,
..
} => {
result.insert(*cond_symbol);
result.insert(*branching_symbol);
stack.push(pass);
stack.push(fail);
}
RuntimeError(_) => {}
}
}
(result, bound_variables)
}
pub fn occuring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) {
use Expr::*;
match expr {
FunctionPointer(symbol, _)
| AccessAtIndex {
structure: symbol, ..
} => {
result.insert(*symbol);
}
FunctionCall { args, .. } => {
// NOTE thouth the function name does occur, it is a static constant in the program
// for liveness, it should not be included here.
result.extend(args.iter().copied());
}
Tag { arguments, .. }
| Struct(arguments)
| Array {
elems: arguments, ..
} => {
result.extend(arguments.iter().copied());
}
RunLowLevel(_, _) | EmptyArray | RuntimeErrorFunction(_) | Literal(_) => {}
}
}
/* Insert explicit RC instructions. So, it assumes the input code does not contain `inc` nor `dec` instructions.
This transformation is applied before lower level optimizations
that introduce the instructions `release` and `set`
*/
#[derive(Clone, Debug, Copy)]
struct VarInfo {
reference: bool, // true if the variable may be a reference (aka pointer) at runtime
persistent: bool, // true if the variable is statically known to be marked a Persistent at runtime
consume: bool, // true if the variable RC must be "consumed"
}
type VarMap = MutMap<Symbol, VarInfo>;
type LiveVarSet = MutSet<Symbol>;
type JPLiveVarMap = MutMap<JoinPointId, LiveVarSet>;
#[derive(Clone, Debug)]
pub struct Context<'a> {
arena: &'a Bump,
vars: VarMap,
jp_live_vars: JPLiveVarMap, // map: join point => live variables
local_context: LocalContext<'a>, // we use it to store the join point declarations
function_params: MutMap<Symbol, &'a [Param<'a>]>,
}
fn update_live_vars<'a>(expr: &Expr<'a>, v: &LiveVarSet) -> LiveVarSet {
let mut v = v.clone();
occuring_variables_expr(expr, &mut v);
v
}
fn is_first_occurence(xs: &[Symbol], i: usize) -> bool {
match xs.get(i) {
None => unreachable!(),
Some(s) => i == xs.iter().position(|v| s == v).unwrap(),
}
}
fn get_num_consumptions<F>(x: Symbol, ys: &[Symbol], consume_param_pred: F) -> usize
where
F: Fn(usize) -> bool,
{
let mut n = 0;
for (i, y) in ys.iter().enumerate() {
if x == *y && consume_param_pred(i) {
n += 1;
}
}
n
}
fn is_borrow_param_help<F>(x: Symbol, ys: &[Symbol], consume_param_pred: F) -> bool
where
F: Fn(usize) -> bool,
{
ys.iter()
.enumerate()
.any(|(i, y)| x == *y && !consume_param_pred(i))
}
fn is_borrow_param(x: Symbol, ys: &[Symbol], ps: &[Param]) -> bool {
// default to owned arguments
let pred = |i: usize| match ps.get(i) {
Some(param) => !param.borrow,
None => true,
};
is_borrow_param_help(x, ys, pred)
}
// We do not need to consume the projection of a variable that is not consumed
fn consume_expr(m: &VarMap, e: &Expr<'_>) -> bool {
match e {
Expr::AccessAtIndex { structure: x, .. } => match m.get(x) {
Some(info) => info.consume,
None => true,
},
_ => true,
}
}
impl<'a> Context<'a> {
pub fn new(arena: &'a Bump) -> Self {
Self {
arena,
vars: MutMap::default(),
jp_live_vars: MutMap::default(),
local_context: LocalContext::default(),
function_params: MutMap::default(),
}
}
fn get_var_info(&self, symbol: Symbol) -> VarInfo {
match self.vars.get(&symbol) {
Some(info) => *info,
None => panic!(
"Symbol {:?} {} has no info in {:?}",
symbol, symbol, self.vars
),
}
}
fn add_inc(&self, symbol: Symbol, stmt: &'a Stmt<'a>) -> &'a Stmt<'a> {
let info = self.get_var_info(symbol);
if info.persistent {
// persistent values are never reference counted
return stmt;
}
// if this symbol is never a reference, don't emit
if !info.reference {
return stmt;
}
self.arena.alloc(Stmt::Inc(symbol, stmt))
}
fn add_dec(&self, symbol: Symbol, stmt: &'a Stmt<'a>) -> &'a Stmt<'a> {
let info = self.get_var_info(symbol);
if info.persistent {
// persistent values are never reference counted
return stmt;
}
// if this symbol is never a reference, don't emit
if !info.reference {
return stmt;
}
self.arena.alloc(Stmt::Dec(symbol, stmt))
}
fn add_inc_before_consume_all_help<F>(
&self,
xs: &[Symbol],
consume_param_pred: F,
mut b: &'a Stmt<'a>,
live_vars_after: &LiveVarSet,
) -> &'a Stmt<'a>
where
F: Fn(usize) -> bool + Clone,
{
for (i, x) in xs.iter().enumerate() {
let info = self.get_var_info(*x);
if !info.reference || !is_first_occurence(xs, i) {
// do nothing
} else {
// number of times the argument is used (in the body?)
let num_consumptions = get_num_consumptions(*x, xs, consume_param_pred.clone());
// `x` is not a variable that must be consumed by the current procedure
// `x` is live after executing instruction
// `x` is used in a position that is passed as a borrow reference
let lives_on = !info.consume
|| live_vars_after.contains(x)
|| is_borrow_param_help(*x, xs, consume_param_pred.clone());
let num_incs = if lives_on {
num_consumptions
} else {
num_consumptions - 1
};
// Lean can increment by more than 1 at once. Is that needed?
debug_assert!(num_incs <= 1);
if num_incs == 1 {
b = self.add_inc(*x, b);
}
}
}
b
}
fn add_inc_before_consume_all(
&self,
xs: &[Symbol],
b: &'a Stmt<'a>,
live_vars_after: &LiveVarSet,
) -> &'a Stmt<'a> {
self.add_inc_before_consume_all_help(xs, |_: usize| true, b, live_vars_after)
}
fn add_inc_before_help<F>(
&self,
xs: &[Symbol],
consume_param_pred: F,
mut b: &'a Stmt<'a>,
live_vars_after: &LiveVarSet,
) -> &'a Stmt<'a>
where
F: Fn(usize) -> bool + Clone,
{
for (i, x) in xs.iter().enumerate() {
let info = self.get_var_info(*x);
if !info.reference || !is_first_occurence(xs, i) {
// do nothing
} else {
let num_consumptions = get_num_consumptions(*x, xs, consume_param_pred.clone()); // number of times the argument is used
let num_incs = if !info.consume || // `x` is not a variable that must be consumed by the current procedure
live_vars_after.contains(x) || // `x` is live after executing instruction
is_borrow_param_help( *x ,xs, consume_param_pred.clone())
// `x` is used in a position that is passed as a borrow reference
{
num_consumptions
} else {
num_consumptions - 1
};
// verify that this is indeed always 1
debug_assert!(num_incs <= 1);
if num_incs == 1 {
b = self.add_inc(*x, b)
}
}
}
b
}
fn add_inc_before(
&self,
xs: &[Symbol],
ps: &[Param],
b: &'a Stmt<'a>,
live_vars_after: &LiveVarSet,
) -> &'a Stmt<'a> {
// default to owned arguments
let pred = |i: usize| match ps.get(i) {
Some(param) => !param.borrow,
None => true,
};
self.add_inc_before_help(xs, pred, b, live_vars_after)
}
fn add_dec_if_needed(
&self,
x: Symbol,
b: &'a Stmt<'a>,
b_live_vars: &LiveVarSet,
) -> &'a Stmt<'a> {
if self.must_consume(x) && !b_live_vars.contains(&x) {
self.add_dec(x, b)
} else {
b
}
}
fn must_consume(&self, x: Symbol) -> bool {
let info = self.get_var_info(x);
info.reference && info.consume
}
fn add_dec_after_application(
&self,
xs: &[Symbol],
ps: &[Param],
mut b: &'a Stmt<'a>,
b_live_vars: &LiveVarSet,
) -> &'a Stmt<'a> {
for (i, x) in xs.iter().enumerate() {
/* We must add a `dec` if `x` must be consumed, it is alive after the application,
and it has been borrowed by the application.
Remark: `x` may occur multiple times in the application (e.g., `f x y x`).
This is why we check whether it is the first occurrence. */
if self.must_consume(*x)
&& is_first_occurence(xs, i)
&& is_borrow_param(*x, xs, ps)
&& !b_live_vars.contains(x)
{
b = self.add_dec(*x, b)
}
}
b
}
#[allow(clippy::many_single_char_names)]
fn visit_variable_declaration(
&self,
z: Symbol,
v: Expr<'a>,
l: Layout<'a>,
b: &'a Stmt<'a>,
b_live_vars: &LiveVarSet,
) -> (&'a Stmt<'a>, LiveVarSet) {
use Expr::*;
let mut live_vars = update_live_vars(&v, &b_live_vars);
live_vars.remove(&z);
let new_b = match v {
Tag { arguments: ys, .. } | Struct(ys) | Array { elems: ys, .. } => self
.add_inc_before_consume_all(
ys,
self.arena.alloc(Stmt::Let(z, v, l, b)),
&b_live_vars,
),
AccessAtIndex { structure: x, .. } => {
let b = self.add_dec_if_needed(x, b, b_live_vars);
let info_x = self.get_var_info(x);
let b = if info_x.consume {
self.add_inc(z, b)
} else {
b
};
self.arena.alloc(Stmt::Let(z, v, l, b))
}
RunLowLevel(_, _) => {
// THEORY: runlowlevel only occurs
//
// - in a custom hard-coded function
// - when we insert them as compiler authors
//
// if we're carefule to only use RunLowLevel for non-rc'd types
// (e.g. when building a cond/switch, we check equality on integers, and to boolean and)
// then RunLowLevel should not change in any way the refcounts.
// let b = self.add_dec_after_application(ys, ps, b, b_live_vars);
self.arena.alloc(Stmt::Let(z, v, l, b))
}
FunctionCall {
args: ys,
call_type,
arg_layouts,
..
} => {
// this is where the borrow signature would come in
//let ps := (getDecl ctx f).params;
use crate::ir::CallType;
use crate::layout::Builtin;
let symbol = match call_type {
CallType::ByName(s) => s,
CallType::ByPointer(s) => s,
};
let ps = Vec::from_iter_in(
arg_layouts.iter().map(|layout| {
let borrow = match layout {
Layout::Builtin(Builtin::List(_, _)) => true,
_ => false,
};
Param {
symbol,
borrow,
layout: layout.clone(),
}
}),
self.arena,
)
.into_bump_slice();
let b = self.add_dec_after_application(ys, ps, b, b_live_vars);
self.arena.alloc(Stmt::Let(z, v, l, b))
}
EmptyArray | FunctionPointer(_, _) | Literal(_) | RuntimeErrorFunction(_) => {
// EmptyArray is always stack-allocated
// function pointers are persistent
self.arena.alloc(Stmt::Let(z, v, l, b))
}
};
(new_b, live_vars)
}
fn update_var_info(&self, symbol: Symbol, layout: &Layout<'a>, expr: &Expr<'a>) -> Self {
let mut ctx = self.clone();
// TODO actually make these non-constant
// can this type be reference-counted at runtime?
let reference = layout.contains_refcounted();
// is this value a constant?
let persistent = false;
// must this value be consumed?
let consume = consume_expr(&ctx.vars, expr);
let info = VarInfo {
reference,
persistent,
consume,
};
ctx.vars.insert(symbol, info);
ctx
}
fn update_var_info_with_params(&self, ps: &[Param]) -> Self {
//def updateVarInfoWithParams (ctx : Context) (ps : Array Param) : Context :=
//let m := ps.foldl (fun (m : VarMap) p => m.insert p.x { ref := p.ty.isObj, consume := !p.borrow }) ctx.varMap;
//{ ctx with varMap := m }
let mut ctx = self.clone();
for p in ps.iter() {
let info = VarInfo {
reference: p.layout.contains_refcounted(),
consume: !p.borrow,
persistent: false,
};
ctx.vars.insert(p.symbol, info);
}
ctx
}
/* Add `dec` instructions for parameters that are references, are not alive in `b`, and are not borrow.
That is, we must make sure these parameters are consumed. */
fn add_dec_for_dead_params(
&self,
ps: &[Param<'a>],
mut b: &'a Stmt<'a>,
b_live_vars: &LiveVarSet,
) -> &'a Stmt<'a> {
for p in ps.iter() {
if !p.borrow && p.layout.contains_refcounted() && !b_live_vars.contains(&p.symbol) {
b = self.add_dec(p.symbol, b)
}
}
b
}
fn add_dec_for_alt(
&self,
case_live_vars: &LiveVarSet,
alt_live_vars: &LiveVarSet,
mut b: &'a Stmt<'a>,
) -> &'a Stmt<'a> {
for x in case_live_vars.iter() {
if !alt_live_vars.contains(x) && self.must_consume(*x) {
b = self.add_dec(*x, b);
}
}
b
}
fn visit_stmt(&self, stmt: &'a Stmt<'a>) -> (&'a Stmt<'a>, LiveVarSet) {
use Stmt::*;
// let-chains can be very long, especially for large (list) literals
// in (rust) debug mode, this function can overflow the stack for such values
// so we have to write an explicit loop.
{
let mut cont = stmt;
let mut triples = Vec::new_in(self.arena);
while let Stmt::Let(symbol, expr, layout, new_cont) = cont {
triples.push((symbol, expr, layout));
cont = new_cont;
}
if !triples.is_empty() {
let mut ctx = self.clone();
for (symbol, expr, layout) in triples.iter() {
ctx = ctx.update_var_info(**symbol, layout, expr);
}
let (mut b, mut b_live_vars) = ctx.visit_stmt(cont);
for (symbol, expr, layout) in triples.into_iter().rev() {
let pair = ctx.visit_variable_declaration(
*symbol,
(*expr).clone(),
(*layout).clone(),
b,
&b_live_vars,
);
b = pair.0;
b_live_vars = pair.1;
}
return (b, b_live_vars);
}
}
match stmt {
Let(symbol, expr, layout, cont) => {
let ctx = self.update_var_info(*symbol, layout, expr);
let (b, b_live_vars) = ctx.visit_stmt(cont);
ctx.visit_variable_declaration(
*symbol,
expr.clone(),
layout.clone(),
b,
&b_live_vars,
)
}
Join {
id: j,
parameters: xs,
remainder: b,
continuation: v,
} => {
let xs = *xs;
let v_orig = v;
let (v, v_live_vars) = {
let ctx = self.update_var_info_with_params(xs);
ctx.visit_stmt(v)
};
let v = self.add_dec_for_dead_params(xs, v, &v_live_vars);
let mut ctx = self.clone();
// NOTE deviation from lean, insert into local context
ctx.local_context.join_points.insert(*j, (xs, v_orig));
update_jp_live_vars(*j, xs, v, &mut ctx.jp_live_vars);
let (b, b_live_vars) = ctx.visit_stmt(b);
(
ctx.arena.alloc(Join {
id: *j,
parameters: xs,
remainder: b,
continuation: v,
}),
b_live_vars,
)
}
Ret(x) => {
let info = self.get_var_info(*x);
let mut live_vars = MutSet::default();
live_vars.insert(*x);
if info.reference && !info.consume {
(self.add_inc(*x, stmt), live_vars)
} else {
(stmt, live_vars)
}
}
Jump(j, xs) => {
let empty = MutSet::default();
let j_live_vars = match self.jp_live_vars.get(j) {
Some(vars) => vars,
None => &empty,
};
let ps = self.local_context.join_points.get(j).unwrap().0;
let b = self.add_inc_before(xs, ps, stmt, j_live_vars);
let b_live_vars = collect_stmt(b, &self.jp_live_vars, MutSet::default());
(b, b_live_vars)
}
Cond {
pass,
fail,
cond_symbol,
cond_layout,
branching_symbol,
branching_layout,
ret_layout,
} => {
let case_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default());
let pass = {
// TODO should we use ctor info like Lean?
let ctx = self.clone();
let (b, alt_live_vars) = ctx.visit_stmt(pass);
ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b)
};
let fail = {
// TODO should we use ctor info like Lean?
let ctx = self.clone();
let (b, alt_live_vars) = ctx.visit_stmt(fail);
ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b)
};
let cond = self.arena.alloc(Cond {
cond_symbol: *cond_symbol,
cond_layout: cond_layout.clone(),
branching_symbol: *branching_symbol,
branching_layout: branching_layout.clone(),
pass,
fail,
ret_layout: ret_layout.clone(),
});
(cond, case_live_vars)
}
Switch {
cond_symbol,
cond_layout,
branches,
default_branch,
ret_layout,
} => {
let case_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default());
let branches = Vec::from_iter_in(
branches.iter().map(|(label, branch)| {
// TODO should we use ctor info like Lean?
let ctx = self.clone();
let (b, alt_live_vars) = ctx.visit_stmt(branch);
let b = ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b);
(*label, b.clone())
}),
self.arena,
)
.into_bump_slice();
let default_branch = {
// TODO should we use ctor info like Lean?
let ctx = self.clone();
let (b, alt_live_vars) = ctx.visit_stmt(default_branch);
ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b)
};
let switch = self.arena.alloc(Switch {
cond_symbol: *cond_symbol,
branches,
default_branch,
cond_layout: cond_layout.clone(),
ret_layout: ret_layout.clone(),
});
(switch, case_live_vars)
}
RuntimeError(_) | Inc(_, _) | Dec(_, _) => (stmt, MutSet::default()),
}
}
}
#[derive(Clone, Debug, Default)]
struct LocalContext<'a> {
join_points: MutMap<JoinPointId, (&'a [Param<'a>], &'a Stmt<'a>)>,
}
pub fn collect_stmt(
stmt: &Stmt<'_>,
jp_live_vars: &JPLiveVarMap,
mut vars: LiveVarSet,
) -> LiveVarSet {
use Stmt::*;
match stmt {
Let(symbol, expr, _, cont) => {
vars = collect_stmt(cont, jp_live_vars, vars);
vars.remove(symbol);
let mut result = MutSet::default();
occuring_variables_expr(expr, &mut result);
vars.extend(result);
vars
}
Ret(symbol) => {
vars.insert(*symbol);
vars
}
Inc(symbol, cont) | Dec(symbol, cont) => {
vars.insert(*symbol);
collect_stmt(cont, jp_live_vars, vars)
}
Jump(_, arguments) => {
vars.extend(arguments.iter().copied());
vars
}
Join {
id: j,
parameters,
remainder: b,
continuation: v,
} => {
let mut j_live_vars = collect_stmt(v, jp_live_vars, MutSet::default());
for param in parameters.iter() {
j_live_vars.remove(&param.symbol);
}
let mut jp_live_vars = jp_live_vars.clone();
jp_live_vars.insert(*j, j_live_vars);
collect_stmt(b, &jp_live_vars, vars)
}
Switch {
cond_symbol,
branches,
default_branch,
..
} => {
vars.insert(*cond_symbol);
for (_, branch) in branches.iter() {
vars.extend(collect_stmt(branch, jp_live_vars, vars.clone()));
}
vars.extend(collect_stmt(default_branch, jp_live_vars, vars.clone()));
vars
}
Cond {
cond_symbol,
branching_symbol,
pass,
fail,
..
} => {
vars.insert(*cond_symbol);
vars.insert(*branching_symbol);
vars.extend(collect_stmt(pass, jp_live_vars, vars.clone()));
vars.extend(collect_stmt(fail, jp_live_vars, vars.clone()));
vars
}
RuntimeError(_) => vars,
}
}
fn update_jp_live_vars(j: JoinPointId, ys: &[Param], v: &Stmt<'_>, m: &mut JPLiveVarMap) {
let j_live_vars = MutSet::default();
let mut j_live_vars = collect_stmt(v, m, j_live_vars);
for param in ys {
j_live_vars.remove(&param.symbol);
}
m.insert(j, j_live_vars);
}
pub fn visit_declaration<'a>(arena: &'a Bump, stmt: &'a Stmt<'a>) -> &'a Stmt<'a> {
let ctx = Context::new(arena);
let params = &[] as &[_];
let ctx = ctx.update_var_info_with_params(params);
let (b, b_live_vars) = ctx.visit_stmt(stmt);
ctx.add_dec_for_dead_params(params, b, &b_live_vars)
}
pub fn visit_proc<'a>(arena: &'a Bump, proc: &mut Proc<'a>) {
let ctx = Context::new(arena);
if proc.name.is_builtin() {
// we must take care of our own refcounting in builtins
return;
}
let params = Vec::from_iter_in(
proc.args.iter().map(|(layout, symbol)| Param {
symbol: *symbol,
layout: layout.clone(),
borrow: layout.contains_refcounted(),
}),
arena,
)
.into_bump_slice();
let stmt = arena.alloc(proc.body.clone());
let ctx = ctx.update_var_info_with_params(params);
let (b, b_live_vars) = ctx.visit_stmt(stmt);
let b = ctx.add_dec_for_dead_params(params, b, &b_live_vars);
proc.body = b.clone();
}

3223
compiler/mono/src/ir.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -27,6 +27,12 @@ pub enum Layout<'a> {
Pointer(&'a Layout<'a>),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub enum MemoryMode {
Unique,
Refcounted,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Builtin<'a> {
Int128,
@ -42,7 +48,7 @@ pub enum Builtin<'a> {
Str,
Map(&'a Layout<'a>, &'a Layout<'a>),
Set(&'a Layout<'a>),
List(&'a Layout<'a>),
List(MemoryMode, &'a Layout<'a>),
EmptyStr,
EmptyList,
EmptyMap,
@ -136,6 +142,31 @@ impl<'a> Layout<'a> {
Pointer(_) => pointer_size,
}
}
pub fn is_refcounted(&self) -> bool {
match self {
Layout::Builtin(Builtin::List(_, _)) => true,
_ => false,
}
}
/// Even if a value (say, a record) is not itself reference counted,
/// it may contains values/fields that are. Therefore when this record
/// goes out of scope, the refcount on those values/fields must be decremented.
pub fn contains_refcounted(&self) -> bool {
use Layout::*;
match self {
Builtin(builtin) => builtin.is_refcounted(),
Struct(fields) => fields.iter().any(|f| f.is_refcounted()),
Union(fields) => fields
.iter()
.map(|ls| ls.iter())
.flatten()
.any(|f| f.is_refcounted()),
FunctionPointer(_, _) | Pointer(_) => false,
}
}
}
/// Avoid recomputing Layout from Variable multiple times.
@ -210,7 +241,7 @@ impl<'a> Builtin<'a> {
Str | EmptyStr => Builtin::STR_WORDS * pointer_size,
Map(_, _) | EmptyMap => Builtin::MAP_WORDS * pointer_size,
Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size,
List(_) | EmptyList => Builtin::LIST_WORDS * pointer_size,
List(_, _) | EmptyList => Builtin::LIST_WORDS * pointer_size,
}
}
@ -220,7 +251,23 @@ impl<'a> Builtin<'a> {
match self {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Float128 | Float64 | Float32
| Float16 | EmptyStr | EmptyMap | EmptyList | EmptySet => true,
Str | Map(_, _) | Set(_) | List(_) => false,
Str | Map(_, _) | Set(_) | List(_, _) => false,
}
}
// Question: does is_refcounted exactly correspond with the "safe to memcpy" property?
pub fn is_refcounted(&self) -> bool {
use Builtin::*;
match self {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Float128 | Float64 | Float32
| Float16 | EmptyStr | EmptyMap | EmptyList | EmptySet => false,
List(mode, element_layout) => match mode {
MemoryMode::Refcounted => true,
MemoryMode::Unique => element_layout.contains_refcounted(),
},
Str | Map(_, _) | Set(_) => true,
}
}
}
@ -258,13 +305,26 @@ fn layout_from_flat_type<'a>(
debug_assert_eq!(args.len(), 2);
// The first argument is the uniqueness info;
// that doesn't affect layout, so we don't need it here.
// second is the base type
let wrapped_var = args[1];
// For now, layout is unaffected by uniqueness.
// (Incorporating refcounting may change this.)
// Unwrap and continue
Layout::from_var(arena, wrapped_var, subs)
// correct the memory mode of unique lists
match Layout::from_var(arena, wrapped_var, subs)? {
Layout::Builtin(Builtin::List(_, elem_layout)) => {
let uniqueness_var = args[0];
let uniqueness_content =
subs.get_without_compacting(uniqueness_var).content;
let mode = if uniqueness_content.is_unique(subs) {
MemoryMode::Unique
} else {
MemoryMode::Refcounted
};
Ok(Layout::Builtin(Builtin::List(mode, elem_layout)))
}
other => Ok(other),
}
}
_ => {
panic!("TODO layout_from_flat_type for {:?}", Apply(symbol, args));
@ -336,8 +396,8 @@ fn layout_from_flat_type<'a>(
Ok(layout_from_tag_union(arena, tags, subs))
}
RecursiveTagUnion(_, _, _) => {
panic!("TODO make Layout for non-empty Tag Union");
RecursiveTagUnion(_rec_var, _tags, _ext_var) => {
panic!("TODO make Layout for empty RecursiveTagUnion");
}
EmptyTagUnion => {
panic!("TODO make Layout for empty Tag Union");
@ -656,15 +716,15 @@ fn unwrap_num_tag<'a>(subs: &Subs, var: Variable) -> Result<Layout<'a>, LayoutPr
pub fn list_layout_from_elem<'a>(
arena: &'a Bump,
subs: &Subs,
var: Variable,
elem_var: Variable,
) -> Result<Layout<'a>, LayoutProblem> {
match subs.get_without_compacting(var).content {
match subs.get_without_compacting(elem_var).content {
Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, args)) => {
debug_assert_eq!(args.len(), 2);
let arg_var = args.get(1).unwrap();
let var = *args.get(1).unwrap();
list_layout_from_elem(arena, subs, *arg_var)
list_layout_from_elem(arena, subs, var)
}
Content::FlexVar(_) | Content::RigidVar(_) => {
// If this was still a (List *) then it must have been an empty list
@ -674,7 +734,28 @@ pub fn list_layout_from_elem<'a>(
let elem_layout = Layout::new(arena, content, subs)?;
// This is a normal list.
Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout))))
Ok(Layout::Builtin(Builtin::List(
MemoryMode::Refcounted,
arena.alloc(elem_layout),
)))
}
}
}
pub fn mode_from_var(var: Variable, subs: &Subs) -> MemoryMode {
match subs.get_without_compacting(var).content {
Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, args)) => {
debug_assert_eq!(args.len(), 2);
let uvar = *args.get(0).unwrap();
let content = subs.get_without_compacting(uvar).content;
if content.is_unique(subs) {
MemoryMode::Unique
} else {
MemoryMode::Refcounted
}
}
_ => MemoryMode::Refcounted,
}
}

View file

@ -11,12 +11,15 @@
// re-enable this when working on performance optimizations than have it block PRs.
#![allow(clippy::large_enum_variant)]
pub mod expr;
pub mod inc_dec;
pub mod ir;
pub mod layout;
// 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;
#[allow(clippy::ptr_arg)]
pub mod decision_tree;
#[allow(clippy::ptr_arg)]
pub mod pattern;
pub mod exhaustive;

View file

@ -0,0 +1,648 @@
use crate::expr::Env;
use crate::expr::Expr;
use bumpalo::collections::Vec;
use roc_collections::all::MutSet;
use roc_module::symbol::Symbol;
pub fn function_r<'a>(env: &mut Env<'a, '_>, body: &'a Expr<'a>) -> Expr<'a> {
use Expr::*;
match body {
Switch {
cond_symbol,
branches,
cond,
cond_layout,
default_branch,
ret_layout,
} => {
let stack_size = cond_layout.stack_size(env.pointer_size);
let mut new_branches = Vec::with_capacity_in(branches.len(), env.arena);
for (tag, stores, branch) in branches.iter() {
let new_branch = function_d(env, *cond_symbol, stack_size as _, branch);
new_branches.push((*tag, *stores, new_branch));
}
let new_default_branch = (
default_branch.0,
&*env.arena.alloc(function_d(
env,
*cond_symbol,
stack_size as _,
default_branch.1,
)),
);
Switch {
cond_symbol: *cond_symbol,
branches: new_branches.into_bump_slice(),
default_branch: new_default_branch,
ret_layout: ret_layout.clone(),
cond: *cond,
cond_layout: cond_layout.clone(),
}
}
Cond {
cond_symbol,
cond_layout,
branching_symbol,
branching_layout,
pass,
fail,
ret_layout,
} => {
let stack_size = cond_layout.stack_size(env.pointer_size);
let new_pass = (
pass.0,
&*env
.arena
.alloc(function_d(env, *cond_symbol, stack_size as _, pass.1)),
);
let new_fail = (
fail.0,
&*env
.arena
.alloc(function_d(env, *cond_symbol, stack_size as _, fail.1)),
);
Cond {
cond_symbol: *cond_symbol,
cond_layout: cond_layout.clone(),
branching_symbol: *branching_symbol,
branching_layout: branching_layout.clone(),
ret_layout: ret_layout.clone(),
pass: new_pass,
fail: new_fail,
}
}
Store(stores, body) => {
let new_body = function_r(env, body);
Store(stores, env.arena.alloc(new_body))
}
DecAfter(symbol, body) => {
let new_body = function_r(env, body);
DecAfter(*symbol, env.arena.alloc(new_body))
}
CallByName { .. }
| CallByPointer(_, _, _)
| RunLowLevel(_, _)
| Tag { .. }
| Struct(_)
| Array { .. }
| AccessAtIndex { .. } => {
// TODO
// how often are `when` expressions in one of the above?
body.clone()
}
Int(_)
| Float(_)
| Str(_)
| Bool(_)
| Byte(_)
| Load(_)
| EmptyArray
| Inc(_, _)
| FunctionPointer(_, _)
| RuntimeError(_)
| RuntimeErrorFunction(_) => body.clone(),
Reset(_, _) | Reuse(_, _) => unreachable!("reset/reuse should not have been inserted yet!"),
}
}
fn function_d<'a>(
env: &mut Env<'a, '_>,
z: Symbol,
stack_size: usize,
body: &'a Expr<'a>,
) -> Expr<'a> {
let symbols = symbols_in_expr(body);
if symbols.contains(&z) {
return body.clone();
}
if let Ok(reused) = function_s(env, z, stack_size, body) {
Expr::Reset(z, env.arena.alloc(reused))
} else {
body.clone()
}
/*
match body {
Expr::Tag { .. } => Some(env.arena.alloc(Expr::Reuse(w, body))),
_ => None,
}
*/
}
fn function_s<'a>(
env: &mut Env<'a, '_>,
w: Symbol,
stack_size: usize,
body: &'a Expr<'a>,
) -> Result<&'a Expr<'a>, &'a Expr<'a>> {
use Expr::*;
match body {
Tag { tag_layout, .. } => {
if tag_layout.stack_size(env.pointer_size) as usize <= stack_size {
Ok(env.arena.alloc(Expr::Reuse(w, body)))
} else {
Err(body)
}
}
Array { .. } | Struct(_) => {
// TODO
Err(body)
}
Switch {
cond_symbol,
branches,
cond,
cond_layout,
default_branch,
ret_layout,
} => {
// we can re-use `w` in each branch
let mut has_been_reused = false;
let mut new_branches = Vec::with_capacity_in(branches.len(), env.arena);
for (tag, stores, branch) in branches.iter() {
match function_s(env, *cond_symbol, stack_size as _, branch) {
Ok(new_branch) => {
has_been_reused = true;
new_branches.push((*tag, *stores, new_branch.clone()));
}
Err(new_branch) => {
new_branches.push((*tag, *stores, new_branch.clone()));
}
}
}
let new_default_branch = (
default_branch.0,
match function_s(env, *cond_symbol, stack_size, default_branch.1) {
Ok(new) => {
has_been_reused = true;
new
}
Err(new) => new,
},
);
let result = env.arena.alloc(Switch {
cond_symbol: *cond_symbol,
branches: new_branches.into_bump_slice(),
default_branch: new_default_branch,
ret_layout: ret_layout.clone(),
cond: *cond,
cond_layout: cond_layout.clone(),
});
if has_been_reused {
Ok(result)
} else {
Err(result)
}
}
Cond {
cond_symbol,
cond_layout,
branching_symbol,
branching_layout,
pass,
fail,
ret_layout,
} => {
let mut has_been_reused = false;
let new_pass = (
pass.0,
match function_s(env, *cond_symbol, stack_size, pass.1) {
Ok(new) => {
has_been_reused = true;
new
}
Err(new) => new,
},
);
let new_fail = (
fail.0,
match function_s(env, *cond_symbol, stack_size, fail.1) {
Ok(new) => {
has_been_reused = true;
new
}
Err(new) => new,
},
);
let result = env.arena.alloc(Cond {
cond_symbol: *cond_symbol,
cond_layout: cond_layout.clone(),
branching_symbol: *branching_symbol,
branching_layout: branching_layout.clone(),
ret_layout: ret_layout.clone(),
pass: new_pass,
fail: new_fail,
});
if has_been_reused {
Ok(result)
} else {
Err(result)
}
}
Store(stores, expr) => {
let new_expr = function_s(env, w, stack_size, expr)?;
Ok(env.arena.alloc(Store(*stores, new_expr)))
}
DecAfter(symbol, expr) => {
let new_expr = function_s(env, w, stack_size, expr)?;
Ok(env.arena.alloc(DecAfter(*symbol, new_expr)))
}
CallByName { .. } | CallByPointer(_, _, _) | RunLowLevel(_, _) | AccessAtIndex { .. } => {
// TODO
// how often are `Tag` expressions in one of the above?
Err(body)
}
Int(_)
| Float(_)
| Str(_)
| Bool(_)
| Byte(_)
| Load(_)
| EmptyArray
| Inc(_, _)
| FunctionPointer(_, _)
| RuntimeError(_)
| RuntimeErrorFunction(_) => Err(body),
Reset(_, _) | Reuse(_, _) => {
unreachable!("reset/reuse should not have been introduced yet")
}
}
}
fn free_variables<'a>(initial: &Expr<'a>) -> MutSet<Symbol> {
use Expr::*;
let mut seen = MutSet::default();
let mut bound = MutSet::default();
let mut stack = vec![initial];
// in other words, variables that are referenced, but not stored
while let Some(expr) = stack.pop() {
match expr {
FunctionPointer(symbol, _) | Load(symbol) => {
seen.insert(*symbol);
}
Reset(symbol, expr) | Reuse(symbol, expr) => {
seen.insert(*symbol);
stack.push(expr)
}
Cond {
cond_symbol,
branching_symbol,
pass,
fail,
..
} => {
seen.insert(*cond_symbol);
seen.insert(*branching_symbol);
for (symbol, _, expr) in pass.0.iter() {
seen.insert(*symbol);
stack.push(expr)
}
for (symbol, _, expr) in fail.0.iter() {
seen.insert(*symbol);
stack.push(expr)
}
}
Switch {
cond,
cond_symbol,
branches,
default_branch,
..
} => {
stack.push(cond);
seen.insert(*cond_symbol);
for (_, stores, expr) in branches.iter() {
stack.push(expr);
for (symbol, _, expr) in stores.iter() {
bound.insert(*symbol);
stack.push(expr)
}
}
stack.push(default_branch.1);
for (symbol, _, expr) in default_branch.0.iter() {
seen.insert(*symbol);
stack.push(expr)
}
}
Store(stores, body) => {
for (symbol, _, expr) in stores.iter() {
bound.insert(*symbol);
stack.push(&expr)
}
stack.push(body)
}
DecAfter(symbol, body) | Inc(symbol, body) => {
seen.insert(*symbol);
stack.push(body);
}
CallByName { name, args, .. } => {
seen.insert(*name);
for (expr, _) in args.iter() {
stack.push(expr);
}
}
CallByPointer(function, args, _) => {
stack.push(function);
stack.extend(args.iter());
}
RunLowLevel(_, args) => {
for (expr, _) in args.iter() {
stack.push(expr);
}
}
Tag { arguments, .. } => {
for (symbol, _) in arguments.iter() {
seen.insert(*symbol);
}
}
Struct(arguments) => {
for (expr, _) in arguments.iter() {
stack.push(expr);
}
}
Array { elems, .. } => {
for expr in elems.iter() {
stack.push(expr);
}
}
AccessAtIndex { expr, .. } => {
stack.push(expr);
}
Int(_)
| Float(_)
| Str(_)
| Bool(_)
| Byte(_)
| EmptyArray
| RuntimeError(_)
| RuntimeErrorFunction(_) => {}
}
}
for symbol in bound.iter() {
seen.remove(symbol);
}
seen
}
fn symbols_in_expr<'a>(initial: &Expr<'a>) -> MutSet<Symbol> {
use Expr::*;
let mut result = MutSet::default();
let mut stack = vec![initial];
while let Some(expr) = stack.pop() {
match expr {
FunctionPointer(symbol, _) | Load(symbol) => {
result.insert(*symbol);
}
Reset(symbol, expr) | Reuse(symbol, expr) => {
result.insert(*symbol);
stack.push(expr)
}
Cond {
cond_symbol,
branching_symbol,
pass,
fail,
..
} => {
result.insert(*cond_symbol);
result.insert(*branching_symbol);
for (symbol, _, expr) in pass.0.iter() {
result.insert(*symbol);
stack.push(expr)
}
for (symbol, _, expr) in fail.0.iter() {
result.insert(*symbol);
stack.push(expr)
}
}
Switch {
cond,
cond_symbol,
branches,
default_branch,
..
} => {
stack.push(cond);
result.insert(*cond_symbol);
for (_, stores, expr) in branches.iter() {
stack.push(expr);
for (symbol, _, expr) in stores.iter() {
result.insert(*symbol);
stack.push(expr)
}
}
stack.push(default_branch.1);
for (symbol, _, expr) in default_branch.0.iter() {
result.insert(*symbol);
stack.push(expr)
}
}
Store(stores, body) => {
for (symbol, _, expr) in stores.iter() {
result.insert(*symbol);
stack.push(&expr)
}
stack.push(body)
}
DecAfter(symbol, body) | Inc(symbol, body) => {
result.insert(*symbol);
stack.push(body);
}
CallByName { name, args, .. } => {
result.insert(*name);
for (expr, _) in args.iter() {
stack.push(expr);
}
}
CallByPointer(function, args, _) => {
stack.push(function);
stack.extend(args.iter());
}
RunLowLevel(_, args) => {
for (expr, _) in args.iter() {
stack.push(expr);
}
}
Tag { arguments, .. } => {
for (symbol, _) in arguments.iter() {
result.insert(*symbol);
}
}
Struct(arguments) => {
for (expr, _) in arguments.iter() {
stack.push(expr);
}
}
Array { elems, .. } => {
for expr in elems.iter() {
stack.push(expr);
}
}
AccessAtIndex { expr, .. } => {
stack.push(expr);
}
Int(_)
| Float(_)
| Str(_)
| Bool(_)
| Byte(_)
| EmptyArray
| RuntimeError(_)
| RuntimeErrorFunction(_) => {}
}
}
result
}
pub fn function_c<'a>(env: &mut Env<'a, '_>, body: Expr<'a>) -> Expr<'a> {
let fv = free_variables(&body);
function_c_help(env, body, fv)
}
pub fn function_c_help<'a>(env: &mut Env<'a, '_>, body: Expr<'a>, fv: MutSet<Symbol>) -> Expr<'a> {
use Expr::*;
match body {
Tag { arguments, .. } => {
let symbols = arguments
.iter()
.map(|(x, _)| x)
.copied()
.collect::<std::vec::Vec<_>>();
function_c_app(env, &symbols, &fv, body)
}
_ => body,
}
}
fn function_c_app<'a>(
env: &mut Env<'a, '_>,
arguments: &[Symbol],
orig_fv: &MutSet<Symbol>,
mut application: Expr<'a>,
) -> Expr<'a> {
// in the future, this will need to be a check
let is_owned = true;
for (i, y) in arguments.iter().rev().enumerate() {
if is_owned {
let mut fv = orig_fv.clone();
fv.extend(arguments[i..].iter().copied());
application = insert_increment(env, *y, fv, application)
} else {
unimplemented!("owned references are not implemented yet")
}
}
application
}
fn insert_increment<'a>(
env: &mut Env<'a, '_>,
symbol: Symbol,
live_variables: MutSet<Symbol>,
body: Expr<'a>,
) -> Expr<'a> {
// in the future, this will need to be a check
let is_owned = true;
if is_owned && !live_variables.contains(&symbol) {
body
} else {
Expr::Inc(symbol, env.arena.alloc(body))
}
}
fn insert_decrement<'a>(env: &mut Env<'a, '_>, symbols: &[Symbol], mut body: Expr<'a>) -> Expr<'a> {
// in the future, this will need to be a check
let is_owned = true;
let fv = free_variables(&body);
for symbol in symbols.iter() {
let is_dead = !fv.contains(&symbol);
if is_owned && is_dead {
body = Expr::DecAfter(*symbol, env.arena.alloc(body));
}
}
body
}

File diff suppressed because it is too large Load diff

View file

@ -123,6 +123,8 @@ pub enum RuntimeError {
InvalidInt(IntErrorKind, Base, Region, Box<str>),
CircularDef(Vec<Symbol>, Vec<(Region /* pattern */, Region /* expr */)>),
NonExhaustivePattern,
/// When the author specifies a type annotation but no implementation
NoImplementation,
}

View file

@ -525,6 +525,9 @@ fn pretty_runtime_error<'b>(
alloc.reflow("Only variables can be updated with record update syntax."),
]),
RuntimeError::NoImplementation => todo!("no implementation, unreachable"),
RuntimeError::NonExhaustivePattern => {
unreachable!("not currently reported (but can blow up at runtime)")
}
}
}

View file

@ -5,11 +5,11 @@ use ven_pretty::DocAllocator;
pub fn mono_problem<'b>(
alloc: &'b RocDocAllocator<'b>,
filename: PathBuf,
problem: roc_mono::expr::MonoProblem,
problem: roc_mono::ir::MonoProblem,
) -> Report<'b> {
use roc_mono::expr::MonoProblem::*;
use roc_mono::pattern::Context::*;
use roc_mono::pattern::Error::*;
use roc_mono::exhaustive::Context::*;
use roc_mono::exhaustive::Error::*;
use roc_mono::ir::MonoProblem::*;
match problem {
PatternProblem(Incomplete(region, context, missing)) => match context {
@ -111,7 +111,7 @@ pub fn mono_problem<'b>(
pub fn unhandled_patterns_to_doc_block<'b>(
alloc: &'b RocDocAllocator<'b>,
patterns: Vec<roc_mono::pattern::Pattern>,
patterns: Vec<roc_mono::exhaustive::Pattern>,
) -> RocDocBuilder<'b> {
alloc
.vcat(patterns.into_iter().map(|v| pattern_to_doc(alloc, v)))
@ -121,19 +121,19 @@ pub fn unhandled_patterns_to_doc_block<'b>(
fn pattern_to_doc<'b>(
alloc: &'b RocDocAllocator<'b>,
pattern: roc_mono::pattern::Pattern,
pattern: roc_mono::exhaustive::Pattern,
) -> RocDocBuilder<'b> {
pattern_to_doc_help(alloc, pattern, false)
}
fn pattern_to_doc_help<'b>(
alloc: &'b RocDocAllocator<'b>,
pattern: roc_mono::pattern::Pattern,
pattern: roc_mono::exhaustive::Pattern,
in_type_param: bool,
) -> RocDocBuilder<'b> {
use roc_mono::pattern::Literal::*;
use roc_mono::pattern::Pattern::*;
use roc_mono::pattern::RenderAs;
use roc_mono::exhaustive::Literal::*;
use roc_mono::exhaustive::Pattern::*;
use roc_mono::exhaustive::RenderAs;
match pattern {
Anything => alloc.text("_"),

View file

@ -12,7 +12,7 @@ mod test_reporting {
use crate::helpers::test_home;
use bumpalo::Bump;
use roc_module::symbol::{Interns, ModuleId};
use roc_mono::expr::{Expr, Procs};
use roc_mono::ir::{Procs, Stmt};
use roc_reporting::report::{
can_problem, mono_problem, parse_problem, type_problem, Report, BLUE_CODE, BOLD_CODE,
CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE,
@ -47,7 +47,7 @@ mod test_reporting {
(
Vec<solve::TypeError>,
Vec<roc_problem::can::Problem>,
Vec<roc_mono::expr::MonoProblem>,
Vec<roc_mono::ir::MonoProblem>,
ModuleId,
Interns,
),
@ -87,14 +87,14 @@ mod test_reporting {
let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap();
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
let mut mono_env = roc_mono::expr::Env {
let mut mono_env = roc_mono::ir::Env {
arena: &arena,
subs: &mut subs,
problems: &mut mono_problems,
home,
ident_ids: &mut ident_ids,
};
let _mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs);
let _mono_expr = Stmt::new(&mut mono_env, loc_expr.value, &mut procs);
}
Ok((unify_problems, can_problems, mono_problems, home, interns))

View file

@ -606,6 +606,7 @@ impl VarUsage {
);
closure_signatures.insert(Symbol::LIST_IS_EMPTY, vec![Usage::Simple(Mark::Seen)]);
closure_signatures.insert(Symbol::LIST_LEN, vec![Usage::Simple(Mark::Seen)]);
closure_signatures.insert(
Symbol::LIST_SET,

View file

@ -9,4 +9,10 @@ pub fn main() {
let list = unsafe { list_from_roc() };
println!("Roc quicksort says: {:?}", list);
// the pointer is to the first _element_ of the list,
// but the refcount precedes it. Thus calling free() on
// this pointer would segfault/cause badness. Therefore, we
// leak it for now
Box::leak(list);
}