From 31e76c2dc856af4cd05db7780c25c29ed3245b27 Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Sat, 21 Jul 2018 17:04:13 +1000 Subject: [PATCH] Add exception stack system to runtime Runtime ------- * An exception stack type, `jl_exc_stack_t` and associated manipulation functions has been added. Conceptually this stores a stack of (exception,backtrace) pairs. It's stored in a contiguous buffer so we can avoid reallocating when throwing and catching the same exception or set of exceptions repeatedly. Space for the exception and backtrace is allocated on demand inside `throw_internal`, after changing GC mode. Several variations were tried for allocating this sgorage, including allocating up front with malloc on the thread local state and copying during task switching. Keeping everything on the task seemed the simplest as it involves the least copying and keeps track of the exception stack buffers in a unified way. * The exception in transit is no longer a single pointer in the thread local storage. It's stored in `ptls->current_task->exc_stack` instead, along with associated backtrace. * `jl_current_exception()` is now the method to retreive the current exception from within a `JL_CATCH` dynamic context. * Several places where manual restoration of the exception and backtrace was done are no longer necessary because the stack system handles these automatically. This code is removed, including `jl_apply_with_saved_exception_state`. * `jl_eh_restore_state` has become non-inline. It seemed good to get this out of the public header. * `jl_sig_throw` is now used with `jl_throw_in_ctx` from signal handlers, to make the special circumstances clear and to avoid conflation with rethrow which now simply rethrows the existing exception stack. * Make rethrow outside a catch block into an error. * Use `ptls->previous_exception` to support `jl_exception_occurred` in embedding API. * finally lowering no longer includes a `foreigncall`, so expressions using finally can now be interpreted. Interpreter / Codegen --------------------- Mostly small changes here, to track the `:enter` and `:pop_exc` association with the SSAValue token. The token SSAValue slot is ideal here for storing the state of the exception stack at the `:enter`. GC -- Integrate exception and raw backtrace scanning as a special case into GC mark loop. This is necessary now that Task objects can refer to exception and backtrace data in raw form. --- src/ast.c | 6 ++- src/cgutils.cpp | 8 ---- src/codegen.cpp | 62 ++++++++++++++++++++++++----- src/dump.c | 3 +- src/gc.c | 75 ++++++++++++++++++++++++++--------- src/gc.h | 28 ++++++++----- src/gf.c | 14 ++++++- src/init.c | 8 ++-- src/interpreter.c | 12 ++++-- src/jlapi.c | 21 +++++++--- src/julia.h | 17 ++++++-- src/julia_internal.h | 23 ++++++++++- src/julia_threads.h | 14 +++++-- src/method.c | 8 ++-- src/rtutils.c | 94 ++++++++++++++++++++++++++++++-------------- src/signals-mach.c | 4 +- src/signals-unix.c | 6 ++- src/signals-win.c | 6 +-- src/stackwalk.c | 36 +++++++++++------ src/staticdata.c | 3 +- src/task.c | 69 ++++++++++++++++++++++++-------- src/threading.c | 3 +- src/toplevel.c | 2 +- test/channels.jl | 5 ++- ui/repl.c | 26 +++--------- 25 files changed, 386 insertions(+), 167 deletions(-) diff --git a/src/ast.c b/src/ast.c index 752aa805c6954..8e98f0f19e87b 100644 --- a/src/ast.c +++ b/src/ast.c @@ -889,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; @@ -901,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; @@ -1044,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(""); 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; diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 248cbe4c902e2..83a1673eea9f9 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -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(offset), "jl_exception_in_transit"); -} - static void emit_signal_fence(jl_codectx_t &ctx) { #if defined(_CPU_ARM_) || defined(_CPU_AARCH64_) diff --git a/src/codegen.cpp b/src/codegen.cpp index ccf5cf1acaf88..6a70ab3d88cf8 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -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; @@ -782,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); @@ -1772,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; } @@ -1854,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; @@ -3761,7 +3775,9 @@ static void emit_stmtpos(jl_codectx_t &ctx, jl_value_t *expr, int ssaval_result) ConstantInt::get(T_int32, jl_unbox_long(args[0]))); } else if (head == pop_exc_sym) { - // FIXME + 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 { @@ -3916,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; @@ -3986,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) { @@ -6179,6 +6194,14 @@ static std::unique_ptr 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(); @@ -6333,6 +6356,7 @@ static std::unique_ptr emit_function( } } assert(found); + (void)found; } else { terminator->removeFromParent(); @@ -7079,6 +7103,12 @@ 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_ #if defined(_CPU_X86_64_) juliapersonality_func = Function::Create(FunctionType::get(T_int32, true), @@ -7118,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 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)); diff --git a/src/dump.c b/src/dump.c index 773f943b43c70..197748dd05f44 100644 --- a/src/dump.c +++ b/src/dump.c @@ -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 @@ -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"); } } diff --git a/src/gc.c b/src/gc.c index 9c20d8a1e5f3e..12e50d6e09a49 100644 --- a/src/gc.c +++ b/src/gc.c @@ -234,7 +234,7 @@ static void run_finalizer(jl_ptls_t ptls, jl_value_t *o, jl_value_t *ff) } JL_CATCH { jl_printf(JL_STDERR, "error in running finalizer: "); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); jl_printf(JL_STDERR, "\n"); } } @@ -313,27 +313,11 @@ static void jl_gc_run_finalizers_in_list(jl_ptls_t ptls, arraylist_t *list) jl_value_t **items = (jl_value_t**)list->items; size_t len = list->len; JL_UNLOCK_NOGC(&finalizers_lock); - // from jl_apply_with_saved_exception_state; to hoist state saving out of the loop - jl_value_t *exc = ptls->exception_in_transit; - jl_array_t *bt = NULL; - jl_array_t *bt2 = NULL; - JL_GC_PUSH3(&exc, &bt, &bt2); - if (ptls->bt_size > 0) { - jl_get_backtrace(&bt, &bt2); - ptls->bt_size = 0; - } // run finalizers in reverse order they were added, so lower-level finalizers run last for (size_t i = len-4; i >= 2; i -= 2) run_finalizer(ptls, items[i], items[i + 1]); // first entries were moved last to make room for GC frame metadata run_finalizer(ptls, items[len-2], items[len-1]); - ptls->exception_in_transit = exc; - if (bt != NULL) { - // This is sufficient because bt2 roots the managed values - memcpy(ptls->bt_data, bt->data, jl_array_len(bt) * sizeof(void*)); - ptls->bt_size = jl_array_len(bt); - } - JL_GC_POP(); // matches the jl_gc_push_arraylist above JL_GC_POP(); } @@ -1844,6 +1828,8 @@ STATIC_INLINE int gc_mark_scan_obj32(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, gc_mar goto obj32; \ case GC_MARK_L_stack: \ goto stack; \ + case GC_MARK_L_excstack: \ + goto excstack; \ case GC_MARK_L_module_binding: \ goto module_binding; \ default: \ @@ -1929,6 +1915,7 @@ JL_EXTENSION NOINLINE void gc_mark_loop(jl_ptls_t ptls, jl_gc_mark_sp_t sp) gc_mark_label_addrs[GC_MARK_L_obj16] = gc_mark_laddr(obj16); gc_mark_label_addrs[GC_MARK_L_obj32] = gc_mark_laddr(obj32); gc_mark_label_addrs[GC_MARK_L_stack] = gc_mark_laddr(stack); + gc_mark_label_addrs[GC_MARK_L_excstack] = gc_mark_laddr(excstack); gc_mark_label_addrs[GC_MARK_L_module_binding] = gc_mark_laddr(module_binding); return; } @@ -2081,6 +2068,46 @@ stack: { } } +excstack: { + // Scan an exception stack + gc_mark_exc_stack_t *stackitr = gc_pop_markdata(&sp, gc_mark_exc_stack_t); + jl_exc_stack_t *exc_stack = stackitr->s; + size_t itr = stackitr->itr; + size_t i = stackitr->i; + while (itr > 0) { + size_t bt_size = jl_exc_stack_bt_size(exc_stack, itr); + uintptr_t *bt_data = jl_exc_stack_bt_data(exc_stack, itr); + while (i+2 < bt_size) { + if (bt_data[i] != (uintptr_t)-1) { + i++; + continue; + } + // found an interpreter frame to mark + new_obj = (jl_value_t*)bt_data[i+1]; + uintptr_t nptr = 0; + if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) { + stackitr->i = i + 3; + stackitr->itr = itr; + gc_repush_markdata(&sp, gc_mark_exc_stack_t); + goto mark; + } + i += 3; + } + // mark the exception + new_obj = jl_exc_stack_exception(exc_stack, itr); + itr = jl_exc_stack_next(exc_stack, itr); + i = 0; + uintptr_t nptr = 0; + if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) { + stackitr->i = i; + stackitr->itr = itr; + gc_repush_markdata(&sp, gc_mark_exc_stack_t); + goto mark; + } + } + goto pop; + } + module_binding: { // Scan a module. see `gc_mark_binding_t` // Other fields of the module will be scanned after the bindings are scanned @@ -2340,13 +2367,22 @@ mark: { gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(stack), &stackdata, sizeof(stackdata), 1); } + if (ta->exc_stack) { + gc_setmark_buf_(ptls, ta->exc_stack, bits, sizeof(jl_exc_stack_t) + + sizeof(uintptr_t)*ta->exc_stack->reserved_size); + gc_mark_exc_stack_t stackdata = {ta->exc_stack, ta->exc_stack->top, 0}; + gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(excstack), + &stackdata, sizeof(stackdata), 1); + } const jl_datatype_layout_t *layout = jl_task_type->layout; assert(layout->fielddesc_type == 0); size_t nfields = layout->nfields; assert(nfields > 0); obj8_begin = (jl_fielddesc8_t*)jl_dt_layout_fields(layout); obj8_end = obj8_begin + nfields; - gc_mark_obj8_t markdata = {new_obj, obj8_begin, obj8_end, (9 << 2) | 1 | bits}; + // assume tasks always reference young objects: set lowest bit + uintptr_t nptr = (9 << 2) | 1 | bits; + gc_mark_obj8_t markdata = {new_obj, obj8_begin, obj8_end, nptr}; gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(obj8), &markdata, sizeof(markdata), 0); obj8 = (gc_mark_obj8_t*)sp.data; @@ -2441,7 +2477,8 @@ static void jl_gc_queue_thread_local(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp { gc_mark_queue_obj(gc_cache, sp, ptls2->current_task); gc_mark_queue_obj(gc_cache, sp, ptls2->root_task); - gc_mark_queue_obj(gc_cache, sp, ptls2->exception_in_transit); + if (ptls2->previous_exception) + gc_mark_queue_obj(gc_cache, sp, ptls2->previous_exception); } // mark the initial root set diff --git a/src/gc.h b/src/gc.h index b3d5af52d581b..9a46a0959b1e8 100644 --- a/src/gc.h +++ b/src/gc.h @@ -84,19 +84,21 @@ enum { GC_MARK_L_obj16, GC_MARK_L_obj32, GC_MARK_L_stack, + GC_MARK_L_excstack, GC_MARK_L_module_binding, _GC_MARK_L_MAX }; -/** - * The `nptr` member of marking data records the number of pointers slots referenced by - * an object to be used in the full collection heuristics as well as whether the object - * references young objects. - * `nptr >> 2` is the number of pointers fields referenced by the object. - * The lowest bit of `nptr` is set if the object references young object. - * The 2nd lowest bit of `nptr` is the GC old bits of the object after marking. - * A `0x3` in the low bits means that the object needs to be in the remset. - */ +// The following structs (`gc_mark_*_t`) contain iterator state used for the +// scanning of various object types. +// +// The `nptr` member records the number of pointers slots referenced by +// an object to be used in the full collection heuristics as well as whether the object +// references young objects. +// `nptr >> 2` is the number of pointers fields referenced by the object. +// The lowest bit of `nptr` is set if the object references young object. +// The 2nd lowest bit of `nptr` is the GC old bits of the object after marking. +// A `0x3` in the low bits means that the object needs to be in the remset. // An generic object that's marked and needs to be scanned // The metadata might need update too (depend on the PC) @@ -149,6 +151,13 @@ typedef struct { uintptr_t ub; } gc_mark_stackframe_t; +// Exception stack data +typedef struct { + jl_exc_stack_t *s; // Stack of exceptions + size_t itr; // Iterator into exception stack + size_t i; // Iterator into backtrace data for exception +} gc_mark_exc_stack_t; + // Module bindings. This is also the beginning of module scanning. // The loop will start marking other references in a module after the bindings are marked typedef struct { @@ -176,6 +185,7 @@ union _jl_gc_mark_data { gc_mark_obj16_t obj16; gc_mark_obj32_t obj32; gc_mark_stackframe_t stackframe; + gc_mark_exc_stack_t excstackframe; gc_mark_binding_t binding; gc_mark_finlist_t finlist; }; diff --git a/src/gf.c b/src/gf.c index aa597ecbaa116..4865c740f4e02 100644 --- a/src/gf.c +++ b/src/gf.c @@ -119,7 +119,7 @@ void jl_call_tracer(tracer_cb callback, jl_value_t *tracee) JL_CATCH { ptls->in_pure_callback = last_in; jl_printf(JL_STDERR, "WARNING: tracer callback function threw an error:\n"); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); jl_printf(JL_STDERR, "\n"); jlbacktrace(); } @@ -272,7 +272,17 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t **pli JL_ROOTS_TEMPORARILY, s ptls->world_age = jl_typeinf_world; li->inInference = 1; in_inference++; - jl_svec_t *linfo_src = (jl_svec_t*)jl_apply_with_saved_exception_state(fargs, 3, 0); + jl_svec_t *linfo_src; + JL_TRY { + linfo_src = (jl_svec_t*)jl_apply(fargs, 3); + } + JL_CATCH { + jl_printf(JL_STDERR, "Internal error: encountered unexpected error in runtime:\n"); + jl_static_show(JL_STDERR, jl_current_exception()); + jl_printf(JL_STDERR, "\n"); + jlbacktrace(); // written to STDERR_FILENO + linfo_src = NULL; + } ptls->world_age = last_age; in_inference--; li->inInference = 0; diff --git a/src/init.c b/src/init.c index 18c1feeea6237..cf3a420d76d98 100644 --- a/src/init.c +++ b/src/init.c @@ -236,7 +236,7 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) } JL_CATCH { jl_printf(JL_STDERR, "\natexit hook threw an error: "); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); } } } @@ -270,7 +270,7 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) assert(item); uv_unref(item->h); jl_printf(JL_STDERR, "error during exit cleanup: close: "); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); item = next_shutdown_queue_item(item); } } @@ -286,6 +286,8 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) loop->stop_flag = 0; while (uv_run(loop, UV_RUN_DEFAULT)) { } + // TODO: Destroy threads + jl_destroy_timing(); #ifdef ENABLE_TIMINGS jl_print_timings(); @@ -745,7 +747,7 @@ void _julia_init(JL_IMAGE_SEARCH rel) } JL_CATCH { jl_printf(JL_STDERR, "error during init:\n"); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); jl_printf(JL_STDERR, "\n"); jl_exit(1); } diff --git a/src/interpreter.c b/src/interpreter.c index bbaaec49af6db..36217fe3b270a 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -483,7 +483,7 @@ SECT_INTERP static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) return jl_copy_ast(eval_value(args[0], s)); } else if (head == exc_sym) { - return jl_get_ptls_states()->exception_in_transit; + return jl_current_exception(); } else if (head == boundscheck_sym) { return jl_true; @@ -681,6 +681,8 @@ SECT_INTERP static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s s->locals[jl_source_nslots(s->src) + catch_ip] = NULL; catch_ip += 1; } + // store current top of exception stack for restore in pop_exc. + s->locals[jl_source_nslots(s->src) + ip] = jl_box_ulong(jl_exc_stack_state()); if (!jl_setjmp(__eh.eh_ctx, 1)) { return eval_body(stmts, s, next_ip, toplevel); } @@ -697,18 +699,20 @@ SECT_INTERP static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s else if (head == leave_sym) { int hand_n_leave = jl_unbox_long(jl_exprarg(stmt, 0)); assert(hand_n_leave > 0); - // equivalent to jl_pop_handler(hand_n_leave) : + // equivalent to jl_pop_handler(hand_n_leave), but retaining eh for longjmp: jl_ptls_t ptls = jl_get_ptls_states(); jl_handler_t *eh = ptls->current_task->eh; while (--hand_n_leave > 0) eh = eh->prev; jl_eh_restore_state(eh); - // pop jmp_bufs from stack + // leave happens during normal control flow, but we must + // longjmp to pop the eval_body call for each enter. s->continue_at = next_ip; jl_longjmp(eh->eh_ctx, 1); } else if (head == pop_exc_sym) { - // FIXME + size_t prev_state = jl_unbox_ulong(eval_value(jl_exprarg(stmt, 0), s)); + jl_restore_exc_stack(prev_state); } else if (head == const_sym) { jl_sym_t *sym = (jl_sym_t*)jl_exprarg(stmt, 0); diff --git a/src/jlapi.c b/src/jlapi.c index e20e0e3ae45ba..247b1638d5146 100644 --- a/src/jlapi.c +++ b/src/jlapi.c @@ -13,6 +13,7 @@ #include "julia.h" #include "options.h" #include "julia_assert.h" +#include "julia_internal.h" #ifdef __cplusplus #include @@ -95,22 +96,26 @@ JL_DLLEXPORT jl_value_t *jl_eval_string(const char *str) jl_exception_clear(); } JL_CATCH { + jl_get_ptls_states()->previous_exception = jl_current_exception(); r = NULL; } return r; } +JL_DLLEXPORT jl_value_t *jl_current_exception(void) +{ + jl_exc_stack_t *s = jl_get_ptls_states()->current_task->exc_stack; + return s && s->top != 0 ? jl_exc_stack_exception(s, s->top) : jl_nothing; +} + JL_DLLEXPORT jl_value_t *jl_exception_occurred(void) { - jl_ptls_t ptls = jl_get_ptls_states(); - return ptls->exception_in_transit == jl_nothing ? NULL : - ptls->exception_in_transit; + return jl_get_ptls_states()->previous_exception; } JL_DLLEXPORT void jl_exception_clear(void) { - jl_ptls_t ptls = jl_get_ptls_states(); - ptls->exception_in_transit = jl_nothing; + jl_get_ptls_states()->previous_exception = NULL; } // get the name of a type as a string @@ -164,6 +169,7 @@ JL_DLLEXPORT jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, int32_t na jl_exception_clear(); } JL_CATCH { + jl_get_ptls_states()->previous_exception = jl_current_exception(); v = NULL; } return v; @@ -182,6 +188,7 @@ JL_DLLEXPORT jl_value_t *jl_call0(jl_function_t *f) jl_exception_clear(); } JL_CATCH { + jl_get_ptls_states()->previous_exception = jl_current_exception(); v = NULL; } return v; @@ -202,6 +209,7 @@ JL_DLLEXPORT jl_value_t *jl_call1(jl_function_t *f, jl_value_t *a) jl_exception_clear(); } JL_CATCH { + jl_get_ptls_states()->previous_exception = jl_current_exception(); v = NULL; } return v; @@ -222,6 +230,7 @@ JL_DLLEXPORT jl_value_t *jl_call2(jl_function_t *f, jl_value_t *a, jl_value_t *b jl_exception_clear(); } JL_CATCH { + jl_get_ptls_states()->previous_exception = jl_current_exception(); v = NULL; } return v; @@ -243,6 +252,7 @@ JL_DLLEXPORT jl_value_t *jl_call3(jl_function_t *f, jl_value_t *a, jl_exception_clear(); } JL_CATCH { + jl_get_ptls_states()->previous_exception = jl_current_exception(); v = NULL; } return v; @@ -267,6 +277,7 @@ JL_DLLEXPORT jl_value_t *jl_get_field(jl_value_t *o, const char *fld) jl_exception_clear(); } JL_CATCH { + jl_get_ptls_states()->previous_exception = jl_current_exception(); v = NULL; } return v; diff --git a/src/julia.h b/src/julia.h index 01e2bfcaab0eb..427622c8bc89a 100644 --- a/src/julia.h +++ b/src/julia.h @@ -447,6 +447,7 @@ typedef struct _jl_module_t { JL_DATA_TYPE jl_sym_t *name; struct _jl_module_t *parent; + // hidden fields: htable_t bindings; arraylist_t usings; // modules with all bindings potentially imported uint64_t build_id; @@ -1205,6 +1206,7 @@ JL_DLLEXPORT int jl_get_size(jl_value_t *val, size_t *pnt); #define jl_box_long(x) jl_box_int64(x) #define jl_box_ulong(x) jl_box_uint64(x) #define jl_unbox_long(x) jl_unbox_int64(x) +#define jl_unbox_ulong(x) jl_unbox_uint64(x) #define jl_is_long(x) jl_is_int64(x) #define jl_long_type jl_int64_type #define jl_ulong_type jl_uint64_type @@ -1212,6 +1214,7 @@ JL_DLLEXPORT int jl_get_size(jl_value_t *val, size_t *pnt); #define jl_box_long(x) jl_box_int32(x) #define jl_box_ulong(x) jl_box_uint32(x) #define jl_unbox_long(x) jl_unbox_int32(x) +#define jl_unbox_ulong(x) jl_unbox_uint32(x) #define jl_is_long(x) jl_is_int32(x) #define jl_long_type jl_int32_type #define jl_ulong_type jl_uint32_type @@ -1422,6 +1425,10 @@ JL_DLLEXPORT void JL_NORETURN jl_bounds_error_tuple_int(jl_value_t **v, JL_DLLEXPORT void JL_NORETURN jl_bounds_error_unboxed_int(void *v, jl_value_t *vt, size_t i); JL_DLLEXPORT void JL_NORETURN jl_bounds_error_ints(jl_value_t *v, size_t *idxs, size_t nidxs); JL_DLLEXPORT void JL_NORETURN jl_eof_error(void); +// Return the exception currently being handled, or nothing if we are not +// inside the scope of a JL_CATCH. Note that catch scope is determined +// dynamically so this works in functions called from a catch block. +JL_DLLEXPORT jl_value_t *jl_current_exception(void); JL_DLLEXPORT jl_value_t *jl_exception_occurred(void); JL_DLLEXPORT void jl_exception_clear(void) JL_NOTSAFEPOINT; @@ -1616,6 +1623,8 @@ typedef struct _jl_task_t { jl_handler_t *eh; // saved gc stack top for context switches jl_gcframe_t *gcstack; + // saved exception stack + jl_exc_stack_t *exc_stack; // current world age size_t world_age; @@ -1633,6 +1642,7 @@ JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, size_t ssize); JL_DLLEXPORT void jl_switchto(jl_task_t **pt); JL_DLLEXPORT void JL_NORETURN jl_throw(jl_value_t *e JL_MAYBE_UNROOTED); JL_DLLEXPORT void JL_NORETURN jl_rethrow(void); +JL_DLLEXPORT void JL_NORETURN jl_sig_throw(void); JL_DLLEXPORT void JL_NORETURN jl_rethrow_other(jl_value_t *e JL_MAYBE_UNROOTED); JL_DLLEXPORT void JL_NORETURN jl_no_exc_handler(jl_value_t *e); @@ -1641,6 +1651,8 @@ JL_DLLEXPORT void JL_NORETURN jl_no_exc_handler(jl_value_t *e); JL_DLLEXPORT void jl_enter_handler(jl_handler_t *eh); JL_DLLEXPORT void jl_eh_restore_state(jl_handler_t *eh); JL_DLLEXPORT void jl_pop_handler(int n); +JL_DLLEXPORT size_t jl_exc_stack_state(void); +JL_DLLEXPORT void jl_restore_exc_stack(size_t state); #if defined(_OS_WINDOWS_) #if defined(_COMPILER_MINGW_) @@ -1680,13 +1692,14 @@ extern int had_exception; #define JL_TRY \ int i__tr, i__ca; jl_handler_t __eh; \ + size_t __exc_stack_state = jl_exc_stack_state(); \ jl_enter_handler(&__eh); \ if (!jl_setjmp(__eh.eh_ctx,0)) \ for (i__tr=1; i__tr; i__tr=0, jl_eh_restore_state(&__eh)) #define JL_CATCH \ else \ - for (i__ca=1, jl_eh_restore_state(&__eh); i__ca; i__ca=0) + for (i__ca=1, jl_eh_restore_state(&__eh); i__ca; i__ca=0, jl_restore_exc_stack(__exc_stack_state)) #endif @@ -1889,8 +1902,6 @@ typedef struct { #define jl_current_task (jl_get_ptls_states()->current_task) #define jl_root_task (jl_get_ptls_states()->root_task) -#define jl_exception_in_transit (jl_get_ptls_states()->exception_in_transit) - // codegen interface ---------------------------------------------------------- diff --git a/src/julia_internal.h b/src/julia_internal.h index b68aa6640be3c..edf35628b20b9 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -636,7 +636,6 @@ size_t rec_backtrace_ctx(uintptr_t *data, size_t maxsize, bt_context_t *ctx) JL_ size_t rec_backtrace_ctx_dwarf(uintptr_t *data, size_t maxsize, bt_context_t *ctx); #endif JL_DLLEXPORT void jl_get_backtrace(jl_array_t **bt, jl_array_t **bt2); -JL_DLLEXPORT jl_value_t *jl_apply_with_saved_exception_state(jl_value_t **args, uint32_t nargs, int drop_exceptions); void jl_critical_error(int sig, bt_context_t *context, uintptr_t *bt_data, size_t *bt_size); JL_DLLEXPORT void jl_raise_debugger(void); int jl_getFunctionInfo(jl_frame_t **frames, uintptr_t pointer, int skipC, int noInline); @@ -658,6 +657,28 @@ JL_DLLEXPORT int jl_is_interpreter_frame(uintptr_t ip); JL_DLLEXPORT int jl_is_enter_interpreter_frame(uintptr_t ip); JL_DLLEXPORT size_t jl_capture_interp_frame(uintptr_t *data, uintptr_t sp, uintptr_t fp, size_t space_remaining); +// Exception stack: a stack of pairs of (exception,raw_backtrace). +// The stack may be traversed and accessed with the macros below. +typedef struct _jl_exc_stack_t { + size_t top; + size_t reserved_size; + // Pack all stack entries into a growable buffer to amortize allocation + // across repeated exception handling. + // uintptr_t data[]; // Access with jl_excstk_raw +#define jl_excstk_raw(stack) ((uintptr_t*)((char*)(stack) + sizeof(jl_exc_stack_t))) +} jl_exc_stack_t; +// Stack access +#define jl_exc_stack_exception(stack, itr) ((jl_value_t*)jl_excstk_raw(stack)[(itr)-1]) +#define jl_exc_stack_bt_size(stack, itr) ((size_t)jl_excstk_raw(stack)[(itr)-2]) +#define jl_exc_stack_bt_data(stack, itr) (jl_excstk_raw(stack) + itr - 2 - jl_excstk_raw(stack)[(itr)-2]) +// Exception stack iteration (start at itr=stack->top, stop at itr=0) +#define jl_exc_stack_next(stack, itr) ((itr) - 2 - jl_exc_stack_bt_size(stack,itr)) +void jl_reserve_exc_stack(jl_exc_stack_t **stack, size_t reserved_size); +void jl_push_exc_stack(jl_exc_stack_t **stack, jl_value_t *exception, + uintptr_t *bt_data, size_t bt_size); +void jl_pop_exc_stack(jl_exc_stack_t *stack, size_t n); +void jl_copy_exc_stack(jl_exc_stack_t *dest, jl_exc_stack_t *src); + // timers // Returns time in nanosec JL_DLLEXPORT uint64_t jl_hrtime(void); diff --git a/src/julia_threads.h b/src/julia_threads.h index d2673ea59a573..97a3a57cf8a82 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -132,12 +132,13 @@ typedef struct { jl_gc_mark_data_t *data_stack; } jl_gc_mark_cache_t; +typedef struct _jl_exc_stack_t jl_exc_stack_t; // This includes all the thread local states we care about for a thread. +// Changes to TLS field types must be reflected in codegen. #define JL_MAX_BT_SIZE 80000 struct _jl_tls_states_t { struct _jl_gcframe_t *pgcstack; size_t world_age; - struct _jl_value_t *exception_in_transit; volatile size_t *safepoint; // Whether it is safe to execute GC at the same time. #define JL_GC_STATE_WAITING 1 @@ -159,9 +160,11 @@ struct _jl_tls_states_t { //#endif jl_jmp_buf *safe_restore; int16_t tid; - size_t bt_size; - // JL_MAX_BT_SIZE + 1 elements long - uintptr_t *bt_data; + // Temp storage for exception thrown in signal handler. Not rooted. + struct _jl_value_t *sig_exception; + // Temporary backtrace buffer. Scanned for gc roots when bt_size > 0. + uintptr_t *bt_data; // JL_MAX_BT_SIZE + 1 elements long + size_t bt_size; // Size for backtrace in transit in bt_data // Atomically set by the sender, reset by the handler. volatile sig_atomic_t signal_request; // Allow the sigint to be raised asynchronously @@ -188,6 +191,9 @@ struct _jl_tls_states_t { jl_gc_mark_cache_t gc_cache; arraylist_t sweep_objs; jl_gc_mark_sp_t gc_mark_sp; + // Saved exception for previous external API call or NULL if cleared. + // Access via jl_exception_occurred(). + struct _jl_value_t *previous_exception; }; // Update codegen version in `ccall.cpp` after changing either `pause` or `wake` diff --git a/src/method.c b/src/method.c index 175f6a24ebfa1..83cc4fabd1783 100644 --- a/src/method.c +++ b/src/method.c @@ -60,7 +60,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve rt = jl_interpret_toplevel_expr_in(module, rt, NULL, sparam_vals); } JL_CATCH { - if (jl_typeis(jl_exception_in_transit, jl_errorexception_type)) + if (jl_typeis(jl_current_exception(), jl_errorexception_type)) jl_error("could not evaluate cfunction return type (it might depend on a local variable)"); else jl_rethrow(); @@ -72,7 +72,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve at = jl_interpret_toplevel_expr_in(module, at, NULL, sparam_vals); } JL_CATCH { - if (jl_typeis(jl_exception_in_transit, jl_errorexception_type)) + if (jl_typeis(jl_current_exception(), jl_errorexception_type)) jl_error("could not evaluate cfunction argument type (it might depend on a local variable)"); else jl_rethrow(); @@ -96,7 +96,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve rt = jl_interpret_toplevel_expr_in(module, rt, NULL, sparam_vals); } JL_CATCH { - if (jl_typeis(jl_exception_in_transit, jl_errorexception_type)) + if (jl_typeis(jl_current_exception(), jl_errorexception_type)) jl_error("could not evaluate ccall return type (it might depend on a local variable)"); else jl_rethrow(); @@ -108,7 +108,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve at = jl_interpret_toplevel_expr_in(module, at, NULL, sparam_vals); } JL_CATCH { - if (jl_typeis(jl_exception_in_transit, jl_errorexception_type)) + if (jl_typeis(jl_current_exception(), jl_errorexception_type)) jl_error("could not evaluate ccall argument type (it might depend on a local variable)"); else jl_rethrow(); diff --git a/src/rtutils.c b/src/rtutils.c index 7fecdfdd32482..3eb7564ee881c 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -205,6 +205,8 @@ JL_DLLEXPORT void jl_typeassert(jl_value_t *x, jl_value_t *t) jl_type_error("typeassert", t, x); } +// exceptions ----------------------------------------------------------------- + JL_DLLEXPORT void jl_enter_handler(jl_handler_t *eh) { jl_ptls_t ptls = jl_get_ptls_states(); @@ -225,7 +227,12 @@ JL_DLLEXPORT void jl_enter_handler(jl_handler_t *eh) #endif } -STATIC_INLINE void jl_eh_restore_state(jl_handler_t *eh) +// Restore thread local state to saved state in error handler `eh`. +// This is executed in two circumstances: +// * We leave a try block through normal control flow +// * An exception causes a nonlocal jump to the catch block. In this case +// there's additional cleanup required, eg pushing the exception stack. +JL_DLLEXPORT void jl_eh_restore_state(jl_handler_t *eh) { jl_ptls_t ptls = jl_get_ptls_states(); #ifdef _OS_WINDOWS_ @@ -235,7 +242,7 @@ STATIC_INLINE void jl_eh_restore_state(jl_handler_t *eh) } #endif jl_task_t *current_task = ptls->current_task; - // `eh` may not be `ptls->current_task->eh`. See `jl_pop_handler` + // `eh` may be not equal to `ptls->current_task->eh`. See `jl_pop_handler` // This function should **NOT** have any safepoint before the ones at the // end. sig_atomic_t old_defer_signal = ptls->defer_signal; @@ -261,6 +268,7 @@ STATIC_INLINE void jl_eh_restore_state(jl_handler_t *eh) jl_sigint_safepoint(ptls); } } + JL_DLLEXPORT void jl_pop_handler(int n) { jl_ptls_t ptls = jl_get_ptls_states(); @@ -272,38 +280,64 @@ JL_DLLEXPORT void jl_pop_handler(int n) jl_eh_restore_state(eh); } -JL_DLLEXPORT jl_value_t *jl_apply_with_saved_exception_state(jl_value_t **args, uint32_t nargs, int drop_exceptions) +JL_DLLEXPORT size_t jl_exc_stack_state(void) { jl_ptls_t ptls = jl_get_ptls_states(); - jl_value_t *exc = ptls->exception_in_transit; - jl_array_t *bt = NULL; - jl_array_t *bt2 = NULL; - JL_GC_PUSH3(&exc, &bt, &bt2); - if (ptls->bt_size > 0) { - jl_get_backtrace(&bt, &bt2); - ptls->bt_size = 0; - } - jl_value_t *v; - JL_TRY { - v = jl_apply(args, nargs); - } - JL_CATCH { - if (!drop_exceptions) { - jl_printf(JL_STDERR, "Internal error: encountered unexpected error in runtime:\n"); - jl_static_show(JL_STDERR, ptls->exception_in_transit); - jl_printf(JL_STDERR, "\n"); - jlbacktrace(); // written to STDERR_FILENO - } - v = NULL; + jl_exc_stack_t *s = ptls->current_task->exc_stack; + return s ? s->top : 0; +} + +JL_DLLEXPORT void jl_restore_exc_stack(size_t state) +{ + jl_ptls_t ptls = jl_get_ptls_states(); + jl_exc_stack_t *s = ptls->current_task->exc_stack; + if (s) { + assert(s->top >= state); + s->top = state; } - ptls->exception_in_transit = exc; - if (bt != NULL) { - // This is sufficient because bt2 roots the gc-managed values - memcpy(ptls->bt_data, bt->data, jl_array_len(bt) * sizeof(void*)); - ptls->bt_size = jl_array_len(bt); +} + +void jl_copy_exc_stack(jl_exc_stack_t *dest, jl_exc_stack_t *src) +{ + assert(dest->reserved_size >= src->top); + memcpy(jl_excstk_raw(dest), jl_excstk_raw(src), sizeof(uintptr_t)*src->top); + dest->top = src->top; +} + +void jl_reserve_exc_stack(jl_exc_stack_t **stack, size_t reserved_size) +{ + jl_exc_stack_t *s = *stack; + if (s && s->reserved_size >= reserved_size) + return; + size_t bufsz = sizeof(jl_exc_stack_t) + sizeof(uintptr_t)*reserved_size; + jl_exc_stack_t *new_s = (jl_exc_stack_t*)jl_gc_alloc_buf(jl_get_ptls_states(), bufsz); + new_s->top = 0; + new_s->reserved_size = reserved_size; + if (s) + jl_copy_exc_stack(new_s, s); + *stack = new_s; +} + +void jl_push_exc_stack(jl_exc_stack_t **stack, jl_value_t *exception, + uintptr_t *bt_data, size_t bt_size) +{ + jl_reserve_exc_stack(stack, (*stack ? (*stack)->top : 0) + bt_size + 2); + jl_exc_stack_t *s = *stack; + memcpy(jl_excstk_raw(s) + s->top, bt_data, sizeof(uintptr_t)*bt_size); + s->top += bt_size + 2; + jl_excstk_raw(s)[s->top-2] = bt_size; + jl_excstk_raw(s)[s->top-1] = (uintptr_t)exception; +} + +void jl_pop_exc_stack(jl_exc_stack_t *stack, size_t n) +{ + size_t top = stack->top; + while (n != 0 && top > 0) { + top = jl_exc_stack_next(stack, top); + --n; } - JL_GC_POP(); - return v; + stack->top = top; + assert(n == 0); } // misc ----------------------------------------------------------------------- diff --git a/src/signals-mach.c b/src/signals-mach.c index ef38b25b01ff3..c1add709a3e6b 100644 --- a/src/signals-mach.c +++ b/src/signals-mach.c @@ -147,9 +147,9 @@ static void jl_throw_in_thread(int tid, mach_port_t thread, jl_value_t *exceptio assert(exception); ptls2->bt_size = rec_backtrace_ctx(ptls2->bt_data, JL_MAX_BT_SIZE, (bt_context_t*)&state); - ptls2->exception_in_transit = exception; + ptls2->sig_exception = exception; } - jl_call_in_state(ptls2, &state, &jl_rethrow); + jl_call_in_state(ptls2, &state, &jl_sig_throw); ret = thread_set_state(thread, x86_THREAD_STATE64, (thread_state_t)&state, count); HANDLE_MACH_ERROR("thread_set_state",ret); diff --git a/src/signals-unix.c b/src/signals-unix.c index 85f947edfb2a0..274277d32c845 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -80,6 +80,8 @@ static inline __attribute__((unused)) uintptr_t jl_get_rsp_from_ctx(const void * #endif } +// Modify signal context `_ctx` so that `fptr` will execute when the signal +// returns. `fptr` will execute on the signal stack, and must not return. static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int sig, void *_ctx) { // Modifying the ucontext should work but there is concern that @@ -174,8 +176,8 @@ static void jl_throw_in_ctx(jl_ptls_t ptls, jl_value_t *e, int sig, void *sigctx if (!ptls->safe_restore) ptls->bt_size = rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, jl_to_bt_context(sigctx)); - ptls->exception_in_transit = e; - jl_call_in_ctx(ptls, &jl_rethrow, sig, sigctx); + ptls->sig_exception = e; + jl_call_in_ctx(ptls, &jl_sig_throw, sig, sigctx); } static pthread_t signals_thread; diff --git a/src/signals-win.c b/src/signals-win.c index d45d8377a95d8..25d4a54e9ede3 100644 --- a/src/signals-win.c +++ b/src/signals-win.c @@ -136,16 +136,16 @@ void jl_throw_in_ctx(jl_value_t *excpt, PCONTEXT ctxThread) error_ctx = ctxThread; jl_swapcontext(&error_return_fiber, &collect_backtrace_fiber); } - jl_exception_in_transit = excpt; + ptls->sig_exception = excpt; } #if defined(_CPU_X86_64_) *(DWORD64*)Rsp = 0; ctxThread->Rsp = Rsp; - ctxThread->Rip = (DWORD64)&jl_rethrow; + ctxThread->Rip = (DWORD64)&jl_sig_throw; #elif defined(_CPU_X86_) *(DWORD32*)Esp = 0; ctxThread->Esp = Esp; - ctxThread->Eip = (DWORD)&jl_rethrow; + ctxThread->Eip = (DWORD)&jl_sig_throw; #endif } diff --git a/src/stackwalk.c b/src/stackwalk.c index 3bc2fdd690d0e..0c288330663a7 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -139,23 +139,23 @@ JL_DLLEXPORT jl_value_t *jl_backtrace_from_here(int returnsp) return bt; } -JL_DLLEXPORT void jl_get_backtrace(jl_array_t **btout, jl_array_t **bt2out) +void decode_backtrace(uintptr_t *bt_data, size_t bt_size, + jl_array_t **btout, jl_array_t **bt2out) { - jl_ptls_t ptls = jl_get_ptls_states(); jl_array_t *bt = NULL; jl_array_t *bt2 = NULL; JL_GC_PUSH2(&bt, &bt2); if (array_ptr_void_type == NULL) { array_ptr_void_type = jl_apply_type2((jl_value_t*)jl_array_type, (jl_value_t*)jl_voidpointer_type, jl_box_long(1)); } - bt = jl_alloc_array_1d(array_ptr_void_type, ptls->bt_size); - memcpy(bt->data, ptls->bt_data, ptls->bt_size * sizeof(void*)); + bt = jl_alloc_array_1d(array_ptr_void_type, bt_size); + memcpy(bt->data, bt_data, bt_size * sizeof(void*)); bt2 = jl_alloc_array_1d(jl_array_any_type, 0); // Scan the stack for any interpreter frames size_t n = 0; - while (n < ptls->bt_size) { - if (ptls->bt_data[n] == (uintptr_t)-1) { - jl_array_ptr_1d_push(bt2, (jl_value_t*)ptls->bt_data[n+1]); + while (n < bt_size) { + if (bt_data[n] == (uintptr_t)-1) { + jl_array_ptr_1d_push(bt2, (jl_value_t*)bt_data[n+1]); n += 2; } n++; @@ -165,6 +165,17 @@ JL_DLLEXPORT void jl_get_backtrace(jl_array_t **btout, jl_array_t **bt2out) JL_GC_POP(); } +JL_DLLEXPORT void jl_get_backtrace(jl_array_t **btout, jl_array_t **bt2out) +{ + jl_exc_stack_t *s = jl_get_ptls_states()->current_task->exc_stack; + uintptr_t *bt_data = NULL; + size_t bt_size = 0; + if (s && s->top) { + bt_data = jl_exc_stack_bt_data(s, s->top); + bt_size = jl_exc_stack_bt_size(s, s->top); + } + decode_backtrace(bt_data, bt_size, btout, bt2out); +} #if defined(_OS_WINDOWS_) #ifdef _CPU_X86_64_ @@ -474,10 +485,13 @@ JL_DLLEXPORT void jl_gdblookup(uintptr_t ip) JL_DLLEXPORT void jlbacktrace(void) { - jl_ptls_t ptls = jl_get_ptls_states(); - size_t i, n = ptls->bt_size; // ptls->bt_size > 400 ? 400 : ptls->bt_size; - for (i = 0; i < n; i++) - jl_gdblookup(ptls->bt_data[i] - 1); + jl_exc_stack_t *s = jl_get_ptls_states()->current_task->exc_stack; + if (!s) + return; + size_t bt_size = jl_exc_stack_bt_size(s, s->top); + uintptr_t *bt_data = jl_exc_stack_bt_data(s, s->top); + for (size_t i = 0; i < bt_size; i++) + jl_gdblookup(bt_data[i] - 1); } #ifdef __cplusplus diff --git a/src/staticdata.c b/src/staticdata.c index 621284dce8b05..8747401f08065 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -1100,7 +1100,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 @@ -1173,7 +1172,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"); } } diff --git a/src/task.c b/src/task.c index ab3939263fdce..adeb72019a615 100644 --- a/src/task.c +++ b/src/task.c @@ -174,7 +174,7 @@ static void JL_NORETURN finish_task(jl_task_t *t, jl_value_t *resultval JL_MAYBE jl_apply(args, 2); } JL_CATCH { - jl_no_exc_handler(jl_exception_in_transit); + jl_no_exc_handler(jl_current_exception()); } } gc_debug_critical_error(); @@ -211,13 +211,12 @@ JL_DLLEXPORT void *jl_task_stack_buffer(jl_task_t *task, size_t *size, int *tid) return (void *)((char *)task->stkbuf + off); } -static void record_backtrace(void) JL_NOTSAFEPOINT +static void record_backtrace(jl_ptls_t ptls) JL_NOTSAFEPOINT { - jl_ptls_t ptls = jl_get_ptls_states(); + // storing bt_size in ptls ensures roots in bt_data will be found ptls->bt_size = rec_backtrace(ptls->bt_data, JL_MAX_BT_SIZE); } - JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) { _julia_init(rel); @@ -235,8 +234,6 @@ static void ctx_switch(jl_ptls_t ptls, jl_task_t **pt) if (blk) jl_timing_block_stop(blk); #endif - // backtraces don't survive task switches, see e.g. issue #12485 - ptls->bt_size = 0; #ifdef JULIA_ENABLE_THREADING // If the current task is not holding any locks, free the locks list // so that it can be GC'd without leaking memory @@ -361,15 +358,25 @@ JL_DLLEXPORT JL_NORETURN void jl_no_exc_handler(jl_value_t *e) JL_NOTSAFEPOINT jl_timing_block_t *jl_pop_timing_block(jl_timing_block_t *cur_block); // yield to exception handler -void JL_NORETURN throw_internal(jl_value_t *e JL_MAYBE_UNROOTED) +void JL_NORETURN throw_internal(jl_value_t *exception JL_MAYBE_UNROOTED) { jl_ptls_t ptls = jl_get_ptls_states(); ptls->io_wait = 0; if (ptls->safe_restore) jl_longjmp(*ptls->safe_restore, 1); - assert(e != NULL); - ptls->exception_in_transit = e; jl_gc_unsafe_enter(ptls); + if (exception) { + // The temporary ptls->bt_data is rooted by special purpose code in the + // GC. This exists only for the purpose of preserving bt_data until we + // set ptls->bt_size=0 below. + JL_GC_PUSH1(&exception); + assert(ptls->current_task); + jl_push_exc_stack(&ptls->current_task->exc_stack, exception, + ptls->bt_data, ptls->bt_size); + ptls->bt_size = 0; + JL_GC_POP(); + } + assert(ptls->current_task->exc_stack && ptls->current_task->exc_stack->top); jl_handler_t *eh = ptls->current_task->eh; if (eh != NULL) { #ifdef ENABLE_TIMINGS @@ -382,7 +389,7 @@ void JL_NORETURN throw_internal(jl_value_t *e JL_MAYBE_UNROOTED) jl_longjmp(eh->eh_ctx, 1); } else { - jl_no_exc_handler(e); + jl_no_exc_handler(exception); } assert(0); } @@ -392,20 +399,43 @@ JL_DLLEXPORT void jl_throw(jl_value_t *e) { jl_ptls_t ptls = jl_get_ptls_states(); assert(e != NULL); - if (!ptls->safe_restore) - record_backtrace(); + if (ptls->safe_restore) + throw_internal(NULL); + record_backtrace(ptls); throw_internal(e); } +// rethrow with current exc_stack state JL_DLLEXPORT void jl_rethrow(void) +{ + jl_exc_stack_t *exc_stack = jl_get_ptls_states()->current_task->exc_stack; + if (!exc_stack || exc_stack->top == 0) + jl_error("rethrow() not allowed outside a catch block"); + throw_internal(NULL); +} + +// Special case throw for errors detected inside signal handlers. This is not +// (cannot be) called directly in the signal handler itself, but is returned to +// after the signal handler exits. +JL_DLLEXPORT void jl_sig_throw(void) { jl_ptls_t ptls = jl_get_ptls_states(); - throw_internal(ptls->exception_in_transit); + jl_value_t *e = ptls->sig_exception; + assert(e && ptls->bt_size != 0); + ptls->sig_exception = NULL; + throw_internal(e); } JL_DLLEXPORT void jl_rethrow_other(jl_value_t *e) { - throw_internal(e); + // TODO: Should uses of `rethrow(exc)` be replaced with a normal throw, now + // that exception stacks allow root cause analysis? + jl_exc_stack_t *exc_stack = jl_get_ptls_states()->current_task->exc_stack; + if (!exc_stack || exc_stack->top == 0) + jl_error("rethrow(exc) not allowed outside a catch block"); + // overwrite exception on top of stack. see jl_exc_stack_exception + jl_excstk_raw(exc_stack)[exc_stack->top-1] = (uintptr_t)e; + throw_internal(NULL); } JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, size_t ssize) @@ -439,6 +469,7 @@ JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, size_t ssize) t->eh = NULL; t->tid = 0; t->gcstack = NULL; + t->exc_stack = NULL; t->stkbuf = NULL; t->tid = 0; t->started = 0; @@ -506,7 +537,9 @@ static void NOINLINE JL_NORETURN start_task(void) jl_value_t *res; t->started = 1; if (t->exception != jl_nothing) { - record_backtrace(); + record_backtrace(ptls); + jl_push_exc_stack(&t->exc_stack, t->exception, + ptls->bt_data, ptls->bt_size); res = t->exception; } else { @@ -520,10 +553,12 @@ static void NOINLINE JL_NORETURN start_task(void) res = jl_apply(&t->start, 1); } JL_CATCH { - res = jl_exception_in_transit; + res = jl_current_exception(); t->exception = res; jl_gc_wb(t, res); + goto skip_pop_exc; } +skip_pop_exc:; } finish_task(t, res); gc_debug_critical_error(); @@ -846,12 +881,12 @@ void jl_init_root_task(void *stack_lo, void *stack_hi) ptls->current_task->logstate = jl_nothing; ptls->current_task->eh = NULL; ptls->current_task->gcstack = NULL; + ptls->current_task->exc_stack = NULL; ptls->current_task->tid = ptls->tid; #ifdef JULIA_ENABLE_THREADING arraylist_new(&ptls->current_task->locks, 0); #endif - ptls->exception_in_transit = (jl_value_t*)jl_nothing; ptls->root_task = ptls->current_task; jl_init_basefiber(JL_STACK_SIZE); diff --git a/src/threading.c b/src/threading.c index 869ffd6dffcdd..0a68e2025aede 100644 --- a/src/threading.c +++ b/src/threading.c @@ -282,6 +282,7 @@ static void ti_initthread(int16_t tid) } memset(bt_data, 0, sizeof(uintptr_t) * (JL_MAX_BT_SIZE + 1)); ptls->bt_data = (uintptr_t*)bt_data; + ptls->sig_exception = NULL; #ifdef _OS_WINDOWS_ ptls->needs_resetstkoflw = 0; #endif @@ -323,7 +324,7 @@ static jl_value_t *ti_run_fun(jl_callptr_t fptr, jl_method_instance_t *mfunc, ptls->safe_restore = &buf; jl_printf(JL_STDERR, "\nError thrown in threaded loop on thread %d: ", (int)ptls->tid); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); } ptls->safe_restore = old_buf; JL_UNLOCK_NOGC(&lock); diff --git a/src/toplevel.c b/src/toplevel.c index 4a940c0ab4d4d..ecbeb7fe1dfd4 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -78,7 +78,7 @@ void jl_module_run_initializer(jl_module_t *m) } else { jl_rethrow_other(jl_new_struct(jl_initerror_type, m->name, - jl_exception_in_transit)); + jl_current_exception())); } } } diff --git a/test/channels.jl b/test/channels.jl index aaaaa69d71931..8ce6a87bec736 100644 --- a/test/channels.jl +++ b/test/channels.jl @@ -208,7 +208,10 @@ using Dates end @testset "yield/wait/event failures" begin - @noinline garbage_finalizer(f) = finalizer(f, "gar" * "bage") + # garbage_finalizer returns `nothing` rather than the garbage object so + # that the interpreter doesn't accidentally root the garbage when + # interpreting the calling function. + @noinline garbage_finalizer(f) = (finalizer(f, "gar" * "bage"); nothing) run = Ref(0) GC.enable(false) # test for finalizers trying to yield leading to failed attempts to context switch diff --git a/ui/repl.c b/ui/repl.c index fe5ecf53fbdd9..a33f81ed2ab0c 100644 --- a/ui/repl.c +++ b/ui/repl.c @@ -31,28 +31,18 @@ extern "C" { static int exec_program(char *program) { - jl_ptls_t ptls = jl_get_ptls_states(); JL_TRY { jl_load(jl_main_module, program); } JL_CATCH { jl_value_t *errs = jl_stderr_obj(); - jl_value_t *e = ptls->exception_in_transit; - // Manually save and restore the backtrace so that we print the original - // one instead of the one caused by `show`. - // We can't use safe_restore since that will cause any error - // (including the ones that would have been caught) to abort. - uintptr_t *volatile bt_data = NULL; - size_t bt_size = ptls->bt_size; volatile int shown_err = 0; jl_printf(JL_STDERR, "error during bootstrap:\n"); JL_TRY { if (errs) { - bt_data = (uintptr_t*)malloc(bt_size * sizeof(void*)); - memcpy(bt_data, ptls->bt_data, bt_size * sizeof(void*)); jl_value_t *showf = jl_get_function(jl_base_module, "show"); if (showf != NULL) { - jl_call2(showf, errs, e); + jl_call2(showf, errs, jl_current_exception()); jl_printf(JL_STDERR, "\n"); shown_err = 1; } @@ -60,13 +50,8 @@ static int exec_program(char *program) } JL_CATCH { } - if (bt_data) { - ptls->bt_size = bt_size; - memcpy(ptls->bt_data, bt_data, bt_size * sizeof(void*)); - free(bt_data); - } if (!shown_err) { - jl_static_show(JL_STDERR, e); + jl_static_show(JL_STDERR, jl_current_exception()); jl_printf(JL_STDERR, "\n"); } jlbacktrace(); @@ -99,7 +84,6 @@ static void print_profile(void) static NOINLINE int true_main(int argc, char *argv[]) { - jl_ptls_t ptls = jl_get_ptls_states(); jl_set_ARGS(argc, argv); jl_function_t *start_client = jl_base_module ? @@ -113,7 +97,7 @@ static NOINLINE int true_main(int argc, char *argv[]) jl_get_ptls_states()->world_age = last_age; } JL_CATCH { - jl_no_exc_handler(jl_exception_in_transit); + jl_no_exc_handler(jl_current_exception()); } return 0; } @@ -138,7 +122,7 @@ static NOINLINE int true_main(int argc, char *argv[]) jl_value_t *val = (jl_value_t*)jl_eval_string(line); if (jl_exception_occurred()) { jl_printf(JL_STDERR, "error during run:\n"); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_exception_occurred()); jl_exception_clear(); } else if (val) { @@ -155,7 +139,7 @@ static NOINLINE int true_main(int argc, char *argv[]) line = NULL; } jl_printf(JL_STDERR, "\nparser error:\n"); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); jl_printf(JL_STDERR, "\n"); jlbacktrace(); }