Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exception stacks #28878

Merged
merged 16 commits into from
Oct 23, 2018
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion base/compiler/ssair/ir.jl
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ function is_relevant_expr(e::Expr)
:gc_preserve_begin, :gc_preserve_end,
:foreigncall, :isdefined, :copyast,
:undefcheck, :throw_undef_if_not,
:cfunction, :method,
:cfunction, :method, :pop_exc,
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to be terse here, I would prefer :pop_exception, also true for the rest of the the PR

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to change the symbol used in the AST.

For the C code I'm not so sure — names like jl_exception_stack_t and jl_exception_stack_bt_size get a bit tiresome after a while. When spelling it out is too verbose it can be useful to make a new noun, so I could compress all exc_stack into excstack in the C code and just be consistent in the use of that. I tried to avoid using ex as an abbreviation because that commonly stands for "expression".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vtjnash would you also prefer all the C code to also use exception_stack (at least for types and function/macro names)?

Obviously after massaging this PR for a few months I've gotten rather used to my "new noun" exc_stack and I think it's a good tradeoff (though possibly better a single word excstack). But renaming is easy and I can use exception_stack everywhere if there's a consensus.

#=legacy IR format support=# :gotoifnot, :return)
end

Expand Down
1 change: 1 addition & 0 deletions base/compiler/ssair/slot2ssa.jl
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,7 @@ function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::Do
end
elseif isexpr(stmt, :enter)
new_code[idx] = Expr(:enter, block_for_inst(cfg, stmt.args[1]))
ssavalmap[idx] = SSAValue(idx)
c42f marked this conversation as resolved.
Show resolved Hide resolved
elseif isexpr(stmt, :leave) || isexpr(stmt, :(=)) || isexpr(stmt, :return) ||
isexpr(stmt, :meta) || isa(stmt, NewvarNode)
new_code[idx] = stmt
Expand Down
3 changes: 2 additions & 1 deletion base/compiler/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const VALID_EXPR_HEADS = IdDict{Any,Any}(
:the_exception => 0:0,
:enter => 1:1,
:leave => 1:1,
:pop_exc => 1:1,
:inbounds => 1:1,
:boundscheck => 0:0,
:copyast => 1:1,
Expand Down Expand Up @@ -139,7 +140,7 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo, is_top_
validate_val!(x.args[1])
elseif head === :call || head === :invoke || head == :gc_preserve_end || head === :meta ||
head === :inbounds || head === :foreigncall || head === :cfunction ||
head === :const || head === :enter || head === :leave ||
head === :const || head === :enter || head === :leave || head == :pop_exc ||
head === :method || head === :global || head === :static_parameter ||
head === :new || head === :thunk || head === :simdloop ||
head === :throw_undef_if_not || head === :unreachable
Expand Down
24 changes: 24 additions & 0 deletions base/error.jl
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,30 @@ function catch_backtrace()
return _reformat_bt(bt[], bt2[])
end

"""
catch_stack(task=current_task(); [inclue_bt=true])

Get the stack of exceptions currently being handled. For nested catch blocks
there may be more than one current exception in which case the most recently
thrown exception is last in the stack. The stack is returned as a Vector of
`(exception,backtrace)` pairs, or a Vector of exceptions if `include_bt` is
false.

Explicitly passing `task` will return the current exception stack on an
arbitrary task. This is useful for inspecting tasks which have failed due to
uncaught exceptions.
"""
function catch_stack(task=current_task(); include_bt=true)
c42f marked this conversation as resolved.
Show resolved Hide resolved
raw = ccall(:jl_get_exc_stack, Any, (Any,Cint,Cint), task, include_bt, typemax(Cint))
formatted = Any[]
stride = include_bt ? 3 : 1
for i = reverse(1:stride:length(raw))
e = raw[i]
push!(formatted, include_bt ? (e,Base._reformat_bt(raw[i+1],raw[i+2])) : e)
end
formatted
end

## keyword arg lowering generates calls to this ##
function kwerr(kw, args::Vararg{Any,N}) where {N}
@_noinline_meta
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,7 @@ export
# errors
backtrace,
catch_backtrace,
catch_stack,
error,
rethrow,
retry,
Expand Down
10 changes: 7 additions & 3 deletions doc/src/devdocs/ast.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,18 +142,22 @@ These symbols appear in the `head` field of `Expr`s in lowered form.

* `the_exception`

Yields the caught exception inside a `catch` block. This is the value of the run time system variable
`jl_exception_in_transit`.
Yields the caught exception inside a `catch` block, as returned by `jl_current_exception()`.

* `enter`

Enters an exception handler (`setjmp`). `args[1]` is the label of the catch block to jump to on
error.
error. Yields a token which is consumed by `pop_exc`.

* `leave`

Pop exception handlers. `args[1]` is the number of handlers to pop.

* `pop_exc`

Pop the stack of current exceptions back to the state at the associated `enter` when leaving a
catch block. `args[1]` contains the token from the associated `enter`.

* `inbounds`

Controls turning bounds checks on or off. A stack is maintained; if the first argument of this
Expand Down
8 changes: 6 additions & 2 deletions src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ jl_sym_t *lambda_sym; jl_sym_t *assign_sym;
jl_sym_t *globalref_sym; jl_sym_t *do_sym;
jl_sym_t *method_sym; jl_sym_t *core_sym;
jl_sym_t *enter_sym; jl_sym_t *leave_sym;
jl_sym_t *pop_exc_sym;
jl_sym_t *exc_sym; jl_sym_t *error_sym;
jl_sym_t *new_sym; jl_sym_t *using_sym;
jl_sym_t *const_sym; jl_sym_t *thunk_sym;
Expand Down Expand Up @@ -342,6 +343,7 @@ void jl_init_frontend(void)
exc_sym = jl_symbol("the_exception");
enter_sym = jl_symbol("enter");
leave_sym = jl_symbol("leave");
pop_exc_sym = jl_symbol("pop_exc");
new_sym = jl_symbol("new");
const_sym = jl_symbol("const");
global_sym = jl_symbol("global");
Expand Down Expand Up @@ -887,7 +889,9 @@ jl_value_t *jl_parse_eval_all(const char *fname,
form = jl_pchar_to_string(fname, len);
result = jl_box_long(jl_lineno);
err = 1;
goto finally; // skip jl_restore_exc_stack
}
finally:
jl_get_ptls_states()->world_age = last_age;
jl_lineno = last_lineno;
jl_filename = last_filename;
Expand All @@ -899,7 +903,7 @@ jl_value_t *jl_parse_eval_all(const char *fname,
jl_rethrow();
else
jl_rethrow_other(jl_new_struct(jl_loaderror_type, form, result,
ptls->exception_in_transit));
jl_current_exception()));
}
JL_GC_POP();
return result;
Expand Down Expand Up @@ -1042,7 +1046,7 @@ static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule
margs[0] = jl_cstr_to_string("<macrocall>");
margs[1] = jl_fieldref(lno, 0); // extract and allocate line number
jl_rethrow_other(jl_new_struct(jl_loaderror_type, margs[0], margs[1],
ptls->exception_in_transit));
jl_current_exception()));
}
}
ptls->world_age = last_age;
Expand Down
8 changes: 0 additions & 8 deletions src/cgutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2568,14 +2568,6 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg
}
}

static Value *emit_exc_in_transit(jl_codectx_t &ctx)
{
Value *pexc_in_transit = emit_bitcast(ctx, ctx.ptlsStates, T_pprjlvalue);
Constant *offset = ConstantInt::getSigned(T_int32,
offsetof(jl_tls_states_t, exception_in_transit) / sizeof(void*));
return ctx.builder.CreateInBoundsGEP(pexc_in_transit, ArrayRef<Value*>(offset), "jl_exception_in_transit");
}

static void emit_signal_fence(jl_codectx_t &ctx)
{
#if defined(_CPU_ARM_) || defined(_CPU_AARCH64_)
Expand Down
85 changes: 58 additions & 27 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,10 @@ static Function *jlgetfield_func;
static Function *jlmethod_func;
static Function *jlgenericfunction_func;
static Function *jlenter_func;
static Function *jlcurrent_exception_func;
static Function *jlleave_func;
static Function *jl_restore_exc_stack_func;
static Function *jl_exc_stack_state_func;
static Function *jlegal_func;
static Function *jl_alloc_obj_func;
static Function *jl_newbits_func;
Expand Down Expand Up @@ -320,7 +323,6 @@ static Function *jlgetnthfieldchecked_func;
//static Function *jlsetnthfield_func;
static Function *jlgetcfunctiontrampoline_func;
#ifdef _OS_WINDOWS_
static Function *resetstkoflw_func;
#if defined(_CPU_X86_64_)
Function *juliapersonality_func;
#endif
Expand Down Expand Up @@ -783,9 +785,8 @@ static void emit_write_barrier(jl_codectx_t&, Value*, Value*);

static void jl_rethrow_with_add(const char *fmt, ...)
{
jl_ptls_t ptls = jl_get_ptls_states();
if (jl_typeis(ptls->exception_in_transit, jl_errorexception_type)) {
char *str = jl_string_data(jl_fieldref(ptls->exception_in_transit,0));
if (jl_typeis(jl_current_exception(), jl_errorexception_type)) {
char *str = jl_string_data(jl_fieldref(jl_current_exception(),0));
char buf[1024];
va_list args;
va_start(args, fmt);
Expand Down Expand Up @@ -1773,7 +1774,13 @@ static jl_value_t *static_apply_type(jl_codectx_t &ctx, const jl_cgval_t *args,
size_t last_age = jl_get_ptls_states()->world_age;
// call apply_type, but ignore errors. we know that will work in world 1.
jl_get_ptls_states()->world_age = 1;
jl_value_t *result = jl_apply_with_saved_exception_state(v, nargs, 1);
jl_value_t *result;
JL_TRY {
result = jl_apply(v, nargs);
}
JL_CATCH {
result = NULL;
}
jl_get_ptls_states()->world_age = last_age;
return result;
}
Expand Down Expand Up @@ -1855,7 +1862,13 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex, int sparams=tr
size_t last_age = jl_get_ptls_states()->world_age;
// here we know we're calling specific builtin functions that work in world 1.
jl_get_ptls_states()->world_age = 1;
jl_value_t *result = jl_apply_with_saved_exception_state(v, n+1, 1);
jl_value_t *result;
JL_TRY {
result = jl_apply(v, n+1);
}
JL_CATCH {
result = NULL;
}
jl_get_ptls_states()->world_age = last_age;
JL_GC_POP();
return result;
Expand Down Expand Up @@ -3761,6 +3774,12 @@ static void emit_stmtpos(jl_codectx_t &ctx, jl_value_t *expr, int ssaval_result)
ctx.builder.CreateCall(prepare_call(jlleave_func),
ConstantInt::get(T_int32, jl_unbox_long(args[0])));
}
else if (head == pop_exc_sym) {
jl_cgval_t exc_stack_state = emit_expr(ctx, jl_exprarg(expr, 0));
assert(exc_stack_state.V && exc_stack_state.V->getType() == T_size);
ctx.builder.CreateCall(prepare_call(jl_restore_exc_stack_func), exc_stack_state.V);
return;
}
else {
if (!jl_is_method(ctx.linfo->def.method)) {
// TODO: inference is invalid if this has an effect
Expand Down Expand Up @@ -3913,8 +3932,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval)
bnd = jl_get_binding_for_method_def(mod, (jl_sym_t*)mn);
}
JL_CATCH {
jl_ptls_t ptls = jl_get_ptls_states();
jl_value_t *e = ptls->exception_in_transit;
jl_value_t *e = jl_current_exception();
// errors. boo. root it somehow :(
bnd = jl_get_binding_wr(ctx.module, (jl_sym_t*)jl_gensym(), 1);
bnd->value = e;
Expand Down Expand Up @@ -3983,9 +4001,9 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval)
Value *val = emit_jlcall(ctx, jlnew_func, typ, &argv[1], nargs - 1);
return mark_julia_type(ctx, val, true, ty);
}
else if (head == exc_sym) { // *ptls->exception_in_transit
else if (head == exc_sym) {
return mark_julia_type(ctx,
ctx.builder.CreateLoad(emit_exc_in_transit(ctx), /*isvolatile*/true),
ctx.builder.CreateCall(prepare_call(jlcurrent_exception_func)),
true, jl_any_type);
}
else if (head == copyast_sym) {
Expand Down Expand Up @@ -4014,6 +4032,9 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval)
else if (head == leave_sym) {
jl_error("Expr(:leave) in value position");
}
else if (head == pop_exc_sym) {
jl_error("Expr(:pop_exc) in value position");
}
else if (head == enter_sym) {
jl_error("Expr(:enter) in value position");
}
Expand Down Expand Up @@ -6173,6 +6194,14 @@ static std::unique_ptr<Module> emit_function(

assert(jl_is_long(args[0]));
int lname = jl_unbox_long(args[0]);
// Save exception stack depth at enter for use in pop_exc

Value *exc_stack_state =
ctx.builder.CreateCall(prepare_call(jl_exc_stack_state_func));
assert(!ctx.ssavalue_assigned.at(cursor));
ctx.SAvalues.at(cursor) = jl_cgval_t(exc_stack_state, NULL, false,
(jl_value_t*)jl_ulong_type, NULL);
ctx.ssavalue_assigned.at(cursor) = true;
CallInst *sj = ctx.builder.CreateCall(prepare_call(except_enter_func));
// We need to mark this on the call site as well. See issue #6757
sj->setCanReturnTwice();
Expand All @@ -6182,21 +6211,7 @@ static std::unique_ptr<Module> emit_function(
handlr = BB[lname];
workstack.push_back(lname - 1);
come_from_bb[cursor + 1] = ctx.builder.GetInsertBlock();
#ifdef _OS_WINDOWS_
BasicBlock *cond_resetstkoflw_blk = BasicBlock::Create(jl_LLVMContext, "cond_resetstkoflw", f);
BasicBlock *resetstkoflw_blk = BasicBlock::Create(jl_LLVMContext, "resetstkoflw", f);
ctx.builder.CreateCondBr(isz, tryblk, cond_resetstkoflw_blk);
ctx.builder.SetInsertPoint(cond_resetstkoflw_blk);
ctx.builder.CreateCondBr(ctx.builder.CreateICmpEQ(
maybe_decay_untracked(literal_pointer_val(ctx, jl_stackovf_exception)),
ctx.builder.CreateLoad(emit_exc_in_transit(ctx), /*isvolatile*/true)),
resetstkoflw_blk, handlr);
ctx.builder.SetInsertPoint(resetstkoflw_blk);
ctx.builder.CreateCall(prepare_call(resetstkoflw_func), {});
ctx.builder.CreateBr(handlr);
#else
ctx.builder.CreateCondBr(isz, tryblk, handlr);
#endif
ctx.builder.SetInsertPoint(tryblk);
}
else {
Expand Down Expand Up @@ -6341,6 +6356,7 @@ static std::unique_ptr<Module> emit_function(
}
}
assert(found);
(void)found;
}
else {
terminator->removeFromParent();
Expand Down Expand Up @@ -7087,10 +7103,13 @@ static void init_julia_llvm_env(Module *m)
"jl_enter_handler", m);
add_named_global(jlenter_func, &jl_enter_handler);

jlcurrent_exception_func =
Function::Create(FunctionType::get(T_prjlvalue, false),
Function::ExternalLinkage,
"jl_current_exception", m);
add_named_global(jlcurrent_exception_func, &jl_current_exception);

#ifdef _OS_WINDOWS_
resetstkoflw_func = Function::Create(FunctionType::get(T_int32, false),
Function::ExternalLinkage, "_resetstkoflw", m);
add_named_global(resetstkoflw_func, &_resetstkoflw);
#if defined(_CPU_X86_64_)
juliapersonality_func = Function::Create(FunctionType::get(T_int32, true),
Function::ExternalLinkage, "__julia_personality", m);
Expand Down Expand Up @@ -7129,6 +7148,18 @@ static void init_julia_llvm_env(Module *m)
"jl_pop_handler", m);
add_named_global(jlleave_func, &jl_pop_handler);

jl_restore_exc_stack_func =
Function::Create(FunctionType::get(T_void, T_size, false),
Function::ExternalLinkage,
"jl_restore_exc_stack", m);
add_named_global(jl_restore_exc_stack_func, &jl_restore_exc_stack);

jl_exc_stack_state_func =
Function::Create(FunctionType::get(T_size, false),
Function::ExternalLinkage,
"jl_exc_stack_state", m);
add_named_global(jl_exc_stack_state_func, &jl_exc_stack_state);

std::vector<Type *> args_2vals_callee_rooted(0);
args_2vals_callee_rooted.push_back(PointerType::get(T_jlvalue, AddressSpace::CalleeRooted));
args_2vals_callee_rooted.push_back(PointerType::get(T_jlvalue, AddressSpace::CalleeRooted));
Expand Down
3 changes: 1 addition & 2 deletions src/dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -2337,7 +2337,6 @@ static void jl_finalize_serializer(jl_serializer_state *s)
void jl_typemap_rehash(jl_typemap_t *ml, int8_t offs);
static void jl_reinit_item(jl_value_t *v, int how, arraylist_t *tracee_list)
{
jl_ptls_t ptls = jl_get_ptls_states();
JL_TRY {
switch (how) {
case 1: { // rehash IdDict
Expand Down Expand Up @@ -2390,7 +2389,7 @@ static void jl_reinit_item(jl_value_t *v, int how, arraylist_t *tracee_list)
jl_printf(JL_STDERR, "WARNING: error while reinitializing value ");
jl_static_show(JL_STDERR, v);
jl_printf(JL_STDERR, ":\n");
jl_static_show(JL_STDERR, ptls->exception_in_transit);
jl_static_show(JL_STDERR, jl_current_exception());
jl_printf(JL_STDERR, "\n");
}
}
Expand Down
Loading