This commit is contained in:
Folkert 2022-05-04 19:29:24 +02:00
parent 5d91ad9254
commit 42ee775fce
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
4 changed files with 218 additions and 109 deletions

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

@ -8,6 +8,41 @@ use bumpalo::Bump;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
/// Data and Function ownership relation
///
/// 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.
#[allow(clippy::enum_variant_names)]
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
enum DataFunction {
DataOwnedFunctionOwns,
DataOwnedFunctionBorrows,
DataBorrowedFunctionBorrows,
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);
@ -519,50 +554,66 @@ impl<'a> Context<'a> {
b: &'a Stmt<'a>, b: &'a Stmt<'a>,
b_live_vars: &LiveVarSet, b_live_vars: &LiveVarSet,
) -> &'a Stmt<'a> { ) -> &'a Stmt<'a> {
use crate::low_level::HigherOrder::*;
use DataFunction::*;
let HigherOrderLowLevel { let HigherOrderLowLevel {
op, op,
closure_env_layout,
update_mode,
passed_function, passed_function,
.. ..
} = lowlevel; } = lowlevel;
// setup
use crate::low_level::HigherOrder::*;
macro_rules! create_call { macro_rules! create_call {
($borrows:expr) => { ($borrows:expr) => {
Expr::Call(crate::ir::Call { create_holl_call(self.arena, lowlevel, $borrows, arguments)
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 {
CallType::HigherOrder(lowlevel)
},
arguments,
})
}; };
} }
macro_rules! decref_if_owned { macro_rules! handle_ownership_post {
($borrows:expr, $argument:expr, $stmt:expr) => { ($ownership:expr, $argument:expr, $stmt:expr) => {
if !$borrows { match $ownership {
self.arena.alloc(Stmt::Refcounting( DataOwnedFunctionOwns | DataBorrowedFunctionOwns => {
ModifyRc::DecRef($argument), // elements have been consumed, must still consume the list itself
self.arena.alloc($stmt), let rest = self.arena.alloc($stmt);
)) let rc = Stmt::Refcounting(ModifyRc::DecRef($argument), rest);
} else {
&*self.arena.alloc(rc)
}
DataOwnedFunctionBorrows => {
// must consume list and elements
let rest = self.arena.alloc($stmt);
let rc = Stmt::Refcounting(ModifyRc::Dec($argument), rest);
&*self.arena.alloc(rc)
}
DataBorrowedFunctionBorrows => {
// list borrows, function borrows, so there is nothing to do
$stmt $stmt
} }
}
};
}
macro_rules! handle_ownership_pre {
($ownership:expr, $argument:expr, $stmt:expr) => {
match $ownership {
DataBorrowedFunctionOwns => {
// the data is borrowed;
// increment it to own the values so the function can use them
let rest = self.arena.alloc($stmt);
let rc = Stmt::Refcounting(ModifyRc::Inc($argument, 1), rest);
self.arena.alloc(rc)
}
DataOwnedFunctionOwns | DataOwnedFunctionBorrows => {
// we actually own the data; nothing to do
$stmt
}
DataBorrowedFunctionBorrows => {
// list borrows, function borrows, so there is nothing to do
$stmt
}
}
}; };
} }
@ -582,7 +633,7 @@ impl<'a> Context<'a> {
None => unreachable!(), None => unreachable!(),
}; };
match op { match *op {
ListMap { xs } ListMap { xs }
| ListKeepIf { xs } | ListKeepIf { xs }
| ListKeepOks { xs } | ListKeepOks { xs }
@ -590,116 +641,136 @@ impl<'a> Context<'a> {
| ListAny { xs } | ListAny { xs }
| ListAll { xs } | ListAll { xs }
| ListFindUnsafe { xs } => { | ListFindUnsafe { xs } => {
let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA]; let ownership1 = DataFunction::new(&self.vars, xs, function_ps[0]);
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); let borrows = [FUNCTION, CLOSURE_DATA];
let b = self.add_dec_after_lowlevel(&arguments[1..], &borrows, b, b_live_vars);
// if the list is owned, then all elements have been consumed, but not the list itself let b = handle_ownership_post!(ownership1, xs, b);
println!("function borrows element? {}", borrows[0]);
let b = decref_if_owned!(function_ps[0].borrow, *xs, b);
let v = create_call!(function_ps.get(1)); let v = create_call!(function_ps.get(1));
&*self.arena.alloc(Stmt::Let(z, v, l, b)) let b = &*self.arena.alloc(Stmt::Let(z, v, l, b));
handle_ownership_pre!(ownership1, xs, b)
} }
ListMap2 { xs, ys } => { ListMap2 { xs, ys } => {
let borrows = [ let ownership1 = DataFunction::new(&self.vars, xs, function_ps[0]);
function_ps[0].borrow, let ownership2 = DataFunction::new(&self.vars, ys, function_ps[1]);
function_ps[1].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); let borrows = [FUNCTION, CLOSURE_DATA];
let b = decref_if_owned!(function_ps[0].borrow, *xs, b); let b = self.add_dec_after_lowlevel(&arguments[2..], &borrows, b, b_live_vars);
let b = decref_if_owned!(function_ps[1].borrow, *ys, b);
let b = handle_ownership_post!(ownership1, xs, b);
let b = handle_ownership_post!(ownership2, ys, b);
let v = create_call!(function_ps.get(2)); let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b)) let b = &*self.arena.alloc(Stmt::Let(z, v, l, b));
let b = handle_ownership_pre!(ownership1, xs, b);
handle_ownership_pre!(ownership2, ys, b)
} }
ListMap3 { xs, ys, zs } => { ListMap3 { xs, ys, zs } => {
let borrows = [ let ownership1 = DataFunction::new(&self.vars, xs, function_ps[0]);
function_ps[0].borrow, let ownership2 = DataFunction::new(&self.vars, ys, function_ps[1]);
function_ps[1].borrow, let ownership3 = DataFunction::new(&self.vars, zs, function_ps[2]);
function_ps[2].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); let borrows = [FUNCTION, CLOSURE_DATA];
let b = self.add_dec_after_lowlevel(&arguments[3..], &borrows, b, b_live_vars);
let b = decref_if_owned!(function_ps[0].borrow, *xs, b); let b = handle_ownership_post!(ownership1, xs, b);
let b = decref_if_owned!(function_ps[1].borrow, *ys, b); let b = handle_ownership_post!(ownership2, ys, b);
let b = decref_if_owned!(function_ps[2].borrow, *zs, b); let b = handle_ownership_post!(ownership3, zs, b);
let v = create_call!(function_ps.get(3)); let v = create_call!(function_ps.get(3));
&*self.arena.alloc(Stmt::Let(z, v, l, b)) let b = &*self.arena.alloc(Stmt::Let(z, v, l, b));
let b = handle_ownership_pre!(ownership1, xs, b);
let b = handle_ownership_pre!(ownership2, ys, b);
handle_ownership_pre!(ownership3, zs, b)
} }
ListMap4 { xs, ys, zs, ws } => { ListMap4 { xs, ys, zs, ws } => {
let borrows = [ let ownership1 = DataFunction::new(&self.vars, xs, function_ps[0]);
function_ps[0].borrow, let ownership2 = DataFunction::new(&self.vars, ys, function_ps[1]);
function_ps[1].borrow, let ownership3 = DataFunction::new(&self.vars, zs, function_ps[2]);
function_ps[2].borrow, let ownership4 = DataFunction::new(&self.vars, ws, function_ps[3]);
function_ps[3].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); let borrows = [FUNCTION, CLOSURE_DATA];
let b = decref_if_owned!(function_ps[0].borrow, *xs, b); let b = self.add_dec_after_lowlevel(&arguments[4..], &borrows, b, b_live_vars);
let b = decref_if_owned!(function_ps[1].borrow, *ys, b);
let b = decref_if_owned!(function_ps[2].borrow, *zs, b); let b = handle_ownership_post!(ownership1, xs, b);
let b = decref_if_owned!(function_ps[3].borrow, *ws, b); let b = handle_ownership_post!(ownership2, ys, b);
let b = handle_ownership_post!(ownership3, zs, b);
let b = handle_ownership_post!(ownership4, ws, b);
let v = create_call!(function_ps.get(3)); let v = create_call!(function_ps.get(3));
&*self.arena.alloc(Stmt::Let(z, v, l, b)) let b = &*self.arena.alloc(Stmt::Let(z, v, l, b));
let b = handle_ownership_pre!(ownership1, xs, b);
let b = handle_ownership_pre!(ownership2, ys, b);
handle_ownership_pre!(ownership3, zs, b)
} }
ListMapWithIndex { xs } => { ListMapWithIndex { xs } => {
let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA]; let ownership1 = DataFunction::new(&self.vars, xs, function_ps[1]);
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); let borrows = [FUNCTION, CLOSURE_DATA];
let b = self.add_dec_after_lowlevel(&arguments[1..], &borrows, b, b_live_vars);
let b = decref_if_owned!(function_ps[1].borrow, *xs, b); let b = handle_ownership_post!(ownership1, xs, b);
let v = create_call!(function_ps.get(2)); let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b)) let b = &*self.arena.alloc(Stmt::Let(z, v, l, b));
handle_ownership_pre!(ownership1, xs, b)
} }
ListSortWith { xs: _ } => { ListSortWith { xs } => {
let borrows = [OWNED, FUNCTION, CLOSURE_DATA]; // 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 ownership1 = DataFunction::new(&self.vars, xs, function_ps[0]);
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars); let borrows = [FUNCTION, CLOSURE_DATA];
let b = self.add_dec_after_lowlevel(&arguments[1..], &borrows, b, b_live_vars);
let b = handle_ownership_post!(ownership1, xs, b);
let v = create_call!(function_ps.get(2)); let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b)) let b = &*self.arena.alloc(Stmt::Let(z, v, l, b));
handle_ownership_pre!(ownership1, xs, b)
} }
ListWalk { xs, state: _ } ListWalk { xs, state }
| ListWalkUntil { xs, state: _ } | ListWalkUntil { xs, state }
| ListWalkBackwards { xs, state: _ } | ListWalkBackwards { xs, state }
| DictWalk { xs, state: _ } => { | DictWalk { xs, state } => {
// borrow data structure based on first argument of the folded function // borrow data structure based on second argument of the folded function
// borrow the default based on second argument of the folded function let ownership1 = DataFunction::new(&self.vars, xs, function_ps[1]);
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); // borrow the default based on first argument of the folded function
// let ownership2 = DataFunction::new(&self.vars, state, function_ps[0]);
let b = decref_if_owned!(function_ps[1].borrow, *xs, b); let borrows = [FUNCTION, CLOSURE_DATA];
let b = self.add_dec_after_lowlevel(&arguments[2..], &borrows, b, b_live_vars);
let b = handle_ownership_post!(ownership1, xs, b);
// let b = handle_ownership_post!(ownership2, state, b);
let v = create_call!(function_ps.get(2)); let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b)) let b = &*self.arena.alloc(Stmt::Let(z, v, l, b));
let b = handle_ownership_pre!(ownership1, xs, b);
// handle_ownership_pre!(ownership2, state, b)
b
} }
} }
} }
@ -1053,6 +1124,34 @@ impl<'a> Context<'a> {
} }
} }
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 {
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,

View file

@ -2644,11 +2644,7 @@ fn list_sort_with() {
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm"))]
fn list_sort_asc() { fn list_sort_asc() {
assert_evals_to!( // assert_evals_to!( "List.sortAsc []", RocList::<i64>::from_slice(&[]), RocList<i64>);
"List.sortAsc []",
RocList::<i64>::from_slice(&[]),
RocList<i64>
);
assert_evals_to!( assert_evals_to!(
"List.sortAsc [ 4,3,2,1 ]", "List.sortAsc [ 4,3,2,1 ]",
RocList::from_slice(&[1, 2, 3, 4]), RocList::from_slice(&[1, 2, 3, 4]),

View file

@ -1,11 +1,23 @@
app "helloWorld" app "helloWorld"
packages { pf: "c-platform" } # packages { pf: "c-platform" }
# To switch platforms, comment-out the line above and un-comment one below. # To switch platforms, comment-out the line above and un-comment one below.
# packages { pf: "rust-platform" } # packages { pf: "rust-platform" }
# packages { pf: "swift-platform" } # packages { pf: "swift-platform" }
# packages { pf: "web-platform" } # See ./web-platform/README.md # packages { pf: "web-platform" } # See ./web-platform/README.md
# packages { pf: "zig-platform" } packages { pf: "zig-platform" }
imports [] imports []
provides [ main ] to pf provides [ main ] to pf
main = "Hello, World!\n" list = [ Str.concat "lllllllllllllllllllllooooooooooong" "g" ]
# currently correct
example1 = List.map list \string -> Str.repeat string 2
# currently wrong
example2 = List.map list \string -> Str.concat string "!"
main =
when List.get example2 0 is
Ok s -> s
Err _ -> "Hello, World!\n"