Merge remote-tracking branch 'origin/specialize-owned' into dict-more

This commit is contained in:
Folkert 2021-02-18 22:42:17 +01:00
commit 32bba5206a
11 changed files with 478 additions and 282 deletions

View file

@ -199,7 +199,7 @@ mod cli_run {
"deriv",
&[],
"1 count: 6\n2 count: 22\n",
false,
true,
);
}
@ -241,7 +241,6 @@ mod cli_run {
);
}
#[ignore]
#[test]
#[serial(closure1)]
fn closure1() {
@ -254,7 +253,6 @@ mod cli_run {
);
}
#[ignore]
#[test]
#[serial(closure2)]
fn closure2() {
@ -267,7 +265,6 @@ mod cli_run {
);
}
#[ignore]
#[test]
#[serial(closure3)]
fn closure3() {
@ -280,7 +277,6 @@ mod cli_run {
);
}
#[ignore]
#[test]
#[serial(closure4)]
fn closure4() {

View file

@ -928,7 +928,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
tag_layout: Layout::Union(UnionLayout::NonRecursive(fields)),
union_size,
tag_id,
tag_name,
..
} => {
let tag_layout = Layout::Union(UnionLayout::NonRecursive(fields));
@ -944,15 +943,10 @@ pub fn build_exp_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);
let tag_field_layouts = if let TagName::Closure(_) = tag_name {
// closures ignore (and do not store) the discriminant
&fields[*tag_id as usize][1..]
} else {
&fields[*tag_id as usize]
};
let tag_field_layouts = &fields[*tag_id as usize];
for (field_symbol, tag_field_layout) in arguments.iter().zip(tag_field_layouts.iter()) {
let (val, val_layout) = load_symbol_and_layout(scope, field_symbol);
let (val, _val_layout) = load_symbol_and_layout(scope, field_symbol);
// Zero-sized fields have no runtime representation.
// The layout of the struct expects them to be dropped!
@ -968,7 +962,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
);
} else {
// this check fails for recursive tag unions, but can be helpful while debugging
debug_assert_eq!(tag_field_layout, val_layout);
// debug_assert_eq!(tag_field_layout, val_layout);
field_vals.push(val);
}
@ -1025,7 +1019,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
tag_layout: Layout::Union(UnionLayout::Recursive(fields)),
union_size,
tag_id,
tag_name,
..
} => {
let tag_layout = Layout::Union(UnionLayout::NonRecursive(fields));
@ -1041,12 +1034,7 @@ pub fn build_exp_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);
let tag_field_layouts = if let TagName::Closure(_) = tag_name {
// closures ignore (and do not store) the discriminant
&fields[*tag_id as usize][1..]
} else {
&fields[*tag_id as usize]
};
let tag_field_layouts = &fields[*tag_id as usize];
for (field_symbol, tag_field_layout) in arguments.iter().zip(tag_field_layouts.iter()) {
let (val, val_layout) = load_symbol_and_layout(scope, field_symbol);
@ -1189,7 +1177,6 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
}),
union_size,
tag_id,
tag_name,
..
} => {
let tag_layout = Layout::Union(UnionLayout::NonRecursive(fields));
@ -1212,10 +1199,7 @@ pub fn build_exp_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);
let tag_field_layouts = if let TagName::Closure(_) = tag_name {
// closures ignore (and do not store) the discriminant
&fields[*tag_id as usize][1..]
} else {
let tag_field_layouts = {
use std::cmp::Ordering::*;
match tag_id.cmp(&(*nullable_id as u8)) {
Equal => &[] as &[_],
@ -2225,14 +2209,6 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
match modify {
Inc(symbol, inc_amount) => {
match cont {
Refcounting(ModifyRc::Dec(symbol1), contcont)
if *inc_amount == 1 && symbol == symbol1 =>
{
// the inc and dec cancel out
build_exp_stmt(env, layout_ids, scope, parent, contcont)
}
_ => {
let (value, layout) = load_symbol_and_layout(scope, symbol);
let layout = layout.clone();
@ -2249,8 +2225,6 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
build_exp_stmt(env, layout_ids, scope, parent, cont)
}
}
}
Dec(symbol) => {
let (value, layout) = load_symbol_and_layout(scope, symbol);
@ -3455,9 +3429,16 @@ fn function_value_by_name<'a, 'ctx, 'env>(
if symbol.is_builtin() {
panic!("Unrecognized builtin function: {:?}", fn_name)
} else {
panic!(
"Unrecognized non-builtin function: {:?} (symbol: {:?}, layout: {:?})",
// Unrecognized non-builtin function:
eprintln!(
"Unrecognized non-builtin function: {:?}\n\nSymbol: {:?}\nLayout: {:?}\n",
fn_name, symbol, layout
);
eprintln!("Is the function defined? If so, maybe there is a problem with the layout");
panic!(
"Unrecognized non-builtin function: {:?} (symbol: {:?})",
fn_name, symbol,
)
}
})

View file

@ -13,6 +13,7 @@ use inkwell::module::Linkage;
use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate};
use roc_module::symbol::Interns;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, MemoryMode, UnionLayout};
@ -515,14 +516,14 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_list", Symbol::INC),
Mode::Dec => ("decrement_list", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_list",
"decrement_list",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -614,14 +615,14 @@ fn modify_refcount_str<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_str", Symbol::INC),
Mode::Dec => ("decrement_str", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_str",
"decrement_str",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -712,14 +713,14 @@ fn modify_refcount_dict<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_str", Symbol::INC),
Mode::Dec => ("decrement_str", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_dict",
"decrement_dict",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -899,14 +900,14 @@ fn build_rec_union<'a, 'ctx, 'env>(
) {
let layout = Layout::Union(UnionLayout::Recursive(fields));
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_rec_union", Symbol::INC),
Mode::Dec => ("decrement_rec_union", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_rec_union",
"decrement_rec_union",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@ -1183,6 +1184,26 @@ fn call_help<'a, 'ctx, 'env>(
call
}
fn function_name_from_mode<'a>(
layout_ids: &mut LayoutIds<'a>,
interns: &Interns,
if_inc: &'static str,
if_dec: &'static str,
layout: &Layout<'a>,
mode: Mode,
) -> (&'static str, String) {
// NOTE this is not a typo, we always determine the layout ID
// using the DEC symbol. Anything that is incrementing must also be
// decremented, so `dec` is used on more layouts. That can cause the
// layout ids of the inc and dec versions to be different, which is
// rather confusing, so now `inc_x` always corresponds to `dec_x`
let layout_id = layout_ids.get(Symbol::DEC, layout);
match mode {
Mode::Inc(_) => (if_inc, layout_id.to_symbol_string(Symbol::INC, interns)),
Mode::Dec => (if_dec, layout_id.to_symbol_string(Symbol::DEC, interns)),
}
}
fn modify_refcount_union<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
@ -1195,14 +1216,14 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let (call_name, symbol) = match mode {
Mode::Inc(_) => ("increment_union", Symbol::INC),
Mode::Dec => ("decrement_union", Symbol::DEC),
};
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let (call_name, fn_name) = function_name_from_mode(
layout_ids,
&env.interns,
"increment_union",
"decrement_union",
&layout,
mode,
);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,

View file

@ -751,7 +751,6 @@ enum Msg<'a> {
layout_cache: LayoutCache<'a>,
external_specializations_requested: MutMap<ModuleId, ExternalSpecializations>,
procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>,
passed_by_pointer: MutMap<(Symbol, Layout<'a>), Symbol>,
problems: Vec<roc_mono::ir::MonoProblem>,
module_timing: ModuleTiming,
subs: Subs,
@ -782,7 +781,6 @@ struct State<'a> {
pub module_cache: ModuleCache<'a>,
pub dependencies: Dependencies<'a>,
pub procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>,
pub passed_by_pointer: MutMap<(Symbol, Layout<'a>), Symbol>,
pub exposed_to_host: MutMap<Symbol, Variable>,
/// This is the "final" list of IdentIds, after canonicalization and constraint gen
@ -1405,7 +1403,6 @@ where
module_cache: ModuleCache::default(),
dependencies: Dependencies::default(),
procedures: MutMap::default(),
passed_by_pointer: MutMap::default(),
exposed_to_host: MutMap::default(),
exposed_types,
headers_parsed,
@ -1934,7 +1931,6 @@ fn update<'a>(
mut ident_ids,
subs,
procedures,
passed_by_pointer,
external_specializations_requested,
problems,
module_timing,
@ -1949,17 +1945,12 @@ fn update<'a>(
.notify(module_id, Phase::MakeSpecializations);
state.procedures.extend(procedures);
state.passed_by_pointer.extend(passed_by_pointer);
state.timings.insert(module_id, module_timing);
if state.dependencies.solved_all() && state.goal_phase == Phase::MakeSpecializations {
debug_assert!(work.is_empty(), "still work remaining {:?}", &work);
Proc::insert_refcount_operations(
arena,
&mut state.procedures,
&state.passed_by_pointer,
);
Proc::insert_refcount_operations(arena, &mut state.procedures);
Proc::optimize_refcount_operations(
arena,
@ -3630,7 +3621,7 @@ fn make_specializations<'a>(
);
let external_specializations_requested = procs.externals_we_need.clone();
let (procedures, passed_by_pointer) = procs.get_specialized_procs_without_rc(mono_env.arena);
let procedures = procs.get_specialized_procs_without_rc(mono_env.arena);
let make_specializations_end = SystemTime::now();
module_timing.make_specializations = make_specializations_end
@ -3642,7 +3633,6 @@ fn make_specializations<'a>(
ident_ids,
layout_cache,
procedures,
passed_by_pointer,
problems: mono_problems,
subs,
external_specializations_requested,

View file

@ -16,23 +16,13 @@ fn should_borrow_layout(layout: &Layout) -> bool {
pub fn infer_borrow<'a>(
arena: &'a Bump,
procs: &MutMap<(Symbol, Layout<'a>), Proc<'a>>,
passed_by_pointer: &MutMap<(Symbol, Layout<'a>), Symbol>,
) -> ParamMap<'a> {
let mut param_map = ParamMap {
items: MutMap::default(),
};
for (key, other) in passed_by_pointer {
if let Some(proc) = procs.get(key) {
let mut proc: Proc = proc.clone();
proc.name = *other;
param_map.visit_proc_always_owned(arena, &proc);
}
}
for proc in procs.values() {
param_map.visit_proc(arena, proc);
for (key, proc) in procs {
param_map.visit_proc(arena, proc, key.clone());
}
let mut env = BorrowInfState {
@ -56,8 +46,8 @@ pub fn infer_borrow<'a>(
// TODO in the future I think we need to do this properly, and group
// mutually recursive functions (or just make all their arguments owned)
for proc in procs.values() {
env.collect_proc(proc);
for (key, proc) in procs {
env.collect_proc(proc, key.1.clone());
}
if !env.modified {
@ -73,19 +63,19 @@ pub fn infer_borrow<'a>(
}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub enum Key {
Declaration(Symbol),
pub enum Key<'a> {
Declaration(Symbol, Layout<'a>),
JoinPoint(JoinPointId),
}
#[derive(Debug, Clone, Default)]
pub struct ParamMap<'a> {
items: MutMap<Key, &'a [Param<'a>]>,
items: MutMap<Key<'a>, &'a [Param<'a>]>,
}
impl<'a> IntoIterator for ParamMap<'a> {
type Item = (Key, &'a [Param<'a>]);
type IntoIter = <std::collections::HashMap<Key, &'a [Param<'a>]> as IntoIterator>::IntoIter;
type Item = (Key<'a>, &'a [Param<'a>]);
type IntoIter = <std::collections::HashMap<Key<'a>, &'a [Param<'a>]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.items.into_iter()
@ -93,8 +83,9 @@ impl<'a> IntoIterator for ParamMap<'a> {
}
impl<'a> IntoIterator for &'a ParamMap<'a> {
type Item = (&'a Key, &'a &'a [Param<'a>]);
type IntoIter = <&'a std::collections::HashMap<Key, &'a [Param<'a>]> as IntoIterator>::IntoIter;
type Item = (&'a Key<'a>, &'a &'a [Param<'a>]);
type IntoIter =
<&'a std::collections::HashMap<Key<'a>, &'a [Param<'a>]> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.items.iter()
@ -102,8 +93,8 @@ impl<'a> IntoIterator for &'a ParamMap<'a> {
}
impl<'a> ParamMap<'a> {
pub fn get_symbol(&self, symbol: Symbol) -> Option<&'a [Param<'a>]> {
let key = Key::Declaration(symbol);
pub fn get_symbol(&self, symbol: Symbol, layout: Layout<'a>) -> Option<&'a [Param<'a>]> {
let key = Key::Declaration(symbol, layout);
self.items.get(&key).copied()
}
@ -157,20 +148,31 @@ impl<'a> ParamMap<'a> {
.into_bump_slice()
}
fn visit_proc(&mut self, arena: &'a Bump, proc: &Proc<'a>) {
self.items.insert(
Key::Declaration(proc.name),
fn visit_proc(&mut self, arena: &'a Bump, proc: &Proc<'a>, key: (Symbol, Layout<'a>)) {
if proc.must_own_arguments {
self.visit_proc_always_owned(arena, proc, key);
return;
}
let already_in_there = self.items.insert(
Key::Declaration(proc.name, key.1),
Self::init_borrow_args(arena, proc.args),
);
debug_assert!(already_in_there.is_none());
self.visit_stmt(arena, proc.name, &proc.body);
}
fn visit_proc_always_owned(&mut self, arena: &'a Bump, proc: &Proc<'a>) {
self.items.insert(
Key::Declaration(proc.name),
fn visit_proc_always_owned(
&mut self,
arena: &'a Bump,
proc: &Proc<'a>,
key: (Symbol, Layout<'a>),
) {
let already_in_there = self.items.insert(
Key::Declaration(proc.name, key.1),
Self::init_borrow_args_always_owned(arena, proc.args),
);
debug_assert!(already_in_there.is_none());
self.visit_stmt(arena, proc.name, &proc.body);
}
@ -188,8 +190,10 @@ impl<'a> ParamMap<'a> {
remainder: v,
continuation: b,
} => {
self.items
let already_in_there = self
.items
.insert(Key::JoinPoint(*j), Self::init_borrow_params(arena, xs));
debug_assert!(already_in_there.is_none());
stack.push(v);
stack.push(b);
@ -252,7 +256,7 @@ impl<'a> BorrowInfState<'a> {
}
}
fn update_param_map(&mut self, k: Key) {
fn update_param_map(&mut self, k: Key<'a>) {
let arena = self.arena;
if let Some(ps) = self.param_map.items.get(&k) {
let ps = Vec::from_iter_in(
@ -359,9 +363,11 @@ impl<'a> BorrowInfState<'a> {
} = e;
match call_type {
ByName { name, .. } => {
ByName {
name, full_layout, ..
} => {
// get the borrow signature of the applied function
match self.param_map.get_symbol(*name) {
match self.param_map.get_symbol(*name, full_layout.clone()) {
Some(ps) => {
// the return value will be owned
self.own_var(z);
@ -458,7 +464,12 @@ impl<'a> BorrowInfState<'a> {
match (v, b) {
(
Expr::Call(crate::ir::Call {
call_type: crate::ir::CallType::ByName { name: g, .. },
call_type:
crate::ir::CallType::ByName {
name: g,
full_layout,
..
},
arguments: ys,
..
}),
@ -466,7 +477,12 @@ impl<'a> BorrowInfState<'a> {
)
| (
Expr::Call(crate::ir::Call {
call_type: crate::ir::CallType::ByPointer { name: g, .. },
call_type:
crate::ir::CallType::ByPointer {
name: g,
full_layout,
..
},
arguments: ys,
..
}),
@ -475,7 +491,7 @@ impl<'a> BorrowInfState<'a> {
if self.current_proc == *g && x == *z {
// anonymous functions (for which the ps may not be known)
// can never be tail-recursive, so this is fine
if let Some(ps) = self.param_map.get_symbol(*g) {
if let Some(ps) = self.param_map.get_symbol(*g, full_layout.clone()) {
self.own_params_using_args(ys, ps)
}
}
@ -517,8 +533,10 @@ impl<'a> BorrowInfState<'a> {
Let(x, Expr::FunctionPointer(fsymbol, layout), _, b) => {
// ensure that the function pointed to is in the param map
if let Some(params) = self.param_map.get_symbol(*fsymbol) {
self.param_map.items.insert(Key::Declaration(*x), params);
if let Some(params) = self.param_map.get_symbol(*fsymbol, layout.clone()) {
self.param_map
.items
.insert(Key::Declaration(*x, layout.clone()), params);
}
self.collect_stmt(b);
@ -574,7 +592,7 @@ impl<'a> BorrowInfState<'a> {
}
}
fn collect_proc(&mut self, proc: &Proc<'a>) {
fn collect_proc(&mut self, proc: &Proc<'a>, layout: Layout<'a>) {
let old = self.param_set.clone();
let ys = Vec::from_iter_in(proc.args.iter().map(|t| t.1), self.arena).into_bump_slice();
@ -585,7 +603,7 @@ impl<'a> BorrowInfState<'a> {
self.owned.entry(proc.name).or_default();
self.collect_stmt(&proc.body);
self.update_param_map(Key::Declaration(proc.name));
self.update_param_map(Key::Declaration(proc.name, layout));
self.param_set = old;
}

View file

@ -232,7 +232,7 @@ impl<'a> Context<'a> {
let mut vars = MutMap::default();
for (key, _) in param_map.into_iter() {
if let crate::borrow::Key::Declaration(symbol) = key {
if let crate::borrow::Key::Declaration(symbol, _) = key {
vars.insert(
*symbol,
VarInfo {
@ -466,9 +466,11 @@ impl<'a> Context<'a> {
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ByName { name, .. } => {
ByName {
name, full_layout, ..
} => {
// get the borrow signature
match self.param_map.get_symbol(*name) {
match self.param_map.get_symbol(*name, full_layout.clone()) {
Some(ps) => {
let v = Expr::Call(crate::ir::Call {
call_type,
@ -601,11 +603,8 @@ impl<'a> Context<'a> {
persistent: bool,
consume: bool,
) -> Self {
// can this type be reference-counted at runtime?
let reference = match layout {
Layout::Closure(_, closure, _) => closure.layout.contains_refcounted(),
_ => layout.contains_refcounted(),
};
// should we perform incs and decs on this value?
let reference = layout.contains_refcounted();
let info = VarInfo {
reference,
@ -1005,10 +1004,15 @@ pub fn visit_declaration<'a>(
ctx.add_dec_for_dead_params(params, b, &b_live_vars)
}
pub fn visit_proc<'a>(arena: &'a Bump, param_map: &'a ParamMap<'a>, proc: &mut Proc<'a>) {
pub fn visit_proc<'a>(
arena: &'a Bump,
param_map: &'a ParamMap<'a>,
proc: &mut Proc<'a>,
layout: Layout<'a>,
) {
let ctx = Context::new(arena, param_map);
let params = match param_map.get_symbol(proc.name) {
let params = match param_map.get_symbol(proc.name, layout) {
Some(slice) => slice,
None => Vec::from_iter_in(
proc.args.iter().cloned().map(|(layout, symbol)| Param {

View file

@ -1,8 +1,8 @@
use self::InProgressProc::*;
use crate::exhaustive::{Ctor, Guard, RenderAs, TagId};
use crate::layout::{
Builtin, ClosureLayout, Layout, LayoutCache, LayoutProblem, UnionLayout, WrappedVariant,
TAG_SIZE,
BuildClosureData, Builtin, ClosureLayout, Layout, LayoutCache, LayoutProblem, UnionLayout,
WrappedVariant, TAG_SIZE,
};
use bumpalo::collections::Vec;
use bumpalo::Bump;
@ -118,6 +118,7 @@ pub struct Proc<'a> {
pub closure_data_layout: Option<Layout<'a>>,
pub ret_layout: Layout<'a>,
pub is_self_recursive: SelfRecursive,
pub must_own_arguments: bool,
pub host_exposed_layouts: HostExposedLayouts<'a>,
}
@ -195,31 +196,11 @@ impl<'a> Proc<'a> {
pub fn insert_refcount_operations(
arena: &'a Bump,
procs: &mut MutMap<(Symbol, Layout<'a>), Proc<'a>>,
_passed_by_pointer: &MutMap<(Symbol, Layout<'a>), Symbol>,
) {
// currently we ignore the passed-by-pointerness
let passed_by_pointer = &Default::default();
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, procs));
let borrow_params =
arena.alloc(crate::borrow::infer_borrow(arena, procs, passed_by_pointer));
for (key, other) in passed_by_pointer {
if let Some(proc) = procs.get(key) {
let mut proc: Proc = proc.clone();
proc.name = *other;
let layout = key.1.clone();
procs.insert((*other, layout), proc);
} else {
unreachable!(
"we need a by-pointer version of {:?}, but its by-name version does not exist",
key.0
)
}
}
for (_, proc) in procs.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc);
for (key, proc) in procs.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1.clone());
}
}
@ -298,7 +279,6 @@ pub struct Procs<'a> {
pub runtime_errors: MutMap<Symbol, &'a str>,
pub externals_others_need: ExternalSpecializations,
pub externals_we_need: MutMap<ModuleId, ExternalSpecializations>,
pub passed_by_pointer: MutMap<(Symbol, Layout<'a>), Symbol>,
}
impl<'a> Default for Procs<'a> {
@ -311,7 +291,6 @@ impl<'a> Default for Procs<'a> {
runtime_errors: MutMap::default(),
externals_we_need: MutMap::default(),
externals_others_need: ExternalSpecializations::default(),
passed_by_pointer: MutMap::default(),
}
}
}
@ -356,14 +335,10 @@ impl<'a> Procs<'a> {
}
}
#[allow(clippy::type_complexity)]
pub fn get_specialized_procs_without_rc(
self,
arena: &'a Bump,
) -> (
MutMap<(Symbol, Layout<'a>), Proc<'a>>,
MutMap<(Symbol, Layout<'a>), Symbol>,
) {
) -> MutMap<(Symbol, Layout<'a>), Proc<'a>> {
let mut result = MutMap::with_capacity_and_hasher(self.specialized.len(), default_hasher());
for (key, in_prog_proc) in self.specialized.into_iter() {
@ -386,7 +361,7 @@ impl<'a> Procs<'a> {
}
}
(result, self.passed_by_pointer)
result
}
// TODO investigate make this an iterator?
@ -415,14 +390,10 @@ impl<'a> Procs<'a> {
}
}
let borrow_params = arena.alloc(crate::borrow::infer_borrow(
arena,
&result,
&self.passed_by_pointer,
));
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result));
for (_, proc) in result.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc);
for (key, proc) in result.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1.clone());
}
result
@ -459,14 +430,10 @@ impl<'a> Procs<'a> {
}
}
let borrow_params = arena.alloc(crate::borrow::infer_borrow(
arena,
&result,
&self.passed_by_pointer,
));
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, &result));
for (_, proc) in result.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc);
for (key, proc) in result.iter_mut() {
crate::inc_dec::visit_proc(arena, borrow_params, proc, key.1.clone());
}
(result, borrow_params)
@ -1935,6 +1902,7 @@ fn specialize_external<'a>(
closure_data_layout,
ret_layout: full_layout,
is_self_recursive: recursivity,
must_own_arguments: false,
host_exposed_layouts,
};
@ -1962,12 +1930,14 @@ fn specialize_external<'a>(
match tag_layout {
Layout::Struct(field_layouts) => {
// NOTE closure unions do not store the tag!
let field_layouts = &field_layouts[1..];
// TODO check for field_layouts.len() == 1 and do a rename in that case?
for (index, (symbol, _variable)) in captured.iter().enumerate()
for (mut index, (symbol, _variable)) in
captured.iter().enumerate()
{
// the field layouts do store the tag, but the tag value is
// not captured. So we drop the layout of the tag ID here
index += 1;
// TODO therefore should the wrapped here not be RecordOrSingleTagUnion?
let expr = Expr::AccessAtIndex {
index: index as _,
@ -2069,6 +2039,7 @@ fn specialize_external<'a>(
closure_data_layout,
ret_layout,
is_self_recursive: recursivity,
must_own_arguments: false,
host_exposed_layouts,
};
@ -2789,8 +2760,7 @@ pub fn with_hole<'a>(
);
let outer_symbol = env.unique_symbol();
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
.unwrap();
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
// convert the def body, store in outer_symbol
with_hole(
@ -3762,8 +3732,12 @@ pub fn with_hole<'a>(
.into_bump_slice();
// define the closure data, unless it's a basic unwrapped type already
match closure_layout.build_closure_data(name, symbols) {
Ok(expr) => {
match closure_layout.build_closure_data(name, &symbols) {
BuildClosureData::Alias(current) => {
// there is only one symbol captured, use that immediately
substitute_in_exprs(env.arena, &mut stmt, closure_data, current);
}
BuildClosureData::Struct(expr) => {
stmt = Stmt::Let(
closure_data,
expr,
@ -3771,9 +3745,40 @@ pub fn with_hole<'a>(
env.arena.alloc(stmt),
);
}
Err(current) => {
// there is only one symbol captured, use that immediately
substitute_in_exprs(env.arena, &mut stmt, closure_data, current);
BuildClosureData::Union {
tag_id,
tag_layout,
union_size,
tag_name,
} => {
let tag_id_symbol = env.unique_symbol();
let mut tag_symbols =
Vec::with_capacity_in(symbols.len() + 1, env.arena);
tag_symbols.push(tag_id_symbol);
tag_symbols.extend(symbols);
let expr1 = Expr::Literal(Literal::Int(tag_id as i64));
let expr2 = Expr::Tag {
tag_id,
tag_layout,
union_size,
tag_name,
arguments: tag_symbols.into_bump_slice(),
};
stmt = Stmt::Let(
closure_data,
expr2,
closure_data_layout.clone(),
env.arena.alloc(stmt),
);
stmt = Stmt::Let(
tag_id_symbol,
expr1,
Layout::Builtin(Builtin::Int64),
env.arena.alloc(stmt),
);
}
}
@ -4517,12 +4522,10 @@ pub fn from_can<'a>(
if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
.unwrap()
} else {
let outer_symbol = env.unique_symbol();
stmt =
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
.unwrap();
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
// convert the def body, store in outer_symbol
with_hole(
@ -4733,8 +4736,9 @@ fn from_can_when<'a>(
jump,
);
match store_pattern(env, procs, layout_cache, &pattern, cond_symbol, guard_stmt) {
Ok(new_guard_stmt) => (
let new_guard_stmt =
store_pattern(env, procs, layout_cache, &pattern, cond_symbol, guard_stmt);
(
pattern,
Guard::Guard {
id,
@ -4742,22 +4746,11 @@ fn from_can_when<'a>(
stmt: new_guard_stmt,
},
branch_stmt,
),
Err(msg) => (
Pattern::Underscore,
Guard::NoGuard,
Stmt::RuntimeError(env.arena.alloc(msg)),
),
}
)
} else {
match store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch_stmt) {
Ok(new_branch_stmt) => (pattern, Guard::NoGuard, new_branch_stmt),
Err(msg) => (
Pattern::Underscore,
Guard::NoGuard,
Stmt::RuntimeError(env.arena.alloc(msg)),
),
}
let new_branch_stmt =
store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch_stmt);
(pattern, Guard::NoGuard, new_branch_stmt)
}
});
let mono_branches = Vec::from_iter_in(it, arena);
@ -5142,13 +5135,36 @@ fn substitute_in_expr<'a>(
#[allow(clippy::too_many_arguments)]
fn store_pattern<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
can_pat: &Pattern<'a>,
outer_symbol: Symbol,
stmt: Stmt<'a>,
) -> Stmt<'a> {
match store_pattern_help(env, procs, layout_cache, can_pat, outer_symbol, stmt) {
StorePattern::Productive(new) => new,
StorePattern::NotProductive(new) => new,
}
}
enum StorePattern<'a> {
/// we bound new symbols
Productive(Stmt<'a>),
/// no new symbols were bound in this pattern
NotProductive(Stmt<'a>),
}
/// It is crucial for correct RC insertion that we don't create dead variables!
#[allow(clippy::too_many_arguments)]
fn store_pattern_help<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
can_pat: &Pattern<'a>,
outer_symbol: Symbol,
mut stmt: Stmt<'a>,
) -> Result<Stmt<'a>, &'a str> {
) -> StorePattern<'a> {
use Pattern::*;
match can_pat {
@ -5157,19 +5173,26 @@ fn store_pattern<'a>(
}
Underscore => {
// do nothing
return StorePattern::NotProductive(stmt);
}
IntLiteral(_)
| FloatLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {}
| StrLiteral(_) => {
return StorePattern::NotProductive(stmt);
}
AppliedTag {
arguments, layout, ..
arguments,
layout,
tag_name,
..
} => {
let wrapped = Wrapped::from_layout(layout);
let write_tag = wrapped == Wrapped::MultiTagUnion;
let mut arg_layouts = Vec::with_capacity_in(arguments.len(), env.arena);
let mut is_productive = false;
if write_tag {
// add an element for the tag discriminant
@ -5200,6 +5223,7 @@ fn store_pattern<'a>(
Identifier(symbol) => {
// store immediately in the given symbol
stmt = Stmt::Let(*symbol, load, arg_layout.clone(), env.arena.alloc(stmt));
is_productive = true;
}
Underscore => {
// ignore
@ -5214,17 +5238,42 @@ fn store_pattern<'a>(
let symbol = env.unique_symbol();
// first recurse, continuing to unpack symbol
stmt = store_pattern(env, procs, layout_cache, argument, symbol, stmt)?;
match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
StorePattern::Productive(new) => {
is_productive = true;
println!(
"Access {:?}.{:?} {:?}",
tag_name.clone(),
outer_symbol,
index
);
stmt = new;
// only if we bind one of its (sub)fields to a used name should we
// extract the field
stmt = Stmt::Let(
symbol,
load,
arg_layout.clone(),
env.arena.alloc(stmt),
);
}
StorePattern::NotProductive(new) => {
// do nothing
stmt = new;
}
}
}
}
}
// then store the symbol
stmt = Stmt::Let(symbol, load, arg_layout.clone(), env.arena.alloc(stmt));
}
}
if !is_productive {
return StorePattern::NotProductive(stmt);
}
}
RecordDestructure(destructs, Layout::Struct(sorted_fields)) => {
let mut is_productive = false;
for (index, destruct) in destructs.iter().enumerate().rev() {
stmt = store_record_destruct(
match store_record_destruct(
env,
procs,
layout_cache,
@ -5233,7 +5282,19 @@ fn store_pattern<'a>(
outer_symbol,
sorted_fields,
stmt,
)?;
) {
StorePattern::Productive(new) => {
is_productive = true;
stmt = new;
}
StorePattern::NotProductive(new) => {
stmt = new;
}
}
}
if !is_productive {
return StorePattern::NotProductive(stmt);
}
}
@ -5242,7 +5303,7 @@ fn store_pattern<'a>(
}
}
Ok(stmt)
StorePattern::Productive(stmt)
}
#[allow(clippy::too_many_arguments)]
@ -5255,7 +5316,7 @@ fn store_record_destruct<'a>(
outer_symbol: Symbol,
sorted_fields: &'a [Layout<'a>],
mut stmt: Stmt<'a>,
) -> Result<Stmt<'a>, &'a str> {
) -> StorePattern<'a> {
use Pattern::*;
let wrapped = Wrapped::from_layout(&Layout::Struct(sorted_fields));
@ -5298,24 +5359,32 @@ fn store_record_destruct<'a>(
// { x, y: _ } -> ...
//
// internally. But `y` is never used, so we must make sure it't not stored/loaded.
return StorePattern::NotProductive(stmt);
}
IntLiteral(_)
| FloatLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {}
| StrLiteral(_) => {
return StorePattern::NotProductive(stmt);
}
_ => {
let symbol = env.unique_symbol();
stmt = store_pattern(env, procs, layout_cache, guard_pattern, symbol, stmt)?;
stmt = Stmt::Let(symbol, load, destruct.layout.clone(), env.arena.alloc(stmt));
match store_pattern_help(env, procs, layout_cache, guard_pattern, symbol, stmt) {
StorePattern::Productive(new) => {
stmt = new;
stmt =
Stmt::Let(symbol, load, destruct.layout.clone(), env.arena.alloc(stmt));
}
StorePattern::NotProductive(stmt) => return StorePattern::NotProductive(stmt),
}
}
},
}
Ok(stmt)
StorePattern::Productive(stmt)
}
/// We want to re-use symbols that are not function symbols
@ -5515,8 +5584,12 @@ fn reuse_function_symbol<'a>(
};
// define the closure data, unless it's a basic unwrapped type already
match closure_layout.build_closure_data(original, symbols) {
Ok(expr) => {
match closure_layout.build_closure_data(original, &symbols) {
BuildClosureData::Alias(current) => {
// there is only one symbol captured, use that immediately
substitute_in_exprs(env.arena, &mut stmt, closure_data, current);
}
BuildClosureData::Struct(expr) => {
stmt = Stmt::Let(
closure_data,
expr,
@ -5524,9 +5597,40 @@ fn reuse_function_symbol<'a>(
env.arena.alloc(stmt),
);
}
Err(current) => {
// there is only one symbol captured, use that immediately
substitute_in_exprs(env.arena, &mut stmt, closure_data, current);
BuildClosureData::Union {
tag_id,
tag_layout,
union_size,
tag_name,
} => {
let tag_id_symbol = env.unique_symbol();
let mut tag_symbols =
Vec::with_capacity_in(symbols.len() + 1, env.arena);
tag_symbols.push(tag_id_symbol);
tag_symbols.extend(symbols);
let expr1 = Expr::Literal(Literal::Int(tag_id as i64));
let expr2 = Expr::Tag {
tag_id,
tag_layout,
union_size,
tag_name,
arguments: tag_symbols.into_bump_slice(),
};
stmt = Stmt::Let(
closure_data,
expr2,
closure_data_layout.clone(),
env.arena.alloc(stmt),
);
stmt = Stmt::Let(
tag_id_symbol,
expr1,
Layout::Builtin(Builtin::Int64),
env.arena.alloc(stmt),
);
}
}
@ -5630,19 +5734,93 @@ where
}
fn call_by_pointer<'a>(
_env: &mut Env<'a, '_>,
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
symbol: Symbol,
layout: Layout<'a>,
) -> Expr<'a> {
// let other = env.unique_symbol();
let other = symbol;
// when we call a known function by-pointer, we must make sure we call a function that owns all
// its arguments (in an RC sense). we can't know this at this point, so we wrap such calls in
// a proc that we guarantee owns all its arguments. E.g. we turn
//
// foo = \x -> ...
//
// x = List.map [ ... ] foo
//
// into
//
// foo = \x -> ...
//
// @owns_all_arguments
// foo1 = \x -> foo x
//
// x = List.map [ ... ] foo1
// TODO can we cache this `any`?
let is_specialized = procs.specialized.keys().any(|(s, _)| *s == symbol);
if env.is_imported_symbol(symbol) || procs.partial_procs.contains_key(&symbol) || is_specialized
{
match layout {
Layout::FunctionPointer(arg_layouts, ret_layout) => {
if arg_layouts.iter().any(|l| l.contains_refcounted()) {
let name = env.unique_symbol();
let mut args = Vec::with_capacity_in(arg_layouts.len(), env.arena);
let mut arg_symbols = Vec::with_capacity_in(arg_layouts.len(), env.arena);
for layout in arg_layouts {
let symbol = env.unique_symbol();
args.push((layout.clone(), symbol));
arg_symbols.push(symbol);
}
let args = args.into_bump_slice();
let call_symbol = env.unique_symbol();
let call_type = CallType::ByName {
name: symbol,
full_layout: layout.clone(),
ret_layout: ret_layout.clone(),
arg_layouts,
};
let call = Call {
call_type,
arguments: arg_symbols.into_bump_slice(),
};
let expr = Expr::Call(call);
let mut body = Stmt::Ret(call_symbol);
body = Stmt::Let(call_symbol, expr, ret_layout.clone(), env.arena.alloc(body));
let closure_data_layout = None;
let proc = Proc {
name,
args,
body,
closure_data_layout,
ret_layout: ret_layout.clone(),
is_self_recursive: SelfRecursive::NotSelfRecursive,
must_own_arguments: true,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
};
procs
.passed_by_pointer
.insert((symbol, layout.clone()), other);
Expr::FunctionPointer(other, layout)
.specialized
.insert((name, layout.clone()), InProgressProc::Done(proc));
Expr::FunctionPointer(name, layout)
} else {
// if none of the arguments is refcounted, then owning the arguments has no
// meaning
Expr::FunctionPointer(symbol, layout)
}
}
_ => {
// e.g. Num.maxInt or other constants
Expr::FunctionPointer(symbol, layout)
}
}
} else {
Expr::FunctionPointer(symbol, layout)
}
}
fn add_needed_external<'a>(

View file

@ -79,7 +79,7 @@ impl<'a> UnionLayout<'a> {
let tags_doc = tags.iter().map(|fields| {
alloc.text("C ").append(alloc.intersperse(
fields.iter().map(|x| x.to_doc(alloc, Parens::InTypeParam)),
", ",
" ",
))
});
@ -316,16 +316,16 @@ impl<'a> ClosureLayout<'a> {
&self,
original: Symbol,
symbols: &'a [Symbol],
) -> Result<crate::ir::Expr<'a>, Symbol> {
) -> BuildClosureData<'a> {
use crate::ir::Expr;
match self.layout {
Layout::Struct(fields) if fields.len() == 1 => Err(symbols[0]),
Layout::Struct(fields) if fields.len() == 1 => BuildClosureData::Alias(symbols[0]),
Layout::Struct(fields) => {
debug_assert!(fields.len() > 1);
debug_assert_eq!(fields.len(), symbols.len());
Ok(Expr::Struct(symbols))
BuildClosureData::Struct(Expr::Struct(symbols))
}
Layout::Union(UnionLayout::NonRecursive(tags)) => {
// NOTE it's very important that this Union consists of Closure tags
@ -337,20 +337,17 @@ impl<'a> ClosureLayout<'a> {
.position(|(tn, _)| *tn == TagName::Closure(original))
.unwrap() as _;
let expr = Expr::Tag {
BuildClosureData::Union {
tag_layout: Layout::Union(UnionLayout::NonRecursive(tags)),
tag_name: TagName::Closure(original),
tag_id,
union_size: tags.len() as u8,
arguments: symbols,
};
Ok(expr)
}
}
Layout::PhantomEmptyStruct => {
debug_assert_eq!(symbols.len(), 1);
Ok(Expr::Struct(&[]))
BuildClosureData::Struct(Expr::Struct(&[]))
}
_ => {
@ -362,12 +359,23 @@ impl<'a> ClosureLayout<'a> {
&self.layout
);
Err(symbols[0])
BuildClosureData::Alias(symbols[0])
}
}
}
}
pub enum BuildClosureData<'a> {
Alias(Symbol),
Struct(crate::ir::Expr<'a>),
Union {
tag_layout: Layout<'a>,
tag_name: TagName,
tag_id: u8,
union_size: u8,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub enum MemoryMode {
Unique,

View file

@ -1969,15 +1969,14 @@ mod test_mono {
let Test.7 = Index 1 Test.2;
let Test.8 = 0i64;
let Test.9 = Index 0 Test.7;
dec Test.7;
decref Test.2;
let Test.10 = lowlevel Eq Test.8 Test.9;
if Test.10 then
let Test.4 = Index 1 Test.2;
let Test.3 = 1i64;
decref Test.2;
ret Test.3;
else
let Test.5 = 0i64;
dec Test.2;
ret Test.5;
else
let Test.6 = 0i64;

View file

@ -12,4 +12,6 @@ main =
Task.succeed {}
|> Task.map (\_ -> x)
|> Task.map (\_ -> {})
|> Task.map toUnit
toUnit = (\_ -> {})

View file

@ -12,4 +12,3 @@ main =
Task.succeed {}
|> Task.after (\_ -> Task.succeed x |> Task.map (\_ -> {}))