mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
commit
ddfe16b1b4
36 changed files with 8181 additions and 1632 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2250,6 +2250,7 @@ dependencies = [
|
|||
"roc_solve",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"ven_pretty",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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(_)
|
||||
|
|
|
@ -203,7 +203,7 @@ pub fn constrain_expr(
|
|||
List {
|
||||
elem_var,
|
||||
loc_elems,
|
||||
..
|
||||
list_var: _unused,
|
||||
} => {
|
||||
if loc_elems.is_empty() {
|
||||
exists(
|
||||
|
|
|
@ -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
|
@ -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)),
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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#"
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)",
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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)))))",
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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))),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
901
compiler/mono/src/inc_dec.rs
Normal file
901
compiler/mono/src/inc_dec.rs
Normal 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(¶m.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(¶m.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
3223
compiler/mono/src/ir.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
648
compiler/mono/src/reset_reuse.rs
Normal file
648
compiler/mono/src/reset_reuse.rs
Normal 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
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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("_"),
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue