Merge branch 'resetref' into Frame-Limited

This commit is contained in:
J.Teeuwissen 2023-04-03 10:02:31 +02:00
commit 87abe8df7d
No known key found for this signature in database
GPG key ID: DB5F7A1ED8D478AD
13 changed files with 387 additions and 16 deletions

View file

@ -31,6 +31,7 @@ pub enum HelperOp {
Dec,
DecRef(JoinPointId),
Reset,
ResetRef,
Eq,
}
@ -183,6 +184,38 @@ impl<'a> CodeGenHelp<'a> {
(expr, ctx.new_linker_data)
}
// TODO update to not decrement children.
pub fn call_resetref_refcount(
&mut self,
ident_ids: &mut IdentIds,
layout_interner: &mut STLayoutInterner<'a>,
layout: InLayout<'a>,
argument: Symbol,
) -> (Expr<'a>, Vec<'a, (Symbol, ProcLayout<'a>)>) {
let mut ctx = Context {
new_linker_data: Vec::new_in(self.arena),
recursive_union: None,
op: HelperOp::Reset,
};
let proc_name = self.find_or_create_proc(ident_ids, &mut ctx, layout_interner, layout);
let arguments = self.arena.alloc([argument]);
let ret_layout = layout;
let arg_layouts = self.arena.alloc([layout]);
let expr = Expr::Call(Call {
call_type: CallType::ByName {
name: LambdaName::no_niche(proc_name),
ret_layout,
arg_layouts,
specialization_id: CallSpecId::BACKEND_DUMMY,
},
arguments,
});
(expr, ctx.new_linker_data)
}
/// Generate a refcount increment procedure, *without* a Call expression.
/// *This method should be rarely used* - only when the proc is to be called from Zig.
/// Otherwise you want to generate the Proc and the Call together, using another method.
@ -262,7 +295,7 @@ impl<'a> CodeGenHelp<'a> {
let arg = self.replace_rec_ptr(ctx, layout_interner, layout);
match ctx.op {
Dec | DecRef(_) => (LAYOUT_UNIT, self.arena.alloc([arg])),
Reset => (layout, self.arena.alloc([layout])),
Reset | ResetRef => (layout, self.arena.alloc([layout])),
Inc => (LAYOUT_UNIT, self.arena.alloc([arg, self.layout_isize])),
Eq => (LAYOUT_BOOL, self.arena.alloc([arg, arg])),
}
@ -347,6 +380,17 @@ impl<'a> CodeGenHelp<'a> {
Symbol::ARG_1,
),
),
ResetRef => (
layout,
refcount::refcount_resetref_proc_body(
self,
ident_ids,
ctx,
layout_interner,
layout,
Symbol::ARG_1,
),
),
Eq => (
LAYOUT_BOOL,
equality::eq_generic(self, ident_ids, ctx, layout_interner, layout),
@ -360,7 +404,7 @@ impl<'a> CodeGenHelp<'a> {
let inc_amount = (self.layout_isize, ARG_2);
self.arena.alloc([roc_value, inc_amount])
}
Dec | DecRef(_) | Reset => self.arena.alloc([roc_value]),
Dec | DecRef(_) | Reset | ResetRef => self.arena.alloc([roc_value]),
Eq => self.arena.alloc([roc_value, (layout, ARG_2)]),
}
};
@ -411,6 +455,7 @@ impl<'a> CodeGenHelp<'a> {
niche: Niche::NONE,
},
HelperOp::DecRef(_) => unreachable!("No generated Proc for DecRef"),
HelperOp::ResetRef => unreachable!("No generated Proc for ResetRef"),
HelperOp::Eq => ProcLayout {
arguments: self.arena.alloc([layout, layout]),
result: LAYOUT_BOOL,

View file

@ -413,6 +413,206 @@ pub fn refcount_reset_proc_body<'a>(
rc_ptr_stmt
}
pub fn refcount_resetref_proc_body<'a>(
root: &mut CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
ctx: &mut Context<'a>,
layout_interner: &mut STLayoutInterner<'a>,
layout: InLayout<'a>,
structure: Symbol,
) -> Stmt<'a> {
let rc_ptr = root.create_symbol(ident_ids, "rc_ptr");
let rc = root.create_symbol(ident_ids, "rc");
let refcount_1 = root.create_symbol(ident_ids, "refcount_1");
let is_unique = root.create_symbol(ident_ids, "is_unique");
let addr = root.create_symbol(ident_ids, "addr");
let union_layout = match layout_interner.get(layout) {
Layout::Union(u) => u,
_ => unimplemented!("Reset is only implemented for UnionLayout"),
};
// Whenever we recurse into a child layout we will want to Decrement
ctx.op = HelperOp::Dec;
ctx.recursive_union = Some(union_layout);
let recursion_ptr = layout_interner.insert(Layout::RecursivePointer(layout));
// Reset structure is unique. Return a pointer to the allocation.
let then_stmt = {
let alignment = root.create_symbol(ident_ids, "alignment");
let alignment_expr = Expr::Literal(Literal::Int(
(layout_interner
.get(layout)
.alignment_bytes(layout_interner, root.target_info) as i128)
.to_ne_bytes(),
));
let alloc_addr = root.create_symbol(ident_ids, "alloc_addr");
let alloc_addr_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::NumSubWrap,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: root.arena.alloc([addr, alignment]),
});
Stmt::Let(
alignment,
alignment_expr,
root.layout_isize,
root.arena.alloc(
//
Stmt::Let(
alloc_addr,
alloc_addr_expr,
root.layout_isize,
root.arena.alloc(
//
Stmt::Ret(alloc_addr),
),
),
),
)
};
// Reset structure is not unique. Decrement it and return a NULL pointer.
let else_stmt = {
let decrement_unit = root.create_symbol(ident_ids, "decrement_unit");
let decrement_expr = root
.call_specialized_op(
ident_ids,
ctx,
layout_interner,
layout,
root.arena.alloc([structure]),
)
.unwrap();
let decrement_stmt = |next| Stmt::Let(decrement_unit, decrement_expr, LAYOUT_UNIT, next);
// Zero
let zero = root.create_symbol(ident_ids, "zero");
let zero_expr = Expr::Literal(Literal::Int(0i128.to_ne_bytes()));
let zero_stmt = |next| Stmt::Let(zero, zero_expr, root.layout_isize, next);
// Null pointer with union layout
let null = root.create_symbol(ident_ids, "null");
let null_stmt =
|next| let_lowlevel(root.arena, root.layout_isize, null, PtrCast, &[zero], next);
decrement_stmt(root.arena.alloc(
//
zero_stmt(root.arena.alloc(
//
null_stmt(root.arena.alloc(
//
Stmt::Ret(null),
)),
)),
))
};
let if_stmt = Stmt::Switch {
cond_symbol: is_unique,
cond_layout: LAYOUT_BOOL,
branches: root.arena.alloc([(1, BranchInfo::None, then_stmt)]),
default_branch: (BranchInfo::None, root.arena.alloc(else_stmt)),
ret_layout: layout,
};
// Uniqueness test
let is_unique_stmt = {
let_lowlevel(
root.arena,
LAYOUT_BOOL,
is_unique,
Eq,
&[rc, refcount_1],
root.arena.alloc(if_stmt),
)
};
// Constant for unique refcount
let refcount_1_encoded = match root.target_info.ptr_width() {
PtrWidth::Bytes4 => i32::MIN as i128,
PtrWidth::Bytes8 => i64::MIN as i128,
}
.to_ne_bytes();
let refcount_1_expr = Expr::Literal(Literal::Int(refcount_1_encoded));
let refcount_1_stmt = Stmt::Let(
refcount_1,
refcount_1_expr,
root.layout_isize,
root.arena.alloc(is_unique_stmt),
);
// Refcount value
let rc_expr = Expr::UnionAtIndex {
structure: rc_ptr,
tag_id: 0,
union_layout: root.union_refcount,
index: 0,
};
let rc_stmt = Stmt::Let(
rc,
rc_expr,
root.layout_isize,
root.arena.alloc(refcount_1_stmt),
);
// Refcount pointer
let rc_ptr_stmt = {
rc_ptr_from_data_ptr_help(
root,
ident_ids,
structure,
rc_ptr,
union_layout.stores_tag_id_in_pointer(root.target_info),
root.arena.alloc(rc_stmt),
addr,
recursion_ptr,
)
};
rc_ptr_stmt
}
// Check if refcounting is implemented yet. In the long term, this will be deleted.
// In the short term, it helps us to skip refcounting and let it leak, so we can make
// progress incrementally. Kept in sync with generate_procs using assertions.
pub fn is_rc_implemented_yet<'a, I>(interner: &I, layout: InLayout<'a>) -> bool
where
I: LayoutInterner<'a>,
{
use UnionLayout::*;
match interner.get(layout) {
Layout::Builtin(Builtin::List(elem_layout)) => is_rc_implemented_yet(interner, elem_layout),
Layout::Builtin(_) => true,
Layout::Struct { field_layouts, .. } => field_layouts
.iter()
.all(|l| is_rc_implemented_yet(interner, *l)),
Layout::Union(union_layout) => match union_layout {
NonRecursive(tags) => tags
.iter()
.all(|fields| fields.iter().all(|l| is_rc_implemented_yet(interner, *l))),
Recursive(tags) => tags
.iter()
.all(|fields| fields.iter().all(|l| is_rc_implemented_yet(interner, *l))),
NonNullableUnwrapped(fields) => {
fields.iter().all(|l| is_rc_implemented_yet(interner, *l))
}
NullableWrapped { other_tags, .. } => other_tags
.iter()
.all(|fields| fields.iter().all(|l| is_rc_implemented_yet(interner, *l))),
NullableUnwrapped { other_fields, .. } => other_fields
.iter()
.all(|l| is_rc_implemented_yet(interner, *l)),
},
Layout::LambdaSet(lambda_set) => is_rc_implemented_yet(interner, lambda_set.representation),
Layout::RecursivePointer(_) => true,
Layout::Boxed(_) => true,
}
}
fn rc_return_stmt<'a>(
root: &CodeGenHelp<'a>,
ident_ids: &mut IdentIds,