mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 19:58:18 +00:00
free or reuse unconditionally when value is unique
This commit is contained in:
parent
e3ab023f62
commit
fc3004da58
21 changed files with 381 additions and 69 deletions
|
@ -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)?;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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!(),
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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>)>;
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -3141,6 +3141,7 @@ fn update<'a>(
|
|||
arena,
|
||||
&layout_interner,
|
||||
module_id,
|
||||
state.target_info,
|
||||
ident_ids,
|
||||
&mut update_mode_ids,
|
||||
&mut state.procedures,
|
||||
|
|
|
@ -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!(),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -118,6 +118,9 @@ pub fn refcount_stmt<'a>(
|
|||
},
|
||||
}
|
||||
}
|
||||
ModifyRc::Free(_) => {
|
||||
unreachable!("free should be handled by the backend directly")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue