mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
Merge pull request #3009 from rtfeldman/list-map-ownership
List map ownership
This commit is contained in:
commit
dd51479764
10 changed files with 739 additions and 202 deletions
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
61
compiler/test_mono/generated/list_map_closure_borrows.txt
Normal file
61
compiler/test_mono/generated/list_map_closure_borrows.txt
Normal 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;
|
60
compiler/test_mono/generated/list_map_closure_owns.txt
Normal file
60
compiler/test_mono/generated/list_map_closure_owns.txt
Normal 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;
|
22
compiler/test_mono/generated/list_sort_asc.txt
Normal file
22
compiler/test_mono/generated/list_sort_asc.txt
Normal 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;
|
|
@ -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 ]
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue