Generate IR helper proc for list refcounting

This commit is contained in:
Brian Carroll 2021-12-30 15:43:54 +00:00
parent e612f51905
commit c5663e3538
4 changed files with 468 additions and 79 deletions

View file

@ -151,12 +151,14 @@ fn eq_struct<'a>(
};
let field2_stmt = |next| Stmt::Let(field2_sym, field2_expr, *layout, next);
let eq_call_expr = root.call_specialized_op(
ident_ids,
ctx,
*layout,
root.arena.alloc([field1_sym, field2_sym]),
);
let eq_call_expr = root
.call_specialized_op(
ident_ids,
ctx,
*layout,
root.arena.alloc([field1_sym, field2_sym]),
)
.unwrap();
let eq_call_name = format!("eq_call_{}", i);
let eq_call_sym = root.create_symbol(ident_ids, &eq_call_name);
@ -478,12 +480,14 @@ fn eq_tag_fields<'a>(
structure: operands[1],
};
let eq_call_expr = root.call_specialized_op(
ident_ids,
ctx,
*layout,
root.arena.alloc([field1_sym, field2_sym]),
);
let eq_call_expr = root
.call_specialized_op(
ident_ids,
ctx,
*layout,
root.arena.alloc([field1_sym, field2_sym]),
)
.unwrap();
let eq_call_name = format!("eq_call_{}", i);
let eq_call_sym = root.create_symbol(ident_ids, &eq_call_name);
@ -657,7 +661,10 @@ fn eq_list<'a>(
// Compare the two current elements
let eq_elems = root.create_symbol(ident_ids, "eq_elems");
let eq_elems_expr = root.call_specialized_op(ident_ids, ctx, *elem_layout, &[elem1, elem2]);
let eq_elems_args = root.arena.alloc([elem1, elem2]);
let eq_elems_expr = root
.call_specialized_op(ident_ids, ctx, *elem_layout, eq_elems_args)
.unwrap();
let eq_elems_stmt = |next| Stmt::Let(eq_elems, eq_elems_expr, LAYOUT_BOOL, next);

View file

@ -148,12 +148,14 @@ impl<'a> CodeGenHelp<'a> {
// Call helper proc, passing the Roc structure and constant amount
let call_result_empty = self.create_symbol(ident_ids, "call_result_empty");
let call_expr = self.call_specialized_op(
ident_ids,
&mut ctx,
layout,
arena.alloc([*structure, amount_sym]),
);
let call_expr = self
.call_specialized_op(
ident_ids,
&mut ctx,
layout,
arena.alloc([*structure, amount_sym]),
)
.unwrap();
let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following);
let rc_stmt = arena.alloc(amount_stmt(arena.alloc(call_stmt)));
@ -163,12 +165,9 @@ impl<'a> CodeGenHelp<'a> {
ModifyRc::Dec(structure) => {
// Call helper proc, passing the Roc structure
let call_result_empty = self.create_symbol(ident_ids, "call_result_empty");
let call_expr = self.call_specialized_op(
ident_ids,
&mut ctx,
layout,
arena.alloc([*structure]),
);
let call_expr = self
.call_specialized_op(ident_ids, &mut ctx, layout, arena.alloc([*structure]))
.unwrap();
let rc_stmt = arena.alloc(Stmt::Let(
call_result_empty,
@ -195,7 +194,8 @@ impl<'a> CodeGenHelp<'a> {
});
let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following);
let rc_stmt = arena.alloc(refcount::rc_ptr_from_struct(
// FIXME: `structure` is a pointer to the stack, not the heap!
let rc_stmt = arena.alloc(refcount::rc_ptr_from_data_ptr(
self,
ident_ids,
*structure,
@ -222,7 +222,9 @@ impl<'a> CodeGenHelp<'a> {
op: HelperOp::Eq,
};
let expr = self.call_specialized_op(ident_ids, &mut ctx, *layout, arguments);
let expr = self
.call_specialized_op(ident_ids, &mut ctx, *layout, arguments)
.unwrap();
(expr, ctx.new_linker_data)
}
@ -238,8 +240,8 @@ impl<'a> CodeGenHelp<'a> {
ident_ids: &mut IdentIds,
ctx: &mut Context<'a>,
called_layout: Layout<'a>,
arguments: &[Symbol],
) -> Expr<'a> {
arguments: &'a [Symbol],
) -> Option<Expr<'a>> {
use HelperOp::*;
debug_assert!(self.debug_recursion_depth < 10);
@ -263,23 +265,25 @@ impl<'a> CodeGenHelp<'a> {
}
};
Expr::Call(Call {
Some(Expr::Call(Call {
call_type: CallType::ByName {
name: proc_name,
ret_layout,
arg_layouts,
specialization_id: CallSpecId::BACKEND_DUMMY,
},
arguments: self.arena.alloc_slice_copy(arguments),
})
} else {
Expr::Call(Call {
arguments,
}))
} else if ctx.op == HelperOp::Eq {
Some(Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::Eq,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: self.arena.alloc_slice_copy(arguments),
})
arguments,
}))
} else {
None
}
}

View file

@ -1,9 +1,12 @@
use roc_builtins::bitcode::IntWidth;
use roc_module::low_level::LowLevel;
use roc_module::low_level::{LowLevel, LowLevel::*};
use roc_module::symbol::{IdentIds, Symbol};
use crate::ir::{BranchInfo, Call, CallType, Expr, Literal, Stmt, UpdateModeId};
use crate::layout::{Builtin, Layout};
use crate::code_gen_help::let_lowlevel;
use crate::ir::{
BranchInfo, Call, CallType, Expr, JoinPointId, Literal, Param, Stmt, UpdateModeId,
};
use crate::layout::{Builtin, Layout, UnionLayout};
use super::{CodeGenHelp, Context, HelperOp};
@ -16,7 +19,7 @@ const ARG_1: Symbol = Symbol::ARG_1;
const ARG_2: Symbol = Symbol::ARG_2;
pub fn refcount_generic<'a>(
root: &CodeGenHelp<'a>,
root: &mut CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
ctx: &mut Context<'a>,
layout: Layout<'a>,
@ -29,7 +32,10 @@ pub fn refcount_generic<'a>(
unreachable!("Not refcounted: {:?}", layout)
}
Layout::Builtin(Builtin::Str) => refcount_str(root, ident_ids, ctx),
Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) => rc_todo(),
Layout::Builtin(Builtin::List(elem_layout)) => {
refcount_list(root, ident_ids, ctx, &layout, elem_layout)
}
Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_)) => rc_todo(),
Layout::Struct(_) => rc_todo(),
Layout::Union(_) => rc_todo(),
Layout::LambdaSet(_) => {
@ -43,7 +49,7 @@ pub fn refcount_generic<'a>(
// 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(layout: &Layout) -> bool {
matches!(layout, Layout::Builtin(Builtin::Str))
matches!(layout, Layout::Builtin(Builtin::Str | Builtin::List(_)))
}
fn return_unit<'a>(root: &CodeGenHelp<'a>, ident_ids: &mut IdentIds) -> Stmt<'a> {
@ -52,11 +58,20 @@ fn return_unit<'a>(root: &CodeGenHelp<'a>, ident_ids: &mut IdentIds) -> Stmt<'a>
Stmt::Let(unit, Expr::Struct(&[]), LAYOUT_UNIT, ret_stmt)
}
fn refcount_args<'a>(root: &CodeGenHelp<'a>, ctx: &Context<'a>, structure: Symbol) -> &'a [Symbol] {
if ctx.op == HelperOp::Inc {
// second argument is always `amount`, passed down through the call stack
root.arena.alloc([structure, ARG_2])
} else {
root.arena.alloc([structure])
}
}
// Subtract a constant from a pointer to find the refcount
// Also does some type casting, so that we have different Symbols and Layouts
// for the 'pointer' and 'integer' versions of the address.
// This helps to avoid issues with the backends Symbol->Layout mapping.
pub fn rc_ptr_from_struct<'a>(
pub fn rc_ptr_from_data_ptr<'a>(
root: &CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
structure: Symbol,
@ -87,7 +102,7 @@ pub fn rc_ptr_from_struct<'a>(
op: LowLevel::NumSub,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: root.arena.alloc([structure, ptr_size_sym]),
arguments: root.arena.alloc([addr_sym, ptr_size_sym]),
});
let rc_addr_stmt = |next| Stmt::Let(rc_addr_sym, rc_addr_expr, root.layout_isize, next);
@ -116,14 +131,58 @@ pub fn rc_ptr_from_struct<'a>(
))
}
fn modify_refcount<'a>(
root: &CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
ctx: &mut Context<'a>,
rc_ptr: Symbol,
alignment: u32,
following: &'a Stmt<'a>,
) -> Stmt<'a> {
// Call the relevant Zig lowlevel to actually modify the refcount
let zig_call_result = root.create_symbol(ident_ids, "zig_call_result");
match ctx.op {
HelperOp::Inc => {
let zig_call_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::RefCountInc,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: root.arena.alloc([rc_ptr, ARG_2]),
});
Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, following)
}
HelperOp::Dec | HelperOp::DecRef => {
let alignment_sym = root.create_symbol(ident_ids, "alignment");
let alignment_expr = Expr::Literal(Literal::Int(alignment as i128));
let alignment_stmt = |next| Stmt::Let(alignment_sym, alignment_expr, LAYOUT_U32, next);
let zig_call_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::RefCountDec,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: root.arena.alloc([rc_ptr, alignment_sym]),
});
let zig_call_stmt = Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, following);
alignment_stmt(root.arena.alloc(
//
zig_call_stmt,
))
}
_ => unreachable!(),
}
}
/// Generate a procedure to modify the reference count of a Str
fn refcount_str<'a>(
root: &CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
ctx: &mut Context<'a>,
) -> Stmt<'a> {
let op = ctx.op;
let string = ARG_1;
let layout_isize = root.layout_isize;
@ -164,53 +223,33 @@ fn refcount_str<'a>(
// A pointer to the refcount value itself
let rc_ptr = root.create_symbol(ident_ids, "rc_ptr");
let alignment = root.ptr_size;
// Alignment constant (same value as ptr_size but different layout)
let alignment = root.create_symbol(ident_ids, "alignment");
let alignment_expr = Expr::Literal(Literal::Int(root.ptr_size as i128));
let alignment_stmt = |next| Stmt::Let(alignment, alignment_expr, LAYOUT_U32, next);
// Call the relevant Zig lowlevel to actually modify the refcount
let zig_call_result = root.create_symbol(ident_ids, "zig_call_result");
let zig_call_expr = match op {
HelperOp::Inc => Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::RefCountInc,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: root.arena.alloc([rc_ptr, ARG_2]),
}),
HelperOp::Dec | HelperOp::DecRef => Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::RefCountDec,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: root.arena.alloc([rc_ptr, alignment]),
}),
_ => unreachable!(),
};
let zig_call_stmt = |next| Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, next);
let ret_unit_stmt = return_unit(root, ident_ids);
let mod_rc_stmt = modify_refcount(
root,
ident_ids,
ctx,
rc_ptr,
alignment,
root.arena.alloc(ret_unit_stmt),
);
// Generate an `if` to skip small strings but modify big strings
let then_branch = elements_stmt(root.arena.alloc(
//
rc_ptr_from_struct(
rc_ptr_from_data_ptr(
root,
ident_ids,
elements,
rc_ptr,
root.arena.alloc(
//
alignment_stmt(root.arena.alloc(
//
zig_call_stmt(root.arena.alloc(
//
Stmt::Ret(zig_call_result),
)),
)),
mod_rc_stmt,
),
),
));
let if_stmt = Stmt::Switch {
cond_symbol: is_big_str,
cond_layout: LAYOUT_BOOL,
@ -234,3 +273,266 @@ fn refcount_str<'a>(
)),
))
}
fn refcount_list<'a>(
root: &mut CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
ctx: &mut Context<'a>,
layout: &Layout,
elem_layout: &'a Layout,
) -> Stmt<'a> {
let layout_isize = root.layout_isize;
let arena = root.arena;
// A "Box" layout (heap pointer to a single list element)
let box_union_layout = UnionLayout::NonNullableUnwrapped(arena.alloc([*elem_layout]));
let box_layout = Layout::Union(box_union_layout);
//
// Check if the list is empty
//
let len = root.create_symbol(ident_ids, "len");
let len_stmt = |next| let_lowlevel(arena, layout_isize, len, ListLen, &[ARG_1], 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, layout_isize, next);
// let is_empty = lowlevel Eq len zero
let is_empty = root.create_symbol(ident_ids, "is_empty");
let is_empty_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::Eq,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: root.arena.alloc([len, zero]),
});
let is_empty_stmt = |next| Stmt::Let(is_empty, is_empty_expr, LAYOUT_BOOL, next);
// get elements pointer
let elements = root.create_symbol(ident_ids, "elements");
let elements_expr = Expr::StructAtIndex {
index: 0,
field_layouts: arena.alloc([box_layout, layout_isize]),
structure: ARG_1,
};
let elements_stmt = |next| Stmt::Let(elements, elements_expr, box_layout, next);
//
// modify refcount of the list and its elements
//
let rc_ptr = root.create_symbol(ident_ids, "rc_ptr");
let alignment = layout.alignment_bytes(root.ptr_size);
let modify_elems = if elem_layout.is_refcounted() {
refcount_list_elems(
root,
ident_ids,
ctx,
elem_layout,
LAYOUT_UNIT,
box_union_layout,
len,
elements,
)
} else {
return_unit(root, ident_ids)
};
let modify_list = modify_refcount(
root,
ident_ids,
ctx,
rc_ptr,
alignment,
arena.alloc(modify_elems),
);
let modify_list_and_elems = elements_stmt(arena.alloc(
//
rc_ptr_from_data_ptr(root, ident_ids, elements, rc_ptr, arena.alloc(modify_list)),
));
//
// Do nothing if the list is empty
//
let if_stmt = Stmt::Switch {
cond_symbol: is_empty,
cond_layout: LAYOUT_BOOL,
branches: root
.arena
.alloc([(1, BranchInfo::None, return_unit(root, ident_ids))]),
default_branch: (BranchInfo::None, root.arena.alloc(modify_list_and_elems)),
ret_layout: LAYOUT_UNIT,
};
len_stmt(arena.alloc(
//
zero_stmt(arena.alloc(
//
is_empty_stmt(arena.alloc(
//
if_stmt,
)),
)),
))
}
#[allow(clippy::too_many_arguments)]
fn refcount_list_elems<'a>(
root: &mut CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
ctx: &mut Context<'a>,
elem_layout: &Layout<'a>,
ret_layout: Layout<'a>,
box_union_layout: UnionLayout<'a>,
length: Symbol,
elements: Symbol,
) -> Stmt<'a> {
use LowLevel::*;
let layout_isize = root.layout_isize;
let arena = root.arena;
// Cast to integer
let start = root.create_symbol(ident_ids, "start");
let start_stmt = |next| let_lowlevel(arena, layout_isize, start, PtrCast, &[elements], next);
//
// Loop initialisation
//
// let size = literal int
let size = root.create_symbol(ident_ids, "size");
let size_expr = Expr::Literal(Literal::Int(elem_layout.stack_size(root.ptr_size) as i128));
let size_stmt = |next| Stmt::Let(size, size_expr, layout_isize, next);
// let list_size = len * size
let list_size = root.create_symbol(ident_ids, "list_size");
let list_size_stmt = |next| {
let_lowlevel(
arena,
layout_isize,
list_size,
NumMul,
&[length, size],
next,
)
};
// let end = start + list_size
let end = root.create_symbol(ident_ids, "end");
let end_stmt = |next| let_lowlevel(arena, layout_isize, end, NumAdd, &[start, list_size], next);
//
// Loop name & parameter
//
let elems_loop = JoinPointId(root.create_symbol(ident_ids, "elems_loop"));
let addr = root.create_symbol(ident_ids, "addr");
let param_addr = Param {
symbol: addr,
borrow: false,
layout: layout_isize,
};
//
// if we haven't reached the end yet...
//
// Cast integer to box pointer
let box_ptr = root.create_symbol(ident_ids, "box");
let box_layout = Layout::Union(box_union_layout);
let box_stmt = |next| let_lowlevel(arena, box_layout, box_ptr, PtrCast, &[addr], next);
// Dereference the box pointer to get the current element
let elem = root.create_symbol(ident_ids, "elem");
let elem_expr = Expr::UnionAtIndex {
structure: box_ptr,
union_layout: box_union_layout,
tag_id: 0,
index: 0,
};
let elem_stmt = |next| Stmt::Let(elem, elem_expr, *elem_layout, next);
//
// Modify element refcount
//
let mod_elem_unit = root.create_symbol(ident_ids, "mod_elem_unit");
let mod_elem_args = refcount_args(root, ctx, elem);
let mod_elem_expr = root
.call_specialized_op(ident_ids, ctx, *elem_layout, mod_elem_args)
.unwrap();
let mod_elem_stmt = |next| Stmt::Let(mod_elem_unit, mod_elem_expr, LAYOUT_UNIT, next);
//
// Next loop iteration
//
let next_addr = root.create_symbol(ident_ids, "next_addr");
let next_addr_stmt =
|next| let_lowlevel(arena, layout_isize, next_addr, NumAdd, &[addr, size], next);
//
// Control flow
//
let is_end = root.create_symbol(ident_ids, "is_end");
let is_end_stmt = |next| let_lowlevel(arena, LAYOUT_BOOL, is_end, NumGte, &[addr, end], next);
let if_end_of_list = Stmt::Switch {
cond_symbol: is_end,
cond_layout: LAYOUT_BOOL,
ret_layout,
branches: root
.arena
.alloc([(1, BranchInfo::None, return_unit(root, ident_ids))]),
default_branch: (
BranchInfo::None,
arena.alloc(box_stmt(arena.alloc(
//
elem_stmt(arena.alloc(
//
mod_elem_stmt(arena.alloc(
//
next_addr_stmt(arena.alloc(
//
Stmt::Jump(elems_loop, arena.alloc([next_addr])),
)),
)),
)),
))),
),
};
let joinpoint_loop = Stmt::Join {
id: elems_loop,
parameters: arena.alloc([param_addr]),
body: arena.alloc(
//
is_end_stmt(
//
arena.alloc(if_end_of_list),
),
),
remainder: root
.arena
.alloc(Stmt::Jump(elems_loop, arena.alloc([start]))),
};
start_stmt(arena.alloc(
//
size_stmt(arena.alloc(
//
list_size_stmt(arena.alloc(
//
end_stmt(arena.alloc(
//
joinpoint_loop,
)),
)),
)),
))
}