roc/crates/compiler/mono/src/inc_dec.rs
2022-07-21 10:42:57 -04:00

1423 lines
44 KiB
Rust

use crate::borrow::{ParamMap, BORROWED, OWNED};
use crate::ir::{
CallType, Expr, HigherOrderLowLevel, JoinPointId, ModifyRc, Param, Proc, ProcLayout, Stmt,
UpdateModeIds,
};
use crate::layout::Layout;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_collections::all::{MutMap, MutSet};
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> {
let (mut occurring, bound) = occurring_variables(stmt);
for ref s in bound {
occurring.remove(s);
}
occurring
}
pub fn occurring_variables(stmt: &Stmt<'_>) -> (MutSet<Symbol>, MutSet<Symbol>) {
let mut stack = std::vec![stmt];
let mut result = MutSet::default();
let mut bound_variables = MutSet::default();
while let Some(stmt) = stack.pop() {
use Stmt::*;
match stmt {
Let(symbol, expr, _, cont) => {
occurring_variables_expr(expr, &mut result);
result.insert(*symbol);
bound_variables.insert(*symbol);
stack.push(cont);
}
Ret(symbol) => {
result.insert(*symbol);
}
Refcounting(modify, cont) => {
let symbol = modify.get_symbol();
result.insert(symbol);
stack.push(cont);
}
Expect {
condition,
remainder,
..
} => {
result.insert(*condition);
stack.push(remainder);
}
Jump(_, arguments) => {
result.extend(arguments.iter().copied());
}
Join {
parameters,
body: continuation,
remainder,
..
} => {
result.extend(parameters.iter().map(|p| p.symbol));
stack.push(continuation);
stack.push(remainder);
}
Switch {
cond_symbol,
branches,
default_branch,
..
} => {
result.insert(*cond_symbol);
stack.extend(branches.iter().map(|(_, _, s)| s));
stack.push(default_branch.1);
}
RuntimeError(_) => {}
}
}
(result, bound_variables)
}
fn occurring_variables_call(call: &crate::ir::Call<'_>, result: &mut MutSet<Symbol>) {
// NOTE though the function name does occur, it is a static constant in the program
// for liveness, it should not be included here.
result.extend(call.arguments.iter().copied());
}
pub fn occurring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) {
use Expr::*;
match expr {
StructAtIndex {
structure: symbol, ..
} => {
result.insert(*symbol);
}
Call(call) => occurring_variables_call(call, result),
Array {
elems: arguments, ..
} => result.extend(arguments.iter().filter_map(|e| e.to_symbol())),
Tag { arguments, .. } | Struct(arguments) => {
result.extend(arguments.iter().copied());
}
Reuse {
symbol, arguments, ..
} => {
result.extend(arguments.iter().copied());
result.insert(*symbol);
}
Reset { symbol: x, .. } => {
result.insert(*x);
}
ExprBox { symbol } | ExprUnbox { symbol } => {
result.insert(*symbol);
}
EmptyArray | RuntimeErrorFunction(_) | Literal(_) => {}
GetTagId {
structure: symbol, ..
} => {
result.insert(*symbol);
}
UnionAtIndex {
structure: symbol, ..
} => {
result.insert(*symbol);
}
}
}
/* Insert explicit RC instructions. So, it assumes the input code does not contain `inc` nor `dec` instructions.
This transformation is applied before lower level optimizations
that introduce the instructions `release` and `set`
*/
#[derive(Clone, Debug, Copy)]
struct VarInfo {
reference: bool, // true if the variable may be a reference (aka pointer) at runtime
persistent: bool, // true if the variable is statically known to be marked a Persistent at runtime
consume: bool, // true if the variable RC must be "consumed"
reset: bool, // true if the variable is the result of a Reset operation
}
type VarMap = MutMap<Symbol, VarInfo>;
pub type LiveVarSet = MutSet<Symbol>;
pub type JPLiveVarMap = MutMap<JoinPointId, LiveVarSet>;
#[derive(Clone, Debug)]
struct Context<'a> {
arena: &'a Bump,
vars: VarMap,
jp_live_vars: JPLiveVarMap, // map: join point => live variables
param_map: &'a ParamMap<'a>,
}
fn update_live_vars<'a>(expr: &Expr<'a>, v: &LiveVarSet) -> LiveVarSet {
let mut v = v.clone();
occurring_variables_expr(expr, &mut v);
v
}
/// `isFirstOcc xs x i = true` if `xs[i]` is the first occurrence of `xs[i]` in `xs`
fn is_first_occurrence(xs: &[Symbol], i: usize) -> bool {
match xs.get(i) {
None => unreachable!(),
Some(s) => i == xs.iter().position(|v| s == v).unwrap(),
}
}
/// Return `n`, the number of times `x` is consumed.
/// - `ys` is a sequence of instruction parameters where we search for `x`.
/// - `consumeParamPred i = true` if parameter `i` is consumed.
fn get_num_consumptions<F>(x: Symbol, ys: &[Symbol], consume_param_pred: F) -> usize
where
F: Fn(usize) -> bool,
{
let mut n = 0;
for (i, y) in ys.iter().enumerate() {
if x == *y && consume_param_pred(i) {
n += 1;
}
}
n
}
/// Return true if `x` also occurs in `ys` in a position that is not consumed.
/// That is, it is also passed as a borrow reference.
fn is_borrow_param_help<F>(x: Symbol, ys: &[Symbol], consume_param_pred: F) -> bool
where
F: Fn(usize) -> bool,
{
ys.iter()
.enumerate()
.any(|(i, y)| x == *y && !consume_param_pred(i))
}
fn is_borrow_param(x: Symbol, ys: &[Symbol], ps: &[Param]) -> bool {
// default to owned arguments
let is_owned = |i: usize| match ps.get(i) {
Some(param) => !param.borrow,
None => unreachable!("or?"),
};
is_borrow_param_help(x, ys, is_owned)
}
// We do not need to consume the projection of a variable that is not consumed
fn consume_expr(m: &VarMap, e: &Expr<'_>) -> bool {
match e {
Expr::StructAtIndex { structure: x, .. } => match m.get(x) {
Some(info) => info.consume,
None => true,
},
Expr::UnionAtIndex { structure: x, .. } => match m.get(x) {
Some(info) => info.consume,
None => true,
},
_ => true,
}
}
impl<'a> Context<'a> {
pub fn new(arena: &'a Bump, param_map: &'a ParamMap<'a>) -> Self {
let mut vars = MutMap::default();
for symbol in param_map.iter_symbols() {
vars.insert(
*symbol,
VarInfo {
reference: false, // assume function symbols are global constants
persistent: true, // assume function symbols are global constants
consume: false, // no need to consume this variable
reset: false, // reset symbols cannot be passed as function arguments
},
);
}
Self {
arena,
vars,
jp_live_vars: MutMap::default(),
param_map,
}
}
fn get_var_info(&self, symbol: Symbol) -> VarInfo {
match self.vars.get(&symbol) {
Some(info) => *info,
None => {
if cfg!(debug_assertions) {
eprintln!(
"Symbol {:?} {} has no info in self.vars",
symbol,
symbol, // self.vars
);
}
VarInfo {
persistent: true,
reference: false,
consume: false,
reset: false,
}
}
}
}
fn add_inc(&self, symbol: Symbol, inc_amount: u64, stmt: &'a Stmt<'a>) -> &'a Stmt<'a> {
debug_assert!(inc_amount > 0);
let info = self.get_var_info(symbol);
if info.persistent {
// persistent values are never reference counted
return stmt;
}
// if this symbol is never a reference, don't emit
if !info.reference {
return stmt;
}
let modify = ModifyRc::Inc(symbol, inc_amount);
self.arena.alloc(Stmt::Refcounting(modify, stmt))
}
fn add_dec(&self, symbol: Symbol, stmt: &'a Stmt<'a>) -> &'a Stmt<'a> {
let info = self.get_var_info(symbol);
if info.persistent {
// persistent values are never reference counted
return stmt;
}
// if this symbol is never a reference, don't emit
if !info.reference {
return stmt;
}
let modify = if info.reset {
ModifyRc::DecRef(symbol)
} else {
ModifyRc::Dec(symbol)
};
self.arena.alloc(Stmt::Refcounting(modify, stmt))
}
fn add_inc_before_consume_all(
&self,
xs: &[Symbol],
b: &'a Stmt<'a>,
live_vars_after: &LiveVarSet,
) -> &'a Stmt<'a> {
self.add_inc_before_help(xs, |_: usize| true, b, live_vars_after)
}
fn add_inc_before_help<F>(
&self,
xs: &[Symbol],
consume_param_pred: F,
mut b: &'a Stmt<'a>,
live_vars_after: &LiveVarSet,
) -> &'a Stmt<'a>
where
F: Fn(usize) -> bool + Clone,
{
for (i, x) in xs.iter().enumerate() {
let info = self.get_var_info(*x);
if !info.reference || !is_first_occurrence(xs, i) {
// do nothing
} else {
let num_consumptions = get_num_consumptions(*x, xs, consume_param_pred.clone()); // number of times the argument is used
// `x` is not a variable that must be consumed by the current procedure
let need_not_consume = !info.consume;
// `x` is live after executing instruction
let is_live_after = live_vars_after.contains(x);
// `x` is used in a position that is passed as a borrow reference
let is_borrowed = is_borrow_param_help(*x, xs, consume_param_pred.clone());
let num_incs = if need_not_consume || is_live_after || is_borrowed {
num_consumptions
} else {
num_consumptions - 1
};
if num_incs >= 1 {
b = self.add_inc(*x, num_incs as u64, b)
}
}
}
b
}
fn add_inc_before(
&self,
xs: &[Symbol],
ps: &[Param],
b: &'a Stmt<'a>,
live_vars_after: &LiveVarSet,
) -> &'a Stmt<'a> {
// default to owned arguments
let pred = |i: usize| match ps.get(i) {
Some(param) => !param.borrow,
None => unreachable!("or?"),
};
self.add_inc_before_help(xs, pred, b, live_vars_after)
}
fn add_dec_if_needed(
&self,
x: Symbol,
b: &'a Stmt<'a>,
b_live_vars: &LiveVarSet,
) -> &'a Stmt<'a> {
if self.must_consume(x) && !b_live_vars.contains(&x) {
self.add_dec(x, b)
} else {
b
}
}
fn must_consume(&self, x: Symbol) -> bool {
let info = self.get_var_info(x);
info.reference && info.consume
}
fn add_dec_after_application(
&self,
xs: &[Symbol],
ps: &[Param],
mut b: &'a Stmt<'a>,
b_live_vars: &LiveVarSet,
) -> &'a Stmt<'a> {
for (i, x) in xs.iter().enumerate() {
// We must add a `dec` if `x` must be consumed, it is alive after the application,
// and it has been borrowed by the application.
// Remark: `x` may occur multiple times in the application (e.g., `f x y x`).
// This is why we check whether it is the first occurrence.
if self.must_consume(*x)
&& is_first_occurrence(xs, i)
&& is_borrow_param(*x, xs, ps)
&& !b_live_vars.contains(x)
{
b = self.add_dec(*x, b);
}
}
b
}
fn add_dec_after_lowlevel(
&self,
xs: &[Symbol],
ps: &[bool],
mut b: &'a Stmt<'a>,
b_live_vars: &LiveVarSet,
) -> &'a Stmt<'a> {
for (i, (x, is_borrow)) in xs.iter().zip(ps.iter()).enumerate() {
/* We must add a `dec` if `x` must be consumed, it is alive after the application,
and it has been borrowed by the application.
Remark: `x` may occur multiple times in the application (e.g., `f x y x`).
This is why we check whether it is the first occurrence. */
if self.must_consume(*x)
&& is_first_occurrence(xs, i)
&& *is_borrow
&& !b_live_vars.contains(x)
{
b = self.add_dec(*x, b);
}
}
b
}
#[allow(clippy::too_many_arguments)]
fn visit_call<'i>(
&self,
codegen: &mut CodegenTools<'i>,
z: Symbol,
call_type: crate::ir::CallType<'a>,
arguments: &'a [Symbol],
l: Layout<'a>,
b: &'a Stmt<'a>,
b_live_vars: &LiveVarSet,
) -> &'a Stmt<'a> {
use crate::ir::CallType::*;
match &call_type {
LowLevel { op, .. } => {
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op);
let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars);
let v = Expr::Call(crate::ir::Call {
call_type,
arguments,
});
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
HigherOrder(lowlevel) => {
self.visit_higher_order_lowlevel(codegen, z, lowlevel, arguments, l, b, b_live_vars)
}
Foreign { .. } => {
let ps = crate::borrow::foreign_borrow_signature(self.arena, arguments.len());
let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars);
let v = Expr::Call(crate::ir::Call {
call_type,
arguments,
});
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ByName {
name,
ret_layout,
arg_layouts,
..
} => {
let top_level =
ProcLayout::new(self.arena, arg_layouts, name.captures_niche(), **ret_layout);
// get the borrow signature
let ps = self
.param_map
.get_symbol(name.name(), top_level)
.expect("function is defined");
let v = Expr::Call(crate::ir::Call {
call_type,
arguments,
});
let b = self.add_dec_after_application(arguments, ps, b, b_live_vars);
let b = self.arena.alloc(Stmt::Let(z, v, l, b));
self.add_inc_before(arguments, ps, b, b_live_vars)
}
}
}
#[allow(clippy::too_many_arguments)]
fn visit_higher_order_lowlevel<'i>(
&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,
captures_niche: passed_function.name.captures_niche(),
};
let function_ps = match self
.param_map
.get_symbol(passed_function.name.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 } => {
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)
}
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)
}
}
}
#[allow(clippy::many_single_char_names)]
fn visit_variable_declaration<'i>(
&self,
codegen: &mut CodegenTools<'i>,
z: Symbol,
v: Expr<'a>,
l: Layout<'a>,
b: &'a Stmt<'a>,
b_live_vars: &LiveVarSet,
) -> (&'a Stmt<'a>, LiveVarSet) {
use Expr::*;
let mut live_vars = update_live_vars(&v, b_live_vars);
live_vars.remove(&z);
let new_b = match v {
Reuse { arguments: ys, .. } | Tag { arguments: ys, .. } | Struct(ys) => self
.add_inc_before_consume_all(
ys,
self.arena.alloc(Stmt::Let(z, v, l, b)),
b_live_vars,
),
Array { elems, .. } => {
let ys = Vec::from_iter_in(elems.iter().filter_map(|e| e.to_symbol()), self.arena);
self.add_inc_before_consume_all(
&ys,
self.arena.alloc(Stmt::Let(z, v, l, b)),
b_live_vars,
)
}
Call(crate::ir::Call {
call_type,
arguments,
}) => self.visit_call(codegen, z, call_type, arguments, l, b, b_live_vars),
StructAtIndex { structure: x, .. } => {
let b = self.add_dec_if_needed(x, b, b_live_vars);
let info_x = self.get_var_info(x);
let b = if info_x.consume {
self.add_inc(z, 1, b)
} else {
b
};
self.arena.alloc(Stmt::Let(z, v, l, b))
}
GetTagId { structure: x, .. } => {
let b = self.add_dec_if_needed(x, b, b_live_vars);
let info_x = self.get_var_info(x);
let b = if info_x.consume {
self.add_inc(z, 1, b)
} else {
b
};
self.arena.alloc(Stmt::Let(z, v, l, b))
}
UnionAtIndex { structure: x, .. } => {
let b = self.add_dec_if_needed(x, b, b_live_vars);
let info_x = self.get_var_info(x);
let b = if info_x.consume {
self.add_inc(z, 1, b)
} else {
b
};
self.arena.alloc(Stmt::Let(z, v, l, b))
}
ExprBox { symbol: x } => {
// mimics Tag
self.add_inc_before_consume_all(
&[x],
self.arena.alloc(Stmt::Let(z, v, l, b)),
b_live_vars,
)
}
ExprUnbox { symbol: x } => {
// mimics UnionAtIndex
let b = self.add_dec_if_needed(x, b, b_live_vars);
let info_x = self.get_var_info(x);
let b = if info_x.consume {
self.add_inc(z, 1, b)
} else {
b
};
self.arena.alloc(Stmt::Let(z, v, l, b))
}
EmptyArray | Literal(_) | Reset { .. } | RuntimeErrorFunction(_) => {
// EmptyArray is always stack-allocated
// function pointers are persistent
self.arena.alloc(Stmt::Let(z, v, l, b))
}
};
(new_b, live_vars)
}
fn update_var_info(&self, symbol: Symbol, layout: &Layout<'a>, expr: &Expr<'a>) -> Self {
// is this value a constant?
// TODO do function pointers also fall into this category?
let persistent = false;
// must this value be consumed?
let consume = consume_expr(&self.vars, expr);
let reset = matches!(expr, Expr::Reset { .. });
self.update_var_info_help(symbol, layout, persistent, consume, reset)
}
fn update_var_info_help(
&self,
symbol: Symbol,
layout: &Layout<'a>,
persistent: bool,
consume: bool,
reset: bool,
) -> Self {
// should we perform incs and decs on this value?
let reference = layout.contains_refcounted();
let info = VarInfo {
reference,
persistent,
consume,
reset,
};
let mut ctx = self.clone();
ctx.vars.insert(symbol, info);
ctx
}
fn update_var_info_with_params(&self, ps: &[Param]) -> Self {
let mut ctx = self.clone();
for p in ps.iter() {
let info = VarInfo {
reference: p.layout.contains_refcounted(),
consume: !p.borrow,
persistent: false,
reset: false,
};
ctx.vars.insert(p.symbol, info);
}
ctx
}
// Add `dec` instructions for parameters that are
//
// - references
// - not alive in `b`
// - not borrow.
//
// That is, we must make sure these parameters are consumed.
fn add_dec_for_dead_params(
&self,
ps: &[Param<'a>],
mut b: &'a Stmt<'a>,
b_live_vars: &LiveVarSet,
) -> &'a Stmt<'a> {
for p in ps.iter() {
if !p.borrow && p.layout.contains_refcounted() && !b_live_vars.contains(&p.symbol) {
b = self.add_dec(p.symbol, b)
}
}
b
}
fn add_dec_for_alt(
&self,
case_live_vars: &LiveVarSet,
alt_live_vars: &LiveVarSet,
mut b: &'a Stmt<'a>,
) -> &'a Stmt<'a> {
for x in case_live_vars.iter() {
if !alt_live_vars.contains(x) && self.must_consume(*x) {
b = self.add_dec(*x, b);
}
}
b
}
fn visit_stmt<'i>(
&self,
codegen: &mut CodegenTools<'i>,
stmt: &'a Stmt<'a>,
) -> (&'a Stmt<'a>, LiveVarSet) {
use Stmt::*;
// let-chains can be very long, especially for large (list) literals
// in (rust) debug mode, this function can overflow the stack for such values
// so we have to write an explicit loop.
{
let mut cont = stmt;
let mut triples = Vec::new_in(self.arena);
while let Stmt::Let(symbol, expr, layout, new_cont) = cont {
triples.push((symbol, expr, layout));
cont = new_cont;
}
if !triples.is_empty() {
let mut ctx = self.clone();
for (symbol, expr, layout) in triples.iter() {
ctx = ctx.update_var_info(**symbol, layout, expr);
}
let (mut b, mut b_live_vars) = ctx.visit_stmt(codegen, cont);
for (symbol, expr, layout) in triples.into_iter().rev() {
let pair = ctx.visit_variable_declaration(
codegen,
*symbol,
(*expr).clone(),
*layout,
b,
&b_live_vars,
);
b = pair.0;
b_live_vars = pair.1;
}
return (b, b_live_vars);
}
}
match stmt {
Let(symbol, expr, layout, cont) => {
let ctx = self.update_var_info(*symbol, layout, expr);
let (b, b_live_vars) = ctx.visit_stmt(codegen, cont);
ctx.visit_variable_declaration(
codegen,
*symbol,
expr.clone(),
*layout,
b,
&b_live_vars,
)
}
Join {
id: j,
parameters: _,
remainder: b,
body: v,
} => {
// get the parameters with borrow signature
let xs = self.param_map.get_join_point(*j);
let (v, v_live_vars) = {
let ctx = self.update_var_info_with_params(xs);
ctx.visit_stmt(codegen, v)
};
let mut ctx = self.clone();
let v = ctx.add_dec_for_dead_params(xs, v, &v_live_vars);
update_jp_live_vars(*j, xs, v, &mut ctx.jp_live_vars);
let (b, b_live_vars) = ctx.visit_stmt(codegen, b);
(
ctx.arena.alloc(Join {
id: *j,
parameters: xs,
remainder: b,
body: v,
}),
b_live_vars,
)
}
Ret(x) => {
let info = self.get_var_info(*x);
let mut live_vars = MutSet::default();
live_vars.insert(*x);
if info.reference && !info.consume {
(self.add_inc(*x, 1, stmt), live_vars)
} else {
(stmt, live_vars)
}
}
Jump(j, xs) => {
let empty = MutSet::default();
let j_live_vars = match self.jp_live_vars.get(j) {
Some(vars) => vars,
None => &empty,
};
// TODO use borrow signature here?
let ps = self.param_map.get_join_point(*j);
let b = self.add_inc_before(xs, ps, stmt, j_live_vars);
let b_live_vars = collect_stmt(b, &self.jp_live_vars, MutSet::default());
(b, b_live_vars)
}
Switch {
cond_symbol,
cond_layout,
branches,
default_branch,
ret_layout,
} => {
let case_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default());
let branches = Vec::from_iter_in(
branches.iter().map(|(label, info, branch)| {
// TODO should we use ctor info like Lean?
let ctx = self.clone();
let (b, alt_live_vars) = ctx.visit_stmt(codegen, branch);
let b = ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b);
(*label, info.clone(), b.clone())
}),
self.arena,
)
.into_bump_slice();
let default_branch = {
// TODO should we use ctor info like Lean?
let ctx = self.clone();
let (b, alt_live_vars) = ctx.visit_stmt(codegen, default_branch.1);
(
default_branch.0.clone(),
ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b),
)
};
let switch = self.arena.alloc(Switch {
cond_symbol: *cond_symbol,
branches,
default_branch,
cond_layout: *cond_layout,
ret_layout: *ret_layout,
});
(switch, case_live_vars)
}
Expect {
remainder,
condition,
region,
lookups,
layouts,
} => {
let (b, b_live_vars) = self.visit_stmt(codegen, remainder);
let expect = self.arena.alloc(Stmt::Expect {
condition: *condition,
region: *region,
lookups,
layouts,
remainder: b,
});
(expect, b_live_vars)
}
RuntimeError(_) | Refcounting(_, _) => (stmt, MutSet::default()),
}
}
}
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(
stmt: &Stmt<'_>,
jp_live_vars: &JPLiveVarMap,
mut vars: LiveVarSet,
) -> LiveVarSet {
use Stmt::*;
match stmt {
Let(symbol, expr, _, cont) => {
vars = collect_stmt(cont, jp_live_vars, vars);
vars.remove(symbol);
let mut result = MutSet::default();
occurring_variables_expr(expr, &mut result);
vars.extend(result);
vars
}
Ret(symbol) => {
vars.insert(*symbol);
vars
}
Refcounting(modify, cont) => {
let symbol = modify.get_symbol();
vars.insert(symbol);
collect_stmt(cont, jp_live_vars, vars)
}
Expect {
condition,
remainder,
..
} => {
vars.insert(*condition);
collect_stmt(remainder, jp_live_vars, vars)
}
Join {
id: j,
parameters,
remainder: b,
body: v,
} => {
let mut j_live_vars = collect_stmt(v, jp_live_vars, MutSet::default());
for param in parameters.iter() {
j_live_vars.remove(&param.symbol);
}
let mut jp_live_vars = jp_live_vars.clone();
jp_live_vars.insert(*j, j_live_vars);
collect_stmt(b, &jp_live_vars, vars)
}
Jump(id, arguments) => {
vars.extend(arguments.iter().copied());
// NOTE deviation from Lean
// we fall through when no join point is available
if let Some(jvars) = jp_live_vars.get(id) {
vars.extend(jvars);
}
vars
}
Switch {
cond_symbol,
branches,
default_branch,
..
} => {
vars.insert(*cond_symbol);
for (_, _info, branch) in branches.iter() {
vars.extend(collect_stmt(branch, jp_live_vars, vars.clone()));
}
vars.extend(collect_stmt(default_branch.1, jp_live_vars, vars.clone()));
vars
}
RuntimeError(_) => vars,
}
}
fn update_jp_live_vars(j: JoinPointId, ys: &[Param], v: &Stmt<'_>, m: &mut JPLiveVarMap) {
let j_live_vars = MutSet::default();
let mut j_live_vars = collect_stmt(v, m, j_live_vars);
for param in ys {
j_live_vars.remove(&param.symbol);
}
m.insert(j, j_live_vars);
}
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,
home: ModuleId,
ident_ids: &'i mut IdentIds,
update_mode_ids: &'i mut UpdateModeIds,
param_map: &'a ParamMap<'a>,
procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) {
let ctx = Context::new(arena, param_map);
let mut codegen = CodegenTools {
home,
ident_ids,
update_mode_ids,
};
for (key, proc) in procs.iter_mut() {
visit_proc(arena, &mut codegen, param_map, &ctx, proc, key.1);
}
}
fn visit_proc<'a, 'i>(
arena: &'a Bump,
codegen: &mut CodegenTools<'i>,
param_map: &'a ParamMap<'a>,
ctx: &Context<'a>,
proc: &mut Proc<'a>,
layout: ProcLayout<'a>,
) {
let params = match param_map.get_symbol(proc.name.name(), layout) {
Some(slice) => slice,
None => Vec::from_iter_in(
proc.args.iter().cloned().map(|(layout, symbol)| Param {
symbol,
borrow: false,
layout,
}),
arena,
)
.into_bump_slice(),
};
let stmt = arena.alloc(proc.body.clone());
let ctx = ctx.update_var_info_with_params(params);
let (b, b_live_vars) = ctx.visit_stmt(codegen, stmt);
let b = ctx.add_dec_for_dead_params(params, b, &b_live_vars);
proc.body = b.clone();
}