start resetref

This commit is contained in:
J.Teeuwissen 2023-03-29 11:57:13 +02:00
parent 61efec6fe2
commit d4ed6f7778
No known key found for this signature in database
GPG key ID: DB5F7A1ED8D478AD
13 changed files with 214 additions and 18 deletions

View file

@ -1590,6 +1590,10 @@ fn expr_spec<'a>(
Reset {
symbol,
update_mode,
}
| ResetRef {
symbol,
update_mode,
} => {
let tag_value_id = env.symbols[symbol];

View file

@ -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 => {}

View file

@ -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, ..

View file

@ -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>,

View file

@ -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 {

View file

@ -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);
}

View file

@ -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,

View file

@ -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

View file

@ -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.

View file

@ -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))
}

View file

@ -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;

View file

@ -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")
}
}
}

View file

@ -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,