Merge pull request #3009 from rtfeldman/list-map-ownership

List map ownership
This commit is contained in:
Ayaz 2022-05-07 19:59:15 -04:00 committed by GitHub
commit dd51479764
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 739 additions and 202 deletions

View file

@ -2239,7 +2239,13 @@ fn update<'a>(
debug_print_ir!(state, ROC_PRINT_IR_AFTER_RESET_REUSE); debug_print_ir!(state, ROC_PRINT_IR_AFTER_RESET_REUSE);
Proc::insert_refcount_operations(arena, &mut state.procedures); Proc::insert_refcount_operations(
arena,
module_id,
&mut ident_ids,
&mut update_mode_ids,
&mut state.procedures,
);
debug_print_ir!(state, ROC_PRINT_IR_AFTER_REFCOUNT); debug_print_ir!(state, ROC_PRINT_IR_AFTER_REFCOUNT);

View file

@ -6,9 +6,11 @@ use roc_collections::all::{default_hasher, MutMap, MutSet};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
pub const OWNED: bool = false; pub(crate) const OWNED: bool = false;
pub const BORROWED: bool = true; pub(crate) const BORROWED: bool = true;
/// For reference-counted types (lists, (big) strings, recursive tags), owning a value
/// means incrementing its reference count. Hence, we prefer borrowing for these types
fn should_borrow_layout(layout: &Layout) -> bool { fn should_borrow_layout(layout: &Layout) -> bool {
layout.is_refcounted() layout.is_refcounted()
} }

View file

@ -1,12 +1,76 @@
use crate::borrow::{ParamMap, BORROWED, OWNED}; use crate::borrow::{ParamMap, BORROWED, OWNED};
use crate::ir::{ use crate::ir::{
CallType, Expr, HigherOrderLowLevel, JoinPointId, ModifyRc, Param, Proc, ProcLayout, Stmt, CallType, Expr, HigherOrderLowLevel, JoinPointId, ModifyRc, Param, Proc, ProcLayout, Stmt,
UpdateModeIds,
}; };
use crate::layout::Layout; use crate::layout::Layout;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::Symbol; use roc_module::symbol::{IdentIds, ModuleId, Symbol};
/// Data and Function ownership relation for higher-order lowlevels
///
/// Normally, the borrowing algorithm figures out how to own/borrow data so that
/// the borrows match up. But that fails for our higher-order lowlevels, because
/// they are rigid (cannot add extra inc/dec in there dynamically) and opaque to
/// the borrow inference.
///
/// So, we must fix this up ourselves. This code is deliberately verbose to make
/// it easier to understand without full context.
///
/// If we take `List.map list f` as an example, then there are two orders of freedom:
///
/// - `list` can be either owned or borrowed by `List.map`
/// - `f` can require either owned or borrowed elements from `list`
///
/// Hence we get the four options below: the data (`list` in the example) is owned or borrowed by
/// the higher-order function, and the function argument (`f`) either owns or borrows the elements.
#[allow(clippy::enum_variant_names)]
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
enum DataFunction {
/// `list` is owned, and `f` expects owned values. That means that when we run the map, all
/// list elements will be consumed (because they are passed to `f`, which takes owned values)
/// Because we own the whole list, and must consume it, we need to `decref` the list.
/// `decref` just decrements the container, and will never recursively decrement elements
DataOwnedFunctionOwns,
/// `list` is owned, and `f` expects borrowed values. After running `f` for each element, the
/// elements are not consumed, and neither is the list. We must consume it though, because we
/// own the `list`. Therefore we need to perform a `dec`
DataOwnedFunctionBorrows,
/// `list` is borrowed, `f` borrows, so the trivial implementation is correct: just map `f`
/// over elements of `list`, and don't do anything else.
DataBorrowedFunctionBorrows,
/// The trickiest case: we only borrow the `list`, but the mapped function `f` needs owned
/// values. There are two options
///
/// - define some `fBorrow` that takes a borrowed value, `inc`'s it (similar to `.clone()` on
/// an `Rc<T>` in rust) and then passes the (now owned) value to `f`, then rewrite the call
/// to `List.map list fBorrow`
/// - `inc` the list (which recursively increments the elements), map `f` over the list,
/// consuming one RC token on the elements, finally `decref` the list.
///
/// For simplicity, we use the second option right now, but the first option is probably
/// preferable long-term.
DataBorrowedFunctionOwns,
}
impl DataFunction {
fn new(vars: &VarMap, lowlevel_argument: Symbol, passed_function_argument: Param) -> Self {
use DataFunction::*;
let data_borrowed = !vars[&lowlevel_argument].consume;
let function_borrows = passed_function_argument.borrow;
match (data_borrowed, function_borrows) {
(BORROWED, BORROWED) => DataBorrowedFunctionBorrows,
(BORROWED, OWNED) => DataBorrowedFunctionOwns,
(OWNED, BORROWED) => DataOwnedFunctionBorrows,
(OWNED, OWNED) => DataOwnedFunctionOwns,
}
}
}
pub fn free_variables(stmt: &Stmt<'_>) -> MutSet<Symbol> { pub fn free_variables(stmt: &Stmt<'_>) -> MutSet<Symbol> {
let (mut occurring, bound) = occurring_variables(stmt); let (mut occurring, bound) = occurring_variables(stmt);
@ -443,8 +507,10 @@ impl<'a> Context<'a> {
b b
} }
fn visit_call( #[allow(clippy::too_many_arguments)]
fn visit_call<'i>(
&self, &self,
codegen: &mut CodegenTools<'i>,
z: Symbol, z: Symbol,
call_type: crate::ir::CallType<'a>, call_type: crate::ir::CallType<'a>,
arguments: &'a [Symbol], arguments: &'a [Symbol],
@ -467,186 +533,8 @@ impl<'a> Context<'a> {
&*self.arena.alloc(Stmt::Let(z, v, l, b)) &*self.arena.alloc(Stmt::Let(z, v, l, b))
} }
HigherOrder(HigherOrderLowLevel { HigherOrder(lowlevel) => {
op, self.visit_higher_order_lowlevel(codegen, z, lowlevel, arguments, l, b, b_live_vars)
closure_env_layout,
update_mode,
passed_function,
..
}) => {
// setup
use crate::low_level::HigherOrder::*;
macro_rules! create_call {
($borrows:expr) => {
Expr::Call(crate::ir::Call {
call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) {
let mut passed_function = *passed_function;
passed_function.owns_captured_environment = true;
let higher_order = HigherOrderLowLevel {
op: *op,
closure_env_layout: *closure_env_layout,
update_mode: *update_mode,
passed_function,
};
CallType::HigherOrder(self.arena.alloc(higher_order))
} else {
call_type
},
arguments,
})
};
}
macro_rules! decref_if_owned {
($borrows:expr, $argument:expr, $stmt:expr) => {
if !$borrows {
self.arena.alloc(Stmt::Refcounting(
ModifyRc::DecRef($argument),
self.arena.alloc($stmt),
))
} else {
$stmt
}
};
}
const FUNCTION: bool = BORROWED;
const CLOSURE_DATA: bool = BORROWED;
let function_layout = ProcLayout {
arguments: passed_function.argument_layouts,
result: passed_function.return_layout,
};
let function_ps = match self
.param_map
.get_symbol(passed_function.name, function_layout)
{
Some(function_ps) => function_ps,
None => unreachable!(),
};
match op {
ListMap { xs }
| ListKeepIf { xs }
| ListKeepOks { xs }
| ListKeepErrs { xs }
| ListAny { xs }
| ListAll { xs }
| ListFindUnsafe { xs } => {
let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA];
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
// if the list is owned, then all elements have been consumed, but not the list itself
let b = decref_if_owned!(function_ps[0].borrow, *xs, b);
let v = create_call!(function_ps.get(1));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ListMap2 { xs, ys } => {
let borrows = [
function_ps[0].borrow,
function_ps[1].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
let b = decref_if_owned!(function_ps[0].borrow, *xs, b);
let b = decref_if_owned!(function_ps[1].borrow, *ys, b);
let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ListMap3 { xs, ys, zs } => {
let borrows = [
function_ps[0].borrow,
function_ps[1].borrow,
function_ps[2].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
let b = decref_if_owned!(function_ps[0].borrow, *xs, b);
let b = decref_if_owned!(function_ps[1].borrow, *ys, b);
let b = decref_if_owned!(function_ps[2].borrow, *zs, b);
let v = create_call!(function_ps.get(3));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ListMap4 { xs, ys, zs, ws } => {
let borrows = [
function_ps[0].borrow,
function_ps[1].borrow,
function_ps[2].borrow,
function_ps[3].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
let b = decref_if_owned!(function_ps[0].borrow, *xs, b);
let b = decref_if_owned!(function_ps[1].borrow, *ys, b);
let b = decref_if_owned!(function_ps[2].borrow, *zs, b);
let b = decref_if_owned!(function_ps[3].borrow, *ws, b);
let v = create_call!(function_ps.get(3));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ListMapWithIndex { xs } => {
let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA];
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
let b = decref_if_owned!(function_ps[1].borrow, *xs, b);
let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ListSortWith { xs: _ } => {
let borrows = [OWNED, FUNCTION, CLOSURE_DATA];
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ListWalk { xs, state: _ }
| ListWalkUntil { xs, state: _ }
| ListWalkBackwards { xs, state: _ }
| DictWalk { xs, state: _ } => {
// borrow data structure based on first argument of the folded function
// borrow the default based on second argument of the folded function
let borrows = [
function_ps[1].borrow,
function_ps[0].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
let b = decref_if_owned!(function_ps[1].borrow, *xs, b);
let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
}
} }
Foreign { .. } => { Foreign { .. } => {
@ -688,9 +576,273 @@ impl<'a> Context<'a> {
} }
} }
#[allow(clippy::many_single_char_names)] #[allow(clippy::too_many_arguments)]
fn visit_variable_declaration( fn visit_higher_order_lowlevel<'i>(
&self, &self,
codegen: &mut CodegenTools<'i>,
z: Symbol,
lowlevel: &'a crate::ir::HigherOrderLowLevel,
arguments: &'a [Symbol],
l: Layout<'a>,
b: &'a Stmt<'a>,
b_live_vars: &LiveVarSet,
) -> &'a Stmt<'a> {
use crate::low_level::HigherOrder::*;
use DataFunction::*;
let HigherOrderLowLevel {
op,
passed_function,
..
} = lowlevel;
macro_rules! create_call {
($borrows:expr) => {
create_holl_call(self.arena, lowlevel, $borrows, arguments)
};
}
let function_layout = ProcLayout {
arguments: passed_function.argument_layouts,
result: passed_function.return_layout,
};
let function_ps = match self
.param_map
.get_symbol(passed_function.name, function_layout)
{
Some(function_ps) => function_ps,
None => unreachable!(),
};
macro_rules! handle_ownerships_post {
($stmt:expr, $args:expr) => {{
let mut stmt = $stmt;
for (argument, function_ps) in $args.iter().copied() {
let ownership = DataFunction::new(&self.vars, argument, function_ps);
match ownership {
DataOwnedFunctionOwns | DataBorrowedFunctionOwns => {
// elements have been consumed, must still consume the list itself
let rest = self.arena.alloc($stmt);
let rc = Stmt::Refcounting(ModifyRc::DecRef(argument), rest);
stmt = self.arena.alloc(rc);
}
DataOwnedFunctionBorrows => {
// must consume list and elements
let rest = self.arena.alloc($stmt);
let rc = Stmt::Refcounting(ModifyRc::Dec(argument), rest);
stmt = self.arena.alloc(rc);
}
DataBorrowedFunctionBorrows => {
// list borrows, function borrows, so there is nothing to do
}
}
}
stmt
}};
}
macro_rules! handle_ownerships_pre {
($stmt:expr, $args:expr) => {{
let mut stmt = self.arena.alloc($stmt);
for (argument, function_ps) in $args.iter().copied() {
let ownership = DataFunction::new(&self.vars, argument, function_ps);
match ownership {
DataBorrowedFunctionOwns => {
// the data is borrowed;
// increment it to own the values so the function can use them
let rc = Stmt::Refcounting(ModifyRc::Inc(argument, 1), stmt);
stmt = self.arena.alloc(rc);
}
DataOwnedFunctionOwns | DataOwnedFunctionBorrows => {
// we actually own the data; nothing to do
}
DataBorrowedFunctionBorrows => {
// list borrows, function borrows, so there is nothing to do
}
}
}
stmt
}};
}
// incrementing/consuming the closure (if needed) is done by the zig implementation.
// We don't want to touch the RC on the roc side, so treat these as borrowed.
const FUNCTION: bool = BORROWED;
const CLOSURE_DATA: bool = BORROWED;
let borrows = [FUNCTION, CLOSURE_DATA];
let after_arguments = &arguments[op.function_index()..];
match *op {
ListMap { xs }
| ListKeepIf { xs }
| ListKeepOks { xs }
| ListKeepErrs { xs }
| ListAny { xs }
| ListAll { xs }
| ListFindUnsafe { xs } => {
let ownerships = [(xs, function_ps[0])];
let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars);
let b = handle_ownerships_post!(b, ownerships);
let v = create_call!(function_ps.get(1));
handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships)
}
ListMap2 { xs, ys } => {
let ownerships = [(xs, function_ps[0]), (ys, function_ps[1])];
let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars);
let b = handle_ownerships_post!(b, ownerships);
let v = create_call!(function_ps.get(2));
handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships)
}
ListMap3 { xs, ys, zs } => {
let ownerships = [
(xs, function_ps[0]),
(ys, function_ps[1]),
(zs, function_ps[2]),
];
let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars);
let b = handle_ownerships_post!(b, ownerships);
let v = create_call!(function_ps.get(3));
handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships)
}
ListMap4 { xs, ys, zs, ws } => {
let ownerships = [
(xs, function_ps[0]),
(ys, function_ps[1]),
(zs, function_ps[2]),
(ws, function_ps[3]),
];
let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars);
let b = handle_ownerships_post!(b, ownerships);
let v = create_call!(function_ps.get(3));
handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships)
}
ListMapWithIndex { xs } => {
let ownerships = [(xs, function_ps[0])];
let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars);
let b = handle_ownerships_post!(b, ownerships);
let v = create_call!(function_ps.get(2));
handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships)
}
ListSortWith { xs } => {
// NOTE: we may apply the function to the same argument multiple times.
// for that to be valid, the function must borrow its argument. This is not
// enforced at the moment
//
// we also don't check that both arguments have the same ownership characteristics
let ownerships = [(xs, function_ps[0])];
let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars);
// list-sort will sort in-place; that really changes how RC should work
let b = {
let ownership = DataFunction::new(&self.vars, xs, function_ps[0]);
match ownership {
DataOwnedFunctionOwns => {
// if non-unique, elements have been consumed, must still consume the list itself
let rc = Stmt::Refcounting(ModifyRc::DecRef(xs), b);
let condition_stmt = branch_on_list_uniqueness(
self.arena,
codegen,
xs,
l,
b.clone(),
self.arena.alloc(rc),
);
&*self.arena.alloc(condition_stmt)
}
DataOwnedFunctionBorrows => {
// must consume list and elements
let rc = Stmt::Refcounting(ModifyRc::Dec(xs), b);
let condition_stmt = branch_on_list_uniqueness(
self.arena,
codegen,
xs,
l,
b.clone(),
self.arena.alloc(rc),
);
&*self.arena.alloc(condition_stmt)
}
DataBorrowedFunctionOwns => {
// elements have been consumed, must still consume the list itself
let rest = self.arena.alloc(b);
let rc = Stmt::Refcounting(ModifyRc::DecRef(xs), rest);
&*self.arena.alloc(rc)
}
DataBorrowedFunctionBorrows => {
// list borrows, function borrows, so there is nothing to do
b
}
}
};
let v = create_call!(function_ps.get(2));
handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships)
}
ListWalk { xs, state: _ }
| ListWalkUntil { xs, state: _ }
| ListWalkBackwards { xs, state: _ }
| DictWalk { xs, state: _ } => {
let ownerships = [
// borrow data structure based on second argument of the folded function
(xs, function_ps[1]),
];
// borrow the default based on first argument of the folded function
// (state, function_ps[0])
let b = self.add_dec_after_lowlevel(after_arguments, &borrows, b, b_live_vars);
let b = handle_ownerships_post!(b, ownerships);
let v = create_call!(function_ps.get(2));
handle_ownerships_pre!(Stmt::Let(z, v, l, b), ownerships)
}
}
}
#[allow(clippy::many_single_char_names)]
fn visit_variable_declaration<'i>(
&self,
codegen: &mut CodegenTools<'i>,
z: Symbol, z: Symbol,
v: Expr<'a>, v: Expr<'a>,
l: Layout<'a>, l: Layout<'a>,
@ -722,7 +874,7 @@ impl<'a> Context<'a> {
Call(crate::ir::Call { Call(crate::ir::Call {
call_type, call_type,
arguments, arguments,
}) => self.visit_call(z, call_type, arguments, l, b, b_live_vars), }) => self.visit_call(codegen, z, call_type, arguments, l, b, b_live_vars),
StructAtIndex { structure: x, .. } => { StructAtIndex { structure: x, .. } => {
let b = self.add_dec_if_needed(x, b, b_live_vars); let b = self.add_dec_if_needed(x, b, b_live_vars);
@ -883,7 +1035,11 @@ impl<'a> Context<'a> {
b b
} }
fn visit_stmt(&self, stmt: &'a Stmt<'a>) -> (&'a Stmt<'a>, LiveVarSet) { fn visit_stmt<'i>(
&self,
codegen: &mut CodegenTools<'i>,
stmt: &'a Stmt<'a>,
) -> (&'a Stmt<'a>, LiveVarSet) {
use Stmt::*; use Stmt::*;
// let-chains can be very long, especially for large (list) literals // let-chains can be very long, especially for large (list) literals
@ -902,9 +1058,10 @@ impl<'a> Context<'a> {
for (symbol, expr, layout) in triples.iter() { for (symbol, expr, layout) in triples.iter() {
ctx = ctx.update_var_info(**symbol, layout, expr); ctx = ctx.update_var_info(**symbol, layout, expr);
} }
let (mut b, mut b_live_vars) = ctx.visit_stmt(cont); let (mut b, mut b_live_vars) = ctx.visit_stmt(codegen, cont);
for (symbol, expr, layout) in triples.into_iter().rev() { for (symbol, expr, layout) in triples.into_iter().rev() {
let pair = ctx.visit_variable_declaration( let pair = ctx.visit_variable_declaration(
codegen,
*symbol, *symbol,
(*expr).clone(), (*expr).clone(),
*layout, *layout,
@ -923,8 +1080,15 @@ impl<'a> Context<'a> {
match stmt { match stmt {
Let(symbol, expr, layout, cont) => { Let(symbol, expr, layout, cont) => {
let ctx = self.update_var_info(*symbol, layout, expr); let ctx = self.update_var_info(*symbol, layout, expr);
let (b, b_live_vars) = ctx.visit_stmt(cont); let (b, b_live_vars) = ctx.visit_stmt(codegen, cont);
ctx.visit_variable_declaration(*symbol, expr.clone(), *layout, b, &b_live_vars) ctx.visit_variable_declaration(
codegen,
*symbol,
expr.clone(),
*layout,
b,
&b_live_vars,
)
} }
Join { Join {
@ -938,7 +1102,7 @@ impl<'a> Context<'a> {
let (v, v_live_vars) = { let (v, v_live_vars) = {
let ctx = self.update_var_info_with_params(xs); let ctx = self.update_var_info_with_params(xs);
ctx.visit_stmt(v) ctx.visit_stmt(codegen, v)
}; };
let mut ctx = self.clone(); let mut ctx = self.clone();
@ -946,7 +1110,7 @@ impl<'a> Context<'a> {
update_jp_live_vars(*j, xs, v, &mut ctx.jp_live_vars); update_jp_live_vars(*j, xs, v, &mut ctx.jp_live_vars);
let (b, b_live_vars) = ctx.visit_stmt(b); let (b, b_live_vars) = ctx.visit_stmt(codegen, b);
( (
ctx.arena.alloc(Join { ctx.arena.alloc(Join {
@ -1001,7 +1165,7 @@ impl<'a> Context<'a> {
branches.iter().map(|(label, info, branch)| { branches.iter().map(|(label, info, branch)| {
// TODO should we use ctor info like Lean? // TODO should we use ctor info like Lean?
let ctx = self.clone(); let ctx = self.clone();
let (b, alt_live_vars) = ctx.visit_stmt(branch); let (b, alt_live_vars) = ctx.visit_stmt(codegen, branch);
let b = ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b); let b = ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b);
(*label, info.clone(), b.clone()) (*label, info.clone(), b.clone())
@ -1013,7 +1177,7 @@ impl<'a> Context<'a> {
let default_branch = { let default_branch = {
// TODO should we use ctor info like Lean? // TODO should we use ctor info like Lean?
let ctx = self.clone(); let ctx = self.clone();
let (b, alt_live_vars) = ctx.visit_stmt(default_branch.1); let (b, alt_live_vars) = ctx.visit_stmt(codegen, default_branch.1);
( (
default_branch.0.clone(), default_branch.0.clone(),
@ -1037,6 +1201,75 @@ impl<'a> Context<'a> {
} }
} }
fn branch_on_list_uniqueness<'a, 'i>(
arena: &'a Bump,
codegen: &mut CodegenTools<'i>,
list_symbol: Symbol,
return_layout: Layout<'a>,
then_branch_stmt: Stmt<'a>,
else_branch_stmt: &'a Stmt<'a>,
) -> Stmt<'a> {
let condition_symbol = Symbol::new(codegen.home, codegen.ident_ids.add_str("listIsUnique"));
let when_stmt = Stmt::if_then_else(
arena,
condition_symbol,
return_layout,
then_branch_stmt,
else_branch_stmt,
);
let stmt = arena.alloc(when_stmt);
// define the condition
let condition_call_type = CallType::LowLevel {
op: roc_module::low_level::LowLevel::ListIsUnique,
update_mode: codegen.update_mode_ids.next_id(),
};
let condition_call = crate::ir::Call {
call_type: condition_call_type,
arguments: arena.alloc([list_symbol]),
};
Stmt::Let(
condition_symbol,
Expr::Call(condition_call),
Layout::bool(),
stmt,
)
}
fn create_holl_call<'a>(
arena: &'a Bump,
holl: &'a crate::ir::HigherOrderLowLevel,
param: Option<&Param>,
arguments: &'a [Symbol],
) -> Expr<'a> {
let call = crate::ir::Call {
call_type: if let Some(OWNED) = param.map(|p| p.borrow) {
let mut passed_function = holl.passed_function;
passed_function.owns_captured_environment = true;
let higher_order = HigherOrderLowLevel {
op: holl.op,
closure_env_layout: holl.closure_env_layout,
update_mode: holl.update_mode,
passed_function,
};
CallType::HigherOrder(arena.alloc(higher_order))
} else {
debug_assert!(!holl.passed_function.owns_captured_environment);
CallType::HigherOrder(holl)
},
arguments,
};
Expr::Call(call)
}
pub fn collect_stmt( pub fn collect_stmt(
stmt: &Stmt<'_>, stmt: &Stmt<'_>,
jp_live_vars: &JPLiveVarMap, jp_live_vars: &JPLiveVarMap,
@ -1127,20 +1360,36 @@ fn update_jp_live_vars(j: JoinPointId, ys: &[Param], v: &Stmt<'_>, m: &mut JPLiv
m.insert(j, j_live_vars); m.insert(j, j_live_vars);
} }
pub fn visit_procs<'a>( struct CodegenTools<'i> {
home: ModuleId,
ident_ids: &'i mut IdentIds,
update_mode_ids: &'i mut UpdateModeIds,
}
pub fn visit_procs<'a, 'i>(
arena: &'a Bump, arena: &'a Bump,
home: ModuleId,
ident_ids: &'i mut IdentIds,
update_mode_ids: &'i mut UpdateModeIds,
param_map: &'a ParamMap<'a>, param_map: &'a ParamMap<'a>,
procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) { ) {
let ctx = Context::new(arena, param_map); let ctx = Context::new(arena, param_map);
let mut codegen = CodegenTools {
home,
ident_ids,
update_mode_ids,
};
for (key, proc) in procs.iter_mut() { for (key, proc) in procs.iter_mut() {
visit_proc(arena, param_map, &ctx, proc, key.1); visit_proc(arena, &mut codegen, param_map, &ctx, proc, key.1);
} }
} }
fn visit_proc<'a>( fn visit_proc<'a, 'i>(
arena: &'a Bump, arena: &'a Bump,
codegen: &mut CodegenTools<'i>,
param_map: &'a ParamMap<'a>, param_map: &'a ParamMap<'a>,
ctx: &Context<'a>, ctx: &Context<'a>,
proc: &mut Proc<'a>, proc: &mut Proc<'a>,
@ -1161,7 +1410,7 @@ fn visit_proc<'a>(
let stmt = arena.alloc(proc.body.clone()); let stmt = arena.alloc(proc.body.clone());
let ctx = ctx.update_var_info_with_params(params); let ctx = ctx.update_var_info_with_params(params);
let (b, b_live_vars) = ctx.visit_stmt(stmt); let (b, b_live_vars) = ctx.visit_stmt(codegen, stmt);
let b = ctx.add_dec_for_dead_params(params, b, &b_live_vars); let b = ctx.add_dec_for_dead_params(params, b, &b_live_vars);
proc.body = b.clone(); proc.body = b.clone();

View file

@ -391,13 +391,23 @@ impl<'a> Proc<'a> {
String::from_utf8(w).unwrap() String::from_utf8(w).unwrap()
} }
pub fn insert_refcount_operations( pub fn insert_refcount_operations<'i>(
arena: &'a Bump, arena: &'a Bump,
home: ModuleId,
ident_ids: &'i mut IdentIds,
update_mode_ids: &'i mut UpdateModeIds,
procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) { ) {
let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, procs)); let borrow_params = arena.alloc(crate::borrow::infer_borrow(arena, procs));
crate::inc_dec::visit_procs(arena, borrow_params, procs); crate::inc_dec::visit_procs(
arena,
home,
ident_ids,
update_mode_ids,
borrow_params,
procs,
);
} }
pub fn insert_reset_reuse_operations<'i>( pub fn insert_reset_reuse_operations<'i>(
@ -2033,6 +2043,36 @@ impl<'a> Stmt<'a> {
_ => false, _ => false,
} }
} }
pub fn if_then_else(
arena: &'a Bump,
condition_symbol: Symbol,
return_layout: Layout<'a>,
then_branch_stmt: Stmt<'a>,
else_branch_stmt: &'a Stmt<'a>,
) -> Self {
let then_branch_info = BranchInfo::Constructor {
scrutinee: condition_symbol,
layout: Layout::bool(),
tag_id: 1,
};
let then_branch = (1u64, then_branch_info, then_branch_stmt);
let else_branch_info = BranchInfo::Constructor {
scrutinee: condition_symbol,
layout: Layout::bool(),
tag_id: 0,
};
let else_branch = (else_branch_info, else_branch_stmt);
Stmt::Switch {
cond_symbol: condition_symbol,
cond_layout: Layout::bool(),
branches: &*arena.alloc([then_branch]),
default_branch: else_branch,
ret_layout: return_layout,
}
}
} }
/// turn record/tag patterns into a when expression, e.g. /// turn record/tag patterns into a when expression, e.g.

View file

@ -83,6 +83,35 @@ impl HigherOrder {
HigherOrder::ListAll { .. } => 1, HigherOrder::ListAll { .. } => 1,
} }
} }
/// Index in the array of arguments of the symbol that is the closure data
/// (captured environment for this function)
pub const fn closure_data_index(&self) -> usize {
use HigherOrder::*;
match self {
ListMap { .. }
| ListMapWithIndex { .. }
| ListSortWith { .. }
| ListKeepIf { .. }
| ListKeepOks { .. }
| ListKeepErrs { .. }
| ListAny { .. }
| ListAll { .. }
| ListFindUnsafe { .. } => 2,
ListMap2 { .. } => 3,
ListMap3 { .. } => 4,
ListMap4 { .. } => 5,
ListWalk { .. } | ListWalkUntil { .. } | ListWalkBackwards { .. } | DictWalk { .. } => {
3
}
}
}
/// Index of the function symbol in the argument list
pub const fn function_index(&self) -> usize {
self.closure_data_index() - 1
}
} }
#[allow(dead_code)] #[allow(dead_code)]

View file

@ -3453,3 +3453,24 @@ fn polymorphic_lambda_set_multiple_specializations() {
u8 u8
) )
} }
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn list_map2_conslist() {
// this had an RC problem, https://github.com/rtfeldman/roc/issues/2968
assert_evals_to!(
indoc!(
r#"
ConsList a : [ Nil, Cons a (ConsList a) ]
x : List (ConsList Str)
x = List.map2 [ ] [ Nil ] Cons
when List.first x is
_ -> ""
"#
),
RocStr::default(),
RocStr
)
}

View file

@ -0,0 +1,61 @@
procedure List.2 (#Attr.2, #Attr.3):
let List.145 : U64 = lowlevel ListLen #Attr.2;
let List.142 : Int1 = lowlevel NumLt #Attr.3 List.145;
if List.142 then
let List.144 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let List.143 : [C {}, C Str] = Ok List.144;
ret List.143;
else
let List.141 : {} = Struct {};
let List.140 : [C {}, C Str] = Err List.141;
ret List.140;
procedure List.5 (#Attr.2, #Attr.3):
let List.146 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3;
ret List.146;
procedure Str.16 (#Attr.2, #Attr.3):
let Str.65 : Str = lowlevel StrRepeat #Attr.2 #Attr.3;
ret Str.65;
procedure Str.3 (#Attr.2, #Attr.3):
let Str.66 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.66;
procedure Test.1 ():
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";
let Test.22 : Str = "g";
let Test.20 : Str = CallByName Str.3 Test.21 Test.22;
dec Test.22;
let Test.19 : List Str = Array [Test.20];
ret Test.19;
procedure Test.2 ():
let Test.15 : List Str = CallByName Test.1;
let Test.16 : {} = Struct {};
let Test.14 : List Str = CallByName List.5 Test.15 Test.16;
dec Test.15;
ret Test.14;
procedure Test.3 (Test.4):
let Test.18 : U64 = 2i64;
let Test.17 : Str = CallByName Str.16 Test.4 Test.18;
ret Test.17;
procedure Test.0 ():
let Test.12 : List Str = CallByName Test.2;
let Test.13 : U64 = 0i64;
let Test.6 : [C {}, C Str] = CallByName List.2 Test.12 Test.13;
dec Test.12;
let Test.9 : U8 = 1i64;
let Test.10 : U8 = GetTagId Test.6;
let Test.11 : Int1 = lowlevel Eq Test.9 Test.10;
if Test.11 then
let Test.5 : Str = UnionAtIndex (Id 1) (Index 0) Test.6;
inc Test.5;
dec Test.6;
ret Test.5;
else
dec Test.6;
let Test.8 : Str = "Hello, World!\n";
ret Test.8;

View file

@ -0,0 +1,60 @@
procedure List.2 (#Attr.2, #Attr.3):
let List.145 : U64 = lowlevel ListLen #Attr.2;
let List.142 : Int1 = lowlevel NumLt #Attr.3 List.145;
if List.142 then
let List.144 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let List.143 : [C {}, C Str] = Ok List.144;
ret List.143;
else
let List.141 : {} = Struct {};
let List.140 : [C {}, C Str] = Err List.141;
ret List.140;
procedure List.5 (#Attr.2, #Attr.3):
inc #Attr.2;
let List.146 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3;
decref #Attr.2;
ret List.146;
procedure Str.3 (#Attr.2, #Attr.3):
let Str.66 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.66;
procedure Test.1 ():
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";
let Test.22 : Str = "g";
let Test.20 : Str = CallByName Str.3 Test.21 Test.22;
dec Test.22;
let Test.19 : List Str = Array [Test.20];
ret Test.19;
procedure Test.2 ():
let Test.15 : List Str = CallByName Test.1;
let Test.16 : {} = Struct {};
let Test.14 : List Str = CallByName List.5 Test.15 Test.16;
dec Test.15;
ret Test.14;
procedure Test.3 (Test.4):
let Test.18 : Str = "!";
let Test.17 : Str = CallByName Str.3 Test.4 Test.18;
dec Test.18;
ret Test.17;
procedure Test.0 ():
let Test.12 : List Str = CallByName Test.2;
let Test.13 : U64 = 0i64;
let Test.6 : [C {}, C Str] = CallByName List.2 Test.12 Test.13;
dec Test.12;
let Test.9 : U8 = 1i64;
let Test.10 : U8 = GetTagId Test.6;
let Test.11 : Int1 = lowlevel Eq Test.9 Test.10;
if Test.11 then
let Test.5 : Str = UnionAtIndex (Id 1) (Index 0) Test.6;
inc Test.5;
dec Test.6;
ret Test.5;
else
dec Test.6;
let Test.8 : Str = "Hello, World!\n";
ret Test.8;

View file

@ -0,0 +1,22 @@
procedure List.28 (#Attr.2, #Attr.3):
let List.143 : List I64 = lowlevel ListSortWith { xs: `#Attr.#arg1` } #Attr.2 Num.46 #Attr.3;
let Bool.14 : Int1 = lowlevel ListIsUnique #Attr.2;
if Bool.14 then
ret List.143;
else
decref #Attr.2;
ret List.143;
procedure List.54 (List.98):
let List.141 : {} = Struct {};
let List.140 : List I64 = CallByName List.28 List.98 List.141;
ret List.140;
procedure Num.46 (#Attr.2, #Attr.3):
let Num.273 : U8 = lowlevel NumCompare #Attr.2 #Attr.3;
ret Num.273;
procedure Test.0 ():
let Test.2 : List I64 = Array [4i64, 3i64, 2i64, 1i64];
let Test.1 : List I64 = CallByName List.54 Test.2;
ret Test.1;

View file

@ -1362,3 +1362,50 @@ fn opaque_assign_to_symbol() {
// "# // "#
// ) // )
// } // }
#[mono_test]
fn list_map_closure_borrows() {
indoc!(
r#"
app "test" provides [ out ] to "./platform"
list = [ Str.concat "lllllllllllllllllllllooooooooooong" "g" ]
example1 = List.map list \string -> Str.repeat string 2
out =
when List.get example1 0 is
Ok s -> s
Err _ -> "Hello, World!\n"
"#
)
}
#[mono_test]
fn list_map_closure_owns() {
indoc!(
r#"
app "test" provides [ out ] to "./platform"
list = [ Str.concat "lllllllllllllllllllllooooooooooong" "g" ]
example2 = List.map list \string -> Str.concat string "!"
out =
when List.get example2 0 is
Ok s -> s
Err _ -> "Hello, World!\n"
"#
)
}
#[mono_test]
fn list_sort_asc() {
indoc!(
r#"
app "test" provides [ out ] to "./platform"
out = List.sortAsc [ 4,3,2,1 ]
"#
)
}