add exception id to invoke/rethrow

This commit is contained in:
Folkert 2021-05-29 23:00:28 +02:00
parent 3636e18a18
commit 64576ddab5
8 changed files with 101 additions and 92 deletions

View file

@ -48,7 +48,9 @@ use roc_collections::all::{ImMap, MutSet};
use roc_module::ident::TagName; use roc_module::ident::TagName;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::{BranchInfo, CallType, JoinPointId, ModifyRc, TopLevelFunctionLayout, Wrapped}; use roc_mono::ir::{
BranchInfo, CallType, ExceptionId, JoinPointId, ModifyRc, TopLevelFunctionLayout, Wrapped,
};
use roc_mono::layout::{Builtin, InPlace, LambdaSet, Layout, LayoutIds, UnionLayout}; use roc_mono::layout::{Builtin, InPlace, LambdaSet, Layout, LayoutIds, UnionLayout};
use target_lexicon::CallingConvention; use target_lexicon::CallingConvention;
@ -1821,6 +1823,7 @@ fn invoke_roc_function<'a, 'ctx, 'env>(
closure_argument: Option<BasicValueEnum<'ctx>>, closure_argument: Option<BasicValueEnum<'ctx>>,
pass: &'a roc_mono::ir::Stmt<'a>, pass: &'a roc_mono::ir::Stmt<'a>,
fail: &'a roc_mono::ir::Stmt<'a>, fail: &'a roc_mono::ir::Stmt<'a>,
exception_id: ExceptionId,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let context = env.context; let context = env.context;
@ -1877,14 +1880,17 @@ fn invoke_roc_function<'a, 'ctx, 'env>(
context.struct_type(&[exception_ptr, selector_value], false) context.struct_type(&[exception_ptr, selector_value], false)
}; };
env.builder let exception_object = env.builder.build_cleanup_landing_pad(
.build_catch_all_landing_pad( &landing_pad_type,
&landing_pad_type, &BasicValueEnum::IntValue(context.i8_type().const_zero()),
&BasicValueEnum::IntValue(context.i8_type().const_zero()), context.i8_type().ptr_type(AddressSpace::Generic),
context.i8_type().ptr_type(AddressSpace::Generic), "invoke_landing_pad",
"invoke_landing_pad", );
)
.into_struct_value(); scope.insert(
exception_id.into_inner(),
(Layout::Struct(&[]), exception_object),
);
build_exp_stmt(env, layout_ids, scope, parent, fail); build_exp_stmt(env, layout_ids, scope, parent, fail);
} }
@ -1983,7 +1989,8 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
call, call,
layout, layout,
pass, pass,
fail: roc_mono::ir::Stmt::Rethrow, fail: roc_mono::ir::Stmt::Rethrow(_),
exception_id: _,
} => { } => {
// when the fail case is just Rethrow, there is no cleanup work to do // when the fail case is just Rethrow, there is no cleanup work to do
// so we can just treat this invoke as a normal call // so we can just treat this invoke as a normal call
@ -1997,6 +2004,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
layout, layout,
pass, pass,
fail, fail,
exception_id,
} => match call.call_type { } => match call.call_type {
CallType::ByName { CallType::ByName {
name, name,
@ -2017,6 +2025,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
None, None,
pass, pass,
fail, fail,
*exception_id,
) )
} }
@ -2047,11 +2056,9 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
} }
}, },
Rethrow => { Rethrow(exception_id) => {
cxa_rethrow_exception(env); let exception_object = scope.get(&exception_id.into_inner()).unwrap().1;
env.builder.build_resume(&exception_object);
// used in exception handling
env.builder.build_unreachable();
env.context.i64_type().const_zero().into() env.context.i64_type().const_zero().into()
} }

View file

@ -106,6 +106,7 @@ where
call, call,
pass, pass,
fail: _, fail: _,
exception_id: _,
} => { } => {
// for now, treat invoke as a normal call // for now, treat invoke as a normal call
self.build_expr(symbol, &Expr::Call(call.clone()), layout)?; self.build_expr(symbol, &Expr::Call(call.clone()), layout)?;
@ -486,6 +487,7 @@ where
call, call,
pass, pass,
fail: _, fail: _,
exception_id: _,
} => { } => {
// for now, treat invoke as a normal call // for now, treat invoke as a normal call
self.set_last_seen(*symbol, stmt); self.set_last_seen(*symbol, stmt);
@ -508,7 +510,7 @@ where
Stmt::Ret(sym) => { Stmt::Ret(sym) => {
self.set_last_seen(*sym, stmt); self.set_last_seen(*sym, stmt);
} }
Stmt::Rethrow => {} Stmt::Rethrow(_exception_id) => {}
Stmt::Refcounting(modify, following) => { Stmt::Refcounting(modify, following) => {
let sym = modify.get_symbol(); let sym = modify.get_symbol();

View file

@ -105,6 +105,7 @@ fn stmt_spec(
layout: call_layout, layout: call_layout,
pass, pass,
fail, fail,
exception_id: _,
} => { } => {
// a call that might throw an exception // a call that might throw an exception
@ -208,7 +209,7 @@ fn stmt_spec(
let jpid = env.join_points[id]; let jpid = env.join_points[id];
builder.add_jump(block, jpid, argument, ret_type_id) builder.add_jump(block, jpid, argument, ret_type_id)
} }
Rethrow | RuntimeError(_) => { Rethrow(_) | RuntimeError(_) => {
let type_id = layout_spec(builder, layout)?; let type_id = layout_spec(builder, layout)?;
builder.add_terminate(block, type_id) builder.add_terminate(block, type_id)

View file

@ -220,7 +220,7 @@ impl<'a> ParamMap<'a> {
} }
Refcounting(_, _) => unreachable!("these have not been introduced yet"), Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | Rethrow | Jump(_, _) | RuntimeError(_) => { Ret(_) | Rethrow(_) | Jump(_, _) | RuntimeError(_) => {
// these are terminal, do nothing // these are terminal, do nothing
} }
} }
@ -641,6 +641,7 @@ impl<'a> BorrowInfState<'a> {
layout: _, layout: _,
pass, pass,
fail, fail,
exception_id: _,
} => { } => {
self.collect_stmt(pass); self.collect_stmt(pass);
self.collect_stmt(fail); self.collect_stmt(fail);
@ -672,7 +673,7 @@ impl<'a> BorrowInfState<'a> {
} }
Refcounting(_, _) => unreachable!("these have not been introduced yet"), Refcounting(_, _) => unreachable!("these have not been introduced yet"),
Ret(_) | RuntimeError(_) | Rethrow => { Ret(_) | RuntimeError(_) | Rethrow(_) => {
// these are terminal, do nothing // these are terminal, do nothing
} }
} }

View file

@ -602,6 +602,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
layout, layout,
pass, pass,
fail, fail,
exception_id,
} => { } => {
let pass = expand_and_cancel(env, pass); let pass = expand_and_cancel(env, pass);
let fail = expand_and_cancel(env, fail); let fail = expand_and_cancel(env, fail);
@ -612,6 +613,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
layout: *layout, layout: *layout,
pass, pass,
fail, fail,
exception_id: *exception_id,
}; };
env.arena.alloc(stmt) env.arena.alloc(stmt)
@ -636,7 +638,7 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
env.arena.alloc(stmt) env.arena.alloc(stmt)
} }
Rethrow | Ret(_) | Jump(_, _) | RuntimeError(_) => stmt, Rethrow(_) | Ret(_) | Jump(_, _) | RuntimeError(_) => stmt,
} }
}; };

View file

@ -50,7 +50,7 @@ pub fn occuring_variables(stmt: &Stmt<'_>) -> (MutSet<Symbol>, MutSet<Symbol>) {
result.insert(*symbol); result.insert(*symbol);
} }
Rethrow => {} Rethrow(_) => {}
Refcounting(modify, cont) => { Refcounting(modify, cont) => {
let symbol = modify.get_symbol(); let symbol = modify.get_symbol();
@ -892,6 +892,7 @@ impl<'a> Context<'a> {
pass, pass,
fail, fail,
layout, layout,
exception_id,
} => { } => {
// live vars of the whole expression // live vars of the whole expression
let invoke_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default()); let invoke_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default());
@ -926,6 +927,7 @@ impl<'a> Context<'a> {
pass, pass,
fail, fail,
layout: *layout, layout: *layout,
exception_id: *exception_id,
}; };
let cont = self.arena.alloc(invoke); let cont = self.arena.alloc(invoke);
@ -1009,7 +1011,7 @@ impl<'a> Context<'a> {
} }
} }
Rethrow => (stmt, MutSet::default()), Rethrow(_) => (stmt, MutSet::default()),
Jump(j, xs) => { Jump(j, xs) => {
let empty = MutSet::default(); let empty = MutSet::default();
@ -1175,7 +1177,7 @@ pub fn collect_stmt(
vars vars
} }
Rethrow => vars, Rethrow(_) => vars,
RuntimeError(_) => vars, RuntimeError(_) => vars,
} }

View file

@ -855,6 +855,7 @@ pub enum Stmt<'a> {
layout: Layout<'a>, layout: Layout<'a>,
pass: &'a Stmt<'a>, pass: &'a Stmt<'a>,
fail: &'a Stmt<'a>, fail: &'a Stmt<'a>,
exception_id: ExceptionId,
}, },
Switch { Switch {
/// This *must* stand for an integer, because Switch potentially compiles to a jump table. /// This *must* stand for an integer, because Switch potentially compiles to a jump table.
@ -869,7 +870,7 @@ pub enum Stmt<'a> {
ret_layout: Layout<'a>, ret_layout: Layout<'a>,
}, },
Ret(Symbol), Ret(Symbol),
Rethrow, Rethrow(ExceptionId),
Refcounting(ModifyRc, &'a Stmt<'a>), Refcounting(ModifyRc, &'a Stmt<'a>),
/// a join point `join f <params> = <continuation> in remainder` /// a join point `join f <params> = <continuation> in remainder`
Join { Join {
@ -1361,7 +1362,7 @@ impl<'a> Stmt<'a> {
symbol, symbol,
call, call,
pass, pass,
fail: Stmt::Rethrow, fail: Stmt::Rethrow(_),
.. ..
} => alloc } => alloc
.text("let ") .text("let ")
@ -1394,7 +1395,7 @@ impl<'a> Stmt<'a> {
.append(symbol_to_doc(alloc, *symbol)) .append(symbol_to_doc(alloc, *symbol))
.append(";"), .append(";"),
Rethrow => alloc.text("unreachable;"), Rethrow(_) => alloc.text("unreachable;"),
Switch { Switch {
cond_symbol, cond_symbol,
@ -4684,12 +4685,14 @@ pub fn from_can<'a>(
arguments, arguments,
}; };
let exception_id = ExceptionId(env.unique_symbol());
let rest = Stmt::Invoke { let rest = Stmt::Invoke {
symbol: env.unique_symbol(), symbol: env.unique_symbol(),
call, call,
layout: bool_layout, layout: bool_layout,
pass: env.arena.alloc(rest), pass: env.arena.alloc(rest),
fail: env.arena.alloc(Stmt::Rethrow), fail: env.arena.alloc(Stmt::Rethrow(exception_id)),
exception_id,
}; };
with_hole( with_hole(
@ -5271,6 +5274,7 @@ fn substitute_in_stmt_help<'a>(
layout, layout,
pass, pass,
fail, fail,
exception_id,
} => { } => {
let opt_call = substitute_in_call(arena, call, subs); let opt_call = substitute_in_call(arena, call, subs);
let opt_pass = substitute_in_stmt_help(arena, pass, subs); let opt_pass = substitute_in_stmt_help(arena, pass, subs);
@ -5287,6 +5291,7 @@ fn substitute_in_stmt_help<'a>(
layout: *layout, layout: *layout,
pass, pass,
fail, fail,
exception_id: *exception_id,
})) }))
} else { } else {
None None
@ -5406,7 +5411,7 @@ fn substitute_in_stmt_help<'a>(
} }
} }
Rethrow => None, Rethrow(_) => None,
RuntimeError(_) => None, RuntimeError(_) => None,
} }
@ -5909,7 +5914,7 @@ fn force_thunk<'a>(
arguments: &[], arguments: &[],
}; };
Stmt::Let(assigned, Expr::Call(call), layout, env.arena.alloc(hole)) build_call(env, call, assigned, layout, env.arena.alloc(hole))
} }
fn let_empty_struct<'a>(assigned: Symbol, hole: &'a Stmt<'a>) -> Stmt<'a> { fn let_empty_struct<'a>(assigned: Symbol, hole: &'a Stmt<'a>) -> Stmt<'a> {
@ -6160,6 +6165,18 @@ fn can_throw_exception(call: &Call) -> bool {
} }
} }
/// Symbol that links an Invoke with a Rethrow
/// we'll assign the exception object to this symbol
/// so we can later rethrow the exception
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct ExceptionId(Symbol);
impl ExceptionId {
pub fn into_inner(self) -> Symbol {
self.0
}
}
fn build_call<'a>( fn build_call<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
call: Call<'a>, call: Call<'a>,
@ -6168,13 +6185,15 @@ fn build_call<'a>(
hole: &'a Stmt<'a>, hole: &'a Stmt<'a>,
) -> Stmt<'a> { ) -> Stmt<'a> {
if can_throw_exception(&call) { if can_throw_exception(&call) {
let fail = env.arena.alloc(Stmt::Rethrow); let id = ExceptionId(env.unique_symbol());
let fail = env.arena.alloc(Stmt::Rethrow(id));
Stmt::Invoke { Stmt::Invoke {
symbol: assigned, symbol: assigned,
call, call,
layout, layout,
fail, fail,
pass: hole, pass: hole,
exception_id: id,
} }
} else { } else {
Stmt::Let(assigned, Expr::Call(call), layout, hole) Stmt::Let(assigned, Expr::Call(call), layout, hole)
@ -7693,17 +7712,9 @@ where
Layout::Struct(_) => { Layout::Struct(_) => {
let function_symbol = lambda_set.set[0].0; let function_symbol = lambda_set.set[0].0;
// build the call let call = to_lowlevel_call(function_symbol, closure_data_symbol, function_layout);
Stmt::Let(
assigned, build_call(env, call, assigned, return_layout, env.arena.alloc(hole))
Expr::Call(to_lowlevel_call(
function_symbol,
closure_data_symbol,
function_layout,
)),
return_layout,
env.arena.alloc(hole),
)
} }
Layout::Builtin(Builtin::Int1) => { Layout::Builtin(Builtin::Int1) => {
let closure_tag_id_symbol = closure_data_symbol; let closure_tag_id_symbol = closure_data_symbol;
@ -7768,17 +7779,8 @@ where
let hole = Stmt::Jump(join_point_id, env.arena.alloc([assigned])); let hole = Stmt::Jump(join_point_id, env.arena.alloc([assigned]));
// build the call let call = to_lowlevel_call(*function_symbol, closure_data_symbol, function_layout);
let stmt = Stmt::Let( let stmt = build_call(env, call, assigned, return_layout, env.arena.alloc(hole));
assigned,
Expr::Call(to_lowlevel_call(
*function_symbol,
closure_data_symbol,
function_layout,
)),
return_layout,
env.arena.alloc(hole),
);
branches.push((i as u64, BranchInfo::None, stmt)); branches.push((i as u64, BranchInfo::None, stmt));
} }
@ -8036,21 +8038,18 @@ fn union_lambda_set_branch_help<'a>(
let full_layout = Layout::FunctionPointer(argument_layouts, env.arena.alloc(return_layout)); let full_layout = Layout::FunctionPointer(argument_layouts, env.arena.alloc(return_layout));
// build the call // build the call
Stmt::Let( let call = self::Call {
assigned, call_type: CallType::ByName {
Expr::Call(self::Call { name: function_symbol,
call_type: CallType::ByName { full_layout,
name: function_symbol, ret_layout: return_layout,
full_layout, arg_layouts: argument_layouts,
ret_layout: return_layout, specialization_id: env.next_call_specialization_id(),
arg_layouts: argument_layouts, },
specialization_id: env.next_call_specialization_id(), arguments: argument_symbols,
}, };
arguments: argument_symbols,
}), build_call(env, call, assigned, return_layout, hole)
return_layout,
hole,
)
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -8159,22 +8158,17 @@ fn enum_lambda_set_branch<'a>(
let full_layout = Layout::FunctionPointer(argument_layouts, env.arena.alloc(return_layout)); let full_layout = Layout::FunctionPointer(argument_layouts, env.arena.alloc(return_layout));
// build the call let call = self::Call {
Stmt::Let( call_type: CallType::ByName {
assigned, name: function_symbol,
Expr::Call(self::Call { full_layout,
call_type: CallType::ByName { ret_layout: return_layout,
name: function_symbol, arg_layouts: argument_layouts,
full_layout, specialization_id: env.next_call_specialization_id(),
ret_layout: return_layout, },
arg_layouts: argument_layouts, arguments: argument_symbols,
specialization_id: env.next_call_specialization_id(), };
}, build_call(env, call, assigned, return_layout, env.arena.alloc(hole))
arguments: argument_symbols,
}),
return_layout,
env.arena.alloc(hole),
)
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -8204,14 +8198,11 @@ where
let hole = Stmt::Jump(join_point_id, env.arena.alloc([result_symbol])); let hole = Stmt::Jump(join_point_id, env.arena.alloc([result_symbol]));
// build the call let call = to_lowlevel_call(*function_symbol, closure_data_symbol, function_layout);
let stmt = Stmt::Let( let stmt = build_call(
env,
call,
result_symbol, result_symbol,
Expr::Call(to_lowlevel_call(
*function_symbol,
closure_data_symbol,
function_layout,
)),
return_layout, return_layout,
env.arena.alloc(hole), env.arena.alloc(hole),
); );

View file

@ -102,9 +102,10 @@ fn insert_jumps<'a>(
}, },
fail, fail,
pass: Stmt::Ret(rsym), pass: Stmt::Ret(rsym),
exception_id,
.. ..
} if needle == *fsym && symbol == rsym => { } if needle == *fsym && symbol == rsym => {
debug_assert_eq!(fail, &&Stmt::Rethrow); debug_assert_eq!(fail, &&Stmt::Rethrow(*exception_id));
// replace the call and return with a jump // replace the call and return with a jump
@ -131,6 +132,7 @@ fn insert_jumps<'a>(
fail, fail,
pass, pass,
layout, layout,
exception_id,
} => { } => {
let opt_pass = insert_jumps(arena, pass, goal_id, needle); let opt_pass = insert_jumps(arena, pass, goal_id, needle);
let opt_fail = insert_jumps(arena, fail, goal_id, needle); let opt_fail = insert_jumps(arena, fail, goal_id, needle);
@ -145,6 +147,7 @@ fn insert_jumps<'a>(
layout: *layout, layout: *layout,
pass, pass,
fail, fail,
exception_id: *exception_id,
}; };
Some(arena.alloc(stmt)) Some(arena.alloc(stmt))
@ -238,7 +241,7 @@ fn insert_jumps<'a>(
None => None, None => None,
}, },
Rethrow => None, Rethrow(_) => None,
Ret(_) => None, Ret(_) => None,
Jump(_, _) => None, Jump(_, _) => None,
RuntimeError(_) => None, RuntimeError(_) => None,