inc lowlevel return value

This commit is contained in:
J.Teeuwissen 2023-04-29 10:18:04 +02:00
parent e28db15b7e
commit c1ced3c5d2
No known key found for this signature in database
GPG key ID: DB5F7A1ED8D478AD
15 changed files with 227 additions and 189 deletions

View file

@ -3,7 +3,6 @@ use crate::llvm::build::{
allocate_with_refcount_help, cast_basic_basic, Env, RocFunctionCall, Scope,
};
use crate::llvm::convert::basic_type_from_layout;
use crate::llvm::refcounting::increment_refcount_layout;
use inkwell::builder::Builder;
use inkwell::types::{BasicType, PointerType};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
@ -125,7 +124,6 @@ pub(crate) fn list_with_capacity<'a, 'ctx, 'env>(
pub(crate) fn list_get_unsafe<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
element_layout: InLayout<'a>,
elem_index: IntValue<'ctx>,
wrapper_struct: StructValue<'ctx>,
@ -148,17 +146,13 @@ pub(crate) fn list_get_unsafe<'a, 'ctx, 'env>(
)
};
let result = load_roc_value(
load_roc_value(
env,
layout_interner,
element_layout,
elem_ptr,
"list_get_load_element",
);
increment_refcount_layout(env, layout_interner, layout_ids, 1, result, element_layout);
result
)
}
/// List.reserve : List elem, Nat -> List elem

View file

@ -807,7 +807,6 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
list_get_unsafe(
env,
layout_interner,
layout_ids,
list_element_layout!(layout_interner, list_layout),
element_index.into_int_value(),
wrapper_struct.into_struct_value(),

View file

@ -158,6 +158,7 @@ impl LowLevel {
/// Some wrapper functions can just be replaced by lowlevels in the backend for performance.
/// For example, Num.add should be an instruction, not a function call.
/// Variant names are chosen to help explain what to do when adding new lowlevels
#[derive(PartialEq, Eq)]
pub enum LowLevelWrapperType {
/// This wrapper function contains no logic and we can remove it in code gen
CanBeReplacedBy(LowLevel),

View file

@ -848,20 +848,16 @@ fn get_union_tag_layout(union_layout: UnionLayout<'_>, tag: Option<Tag>) -> Unio
Branch on the uniqueness of a symbol.
Using a joinpoint with the continuation as the body.
*/
fn branch_uniqueness<'a, 'i, F1, F2>(
fn branch_uniqueness<'a, 'i>(
arena: &'a Bump,
ident_ids: &'i mut IdentIds,
layout_interner: &'i mut STLayoutInterner<'a>,
environment: &DropSpecializationEnvironment<'a>,
symbol: Symbol,
unique: F1,
not_unique: F2,
unique: impl FnOnce(&mut STLayoutInterner<'a>, &mut IdentIds, &'a Stmt<'a>) -> &'a Stmt<'a>,
not_unique: impl FnOnce(&mut STLayoutInterner<'a>, &mut IdentIds, &'a Stmt<'a>) -> &'a Stmt<'a>,
continutation: &'a Stmt<'a>,
) -> &'a Stmt<'a>
where
F1: FnOnce(&mut STLayoutInterner<'a>, &mut IdentIds, &'a Stmt<'a>) -> &'a Stmt<'a>,
F2: FnOnce(&mut STLayoutInterner<'a>, &mut IdentIds, &'a Stmt<'a>) -> &'a Stmt<'a>,
{
) -> &'a Stmt<'a> {
match continutation {
// The continuation is a single stmt. So we can insert it inline and skip creating a joinpoint.
Stmt::Ret(_) | Stmt::Jump(_, _) => {
@ -911,16 +907,13 @@ where
}
}
fn unique_symbol<'a, 'i, F>(
fn unique_symbol<'a, 'i>(
arena: &'a Bump,
ident_ids: &'i mut IdentIds,
environment: &DropSpecializationEnvironment<'a>,
symbol: Symbol,
continuation: F,
) -> &'a Stmt<'a>
where
F: FnOnce(Symbol) -> &'a mut Stmt<'a>,
{
continuation: impl FnOnce(Symbol) -> &'a mut Stmt<'a>,
) -> &'a Stmt<'a> {
let is_unique = environment.create_symbol(ident_ids, "is_unique");
arena.alloc(Stmt::Let(

View file

@ -10,6 +10,7 @@ use std::{collections::HashMap, hash::BuildHasherDefault};
use bumpalo::collections::{CollectIn, Vec};
use bumpalo::Bump;
use roc_collections::{all::WyHash, MutMap, MutSet};
use roc_module::low_level::LowLevel;
use roc_module::{low_level::LowLevelWrapperType, symbol::Symbol};
use crate::{
@ -178,7 +179,6 @@ impl<'a, 'i> SymbolRcTypesEnv<'a, 'i> {
.chain([(&default_branch.0, default_branch.1)])
{
match info {
BranchInfo::None => (),
BranchInfo::Constructor {
scrutinee,
layout,
@ -186,6 +186,7 @@ impl<'a, 'i> SymbolRcTypesEnv<'a, 'i> {
} => {
self.insert_symbol_layout_rc_type(scrutinee, layout);
}
_ => (),
}
self.insert_symbols_rc_type_stmt(stmt);
@ -860,7 +861,7 @@ fn insert_refcount_operations_binding<'a>(
stmt: &'a Stmt<'a>,
) -> &'a Stmt<'a> {
macro_rules! dec_borrowed {
($symbols:expr,$stmt:expr) => {
($symbols:expr, $stmt:expr) => {
// Insert decrement operations for borrowed symbols if they are currently owned.
consume_and_insert_dec_stmts(
arena,
@ -889,175 +890,41 @@ fn insert_refcount_operations_binding<'a>(
};
}
macro_rules! refcount_listget {
($arguments:expr) => {{
// TODO this index can be used to store the children of the list symbol.
// On the decrement of the list (if the list size is known and) the function can be specialized.
let [structure, _index] = match $arguments {
[structure, index] => Some([*structure, *index]),
_ => None,
}
.unwrap();
// All structures are alive at this point and don't have to be copied in order to take an index out/get tag id/copy values to the stack.
// But we do want to make sure to decrement this item if it is the last reference.
let new_stmt = dec_borrowed!([structure], stmt);
// Add an increment operation for the binding if it is reference counted and if the expression creates a new reference to a value.
let newer_stmt = if matches!(
environment.get_symbol_rc_type(binding),
VarRcType::ReferenceCounted
) {
insert_inc_stmt(arena, *binding, 1, new_stmt)
} else {
// If the symbol is not reference counted, we don't need to increment it.
new_stmt
};
new_let!(newer_stmt)
}};
}
match expr {
Expr::Literal(_) | Expr::NullPointer | Expr::EmptyArray | Expr::RuntimeErrorFunction(_) => {
// Literals, empty arrays, and runtime errors are not (and have nothing) reference counted.
new_let!(stmt)
}
Expr::Call(Call {
arguments,
call_type,
}) => match call_type {
// A by name call refers to a normal function call.
// Normal functions take all their parameters as owned, so we can mark them all as such.
CallType::ByName { name, .. } => {
// Lowlevels are wrapped in another function in order to add type signatures which help with inference.
// But the reference counting algorithm inserts reference counting operations in the wrapper function.
// But in a later stage, calls to the wrapper function were replaced by calls to the lowlevel function.
// Effectively removing the inserted reference counting operations.
// Thus to prevent that, we inline the operations here already.
if let LowLevelWrapperType::CanBeReplacedBy(op) =
LowLevelWrapperType::from_symbol(name.name())
{
let borrow_signature = lowlevel_borrow_signature(arena, op);
let arguments_with_borrow_signature = arguments
.iter()
.copied()
.zip(borrow_signature.iter().copied());
let owned_arguments = arguments_with_borrow_signature
.clone()
.filter_map(|(symbol, ownership)| ownership.is_owned().then_some(symbol));
let borrowed_arguments =
arguments_with_borrow_signature.filter_map(|(symbol, ownership)| {
ownership.is_borrowed().then_some(symbol)
});
let new_stmt = dec_borrowed!(borrowed_arguments, stmt);
let new_let = new_let!(new_stmt);
inc_owned!(owned_arguments, new_let)
} else {
let new_let = new_let!(stmt);
inc_owned!(arguments.iter().copied(), new_let)
}
}
CallType::Foreign { .. } => {
// Foreign functions should be responsible for their own memory management.
// But previously they were assumed to be called with borrowed parameters, so we do the same now.
let new_stmt = dec_borrowed!(arguments.iter().copied(), stmt);
new_let!(new_stmt)
}
// Doesn't include higher order
CallType::LowLevel {
op: operator,
update_mode: _,
} => {
let borrow_signature = lowlevel_borrow_signature(arena, *operator);
let arguments_with_borrow_signature = arguments
.iter()
.copied()
.zip(borrow_signature.iter().copied());
let owned_arguments = arguments_with_borrow_signature
.clone()
.filter_map(|(symbol, ownership)| ownership.is_owned().then_some(symbol));
let borrowed_arguments = arguments_with_borrow_signature
.filter_map(|(symbol, ownership)| ownership.is_borrowed().then_some(symbol));
let new_stmt = dec_borrowed!(borrowed_arguments, stmt);
let new_let = new_let!(new_stmt);
inc_owned!(owned_arguments, new_let)
}
CallType::HigherOrder(HigherOrderLowLevel {
op: operator,
closure_env_layout: _,
/// update mode of the higher order lowlevel itself
update_mode: _,
passed_function,
}) => {
// Functions always take their arguments as owned.
// (Except lowlevels, but those are wrapped in functions that take their arguments as owned and perform rc.)
// This should always be true, not sure where this could be set to false.
debug_assert!(passed_function.owns_captured_environment);
// define macro that inserts a decref statement for a symbol amount of symbols
macro_rules! decref_lists {
($stmt:expr, $symbol:expr) => {
arena.alloc(Stmt::Refcounting(ModifyRc::DecRef($symbol), $stmt))
};
($stmt:expr, $symbol:expr, $($symbols:expr),+) => {{
decref_lists!(decref_lists!($stmt, $symbol), $($symbols),+)
}};
}
match operator {
HigherOrder::ListMap { xs } => {
if let [_xs_symbol, _function_symbol, closure_symbol] = &arguments {
let new_stmt = dec_borrowed!([*closure_symbol], stmt);
let new_stmt = decref_lists!(new_stmt, *xs);
let new_let = new_let!(new_stmt);
inc_owned!([*xs].into_iter(), new_let)
} else {
panic!("ListMap should have 3 arguments");
}
}
HigherOrder::ListMap2 { xs, ys } => {
if let [_xs_symbol, _ys_symbol, _function_symbol, closure_symbol] =
&arguments
{
let new_stmt = dec_borrowed!([*closure_symbol], stmt);
let new_stmt = decref_lists!(new_stmt, *xs, *ys);
let new_let = new_let!(new_stmt);
inc_owned!([*xs, *ys].into_iter(), new_let)
} else {
panic!("ListMap2 should have 4 arguments");
}
}
HigherOrder::ListMap3 { xs, ys, zs } => {
if let [_xs_symbol, _ys_symbol, _zs_symbol, _function_symbol, closure_symbol] =
&arguments
{
let new_stmt = dec_borrowed!([*closure_symbol], stmt);
let new_stmt = decref_lists!(new_stmt, *xs, *ys, *zs);
let new_let = new_let!(new_stmt);
inc_owned!([*xs, *ys, *zs].into_iter(), new_let)
} else {
panic!("ListMap3 should have 5 arguments");
}
}
HigherOrder::ListMap4 { xs, ys, zs, ws } => {
if let [_xs_symbol, _ys_symbol, _zs_symbol, _ws_symbol, _function_symbol, closure_symbol] =
&arguments
{
let new_stmt = dec_borrowed!([*closure_symbol], stmt);
let new_stmt = decref_lists!(new_stmt, *xs, *ys, *zs, *ws);
let new_let = new_let!(new_stmt);
inc_owned!([*xs, *ys, *zs, *ws].into_iter(), new_let)
} else {
panic!("ListMap4 should have 6 arguments");
}
}
HigherOrder::ListSortWith { xs } => {
// TODO if non-unique, elements have been consumed, must still consume the list itself
if let [_xs_symbol, _function_symbol, closure_symbol] = &arguments {
let new_stmt = dec_borrowed!([*closure_symbol], stmt);
let new_let = new_let!(new_stmt);
inc_owned!([*xs].into_iter(), new_let)
} else {
panic!("ListSortWith should have 3 arguments");
}
}
}
}
},
Expr::Tag { arguments, .. } | Expr::Struct(arguments) => {
let new_let = new_let!(stmt);
@ -1068,13 +935,30 @@ fn insert_refcount_operations_binding<'a>(
inc_owned!([*symbol], new_let)
}
Expr::Call(Call {
arguments,
call_type:
CallType::LowLevel {
op: LowLevel::ListGetUnsafe,
..
},
}) => {
refcount_listget!(arguments)
}
Expr::Call(Call {
arguments,
call_type: CallType::ByName { name, .. },
}) if (LowLevelWrapperType::from_symbol(name.name())
== LowLevelWrapperType::CanBeReplacedBy(LowLevel::ListGetUnsafe)) =>
{
refcount_listget!(arguments)
}
Expr::GetTagId { structure, .. }
| Expr::StructAtIndex { structure, .. }
| Expr::UnionAtIndex { structure, .. }
| Expr::ExprUnbox { symbol: structure } => {
// All structures are alive at this point and don't have to be copied in order to take an index out/get tag id/copy values to the stack.
// But we do want to make sure to decrement this item if it is the last reference.
let new_stmt = dec_borrowed!([*structure], stmt);
// Add an increment operation for the binding if it is reference counted and if the expression creates a new reference to a value.
@ -1114,6 +998,162 @@ fn insert_refcount_operations_binding<'a>(
new_let
)
}
Expr::Call(Call {
arguments,
call_type,
}) => {
macro_rules! rc_lowlevel {
($operator:expr) => {{
let borrow_signature = lowlevel_borrow_signature(arena, $operator);
let arguments_with_borrow_signature = arguments
.iter()
.copied()
.zip(borrow_signature.iter().copied());
let owned_arguments = arguments_with_borrow_signature
.clone()
.filter_map(|(symbol, ownership)| ownership.is_owned().then_some(symbol));
let borrowed_arguments =
arguments_with_borrow_signature.filter_map(|(symbol, ownership)| {
ownership.is_borrowed().then_some(symbol)
});
let new_stmt = dec_borrowed!(borrowed_arguments, stmt);
let new_let = new_let!(new_stmt);
inc_owned!(owned_arguments, new_let)
}};
}
match call_type {
// A by name call refers to a normal function call.
// Normal functions take all their parameters as owned, so we can mark them all as such.
CallType::ByName { name, .. } => {
// Lowlevels are wrapped in another function in order to add type signatures which help with inference.
// But the reference counting algorithm inserts reference counting operations in the wrapper function.
// But in a later stage, calls to the wrapper function were replaced by calls to the lowlevel function.
// Effectively removing the inserted reference counting operations.
// Thus to prevent that, we inline the operations here already.
if let LowLevelWrapperType::CanBeReplacedBy(operator) =
LowLevelWrapperType::from_symbol(name.name())
{
rc_lowlevel!(operator)
} else {
let new_let = new_let!(stmt);
inc_owned!(arguments.iter().copied(), new_let)
}
}
CallType::Foreign { .. } => {
// Foreign functions should be responsible for their own memory management.
// But previously they were assumed to be called with borrowed parameters, so we do the same now.
let new_stmt = dec_borrowed!(arguments.iter().copied(), stmt);
new_let!(new_stmt)
}
// Doesn't include higher order
CallType::LowLevel {
op: operator,
update_mode: _,
} => {
rc_lowlevel!(*operator)
}
CallType::HigherOrder(HigherOrderLowLevel {
op: operator,
closure_env_layout: _,
/// update mode of the higher order lowlevel itself
update_mode: _,
passed_function,
}) => {
// Functions always take their arguments as owned.
// (Except lowlevels, but those are wrapped in functions that take their arguments as owned and perform rc.)
// This should always be true, not sure where this could be set to false.
debug_assert!(passed_function.owns_captured_environment);
// define macro that inserts a decref statement for a symbol amount of symbols
macro_rules! decref_lists {
($stmt:expr, $symbol:expr) => {
arena.alloc(Stmt::Refcounting(ModifyRc::DecRef($symbol), $stmt))
};
($stmt:expr, $symbol:expr, $($symbols:expr),+) => {{
decref_lists!(decref_lists!($stmt, $symbol), $($symbols),+)
}};
}
match operator {
HigherOrder::ListMap { xs } => {
if let [_xs_symbol, _function_symbol, closure_symbol] = &arguments {
let new_stmt = dec_borrowed!([*closure_symbol], stmt);
let new_stmt = decref_lists!(new_stmt, *xs);
let new_let = new_let!(new_stmt);
inc_owned!([*xs].into_iter(), new_let)
} else {
panic!("ListMap should have 3 arguments");
}
}
HigherOrder::ListMap2 { xs, ys } => {
if let [_xs_symbol, _ys_symbol, _function_symbol, closure_symbol] =
&arguments
{
let new_stmt = dec_borrowed!([*closure_symbol], stmt);
let new_stmt = decref_lists!(new_stmt, *xs, *ys);
let new_let = new_let!(new_stmt);
inc_owned!([*xs, *ys].into_iter(), new_let)
} else {
panic!("ListMap2 should have 4 arguments");
}
}
HigherOrder::ListMap3 { xs, ys, zs } => {
if let [_xs_symbol, _ys_symbol, _zs_symbol, _function_symbol, closure_symbol] =
&arguments
{
let new_stmt = dec_borrowed!([*closure_symbol], stmt);
let new_stmt = decref_lists!(new_stmt, *xs, *ys, *zs);
let new_let = new_let!(new_stmt);
inc_owned!([*xs, *ys, *zs].into_iter(), new_let)
} else {
panic!("ListMap3 should have 5 arguments");
}
}
HigherOrder::ListMap4 { xs, ys, zs, ws } => {
if let [_xs_symbol, _ys_symbol, _zs_symbol, _ws_symbol, _function_symbol, closure_symbol] =
&arguments
{
let new_stmt = dec_borrowed!([*closure_symbol], stmt);
let new_stmt = decref_lists!(new_stmt, *xs, *ys, *zs, *ws);
let new_let = new_let!(new_stmt);
inc_owned!([*xs, *ys, *zs, *ws].into_iter(), new_let)
} else {
panic!("ListMap4 should have 6 arguments");
}
}
HigherOrder::ListSortWith { xs } => {
// TODO if non-unique, elements have been consumed, must still consume the list itself
if let [_xs_symbol, _function_symbol, closure_symbol] = &arguments {
let new_stmt = dec_borrowed!([*closure_symbol], stmt);
let new_let = new_let!(new_stmt);
inc_owned!([*xs].into_iter(), new_let)
} else {
panic!("ListSortWith should have 3 arguments");
}
}
}
}
}
}
Expr::Reuse { .. } | Expr::Reset { .. } | Expr::ResetRef { .. } => {
unreachable!("Reset(ref) and reuse should not exist at this point")
}

View file

@ -7,6 +7,7 @@ procedure List.2 (List.96, List.97):
let List.504 : Int1 = CallByName Num.22 List.97 List.508;
if List.504 then
let List.506 : Str = CallByName List.66 List.96 List.97;
inc List.506;
dec List.96;
let List.505 : [C {}, C Str] = TagId(1) List.506;
ret List.505;

View file

@ -247,6 +247,7 @@ procedure List.80 (List.547, List.548, List.549, List.550, List.551):
let List.524 : Int1 = CallByName Num.22 List.436 List.437;
if List.524 then
let List.531 : {Str, Str} = CallByName List.66 List.433 List.436;
inc List.531;
let List.525 : {List U8, U64} = CallByName List.139 List.434 List.531 List.435;
let List.528 : U64 = 1i64;
let List.527 : U64 = CallByName Num.19 List.436 List.528;
@ -262,6 +263,7 @@ procedure List.80 (List.621, List.622, List.623, List.624, List.625):
let List.597 : Int1 = CallByName Num.22 List.436 List.437;
if List.597 then
let List.604 : {Str, Str} = CallByName List.66 List.433 List.436;
inc List.604;
let List.598 : {List U8, U64} = CallByName List.139 List.434 List.604 List.435;
let List.601 : U64 = 1i64;
let List.600 : U64 = CallByName Num.19 List.436 List.601;

View file

@ -152,6 +152,7 @@ procedure List.80 (List.554, List.555, List.556, List.557, List.558):
let List.530 : Int1 = CallByName Num.22 List.436 List.437;
if List.530 then
let List.537 : {Str, Str} = CallByName List.66 List.433 List.436;
inc List.537;
let List.531 : {List U8, U64} = CallByName List.139 List.434 List.537 List.435;
let List.534 : U64 = 1i64;
let List.533 : U64 = CallByName Num.19 List.436 List.534;

View file

@ -159,6 +159,7 @@ procedure List.80 (List.554, List.555, List.556, List.557, List.558):
let List.530 : Int1 = CallByName Num.22 List.436 List.437;
if List.530 then
let List.537 : {Str, Str} = CallByName List.66 List.433 List.436;
inc List.537;
let List.531 : {List U8, U64} = CallByName List.139 List.434 List.537 List.435;
let List.534 : U64 = 1i64;
let List.533 : U64 = CallByName Num.19 List.436 List.534;

View file

@ -161,6 +161,7 @@ procedure List.80 (List.560, List.561, List.562, List.563, List.564):
let List.536 : Int1 = CallByName Num.22 List.436 List.437;
if List.536 then
let List.543 : Str = CallByName List.66 List.433 List.436;
inc List.543;
let List.537 : {List U8, U64} = CallByName List.139 List.434 List.543 List.435;
let List.540 : U64 = 1i64;
let List.539 : U64 = CallByName Num.19 List.436 List.540;

View file

@ -164,6 +164,7 @@ procedure List.80 (List.560, List.561, List.562, List.563, List.564):
let List.536 : Int1 = CallByName Num.22 List.436 List.437;
if List.536 then
let List.543 : Str = CallByName List.66 List.433 List.436;
inc List.543;
let List.537 : {List U8, U64} = CallByName List.139 List.434 List.543 List.435;
let List.540 : U64 = 1i64;
let List.539 : U64 = CallByName Num.19 List.436 List.540;

View file

@ -19,6 +19,7 @@ procedure List.80 (List.517, List.518, List.519, List.520, List.521):
let List.502 : Int1 = CallByName Num.22 List.436 List.437;
if List.502 then
let List.509 : [<rnu>C *self, <null>] = CallByName List.66 List.433 List.436;
inc List.509;
let List.503 : [<rnu><null>, C {[<rnu>C *self, <null>], *self}] = CallByName List.139 List.434 List.509 List.435;
let List.506 : U64 = 1i64;
let List.505 : U64 = CallByName Num.19 List.436 List.506;

View file

@ -3,6 +3,7 @@ procedure List.2 (List.96, List.97):
let List.496 : Int1 = CallByName Num.22 List.97 List.500;
if List.496 then
let List.498 : Str = CallByName List.66 List.96 List.97;
inc List.498;
dec List.96;
let List.497 : [C {}, C Str] = TagId(1) List.498;
ret List.497;

View file

@ -3,6 +3,7 @@ procedure List.2 (List.96, List.97):
let List.496 : Int1 = CallByName Num.22 List.97 List.500;
if List.496 then
let List.498 : Str = CallByName List.66 List.96 List.97;
inc List.498;
dec List.96;
let List.497 : [C {}, C Str] = TagId(1) List.498;
ret List.497;

View file

@ -146,6 +146,7 @@ procedure List.80 (List.558, List.559, List.560, List.561, List.562):
let List.534 : Int1 = CallByName Num.22 List.436 List.437;
if List.534 then
let List.541 : Str = CallByName List.66 List.433 List.436;
inc List.541;
let List.535 : {List U8, U64} = CallByName List.139 List.434 List.541 List.435;
let List.538 : U64 = 1i64;
let List.537 : U64 = CallByName Num.19 List.436 List.538;