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