free or reuse unconditionally when value is unique

This commit is contained in:
Folkert 2023-06-27 17:33:53 +02:00
parent e3ab023f62
commit fc3004da58
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
21 changed files with 381 additions and 69 deletions

View file

@ -510,6 +510,12 @@ fn apply_refcount_operation(
builder.add_recursive_touch(block, argument)?;
}
ModifyRc::DecRef(symbol) => {
// this is almost certainly suboptimal, but not incorrect
let argument = env.symbols[symbol];
builder.add_recursive_touch(block, argument)?;
}
ModifyRc::Free(symbol) => {
// this is almost certainly suboptimal, but not incorrect
let argument = env.symbols[symbol];
builder.add_recursive_touch(block, argument)?;
}

View file

@ -195,8 +195,10 @@ comptime {
exportUtilsFn(utils.test_panic, "test_panic");
exportUtilsFn(utils.increfRcPtrC, "incref_rc_ptr");
exportUtilsFn(utils.decrefRcPtrC, "decref_rc_ptr");
exportUtilsFn(utils.freeRcPtrC, "free_rc_ptr");
exportUtilsFn(utils.increfDataPtrC, "incref_data_ptr");
exportUtilsFn(utils.decrefDataPtrC, "decref_data_ptr");
exportUtilsFn(utils.freeDataPtrC, "free_data_ptr");
exportUtilsFn(utils.isUnique, "is_unique");
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
exportUtilsFn(utils.allocateWithRefcountC, "allocate_with_refcount");

View file

@ -220,6 +220,29 @@ pub fn increfDataPtrC(
return increfRcPtrC(isizes, inc_amount);
}
pub fn freeDataPtrC(
bytes_or_null: ?[*]isize,
alignment: u32,
) callconv(.C) void {
var bytes = bytes_or_null orelse return;
const ptr = @ptrToInt(bytes);
const tag_mask: usize = if (@sizeOf(usize) == 8) 0b111 else 0b11;
const masked_ptr = ptr & ~tag_mask;
const isizes: [*]isize = @intToPtr([*]isize, masked_ptr);
return freeRcPtrC(isizes - 1, alignment);
}
pub fn freeRcPtrC(
bytes_or_null: ?[*]isize,
alignment: u32,
) callconv(.C) void {
var bytes = bytes_or_null orelse return;
return free_ptr_to_refcount(bytes, alignment);
}
pub fn decref(
bytes_or_null: ?[*]u8,
data_bytes: usize,
@ -236,6 +259,17 @@ pub fn decref(
decref_ptr_to_refcount(isizes - 1, alignment);
}
inline fn free_ptr_to_refcount(
refcount_ptr: [*]isize,
alignment: u32,
) void {
if (RC_TYPE == Refcount.none) return;
const extra_bytes = std.math.max(alignment, @sizeOf(usize));
// NOTE: we don't even check whether the refcount is "infinity" here!
dealloc(@ptrCast([*]u8, refcount_ptr) - (extra_bytes - @sizeOf(usize)), alignment);
}
inline fn decref_ptr_to_refcount(
refcount_ptr: [*]isize,
alignment: u32,

View file

@ -393,8 +393,10 @@ pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
pub const UTILS_ALLOCATE_WITH_REFCOUNT: &str = "roc_builtins.utils.allocate_with_refcount";
pub const UTILS_INCREF_RC_PTR: &str = "roc_builtins.utils.incref_rc_ptr";
pub const UTILS_DECREF_RC_PTR: &str = "roc_builtins.utils.decref_rc_ptr";
pub const UTILS_FREE_RC_PTR: &str = "roc_builtins.utils.free_rc_ptr";
pub const UTILS_INCREF_DATA_PTR: &str = "roc_builtins.utils.incref_data_ptr";
pub const UTILS_DECREF_DATA_PTR: &str = "roc_builtins.utils.decref_data_ptr";
pub const UTILS_FREE_DATA_PTR: &str = "roc_builtins.utils.free_data_ptr";
pub const UTILS_IS_UNIQUE: &str = "roc_builtins.utils.is_unique";
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
pub const UTILS_DICT_PSEUDO_SEED: &str = "roc_builtins.utils.dict_pseudo_seed";

View file

@ -87,6 +87,7 @@ macro_rules! map_symbol_to_lowlevel_and_arity {
LowLevel::PtrCast => unimplemented!(),
LowLevel::PtrStore => unimplemented!(),
LowLevel::PtrLoad => unimplemented!(),
LowLevel::PtrClearTagId => unimplemented!(),
LowLevel::Alloca => unimplemented!(),
LowLevel::RefCountIncRcPtr => unimplemented!(),
LowLevel::RefCountDecRcPtr=> unimplemented!(),

View file

@ -2996,12 +2996,17 @@ impl<
);
}
fn build_ptr_to_stack_value(
&mut self,
sym: Symbol,
value: Symbol,
element_layout: InLayout<'a>,
) {
fn build_ptr_clear_tag_id(&mut self, sym: Symbol, ptr: Symbol) {
let buf = &mut self.buf;
let ptr_reg = self.storage_manager.load_to_general_reg(buf, &ptr);
let sym_reg = self.storage_manager.claim_general_reg(buf, &sym);
ASM::mov_reg64_imm64(buf, sym_reg, !0b111);
ASM::and_reg64_reg64_reg64(buf, sym_reg, sym_reg, ptr_reg);
}
fn build_alloca(&mut self, sym: Symbol, value: Symbol, element_layout: InLayout<'a>) {
// 1. acquire some stack space
let element_width = self.interner().stack_size(element_layout);
let allocation = self.debug_symbol("stack_allocation");

View file

@ -17,7 +17,7 @@ use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::code_gen_help::{CallerProc, CodeGenHelp};
use roc_mono::ir::{
BranchInfo, CallType, CrashTag, Expr, HigherOrderLowLevel, JoinPointId, ListLiteralElement,
Literal, Param, Proc, ProcLayout, SelfRecursive, Stmt,
Literal, ModifyRc, Param, Proc, ProcLayout, SelfRecursive, Stmt,
};
use roc_mono::layout::{
Builtin, InLayout, LambdaName, Layout, LayoutIds, LayoutInterner, LayoutRepr, STLayoutInterner,
@ -525,6 +525,29 @@ trait Backend<'a> {
self.return_symbol(sym, ret_layout);
self.free_symbols(stmt);
}
Stmt::Refcounting(ModifyRc::Free(symbol), following) => {
let dst = Symbol::DEV_TMP;
let layout = *self.layout_map().get(symbol).unwrap();
let alignment_bytes = self.interner().allocation_alignment_bytes(layout);
let alignment = self.debug_symbol("alignment");
self.load_literal_i32(&alignment, alignment_bytes as i32);
// NOTE: UTILS_FREE_DATA_PTR clears any tag id bits
self.build_fn_call(
&dst,
bitcode::UTILS_FREE_DATA_PTR.to_string(),
&[*symbol, alignment],
&[Layout::I64, Layout::I32],
&Layout::UNIT,
);
self.free_symbol(&dst);
self.free_symbol(&alignment);
self.build_stmt(layout_ids, following, ret_layout)
}
Stmt::Refcounting(modify, following) => {
let sym = modify.get_symbol();
let layout = *self.layout_map().get(&sym).unwrap();
@ -1605,8 +1628,12 @@ trait Backend<'a> {
self.build_ptr_load(*sym, args[0], *ret_layout);
}
LowLevel::PtrClearTagId => {
self.build_ptr_clear_tag_id(*sym, args[0]);
}
LowLevel::Alloca => {
self.build_ptr_to_stack_value(*sym, args[0], arg_layouts[0]);
self.build_alloca(*sym, args[0], arg_layouts[0]);
}
LowLevel::RefCountDecRcPtr => self.build_fn_call(
@ -2247,12 +2274,9 @@ trait Backend<'a> {
fn build_ptr_load(&mut self, sym: Symbol, ptr: Symbol, element_layout: InLayout<'a>);
fn build_ptr_to_stack_value(
&mut self,
sym: Symbol,
value: Symbol,
element_layout: InLayout<'a>,
);
fn build_ptr_clear_tag_id(&mut self, sym: Symbol, ptr: Symbol);
fn build_alloca(&mut self, sym: Symbol, value: Symbol, element_layout: InLayout<'a>);
/// literal_map gets the map from symbol to literal and layout, used for lazy loading and literal folding.
fn literal_map(&mut self) -> &mut MutMap<Symbol, (*const Literal<'a>, *const InLayout<'a>)>;

View file

@ -2919,6 +2919,39 @@ pub(crate) fn build_exp_stmt<'a, 'ctx>(
cont,
)
}
Free(symbol) => {
// unconditionally deallocate the symbol
let (value, layout) = scope.load_symbol_and_layout(symbol);
let alignment = layout_interner.alignment_bytes(layout);
debug_assert!(value.is_pointer_value());
let value = value.into_pointer_value();
let clear_tag_id = match layout_interner.chase_recursive(layout) {
LayoutRepr::Union(union) => union.stores_tag_id_in_pointer(env.target_info),
_ => false,
};
let ptr = if clear_tag_id {
tag_pointer_clear_tag_id(env, value)
} else {
value
};
let rc_ptr = PointerToRefcount::from_ptr_to_data(env, ptr);
rc_ptr.deallocate(env, alignment);
build_exp_stmt(
env,
layout_interner,
layout_ids,
func_spec_solutions,
scope,
parent,
cont,
)
}
}
}

View file

@ -1325,6 +1325,12 @@ pub(crate) fn run_low_level<'a, 'ctx>(
.new_build_load(element_type, ptr.into_pointer_value(), "ptr_load")
}
PtrClearTagId => {
arguments!(ptr);
tag_pointer_clear_tag_id(env, ptr.into_pointer_value()).into()
}
Alloca => {
arguments!(initial_value);

View file

@ -14,7 +14,7 @@ use bumpalo::collections::Vec;
use inkwell::basic_block::BasicBlock;
use inkwell::module::Linkage;
use inkwell::types::{AnyTypeEnum, BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue};
use inkwell::values::{BasicValueEnum, FunctionValue, InstructionValue, IntValue, PointerValue};
use inkwell::{AddressSpace, IntPredicate};
use roc_module::symbol::Interns;
use roc_module::symbol::Symbol;
@ -193,6 +193,14 @@ impl<'ctx> PointerToRefcount<'ctx> {
builder.build_return(None);
}
pub fn deallocate<'a, 'env>(
&self,
env: &Env<'a, 'ctx, 'env>,
alignment: u32,
) -> InstructionValue<'ctx> {
free_pointer(env, self.value, alignment)
}
}
fn incref_pointer<'ctx>(
@ -216,6 +224,28 @@ fn incref_pointer<'ctx>(
);
}
fn free_pointer<'ctx>(
env: &Env<'_, 'ctx, '_>,
pointer: PointerValue<'ctx>,
alignment: u32,
) -> InstructionValue<'ctx> {
let alignment = env.context.i32_type().const_int(alignment as _, false);
call_void_bitcode_fn(
env,
&[
env.builder
.build_pointer_cast(
pointer,
env.ptr_int().ptr_type(AddressSpace::default()),
"to_isize_ptr",
)
.into(),
alignment.into(),
],
roc_builtins::bitcode::UTILS_FREE_RC_PTR,
)
}
fn decref_pointer<'ctx>(env: &Env<'_, 'ctx, '_>, pointer: PointerValue<'ctx>, alignment: u32) {
let alignment = env.context.i32_type().const_int(alignment as _, false);
call_void_bitcode_fn(

View file

@ -1,7 +1,7 @@
use bitvec::vec::BitVec;
use bumpalo::collections::{String, Vec};
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_collections::all::MutMap;
use roc_error_macros::internal_error;
use roc_module::low_level::{LowLevel, LowLevelWrapperType};
@ -719,7 +719,10 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
Stmt::Jump(id, arguments) => self.stmt_jump(*id, arguments),
Stmt::Refcounting(modify, following) => self.stmt_refcounting(modify, following),
Stmt::Refcounting(modify, following) => match modify {
ModifyRc::Free(symbol) => self.stmt_refcounting_free(*symbol, following),
_ => self.stmt_refcounting(modify, following),
},
Stmt::Dbg { .. } => todo!("dbg is not implemented in the wasm backend"),
Stmt::Expect { .. } => todo!("expect is not implemented in the wasm backend"),
@ -999,6 +1002,43 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
self.stmt(rc_stmt);
}
fn stmt_refcounting_free(&mut self, value: Symbol, following: &'a Stmt<'a>) {
let layout = self.storage.symbol_layouts[&value];
let alignment = self.layout_interner.allocation_alignment_bytes(layout);
// Get pointer and offset
let value_storage = self.storage.get(&value).to_owned();
let stored_with_local =
self.storage
.ensure_value_has_local(&mut self.code_builder, value, value_storage);
let (tag_local_id, tag_offset) = match stored_with_local {
StoredValue::StackMemory { location, .. } => {
location.local_and_offset(self.storage.stack_frame_pointer)
}
StoredValue::Local { local_id, .. } => (local_id, 0),
StoredValue::VirtualMachineStack { .. } => {
internal_error!("{:?} should have a local variable", value)
}
};
// load pointer, and add the offset to the pointer
self.code_builder.get_local(tag_local_id);
if tag_offset > 0 {
self.code_builder.i32_const(tag_offset as i32);
self.code_builder.i32_add();
}
// NOTE: UTILS_FREE_DATA_PTR clears any tag id bits
// push the allocation's alignment
self.code_builder.i32_const(alignment as i32);
self.call_host_fn_after_loading_args(bitcode::UTILS_FREE_DATA_PTR, 2, false);
self.stmt(following);
}
pub fn stmt_internal_error(&mut self, msg: &'a str) {
let msg_sym = self.create_symbol("panic_str");
let msg_storage = self.storage.allocate_var(

View file

@ -1979,6 +1979,19 @@ impl<'a> LowLevelCall<'a> {
);
}
PtrLoad => backend.expr_unbox(self.ret_symbol, self.arguments[0]),
PtrClearTagId => {
let ptr = self.arguments[0];
let ptr_local_id = match backend.storage.get(&ptr) {
StoredValue::Local { local_id, .. } => *local_id,
_ => internal_error!("A pointer will always be an i32"),
};
backend.code_builder.get_local(ptr_local_id);
backend.code_builder.i32_const(-4); // 11111111...1100
backend.code_builder.i32_and();
}
Alloca => {
// Alloca : a -> Ptr a
let arg = self.arguments[0];

View file

@ -3141,6 +3141,7 @@ fn update<'a>(
arena,
&layout_interner,
module_id,
state.target_info,
ident_ids,
&mut update_mode_ids,
&mut state.procedures,

View file

@ -120,6 +120,7 @@ pub enum LowLevel {
PtrCast,
PtrStore,
PtrLoad,
PtrClearTagId,
Alloca,
RefCountIncRcPtr,
RefCountDecRcPtr,
@ -232,6 +233,7 @@ macro_rules! map_symbol_to_lowlevel {
LowLevel::PtrCast => unimplemented!(),
LowLevel::PtrStore => unimplemented!(),
LowLevel::PtrLoad => unimplemented!(),
LowLevel::PtrClearTagId => unimplemented!(),
LowLevel::Alloca => unimplemented!(),
LowLevel::RefCountIncRcPtr => unimplemented!(),
LowLevel::RefCountDecRcPtr=> unimplemented!(),

View file

@ -1047,8 +1047,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[Ownership] {
PtrLoad => arena.alloc_slice_copy(&[owned]),
Alloca => arena.alloc_slice_copy(&[owned]),
PtrCast | RefCountIncRcPtr | RefCountDecRcPtr | RefCountIncDataPtr | RefCountDecDataPtr
| RefCountIsUnique => {
PtrClearTagId | PtrCast | RefCountIncRcPtr | RefCountDecRcPtr | RefCountIncDataPtr
| RefCountDecDataPtr | RefCountIsUnique => {
unreachable!("Only inserted *after* borrow checking: {:?}", op);
}
}

View file

@ -141,6 +141,7 @@ impl<'a> CodeGenHelp<'a> {
let jp_decref = JoinPointId(self.create_symbol(ident_ids, "jp_decref"));
HelperOp::DecRef(jp_decref)
}
ModifyRc::Free(_) => unreachable!("free should be handled by the backend directly"),
};
let mut ctx = Context {

View file

@ -118,6 +118,9 @@ pub fn refcount_stmt<'a>(
},
}
}
ModifyRc::Free(_) => {
unreachable!("free should be handled by the backend directly")
}
}
}

View file

@ -716,8 +716,10 @@ impl<'a, 'r> Ctx<'a, 'r> {
}
fn check_modify_rc(&mut self, rc: ModifyRc) {
use ModifyRc::*;
match rc {
ModifyRc::Inc(sym, _) | ModifyRc::Dec(sym) | ModifyRc::DecRef(sym) => {
Inc(sym, _) | Dec(sym) | DecRef(sym) | Free(sym) => {
// TODO: also check that sym layout needs refcounting
self.check_sym_exists(sym);
}

View file

@ -582,8 +582,9 @@ fn specialize_drops_stmt<'a, 'i>(
updated_stmt
}
}
ModifyRc::DecRef(_) => {
// Inlining has no point, since it doesn't decrement it's children
ModifyRc::DecRef(_) | ModifyRc::Free(_) => {
// These operations are not recursive (the children are not touched)
// so inlining is not useful
arena.alloc(Stmt::Refcounting(
*rc,
specialize_drops_stmt(
@ -1031,8 +1032,10 @@ fn specialize_union<'a, 'i>(
))
}),
arena.alloc(Stmt::Refcounting(
// TODO this could be replaced by a free if ever added to the IR.
ModifyRc::DecRef(*symbol),
// we know for sure that the allocation is unique at
// this point. Therefore we can free (or maybe reuse)
// without checking the refcount again.
ModifyRc::Free(*symbol),
continuation,
)),
)
@ -1101,8 +1104,10 @@ fn specialize_boxed<'a, 'i>(
// - free the box
|_, _, continuation| {
arena.alloc(Stmt::Refcounting(
// TODO can be replaced by free if ever added to the IR.
ModifyRc::DecRef(*symbol),
// we know for sure that the allocation is unique at
// this point. Therefore we can free (or maybe reuse)
// without checking the refcount again.
ModifyRc::Free(*symbol),
continuation,
))
},
@ -1682,8 +1687,8 @@ fn low_level_no_rc(lowlevel: &LowLevel) -> RC {
PtrLoad => RC::NoRc,
Alloca => RC::NoRc,
PtrCast | RefCountIncRcPtr | RefCountDecRcPtr | RefCountIncDataPtr | RefCountDecDataPtr
| RefCountIsUnique => {
PtrClearTagId | PtrCast | RefCountIncRcPtr | RefCountDecRcPtr | RefCountIncDataPtr
| RefCountDecDataPtr | RefCountIsUnique => {
unreachable!("Only inserted *after* borrow checking: {:?}", lowlevel);
}
}

View file

@ -1612,6 +1612,9 @@ pub enum ModifyRc {
/// sometimes we know we already dealt with the elements (e.g. by copying them all over
/// to a new list) and so we can just do a DecRef, which is much cheaper in such a case.
DecRef(Symbol),
/// Unconditionally deallocate the memory. For tag union that do pointer tagging (store the tag
/// id in the pointer) the backend has to clear the tag id!
Free(Symbol),
}
impl ModifyRc {
@ -1641,6 +1644,10 @@ impl ModifyRc {
.text("decref ")
.append(symbol_to_doc(alloc, symbol, pretty))
.append(";"),
Free(symbol) => alloc
.text("free ")
.append(symbol_to_doc(alloc, symbol, pretty))
.append(";"),
}
}
@ -1651,6 +1658,7 @@ impl ModifyRc {
Inc(symbol, _) => *symbol,
Dec(symbol) => *symbol,
DecRef(symbol) => *symbol,
Free(symbol) => *symbol,
}
}
}

View file

@ -19,7 +19,9 @@ use bumpalo::Bump;
use bumpalo::collections::vec::Vec;
use bumpalo::collections::CollectIn;
use roc_collections::{MutMap, MutSet};
use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_target::TargetInfo;
/**
Insert reset and reuse operations into the IR.
@ -29,6 +31,7 @@ pub fn insert_reset_reuse_operations<'a, 'i>(
arena: &'a Bump,
layout_interner: &'i STLayoutInterner<'a>,
home: ModuleId,
target_info: TargetInfo,
ident_ids: &'i mut IdentIds,
update_mode_ids: &'i mut UpdateModeIds,
procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
@ -42,6 +45,7 @@ pub fn insert_reset_reuse_operations<'a, 'i>(
let new_proc = insert_reset_reuse_operations_proc(
arena,
layout_interner,
target_info,
home,
ident_ids,
update_mode_ids,
@ -55,6 +59,7 @@ pub fn insert_reset_reuse_operations<'a, 'i>(
fn insert_reset_reuse_operations_proc<'a, 'i>(
arena: &'a Bump,
layout_interner: &'i STLayoutInterner<'a>,
target_info: TargetInfo,
home: ModuleId,
ident_ids: &'i mut IdentIds,
update_mode_ids: &'i mut UpdateModeIds,
@ -66,6 +71,7 @@ fn insert_reset_reuse_operations_proc<'a, 'i>(
}
let mut env = ReuseEnvironment {
target_info,
symbol_tags: MutMap::default(),
non_unique_symbols: MutSet::default(),
reuse_tokens: MutMap::default(),
@ -398,33 +404,83 @@ fn insert_reset_reuse_operations_stmt<'a, 'i>(
})
}
Stmt::Refcounting(rc, continuation) => {
let reuse_pair = match rc {
ModifyRc::Dec(symbol) | ModifyRc::DecRef(symbol)
if !environment.non_unique_symbols.contains(symbol) =>
{
enum SymbolIsUnique {
Never,
Always(Symbol),
MustCheck(Symbol),
}
let can_reuse = match rc {
ModifyRc::Dec(symbol) | ModifyRc::DecRef(symbol) => {
// can only reuse if the symbol is (potentially) unique
if environment.non_unique_symbols.contains(symbol) {
SymbolIsUnique::Never
} else {
SymbolIsUnique::MustCheck(*symbol)
}
}
ModifyRc::Free(symbol) => {
// a free'd symbol is guaranteed to be unique
SymbolIsUnique::Always(*symbol)
}
ModifyRc::Inc(_, _) => {
// an incremented symbol is never unique
SymbolIsUnique::Never
}
};
enum ResetOperation {
Reset,
ResetRef,
ClearTagId,
Nothing,
}
let reuse_pair = match can_reuse {
SymbolIsUnique::MustCheck(symbol) | SymbolIsUnique::Always(symbol) => {
// Get the layout of the symbol from where it is defined.
let layout_option = environment.get_symbol_layout(*symbol);
let layout_option = environment.get_symbol_layout(symbol);
// If the symbol is defined in the current proc, we can use the layout from the environment.
match layout_option.clone() {
match layout_option {
LayoutOption::Layout(layout) => {
match symbol_layout_reusability(
layout_interner,
environment,
symbol,
&symbol,
layout,
) {
Reuse::Reusable(union_layout) => {
let (reuse_symbol, reset_op) = match rc {
ModifyRc::Dec(_) => (
Symbol::new(home, ident_ids.gen_unique()),
ResetOperation::Reset,
),
ModifyRc::DecRef(_) => (
Symbol::new(home, ident_ids.gen_unique()),
ResetOperation::ResetRef,
),
ModifyRc::Free(_) => {
if union_layout
.stores_tag_id_in_pointer(environment.target_info)
{
(
Symbol::new(home, ident_ids.gen_unique()),
ResetOperation::ClearTagId,
)
} else {
(symbol, ResetOperation::Nothing)
}
}
_ => unreachable!(),
};
let reuse_token = ReuseToken {
symbol: Symbol::new(home, ident_ids.gen_unique()),
symbol: reuse_symbol,
update_mode_id: update_mode_ids.next_id(),
};
let dec_ref = match rc {
ModifyRc::Dec(_) => false,
ModifyRc::DecRef(_) => true,
_ => unreachable!(),
};
let owned_layout = **layout;
environment.push_reuse_token(
arena,
@ -432,7 +488,14 @@ fn insert_reset_reuse_operations_stmt<'a, 'i>(
reuse_token,
layout,
);
Some((layout, union_layout, *symbol, reuse_token, dec_ref))
Some((
owned_layout,
union_layout,
symbol,
reuse_token,
reset_op,
))
}
Reuse::Nonreusable => None,
}
@ -440,7 +503,7 @@ fn insert_reset_reuse_operations_stmt<'a, 'i>(
_ => None,
}
}
_ => {
SymbolIsUnique::Never => {
// We don't need to do anything for an inc or symbols known to be non-unique.
None
}
@ -457,7 +520,7 @@ fn insert_reset_reuse_operations_stmt<'a, 'i>(
);
// If we inserted a reuse token, we need to insert a reset reuse operation if the reuse token is consumed.
if let Some((layout, union_layout, symbol, reuse_token, dec_ref)) = reuse_pair {
if let Some((layout, union_layout, symbol, reuse_token, reset_op)) = reuse_pair {
let stack_reuse_token = environment
.peek_reuse_token(&get_reuse_layout_info(layout_interner, union_layout));
@ -471,29 +534,56 @@ fn insert_reset_reuse_operations_stmt<'a, 'i>(
_ => {
// The token we inserted is no longer on the stack, it must have been consumed.
// So we need to insert a reset operation.
let reset_expr = match dec_ref {
// A decref will be replaced by a resetref.
true => Expr::ResetRef {
symbol,
update_mode: reuse_token.update_mode_id,
},
// And a dec will be replaced by a reset.
false => Expr::Reset {
symbol,
update_mode: reuse_token.update_mode_id,
},
};
match reset_op {
ResetOperation::Reset => {
// a dec will be replaced by a reset.
let reset_expr = Expr::Reset {
symbol,
update_mode: reuse_token.update_mode_id,
};
// If we generate a reuse token, we no longer want to use the drop statement anymore. So we just return the reset expression.
// TODO verify if this works for both dec and decref.
// TODO reset probably decrements it's children. So we probably need to create a resetref that only does the token.
return arena.alloc(Stmt::Let(
reuse_token.symbol,
reset_expr,
// TODO not sure what the layout should be for a reset token. Currently it is the layout of the symbol.
*layout,
new_continuation,
));
return arena.alloc(Stmt::Let(
reuse_token.symbol,
reset_expr,
layout,
new_continuation,
));
}
ResetOperation::ResetRef => {
// a decref will be replaced by a resetref.
let reset_expr = Expr::ResetRef {
symbol,
update_mode: reuse_token.update_mode_id,
};
return arena.alloc(Stmt::Let(
reuse_token.symbol,
reset_expr,
layout,
new_continuation,
));
}
ResetOperation::ClearTagId => {
let reset_expr = Expr::Call(crate::ir::Call {
call_type: crate::ir::CallType::LowLevel {
op: LowLevel::PtrClearTagId,
update_mode: update_mode_ids.next_id(),
},
arguments: arena.alloc([symbol]),
});
return arena.alloc(Stmt::Let(
reuse_token.symbol,
reset_expr,
layout,
new_continuation,
));
}
ResetOperation::Nothing => {
// the reuse token is already in a valid state
return new_continuation;
}
}
}
}
}
@ -661,6 +751,7 @@ fn insert_reset_reuse_operations_stmt<'a, 'i>(
// Create a new environment for the body. With everything but the jump reuse tokens. As those should be given by the jump.
let mut first_pass_body_environment = ReuseEnvironment {
target_info: environment.target_info,
symbol_tags: environment.symbol_tags.clone(),
non_unique_symbols: environment.non_unique_symbols.clone(),
reuse_tokens: max_reuse_token_symbols.clone(),
@ -824,6 +915,7 @@ fn insert_reset_reuse_operations_stmt<'a, 'i>(
let (second_pass_body_environment, second_pass_body) = {
// Create a new environment for the body. With everything but the jump reuse tokens. As those should be given by the jump.
let mut body_environment = ReuseEnvironment {
target_info: environment.target_info,
symbol_tags: environment.symbol_tags.clone(),
non_unique_symbols: environment.non_unique_symbols.clone(),
reuse_tokens: used_reuse_tokens.clone(),
@ -889,7 +981,8 @@ fn insert_reset_reuse_operations_stmt<'a, 'i>(
let mut void_pointer_layout_symbols = Vec::new_in(arena);
// See what tokens we can get from the env, if none are available, use a void pointer.
// We process the tokens in reverse order, so that when we consume the tokens we last added, we consume the tokens that are most likely not to be null.
// We process the tokens in reverse order, so that when we consume the tokens we last added,
// we consume the tokens that are most likely not to be null.
let tokens = token_layouts_clone
.iter()
.rev()
@ -1002,7 +1095,7 @@ fn insert_reset_reuse_operations_stmt<'a, 'i>(
fn create_ptr_cast(arena: &Bump, symbol: Symbol) -> Expr {
Expr::Call(crate::ir::Call {
call_type: crate::ir::CallType::LowLevel {
op: roc_module::low_level::LowLevel::PtrCast,
op: LowLevel::PtrCast,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: Vec::from_iter_in([symbol], arena).into_bump_slice(),
@ -1092,8 +1185,9 @@ enum JoinPointReuseTokens<'a> {
RemainderSecond(Vec<'a, (&'a InLayout<'a>, TokenLayout)>),
}
#[derive(Default, Clone)]
#[derive(Clone)]
struct ReuseEnvironment<'a> {
target_info: TargetInfo,
symbol_tags: MutMap<Symbol, Tag>,
non_unique_symbols: MutSet<Symbol>,
reuse_tokens: ReuseTokens<'a>,