mirror of
https://github.com/roc-lang/roc.git
synced 2025-11-25 13:36:37 +00:00
start resetref
This commit is contained in:
parent
61efec6fe2
commit
d4ed6f7778
13 changed files with 214 additions and 18 deletions
|
|
@ -1590,6 +1590,10 @@ fn expr_spec<'a>(
|
|||
Reset {
|
||||
symbol,
|
||||
update_mode,
|
||||
}
|
||||
| ResetRef {
|
||||
symbol,
|
||||
update_mode,
|
||||
} => {
|
||||
let tag_value_id = env.symbols[symbol];
|
||||
|
||||
|
|
|
|||
|
|
@ -1135,7 +1135,7 @@ trait Backend<'a> {
|
|||
self.set_last_seen(*sym, stmt);
|
||||
}
|
||||
}
|
||||
Expr::Reset { symbol, .. } => {
|
||||
Expr::Reset { symbol, .. } | Expr::ResetRef { symbol, .. } => {
|
||||
self.set_last_seen(*symbol, stmt);
|
||||
}
|
||||
Expr::EmptyArray => {}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ use crate::llvm::convert::{
|
|||
};
|
||||
use crate::llvm::expect::{clone_to_shared_memory, SharedMemoryPointer};
|
||||
use crate::llvm::refcounting::{
|
||||
build_reset, decrement_refcount_layout, increment_refcount_layout, PointerToRefcount,
|
||||
build_reset, build_resetref, decrement_refcount_layout, increment_refcount_layout,
|
||||
PointerToRefcount,
|
||||
};
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
|
|
@ -1226,6 +1227,74 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
|||
phi.as_basic_value()
|
||||
}
|
||||
}
|
||||
ResetRef {
|
||||
symbol,
|
||||
update_mode,
|
||||
} => {
|
||||
let bytes = update_mode.to_bytes();
|
||||
let update_var = UpdateModeVar(&bytes);
|
||||
let update_mode = func_spec_solutions
|
||||
.update_mode(update_var)
|
||||
.unwrap_or(UpdateMode::Immutable);
|
||||
|
||||
let (tag_ptr, layout) = load_symbol_and_layout(scope, symbol);
|
||||
let tag_ptr = tag_ptr.into_pointer_value();
|
||||
|
||||
// reset is only generated for union values
|
||||
let union_layout = match layout_interner.get(layout) {
|
||||
Layout::Union(ul) => ul,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let ctx = env.context;
|
||||
let then_block = ctx.append_basic_block(parent, "then_resetref");
|
||||
let else_block = ctx.append_basic_block(parent, "else_decref");
|
||||
let cont_block = ctx.append_basic_block(parent, "cont");
|
||||
|
||||
let refcount_ptr =
|
||||
PointerToRefcount::from_ptr_to_data(env, tag_pointer_clear_tag_id(env, tag_ptr));
|
||||
|
||||
let is_unique = match update_mode {
|
||||
UpdateMode::InPlace => env.context.bool_type().const_int(1, false),
|
||||
UpdateMode::Immutable => refcount_ptr.is_1(env),
|
||||
};
|
||||
|
||||
env.builder
|
||||
.build_conditional_branch(is_unique, then_block, else_block);
|
||||
|
||||
{
|
||||
// reset, when used on a unique reference, eagerly decrements the components of the
|
||||
// referenced value, and returns the location of the now-invalid cell
|
||||
env.builder.position_at_end(then_block);
|
||||
|
||||
let reset_function = build_resetref(env, layout_interner, layout_ids, union_layout);
|
||||
let call =
|
||||
env.builder
|
||||
.build_call(reset_function, &[tag_ptr.into()], "call_resetref");
|
||||
|
||||
call.set_call_convention(FAST_CALL_CONV);
|
||||
|
||||
let _ = call.try_as_basic_value();
|
||||
|
||||
env.builder.build_unconditional_branch(cont_block);
|
||||
}
|
||||
{
|
||||
// If reset is used on a shared, non-reusable reference, it behaves
|
||||
// like dec and returns NULL, which instructs reuse to behave like ctor
|
||||
env.builder.position_at_end(else_block);
|
||||
refcount_ptr.decrement(env, layout_interner, layout);
|
||||
env.builder.build_unconditional_branch(cont_block);
|
||||
}
|
||||
{
|
||||
env.builder.position_at_end(cont_block);
|
||||
let phi = env.builder.build_phi(tag_ptr.get_type(), "branch");
|
||||
|
||||
let null_ptr = tag_ptr.get_type().const_null();
|
||||
phi.add_incoming(&[(&tag_ptr, then_block), (&null_ptr, else_block)]);
|
||||
|
||||
phi.as_basic_value()
|
||||
}
|
||||
}
|
||||
|
||||
StructAtIndex {
|
||||
index, structure, ..
|
||||
|
|
|
|||
|
|
@ -1543,6 +1543,61 @@ pub fn build_reset<'a, 'ctx, 'env>(
|
|||
function
|
||||
}
|
||||
|
||||
pub fn build_resetref<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout_interner: &mut STLayoutInterner<'a>,
|
||||
layout_ids: &mut LayoutIds<'a>,
|
||||
union_layout: UnionLayout<'a>,
|
||||
) -> FunctionValue<'ctx> {
|
||||
// TODO update to not decref the children.
|
||||
todo!("update to not decref the children.");
|
||||
let mode = Mode::Dec;
|
||||
|
||||
let union_layout_in = layout_interner.insert(Layout::Union(union_layout));
|
||||
let layout_id = layout_ids.get(Symbol::DEC, &union_layout_in);
|
||||
let fn_name = layout_id.to_symbol_string(Symbol::DEC, &env.interns);
|
||||
let fn_name = format!("{}_resetref", fn_name);
|
||||
|
||||
let when_recursive = WhenRecursive::Loop(union_layout);
|
||||
let dec_function = build_rec_union(
|
||||
env,
|
||||
layout_interner,
|
||||
layout_ids,
|
||||
Mode::Dec,
|
||||
&when_recursive,
|
||||
union_layout,
|
||||
);
|
||||
|
||||
let function = match env.module.get_function(fn_name.as_str()) {
|
||||
Some(function_value) => function_value,
|
||||
None => {
|
||||
let block = env.builder.get_insert_block().expect("to be in a function");
|
||||
let di_location = env.builder.get_current_debug_location().unwrap();
|
||||
|
||||
let basic_type = basic_type_from_layout(env, layout_interner, union_layout_in);
|
||||
let function_value = build_header(env, basic_type, mode, &fn_name);
|
||||
|
||||
build_reuse_rec_union_help(
|
||||
env,
|
||||
layout_interner,
|
||||
layout_ids,
|
||||
&when_recursive,
|
||||
union_layout,
|
||||
function_value,
|
||||
dec_function,
|
||||
);
|
||||
|
||||
env.builder.position_at_end(block);
|
||||
env.builder
|
||||
.set_current_debug_location(env.context, di_location);
|
||||
|
||||
function_value
|
||||
}
|
||||
};
|
||||
|
||||
function
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_reuse_rec_union_help<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
|
|
|
|||
|
|
@ -1093,6 +1093,8 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
|
|||
|
||||
Expr::Reset { symbol: arg, .. } => self.expr_reset(*arg, sym, storage),
|
||||
|
||||
Expr::ResetRef { symbol: arg, .. } => self.expr_resetref(*arg, sym, storage),
|
||||
|
||||
Expr::RuntimeErrorFunction(_) => {
|
||||
todo!("Expression `{}`", expr.to_pretty(100, false))
|
||||
}
|
||||
|
|
@ -2011,6 +2013,34 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO update to not decrement children.
|
||||
fn expr_resetref(&mut self, argument: Symbol, ret_symbol: Symbol, ret_storage: &StoredValue) {
|
||||
let ident_ids = self
|
||||
.interns
|
||||
.all_ident_ids
|
||||
.get_mut(&self.env.module_id)
|
||||
.unwrap();
|
||||
|
||||
// Get an IR expression for the call to the specialized procedure
|
||||
let layout = self.storage.symbol_layouts[&argument];
|
||||
let (specialized_call_expr, new_specializations) = self
|
||||
.helper_proc_gen
|
||||
.call_reset_refcount(ident_ids, self.layout_interner, layout, argument);
|
||||
|
||||
// If any new specializations were created, register their symbol data
|
||||
for (spec_sym, spec_layout) in new_specializations.into_iter() {
|
||||
self.register_helper_proc(spec_sym, spec_layout, ProcSource::Helper);
|
||||
}
|
||||
|
||||
// Generate Wasm code for the IR call expression
|
||||
self.expr(
|
||||
ret_symbol,
|
||||
self.env.arena.alloc(specialized_call_expr),
|
||||
Layout::BOOL,
|
||||
ret_storage,
|
||||
);
|
||||
}
|
||||
|
||||
/// Generate a refcount helper procedure and return a pointer (table index) to it
|
||||
/// This allows it to be indirectly called from Zig code
|
||||
pub fn get_refcount_fn_index(&mut self, layout: InLayout<'a>, op: HelperOp) -> u32 {
|
||||
|
|
|
|||
|
|
@ -707,7 +707,7 @@ impl<'a> BorrowInfState<'a> {
|
|||
self.if_is_owned_then_own(z, *x);
|
||||
}
|
||||
|
||||
Reset { symbol: x, .. } => {
|
||||
Reset { symbol: x, .. } | ResetRef { symbol: x, .. } => {
|
||||
self.own_var(z);
|
||||
self.own_var(*x);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ pub enum HelperOp {
|
|||
Dec,
|
||||
DecRef(JoinPointId),
|
||||
Reset,
|
||||
// TODO update all usages
|
||||
ResetRef,
|
||||
Eq,
|
||||
}
|
||||
|
||||
|
|
@ -272,7 +274,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])),
|
||||
}
|
||||
|
|
@ -346,7 +348,7 @@ impl<'a> CodeGenHelp<'a> {
|
|||
Symbol::ARG_1,
|
||||
),
|
||||
),
|
||||
Reset => (
|
||||
Reset | ResetRef => (
|
||||
layout,
|
||||
refcount::refcount_reset_proc_body(
|
||||
self,
|
||||
|
|
@ -370,7 +372,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)]),
|
||||
}
|
||||
};
|
||||
|
|
@ -421,6 +423,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,
|
||||
|
|
|
|||
|
|
@ -464,6 +464,10 @@ impl<'a, 'r> Ctx<'a, 'r> {
|
|||
&Expr::Reset {
|
||||
symbol,
|
||||
update_mode: _,
|
||||
}
|
||||
| &Expr::ResetRef {
|
||||
symbol,
|
||||
update_mode: _,
|
||||
} => {
|
||||
self.check_sym_exists(symbol);
|
||||
None
|
||||
|
|
|
|||
|
|
@ -310,7 +310,14 @@ fn insert_reset_reuse_operations_stmt<'a, 'i>(
|
|||
update_mode_id: update_mode_ids.next_id(),
|
||||
};
|
||||
environment.push_reuse_token(&layout_clone, reuse_token);
|
||||
Some((layout_clone, *symbol, reuse_token))
|
||||
|
||||
let dec_ref = match rc {
|
||||
ModifyRc::Dec(_) => false,
|
||||
ModifyRc::DecRef(_) => true,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Some((layout_clone, *symbol, reuse_token, dec_ref))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
|
@ -328,7 +335,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, symbol, reuse_token)) = reuse_pair {
|
||||
if let Some((layout, symbol, reuse_token, dec_ref)) = reuse_pair {
|
||||
let stack_reuse_token = environment.peek_reuse_token(&layout);
|
||||
|
||||
match stack_reuse_token {
|
||||
|
|
@ -340,9 +347,17 @@ 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 = Expr::Reset {
|
||||
symbol,
|
||||
update_mode: reuse_token.update_mode_id,
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
// If we generate a reuse token, we no longer want to use the drop statement anymore. So we just return the reset expression.
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ pub fn occurring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) {
|
|||
result.extend(arguments.iter().copied());
|
||||
result.insert(*symbol);
|
||||
}
|
||||
Reset { symbol: x, .. } => {
|
||||
Reset { symbol: x, .. } | ResetRef { symbol: x, .. } => {
|
||||
result.insert(*x);
|
||||
}
|
||||
|
||||
|
|
@ -945,7 +945,7 @@ impl<'a, 'i> Context<'a, 'i> {
|
|||
self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||
}
|
||||
|
||||
EmptyArray | Literal(_) | Reset { .. } | RuntimeErrorFunction(_) => {
|
||||
EmptyArray | Literal(_) | Reset { .. } | ResetRef { .. } | RuntimeErrorFunction(_) => {
|
||||
// EmptyArray is always stack-allocated function pointers are persistent
|
||||
self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1935,6 +1935,13 @@ pub enum Expr<'a> {
|
|||
update_mode: UpdateModeId,
|
||||
},
|
||||
|
||||
// Just like Reset, but does not recursively decrement the children.
|
||||
// Used in reuse analysis to replace a decref with a resetRef to avoid decrementing when the dec ref didn't.
|
||||
ResetRef {
|
||||
symbol: Symbol,
|
||||
update_mode: UpdateModeId,
|
||||
},
|
||||
|
||||
RuntimeErrorFunction(&'a str),
|
||||
}
|
||||
|
||||
|
|
@ -2057,7 +2064,13 @@ impl<'a> Expr<'a> {
|
|||
"Reset {{ symbol: {:?}, id: {} }}",
|
||||
symbol, update_mode.id
|
||||
)),
|
||||
|
||||
ResetRef {
|
||||
symbol,
|
||||
update_mode,
|
||||
} => alloc.text(format!(
|
||||
"ResetRef {{ symbol: {:?}, id: {} }}",
|
||||
symbol, update_mode.id
|
||||
)),
|
||||
Struct(args) => {
|
||||
let it = args.iter().map(|s| symbol_to_doc(alloc, *s, pretty));
|
||||
|
||||
|
|
@ -7187,7 +7200,9 @@ fn substitute_in_expr<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
Reuse { .. } | Reset { .. } => unreachable!("reset/reuse have not been introduced yet"),
|
||||
Reuse { .. } | Reset { .. } | ResetRef { .. } => {
|
||||
unreachable!("reset/resetref/reuse have not been introduced yet")
|
||||
}
|
||||
|
||||
Struct(args) => {
|
||||
let mut did_change = false;
|
||||
|
|
|
|||
|
|
@ -427,8 +427,8 @@ impl VariableUsage {
|
|||
borrowed: MutSet::default(),
|
||||
}
|
||||
}
|
||||
Expr::Reuse { .. } | Expr::Reset { .. } => {
|
||||
unreachable!("Reset and reuse should not exist at this point")
|
||||
Expr::Reuse { .. } | Expr::Reset { .. } | Expr::ResetRef { .. } => {
|
||||
unreachable!("Reset(ref) and reuse should not exist at this point")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -326,6 +326,7 @@ fn insert_reset<'a>(
|
|||
| EmptyArray
|
||||
| Reuse { .. }
|
||||
| Reset { .. }
|
||||
| ResetRef { .. }
|
||||
| RuntimeErrorFunction(_) => break,
|
||||
}
|
||||
}
|
||||
|
|
@ -862,7 +863,7 @@ fn has_live_var_expr<'a>(expr: &'a Expr<'a>, needle: Symbol) -> bool {
|
|||
Expr::Reuse {
|
||||
symbol, arguments, ..
|
||||
} => needle == *symbol || arguments.iter().any(|s| *s == needle),
|
||||
Expr::Reset { symbol, .. } => needle == *symbol,
|
||||
Expr::Reset { symbol, .. } | Expr::ResetRef { symbol, .. } => needle == *symbol,
|
||||
Expr::ExprBox { symbol, .. } => needle == *symbol,
|
||||
Expr::ExprUnbox { symbol, .. } => needle == *symbol,
|
||||
Expr::RuntimeErrorFunction(_) => false,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue