Merge remote-tracking branch 'origin/trunk' into list-str-capacity

This commit is contained in:
Folkert 2022-03-30 14:07:17 +02:00
commit c73c01aa34
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
116 changed files with 3265 additions and 1537 deletions

View file

@ -29,6 +29,7 @@ enum HelperOp {
Inc,
Dec,
DecRef(JoinPointId),
Reset,
Eq,
}
@ -76,17 +77,24 @@ pub struct CodeGenHelp<'a> {
home: ModuleId,
target_info: TargetInfo,
layout_isize: Layout<'a>,
union_refcount: UnionLayout<'a>,
specializations: Vec<'a, Specialization<'a>>,
debug_recursion_depth: usize,
}
impl<'a> CodeGenHelp<'a> {
pub fn new(arena: &'a Bump, target_info: TargetInfo, home: ModuleId) -> Self {
let layout_isize = Layout::usize(target_info);
// Refcount is a boxed isize. TODO: use the new Box layout when dev backends support it
let union_refcount = UnionLayout::NonNullableUnwrapped(arena.alloc([layout_isize]));
CodeGenHelp {
arena,
home,
target_info,
layout_isize: Layout::usize(target_info),
layout_isize,
union_refcount,
specializations: Vec::with_capacity_in(16, arena),
debug_recursion_depth: 0,
}
@ -144,6 +152,36 @@ impl<'a> CodeGenHelp<'a> {
(rc_stmt, ctx.new_linker_data)
}
pub fn call_reset_refcount(
&mut self,
ident_ids: &mut IdentIds,
layout: Layout<'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);
let arguments = self.arena.alloc([argument]);
let ret_layout = self.arena.alloc(layout);
let arg_layouts = self.arena.alloc([layout]);
let expr = Expr::Call(Call {
call_type: CallType::ByName {
name: proc_name,
ret_layout,
arg_layouts,
specialization_id: CallSpecId::BACKEND_DUMMY,
},
arguments,
});
(expr, ctx.new_linker_data)
}
/// Replace a generic `Lowlevel::Eq` call with a specialized helper proc.
/// The helper procs themselves are to be generated later with `generate_procs`
pub fn call_specialized_equals(
@ -197,6 +235,7 @@ impl<'a> CodeGenHelp<'a> {
let arg = self.replace_rec_ptr(ctx, layout);
match ctx.op {
Dec | DecRef(_) => (&LAYOUT_UNIT, self.arena.alloc([arg])),
Reset => (self.arena.alloc(layout), self.arena.alloc([layout])),
Inc => (&LAYOUT_UNIT, self.arena.alloc([arg, self.layout_isize])),
Eq => (&LAYOUT_BOOL, self.arena.alloc([arg, arg])),
}
@ -262,6 +301,10 @@ impl<'a> CodeGenHelp<'a> {
LAYOUT_UNIT,
refcount::refcount_generic(self, ident_ids, ctx, layout, Symbol::ARG_1),
),
Reset => (
layout,
refcount::refcount_reset_proc_body(self, ident_ids, ctx, layout, Symbol::ARG_1),
),
Eq => (
LAYOUT_BOOL,
equality::eq_generic(self, ident_ids, ctx, layout),
@ -275,7 +318,7 @@ impl<'a> CodeGenHelp<'a> {
let inc_amount = (self.layout_isize, ARG_2);
self.arena.alloc([roc_value, inc_amount])
}
Dec | DecRef(_) => self.arena.alloc([roc_value]),
Dec | DecRef(_) | Reset => self.arena.alloc([roc_value]),
Eq => self.arena.alloc([roc_value, (layout, ARG_2)]),
}
};
@ -318,6 +361,10 @@ impl<'a> CodeGenHelp<'a> {
arguments: self.arena.alloc([*layout]),
result: LAYOUT_UNIT,
},
HelperOp::Reset => ProcLayout {
arguments: self.arena.alloc([*layout]),
result: *layout,
},
HelperOp::DecRef(_) => unreachable!("No generated Proc for DecRef"),
HelperOp::Eq => ProcLayout {
arguments: self.arena.alloc([*layout, *layout]),

View file

@ -2,6 +2,7 @@ use bumpalo::collections::vec::Vec;
use roc_builtins::bitcode::IntWidth;
use roc_module::low_level::{LowLevel, LowLevel::*};
use roc_module::symbol::{IdentIds, Symbol};
use roc_target::PtrWidth;
use crate::code_gen_help::let_lowlevel;
use crate::ir::{
@ -13,9 +14,11 @@ use super::{CodeGenHelp, Context, HelperOp};
const LAYOUT_BOOL: Layout = Layout::Builtin(Builtin::Bool);
const LAYOUT_UNIT: Layout = Layout::UNIT;
const LAYOUT_PTR: Layout = Layout::RecursivePointer;
const LAYOUT_U32: Layout = Layout::Builtin(Builtin::Int(IntWidth::U32));
// TODO: Replace usages with root.union_refcount
const LAYOUT_PTR: Layout = Layout::RecursivePointer;
pub fn refcount_stmt<'a>(
root: &mut CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
@ -121,11 +124,192 @@ pub fn refcount_generic<'a>(
let runtime_layout = lambda_set.runtime_representation();
refcount_generic(root, ident_ids, ctx, runtime_layout, structure)
}
Layout::RecursivePointer => rc_todo(),
Layout::RecursivePointer => unreachable!(
"We should never call a refcounting helper on a RecursivePointer layout directly"
),
Layout::Boxed(_) => rc_todo(),
}
}
pub fn refcount_reset_proc_body<'a>(
root: &mut CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
ctx: &mut Context<'a>,
layout: Layout<'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 union_layout = match 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);
// Reset structure is unique. Decrement its children and return a pointer to the allocation.
let then_stmt = {
use UnionLayout::*;
let tag_layouts;
let mut null_id = None;
match union_layout {
NonRecursive(tags) => {
tag_layouts = tags;
}
Recursive(tags) => {
tag_layouts = tags;
}
NonNullableUnwrapped(field_layouts) => {
tag_layouts = root.arena.alloc([field_layouts]);
}
NullableWrapped {
other_tags: tags,
nullable_id,
} => {
null_id = Some(nullable_id);
tag_layouts = tags;
}
NullableUnwrapped {
other_fields,
nullable_id,
} => {
null_id = Some(nullable_id as TagIdIntType);
tag_layouts = root.arena.alloc([other_fields]);
}
};
let tag_id_layout = union_layout.tag_id_layout();
let tag_id_sym = root.create_symbol(ident_ids, "tag_id");
let tag_id_stmt = |next| {
Stmt::Let(
tag_id_sym,
Expr::GetTagId {
structure,
union_layout,
},
tag_id_layout,
next,
)
};
let rc_contents_stmt = refcount_union_contents(
root,
ident_ids,
ctx,
union_layout,
tag_layouts,
null_id,
structure,
tag_id_sym,
tag_id_layout,
Stmt::Ret(structure),
);
tag_id_stmt(root.arena.alloc(
//
rc_contents_stmt,
))
};
// 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, 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(0));
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,
};
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(
root,
ident_ids,
structure,
rc_ptr,
union_layout.stores_tag_id_in_pointer(root.target_info),
root.arena.alloc(rc_stmt),
)
};
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.
@ -893,9 +1077,9 @@ fn refcount_union_contents<'a>(
structure: Symbol,
tag_id_sym: Symbol,
tag_id_layout: Layout<'a>,
modify_union_stmt: Stmt<'a>,
next_stmt: Stmt<'a>,
) -> Stmt<'a> {
let jp_modify_union = JoinPointId(root.create_symbol(ident_ids, "jp_modify_union"));
let jp_contents_modified = JoinPointId(root.create_symbol(ident_ids, "jp_contents_modified"));
let mut tag_branches = Vec::with_capacity_in(tag_layouts.len() + 1, root.arena);
if let Some(id) = null_id {
@ -914,7 +1098,7 @@ fn refcount_union_contents<'a>(
// After refcounting the fields, jump to modify the union itself
// (Order is important, to avoid use-after-free for Dec)
let following = Stmt::Jump(jp_modify_union, &[]);
let following = Stmt::Jump(jp_contents_modified, &[]);
let fields_stmt = refcount_tag_fields(
root,
@ -943,9 +1127,9 @@ fn refcount_union_contents<'a>(
};
Stmt::Join {
id: jp_modify_union,
id: jp_contents_modified,
parameters: &[],
body: root.arena.alloc(modify_union_stmt),
body: root.arena.alloc(next_stmt),
remainder: root.arena.alloc(tag_id_switch),
}
}