From b716636db0ec711a2012e91cf6eca432ed7cb2c3 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 26 Jul 2020 16:31:46 +0200 Subject: [PATCH 01/60] insert inc and dec instructions --- compiler/gen/src/llvm/build.rs | 3 +++ compiler/mono/src/expr.rs | 38 ++++++++++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 1830390078..0573b2a57e 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -717,6 +717,9 @@ pub fn build_expr<'a, 'ctx, 'env>( todo!("LLVM build runtime error of {:?}", expr); } RunLowLevel(op, args) => run_low_level(env, layout_ids, scope, parent, *op, args), + + IncBefore(_, expr) => build_expr(env, layout_ids, scope, parent, expr), + DecAfter(_, expr) => build_expr(env, layout_ids, scope, parent, expr), } } diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index a62c9d7c9f..b831c621f7 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -352,6 +352,10 @@ pub enum Expr<'a> { }, EmptyArray, + /// RC instructions + IncBefore(Symbol, &'a Expr<'a>), + DecAfter(Symbol, &'a Expr<'a>), + RuntimeError(&'a str), } @@ -600,12 +604,20 @@ fn from_can<'a>( layout_cache, ) } else { - Expr::Load(symbol) + Expr::IncBefore(symbol, env.arena.alloc(Expr::Load(symbol))) } } LetRec(defs, ret_expr, _, _) => from_can_defs(env, defs, *ret_expr, layout_cache, procs), LetNonRec(def, ret_expr, _, _) => { - from_can_defs(env, vec![*def], *ret_expr, layout_cache, procs) + let symbols = roc_can::pattern::symbols_from_pattern(&def.loc_pattern.value); + let mut result = from_can_defs(env, vec![*def], *ret_expr, layout_cache, procs); + + // TODO is order important here? + for symbol in symbols { + result = Expr::DecAfter(symbol, env.arena.alloc(result)); + } + + result } Closure(ann, name, _, loc_args, boxed_body) => { @@ -1208,6 +1220,13 @@ fn from_can_when<'a>( let arena = env.arena; let mut stored = Vec::with_capacity_in(1, arena); + let bound_symbols = first + .patterns + .iter() + .map(|pat| roc_can::pattern::symbols_from_pattern(&pat.value)) + .flatten() + .collect::>(); + let loc_when_pattern = &first.patterns[0]; let mono_pattern = from_can_pattern(env, procs, layout_cache, &loc_when_pattern.value); @@ -1247,11 +1266,16 @@ fn from_can_when<'a>( // NOTE this will still store shadowed names. // that's fine: the branch throws a runtime error anyway - let ret = match store_pattern(env, &mono_pattern, cond_symbol, cond_layout, &mut stored) { + let mut ret = match store_pattern(env, &mono_pattern, cond_symbol, cond_layout, &mut stored) + { Ok(_) => from_can(env, first.value.value, procs, layout_cache), Err(message) => Expr::RuntimeError(env.arena.alloc(message)), }; + for symbol in bound_symbols { + ret = Expr::DecAfter(symbol, env.arena.alloc(ret)); + } + Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) } else { let cond_layout = layout_cache @@ -1612,7 +1636,13 @@ fn specialize<'a>( debug_assert!(matches!(unified, roc_unify::unify::Unified::Success(_))); - let specialized_body = from_can(env, body, procs, layout_cache); + let mut specialized_body = from_can(env, body, procs, layout_cache); + + // TODO does order matter here? + for &symbol in pattern_symbols.iter() { + specialized_body = Expr::DecAfter(symbol, env.arena.alloc(specialized_body)); + } + // reset subs, so we don't get type errors when specializing for a different signature env.subs.rollback_to(snapshot); From c85cee3bc02d1d6555f6c4782e1b5be0e5e05c2d Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 26 Jul 2020 23:20:41 +0200 Subject: [PATCH 02/60] compiling again --- compiler/gen/src/llvm/build.rs | 6 +- compiler/mono/src/decision_tree.rs | 1 + compiler/mono/src/expr.rs | 173 +++++++++++++--- compiler/mono/tests/test_mono.rs | 318 ++++++++++++++++++++--------- 4 files changed, 370 insertions(+), 128 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 0573b2a57e..dfb65a5f1b 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -237,6 +237,7 @@ pub fn build_expr<'a, 'ctx, 'env>( default_branch: (default_stores, default_expr), ret_layout, cond_layout, + cond_symbol: _, } => { let ret_type = basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes); @@ -346,6 +347,7 @@ pub fn build_expr<'a, 'ctx, 'env>( .left() .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")) } + LoadWithoutIncrement(_symbol) => todo!("implement load without increment"), Load(symbol) => load_symbol(env, scope, symbol), Str(str_literal) => { if str_literal.is_empty() { @@ -718,8 +720,10 @@ pub fn build_expr<'a, 'ctx, 'env>( } RunLowLevel(op, args) => run_low_level(env, layout_ids, scope, parent, *op, args), - IncBefore(_, expr) => build_expr(env, layout_ids, scope, parent, expr), DecAfter(_, expr) => build_expr(env, layout_ids, scope, parent, expr), + + Reuse(_, expr) => build_expr(env, layout_ids, scope, parent, expr), + Reset(_, expr) => build_expr(env, layout_ids, scope, parent, expr), } } diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index ad3e3fef73..3f820d86e1 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -1194,6 +1194,7 @@ fn decide_to_branching<'a>( Expr::Switch { cond: env.arena.alloc(cond), cond_layout, + cond_symbol, branches: branches.into_bump_slice(), default_branch, ret_layout, diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index b831c621f7..d637a376d9 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -288,6 +288,10 @@ pub enum Expr<'a> { Load(Symbol), Store(&'a [(Symbol, Layout<'a>, Expr<'a>)], &'a Expr<'a>), + /// RC instructions + LoadWithoutIncrement(Symbol), + DecAfter(Symbol, &'a Expr<'a>), + // Functions FunctionPointer(Symbol, Layout<'a>), RuntimeErrorFunction(&'a str), @@ -323,6 +327,7 @@ pub enum Expr<'a> { /// This *must* be an integer, because Switch potentially compiles to a jump table. cond: &'a Expr<'a>, cond_layout: Layout<'a>, + cond_symbol: Symbol, /// The u64 in the tuple will be compared directly to the condition Expr. /// If they are equal, this branch will be taken. branches: &'a [(u64, Stores<'a>, Expr<'a>)], @@ -352,13 +357,100 @@ pub enum Expr<'a> { }, EmptyArray, - /// RC instructions - IncBefore(Symbol, &'a Expr<'a>), - DecAfter(Symbol, &'a Expr<'a>), + /// Reset/Reuse + + /// Re-use Symbol in Expr + Reuse(Symbol, &'a Expr<'a>), + Reset(Symbol, &'a Expr<'a>), RuntimeError(&'a str), } +fn function_r<'a>(env: &mut Env<'a, '_>, body: &'a Expr<'a>) -> &'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, + )), + ); + + 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(), + }) + } + Store(stores, body) => { + let new_body = function_r(env, body); + + env.arena.alloc(Store(stores, new_body)) + } + + _ => body, + } +} + +fn function_d<'a>( + env: &mut Env<'a, '_>, + z: Symbol, + stack_size: usize, + body: &'a Expr<'a>, +) -> Expr<'a> { + if let Some(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>, +) -> Option<&'a Expr<'a>> { + match body { + Expr::Tag { tag_layout, .. } + if tag_layout.stack_size(env.pointer_size) as usize <= stack_size => + { + Some(env.arena.alloc(Expr::Reuse(w, body))) + } + _ => None, + } +} + #[derive(Clone, Debug)] pub enum MonoProblem { PatternProblem(crate::pattern::Error), @@ -373,7 +465,8 @@ impl<'a> Expr<'a> { ) -> Self { let mut layout_cache = LayoutCache::default(); - from_can(env, can_expr, procs, &mut layout_cache) + let result = from_can(env, can_expr, procs, &mut layout_cache); + function_r(env, env.arena.alloc(result)).clone() } } @@ -570,6 +663,15 @@ fn pattern_to_when<'a>( } } +fn decrement_refcount<'a>(env: &mut Env<'a, '_>, symbol: Symbol, expr: Expr<'a>) -> Expr<'a> { + // TODO are there any builtins that should be refcounted? + if symbol.is_builtin() { + expr + } else { + Expr::DecAfter(symbol, env.arena.alloc(expr)) + } +} + #[allow(clippy::cognitive_complexity)] fn from_can<'a>( env: &mut Env<'a, '_>, @@ -604,7 +706,8 @@ fn from_can<'a>( layout_cache, ) } else { - Expr::IncBefore(symbol, env.arena.alloc(Expr::Load(symbol))) + // NOTE Load will always increment the refcount + Expr::Load(symbol) } } LetRec(defs, ret_expr, _, _) => from_can_defs(env, defs, *ret_expr, layout_cache, procs), @@ -614,7 +717,7 @@ fn from_can<'a>( // TODO is order important here? for symbol in symbols { - result = Expr::DecAfter(symbol, env.arena.alloc(result)); + result = decrement_refcount(env, symbol, result); } result @@ -699,17 +802,35 @@ fn from_can<'a>( region, loc_cond, branches, - } => from_can_when( - env, - cond_var, - expr_var, - region, - *loc_cond, - branches, - layout_cache, - procs, - ), + } => { + let cond_symbol = if let roc_can::expr::Expr::Var(symbol) = loc_cond.value { + symbol + } else { + env.unique_symbol() + }; + let mono_when = from_can_when( + env, + cond_var, + expr_var, + region, + cond_symbol, + branches, + layout_cache, + procs, + ); + + let mono_cond = from_can(env, loc_cond.value, procs, layout_cache); + + let cond_layout = layout_cache + .from_var(env.arena, cond_var, env.subs, env.pointer_size) + .expect("invalid cond_layout"); + + Expr::Store( + env.arena.alloc([(cond_symbol, cond_layout, mono_cond)]), + env.arena.alloc(mono_when), + ) + } If { cond_var, branch_var, @@ -1203,7 +1324,7 @@ fn from_can_when<'a>( cond_var: Variable, expr_var: Variable, region: Region, - loc_cond: Located, + cond_symbol: Symbol, mut branches: std::vec::Vec, layout_cache: &mut LayoutCache<'a>, procs: &mut Procs<'a>, @@ -1260,9 +1381,6 @@ fn from_can_when<'a>( let cond_layout = layout_cache .from_var(env.arena, cond_var, env.subs, env.pointer_size) .unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err)); - let cond_symbol = env.unique_symbol(); - let cond = from_can(env, loc_cond.value, procs, layout_cache); - stored.push((cond_symbol, cond_layout.clone(), cond)); // NOTE this will still store shadowed names. // that's fine: the branch throws a runtime error anyway @@ -1273,7 +1391,7 @@ fn from_can_when<'a>( }; for symbol in bound_symbols { - ret = Expr::DecAfter(symbol, env.arena.alloc(ret)); + ret = decrement_refcount(env, symbol, ret); } Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) @@ -1282,9 +1400,6 @@ fn from_can_when<'a>( .from_var(env.arena, cond_var, env.subs, env.pointer_size) .unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err)); - let cond = from_can(env, loc_cond.value, procs, layout_cache); - let cond_symbol = env.unique_symbol(); - let mut loc_branches = std::vec::Vec::new(); let mut opt_branches = std::vec::Vec::new(); @@ -1406,17 +1521,13 @@ fn from_can_when<'a>( .from_var(env.arena, expr_var, env.subs, env.pointer_size) .unwrap_or_else(|err| panic!("TODO turn this into a RuntimeError {:?}", err)); - let branching = crate::decision_tree::optimize_when( + crate::decision_tree::optimize_when( env, cond_symbol, cond_layout.clone(), ret_layout, opt_branches, - ); - - let stores = env.arena.alloc([(cond_symbol, cond_layout, cond)]); - - Expr::Store(stores, env.arena.alloc(branching)) + ) } } @@ -1640,7 +1751,7 @@ fn specialize<'a>( // TODO does order matter here? for &symbol in pattern_symbols.iter() { - specialized_body = Expr::DecAfter(symbol, env.arena.alloc(specialized_body)); + specialized_body = decrement_refcount(env, symbol, specialized_body); } // reset subs, so we don't get type errors when specializing for a different signature diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 19a65ac364..0ae4d2ed9e 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -11,6 +11,7 @@ mod helpers; mod test_mono { use crate::helpers::{can_expr, infer_expr, test_home, CanExprOut}; use bumpalo::Bump; + use roc_module::ident::TagName; use roc_module::symbol::{Interns, Symbol}; use roc_mono::expr::Expr::{self, *}; use roc_mono::expr::Procs; @@ -166,30 +167,33 @@ mod test_mono { let home = test_home(); let gen_symbol_0 = Interns::from_index(home, 0); - Struct(&[ - ( - CallByName { - name: gen_symbol_0, - layout: Layout::FunctionPointer( - &[Layout::Builtin(Builtin::Int64)], - &Layout::Builtin(Builtin::Int64), - ), - args: &[(Int(4), Layout::Builtin(Int64))], - }, - Layout::Builtin(Int64), - ), - ( - CallByName { - name: gen_symbol_0, - layout: Layout::FunctionPointer( - &[Layout::Builtin(Builtin::Float64)], - &Layout::Builtin(Builtin::Float64), - ), - args: &[(Float(3.14), Layout::Builtin(Float64))], - }, - Layout::Builtin(Float64), - ), - ]) + DecAfter( + gen_symbol_0, + &Struct(&[ + ( + CallByName { + name: gen_symbol_0, + layout: Layout::FunctionPointer( + &[Layout::Builtin(Builtin::Int64)], + &Layout::Builtin(Builtin::Int64), + ), + args: &[(Int(4), Layout::Builtin(Int64))], + }, + Layout::Builtin(Int64), + ), + ( + CallByName { + name: gen_symbol_0, + layout: Layout::FunctionPointer( + &[Layout::Builtin(Builtin::Float64)], + &Layout::Builtin(Builtin::Float64), + ), + args: &[(Float(3.14), Layout::Builtin(Float64))], + }, + Layout::Builtin(Float64), + ), + ]), + ) }, ) } @@ -299,27 +303,30 @@ mod test_mono { let gen_symbol_0 = Interns::from_index(home, 1); let symbol_x = Interns::from_index(home, 0); - Store( - &[( - symbol_x, - Builtin(Str), - Store( - &[( - gen_symbol_0, - Layout::Builtin(layout::Builtin::Int1), - Expr::Bool(true), - )], - &Cond { - cond_symbol: gen_symbol_0, - branch_symbol: gen_symbol_0, - cond_layout: Builtin(Int1), - pass: (&[] as &[_], &Expr::Str("bar")), - fail: (&[] as &[_], &Expr::Str("foo")), - ret_layout: Builtin(Str), - }, - ), - )], - &Load(symbol_x), + DecAfter( + symbol_x, + &Store( + &[( + symbol_x, + Builtin(Str), + Store( + &[( + gen_symbol_0, + Layout::Builtin(layout::Builtin::Int1), + Expr::Bool(true), + )], + &Cond { + cond_symbol: gen_symbol_0, + branch_symbol: gen_symbol_0, + cond_layout: Builtin(Int1), + pass: (&[] as &[_], &Expr::Str("bar")), + fail: (&[] as &[_], &Expr::Str("foo")), + ret_layout: Builtin(Str), + }, + ), + )], + &Load(symbol_x), + ), ) }, ) @@ -359,27 +366,30 @@ mod test_mono { let gen_symbol_0 = Interns::from_index(home, 0); let struct_layout = Layout::Struct(&[I64_LAYOUT, F64_LAYOUT]); - CallByName { - name: gen_symbol_0, - layout: Layout::FunctionPointer( - &[struct_layout.clone()], - &struct_layout.clone(), - ), - args: &[( - Struct(&[ - ( - CallByName { - name: gen_symbol_0, - layout: Layout::FunctionPointer(&[I64_LAYOUT], &I64_LAYOUT), - args: &[(Int(4), I64_LAYOUT)], - }, - I64_LAYOUT, - ), - (Float(0.1), F64_LAYOUT), - ]), - struct_layout, - )], - } + DecAfter( + gen_symbol_0, + &CallByName { + name: gen_symbol_0, + layout: Layout::FunctionPointer( + &[struct_layout.clone()], + &struct_layout.clone(), + ), + args: &[( + Struct(&[ + ( + CallByName { + name: gen_symbol_0, + layout: Layout::FunctionPointer(&[I64_LAYOUT], &I64_LAYOUT), + args: &[(Int(4), I64_LAYOUT)], + }, + I64_LAYOUT, + ), + (Float(0.1), F64_LAYOUT), + ]), + struct_layout, + )], + }, + ) }, ) } @@ -493,7 +503,9 @@ mod test_mono { let load = Load(var_x); - Store(arena.alloc(stores), arena.alloc(load)) + let store = Store(arena.alloc(stores), arena.alloc(load)); + + DecAfter(var_x, arena.alloc(store)) }, ); } @@ -517,7 +529,9 @@ mod test_mono { let load = Load(var_x); - Store(arena.alloc(stores), arena.alloc(load)) + let store = Store(arena.alloc(stores), arena.alloc(load)); + + DecAfter(var_x, arena.alloc(store)) }, ); } @@ -543,7 +557,9 @@ mod test_mono { let load = Load(var_x); - Store(arena.alloc(stores), arena.alloc(load)) + let store = Store(arena.alloc(stores), arena.alloc(load)); + + DecAfter(var_x, arena.alloc(store)) }, ); } @@ -589,33 +605,143 @@ mod test_mono { }); } - // #[test] - // fn when_on_result() { - // compiles_to( - // r#" - // when 1 is - // 1 -> 12 - // _ -> 34 - // "#, - // { - // use self::Builtin::*; - // use Layout::Builtin; - // let home = test_home(); + // #[test] + // fn when_on_result() { + // compiles_to( + // r#" + // when 1 is + // 1 -> 12 + // _ -> 34 + // "#, + // { + // use self::Builtin::*; + // use Layout::Builtin; + // let home = test_home(); // - // let gen_symbol_3 = Interns::from_index(home, 3); - // let gen_symbol_4 = Interns::from_index(home, 4); + // let gen_symbol_3 = Interns::from_index(home, 3); + // let gen_symbol_4 = Interns::from_index(home, 4); // - // CallByName( - // gen_symbol_3, - // &[( - // Struct(&[( - // CallByName(gen_symbol_4, &[(Int(4), Builtin(Int64))]), - // Builtin(Int64), - // )]), - // Layout::Struct(&[("x".into(), Builtin(Int64))]), - // )], - // ) - // }, - // ) - // } + // CallByName( + // gen_symbol_3, + // &[( + // Struct(&[( + // CallByName(gen_symbol_4, &[(Int(4), Builtin(Int64))]), + // Builtin(Int64), + // )]), + // Layout::Struct(&[("x".into(), Builtin(Int64))]), + // )], + // ) + // }, + // ) + // } + + #[test] + fn insert_reset_reuse() { + compiles_to( + r#" + when Foo 1 is + Foo _ -> Foo 1 + Bar -> Foo 2 + Baz -> Foo 2 + a -> a + "#, + { + use self::Builtin::*; + use Layout::{Builtin, Union}; + + let home = test_home(); + let gen_symbol_1 = Interns::from_index(home, 1); + + let union_layout = Union(&[ + &[Builtin(Int64)], + &[Builtin(Int64)], + &[Builtin(Int64), Builtin(Int64)], + ]); + + Store( + &[( + gen_symbol_1, + union_layout.clone(), + Tag { + tag_layout: union_layout.clone(), + tag_name: TagName::Global("Foo".into()), + tag_id: 2, + union_size: 3, + arguments: &[(Int(2), Builtin(Int64)), (Int(1), Builtin(Int64))], + }, + )], + &Store( + &[], + &Switch { + cond: &Load(gen_symbol_1), + cond_symbol: gen_symbol_1, + branches: &[ + ( + 2, + &[], + Reset( + gen_symbol_1, + &Reuse( + gen_symbol_1, + &Tag { + tag_layout: union_layout.clone(), + tag_name: TagName::Global("Foo".into()), + tag_id: 2, + union_size: 3, + arguments: &[ + (Int(2), Builtin(Int64)), + (Int(1), Builtin(Int64)), + ], + }, + ), + ), + ), + ( + 0, + &[], + Reset( + gen_symbol_1, + &Reuse( + gen_symbol_1, + &Tag { + tag_layout: union_layout.clone(), + tag_name: TagName::Global("Foo".into()), + tag_id: 2, + union_size: 3, + arguments: &[ + (Int(2), Builtin(Int64)), + (Int(2), Builtin(Int64)), + ], + }, + ), + ), + ), + ], + default_branch: ( + &[], + &Reset( + gen_symbol_1, + &Reuse( + gen_symbol_1, + &Tag { + tag_layout: union_layout.clone(), + tag_name: TagName::Global("Foo".into()), + tag_id: 2, + union_size: 3, + arguments: &[ + (Int(2), Builtin(Int64)), + (Int(2), Builtin(Int64)), + ], + }, + ), + ), + ), + ret_layout: union_layout.clone(), + cond_layout: union_layout, + }, + ), + ) + }, + ) + } } From edca61e2d6d4bacfb2ddc7d1285a46058d640896 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 27 Jul 2020 01:24:56 +0200 Subject: [PATCH 03/60] improve reset/reuse insertion --- compiler/mono/src/expr.rs | 364 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 351 insertions(+), 13 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index d637a376d9..713eebed26 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -366,7 +366,7 @@ pub enum Expr<'a> { RuntimeError(&'a str), } -fn function_r<'a>(env: &mut Env<'a, '_>, body: &'a Expr<'a>) -> &'a Expr<'a> { +fn function_r<'a>(env: &mut Env<'a, '_>, body: &'a Expr<'a>) -> Expr<'a> { use Expr::*; match body { @@ -397,22 +397,86 @@ fn function_r<'a>(env: &mut Env<'a, '_>, body: &'a Expr<'a>) -> &'a Expr<'a> { )), ); - env.arena.alloc(Switch { + 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, + branch_symbol, + cond_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, + branch_symbol: *branch_symbol, + cond_layout: cond_layout.clone(), + ret_layout: ret_layout.clone(), + pass: new_pass, + fail: new_fail, + } + } + Store(stores, body) => { let new_body = function_r(env, body); - env.arena.alloc(Store(stores, new_body)) + Store(stores, env.arena.alloc(new_body)) } - _ => 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 + | LoadWithoutIncrement(_) + | FunctionPointer(_, _) + | RuntimeError(_) + | RuntimeErrorFunction(_) => body.clone(), + + Reset(_, _) | Reuse(_, _) => unreachable!("reset/reuse should not have been inserted yet!"), } } @@ -422,7 +486,12 @@ fn function_d<'a>( stack_size: usize, body: &'a Expr<'a>, ) -> Expr<'a> { - if let Some(reused) = function_s(env, z, stack_size, body) { + 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() @@ -440,17 +509,286 @@ fn function_s<'a>( w: Symbol, stack_size: usize, body: &'a Expr<'a>, -) -> Option<&'a Expr<'a>> { +) -> Result<&'a Expr<'a>, &'a Expr<'a>> { + use Expr::*; + match body { - Expr::Tag { tag_layout, .. } - if tag_layout.stack_size(env.pointer_size) as usize <= stack_size => - { - Some(env.arena.alloc(Expr::Reuse(w, 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, + branch_symbol, + cond_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, + branch_symbol: *branch_symbol, + cond_layout: cond_layout.clone(), + ret_layout: ret_layout.clone(), + pass: new_pass, + fail: new_fail, + }); + + if has_been_reused { + Ok(result) + } else { + Err(result) + } + } + + DecAfter(symbol, expr) => { + let new_expr = function_s(env, w, stack_size, expr)?; + + Ok(env.arena.alloc(DecAfter(*symbol, new_expr))) + } + + Store(stores, expr) => { + let new_expr = function_s(env, w, stack_size, expr)?; + + Ok(env.arena.alloc(Store(*stores, 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 + | LoadWithoutIncrement(_) + | FunctionPointer(_, _) + | RuntimeError(_) + | RuntimeErrorFunction(_) => Err(body), + + Reset(_, _) | Reuse(_, _) => { + unreachable!("reset/reuse should not have been introduced yet") } - _ => None, } } +fn symbols_in_expr<'a>(initial: &Expr<'a>) -> MutSet { + use Expr::*; + let mut result = MutSet::default(); + let mut stack = vec![initial]; + + while let Some(expr) = stack.pop() { + match expr { + FunctionPointer(symbol, _) | LoadWithoutIncrement(symbol) | Load(symbol) => { + result.insert(*symbol); + } + + Reset(symbol, expr) | Reuse(symbol, expr) => { + result.insert(*symbol); + stack.push(expr) + } + + Cond { + cond_symbol, + branch_symbol, + pass, + fail, + .. + } => { + result.insert(*cond_symbol); + result.insert(*branch_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) => { + 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 (expr, _) in arguments.iter() { + stack.push(expr); + } + } + + 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 +} + #[derive(Clone, Debug)] pub enum MonoProblem { PatternProblem(crate::pattern::Error), @@ -466,7 +804,7 @@ impl<'a> Expr<'a> { let mut layout_cache = LayoutCache::default(); let result = from_can(env, can_expr, procs, &mut layout_cache); - function_r(env, env.arena.alloc(result)).clone() + function_r(env, env.arena.alloc(result)) } } From 95365959f2952460defaabd64dd76fc50d46f4f7 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 27 Jul 2020 15:10:07 +0200 Subject: [PATCH 04/60] insert reset/reuse for Cond --- compiler/gen/src/llvm/build.rs | 4 ++-- compiler/mono/src/decision_tree.rs | 9 +++++---- compiler/mono/src/expr.rs | 30 ++++++++++++++++++------------ compiler/mono/tests/test_mono.rs | 12 ++++++++---- 4 files changed, 33 insertions(+), 22 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index dfb65a5f1b..b9394c9049 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -174,7 +174,7 @@ pub fn build_expr<'a, 'ctx, 'env>( Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(), Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(), Cond { - branch_symbol, + branching_symbol, pass: (pass_stores, pass_expr), fail: (fail_stores, fail_expr), ret_layout, @@ -186,7 +186,7 @@ pub fn build_expr<'a, 'ctx, 'env>( let ret_type = basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes); - let cond_expr = load_symbol(env, scope, branch_symbol); + let cond_expr = load_symbol(env, scope, branching_symbol); match cond_expr { IntValue(value) => { diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index 3f820d86e1..c19a08d4ec 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -1125,10 +1125,10 @@ fn decide_to_branching<'a>( let condition = boolean_all(env.arena, tests); - let branch_symbol = env.unique_symbol(); - let stores = [(branch_symbol, Layout::Builtin(Builtin::Int1), condition)]; + let branching_symbol = env.unique_symbol(); + let stores = [(branching_symbol, Layout::Builtin(Builtin::Int1), condition)]; - let cond_layout = Layout::Builtin(Builtin::Int1); + let branching_layout = Layout::Builtin(Builtin::Int1); ( env.arena.alloc(stores), @@ -1136,8 +1136,9 @@ fn decide_to_branching<'a>( &[], env.arena.alloc(Expr::Cond { cond_symbol, - branch_symbol, cond_layout, + branching_symbol, + branching_layout, pass, fail, ret_layout, diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 713eebed26..202e02aaa6 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -312,11 +312,12 @@ pub enum Expr<'a> { // symbol storing the original expression that we branch on, e.g. `Ok 42` // required for RC logic cond_symbol: Symbol, + cond_layout: Layout<'a>, // symbol storing the value that we branch on, e.g. `1` representing the `Ok` tag - branch_symbol: Symbol, + branching_symbol: Symbol, + branching_layout: Layout<'a>, - cond_layout: Layout<'a>, // What to do if the condition either passes or fails pass: (Stores<'a>, &'a Expr<'a>), fail: (Stores<'a>, &'a Expr<'a>), @@ -408,8 +409,9 @@ fn function_r<'a>(env: &mut Env<'a, '_>, body: &'a Expr<'a>) -> Expr<'a> { } Cond { cond_symbol, - branch_symbol, cond_layout, + branching_symbol, + branching_layout, pass, fail, ret_layout, @@ -432,8 +434,9 @@ fn function_r<'a>(env: &mut Env<'a, '_>, body: &'a Expr<'a>) -> Expr<'a> { Cond { cond_symbol: *cond_symbol, - branch_symbol: *branch_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, @@ -577,8 +580,9 @@ fn function_s<'a>( Cond { cond_symbol, - branch_symbol, cond_layout, + branching_symbol, + branching_layout, pass, fail, ret_layout, @@ -608,8 +612,9 @@ fn function_s<'a>( let result = env.arena.alloc(Cond { cond_symbol: *cond_symbol, - branch_symbol: *branch_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, @@ -676,13 +681,13 @@ fn symbols_in_expr<'a>(initial: &Expr<'a>) -> MutSet { Cond { cond_symbol, - branch_symbol, + branching_symbol, pass, fail, .. } => { result.insert(*cond_symbol); - result.insert(*branch_symbol); + result.insert(*branching_symbol); for (symbol, _, expr) in pass.0.iter() { result.insert(*symbol); @@ -1189,19 +1194,20 @@ fn from_can<'a>( let cond = from_can(env, loc_cond.value, procs, layout_cache); let then = from_can(env, loc_then.value, procs, layout_cache); - let branch_symbol = env.unique_symbol(); + let branching_symbol = env.unique_symbol(); let cond_expr = Expr::Cond { - cond_symbol: branch_symbol, - branch_symbol, + cond_symbol: branching_symbol, + branching_symbol, cond_layout: cond_layout.clone(), + branching_layout: cond_layout.clone(), pass: (&[], env.arena.alloc(then)), fail: (&[], env.arena.alloc(expr)), ret_layout: ret_layout.clone(), }; expr = Expr::Store( - bumpalo::vec![in arena; (branch_symbol, Layout::Builtin(Builtin::Int1), cond)] + bumpalo::vec![in arena; (branching_symbol, Layout::Builtin(Builtin::Int1), cond)] .into_bump_slice(), env.arena.alloc(cond_expr), ); diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 0ae4d2ed9e..5a66ff1032 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -219,8 +219,9 @@ mod test_mono { )], &Cond { cond_symbol: gen_symbol_0, - branch_symbol: gen_symbol_0, + branching_symbol: gen_symbol_0, cond_layout: Builtin(Int1), + branching_layout: Builtin(Int1), pass: (&[] as &[_], &Expr::Str("bar")), fail: (&[] as &[_], &Expr::Str("foo")), ret_layout: Builtin(Str), @@ -257,8 +258,9 @@ mod test_mono { )], &Cond { cond_symbol: gen_symbol_0, - branch_symbol: gen_symbol_0, + branching_symbol: gen_symbol_0, cond_layout: Builtin(Int1), + branching_layout: Builtin(Int1), pass: (&[] as &[_], &Expr::Str("bar")), fail: ( &[] as &[_], @@ -270,7 +272,8 @@ mod test_mono { )], &Cond { cond_symbol: gen_symbol_1, - branch_symbol: gen_symbol_1, + branching_symbol: gen_symbol_1, + branching_layout: Builtin(Int1), cond_layout: Builtin(Int1), pass: (&[] as &[_], &Expr::Str("foo")), fail: (&[] as &[_], &Expr::Str("baz")), @@ -317,8 +320,9 @@ mod test_mono { )], &Cond { cond_symbol: gen_symbol_0, - branch_symbol: gen_symbol_0, + branching_symbol: gen_symbol_0, cond_layout: Builtin(Int1), + branching_layout: Builtin(Int1), pass: (&[] as &[_], &Expr::Str("bar")), fail: (&[] as &[_], &Expr::Str("foo")), ret_layout: Builtin(Str), From eb793b2b44b05fae1911a1ff647dbe5e2f901bd4 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 28 Jul 2020 01:13:49 +0200 Subject: [PATCH 05/60] write more tests for reset/reuse --- Cargo.lock | 1 + compiler/module/src/symbol.rs | 11 ++ compiler/mono/Cargo.toml | 1 + compiler/mono/src/expr.rs | 152 +++++++++++++++++++++++- compiler/mono/tests/test_mono.rs | 198 +++++++++++++++++++++++++++++++ 5 files changed, 362 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 3f3ffd29ab..706bc62802 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2222,6 +2222,7 @@ dependencies = [ "roc_solve", "roc_types", "roc_unify", + "ven_pretty", ] [[package]] diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 65b9264c04..cdb82509b8 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -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(); diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index e75d8afa11..5301d0bdc3 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -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] diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 202e02aaa6..22b87c3820 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -11,6 +11,7 @@ use roc_problem::can::RuntimeError; use roc_region::all::{Located, Region}; use roc_types::subs::{Content, FlatType, Subs, Variable}; use std::collections::HashMap; +use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder}; #[derive(Clone, Debug, PartialEq)] pub struct PartialProc<'a> { @@ -799,7 +800,6 @@ pub enum MonoProblem { PatternProblem(crate::pattern::Error), } -#[allow(clippy::too_many_arguments)] impl<'a> Expr<'a> { pub fn new( env: &mut Env<'a, '_>, @@ -811,6 +811,156 @@ impl<'a> Expr<'a> { let result = from_can(env, can_expr, procs, &mut layout_cache); function_r(env, env.arena.alloc(result)) } + + pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D, parens: bool) -> DocBuilder<'b, D, A> + where + D: DocAllocator<'b, A>, + D::Doc: Clone, + A: Clone, + { + use Expr::*; + + match self { + Int(lit) => alloc.text(format!("{}i64", lit)), + Float(lit) => alloc.text(format!("{}f64", lit)), + Bool(lit) => alloc.text(format!("{}", lit)), + Byte(lit) => alloc.text(format!("{}u8", lit)), + Str(lit) => alloc.text(format!("{:?}", lit)), + Load(symbol) if parens => alloc.text(format!("(Load {})", symbol)), + Load(symbol) => alloc.text(format!("Load {}", symbol)), + + DecAfter(symbol, expr) => expr + .to_doc(alloc, false) + .append(alloc.hardline()) + .append(alloc.text(format!("Dec {}", symbol))), + + Reset(symbol, expr) => alloc + .text(format!("Reset {}", symbol)) + .append(alloc.hardline()) + .append(expr.to_doc(alloc, false)), + + Reuse(symbol, expr) => alloc + .text(format!("Reuse {}", symbol)) + .append(alloc.hardline()) + .append(expr.to_doc(alloc, false)), + + Store(stores, expr) => { + let doc_stores = stores + .iter() + .map(|(symbol, _, expr)| { + alloc + .text(format!("Store {}: ", symbol)) + .append(expr.to_doc(alloc, false)) + }) + .collect::>(); + + alloc + .intersperse(doc_stores, alloc.hardline()) + .append(alloc.hardline()) + .append(expr.to_doc(alloc, false)) + } + + Cond { + branching_symbol, + pass, + fail, + .. + } => alloc + .text(format!("if {} then", branching_symbol)) + .append(alloc.hardline()) + .append(pass.1.to_doc(alloc, false).indent(4)) + .append(alloc.hardline()) + .append(alloc.text("else")) + .append(alloc.hardline()) + .append(fail.1.to_doc(alloc, false).indent(4)), + + Switch { + cond_symbol, + branches, + default_branch, + .. + } => { + let default_doc = alloc + .text("default:") + .append(alloc.hardline()) + .append(default_branch.1.to_doc(alloc, false).indent(4)) + .indent(4); + + let branches_docs = branches + .iter() + .map(|(tag, _, expr)| { + alloc + .text(format!("case {}:", tag)) + .append(alloc.hardline()) + .append(expr.to_doc(alloc, false).indent(4)) + .indent(4) + }) + .chain(std::iter::once(default_doc)); + // + alloc + .text(format!("switch {}:", cond_symbol)) + .append(alloc.hardline()) + .append( + alloc.intersperse(branches_docs, alloc.hardline().append(alloc.hardline())), + ) + .append(alloc.hardline()) + } + + Struct(fields) => alloc.text(format!("Struct {:?}", fields)), + + Tag { + tag_name, + arguments, + .. + } => { + let doc_tag = match tag_name { + TagName::Global(s) => alloc.text(s.as_str()), + TagName::Private(s) => alloc.text(format!("{}", s)), + }; + let doc_args = arguments.iter().map(|(expr, _)| expr.to_doc(alloc, true)); + + let it = std::iter::once(doc_tag).chain(doc_args); + + alloc.intersperse(it, alloc.space()) + } + AccessAtIndex { index, expr, .. } if parens => alloc + .text(format!("(Access @{} ", index)) + .append(expr.to_doc(alloc, false)) + .append(alloc.text(")")), + + AccessAtIndex { index, expr, .. } => alloc + .text(format!("Access @{} ", index)) + .append(expr.to_doc(alloc, false)), + + RunLowLevel(instr, arguments) => { + let doc_tag = alloc.text(format!("Lowlevel.{:?}", instr)); + let doc_args = arguments.iter().map(|(expr, _)| expr.to_doc(alloc, true)); + + let it = std::iter::once(doc_tag).chain(doc_args); + + if parens { + alloc + .text("(") + .append(alloc.intersperse(it, alloc.space())) + .append(alloc.text(")")) + } else { + alloc.intersperse(it, alloc.space()) + } + } + CallByName { name, .. } => alloc.text("*magic*"), + _ => todo!("not yet implemented: {:?}", self), + } + } + pub fn to_pretty(&self, width: usize) -> String { + let allocator = BoxAllocator; + let mut w = std::vec::Vec::new(); + self.to_doc::<_, ()>(&allocator, false) + .1 + .render(width, &mut w) + .unwrap(); + w.push(b'\n'); + String::from_utf8(w).unwrap() + } } enum IntOrFloat { diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 5a66ff1032..23abb32f8e 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -1,6 +1,9 @@ #[macro_use] extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + extern crate bumpalo; extern crate roc_mono; @@ -80,6 +83,57 @@ mod test_mono { assert_eq!(get_expected(interns), mono_expr); } + fn compiles_to_string(src: &str, expected: &str) { + let arena = Bump::new(); + let CanExprOut { + loc_expr, + var_store, + var, + constraint, + home, + mut interns, + .. + } = can_expr(src); + + let subs = Subs::new(var_store.into()); + let mut unify_problems = Vec::new(); + let (_content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var); + + // Compile and add all the Procs before adding main + let mut procs = Procs::default(); + let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap(); + + // assume 64-bit pointers + let pointer_size = std::mem::size_of::() as u32; + + // Populate Procs and Subs, and get the low-level Expr from the canonical Expr + let mut mono_problems = Vec::new(); + let mut mono_env = roc_mono::expr::Env { + arena: &arena, + subs: &mut subs, + problems: &mut mono_problems, + home, + ident_ids: &mut ident_ids, + pointer_size, + jump_counter: arena.alloc(0), + }; + let mono_expr = Expr::new(&mut mono_env, loc_expr.value, &mut procs); + let procs = + roc_mono::expr::specialize_all(&mut mono_env, procs, &mut LayoutCache::default()); + + assert_eq!( + procs.runtime_errors, + roc_collections::all::MutMap::default() + ); + + // Put this module's ident_ids back in the interns + interns.all_ident_ids.insert(home, ident_ids); + + let result = mono_expr.to_pretty(200); + + assert_eq!(result, expected); + } + #[test] fn int_literal() { compiles_to("5", Int(5)); @@ -748,4 +802,148 @@ mod test_mono { }, ) } + + #[test] + fn simple_to_string() { + compiles_to_string( + r#" + x = 3 + + x + "#, + indoc!( + r#" + Store Test.0: 3i64 + Load Test.0 + Dec Test.0 + "# + ), + ) + } + + #[test] + fn if_to_string() { + compiles_to_string( + r#" + if True then 1 else 2 + "#, + indoc!( + r#" + Store Test.0: true + if Test.0 then + 1i64 + else + 2i64 + "# + ), + ) + } + + #[test] + fn maybe_map_to_string() { + compiles_to_string( + r#" + maybe : [ Nothing, Just Int ] + maybe = Just 3 + + when maybe is + Just x -> Just (x + 1) + Nothing -> Nothing + "#, + indoc!( + r#" + Store Test.0: Just 0i64 3i64 + Store Test.0: Load Test.0 + Store Test.2: Lowlevel.And (Lowlevel.Eq 0i64 (Access @0 Load Test.0)) true + + if Test.2 then + Reset Test.0 + Reuse Test.0 + Just 0i64 *magic* + else + Reset Test.0 + Reuse Test.0 + Nothing 1i64 + Dec Test.0 + "# + ), + ) + } + + #[test] + fn very_maybe_map_to_string() { + compiles_to_string( + r#" + Maybe a : [ Nothing, Just a ] + + veryMaybe : Maybe (Maybe Int) + veryMaybe = Just (Just 3) + + when veryMaybe is + Just (Just _) -> Just (Just 1) + Just Nothing -> Just Nothing + Nothing -> Nothing + "#, + indoc!( + r#" + Store Test.1: Just 0i64 Just 0i64 3i64 + Store Test.1: Load Test.1 + Store Test.5: Lowlevel.And (Lowlevel.Eq 0i64 (Access @0 Load Test.1)) true + + if Test.5 then + + if Test.4 then + Just 0i64 Just 0i64 1i64 + else + Just 0i64 Nothing 1i64 + else + Reset Test.1 + Reuse Test.1 + Nothing 1i64 + Dec Test.1 + "# + ), + ) + } + + #[test] + fn these_map_to_string() { + compiles_to_string( + r#" + These a b : [ This a, That b, These a b ] + + these : These Int Int + these = These 1 2 + + when these is + This a -> This a + That b -> That b + These a b -> These b a + "#, + indoc!( + r#" + Store Test.1: These 1i64 1i64 2i64 + Store Test.1: Load Test.1 + + switch Test.1: + case 2: + Reset Test.1 + Reuse Test.1 + This 2i64 (Load Test.2) + + case 0: + Reset Test.1 + Reuse Test.1 + That 0i64 (Load Test.3) + + default: + Reset Test.1 + Reuse Test.1 + These 1i64 (Load Test.5) (Load Test.4) + + Dec Test.1 + "# + ), + ) + } } From d784f62cd3f7b4640b2430c11c53993137680e40 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 30 Jul 2020 13:30:46 +0200 Subject: [PATCH 06/60] cleanup --- compiler/mono/src/expr.rs | 484 ++++--------------------------- compiler/mono/src/layout.rs | 4 +- compiler/mono/src/lib.rs | 1 + compiler/mono/src/reset_reuse.rs | 482 ++++++++++++++++++++++++++++++ compiler/mono/tests/test_mono.rs | 222 ++++++++------ 5 files changed, 661 insertions(+), 532 deletions(-) create mode 100644 compiler/mono/src/reset_reuse.rs diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 22b87c3820..60dd4e238e 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -36,6 +36,38 @@ pub struct Proc<'a> { pub ret_layout: Layout<'a>, } +impl<'a> Proc<'a> { + pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D, parens: bool) -> DocBuilder<'b, D, A> + where + D: DocAllocator<'b, A>, + D::Doc: Clone, + A: Clone, + { + let args_doc = self + .args + .iter() + .map(|(_, symbol)| alloc.text(format!("{}", symbol))); + + alloc + .text(format!("procedure {} (", self.name)) + .append(alloc.intersperse(args_doc, ", ")) + .append("):") + .append(alloc.hardline()) + .append(self.body.to_doc(alloc, false).indent(4)) + } + + pub fn to_pretty(&self, width: usize) -> String { + let allocator = BoxAllocator; + let mut w = std::vec::Vec::new(); + self.to_doc::<_, ()>(&allocator, false) + .1 + .render(width, &mut w) + .unwrap(); + w.push(b'\n'); + String::from_utf8(w).unwrap() + } +} + #[derive(Clone, Debug, PartialEq, Default)] pub struct Procs<'a> { pub partial_procs: MutMap>, @@ -368,433 +400,6 @@ pub enum Expr<'a> { RuntimeError(&'a str), } -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 - | LoadWithoutIncrement(_) - | 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) - } - } - - DecAfter(symbol, expr) => { - let new_expr = function_s(env, w, stack_size, expr)?; - - Ok(env.arena.alloc(DecAfter(*symbol, new_expr))) - } - - Store(stores, expr) => { - let new_expr = function_s(env, w, stack_size, expr)?; - - Ok(env.arena.alloc(Store(*stores, 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 - | LoadWithoutIncrement(_) - | FunctionPointer(_, _) - | RuntimeError(_) - | RuntimeErrorFunction(_) => Err(body), - - Reset(_, _) | Reuse(_, _) => { - unreachable!("reset/reuse should not have been introduced yet") - } - } -} - -fn symbols_in_expr<'a>(initial: &Expr<'a>) -> MutSet { - use Expr::*; - let mut result = MutSet::default(); - let mut stack = vec![initial]; - - while let Some(expr) = stack.pop() { - match expr { - FunctionPointer(symbol, _) | LoadWithoutIncrement(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) => { - 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 (expr, _) in arguments.iter() { - stack.push(expr); - } - } - - 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 -} - #[derive(Clone, Debug)] pub enum MonoProblem { PatternProblem(crate::pattern::Error), @@ -806,10 +411,12 @@ impl<'a> Expr<'a> { can_expr: roc_can::expr::Expr, procs: &mut Procs<'a>, ) -> Self { + use crate::reset_reuse; + let mut layout_cache = LayoutCache::default(); let result = from_can(env, can_expr, procs, &mut layout_cache); - function_r(env, env.arena.alloc(result)) + reset_reuse::function_r(env, env.arena.alloc(result)) } pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D, parens: bool) -> DocBuilder<'b, D, A> @@ -923,6 +530,15 @@ impl<'a> Expr<'a> { alloc.intersperse(it, alloc.space()) } + Array { elems, .. } if elems.is_empty() => alloc.text("[]"), + Array { elems, .. } => { + let it = elems.iter().map(|expr| expr.to_doc(alloc, true)); + + alloc + .text("[ ") + .append(alloc.intersperse(it, ", ")) + .append(" ]") + } AccessAtIndex { index, expr, .. } if parens => alloc .text(format!("(Access @{} ", index)) .append(expr.to_doc(alloc, false)) @@ -1208,11 +824,6 @@ fn from_can<'a>( let symbols = roc_can::pattern::symbols_from_pattern(&def.loc_pattern.value); let mut result = from_can_defs(env, vec![*def], *ret_expr, layout_cache, procs); - // TODO is order important here? - for symbol in symbols { - result = decrement_refcount(env, symbol, result); - } - result } @@ -1802,11 +1413,16 @@ fn from_can_defs<'a>( } // At this point, it's safe to assume we aren't assigning a Closure to a def. // Extract Procs from the def body and the ret expression, and return the result! - let ret = from_can(env, ret_expr.value, procs, layout_cache); + let mut ret = from_can(env, ret_expr.value, procs, layout_cache); if stored.is_empty() { ret } else { + // NOTE for scoping reasons, it's important that the refcount decrement is inside of the + // Store, not outside it. + for (symbol, _, _) in stored.iter() { + ret = decrement_refcount(env, *symbol, ret); + } Expr::Store(stored.into_bump_slice(), arena.alloc(ret)) } } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index ee050bf13b..68491334c9 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -346,8 +346,8 @@ fn layout_from_flat_type<'a>( Ok(layout_from_tag_union(arena, tags, subs, pointer_size)) } - 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"); diff --git a/compiler/mono/src/lib.rs b/compiler/mono/src/lib.rs index 8b78927d46..391c98a3e1 100644 --- a/compiler/mono/src/lib.rs +++ b/compiler/mono/src/lib.rs @@ -13,6 +13,7 @@ pub mod expr; pub mod layout; +pub mod reset_reuse; // 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. diff --git a/compiler/mono/src/reset_reuse.rs b/compiler/mono/src/reset_reuse.rs new file mode 100644 index 0000000000..54a2c11fec --- /dev/null +++ b/compiler/mono/src/reset_reuse.rs @@ -0,0 +1,482 @@ +use crate::expr::Env; +use crate::expr::Expr; + +use crate::layout::{Builtin, Layout}; +use bumpalo::collections::Vec; +use bumpalo::Bump; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::ident::{Ident, Lowercase, TagName}; +use roc_module::symbol::{IdentIds, ModuleId, Symbol}; +use roc_region::all::{Located, Region}; + +use Expr::*; +/* +R : FnBodypure → FnBodyRC +R(let x = e; F ) = let x = e; R(F ) +R(ret x) = ret x +R(case x of F ) = case x of D(x,ni +, R(Fi)) +where ni = #fields of x in i-th branch +D : Var × N × FnBodyRC → FnBodyRC +D(z,n, case x of F ) = case x of D(z,n, F ) +D(z,n, ret x) = ret x +D(z,n, let x = e; F ) = let x = e; D(z,n, F ) +if z ∈ e or z ∈ F +D(z,n, F ) = let w = reset z; S(w,n, F ) +otherwise, if S(w,n, F ) , F for a fresh w +D(z,n, F ) = F otherwise +S : Var × N × FnBodyRC → FnBodyRC +S(w,n, let x = ctori y; F ) = let x = reuse w in ctori y; F +if | y |= n +S(w,n, let x = e; F ) = let x = e; S(w,n, F ) otherwise +S(w,n, ret x) = ret x +S(w,n, case x of F ) = case x of S(w,n, F ) + + +Maybe a : [ Nothing, Just a ] + +map : Maybe a -> (a -> b) -> Maybe b +map = \maybe f -> + when maybe is + Nothing -> Nothing + Just x -> Just (f x) + + +map : Maybe a -> (a -> b) -> Maybe b +map = \maybe f -> + when maybe is + Nothing -> + let w = reset maybe + let r = reuse w in AppliedTag("Nothing", vec![]) + Just x -> + let v = f x + let w = reset maybe + let r = reuse w in AppliedTag("Just", vec![v]) +*/ + +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 + | LoadWithoutIncrement(_) + | 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 + | LoadWithoutIncrement(_) + | FunctionPointer(_, _) + | RuntimeError(_) + | RuntimeErrorFunction(_) => Err(body), + + Reset(_, _) | Reuse(_, _) => { + unreachable!("reset/reuse should not have been introduced yet") + } + } +} + +fn symbols_in_expr<'a>(initial: &Expr<'a>) -> MutSet { + use Expr::*; + let mut result = MutSet::default(); + let mut stack = vec![initial]; + + while let Some(expr) = stack.pop() { + match expr { + FunctionPointer(symbol, _) | LoadWithoutIncrement(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) => { + 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 (expr, _) in arguments.iter() { + stack.push(expr); + } + } + + 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 +} diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 23abb32f8e..0767730463 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -17,7 +17,7 @@ mod test_mono { use roc_module::ident::TagName; use roc_module::symbol::{Interns, Symbol}; use roc_mono::expr::Expr::{self, *}; - use roc_mono::expr::Procs; + use roc_mono::expr::{InProgressProc, Procs}; use roc_mono::layout; use roc_mono::layout::{Builtin, Layout, LayoutCache}; use roc_types::subs::Subs; @@ -129,7 +129,21 @@ mod test_mono { // Put this module's ident_ids back in the interns interns.all_ident_ids.insert(home, ident_ids); - let result = mono_expr.to_pretty(200); + let mut procs_string = procs + .specialized + .iter() + .map(|(_, value)| { + if let InProgressProc::Done(proc) = value { + proc.to_pretty(200) + } else { + String::new() + } + }) + .collect::>(); + + procs_string.push(mono_expr.to_pretty(200)); + + let result = procs_string.join("\n"); assert_eq!(result, expected); } @@ -221,33 +235,30 @@ mod test_mono { let home = test_home(); let gen_symbol_0 = Interns::from_index(home, 0); - DecAfter( - gen_symbol_0, - &Struct(&[ - ( - CallByName { - name: gen_symbol_0, - layout: Layout::FunctionPointer( - &[Layout::Builtin(Builtin::Int64)], - &Layout::Builtin(Builtin::Int64), - ), - args: &[(Int(4), Layout::Builtin(Int64))], - }, - Layout::Builtin(Int64), - ), - ( - CallByName { - name: gen_symbol_0, - layout: Layout::FunctionPointer( - &[Layout::Builtin(Builtin::Float64)], - &Layout::Builtin(Builtin::Float64), - ), - args: &[(Float(3.14), Layout::Builtin(Float64))], - }, - Layout::Builtin(Float64), - ), - ]), - ) + Struct(&[ + ( + CallByName { + name: gen_symbol_0, + layout: Layout::FunctionPointer( + &[Layout::Builtin(Builtin::Int64)], + &Layout::Builtin(Builtin::Int64), + ), + args: &[(Int(4), Layout::Builtin(Int64))], + }, + Layout::Builtin(Int64), + ), + ( + CallByName { + name: gen_symbol_0, + layout: Layout::FunctionPointer( + &[Layout::Builtin(Builtin::Float64)], + &Layout::Builtin(Builtin::Float64), + ), + args: &[(Float(3.14), Layout::Builtin(Float64))], + }, + Layout::Builtin(Float64), + ), + ]) }, ) } @@ -360,31 +371,28 @@ mod test_mono { let gen_symbol_0 = Interns::from_index(home, 1); let symbol_x = Interns::from_index(home, 0); - DecAfter( - symbol_x, - &Store( - &[( - symbol_x, - Builtin(Str), - Store( - &[( - gen_symbol_0, - Layout::Builtin(layout::Builtin::Int1), - Expr::Bool(true), - )], - &Cond { - cond_symbol: gen_symbol_0, - branching_symbol: gen_symbol_0, - cond_layout: Builtin(Int1), - branching_layout: Builtin(Int1), - pass: (&[] as &[_], &Expr::Str("bar")), - fail: (&[] as &[_], &Expr::Str("foo")), - ret_layout: Builtin(Str), - }, - ), - )], - &Load(symbol_x), - ), + Store( + &[( + symbol_x, + Builtin(Str), + Store( + &[( + gen_symbol_0, + Layout::Builtin(layout::Builtin::Int1), + Expr::Bool(true), + )], + &Cond { + cond_symbol: gen_symbol_0, + branching_symbol: gen_symbol_0, + cond_layout: Builtin(Int1), + branching_layout: Builtin(Int1), + pass: (&[] as &[_], &Expr::Str("bar")), + fail: (&[] as &[_], &Expr::Str("foo")), + ret_layout: Builtin(Str), + }, + ), + )], + &DecAfter(symbol_x, &Load(symbol_x)), ) }, ) @@ -424,30 +432,27 @@ mod test_mono { let gen_symbol_0 = Interns::from_index(home, 0); let struct_layout = Layout::Struct(&[I64_LAYOUT, F64_LAYOUT]); - DecAfter( - gen_symbol_0, - &CallByName { - name: gen_symbol_0, - layout: Layout::FunctionPointer( - &[struct_layout.clone()], - &struct_layout.clone(), - ), - args: &[( - Struct(&[ - ( - CallByName { - name: gen_symbol_0, - layout: Layout::FunctionPointer(&[I64_LAYOUT], &I64_LAYOUT), - args: &[(Int(4), I64_LAYOUT)], - }, - I64_LAYOUT, - ), - (Float(0.1), F64_LAYOUT), - ]), - struct_layout, - )], - }, - ) + CallByName { + name: gen_symbol_0, + layout: Layout::FunctionPointer( + &[struct_layout.clone()], + &struct_layout.clone(), + ), + args: &[( + Struct(&[ + ( + CallByName { + name: gen_symbol_0, + layout: Layout::FunctionPointer(&[I64_LAYOUT], &I64_LAYOUT), + args: &[(Int(4), I64_LAYOUT)], + }, + I64_LAYOUT, + ), + (Float(0.1), F64_LAYOUT), + ]), + struct_layout, + )], + } }, ) } @@ -561,9 +566,9 @@ mod test_mono { let load = Load(var_x); - let store = Store(arena.alloc(stores), arena.alloc(load)); + let dec = DecAfter(var_x, arena.alloc(load)); - DecAfter(var_x, arena.alloc(store)) + Store(arena.alloc(stores), arena.alloc(dec)) }, ); } @@ -587,9 +592,9 @@ mod test_mono { let load = Load(var_x); - let store = Store(arena.alloc(stores), arena.alloc(load)); + let dec = DecAfter(var_x, arena.alloc(load)); - DecAfter(var_x, arena.alloc(store)) + Store(arena.alloc(stores), arena.alloc(dec)) }, ); } @@ -615,9 +620,8 @@ mod test_mono { let load = Load(var_x); - let store = Store(arena.alloc(stores), arena.alloc(load)); - - DecAfter(var_x, arena.alloc(store)) + let dec = DecAfter(var_x, arena.alloc(load)); + Store(arena.alloc(stores), arena.alloc(dec)) }, ); } @@ -843,8 +847,10 @@ mod test_mono { fn maybe_map_to_string() { compiles_to_string( r#" - maybe : [ Nothing, Just Int ] - maybe = Just 3 + Maybe a : [ Nothing, Just a ] + + maybe : Maybe Int + maybe = Just 0x3 when maybe is Just x -> Just (x + 1) @@ -852,19 +858,22 @@ mod test_mono { "#, indoc!( r#" - Store Test.0: Just 0i64 3i64 - Store Test.0: Load Test.0 - Store Test.2: Lowlevel.And (Lowlevel.Eq 0i64 (Access @0 Load Test.0)) true + procedure Num.14 (#Attr.2, #Attr.3): + Lowlevel.NumAdd (Load #Attr.2) (Load #Attr.3) - if Test.2 then - Reset Test.0 - Reuse Test.0 + Store Test.1: Just 0i64 3i64 + Store Test.1: Load Test.1 + Store Test.3: Lowlevel.And (Lowlevel.Eq 0i64 (Access @0 Load Test.1)) true + + if Test.3 then + Reset Test.1 + Reuse Test.1 Just 0i64 *magic* else - Reset Test.0 - Reuse Test.0 + Reset Test.1 + Reuse Test.1 Nothing 1i64 - Dec Test.0 + Dec Test.1 "# ), ) @@ -946,4 +955,25 @@ mod test_mono { ), ) } + + #[test] + fn list_length() { + compiles_to_string( + r#" + x = [ 1,2,3 ] + + List.len x + "#, + indoc!( + r#" + procedure List.7 (#Attr.2): + Lowlevel.ListLen (Load #Attr.2) + + Store Test.0: [ 1i64, 2i64, 3i64 ] + *magic* + Dec Test.0 + "# + ), + ) + } } From 22471167d305b729f1324248a772c16a8395b820 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 30 Jul 2020 13:42:47 +0200 Subject: [PATCH 07/60] introduce Ownership to list layout --- compiler/gen/src/llvm/build.rs | 185 +++++++++++++++++++++++++++++-- compiler/gen/src/llvm/convert.rs | 2 +- compiler/gen/tests/gen_list.rs | 15 +++ compiler/mono/src/expr.rs | 2 +- compiler/mono/src/layout.rs | 25 ++++- 5 files changed, 213 insertions(+), 16 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index b9394c9049..d0be52ae5b 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -720,22 +720,187 @@ pub fn build_expr<'a, 'ctx, 'env>( } RunLowLevel(op, args) => run_low_level(env, layout_ids, scope, parent, *op, args), - DecAfter(_, expr) => build_expr(env, layout_ids, scope, parent, expr), + DecAfter(symbol, expr) => { + match scope.get(symbol) { + None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope), + Some((layout, ptr)) => { + match layout { + Layout::Builtin(Builtin::List(_, elem_layout)) if false => { + // first run the body + let body = build_expr(env, layout_ids, scope, parent, expr); + + let wrapper_struct = env + .builder + .build_load(*ptr, symbol.ident_string(&env.interns)) + .into_struct_value(); + + decrement_refcount_list(env, parent, wrapper_struct, body) + } + _ => { + // not refcounted, do nothing special + build_expr(env, layout_ids, scope, parent, expr) + } + } + } + } + } Reuse(_, expr) => build_expr(env, layout_ids, scope, parent, expr), Reset(_, expr) => build_expr(env, layout_ids, scope, parent, expr), } } +fn refcount_is_one_comparison<'ctx>( + builder: &Builder<'ctx>, + context: &'ctx Context, + refcount: IntValue<'ctx>, +) -> IntValue<'ctx> { + let refcount_one: IntValue<'ctx> = context + .i64_type() + .const_int((std::usize::MAX - 1) as _, false); + // Note: Check for refcount < refcount_1 as the "true" condition, + // to avoid misprediction. (In practice this should usually pass, + // and CPUs generally default to predicting that a forward jump + // shouldn't be taken; that is, they predict "else" won't be taken.) + builder.build_int_compare( + IntPredicate::ULT, + refcount, + refcount_one, + "refcount_one_check", + ) +} + +fn list_get_refcount_ptr<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + list_wrapper: StructValue<'ctx>, +) -> PointerValue<'ctx> { + let builder = env.builder; + let ctx = env.context; + + // basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let elem_type = ctx.i64_type().into(); + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + // Load the pointer to the array data + let array_data_ptr = load_list_ptr(builder, list_wrapper, ptr_type); + + // get the refcount + let zero_index = ctx.i64_type().const_zero(); + unsafe { + // SAFETY + // index 0 is always defined for lists. + builder.build_in_bounds_gep(array_data_ptr, &[zero_index], "refcount_index") + } +} + +fn increment_refcount_list<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + original_wrapper: StructValue<'ctx>, + body: BasicValueEnum<'ctx>, +) -> BasicValueEnum<'ctx> { + let builder = env.builder; + let ctx = env.context; + + let refcount_ptr = list_get_refcount_ptr(env, original_wrapper); + + let refcount = env + .builder + .build_load(refcount_ptr, "get_refcount") + .into_int_value(); + + // our refcount 0 is actually usize::MAX, so incrementing the refcount means decrementing this value. + let decremented = env.builder.build_int_sub( + refcount, + ctx.i64_type().const_int(1 as u64, false), + "new_refcount", + ); + + // Mutate the new array in-place to change the element. + builder.build_store(refcount_ptr, decremented); + + body +} + +fn decrement_refcount_list<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, + original_wrapper: StructValue<'ctx>, + body: BasicValueEnum<'ctx>, +) -> BasicValueEnum<'ctx> { + let builder = env.builder; + let ctx = env.context; + + let refcount_ptr = list_get_refcount_ptr(env, original_wrapper); + + let refcount = env + .builder + .build_load(refcount_ptr, "get_refcount") + .into_int_value(); + + let comparison = refcount_is_one_comparison(builder, env.context, refcount); + + // the refcount is higher than 1, write the decremented value + let build_then = || { + // our refcount 0 is actually usize::MAX, so decrementing the refcount means incrementing this value. + let decremented = env.builder.build_int_add( + ctx.i64_type().const_int(1 as u64, false), + refcount, + "new_refcount", + ); + + // Mutate the new array in-place to change the element. + builder.build_store(refcount_ptr, decremented); + + body + }; + + // refcount is one, and will be decremented. This list can be freed + let build_else = || { + let array_data_ptr = load_list_ptr( + builder, + original_wrapper, + ctx.i64_type().ptr_type(AddressSpace::Generic), + ); + let free = builder.build_free(array_data_ptr); + + builder.insert_instruction(&free, None); + + body + }; + let ret_type = body.get_type(); + + build_basic_phi2( + env, + parent, + comparison, + build_then, + build_else, + ret_type.into(), + ) +} + fn load_symbol<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, symbol: &Symbol, ) -> BasicValueEnum<'ctx> { match scope.get(symbol) { - Some((_, ptr)) => env - .builder - .build_load(*ptr, symbol.ident_string(&env.interns)), + Some((layout, ptr)) => match layout { + Layout::Builtin(Builtin::List(_, _)) if false => { + let load = env + .builder + .build_load(*ptr, symbol.ident_string(&env.interns)); + + let wrapper_struct = env + .builder + .build_load(*ptr, symbol.ident_string(&env.interns)) + .into_struct_value(); + + increment_refcount_list(env, wrapper_struct, load) + } + _ => env + .builder + .build_load(*ptr, symbol.ident_string(&env.interns)), + }, None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope), } } @@ -1670,7 +1835,7 @@ fn run_low_level<'a, 'ctx, 'env>( let (list, list_layout) = &args[0]; match list_layout { - Layout::Builtin(Builtin::List(elem_layout)) => { + Layout::Builtin(Builtin::List(_, elem_layout)) => { let wrapper_struct = build_expr(env, layout_ids, scope, parent, list).into_struct_value(); @@ -1979,7 +2144,7 @@ fn run_low_level<'a, 'ctx, 'env>( build_expr(env, layout_ids, scope, parent, &args[1].0).into_int_value(); match list_layout { - Layout::Builtin(Builtin::List(elem_layout)) => { + Layout::Builtin(Builtin::List(_, elem_layout)) => { let ctx = env.context; let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); @@ -2129,7 +2294,7 @@ fn list_append<'a, 'ctx, 'env>( Layout::Builtin(Builtin::EmptyList) => { match second_list_layout { Layout::Builtin(Builtin::EmptyList) => empty_list(env), - Layout::Builtin(Builtin::List(elem_layout)) => { + Layout::Builtin(Builtin::List(_, elem_layout)) => { // THIS IS A COPY AND PASTE // All the code under the Layout::Builtin(Builtin::List()) match branch // is the same as what is under `if_first_list_is_empty`. Re-using @@ -2175,7 +2340,7 @@ fn list_append<'a, 'ctx, 'env>( } } } - Layout::Builtin(Builtin::List(elem_layout)) => { + Layout::Builtin(Builtin::List(_, elem_layout)) => { let first_list_wrapper = build_expr(env, layout_ids, scope, parent, first_list).into_struct_value(); @@ -2214,7 +2379,7 @@ fn list_append<'a, 'ctx, 'env>( BasicValueEnum::StructValue(new_wrapper) } - Layout::Builtin(Builtin::List(_)) => { + Layout::Builtin(Builtin::List(_, _)) => { // second_list_len > 0 // We do this check to avoid allocating memory. If the second input // list is empty, then we can just return the first list cloned @@ -2453,7 +2618,7 @@ fn list_append<'a, 'ctx, 'env>( let if_first_list_is_empty = || { match second_list_layout { Layout::Builtin(Builtin::EmptyList) => empty_list(env), - Layout::Builtin(Builtin::List(elem_layout)) => { + Layout::Builtin(Builtin::List(_, elem_layout)) => { // second_list_len > 0 // We do this check to avoid allocating memory. If the second input // list is empty, then we can just return the first list cloned diff --git a/compiler/gen/src/llvm/convert.rs b/compiler/gen/src/llvm/convert.rs index 5d6b91bdd6..d5aff6b097 100644 --- a/compiler/gen/src/llvm/convert.rs +++ b/compiler/gen/src/llvm/convert.rs @@ -137,7 +137,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)), }, } diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index e5d440ba89..407dcf7091 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -614,4 +614,19 @@ mod gen_list { ); }) } + + #[test] + fn gen_list_increment_decrement() { + assert_evals_to!( + indoc!( + r#" + x = [ 1,2,3 ] + + List.len x + "# + ), + 3, + i64 + ); + } } diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 60dd4e238e..515bfc2ea6 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -1130,7 +1130,7 @@ fn from_can<'a>( match list_layout_from_elem(arena, subs, elem_var, env.pointer_size) { Ok(Layout::Builtin(Builtin::EmptyList)) => Expr::EmptyArray, - Ok(Layout::Builtin(Builtin::List(elem_layout))) => { + Ok(Layout::Builtin(Builtin::List(_, elem_layout))) => { let mut elems = Vec::with_capacity_in(loc_elems.len(), arena); for loc_elem in loc_elems { diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 68491334c9..bdbc7d5dd1 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -27,6 +27,20 @@ pub enum Layout<'a> { Pointer(&'a Layout<'a>), } +#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)] +pub enum Ownership { + /// The default. Object is reference counted + Owned, + + /// Do not update reference counter, surrounding context + /// keeps this value alive + Borrowed, + + /// Object is unique, can be mutated in-place and + /// is not reference counted + Unique, +} + #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Builtin<'a> { Int128, @@ -42,7 +56,7 @@ pub enum Builtin<'a> { Str, Map(&'a Layout<'a>, &'a Layout<'a>), Set(&'a Layout<'a>), - List(&'a Layout<'a>), + List(Ownership, &'a Layout<'a>), EmptyStr, EmptyList, EmptyMap, @@ -219,7 +233,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, } } @@ -229,7 +243,7 @@ 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, } } } @@ -696,7 +710,10 @@ pub fn list_layout_from_elem<'a>( let elem_layout = Layout::new(arena, content, subs, pointer_size)?; // This is a normal list. - Ok(Layout::Builtin(Builtin::List(arena.alloc(elem_layout)))) + Ok(Layout::Builtin(Builtin::List( + Ownership::Owned, + arena.alloc(elem_layout), + ))) } } } From 4a937b5cc2b8f633a5fdc27b329dbdbf470b75e9 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 30 Jul 2020 14:32:59 +0200 Subject: [PATCH 08/60] allocate lists with a capacity/refcount field at index -1 --- compiler/gen/src/llvm/build.rs | 135 ++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 53 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index d0be52ae5b..c86f1d02fe 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -397,7 +397,6 @@ pub fn build_expr<'a, 'ctx, 'env>( } Array { elem_layout, elems } => { let ctx = env.context; - let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let builder = env.builder; if elems.is_empty() { @@ -411,9 +410,7 @@ pub fn build_expr<'a, 'ctx, 'env>( let len_type = env.ptr_int(); let len = len_type.const_int(bytes_len, false); - env.builder - .build_array_malloc(elem_type, len, "create_list_ptr") - .unwrap() + allocate_list(env, elem_layout, len) // TODO check if malloc returned null; if so, runtime error for OOM! }; @@ -868,14 +865,7 @@ fn decrement_refcount_list<'a, 'ctx, 'env>( }; let ret_type = body.get_type(); - build_basic_phi2( - env, - parent, - comparison, - build_then, - build_else, - ret_type.into(), - ) + build_basic_phi2(env, parent, comparison, build_then, build_else, ret_type) } fn load_symbol<'a, 'ctx, 'env>( @@ -1229,6 +1219,76 @@ pub fn verify_fn(fn_val: FunctionValue<'_>) { } } +pub fn allocate_list<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + elem_layout: &Layout<'a>, + length: IntValue<'ctx>, +) -> PointerValue<'ctx> { + let builder = env.builder; + let ctx = env.context; + + let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; + + let ptr = { + let len_type = env.ptr_int(); + + // bytes per element + let bytes_len = len_type.const_int(elem_bytes, false); + + let len = builder.build_int_mul(bytes_len, length, "data_length"); + // TODO 8 assume 64-bit pointer/refcount + let len = builder.build_int_add(len, len_type.const_int(8, false), "add_refcount_space"); + + env.builder + .build_array_malloc(elem_type, len, "create_list_ptr") + .unwrap() + + // TODO check if malloc returned null; if so, runtime error for OOM! + }; + + // Put the element into the list + let refcount_ptr = unsafe { + builder.build_in_bounds_gep( + ptr, + &[ctx.i64_type().const_int( + // 0 as in 0 index of our new list + 0 as u64, false, + )], + "index", + ) + }; + + // cast the refcount_ptr to be an u64_ptr + let refcount_ptr = builder + .build_bitcast( + refcount_ptr, + get_ptr_type( + &BasicTypeEnum::IntType(ctx.i64_type()), + AddressSpace::Generic, + ), + "refcount_ptr", + ) + .into_pointer_value(); + + // put our "refcount 0" in the first slot + let ref_count_zero = ctx.i64_type().const_int(std::usize::MAX as u64, false); + builder.build_store(refcount_ptr, ref_count_zero); + + // We must return a pointer to the first element: + let ptr_bytes = env.ptr_bytes; + let int_type = ptr_int(ctx, ptr_bytes); + let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr"); + let incremented = builder.build_int_add( + ptr_as_int, + ctx.i64_type().const_int(8, false), + "increment_list_ptr", + ); + + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + builder.build_int_to_ptr(incremented, ptr_type, "list_cast_ptr") +} + /// List.single : a -> List a fn list_single<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, @@ -1238,20 +1298,9 @@ fn list_single<'a, 'ctx, 'env>( let builder = env.builder; let ctx = env.context; - let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; - - let ptr = { - let bytes_len = elem_bytes; - let len_type = env.ptr_int(); - let len = len_type.const_int(bytes_len, false); - - env.builder - .build_array_malloc(elem_type, len, "create_list_ptr") - .unwrap() - - // TODO check if malloc returned null; if so, runtime error for OOM! - }; + // allocate a list of size 1 on the heap + let size = ctx.i64_type().const_int(1, false); + let ptr = allocate_list(env, elem_layout, size); // Put the element into the list let elem_ptr = unsafe { @@ -1308,7 +1357,6 @@ fn list_repeat<'a, 'ctx, 'env>( ) -> BasicValueEnum<'ctx> { let builder = env.builder; let ctx = env.context; - let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); // list_len > 0 // We have to do a loop below, continuously adding the `elem` @@ -1326,10 +1374,7 @@ fn list_repeat<'a, 'ctx, 'env>( let build_then = || { // Allocate space for the new array that we'll copy into. - let list_ptr = builder - .build_array_malloc(elem_type, list_len, "create_list_ptr") - .unwrap(); - + let list_ptr = allocate_list(env, elem_layout, list_len); // TODO check if malloc returned null; if so, runtime error for OOM! let index_name = "#index"; @@ -1542,10 +1587,8 @@ fn clone_nonempty_list<'a, 'ctx, 'env>( .build_int_mul(elem_bytes, list_len, "mul_len_by_elem_bytes"); // Allocate space for the new array that we'll copy into. - let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - let clone_ptr = builder - .build_array_malloc(elem_type, list_len, "list_ptr") - .unwrap(); + let clone_ptr = allocate_list(env, elem_layout, list_len); + let int_type = ptr_int(ctx, ptr_bytes); let ptr_as_int = builder.build_ptr_to_int(clone_ptr, int_type, "list_cast_ptr"); @@ -1658,10 +1701,7 @@ fn list_push<'a, 'ctx, 'env>( .build_int_mul(elem_bytes, list_len, "mul_old_len_by_elem_bytes"); // Allocate space for the new array that we'll copy into. - let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - let clone_ptr = builder - .build_array_malloc(elem_type, new_list_len, "list_ptr") - .unwrap(); + let clone_ptr = allocate_list(env, elem_layout, new_list_len); let int_type = ptr_int(ctx, ptr_bytes); let ptr_as_int = builder.build_ptr_to_int(clone_ptr, int_type, "list_cast_ptr"); @@ -1861,10 +1901,7 @@ fn run_low_level<'a, 'ctx, 'env>( let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - let reversed_list_ptr = env - .builder - .build_array_malloc(elem_type, list_len, "create_reversed_list_ptr") - .unwrap(); + let reversed_list_ptr = allocate_list(env, elem_layout, list_len); // TODO check if malloc returned null; if so, runtime error for OOM! @@ -2355,8 +2392,6 @@ fn list_append<'a, 'ctx, 'env>( let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - let if_second_list_is_empty = || { let (new_wrapper, _) = clone_nonempty_list( env, @@ -2393,14 +2428,8 @@ fn list_append<'a, 'ctx, 'env>( "add_list_lengths", ); - let combined_list_ptr = env - .builder - .build_array_malloc( - elem_type, - combined_list_len, - "create_combined_list_ptr", - ) - .unwrap(); + let combined_list_ptr = + allocate_list(env, elem_layout, combined_list_len); let index_name = "#index"; let index_alloca = builder.build_alloca(ctx.i64_type(), index_name); From f15a50d3fa510de36f89cd18a732fc4483e5ba9f Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 31 Jul 2020 00:02:36 +0200 Subject: [PATCH 09/60] implement inc and dec for lists --- compiler/gen/src/llvm/build.rs | 205 ++++++++++++++++--------------- compiler/gen/tests/gen_list.rs | 45 ++++++- compiler/mono/src/expr.rs | 16 ++- compiler/mono/tests/test_mono.rs | 44 +++++-- 4 files changed, 199 insertions(+), 111 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index c86f1d02fe..4961f20d18 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -19,7 +19,7 @@ use roc_collections::all::ImMap; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, Symbol}; use roc_mono::expr::{Expr, Proc}; -use roc_mono::layout::{Builtin, Layout}; +use roc_mono::layout::{Builtin, Layout, Ownership}; use target_lexicon::CallingConvention; /// This is for Inkwell's FunctionValue::verify - we want to know the verification @@ -388,72 +388,9 @@ pub fn build_expr<'a, 'ctx, 'env>( BasicValueEnum::PointerValue(ptr) } } - EmptyArray => { - let struct_type = collection(env.context, env.ptr_bytes); - - // The pointer should be null (aka zero) and the length should be zero, - // so the whole struct should be a const_zero - BasicValueEnum::StructValue(struct_type.const_zero()) - } + EmptyArray => empty_polymorphic_list(env), Array { elem_layout, elems } => { - let ctx = env.context; - let builder = env.builder; - - if elems.is_empty() { - empty_list(env) - } else { - let len_u64 = elems.len() as u64; - let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; - - let ptr = { - let bytes_len = elem_bytes * len_u64; - let len_type = env.ptr_int(); - let len = len_type.const_int(bytes_len, false); - - allocate_list(env, elem_layout, len) - - // TODO check if malloc returned null; if so, runtime error for OOM! - }; - - // Copy the elements from the list literal into the array - for (index, elem) in elems.iter().enumerate() { - let index_val = ctx.i64_type().const_int(index as u64, false); - let elem_ptr = - unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; - let val = build_expr(env, layout_ids, &scope, parent, &elem); - - builder.build_store(elem_ptr, val); - } - - let ptr_bytes = env.ptr_bytes; - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr"); - let struct_type = collection(ctx, ptr_bytes); - let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false)); - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len") - .unwrap(); - - // Bitcast to an array of raw bytes - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", - ) - } + list_literal(env, layout_ids, scope, parent, elem_layout, elems) } Struct(sorted_fields) => { @@ -722,7 +659,7 @@ pub fn build_expr<'a, 'ctx, 'env>( None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope), Some((layout, ptr)) => { match layout { - Layout::Builtin(Builtin::List(_, elem_layout)) if false => { + Layout::Builtin(Builtin::List(Ownership::Owned, _elem_layout)) => { // first run the body let body = build_expr(env, layout_ids, scope, parent, expr); @@ -774,19 +711,28 @@ fn list_get_refcount_ptr<'a, 'ctx, 'env>( let builder = env.builder; let ctx = env.context; - // basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - let elem_type = ctx.i64_type().into(); - let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - // Load the pointer to the array data - let array_data_ptr = load_list_ptr(builder, list_wrapper, ptr_type); + // pointer to usize + let ptr_bytes = env.ptr_bytes; + let int_type = ptr_int(ctx, ptr_bytes); - // get the refcount - let zero_index = ctx.i64_type().const_zero(); - unsafe { - // SAFETY - // index 0 is always defined for lists. - builder.build_in_bounds_gep(array_data_ptr, &[zero_index], "refcount_index") - } + // fetch the pointer to the array data, as an integer + let ptr_as_int = builder + .build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr") + .unwrap() + .into_int_value(); + + // subtract ptr_size, to access the refcount + let refcount_ptr = builder.build_int_sub( + ptr_as_int, + ctx.i64_type().const_int(env.ptr_bytes as u64, false), + "refcount_ptr", + ); + + builder.build_int_to_ptr( + refcount_ptr, + int_type.ptr_type(AddressSpace::Generic), + "make ptr", + ) } fn increment_refcount_list<'a, 'ctx, 'env>( @@ -808,7 +754,7 @@ fn increment_refcount_list<'a, 'ctx, 'env>( let decremented = env.builder.build_int_sub( refcount, ctx.i64_type().const_int(1 as u64, false), - "new_refcount", + "incremented_refcount", ); // Mutate the new array in-place to change the element. @@ -841,7 +787,7 @@ fn decrement_refcount_list<'a, 'ctx, 'env>( let decremented = env.builder.build_int_add( ctx.i64_type().const_int(1 as u64, false), refcount, - "new_refcount", + "decremented_refcount", ); // Mutate the new array in-place to change the element. @@ -852,12 +798,7 @@ fn decrement_refcount_list<'a, 'ctx, 'env>( // refcount is one, and will be decremented. This list can be freed let build_else = || { - let array_data_ptr = load_list_ptr( - builder, - original_wrapper, - ctx.i64_type().ptr_type(AddressSpace::Generic), - ); - let free = builder.build_free(array_data_ptr); + let free = builder.build_free(refcount_ptr); builder.insert_instruction(&free, None); @@ -875,7 +816,7 @@ fn load_symbol<'a, 'ctx, 'env>( ) -> BasicValueEnum<'ctx> { match scope.get(symbol) { Some((layout, ptr)) => match layout { - Layout::Builtin(Builtin::List(_, _)) if false => { + Layout::Builtin(Builtin::List(Ownership::Owned, _)) => { let load = env .builder .build_load(*ptr, symbol.ident_string(&env.interns)); @@ -1237,11 +1178,14 @@ pub fn allocate_list<'a, 'ctx, 'env>( let bytes_len = len_type.const_int(elem_bytes, false); let len = builder.build_int_mul(bytes_len, length, "data_length"); - // TODO 8 assume 64-bit pointer/refcount - let len = builder.build_int_add(len, len_type.const_int(8, false), "add_refcount_space"); + let len = builder.build_int_add( + len, + len_type.const_int(env.ptr_bytes as u64, false), + "add_refcount_space", + ); env.builder - .build_array_malloc(elem_type, len, "create_list_ptr") + .build_array_malloc(ctx.i8_type(), len, "create_list_ptr") .unwrap() // TODO check if malloc returned null; if so, runtime error for OOM! @@ -1448,7 +1392,7 @@ fn list_repeat<'a, 'ctx, 'env>( ) }; - let build_else = || empty_list(env); + let build_else = || empty_polymorphic_list(env); let struct_type = collection(ctx, env.ptr_bytes); @@ -1640,7 +1584,7 @@ enum InPlace { Clone, } -fn empty_list<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> { +fn empty_polymorphic_list<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> { let ctx = env.context; let struct_type = collection(ctx, env.ptr_bytes); @@ -1650,6 +1594,69 @@ fn empty_list<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> BasicValueEnum::StructValue(struct_type.const_zero()) } +fn list_literal<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + scope: &Scope<'a, 'ctx>, + parent: FunctionValue<'ctx>, + elem_layout: &Layout<'a>, + elems: &&[roc_mono::expr::Expr<'a>], +) -> BasicValueEnum<'ctx> { + let ctx = env.context; + let builder = env.builder; + + let len_u64 = elems.len() as u64; + let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; + + let ptr = { + let bytes_len = elem_bytes * len_u64; + let len_type = env.ptr_int(); + let len = len_type.const_int(bytes_len, false); + + allocate_list(env, elem_layout, len) + + // TODO check if malloc returned null; if so, runtime error for OOM! + }; + + // Copy the elements from the list literal into the array + for (index, elem) in elems.iter().enumerate() { + let index_val = ctx.i64_type().const_int(index as u64, false); + let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; + let val = build_expr(env, layout_ids, &scope, parent, &elem); + + builder.build_store(elem_ptr, val); + } + + let ptr_bytes = env.ptr_bytes; + let int_type = ptr_int(ctx, ptr_bytes); + let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr"); + let struct_type = collection(ctx, ptr_bytes); + let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false)); + let mut struct_val; + + // Store the pointer + struct_val = builder + .build_insert_value( + struct_type.get_undef(), + ptr_as_int, + Builtin::WRAPPER_PTR, + "insert_ptr", + ) + .unwrap(); + + // Store the length + struct_val = builder + .build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len") + .unwrap(); + + // Bitcast to an array of raw bytes + builder.build_bitcast( + struct_val.into_struct_value(), + collection(ctx, ptr_bytes), + "cast_collection", + ) +} + fn bounds_check_comparison<'ctx>( builder: &Builder<'ctx>, elem_index: IntValue<'ctx>, @@ -2009,7 +2016,7 @@ fn run_low_level<'a, 'ctx, 'env>( ) }; - let build_else = || empty_list(env); + let build_else = || empty_polymorphic_list(env); let struct_type = collection(ctx, env.ptr_bytes); @@ -2022,7 +2029,7 @@ fn run_low_level<'a, 'ctx, 'env>( BasicTypeEnum::StructType(struct_type), ) } - Layout::Builtin(Builtin::EmptyList) => empty_list(env), + Layout::Builtin(Builtin::EmptyList) => empty_polymorphic_list(env), _ => { unreachable!("Invalid List layout for List.reverse {:?}", list_layout); } @@ -2330,7 +2337,7 @@ fn list_append<'a, 'ctx, 'env>( match first_list_layout { Layout::Builtin(Builtin::EmptyList) => { match second_list_layout { - Layout::Builtin(Builtin::EmptyList) => empty_list(env), + Layout::Builtin(Builtin::EmptyList) => empty_polymorphic_list(env), Layout::Builtin(Builtin::List(_, elem_layout)) => { // THIS IS A COPY AND PASTE // All the code under the Layout::Builtin(Builtin::List()) match branch @@ -2358,7 +2365,7 @@ fn list_append<'a, 'ctx, 'env>( BasicValueEnum::StructValue(new_wrapper) }; - let build_second_list_else = || empty_list(env); + let build_second_list_else = || empty_polymorphic_list(env); build_basic_phi2( env, @@ -2646,7 +2653,7 @@ fn list_append<'a, 'ctx, 'env>( let if_first_list_is_empty = || { match second_list_layout { - Layout::Builtin(Builtin::EmptyList) => empty_list(env), + Layout::Builtin(Builtin::EmptyList) => empty_polymorphic_list(env), Layout::Builtin(Builtin::List(_, elem_layout)) => { // second_list_len > 0 // We do this check to avoid allocating memory. If the second input @@ -2669,7 +2676,7 @@ fn list_append<'a, 'ctx, 'env>( BasicValueEnum::StructValue(new_wrapper) }; - let build_second_list_else = || empty_list(env); + let build_second_list_else = || empty_polymorphic_list(env); build_basic_phi2( env, diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index 407dcf7091..04895ef2eb 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -616,17 +616,54 @@ mod gen_list { } #[test] - fn gen_list_increment_decrement() { + fn empty_list_increment_decrement() { assert_evals_to!( indoc!( r#" - x = [ 1,2,3 ] + x : List Int + x = [] - List.len x + List.len x + List.len x "# ), - 3, + 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() { + // yes + 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] + ); + } } diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 515bfc2ea6..2fb5e9eaf1 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -563,7 +563,21 @@ impl<'a> Expr<'a> { alloc.intersperse(it, alloc.space()) } } - CallByName { name, .. } => alloc.text("*magic*"), + CallByName { name, args, .. } => { + let doc_name = alloc.text(format!("Call {}", name)); + let doc_args = args.iter().map(|(expr, _)| expr.to_doc(alloc, true)); + + let it = std::iter::once(doc_name).chain(doc_args); + + if parens { + alloc + .text("(") + .append(alloc.intersperse(it, alloc.space())) + .append(alloc.text(")")) + } else { + alloc.intersperse(it, alloc.space()) + } + } _ => todo!("not yet implemented: {:?}", self), } } diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 0767730463..298631fc6e 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -19,6 +19,7 @@ mod test_mono { use roc_mono::expr::Expr::{self, *}; use roc_mono::expr::{InProgressProc, Procs}; use roc_mono::layout; + use roc_mono::layout::Ownership::Owned; use roc_mono::layout::{Builtin, Layout, LayoutCache}; use roc_types::subs::Subs; @@ -632,7 +633,10 @@ mod test_mono { CallByName { name: Symbol::LIST_GET, layout: Layout::FunctionPointer( - &[Layout::Builtin(Builtin::List(&I64_LAYOUT)), I64_LAYOUT], + &[ + Layout::Builtin(Builtin::List(Owned, &I64_LAYOUT)), + I64_LAYOUT, + ], &Layout::Union(&[&[I64_LAYOUT], &[I64_LAYOUT, I64_LAYOUT]]), ), args: &vec![ @@ -641,11 +645,11 @@ mod test_mono { name: Symbol::LIST_SET, layout: Layout::FunctionPointer( &[ - Layout::Builtin(Builtin::List(&I64_LAYOUT)), + Layout::Builtin(Builtin::List(Owned, &I64_LAYOUT)), I64_LAYOUT, I64_LAYOUT, ], - &Layout::Builtin(Builtin::List(&I64_LAYOUT)), + &Layout::Builtin(Builtin::List(Owned, &I64_LAYOUT)), ), args: &vec![ ( @@ -653,13 +657,13 @@ mod test_mono { elem_layout: I64_LAYOUT, elems: &vec![Int(12), Int(9), Int(7), Int(3)], }, - Layout::Builtin(Builtin::List(&I64_LAYOUT)), + Layout::Builtin(Builtin::List(Owned, &I64_LAYOUT)), ), (Int(1), I64_LAYOUT), (Int(42), I64_LAYOUT), ], }, - Layout::Builtin(Builtin::List(&I64_LAYOUT)), + Layout::Builtin(Builtin::List(Owned, &I64_LAYOUT)), ), (Int(1), I64_LAYOUT), ], @@ -864,7 +868,7 @@ mod test_mono { Store Test.1: Just 0i64 3i64 Store Test.1: Load Test.1 Store Test.3: Lowlevel.And (Lowlevel.Eq 0i64 (Access @0 Load Test.1)) true - + if Test.3 then Reset Test.1 Reuse Test.1 @@ -900,7 +904,7 @@ mod test_mono { Store Test.5: Lowlevel.And (Lowlevel.Eq 0i64 (Access @0 Load Test.1)) true if Test.5 then - + if Test.4 then Just 0i64 Just 0i64 1i64 else @@ -976,4 +980,30 @@ mod test_mono { ), ) } + + #[test] + fn pass_list_to_function() { + compiles_to_string( + r#" + x : List Int + x = [1,2,3] + + id : a -> a + id = \y -> y + + id x + "#, + indoc!( + r#" + procedure Test.1 (Test.3): + Load Test.3 + Dec Test.3 + + Store Test.0: [ 1i64, 2i64, 3i64 ] + Call Test.1 (Load Test.0) + Dec Test.0 + "# + ), + ) + } } From 01f9539d8ea43edbcbce57bc2c193d96f6a6119d Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 31 Jul 2020 00:05:47 +0200 Subject: [PATCH 10/60] clipping clippings --- compiler/mono/src/expr.rs | 7 ++----- compiler/mono/src/reset_reuse.rs | 9 ++------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 2fb5e9eaf1..0e937046d7 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -37,7 +37,7 @@ pub struct Proc<'a> { } impl<'a> Proc<'a> { - pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D, parens: bool) -> DocBuilder<'b, D, A> + pub fn to_doc<'b, D, A>(&'b self, alloc: &'b D, _parens: bool) -> DocBuilder<'b, D, A> where D: DocAllocator<'b, A>, D::Doc: Clone, @@ -835,10 +835,7 @@ fn from_can<'a>( } LetRec(defs, ret_expr, _, _) => from_can_defs(env, defs, *ret_expr, layout_cache, procs), LetNonRec(def, ret_expr, _, _) => { - let symbols = roc_can::pattern::symbols_from_pattern(&def.loc_pattern.value); - let mut result = from_can_defs(env, vec![*def], *ret_expr, layout_cache, procs); - - result + from_can_defs(env, vec![*def], *ret_expr, layout_cache, procs) } Closure(ann, name, _, loc_args, boxed_body) => { diff --git a/compiler/mono/src/reset_reuse.rs b/compiler/mono/src/reset_reuse.rs index 54a2c11fec..c5eb356916 100644 --- a/compiler/mono/src/reset_reuse.rs +++ b/compiler/mono/src/reset_reuse.rs @@ -1,15 +1,10 @@ use crate::expr::Env; use crate::expr::Expr; -use crate::layout::{Builtin, Layout}; use bumpalo::collections::Vec; -use bumpalo::Bump; -use roc_collections::all::{MutMap, MutSet}; -use roc_module::ident::{Ident, Lowercase, TagName}; -use roc_module::symbol::{IdentIds, ModuleId, Symbol}; -use roc_region::all::{Located, Region}; +use roc_collections::all::MutSet; +use roc_module::symbol::Symbol; -use Expr::*; /* R : FnBodypure → FnBodyRC R(let x = e; F ) = let x = e; R(F ) From f48a661b3d3931dbbf1cccc50b2a01d03a12a0cc Mon Sep 17 00:00:00 2001 From: Folkert Date: Fri, 31 Jul 2020 00:49:16 +0200 Subject: [PATCH 11/60] fixing tests --- compiler/can/src/pattern.rs | 7 ++++++- compiler/mono/src/expr.rs | 1 + compiler/mono/tests/test_mono.rs | 8 ++------ 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 70dbae9c67..eef2b0532e 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -77,7 +77,12 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { } RecordDestructure { destructs, .. } => { for destruct in destructs { - symbols.push(destruct.value.symbol); + // 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); + } } } diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 0e937046d7..68e1fe979a 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -451,6 +451,7 @@ impl<'a> Expr<'a> { .append(alloc.hardline()) .append(expr.to_doc(alloc, false)), + Store(stores, expr) if stores.is_empty() => expr.to_doc(alloc, false), Store(stores, expr) => { let doc_stores = stores .iter() diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index 298631fc6e..f13ccb80d6 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -868,11 +868,10 @@ mod test_mono { Store Test.1: Just 0i64 3i64 Store Test.1: Load Test.1 Store Test.3: Lowlevel.And (Lowlevel.Eq 0i64 (Access @0 Load Test.1)) true - if Test.3 then Reset Test.1 Reuse Test.1 - Just 0i64 *magic* + Just 0i64 (Call Num.14 (Load Test.2) 1i64) else Reset Test.1 Reuse Test.1 @@ -902,9 +901,7 @@ mod test_mono { Store Test.1: Just 0i64 Just 0i64 3i64 Store Test.1: Load Test.1 Store Test.5: Lowlevel.And (Lowlevel.Eq 0i64 (Access @0 Load Test.1)) true - if Test.5 then - if Test.4 then Just 0i64 Just 0i64 1i64 else @@ -937,7 +934,6 @@ mod test_mono { r#" Store Test.1: These 1i64 1i64 2i64 Store Test.1: Load Test.1 - switch Test.1: case 2: Reset Test.1 @@ -974,7 +970,7 @@ mod test_mono { Lowlevel.ListLen (Load #Attr.2) Store Test.0: [ 1i64, 2i64, 3i64 ] - *magic* + Call List.7 (Load Test.0) Dec Test.0 "# ), From 5d22b6a9cff18648acc90c7dcf429393b9a64cff Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 1 Aug 2020 17:38:59 +0200 Subject: [PATCH 12/60] ensure list elements are aligned --- compiler/gen/src/llvm/build.rs | 69 ++++++++++++++-------------------- compiler/gen/tests/gen_list.rs | 1 - 2 files changed, 28 insertions(+), 42 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 4961f20d18..9f89ead50e 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -799,7 +799,6 @@ fn decrement_refcount_list<'a, 'ctx, 'env>( // refcount is one, and will be decremented. This list can be freed let build_else = || { let free = builder.build_free(refcount_ptr); - builder.insert_instruction(&free, None); body @@ -1171,18 +1170,15 @@ pub fn allocate_list<'a, 'ctx, 'env>( let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; + let len_type = env.ptr_int(); + // bytes per element + let bytes_len = len_type.const_int(elem_bytes, false); + let offset = (env.ptr_bytes as u64).max(elem_bytes); + let ptr = { - let len_type = env.ptr_int(); - - // bytes per element - let bytes_len = len_type.const_int(elem_bytes, false); - let len = builder.build_int_mul(bytes_len, length, "data_length"); - let len = builder.build_int_add( - len, - len_type.const_int(env.ptr_bytes as u64, false), - "add_refcount_space", - ); + let len = + builder.build_int_add(len, len_type.const_int(offset, false), "add_refcount_space"); env.builder .build_array_malloc(ctx.i8_type(), len, "create_list_ptr") @@ -1191,46 +1187,37 @@ pub fn allocate_list<'a, 'ctx, 'env>( // TODO check if malloc returned null; if so, runtime error for OOM! }; - // Put the element into the list - let refcount_ptr = unsafe { - builder.build_in_bounds_gep( - ptr, - &[ctx.i64_type().const_int( - // 0 as in 0 index of our new list - 0 as u64, false, - )], - "index", - ) - }; - - // cast the refcount_ptr to be an u64_ptr - let refcount_ptr = builder - .build_bitcast( - refcount_ptr, - get_ptr_type( - &BasicTypeEnum::IntType(ctx.i64_type()), - AddressSpace::Generic, - ), - "refcount_ptr", - ) - .into_pointer_value(); - - // put our "refcount 0" in the first slot - let ref_count_zero = ctx.i64_type().const_int(std::usize::MAX as u64, false); - builder.build_store(refcount_ptr, ref_count_zero); - // We must return a pointer to the first element: let ptr_bytes = env.ptr_bytes; let int_type = ptr_int(ctx, ptr_bytes); let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr"); let incremented = builder.build_int_add( ptr_as_int, - ctx.i64_type().const_int(8, false), + ctx.i64_type().const_int(offset, false), "increment_list_ptr", ); let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - builder.build_int_to_ptr(incremented, ptr_type, "list_cast_ptr") + let list_element_ptr = builder.build_int_to_ptr(incremented, ptr_type, "list_cast_ptr"); + + // subtract ptr_size, to access the refcount + let refcount_ptr = builder.build_int_sub( + incremented, + ctx.i64_type().const_int(env.ptr_bytes as u64, false), + "refcount_ptr", + ); + + let refcount_ptr = builder.build_int_to_ptr( + refcount_ptr, + int_type.ptr_type(AddressSpace::Generic), + "make ptr", + ); + + // put our "refcount 0" in the first slot + let ref_count_zero = ctx.i64_type().const_int(std::usize::MAX as u64, false); + builder.build_store(refcount_ptr, ref_count_zero); + + list_element_ptr } /// List.single : a -> List a diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index 04895ef2eb..dfae35da21 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -649,7 +649,6 @@ mod gen_list { #[test] fn list_pass_to_function() { - // yes assert_evals_to!( indoc!( r#" From 5548bf136d9eede25ad241411f10e32d32c98f9e Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 1 Aug 2020 21:37:54 +0200 Subject: [PATCH 13/60] conditionally leak memory --- cli/src/repl.rs | 1 + compiler/build/src/program.rs | 1 + compiler/gen/src/llvm/build.rs | 7 +- compiler/gen/tests/gen_list.rs | 317 ++++++++++++++++++++++++++++- compiler/gen/tests/helpers/eval.rs | 28 ++- compiler/mono/tests/test_mono.rs | 25 +++ 6 files changed, 372 insertions(+), 7 deletions(-) diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 6ac71e2c3f..46e06d24be 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -241,6 +241,7 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St interns, module: arena.alloc(module), ptr_bytes, + leak: false, }; let mut procs = Procs::default(); let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 3c66e53113..cd0f56e697 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -157,6 +157,7 @@ pub fn gen( interns: loaded.interns, module: arena.alloc(module), ptr_bytes, + leak: false, }; let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); let mut layout_ids = LayoutIds::default(); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 9f89ead50e..229ac0ffc6 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -44,6 +44,7 @@ pub struct Env<'a, 'ctx, 'env> { pub module: &'ctx Module<'ctx>, pub interns: Interns, pub ptr_bytes: u32, + pub leak: bool, } impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { @@ -798,8 +799,10 @@ fn decrement_refcount_list<'a, 'ctx, 'env>( // refcount is one, and will be decremented. This list can be freed let build_else = || { - let free = builder.build_free(refcount_ptr); - builder.insert_instruction(&free, None); + if !env.leak { + let free = builder.build_free(refcount_ptr); + builder.insert_instruction(&free, None); + } body }; diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index dfae35da21..4becd16a46 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -543,6 +543,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(|| { @@ -610,7 +724,159 @@ mod gen_list { "# ), &[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 ); }) } @@ -665,4 +931,53 @@ mod gen_list { &'static [i64] ); } + + // fn bad() { + // assert_evals_to!( + // indoc!( + // r#" + // id : List Int -> [ Id (List Int) ] + // id = \y -> Pair y 4 + // + // when id [ 1,2,3 ] is + // Id v -> v + // "# + // ), + // &[1, 2, 3], + // &'static [i64] + // ); + // } + // + #[test] + fn list_wrap_in_tag() { + assert_evals_to!( + indoc!( + r#" + id : List Int -> [ Pair (List Int) Int, Nil ] + id = \y -> Pair y 4 + + when id [1,2,3] is + Pair _ _ -> v + "# + ), + &[1, 2, 3], + &'static [i64] + ); + } + + #[test] + fn list_wrap_in_tag() { + assert_evals_to!( + indoc!( + r#" + x = [1,2,3] + + when id [1,2,3] is + Pair _ _ -> v + "# + ), + &[1, 2, 3], + &'static [i64] + ); + } } diff --git a/compiler/gen/tests/helpers/eval.rs b/compiler/gen/tests/helpers/eval.rs index 2d7ac95bb3..5849f5ff0d 100644 --- a/compiler/gen/tests/helpers/eval.rs +++ b/compiler/gen/tests/helpers/eval.rs @@ -1,6 +1,6 @@ #[macro_export] macro_rules! assert_llvm_evals_to { - ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { + ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => { let target = target_lexicon::Triple::host(); let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let arena = Bump::new(); @@ -56,7 +56,9 @@ macro_rules! assert_llvm_evals_to { context: &context, interns, module: arena.alloc(module), - ptr_bytes + ptr_bytes, + leak: $leak + }; let mut procs = Procs::default(); let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); @@ -179,6 +181,10 @@ macro_rules! assert_llvm_evals_to { assert_eq!($transform(main.call()), $expected); } }; + + ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { + assert_llvm_evals_to!($src, $expected, $ty, $transform, false); + }; } // TODO this is almost all code duplication with assert_llvm_evals_to @@ -186,7 +192,7 @@ macro_rules! assert_llvm_evals_to { // Should extract the common logic into test helpers. #[macro_export] macro_rules! assert_opt_evals_to { - ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { + ($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => { let arena = Bump::new(); let target = target_lexicon::Triple::host(); let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; @@ -242,7 +248,8 @@ macro_rules! assert_opt_evals_to { context: &context, interns, module: arena.alloc(module), - ptr_bytes + ptr_bytes, + leak: $leak }; let mut procs = Procs::default(); let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); @@ -365,6 +372,10 @@ macro_rules! assert_opt_evals_to { assert_eq!($transform(main.call()), $expected); } }; + + ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { + assert_llvm_evals_to!($src, $expected, $ty, $transform, false) + }; } #[macro_export] @@ -390,4 +401,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); + } + }; } diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index f13ccb80d6..f40cc05a13 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -1002,4 +1002,29 @@ mod test_mono { ), ) } + + #[test] + fn double_list_len() { + compiles_to_string( + r#" + x : List Int + x = [1,2,3] + + List.len x + List.len x + "#, + indoc!( + r#" + procedure Num.14 (#Attr.2, #Attr.3): + Lowlevel.NumAdd (Load #Attr.2) (Load #Attr.3) + + procedure List.7 (#Attr.2): + Lowlevel.ListLen (Load #Attr.2) + + Store Test.0: [ 1i64, 2i64, 3i64 ] + Call Num.14 (Call List.7 (Load Test.0)) (Call List.7 (Load Test.0)) + Dec Test.0 + "# + ), + ) + } } From a8bfd90a500b4849de4eafd4bbfb05f37bcf7453 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 1 Aug 2020 22:58:29 +0200 Subject: [PATCH 14/60] introduce Inc --- compiler/gen/src/llvm/build.rs | 46 ++++++++++++++++++------------ compiler/mono/src/expr.rs | 2 +- compiler/mono/src/reset_reuse.rs | 8 +++--- compiler/mono/tests/test_mono.rs | 48 ++++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 229ac0ffc6..0f211333f8 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -348,7 +348,6 @@ pub fn build_expr<'a, 'ctx, 'env>( .left() .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")) } - LoadWithoutIncrement(_symbol) => todo!("implement load without increment"), Load(symbol) => load_symbol(env, scope, symbol), Str(str_literal) => { if str_literal.is_empty() { @@ -655,6 +654,31 @@ pub fn build_expr<'a, 'ctx, 'env>( } RunLowLevel(op, args) => run_low_level(env, layout_ids, scope, parent, *op, args), + Inc(symbol, expr) => { + match scope.get(symbol) { + None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope), + Some((layout, ptr)) => { + match layout { + Layout::Builtin(Builtin::List(Ownership::Owned, _elem_layout)) => { + let load = env + .builder + .build_load(*ptr, symbol.ident_string(&env.interns)); + + let wrapper_struct = env + .builder + .build_load(*ptr, symbol.ident_string(&env.interns)) + .into_struct_value(); + + increment_refcount_list(env, wrapper_struct, load) + } + _ => { + // not refcounted, do nothing special + build_expr(env, layout_ids, scope, parent, expr) + } + } + } + } + } DecAfter(symbol, expr) => { match scope.get(symbol) { None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope), @@ -817,23 +841,9 @@ fn load_symbol<'a, 'ctx, 'env>( symbol: &Symbol, ) -> BasicValueEnum<'ctx> { match scope.get(symbol) { - Some((layout, ptr)) => match layout { - Layout::Builtin(Builtin::List(Ownership::Owned, _)) => { - let load = env - .builder - .build_load(*ptr, symbol.ident_string(&env.interns)); - - let wrapper_struct = env - .builder - .build_load(*ptr, symbol.ident_string(&env.interns)) - .into_struct_value(); - - increment_refcount_list(env, wrapper_struct, load) - } - _ => env - .builder - .build_load(*ptr, symbol.ident_string(&env.interns)), - }, + Some((_, ptr)) => env + .builder + .build_load(*ptr, symbol.ident_string(&env.interns)), None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope), } } diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index 68e1fe979a..c3974c2b07 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -322,7 +322,7 @@ pub enum Expr<'a> { Store(&'a [(Symbol, Layout<'a>, Expr<'a>)], &'a Expr<'a>), /// RC instructions - LoadWithoutIncrement(Symbol), + Inc(Symbol, &'a Expr<'a>), DecAfter(Symbol, &'a Expr<'a>), // Functions diff --git a/compiler/mono/src/reset_reuse.rs b/compiler/mono/src/reset_reuse.rs index c5eb356916..8315e70755 100644 --- a/compiler/mono/src/reset_reuse.rs +++ b/compiler/mono/src/reset_reuse.rs @@ -156,7 +156,7 @@ pub fn function_r<'a>(env: &mut Env<'a, '_>, body: &'a Expr<'a>) -> Expr<'a> { | Byte(_) | Load(_) | EmptyArray - | LoadWithoutIncrement(_) + | Inc(_, _) | FunctionPointer(_, _) | RuntimeError(_) | RuntimeErrorFunction(_) => body.clone(), @@ -334,7 +334,7 @@ fn function_s<'a>( | Byte(_) | Load(_) | EmptyArray - | LoadWithoutIncrement(_) + | Inc(_, _) | FunctionPointer(_, _) | RuntimeError(_) | RuntimeErrorFunction(_) => Err(body), @@ -352,7 +352,7 @@ fn symbols_in_expr<'a>(initial: &Expr<'a>) -> MutSet { while let Some(expr) = stack.pop() { match expr { - FunctionPointer(symbol, _) | LoadWithoutIncrement(symbol) | Load(symbol) => { + FunctionPointer(symbol, _) | Load(symbol) => { result.insert(*symbol); } @@ -417,7 +417,7 @@ fn symbols_in_expr<'a>(initial: &Expr<'a>) -> MutSet { stack.push(body) } - DecAfter(symbol, body) => { + DecAfter(symbol, body) | Inc(symbol, body) => { result.insert(*symbol); stack.push(body); } diff --git a/compiler/mono/tests/test_mono.rs b/compiler/mono/tests/test_mono.rs index f40cc05a13..6c25255f67 100644 --- a/compiler/mono/tests/test_mono.rs +++ b/compiler/mono/tests/test_mono.rs @@ -1027,4 +1027,52 @@ mod test_mono { ), ) } + + #[test] + fn is_nil() { + compiles_to_string( + r#" + isNil = \xs -> + when xs is + Nil -> True + Cons _ _ -> False + + isNil Nil + "#, + indoc!( + r#" + procedure Test.0 (Test.2): + Store Test.2: Load Test.2 + Store Test.3: Lowlevel.And (Lowlevel.Eq true (Load Test.2)) true + if Test.3 then + true + else + false + Dec Test.2 + + Call Test.0 true + "# + ), + ) + } + + #[test] + fn y_is_dead() { + compiles_to_string( + r#" + f = \y -> Pair y y + + f [1] + "#, + indoc!( + r#" + procedure Test.0 (Test.2): + Struct [(Load(`Test.y`), Builtin(List(Owned, Builtin(Int64)))), (Load(`Test.y`), Builtin(List(Owned, Builtin(Int64))))] + Dec Test.2 + + Call Test.0 [ 1i64 ] + "# + ), + ) + } } From 2a0b010a7476c3d1dd0f15b75a58004636573f15 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 1 Aug 2020 23:39:11 +0200 Subject: [PATCH 15/60] make Tag arguments into symbols --- compiler/gen/src/llvm/build.rs | 11 +++++---- compiler/mono/src/expr.rs | 40 ++++++++++++++++++++++++-------- compiler/mono/src/reset_reuse.rs | 4 ++-- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 0f211333f8..cce6bf6f8e 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -453,11 +453,12 @@ pub fn build_expr<'a, 'ctx, 'env>( let mut field_types = Vec::with_capacity_in(num_fields, env.arena); let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); - for (field_expr, field_layout) in it { + for (field_symbol, field_layout) in it { // Zero-sized fields have no runtime representation. // The layout of the struct expects them to be dropped! if field_layout.stack_size(ptr_bytes) != 0 { - let val = build_expr(env, layout_ids, &scope, parent, field_expr); + let val = load_symbol(env, scope, field_symbol); + let field_type = basic_type_from_layout( env.arena, env.context, @@ -507,13 +508,13 @@ pub fn build_expr<'a, 'ctx, 'env>( let mut field_types = Vec::with_capacity_in(num_fields, env.arena); let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); - for (field_expr, field_layout) in arguments.iter() { + for (field_symbol, field_layout) in arguments.iter() { let field_size = field_layout.stack_size(ptr_size); // Zero-sized fields have no runtime representation. // The layout of the struct expects them to be dropped! if field_size != 0 { - let val = build_expr(env, layout_ids, &scope, parent, field_expr); + let val = load_symbol(env, scope, field_symbol); let field_type = basic_type_from_layout(env.arena, env.context, &field_layout, ptr_size); @@ -684,7 +685,7 @@ pub fn build_expr<'a, 'ctx, 'env>( None => panic!("There was no entry for {:?} in scope {:?}", symbol, scope), Some((layout, ptr)) => { match layout { - Layout::Builtin(Builtin::List(Ownership::Owned, _elem_layout)) => { + Layout::Builtin(Builtin::List(Ownership::Owned, _elem_layout)) if false => { // first run the body let body = build_expr(env, layout_ids, scope, parent, expr); diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index c3974c2b07..038a08b631 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -375,7 +375,7 @@ pub enum Expr<'a> { tag_name: TagName, tag_id: u8, union_size: u8, - arguments: &'a [(Expr<'a>, Layout<'a>)], + arguments: &'a [(Symbol, Layout<'a>)], }, Struct(&'a [(Expr<'a>, Layout<'a>)]), AccessAtIndex { @@ -525,7 +525,9 @@ impl<'a> Expr<'a> { TagName::Global(s) => alloc.text(s.as_str()), TagName::Private(s) => alloc.text(format!("{}", s)), }; - let doc_args = arguments.iter().map(|(expr, _)| expr.to_doc(alloc, true)); + let doc_args = arguments + .iter() + .map(|(symbol, _)| alloc.text(format!("{}", symbol))); let it = std::iter::once(doc_tag).chain(doc_args); @@ -1067,14 +1069,30 @@ fn from_can<'a>( .expect("tag must be in its own type"); let mut arguments = Vec::with_capacity_in(args.len(), arena); + let mut stores = Vec::with_capacity_in(args.len(), arena); - let it = std::iter::once(Expr::Int(tag_id as i64)).chain( - args.into_iter() - .map(|(_, arg)| from_can(env, arg.value, procs, layout_cache)), - ); + let expr_it = std::iter::once((Expr::Int(tag_id as i64), env.unique_symbol())) + .chain({ + let transform = |(_, arg): (_, Located)| { + let expr = from_can(env, arg.value, procs, layout_cache); + let symbol = env.unique_symbol(); - for (arg_layout, arg_expr) in argument_layouts.iter().zip(it) { - arguments.push((arg_expr, arg_layout.clone())); + (expr, symbol) + }; + + args.into_iter().map(transform) + }); + + let layout_it = argument_layouts.iter(); + + for (arg_layout, (arg_expr, symbol)) in layout_it.zip(expr_it) { + // arguments.push((arg_expr, arg_layout.clone())); + if let Expr::Load(existing) = arg_expr { + arguments.push((existing, arg_layout.clone())); + } else { + arguments.push((symbol, arg_layout.clone())); + stores.push((symbol, arg_layout.clone(), arg_expr)); + } } let mut layouts: Vec<&'a [Layout<'a>]> = @@ -1086,13 +1104,15 @@ fn from_can<'a>( let layout = Layout::Union(layouts.into_bump_slice()); - Expr::Tag { + let tag = Expr::Tag { tag_layout: layout, tag_name, tag_id: tag_id as u8, union_size, arguments: arguments.into_bump_slice(), - } + }; + + Expr::Store(stores.into_bump_slice(), env.arena.alloc(tag)) } } } diff --git a/compiler/mono/src/reset_reuse.rs b/compiler/mono/src/reset_reuse.rs index 8315e70755..0df4c2f867 100644 --- a/compiler/mono/src/reset_reuse.rs +++ b/compiler/mono/src/reset_reuse.rs @@ -441,8 +441,8 @@ fn symbols_in_expr<'a>(initial: &Expr<'a>) -> MutSet { } Tag { arguments, .. } => { - for (expr, _) in arguments.iter() { - stack.push(expr); + for (symbol, _) in arguments.iter() { + result.insert(*symbol); } } From cccfeb51f24182cb1598036035d92520a25b7fd6 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 3 Aug 2020 20:10:41 +0200 Subject: [PATCH 16/60] simple when is working --- compiler/mono/src/decision_tree2.rs | 1575 +++++++++++++++++++++++++++ compiler/mono/src/experiment.rs | 1257 +++++++++++++++++++++ compiler/mono/src/expr.rs | 6 +- compiler/mono/src/lib.rs | 3 + compiler/mono/src/reset_reuse.rs | 259 ++++- compiler/mono/tests/test_mono.rs | 374 ++++--- 6 files changed, 3269 insertions(+), 205 deletions(-) create mode 100644 compiler/mono/src/decision_tree2.rs create mode 100644 compiler/mono/src/experiment.rs diff --git a/compiler/mono/src/decision_tree2.rs b/compiler/mono/src/decision_tree2.rs new file mode 100644 index 0000000000..8e9a14c993 --- /dev/null +++ b/compiler/mono/src/decision_tree2.rs @@ -0,0 +1,1575 @@ +use crate::experiment::{Expr, Literal, Stmt}; +use crate::expr::{DestructType, Env, Pattern}; +use crate::layout::{Builtin, Layout}; +use crate::pattern::{Ctor, RenderAs, TagId, Union}; +use bumpalo::Bump; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::ident::TagName; +use roc_module::low_level::LowLevel; +use roc_module::symbol::Symbol; + +/// COMPILE CASES + +type Label = u64; + +/// Users of this module will mainly interact with this function. It takes +/// some normal branches and gives out a decision tree that has "labels" at all +/// the leafs and a dictionary that maps these "labels" to the code that should +/// run. +pub fn compile<'a>(raw_branches: Vec<(Guard<'a>, Pattern<'a>, u64)>) -> DecisionTree<'a> { + let formatted = raw_branches + .into_iter() + .map(|(guard, pattern, index)| Branch { + goal: index, + patterns: vec![(Path::Empty, guard, pattern)], + }) + .collect(); + + to_decision_tree(formatted) +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Guard<'a> { + NoGuard, + Guard { + stores: &'a [(Symbol, Layout<'a>, Expr<'a>)], + expr: Stmt<'a>, + }, +} + +impl<'a> Guard<'a> { + fn is_none(&self) -> bool { + self == &Guard::NoGuard + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum DecisionTree<'a> { + Match(Label), + Decision { + path: Path, + edges: Vec<(Test<'a>, DecisionTree<'a>)>, + default: Option>>, + }, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Test<'a> { + IsCtor { + tag_id: u8, + tag_name: TagName, + union: crate::pattern::Union, + arguments: Vec<(Pattern<'a>, Layout<'a>)>, + }, + IsInt(i64), + // float patterns are stored as u64 so they are comparable/hashable + IsFloat(u64), + IsStr(Box), + IsBit(bool), + IsByte { + tag_id: u8, + num_alts: usize, + }, + // A pattern that always succeeds (like `_`) can still have a guard + Guarded { + opt_test: Option>>, + stores: &'a [(Symbol, Layout<'a>, Expr<'a>)], + expr: Stmt<'a>, + }, +} +use std::hash::{Hash, Hasher}; +impl<'a> Hash for Test<'a> { + fn hash(&self, state: &mut H) { + use Test::*; + + match self { + IsCtor { tag_id, .. } => { + state.write_u8(0); + tag_id.hash(state); + // The point of this custom implementation is to not hash the tag arguments + } + IsInt(v) => { + state.write_u8(1); + v.hash(state); + } + IsFloat(v) => { + state.write_u8(2); + v.hash(state); + } + IsStr(v) => { + state.write_u8(3); + v.hash(state); + } + IsBit(v) => { + state.write_u8(4); + v.hash(state); + } + IsByte { tag_id, num_alts } => { + state.write_u8(5); + tag_id.hash(state); + num_alts.hash(state); + } + Guarded { opt_test: None, .. } => { + state.write_u8(6); + } + Guarded { + opt_test: Some(nested), + .. + } => { + state.write_u8(7); + nested.hash(state); + } + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Path { + Index { + index: u64, + tag_id: u8, + path: Box, + }, + Unbox(Box), + Empty, +} + +// ACTUALLY BUILD DECISION TREES + +#[derive(Clone, Debug, PartialEq)] +struct Branch<'a> { + goal: Label, + patterns: Vec<(Path, Guard<'a>, Pattern<'a>)>, +} + +fn to_decision_tree(raw_branches: Vec) -> DecisionTree { + let branches: Vec<_> = raw_branches.into_iter().map(flatten_patterns).collect(); + + match check_for_match(&branches) { + Some(goal) => DecisionTree::Match(goal), + None => { + // TODO remove clone + let path = pick_path(branches.clone()); + + let (edges, fallback) = gather_edges(branches, &path); + + let mut decision_edges: Vec<_> = edges + .into_iter() + .map(|(a, b)| (a, to_decision_tree(b))) + .collect(); + + match (decision_edges.split_last_mut(), fallback.split_last()) { + (Some(((_tag, decision_tree), rest)), None) if rest.is_empty() => { + // TODO remove clone + decision_tree.clone() + } + (_, None) => DecisionTree::Decision { + path, + edges: decision_edges, + default: None, + }, + (None, Some(_)) => to_decision_tree(fallback), + _ => DecisionTree::Decision { + path, + edges: decision_edges, + default: Some(Box::new(to_decision_tree(fallback))), + }, + } + } + } +} + +fn is_complete(tests: &[Test]) -> bool { + let length = tests.len(); + debug_assert!(length > 0); + match tests.get(length - 1) { + None => unreachable!("should never happen"), + Some(v) => match v { + Test::IsCtor { union, .. } => length == union.alternatives.len(), + Test::IsByte { num_alts, .. } => length == *num_alts, + Test::IsBit(_) => length == 2, + Test::IsInt(_) => false, + Test::IsFloat(_) => false, + Test::IsStr(_) => false, + Test::Guarded { .. } => false, + }, + } +} + +fn flatten_patterns(branch: Branch) -> Branch { + let mut result = Vec::with_capacity(branch.patterns.len()); + + for path_pattern in branch.patterns { + flatten(path_pattern, &mut result); + } + Branch { + goal: branch.goal, + patterns: result, + } +} + +fn flatten<'a>( + path_pattern: (Path, Guard<'a>, Pattern<'a>), + path_patterns: &mut Vec<(Path, Guard<'a>, Pattern<'a>)>, +) { + match &path_pattern.2 { + Pattern::AppliedTag { + union, + arguments, + tag_id, + .. + } => { + // TODO do we need to check that guard.is_none() here? + if union.alternatives.len() == 1 { + let path = path_pattern.0; + // Theory: unbox doesn't have any value for us, because one-element tag unions + // don't store the tag anyway. + if arguments.len() == 1 { + path_patterns.push(( + Path::Unbox(Box::new(path)), + path_pattern.1.clone(), + path_pattern.2.clone(), + )); + } else { + for (index, (arg_pattern, _)) in arguments.iter().enumerate() { + flatten( + ( + Path::Index { + index: index as u64, + tag_id: *tag_id, + path: Box::new(path.clone()), + }, + // same guard here? + path_pattern.1.clone(), + arg_pattern.clone(), + ), + path_patterns, + ); + } + } + } else { + path_patterns.push(path_pattern); + } + } + + _ => { + path_patterns.push(path_pattern); + } + } +} + +/// SUCCESSFULLY MATCH + +/// If the first branch has no more "decision points" we can finally take that +/// path. If that is the case we give the resulting label and a mapping from free +/// variables to "how to get their value". So a pattern like (Just (x,_)) will give +/// us something like ("x" => value.0.0) +fn check_for_match<'a>(branches: &Vec>) -> Option