From 38d1ba62abe3820bc5195a9d382ab90f6597137d Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Thu, 28 Jun 2018 14:26:56 +0200 Subject: [PATCH 01/14] Some cleanup of indentiation and comments --- src/stackwalk.c | 12 ++++++------ src/threading.c | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/stackwalk.c b/src/stackwalk.c index dce26784dfb1f..3bc2fdd690d0e 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -51,12 +51,12 @@ size_t jl_unw_stepn(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, size_t ma if (!jl_unw_step(cursor, &ip[n], &thesp, &thefp)) break; if (sp) - sp[n] = thesp; - if (add_interp_frames && jl_is_enter_interpreter_frame(ip[n])) { - n += jl_capture_interp_frame(&ip[n], thesp, thefp, maxsize-n-1) + 1; - } else { - n++; - } + sp[n] = thesp; + if (add_interp_frames && jl_is_enter_interpreter_frame(ip[n])) { + n += jl_capture_interp_frame(&ip[n], thesp, thefp, maxsize-n-1) + 1; + } else { + n++; + } } n++; #if !defined(_OS_WINDOWS_) diff --git a/src/threading.c b/src/threading.c index 445d17b485695..ae315230c315c 100644 --- a/src/threading.c +++ b/src/threading.c @@ -275,12 +275,12 @@ static void ti_initthread(int16_t tid) } ptls->defer_signal = 0; void *bt_data = malloc(sizeof(uintptr_t) * (JL_MAX_BT_SIZE + 1)); - memset(bt_data, 0, sizeof(uintptr_t) * (JL_MAX_BT_SIZE + 1)); if (bt_data == NULL) { jl_printf(JL_STDERR, "could not allocate backtrace buffer\n"); gc_debug_critical_error(); abort(); } + memset(bt_data, 0, sizeof(uintptr_t) * (JL_MAX_BT_SIZE + 1)); ptls->bt_data = (uintptr_t*)bt_data; jl_init_thread_heap(ptls); jl_install_thread_signal_handler(ptls); From 0e8811a9b8d8ff5abf5dab7ab0f44beac58362b5 Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Sun, 9 Sep 2018 23:55:40 +1000 Subject: [PATCH 02/14] Consolidate _resetstkoflw handling The logic for this was repeated in three quite different ways, in JL_CATCH, interpreter and codegen but putting it into the runtime in jl_eh_restore_state should be sufficient. Also move jl_eh_restore_state out of main header - seems unnecessary to expose such implementation detail, and inconsistent with other eh functions. --- src/codegen.cpp | 18 ------------------ src/interpreter.c | 4 ---- src/julia.h | 42 +----------------------------------------- src/julia_threads.h | 3 +++ src/rtutils.c | 36 ++++++++++++++++++++++++++++++++++++ src/signals-win.c | 1 + src/threading.c | 3 +++ 7 files changed, 44 insertions(+), 63 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index aab4586e41b4d..761d8247a2d37 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -320,7 +320,6 @@ static Function *jlgetnthfieldchecked_func; //static Function *jlsetnthfield_func; static Function *jlgetcfunctiontrampoline_func; #ifdef _OS_WINDOWS_ -static Function *resetstkoflw_func; #if defined(_CPU_X86_64_) Function *juliapersonality_func; #endif @@ -6182,21 +6181,7 @@ static std::unique_ptr emit_function( handlr = BB[lname]; workstack.push_back(lname - 1); come_from_bb[cursor + 1] = ctx.builder.GetInsertBlock(); -#ifdef _OS_WINDOWS_ - BasicBlock *cond_resetstkoflw_blk = BasicBlock::Create(jl_LLVMContext, "cond_resetstkoflw", f); - BasicBlock *resetstkoflw_blk = BasicBlock::Create(jl_LLVMContext, "resetstkoflw", f); - ctx.builder.CreateCondBr(isz, tryblk, cond_resetstkoflw_blk); - ctx.builder.SetInsertPoint(cond_resetstkoflw_blk); - ctx.builder.CreateCondBr(ctx.builder.CreateICmpEQ( - maybe_decay_untracked(literal_pointer_val(ctx, jl_stackovf_exception)), - ctx.builder.CreateLoad(emit_exc_in_transit(ctx), /*isvolatile*/true)), - resetstkoflw_blk, handlr); - ctx.builder.SetInsertPoint(resetstkoflw_blk); - ctx.builder.CreateCall(prepare_call(resetstkoflw_func), {}); - ctx.builder.CreateBr(handlr); -#else ctx.builder.CreateCondBr(isz, tryblk, handlr); -#endif ctx.builder.SetInsertPoint(tryblk); } else { @@ -7088,9 +7073,6 @@ static void init_julia_llvm_env(Module *m) add_named_global(jlenter_func, &jl_enter_handler); #ifdef _OS_WINDOWS_ - resetstkoflw_func = Function::Create(FunctionType::get(T_int32, false), - Function::ExternalLinkage, "_resetstkoflw", m); - add_named_global(resetstkoflw_func, &_resetstkoflw); #if defined(_CPU_X86_64_) juliapersonality_func = Function::Create(FunctionType::get(T_int32, true), Function::ExternalLinkage, "__julia_personality", m); diff --git a/src/interpreter.c b/src/interpreter.c index bdb2a396d46a5..6f3497306b499 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -690,10 +690,6 @@ SECT_INTERP static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s continue; } else { // a real exeception -#ifdef _OS_WINDOWS_ - if (jl_get_ptls_states()->exception_in_transit == jl_stackovf_exception) - _resetstkoflw(); -#endif ip = catch_ip; continue; } diff --git a/src/julia.h b/src/julia.h index 04f00c363ce62..01e2bfcaab0eb 100644 --- a/src/julia.h +++ b/src/julia.h @@ -31,7 +31,6 @@ #else # include "win32_ucontext.h" # define jl_jmp_buf jmp_buf -# include //for _resetstkoflw # define MAX_ALIGN 8 #endif @@ -1639,38 +1638,8 @@ JL_DLLEXPORT void JL_NORETURN jl_no_exc_handler(jl_value_t *e); #include "locks.h" // requires jl_task_t definition -STATIC_INLINE void jl_eh_restore_state(jl_handler_t *eh) -{ - jl_ptls_t ptls = jl_get_ptls_states(); - jl_task_t *current_task = ptls->current_task; - // `eh` may not be `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; - int8_t old_gc_state = ptls->gc_state; - current_task->eh = eh->prev; - ptls->pgcstack = eh->gcstack; -#ifdef JULIA_ENABLE_THREADING - arraylist_t *locks = ¤t_task->locks; - if (locks->len > eh->locks_len) { - for (size_t i = locks->len;i > eh->locks_len;i--) - jl_mutex_unlock_nogc((jl_mutex_t*)locks->items[i - 1]); - locks->len = eh->locks_len; - } -#endif - ptls->world_age = eh->world_age; - ptls->defer_signal = eh->defer_signal; - ptls->gc_state = eh->gc_state; - ptls->finalizers_inhibited = eh->finalizers_inhibited; - if (old_gc_state && !eh->gc_state) { - jl_gc_safepoint_(ptls); - } - if (old_defer_signal && !eh->defer_signal) { - jl_sigint_safepoint(ptls); - } -} - 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); #if defined(_OS_WINDOWS_) @@ -1715,18 +1684,9 @@ extern int had_exception; if (!jl_setjmp(__eh.eh_ctx,0)) \ for (i__tr=1; i__tr; i__tr=0, jl_eh_restore_state(&__eh)) -#define JL_EH_POP() jl_eh_restore_state(&__eh) - -#ifdef _OS_WINDOWS_ -#define JL_CATCH \ - else \ - for (i__ca=1, jl_eh_restore_state(&__eh); i__ca; i__ca=0) \ - if (((jl_get_ptls_states()->exception_in_transit==jl_stackovf_exception) && _resetstkoflw()) || 1) -#else #define JL_CATCH \ else \ for (i__ca=1, jl_eh_restore_state(&__eh); i__ca; i__ca=0) -#endif #endif diff --git a/src/julia_threads.h b/src/julia_threads.h index 7296a32bdf71f..d2673ea59a573 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -173,6 +173,9 @@ struct _jl_tls_states_t { // These are only used on unix now pthread_t system_id; void *signal_stack; +#endif +#ifdef _OS_WINDOWS_ + int needs_resetstkoflw; #endif // execution of certain certain impure // statements is prohibited from certain diff --git a/src/rtutils.c b/src/rtutils.c index 4cc71ef45f3a4..7fecdfdd32482 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -225,6 +225,42 @@ JL_DLLEXPORT void jl_enter_handler(jl_handler_t *eh) #endif } +STATIC_INLINE void jl_eh_restore_state(jl_handler_t *eh) +{ + jl_ptls_t ptls = jl_get_ptls_states(); +#ifdef _OS_WINDOWS_ + if (ptls->needs_resetstkoflw) { + _resetstkoflw(); + ptls->needs_resetstkoflw = 0; + } +#endif + jl_task_t *current_task = ptls->current_task; + // `eh` may not be `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; + int8_t old_gc_state = ptls->gc_state; + current_task->eh = eh->prev; + ptls->pgcstack = eh->gcstack; +#ifdef JULIA_ENABLE_THREADING + arraylist_t *locks = ¤t_task->locks; + if (locks->len > eh->locks_len) { + for (size_t i = locks->len;i > eh->locks_len;i--) + jl_mutex_unlock_nogc((jl_mutex_t*)locks->items[i - 1]); + locks->len = eh->locks_len; + } +#endif + ptls->world_age = eh->world_age; + ptls->defer_signal = eh->defer_signal; + ptls->gc_state = eh->gc_state; + ptls->finalizers_inhibited = eh->finalizers_inhibited; + if (old_gc_state && !eh->gc_state) { + jl_gc_safepoint_(ptls); + } + if (old_defer_signal && !eh->defer_signal) { + jl_sigint_safepoint(ptls); + } +} JL_DLLEXPORT void jl_pop_handler(int n) { jl_ptls_t ptls = jl_get_ptls_states(); diff --git a/src/signals-win.c b/src/signals-win.c index 2af450725c951..d45d8377a95d8 100644 --- a/src/signals-win.c +++ b/src/signals-win.c @@ -220,6 +220,7 @@ LONG WINAPI jl_exception_handler(struct _EXCEPTION_POINTERS *ExceptionInfo) jl_throw_in_ctx(jl_diverror_exception, ExceptionInfo->ContextRecord); return EXCEPTION_CONTINUE_EXECUTION; case EXCEPTION_STACK_OVERFLOW: + ptls->needs_resetstkoflw = 1; jl_throw_in_ctx(jl_stackovf_exception, ExceptionInfo->ContextRecord); return EXCEPTION_CONTINUE_EXECUTION; case EXCEPTION_ACCESS_VIOLATION: diff --git a/src/threading.c b/src/threading.c index ae315230c315c..869ffd6dffcdd 100644 --- a/src/threading.c +++ b/src/threading.c @@ -282,6 +282,9 @@ 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; +#ifdef _OS_WINDOWS_ + ptls->needs_resetstkoflw = 0; +#endif jl_init_thread_heap(ptls); jl_install_thread_signal_handler(ptls); From 57b46e79ceccb67f7d9ef64fa267dc2eff350c7b Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Thu, 19 Jul 2018 18:08:17 +1000 Subject: [PATCH 03/14] Lowering for exception stacks * A new lowered expression head :pop_exc is introduced and emitted in any location where a catch block exits normally (either by stepping out, or using return, break, goto). The semantics of :pop_exc are to pop the exception stack back to the state of the associated enter. * Make Expr(:enter) return a token which may be consumed by :pop_exc, thereby allowing the interpreter and codegen to know which :enter state should be used to pop the exception stack. I tried various alternatives for this association, but this was by far the nicest in terms of non-disruptive integration into the SSAIR processing code, and supporting both the interpreter and codegen. --- base/compiler/ssair/ir.jl | 2 +- base/compiler/ssair/slot2ssa.jl | 1 + base/compiler/validation.jl | 3 +- doc/src/devdocs/ast.md | 10 +++- src/ast.c | 2 + src/codegen.cpp | 7 +++ src/interpreter.c | 3 + src/julia-syntax.scm | 97 +++++++++++++++++++++------------ src/julia_internal.h | 1 + 9 files changed, 87 insertions(+), 39 deletions(-) diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 191d60927a43d..13673c96ec0ec 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -318,7 +318,7 @@ function is_relevant_expr(e::Expr) :gc_preserve_begin, :gc_preserve_end, :foreigncall, :isdefined, :copyast, :undefcheck, :throw_undef_if_not, - :cfunction, :method, + :cfunction, :method, :pop_exc, #=legacy IR format support=# :gotoifnot, :return) end diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index b88b88d20f8d2..ee79be37a23b9 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -802,6 +802,7 @@ function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::Do end elseif isexpr(stmt, :enter) new_code[idx] = Expr(:enter, block_for_inst(cfg, stmt.args[1])) + ssavalmap[idx] = SSAValue(idx) elseif isexpr(stmt, :leave) || isexpr(stmt, :(=)) || isexpr(stmt, :return) || isexpr(stmt, :meta) || isa(stmt, NewvarNode) new_code[idx] = stmt diff --git a/base/compiler/validation.jl b/base/compiler/validation.jl index 03f3d89038e0d..3b78e7b169fd0 100644 --- a/base/compiler/validation.jl +++ b/base/compiler/validation.jl @@ -16,6 +16,7 @@ const VALID_EXPR_HEADS = IdDict{Any,Any}( :the_exception => 0:0, :enter => 1:1, :leave => 1:1, + :pop_exc => 1:1, :inbounds => 1:1, :boundscheck => 0:0, :copyast => 1:1, @@ -139,7 +140,7 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo, is_top_ validate_val!(x.args[1]) elseif head === :call || head === :invoke || head == :gc_preserve_end || head === :meta || head === :inbounds || head === :foreigncall || head === :cfunction || - head === :const || head === :enter || head === :leave || + head === :const || head === :enter || head === :leave || head == :pop_exc || head === :method || head === :global || head === :static_parameter || head === :new || head === :thunk || head === :simdloop || head === :throw_undef_if_not || head === :unreachable diff --git a/doc/src/devdocs/ast.md b/doc/src/devdocs/ast.md index 8669b0fba3ff2..2c2aa4cc3cfdd 100644 --- a/doc/src/devdocs/ast.md +++ b/doc/src/devdocs/ast.md @@ -142,18 +142,22 @@ These symbols appear in the `head` field of `Expr`s in lowered form. * `the_exception` - Yields the caught exception inside a `catch` block. This is the value of the run time system variable - `jl_exception_in_transit`. + Yields the caught exception inside a `catch` block, as returned by `jl_current_exception()`. * `enter` Enters an exception handler (`setjmp`). `args[1]` is the label of the catch block to jump to on - error. + error. Yields a token which is consumed by `pop_exc`. * `leave` Pop exception handlers. `args[1]` is the number of handlers to pop. + * `pop_exc` + + Pop the stack of current exceptions back to the state at the associated `enter` when leaving a + catch block. `args[1]` contains the token from the associated `enter`. + * `inbounds` Controls turning bounds checks on or off. A stack is maintained; if the first argument of this diff --git a/src/ast.c b/src/ast.c index d758892961c86..752aa805c6954 100644 --- a/src/ast.c +++ b/src/ast.c @@ -38,6 +38,7 @@ jl_sym_t *lambda_sym; jl_sym_t *assign_sym; jl_sym_t *globalref_sym; jl_sym_t *do_sym; jl_sym_t *method_sym; jl_sym_t *core_sym; jl_sym_t *enter_sym; jl_sym_t *leave_sym; +jl_sym_t *pop_exc_sym; jl_sym_t *exc_sym; jl_sym_t *error_sym; jl_sym_t *new_sym; jl_sym_t *using_sym; jl_sym_t *const_sym; jl_sym_t *thunk_sym; @@ -342,6 +343,7 @@ void jl_init_frontend(void) exc_sym = jl_symbol("the_exception"); enter_sym = jl_symbol("enter"); leave_sym = jl_symbol("leave"); + pop_exc_sym = jl_symbol("pop_exc"); new_sym = jl_symbol("new"); const_sym = jl_symbol("const"); global_sym = jl_symbol("global"); diff --git a/src/codegen.cpp b/src/codegen.cpp index 761d8247a2d37..ccf5cf1acaf88 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3760,6 +3760,10 @@ static void emit_stmtpos(jl_codectx_t &ctx, jl_value_t *expr, int ssaval_result) ctx.builder.CreateCall(prepare_call(jlleave_func), ConstantInt::get(T_int32, jl_unbox_long(args[0]))); } + else if (head == pop_exc_sym) { + // FIXME + return; + } else { if (!jl_is_method(ctx.linfo->def.method)) { // TODO: inference is invalid if this has an effect @@ -4013,6 +4017,9 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) else if (head == leave_sym) { jl_error("Expr(:leave) in value position"); } + else if (head == pop_exc_sym) { + jl_error("Expr(:pop_exc) in value position"); + } else if (head == enter_sym) { jl_error("Expr(:enter) in value position"); } diff --git a/src/interpreter.c b/src/interpreter.c index 6f3497306b499..bbaaec49af6db 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -707,6 +707,9 @@ SECT_INTERP static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s s->continue_at = next_ip; jl_longjmp(eh->eh_ctx, 1); } + else if (head == pop_exc_sym) { + // FIXME + } else if (head == const_sym) { jl_sym_t *sym = (jl_sym_t*)jl_exprarg(stmt, 0); jl_module_t *modu = s->module; diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 9e846bb181f3d..2978e8ae05559 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -3396,13 +3396,14 @@ f(x) = yt(x) (arg-map #f) ;; map arguments to new names if they are assigned (label-counter 0) ;; counter for generating label addresses (label-map (table)) ;; maps label names to generated addresses - (label-level (table)) ;; exception handler level of each label + (label-nesting (table)) ;; exception handler and catch block nesting of each label (finally-handler #f) ;; `(var label map level)` where `map` is a list of `(tag . action)`. ;; to exit the current finally block, set `var` to integer `tag`, ;; jump to `label`, and put `(tag . action)` in the map, where `action` ;; is `(return x)`, `(break x)`, or a call to rethrow. (handler-goto-fixups '()) ;; `goto`s that might need `leave` exprs added - (handler-level 0)) ;; exception handler nesting depth + (handler-level 0) ;; exception handler nesting depth + (catch-token-stack '())) ;; tokens identifying handler enter for current catch blocks (define (emit c) (set! code (cons c code))) (define (make-label) @@ -3425,6 +3426,16 @@ f(x) = yt(x) (begin (emit `(leave ,(+ 1 (- handler-level (cadddr finally-handler))))) (emit `(goto ,(cadr finally-handler))))) tag)) + (define (pop-exc-expr src-tokens dest-tokens) + (if (eq? src-tokens dest-tokens) + #f + (let ((restore-token (let loop ((s src-tokens)) + (if (not (pair? s)) + (error "Attempt to jump into catch block")) + (if (eq? (cdr s) dest-tokens) + (car s) + (loop (cdr s)))))) + `(pop_exc ,restore-token)))) (define (emit-return x) (define (actually-return x) (let* ((x (if rett @@ -3432,6 +3443,8 @@ f(x) = yt(x) x)) (tmp (if (valid-ir-return? x) #f (make-ssavalue)))) (if tmp (emit `(= ,tmp ,x))) + (let ((pexc (pop-exc-expr catch-token-stack '()))) + (if pexc (emit pexc))) (emit `(return ,(or tmp x))))) (if x (if (> handler-level 0) @@ -3446,10 +3459,13 @@ f(x) = yt(x) (or tmp x)) (actually-return x)))) (define (emit-break labl) - (let ((lvl (caddr labl))) + (let ((lvl (caddr labl)) + (dest-tokens (cadddr labl))) (if (and finally-handler (> (cadddr finally-handler) lvl)) (leave-finally-block `(break ,labl)) (begin + (let ((pexc (pop-exc-expr catch-token-stack dest-tokens))) + (if pexc (emit pexc))) (if (> handler-level lvl) (emit `(leave ,(- handler-level lvl)))) (emit `(goto ,(cadr labl))))))) @@ -3684,7 +3700,7 @@ f(x) = yt(x) ((break-block) (let ((endl (make-label))) (compile (caddr e) - (cons (list (cadr e) endl handler-level) + (cons (list (cadr e) endl handler-level catch-token-stack) break-labels) #f #f) (mark-label endl)) @@ -3696,9 +3712,9 @@ f(x) = yt(x) (emit-break labl)))) ((label symboliclabel) (if (eq? (car e) 'symboliclabel) - (if (has? label-level (cadr e)) + (if (has? label-nesting (cadr e)) (error (string "label \"" (cadr e) "\" defined multiple times")) - (put! label-level (cadr e) handler-level))) + (put! label-nesting (cadr e) (list handler-level catch-token-stack)))) (let ((m (get label-map (cadr e) #f))) (if m (emit `(label ,m)) @@ -3714,27 +3730,31 @@ f(x) = yt(x) (emit `(null)) ;; save space for `leave` that might be needed (emit `(goto ,m)) (set! handler-goto-fixups - (cons (list code handler-level (cadr e)) handler-goto-fixups)) + (cons (list code handler-level catch-token-stack (cadr e)) handler-goto-fixups)) #f)) ;; exception handlers are lowered using - ;; (enter L) - push handler with catch block at label L + ;; (= tok (enter L)) - push handler with catch block at label L, yielding token ;; (leave n) - pop N exception handlers + ;; (pop_exc tok) - pop exception stack back to state of associated enter ((trycatch tryfinally) - (let ((catch (make-label)) + (let ((handler-token (make-ssavalue)) + (catch (make-label)) (endl (make-label)) (last-finally-handler finally-handler) (finally (if (eq? (car e) 'tryfinally) (new-mutable-var) #f)) (finally-exception (if (eq? (car e) 'tryfinally) (new-mutable-var) #f)) (my-finally-handler #f)) - (emit `(enter ,catch)) + ;; handler block entry + (emit `(= ,handler-token (enter ,catch))) (set! handler-level (+ handler-level 1)) (if finally (begin (set! my-finally-handler (list finally endl '() handler-level)) (set! finally-handler my-finally-handler) (emit `(= ,finally -1)))) - (let* ((v1 (compile (cadr e) break-labels value #f)) + (let* ((v1 (compile (cadr e) break-labels value #f)) ;; emit try block code (val (if (and value (not tail)) (new-mutable-var) #f))) + ;; handler block postfix (if (and val v1) (emit-assignment val v1)) (if tail (begin (if v1 (emit-return v1)) @@ -3742,19 +3762,15 @@ f(x) = yt(x) (begin (emit '(leave 1)) (emit `(goto ,endl)))) (set! handler-level (- handler-level 1)) + ;; emit either catch or finally block (mark-label catch) (emit `(leave 1)) (if finally - (begin (emit `(= ,finally-exception (the_exception))) - (leave-finally-block `(foreigncall 'jl_rethrow_other (top Bottom) (call (core svec) Any) - 'ccall 1 ,finally-exception) - #f)) - (let ((v2 (compile (caddr e) break-labels value tail))) - (if val (emit-assignment val v2)))) - (if endl (mark-label endl)) - (if finally - (begin (set! finally-handler last-finally-handler) + (begin (leave-finally-block '(call rethrow) #f) + (if endl (mark-label endl)) + (set! finally-handler last-finally-handler) (compile (caddr e) break-labels #f #f) + ;; emit actions to be taken at exit of finally block (let loop ((actions (caddr my-finally-handler))) (if (pair? actions) (let ((skip (if (and tail (null? (cdr actions)) @@ -3771,7 +3787,14 @@ f(x) = yt(x) (else ;; assumed to be a rethrow (emit ac)))) (if skip (mark-label skip)) - (loop (cdr actions))))))) + (loop (cdr actions)))))) + (begin (set! catch-token-stack (cons handler-token catch-token-stack)) + (let ((v2 (compile (caddr e) break-labels value tail))) + (if val (emit-assignment val v2)) + (if (not tail) (emit `(pop_exc ,handler-token))) + ;; else done in emit-return from compile + (if endl (mark-label endl))) + (set! catch-token-stack (cdr catch-token-stack)))) val))) ((newvar) @@ -3909,16 +3932,20 @@ f(x) = yt(x) (for-each (lambda (x) (let ((point (car x)) (hl (cadr x)) - (lab (caddr x))) - (let ((target-level (get label-level lab #f))) - (cond ((not target-level) - (error (string "label \"" lab "\" referenced but not defined"))) - ((> target-level hl) - (error (string "cannot goto label \"" lab "\" inside try/catch block"))) - ((= target-level hl) - (set-cdr! point (cddr point))) ;; remove empty slot - (else - (set-car! (cdr point) `(leave ,(- hl target-level)))))))) + (src-tokens (caddr x)) + (lab (cadddr x))) + (let ((target-nesting (get label-nesting lab #f))) + (if (not target-nesting) + (error (string "label \"" lab "\" referenced but not defined"))) + (let ((target-level (car target-nesting))) + (cond ((> target-level hl) + (error (string "cannot goto label \"" lab "\" inside try/catch block"))) + ((= target-level hl) + (set-cdr! point (cddr point))) ;; remove empty slot + (else + (set-car! (cdr point) `(leave ,(- hl target-level)))))) + (let ((pexc (pop-exc-expr src-tokens (cadr target-nesting)))) + (if pexc (set-cdr! point (cons pexc (cdr point)))))))) handler-goto-fixups) (if global-const-error (error (string "`global const` delcaration not allowed inside function" (format-loc global-const-error)))) @@ -3966,12 +3993,14 @@ f(x) = yt(x) (let ((vinf (var-info-for (cadr e) vi))) (if (and vinf (not (vinfo:capt vinf))) (put! vars (cadr e) #t)))) + ((and (pair? e) (or (memq (car e) '(goto gotoifnot)) + (and (eq? (car e) '=) (pair? (caddr e)) + (eq? (car (caddr e)) 'enter)))) + (set! vars (table))) ((and (pair? e) (eq? (car e) '=)) (if (has? vars (cadr e)) (begin (del! vars (cadr e)) - (put! di (cadr e) #t)))) - ((and (pair? e) (memq (car e) '(goto gotoifnot enter))) - (set! vars (table))))) + (put! di (cadr e) #t)))))) (loop (cdr stmts))))))) ;; pass 6: renumber slots and labels diff --git a/src/julia_internal.h b/src/julia_internal.h index e77ebbb266185..b68aa6640be3c 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -947,6 +947,7 @@ extern jl_sym_t *method_sym; extern jl_sym_t *core_sym; extern jl_sym_t *enter_sym; extern jl_sym_t *leave_sym; extern jl_sym_t *exc_sym; extern jl_sym_t *error_sym; extern jl_sym_t *new_sym; extern jl_sym_t *using_sym; +extern jl_sym_t *pop_exc_sym; extern jl_sym_t *const_sym; extern jl_sym_t *thunk_sym; extern jl_sym_t *abstracttype_sym; extern jl_sym_t *primtype_sym; extern jl_sym_t *structtype_sym; extern jl_sym_t *foreigncall_sym; From 31e76c2dc856af4cd05db7780c25c29ed3245b27 Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Sat, 21 Jul 2018 17:04:13 +1000 Subject: [PATCH 04/14] 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(); } From 7407b3bf48a235f7a3e39ba9f3431f10927b910e Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Sun, 22 Jul 2018 23:13:30 +1000 Subject: [PATCH 05/14] catch_stack() for julia level exception stack access + tests --- base/error.jl | 24 +++++ base/exports.jl | 1 + src/stackwalk.c | 34 +++++++ test/choosetests.jl | 2 +- test/exceptions.jl | 210 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 test/exceptions.jl diff --git a/base/error.jl b/base/error.jl index 3282323d0be50..8bb23eba48e38 100644 --- a/base/error.jl +++ b/base/error.jl @@ -91,6 +91,30 @@ function catch_backtrace() return _reformat_bt(bt[], bt2[]) end +""" + catch_stack(task=current_task(); [inclue_bt=true]) + +Get the stack of exceptions currently being handled. For nested catch blocks +there may be more than one current exception in which case the most recently +thrown exception is last in the stack. The stack is returned as a Vector of +`(exception,backtrace)` pairs, or a Vector of exceptions if `include_bt` is +false. + +Explicitly passing `task` will return the current exception stack on an +arbitrary task. This is useful for inspecting tasks which have failed due to +uncaught exceptions. +""" +function catch_stack(task=current_task(); include_bt=true) + raw = ccall(:jl_get_exc_stack, Any, (Any,Cint,Cint), task, include_bt, typemax(Cint)) + formatted = Any[] + stride = include_bt ? 3 : 1 + for i = reverse(1:stride:length(raw)) + e = raw[i] + push!(formatted, include_bt ? (e,Base._reformat_bt(raw[i+1],raw[i+2])) : e) + end + formatted +end + ## keyword arg lowering generates calls to this ## function kwerr(kw, args::Vararg{Any,N}) where {N} @_noinline_meta diff --git a/base/exports.jl b/base/exports.jl index e7cdc8808100b..8f7aa256372d8 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -678,6 +678,7 @@ export # errors backtrace, catch_backtrace, + catch_stack, error, rethrow, retry, diff --git a/src/stackwalk.c b/src/stackwalk.c index 0c288330663a7..09fd03be0b155 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -177,6 +177,40 @@ JL_DLLEXPORT void jl_get_backtrace(jl_array_t **btout, jl_array_t **bt2out) decode_backtrace(bt_data, bt_size, btout, bt2out); } +// Return data from the exception stack for `task` as an array of Any, starting +// with the top of the stack and returning up to `max_entries`. If requested by +// setting the `include_bt` flag, backtrace data in bt,bt2 format is +// interleaved. +JL_DLLEXPORT jl_value_t *jl_get_exc_stack(jl_value_t* task, int include_bt, int max_entries) +{ + jl_array_t *stack = NULL; + jl_array_t *bt = NULL; + jl_array_t *bt2 = NULL; + JL_GC_PUSH3(&stack, &bt, &bt2); + stack = jl_alloc_array_1d(jl_array_any_type, 0); + if (!jl_typeis(task, jl_task_type)) + jl_error("Cannot get exception stack from a non-Task type"); + jl_exc_stack_t *s = ((jl_task_t*)task)->exc_stack; + if (!s) + return (jl_value_t*)stack; + size_t itr = s->top; + int i = 0; + while (itr > 0 && i < max_entries) { + jl_array_ptr_1d_push(stack, jl_exc_stack_exception(s, itr)); + if (include_bt) { + decode_backtrace(jl_exc_stack_bt_data(s, itr), + jl_exc_stack_bt_size(s, itr), + &bt, &bt2); + jl_array_ptr_1d_push(stack, (jl_value_t*)bt); + jl_array_ptr_1d_push(stack, (jl_value_t*)bt2); + } + itr = jl_exc_stack_next(s, itr); + i++; + } + JL_GC_POP(); + return (jl_value_t*)stack; +} + #if defined(_OS_WINDOWS_) #ifdef _CPU_X86_64_ static UNWIND_HISTORY_TABLE HistoryTable; diff --git a/test/choosetests.jl b/test/choosetests.jl index 1ce2591aff61b..2f9c5cd000bf2 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -41,7 +41,7 @@ function choosetests(choices = []) "intfuncs", "simdloop", "vecelement", "rational", "bitarray", "copy", "math", "fastmath", "functional", "iterators", "operators", "path", "ccall", "parse", "loading", "bigint", - "sorting", "spawn", "backtrace", + "sorting", "spawn", "backtrace", "exceptions", "file", "read", "version", "namedtuple", "mpfr", "broadcast", "complex", "floatapprox", "stdlib", "reflection", "regex", "float16", diff --git a/test/exceptions.jl b/test/exceptions.jl new file mode 100644 index 0000000000000..24eec56d3e1d4 --- /dev/null +++ b/test/exceptions.jl @@ -0,0 +1,210 @@ +using Test + +@testset "Exception stack nesting" begin + # Basic exception stack handling + try + error("A") + catch + @test length(catch_stack()) == 1 + end + @test length(catch_stack()) == 0 + try + try + error("A") + finally + @test length(catch_stack()) == 1 + end + catch + @test length(catch_stack()) == 1 + end + # Errors stack up + try + error("RootCause") + catch + @test length(catch_stack()) == 1 + try + error("B") + catch + stack = catch_stack() + @test length(stack) == 2 + @test stack[1][1].msg == "RootCause" + @test stack[2][1].msg == "B" + end + # Stack pops correctly + stack = catch_stack() + @test length(stack) == 1 + @test stack[1][1].msg == "RootCause" + end + # Lowering - value position + val = try + error("A") + catch + @test length(catch_stack()) == 1 + 1 + end + @test val == 1 + function test_exc_stack_tailpos() + # exercise lowering code path for tail position + try + error("A") + catch + length(catch_stack()) + end + end + @test test_exc_stack_tailpos() == 1 + @test length(catch_stack()) == 0 +end + +@testset "Exception stacks and gotos" begin + function test_exc_stack_catch_return() + try + error("A") + catch + @test length(catch_stack()) == 1 + return + end + end + test_exc_stack_catch_return() + for i=1:1 + try + error("A") + catch + @test length(catch_stack()) == 1 + break + end + end + for i=1:1 + try + error("A") + catch + @test length(catch_stack()) == 1 + continue + end + end + try + error("A") + catch + @test length(catch_stack()) == 1 + @goto outofcatch + end + @label outofcatch + @test length(catch_stack()) == 0 +end + +@testset "Deep exception stacks" begin + function test_exc_stack_deep(n) + # Generate deep exception stack with recursive handlers + # Note that if you let this overflow the program stack (not the exception + # stack) julia will crash. See #28577 + n != 1 || error("RootCause") + try + test_exc_stack_deep(n-1) + catch + error("n==$n") + end + end + @test try + test_exc_stack_deep(100) + catch + length(catch_stack()) + end == 100 + @test length(catch_stack()) == 0 +end + +@testset "Exception stacks and Tasks" begin + # See #12485 + try + error("A") + catch + t = @task try + error("B") + catch ex + ex + end + yield(t) + @test t.state == :done + @test t.result == ErrorException("B") + # Task exception state is preserved around task switches + @test length(catch_stack()) == 1 + @test catch_stack()[1][1] == ErrorException("A") + end + @test length(catch_stack()) == 0 + # test rethrow() rethrows correct state + bt = [] + try + try + error("A") + catch + bt = catch_backtrace() + t = @task try + error("B") + catch ex + ex + end + yield(t) + @test t.state == :done + @test t.result == ErrorException("B") + @test bt == catch_backtrace() + rethrow() + end + catch exc + @test exc == ErrorException("A") + @test bt == catch_backtrace() + end + @test length(catch_stack()) == 0 + # test rethrow with argument + bt = [] + try + try + error("A") + catch + t = @task try + error("B") + catch ex + ex + end + yield(t) + @test t.state == :done + @test t.result == ErrorException("B") + bt = catch_backtrace() + rethrow(ErrorException("C")) + end + catch exc + @test exc == ErrorException("C") + @test bt == catch_backtrace() + end + @test length(catch_stack()) == 0 + # Exception stacks on other tasks + t = @task try + error("A") + catch + error("B") + end + yield(t) + @test t.state == :failed + @test t.result == ErrorException("B") + @test catch_stack(t, include_bt=false) == [ErrorException("A"), ErrorException("B")] + # Exception stacks for tasks which never get the chance to start + t = @task nothing + @test try + @async Base.throwto(t, ErrorException("expected")) + wait(t) + catch e + e + end == ErrorException("expected") + @test length(catch_stack(t)) == 1 + @test length(catch_stack(t)[1][2]) > 0 # backtrace is nonempty +end + +@testset "rethrow" begin + @test try + rethrow() + catch ex + ex + end == ErrorException("rethrow() not allowed outside a catch block") + @test try + rethrow(ErrorException("A")) + catch ex + ex + end == ErrorException("rethrow(exc) not allowed outside a catch block") +end From c4132dc658ed1c93dba5635e1b461600a245dafe Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Sat, 6 Oct 2018 16:08:16 +1000 Subject: [PATCH 06/14] Fix some GC rooting problems, add GC-SA annotations Add missing GC static analyzer annotations to exception stack functions and fix two problems found by the GC static analyser: * An early return with missing GC_POP * Missing root for the exception across a safe point in throw_internal --- src/jlapi.c | 4 +++- src/julia.h | 2 +- src/julia_internal.h | 16 +++++++++++----- src/rtutils.c | 19 +++++-------------- src/stackwalk.c | 18 ++++++++---------- src/task.c | 8 ++++---- 6 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/jlapi.c b/src/jlapi.c index 247b1638d5146..1252bbb4a5e62 100644 --- a/src/jlapi.c +++ b/src/jlapi.c @@ -102,7 +102,9 @@ JL_DLLEXPORT jl_value_t *jl_eval_string(const char *str) return r; } -JL_DLLEXPORT jl_value_t *jl_current_exception(void) +// FIXME: annotating this with JL_GLOBALLY_ROOTED is over optimistic. It's +// rooted by the task which is rooted in the TLS. +JL_DLLEXPORT jl_value_t *jl_current_exception(void) JL_GLOBALLY_ROOTED { 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; diff --git a/src/julia.h b/src/julia.h index 427622c8bc89a..3859514dc8d58 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1428,7 +1428,7 @@ 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_current_exception(void) JL_GLOBALLY_ROOTED; JL_DLLEXPORT jl_value_t *jl_exception_occurred(void); JL_DLLEXPORT void jl_exception_clear(void) JL_NOTSAFEPOINT; diff --git a/src/julia_internal.h b/src/julia_internal.h index edf35628b20b9..7774a277777a2 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -667,17 +667,23 @@ typedef struct _jl_exc_stack_t { // 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]) +static inline jl_value_t *jl_exc_stack_exception(jl_exc_stack_t *stack JL_PROPAGATES_ROOT, + size_t itr) JL_NOTSAFEPOINT +{ + return (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, +void jl_reserve_exc_stack(jl_exc_stack_t **stack JL_REQUIRE_ROOTED_SLOT, + size_t reserved_size); +void jl_push_exc_stack(jl_exc_stack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, + jl_value_t *exception JL_ROOTED_ARGUMENT, 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); +void jl_copy_exc_stack(jl_exc_stack_t *dest, jl_exc_stack_t *src) JL_NOTSAFEPOINT; // timers // Returns time in nanosec diff --git a/src/rtutils.c b/src/rtutils.c index 3eb7564ee881c..3dce025e06dde 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -297,14 +297,15 @@ JL_DLLEXPORT void jl_restore_exc_stack(size_t state) } } -void jl_copy_exc_stack(jl_exc_stack_t *dest, jl_exc_stack_t *src) +void jl_copy_exc_stack(jl_exc_stack_t *dest, jl_exc_stack_t *src) JL_NOTSAFEPOINT { 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) +void jl_reserve_exc_stack(jl_exc_stack_t **stack JL_REQUIRE_ROOTED_SLOT, + size_t reserved_size) { jl_exc_stack_t *s = *stack; if (s && s->reserved_size >= reserved_size) @@ -318,7 +319,8 @@ void jl_reserve_exc_stack(jl_exc_stack_t **stack, size_t reserved_size) *stack = new_s; } -void jl_push_exc_stack(jl_exc_stack_t **stack, jl_value_t *exception, +void jl_push_exc_stack(jl_exc_stack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, + jl_value_t *exception JL_ROOTED_ARGUMENT, uintptr_t *bt_data, size_t bt_size) { jl_reserve_exc_stack(stack, (*stack ? (*stack)->top : 0) + bt_size + 2); @@ -329,17 +331,6 @@ void jl_push_exc_stack(jl_exc_stack_t **stack, jl_value_t *exception, 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; - } - stack->top = top; - assert(n == 0); -} - // misc ----------------------------------------------------------------------- // perform f(args...) on stack diff --git a/src/stackwalk.c b/src/stackwalk.c index 09fd03be0b155..b8a524eee165f 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -183,28 +183,26 @@ JL_DLLEXPORT void jl_get_backtrace(jl_array_t **btout, jl_array_t **bt2out) // interleaved. JL_DLLEXPORT jl_value_t *jl_get_exc_stack(jl_value_t* task, int include_bt, int max_entries) { + if (!jl_typeis(task, jl_task_type)) + jl_error("Cannot get exception stack from a non-Task type"); jl_array_t *stack = NULL; jl_array_t *bt = NULL; jl_array_t *bt2 = NULL; JL_GC_PUSH3(&stack, &bt, &bt2); stack = jl_alloc_array_1d(jl_array_any_type, 0); - if (!jl_typeis(task, jl_task_type)) - jl_error("Cannot get exception stack from a non-Task type"); - jl_exc_stack_t *s = ((jl_task_t*)task)->exc_stack; - if (!s) - return (jl_value_t*)stack; - size_t itr = s->top; + jl_exc_stack_t *exc_stack = ((jl_task_t*)task)->exc_stack; + size_t itr = exc_stack ? exc_stack->top : 0; int i = 0; while (itr > 0 && i < max_entries) { - jl_array_ptr_1d_push(stack, jl_exc_stack_exception(s, itr)); + jl_array_ptr_1d_push(stack, jl_exc_stack_exception(exc_stack, itr)); if (include_bt) { - decode_backtrace(jl_exc_stack_bt_data(s, itr), - jl_exc_stack_bt_size(s, itr), + decode_backtrace(jl_exc_stack_bt_data(exc_stack, itr), + jl_exc_stack_bt_size(exc_stack, itr), &bt, &bt2); jl_array_ptr_1d_push(stack, (jl_value_t*)bt); jl_array_ptr_1d_push(stack, (jl_value_t*)bt2); } - itr = jl_exc_stack_next(s, itr); + itr = jl_exc_stack_next(exc_stack, itr); i++; } JL_GC_POP(); diff --git a/src/task.c b/src/task.c index adeb72019a615..2300d15216254 100644 --- a/src/task.c +++ b/src/task.c @@ -364,17 +364,16 @@ void JL_NORETURN throw_internal(jl_value_t *exception JL_MAYBE_UNROOTED) ptls->io_wait = 0; if (ptls->safe_restore) jl_longjmp(*ptls->safe_restore, 1); + JL_GC_PUSH1(&exception); 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; @@ -395,7 +394,7 @@ void JL_NORETURN throw_internal(jl_value_t *exception JL_MAYBE_UNROOTED) } // record backtrace and raise an error -JL_DLLEXPORT void jl_throw(jl_value_t *e) +JL_DLLEXPORT void jl_throw(jl_value_t *e JL_MAYBE_UNROOTED) { jl_ptls_t ptls = jl_get_ptls_states(); assert(e != NULL); @@ -426,7 +425,7 @@ JL_DLLEXPORT void jl_sig_throw(void) throw_internal(e); } -JL_DLLEXPORT void jl_rethrow_other(jl_value_t *e) +JL_DLLEXPORT void jl_rethrow_other(jl_value_t *e JL_MAYBE_UNROOTED) { // TODO: Should uses of `rethrow(exc)` be replaced with a normal throw, now // that exception stacks allow root cause analysis? @@ -435,6 +434,7 @@ JL_DLLEXPORT void jl_rethrow_other(jl_value_t *e) 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; + JL_GC_PROMISE_ROOTED(e); throw_internal(NULL); } From 2fd3212c4dac4685d8b0382db1b629848eb72373 Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Wed, 10 Oct 2018 23:01:19 +1000 Subject: [PATCH 07/14] Use JL_BT_INTERP_FRAME for interpreter frame marker in bt_data --- src/gc.c | 8 ++++---- src/interpreter-stacktrace.c | 2 +- src/julia_internal.h | 2 ++ src/stackwalk.c | 31 ++++++++++++++++++------------- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/gc.c b/src/gc.c index 12e50d6e09a49..3c7efb734a939 100644 --- a/src/gc.c +++ b/src/gc.c @@ -2078,20 +2078,20 @@ excstack: { 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) { + if (bt_data[i] != JL_BT_INTERP_FRAME) { i++; continue; } // found an interpreter frame to mark new_obj = (jl_value_t*)bt_data[i+1]; uintptr_t nptr = 0; + i += 3; if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) { - stackitr->i = i + 3; + stackitr->i = i; 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); @@ -2660,7 +2660,7 @@ static void jl_gc_queue_bt_buf(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp { size_t n = 0; while (n+2 < ptls2->bt_size) { - if (ptls2->bt_data[n] == (uintptr_t)-1) { + if (ptls2->bt_data[n] == JL_BT_INTERP_FRAME) { gc_mark_queue_obj(gc_cache, sp, (jl_value_t*)ptls2->bt_data[n+1]); n += 2; } diff --git a/src/interpreter-stacktrace.c b/src/interpreter-stacktrace.c index c717624ffc1ae..c15fbbc6f4ef9 100644 --- a/src/interpreter-stacktrace.c +++ b/src/interpreter-stacktrace.c @@ -400,7 +400,7 @@ JL_DLLEXPORT size_t jl_capture_interp_frame(uintptr_t *data, uintptr_t sp, uintp if (space_remaining <= 1) return 0; // Sentinel value to indicate an interpreter frame - data[0] = (uintptr_t)-1; + data[0] = JL_BT_INTERP_FRAME; data[1] = s->mi ? (uintptr_t)s->mi : s->src ? (uintptr_t)s->src : (uintptr_t)jl_nothing; data[2] = (uintptr_t)s->ip; return 2; diff --git a/src/julia_internal.h b/src/julia_internal.h index 7774a277777a2..094db30bb87a0 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -630,6 +630,8 @@ typedef unw_cursor_t bt_cursor_t; typedef int bt_context_t; typedef int bt_cursor_t; #endif +// Special marker in backtrace data for encoding interpreter frames +#define JL_BT_INTERP_FRAME (((uintptr_t)0)-1) size_t rec_backtrace(uintptr_t *data, size_t maxsize) JL_NOTSAFEPOINT; size_t rec_backtrace_ctx(uintptr_t *data, size_t maxsize, bt_context_t *ctx) JL_NOTSAFEPOINT; #ifdef LIBOSXUNWIND diff --git a/src/stackwalk.c b/src/stackwalk.c index b8a524eee165f..4b7748dec4c11 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -127,7 +127,7 @@ JL_DLLEXPORT jl_value_t *jl_backtrace_from_here(int returnsp) n = 0; while (n < jl_array_len(ip)) { - if ((uintptr_t)jl_array_ptr_ref(ip, n) == (uintptr_t)-1) { + if ((uintptr_t)jl_array_ptr_ref(ip, n) == JL_BT_INTERP_FRAME) { jl_array_ptr_1d_push(bt2, jl_array_ptr_ref(ip, n+1)); n += 2; } @@ -154,7 +154,7 @@ void decode_backtrace(uintptr_t *bt_data, size_t bt_size, // Scan the stack for any interpreter frames size_t n = 0; while (n < bt_size) { - if (bt_data[n] == (uintptr_t)-1) { + if (bt_data[n] == JL_BT_INTERP_FRAME) { jl_array_ptr_1d_push(bt2, (jl_value_t*)bt_data[n+1]); n += 2; } @@ -326,9 +326,8 @@ static int jl_unw_step(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, uintpt *sp = (uintptr_t)cursor->stackframe.AddrStack.Offset; if (fp) *fp = (uintptr_t)cursor->stackframe.AddrFrame.Offset; - if (*ip == 0 || *ip == ((uintptr_t)0)-1) { - // -1 is a special marker in the backtrace, - // don't leave it in there since it can corrupt the GC. + if (*ip == 0 || *ip == JL_BT_INTERP_FRAME) { + // don't leave special marker in the bt data as it can corrupt the GC. *ip = 0; if (!readable_pointer((LPCVOID)*sp)) return 0; @@ -345,9 +344,8 @@ static int jl_unw_step(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, uintpt *sp = (uintptr_t)cursor->Rsp; if (fp) *fp = (uintptr_t)cursor->Rbp; - if (*ip == 0 || *ip == ((uintptr_t)0)-1) { - // -1 is a special marker in the backtrace, - // don't leave it in there since it can corrupt the GC. + if (*ip == 0 || *ip == JL_BT_INTERP_FRAME) { + // don't leave special marker in the bt data as it can corrupt the GC. *ip = 0; if (!readable_pointer((LPCVOID)*sp)) return 0; @@ -401,9 +399,8 @@ static int jl_unw_step(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, uintpt unw_word_t reg; if (unw_get_reg(cursor, UNW_REG_IP, ®) < 0) return 0; - // -1 is a special marker in the backtrace, - // don't leave it in there since it can corrupt the GC. - *ip = reg == (uintptr_t)-1 ? 0 : reg; + // don't leave special marker in the bt data as it can corrupt the GC. + *ip = reg == JL_BT_INTERP_FRAME ? 0 : reg; if (unw_get_reg(cursor, UNW_REG_SP, ®) < 0) return 0; *sp = reg; @@ -522,8 +519,16 @@ JL_DLLEXPORT void jlbacktrace(void) 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); + for (size_t i = 0; i < bt_size; ) { + if (bt_data[i] == JL_BT_INTERP_FRAME) { + jl_safe_printf("Interpreter frame (ip: %d)\n", (int)bt_data[i+2]); + jl_static_show(JL_STDERR, (jl_value_t*)bt_data[i+1]); + i += 3; + } else { + jl_gdblookup(bt_data[i] - 1); + i += 1; + } + } } #ifdef __cplusplus From 250571c11628ce2cb290ebe1498e02165222be54 Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Thu, 11 Oct 2018 20:31:11 +1000 Subject: [PATCH 08/14] Workaround gcc-5 miscompilation with asserts and double return functions gcc-5 appears to miscompile jl_type_infer under special circumstances, leading to segfaults but gcc-7 and clang do fine on this. The "special circumstances" are that assertions are enabled when preprocessing jl_svecref for use within jl_type_infer specifically. There are various ways to remove the segfaults and force a correct compilation: * The given rearrangement * Replacing jl_svecref in the last part of jl_type_infer with an equivalent function, but without the assertions. * Removing the JL_TRY / JL_CATCH further up. It's suspicious that the error only occurs when both the double-return __sigsetjmp() and the no-return __assert_fail() are present inside the function in sufficient quantities. Perhaps the compiler is failing to analyze the interaction between these. --- src/gf.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/gf.c b/src/gf.c index 4865c740f4e02..ae4a1413b7849 100644 --- a/src/gf.c +++ b/src/gf.c @@ -287,12 +287,13 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t **pli JL_ROOTS_TEMPORARILY, s in_inference--; li->inInference = 0; - if (linfo_src && - jl_is_svec(linfo_src) && jl_svec_len(linfo_src) == 2 && - jl_is_method_instance(jl_svecref(linfo_src, 0)) && - jl_is_code_info(jl_svecref(linfo_src, 1))) { - *pli = (jl_method_instance_t*)jl_svecref(linfo_src, 0); - src = (jl_code_info_t*)jl_svecref(linfo_src, 1); + if (linfo_src && jl_is_svec(linfo_src) && jl_svec_len(linfo_src) == 2) { + jl_value_t *mi = jl_svecref(linfo_src, 0); + jl_value_t *ci = jl_svecref(linfo_src, 1); + if (jl_is_method_instance(mi) && jl_is_code_info(ci)) { + *pli = (jl_method_instance_t*)mi; + src = (jl_code_info_t*)ci; + } } JL_GC_POP(); #endif From 3453c27db9494afd8cc6f89b0ca19222fa64ca35 Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Sun, 14 Oct 2018 16:00:36 +1000 Subject: [PATCH 09/14] Exception stack runtime cleanup * Use functions rather than macros for exception stack access * Use standard type check macro * More clearly document gc rooting of `jl_current_exception()` --- src/jlapi.c | 2 -- src/julia.h | 11 ++++++++--- src/julia_internal.h | 31 +++++++++++++++++++++++-------- src/stackwalk.c | 3 +-- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/jlapi.c b/src/jlapi.c index 1252bbb4a5e62..8bfb433d960c7 100644 --- a/src/jlapi.c +++ b/src/jlapi.c @@ -102,8 +102,6 @@ JL_DLLEXPORT jl_value_t *jl_eval_string(const char *str) return r; } -// FIXME: annotating this with JL_GLOBALLY_ROOTED is over optimistic. It's -// rooted by the task which is rooted in the TLS. JL_DLLEXPORT jl_value_t *jl_current_exception(void) JL_GLOBALLY_ROOTED { jl_exc_stack_t *s = jl_get_ptls_states()->current_task->exc_stack; diff --git a/src/julia.h b/src/julia.h index 3859514dc8d58..4bb92a47b2fff 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1425,9 +1425,14 @@ 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. + +// Return the exception currently being handled, or `jl_nothing`. +// +// The catch scope is determined dynamically so this works in functions called +// from a catch block. The returned value is gc rooted until we exit the +// enclosing JL_CATCH. +// FIXME: Teach the static analyzer about this rather than using +// JL_GLOBALLY_ROOTED which is far too optimistic. JL_DLLEXPORT jl_value_t *jl_current_exception(void) JL_GLOBALLY_ROOTED; JL_DLLEXPORT jl_value_t *jl_exception_occurred(void); JL_DLLEXPORT void jl_exception_clear(void) JL_NOTSAFEPOINT; diff --git a/src/julia_internal.h b/src/julia_internal.h index 094db30bb87a0..d1bd5bff2b88d 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -660,26 +660,41 @@ 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. +// The stack may be traversed and accessed with the functions 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. + // Layout: [bt_data1... bt_size1 exc1 bt_data2... bt_size2 exc2 ..] // 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 -static inline jl_value_t *jl_exc_stack_exception(jl_exc_stack_t *stack JL_PROPAGATES_ROOT, +STATIC_INLINE uintptr_t *jl_excstk_raw(jl_exc_stack_t* stack) +{ + return (uintptr_t*)(stack + 1); +} + +// Exception stack access +STATIC_INLINE jl_value_t *jl_exc_stack_exception(jl_exc_stack_t *stack JL_PROPAGATES_ROOT, size_t itr) JL_NOTSAFEPOINT { - return (jl_value_t*)jl_excstk_raw(stack)[(itr)-1]; + return (jl_value_t*)(jl_excstk_raw(stack)[itr-1]); +} +STATIC_INLINE size_t jl_exc_stack_bt_size(jl_exc_stack_t *stack, size_t itr) +{ + return jl_excstk_raw(stack)[itr-2]; +} +STATIC_INLINE uintptr_t *jl_exc_stack_bt_data(jl_exc_stack_t *stack, size_t itr) +{ + return jl_excstk_raw(stack) + itr-2 - jl_exc_stack_bt_size(stack, itr); } -#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)) +STATIC_INLINE size_t jl_exc_stack_next(jl_exc_stack_t *stack, size_t itr) +{ + return itr-2 - jl_exc_stack_bt_size(stack, itr); +} +// Exception stack manipulation void jl_reserve_exc_stack(jl_exc_stack_t **stack JL_REQUIRE_ROOTED_SLOT, size_t reserved_size); void jl_push_exc_stack(jl_exc_stack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, diff --git a/src/stackwalk.c b/src/stackwalk.c index 4b7748dec4c11..fe3e6f5c60d20 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -183,8 +183,7 @@ JL_DLLEXPORT void jl_get_backtrace(jl_array_t **btout, jl_array_t **bt2out) // interleaved. JL_DLLEXPORT jl_value_t *jl_get_exc_stack(jl_value_t* task, int include_bt, int max_entries) { - if (!jl_typeis(task, jl_task_type)) - jl_error("Cannot get exception stack from a non-Task type"); + JL_TYPECHK(catch_stack, task, task); jl_array_t *stack = NULL; jl_array_t *bt = NULL; jl_array_t *bt2 = NULL; From 1753529f89f8f304f55cb9aa15eadb73c673dc12 Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Sun, 14 Oct 2018 16:41:58 +1000 Subject: [PATCH 10/14] Rename Expr(:pop_exc) to Expr(:pop_exception) Better be explicit here for something that's not commonly encountered. --- base/compiler/ssair/ir.jl | 2 +- base/compiler/ssair/slot2ssa.jl | 2 +- base/compiler/validation.jl | 4 ++-- doc/src/devdocs/ast.md | 4 ++-- src/ast.c | 4 ++-- src/codegen.cpp | 9 ++++----- src/interpreter.c | 4 ++-- src/julia-syntax.scm | 6 +++--- src/julia_internal.h | 2 +- src/task.c | 4 ++-- 10 files changed, 20 insertions(+), 21 deletions(-) diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index 13673c96ec0ec..36b90f4089c8d 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -318,7 +318,7 @@ function is_relevant_expr(e::Expr) :gc_preserve_begin, :gc_preserve_end, :foreigncall, :isdefined, :copyast, :undefcheck, :throw_undef_if_not, - :cfunction, :method, :pop_exc, + :cfunction, :method, :pop_exception, #=legacy IR format support=# :gotoifnot, :return) end diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index ee79be37a23b9..c1dd86e1fa20f 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -802,7 +802,7 @@ function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::Do end elseif isexpr(stmt, :enter) new_code[idx] = Expr(:enter, block_for_inst(cfg, stmt.args[1])) - ssavalmap[idx] = SSAValue(idx) + ssavalmap[idx] = SSAValue(idx) # Slot to store token for pop_exception elseif isexpr(stmt, :leave) || isexpr(stmt, :(=)) || isexpr(stmt, :return) || isexpr(stmt, :meta) || isa(stmt, NewvarNode) new_code[idx] = stmt diff --git a/base/compiler/validation.jl b/base/compiler/validation.jl index 3b78e7b169fd0..567d06861ff1f 100644 --- a/base/compiler/validation.jl +++ b/base/compiler/validation.jl @@ -16,7 +16,7 @@ const VALID_EXPR_HEADS = IdDict{Any,Any}( :the_exception => 0:0, :enter => 1:1, :leave => 1:1, - :pop_exc => 1:1, + :pop_exception => 1:1, :inbounds => 1:1, :boundscheck => 0:0, :copyast => 1:1, @@ -140,7 +140,7 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo, is_top_ validate_val!(x.args[1]) elseif head === :call || head === :invoke || head == :gc_preserve_end || head === :meta || head === :inbounds || head === :foreigncall || head === :cfunction || - head === :const || head === :enter || head === :leave || head == :pop_exc || + head === :const || head === :enter || head === :leave || head == :pop_exception || head === :method || head === :global || head === :static_parameter || head === :new || head === :thunk || head === :simdloop || head === :throw_undef_if_not || head === :unreachable diff --git a/doc/src/devdocs/ast.md b/doc/src/devdocs/ast.md index 2c2aa4cc3cfdd..699a2b00d8d17 100644 --- a/doc/src/devdocs/ast.md +++ b/doc/src/devdocs/ast.md @@ -147,13 +147,13 @@ These symbols appear in the `head` field of `Expr`s in lowered form. * `enter` Enters an exception handler (`setjmp`). `args[1]` is the label of the catch block to jump to on - error. Yields a token which is consumed by `pop_exc`. + error. Yields a token which is consumed by `pop_exception`. * `leave` Pop exception handlers. `args[1]` is the number of handlers to pop. - * `pop_exc` + * `pop_exception` Pop the stack of current exceptions back to the state at the associated `enter` when leaving a catch block. `args[1]` contains the token from the associated `enter`. diff --git a/src/ast.c b/src/ast.c index 8e98f0f19e87b..0f420849cfbb7 100644 --- a/src/ast.c +++ b/src/ast.c @@ -38,7 +38,7 @@ jl_sym_t *lambda_sym; jl_sym_t *assign_sym; jl_sym_t *globalref_sym; jl_sym_t *do_sym; jl_sym_t *method_sym; jl_sym_t *core_sym; jl_sym_t *enter_sym; jl_sym_t *leave_sym; -jl_sym_t *pop_exc_sym; +jl_sym_t *pop_exception_sym; jl_sym_t *exc_sym; jl_sym_t *error_sym; jl_sym_t *new_sym; jl_sym_t *using_sym; jl_sym_t *const_sym; jl_sym_t *thunk_sym; @@ -343,7 +343,7 @@ void jl_init_frontend(void) exc_sym = jl_symbol("the_exception"); enter_sym = jl_symbol("enter"); leave_sym = jl_symbol("leave"); - pop_exc_sym = jl_symbol("pop_exc"); + pop_exception_sym = jl_symbol("pop_exception"); new_sym = jl_symbol("new"); const_sym = jl_symbol("const"); global_sym = jl_symbol("global"); diff --git a/src/codegen.cpp b/src/codegen.cpp index 6a70ab3d88cf8..d43c16d3e9b52 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3774,7 +3774,7 @@ static void emit_stmtpos(jl_codectx_t &ctx, jl_value_t *expr, int ssaval_result) ctx.builder.CreateCall(prepare_call(jlleave_func), ConstantInt::get(T_int32, jl_unbox_long(args[0]))); } - else if (head == pop_exc_sym) { + else if (head == pop_exception_sym) { jl_cgval_t exc_stack_state = emit_expr(ctx, jl_exprarg(expr, 0)); assert(exc_stack_state.V && exc_stack_state.V->getType() == T_size); ctx.builder.CreateCall(prepare_call(jl_restore_exc_stack_func), exc_stack_state.V); @@ -4032,8 +4032,8 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) else if (head == leave_sym) { jl_error("Expr(:leave) in value position"); } - else if (head == pop_exc_sym) { - jl_error("Expr(:pop_exc) in value position"); + else if (head == pop_exception_sym) { + jl_error("Expr(:pop_exception) in value position"); } else if (head == enter_sym) { jl_error("Expr(:enter) in value position"); @@ -6194,8 +6194,7 @@ 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 - + // Save exception stack depth at enter for use in pop_exception Value *exc_stack_state = ctx.builder.CreateCall(prepare_call(jl_exc_stack_state_func)); assert(!ctx.ssavalue_assigned.at(cursor)); diff --git a/src/interpreter.c b/src/interpreter.c index 36217fe3b270a..b56a05e8055ce 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -681,7 +681,7 @@ 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. + // store current top of exception stack for restore in pop_exception. 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); @@ -710,7 +710,7 @@ SECT_INTERP static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s s->continue_at = next_ip; jl_longjmp(eh->eh_ctx, 1); } - else if (head == pop_exc_sym) { + else if (head == pop_exception_sym) { size_t prev_state = jl_unbox_ulong(eval_value(jl_exprarg(stmt, 0), s)); jl_restore_exc_stack(prev_state); } diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 2978e8ae05559..c028cb6f78924 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -3435,7 +3435,7 @@ f(x) = yt(x) (if (eq? (cdr s) dest-tokens) (car s) (loop (cdr s)))))) - `(pop_exc ,restore-token)))) + `(pop_exception ,restore-token)))) (define (emit-return x) (define (actually-return x) (let* ((x (if rett @@ -3736,7 +3736,7 @@ f(x) = yt(x) ;; exception handlers are lowered using ;; (= tok (enter L)) - push handler with catch block at label L, yielding token ;; (leave n) - pop N exception handlers - ;; (pop_exc tok) - pop exception stack back to state of associated enter + ;; (pop_exception tok) - pop exception stack back to state of associated enter ((trycatch tryfinally) (let ((handler-token (make-ssavalue)) (catch (make-label)) @@ -3791,7 +3791,7 @@ f(x) = yt(x) (begin (set! catch-token-stack (cons handler-token catch-token-stack)) (let ((v2 (compile (caddr e) break-labels value tail))) (if val (emit-assignment val v2)) - (if (not tail) (emit `(pop_exc ,handler-token))) + (if (not tail) (emit `(pop_exception ,handler-token))) ;; else done in emit-return from compile (if endl (mark-label endl))) (set! catch-token-stack (cdr catch-token-stack)))) diff --git a/src/julia_internal.h b/src/julia_internal.h index d1bd5bff2b88d..8bacadfeba43b 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -991,7 +991,7 @@ extern jl_sym_t *method_sym; extern jl_sym_t *core_sym; extern jl_sym_t *enter_sym; extern jl_sym_t *leave_sym; extern jl_sym_t *exc_sym; extern jl_sym_t *error_sym; extern jl_sym_t *new_sym; extern jl_sym_t *using_sym; -extern jl_sym_t *pop_exc_sym; +extern jl_sym_t *pop_exception_sym; extern jl_sym_t *const_sym; extern jl_sym_t *thunk_sym; extern jl_sym_t *abstracttype_sym; extern jl_sym_t *primtype_sym; extern jl_sym_t *structtype_sym; extern jl_sym_t *foreigncall_sym; diff --git a/src/task.c b/src/task.c index 2300d15216254..24a7fd4f01127 100644 --- a/src/task.c +++ b/src/task.c @@ -556,9 +556,9 @@ static void NOINLINE JL_NORETURN start_task(void) res = jl_current_exception(); t->exception = res; jl_gc_wb(t, res); - goto skip_pop_exc; + goto skip_pop_exception; } -skip_pop_exc:; +skip_pop_exception:; } finish_task(t, res); gc_debug_critical_error(); From ea7643a7bce35173195fef037413af31bff84306 Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Sun, 14 Oct 2018 19:47:54 +1000 Subject: [PATCH 11/14] More comprehensive exception stack tests * Test try-catch-finally form * Add a few tests to do with exiting try blocks. * More explanatory comments --- test/exceptions.jl | 90 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 20 deletions(-) diff --git a/test/exceptions.jl b/test/exceptions.jl index 24eec56d3e1d4..6bbeebd88e4c5 100644 --- a/test/exceptions.jl +++ b/test/exceptions.jl @@ -1,13 +1,14 @@ using Test -@testset "Exception stack nesting" begin - # Basic exception stack handling +@testset "Basic exception stack handling" begin + # Exiting the catch block normally pops the exception try error("A") catch @test length(catch_stack()) == 1 end @test length(catch_stack()) == 0 + # Exiting via a finally block does not pop the exception try try error("A") @@ -17,7 +18,16 @@ using Test catch @test length(catch_stack()) == 1 end - # Errors stack up + # The combined try-catch-finally form obeys the same rules as above + try + error("A") + catch + @test length(catch_stack()) == 1 + finally + @test length(catch_stack()) == 0 + end + @test length(catch_stack()) == 0 + # Errors are pushed onto the stack according to catch block nesting try error("RootCause") catch @@ -35,7 +45,10 @@ using Test @test length(stack) == 1 @test stack[1][1].msg == "RootCause" end - # Lowering - value position +end + +@testset "Exception stack lowering special cases" begin + # try block in value position val = try error("A") catch @@ -44,7 +57,7 @@ using Test end @test val == 1 function test_exc_stack_tailpos() - # exercise lowering code path for tail position + # try block in tail position try error("A") catch @@ -55,7 +68,9 @@ using Test @test length(catch_stack()) == 0 end -@testset "Exception stacks and gotos" begin +@testset "Exception stacks - early exit from try or catch" begin + # Exiting a catch block early with normal control flow — break, continue, + # return, goto — will result in popping of the exception stack. function test_exc_stack_catch_return() try error("A") @@ -89,13 +104,47 @@ end end @label outofcatch @test length(catch_stack()) == 0 + + # Exiting from a try block in various ways should not affect the exception + # stack state. + try + error("ExceptionInOuterTry") + catch + @test length(catch_stack()) == 1 + function test_exc_stack_try_return() + try + return + catch + end + end + test_exc_stack_try_return() + for i=1:1 + try + break + catch + end + end + for i=1:1 + try + continue + catch + end + end + try + @goto outoftry + catch + end + @label outoftry + @test length(catch_stack()) == 1 + @test catch_stack()[1][1] == ErrorException("ExceptionInOuterTry") + end end @testset "Deep exception stacks" begin + # Generate deep exception stack with recursive handlers Note that if you + # let this overflow the program stack (not the exception stack) julia will + # crash. See #28577 function test_exc_stack_deep(n) - # Generate deep exception stack with recursive handlers - # Note that if you let this overflow the program stack (not the exception - # stack) julia will crash. See #28577 n != 1 || error("RootCause") try test_exc_stack_deep(n-1) @@ -106,20 +155,21 @@ end @test try test_exc_stack_deep(100) catch + @test catch_stack()[1][1] == ErrorException("RootCause") length(catch_stack()) end == 100 @test length(catch_stack()) == 0 end @testset "Exception stacks and Tasks" begin - # See #12485 + # Task switching should not affect exception state. See #12485. try error("A") catch t = @task try error("B") - catch ex - ex + catch exc + exc end yield(t) @test t.state == :done @@ -138,8 +188,8 @@ end bt = catch_backtrace() t = @task try error("B") - catch ex - ex + catch exc + exc end yield(t) @test t.state == :done @@ -160,8 +210,8 @@ end catch t = @task try error("B") - catch ex - ex + catch exc + exc end yield(t) @test t.state == :done @@ -199,12 +249,12 @@ end @testset "rethrow" begin @test try rethrow() - catch ex - ex + catch exc + exc end == ErrorException("rethrow() not allowed outside a catch block") @test try rethrow(ErrorException("A")) - catch ex - ex + catch exc + exc end == ErrorException("rethrow(exc) not allowed outside a catch block") end From 5cab916af22681573cf55526ad90133c80ac3360 Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Sun, 14 Oct 2018 22:19:57 +1000 Subject: [PATCH 12/14] Fix gc static analyzer annotations --- src/julia_internal.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/julia_internal.h b/src/julia_internal.h index 8bacadfeba43b..6af402d78ae70 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -670,7 +670,7 @@ typedef struct _jl_exc_stack_t { // uintptr_t data[]; // Access with jl_excstk_raw } jl_exc_stack_t; -STATIC_INLINE uintptr_t *jl_excstk_raw(jl_exc_stack_t* stack) +STATIC_INLINE uintptr_t *jl_excstk_raw(jl_exc_stack_t* stack) JL_NOTSAFEPOINT { return (uintptr_t*)(stack + 1); } @@ -681,16 +681,16 @@ STATIC_INLINE jl_value_t *jl_exc_stack_exception(jl_exc_stack_t *stack JL_PROPAG { return (jl_value_t*)(jl_excstk_raw(stack)[itr-1]); } -STATIC_INLINE size_t jl_exc_stack_bt_size(jl_exc_stack_t *stack, size_t itr) +STATIC_INLINE size_t jl_exc_stack_bt_size(jl_exc_stack_t *stack, size_t itr) JL_NOTSAFEPOINT { return jl_excstk_raw(stack)[itr-2]; } -STATIC_INLINE uintptr_t *jl_exc_stack_bt_data(jl_exc_stack_t *stack, size_t itr) +STATIC_INLINE uintptr_t *jl_exc_stack_bt_data(jl_exc_stack_t *stack, size_t itr) JL_NOTSAFEPOINT { return jl_excstk_raw(stack) + itr-2 - jl_exc_stack_bt_size(stack, itr); } // Exception stack iteration (start at itr=stack->top, stop at itr=0) -STATIC_INLINE size_t jl_exc_stack_next(jl_exc_stack_t *stack, size_t itr) +STATIC_INLINE size_t jl_exc_stack_next(jl_exc_stack_t *stack, size_t itr) JL_NOTSAFEPOINT { return itr-2 - jl_exc_stack_bt_size(stack, itr); } From 5b0ca6326591b6cdb5b28e4b51d44e2b13349d98 Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Wed, 17 Oct 2018 23:08:55 +1000 Subject: [PATCH 13/14] Automated renaming exc_stack -> excstack cd src ag -l exc_stack | xargs sed -i -e 's/exc_stack/excstack/g' ag -l excstk_raw | xargs sed -i -e 's/excstk_raw/excstack_raw/g' sed -i -e 's/jlcurrent_exception_func/jl_current_exception_func/g' codegen.cpp cd ../base ag -l exc_stack | xargs sed -i -e 's/exc_stack/excstack/g' --- base/error.jl | 2 +- src/ast.c | 2 +- src/codegen.cpp | 36 ++++++++++++++++++------------------ src/gc.c | 24 ++++++++++++------------ src/gc.h | 6 +++--- src/interpreter.c | 4 ++-- src/jlapi.c | 4 ++-- src/julia.h | 10 +++++----- src/julia_internal.h | 30 +++++++++++++++--------------- src/julia_threads.h | 2 +- src/rtutils.c | 34 +++++++++++++++++----------------- src/stackwalk.c | 26 +++++++++++++------------- src/task.c | 24 ++++++++++++------------ 13 files changed, 102 insertions(+), 102 deletions(-) diff --git a/base/error.jl b/base/error.jl index 8bb23eba48e38..3693129600736 100644 --- a/base/error.jl +++ b/base/error.jl @@ -105,7 +105,7 @@ arbitrary task. This is useful for inspecting tasks which have failed due to uncaught exceptions. """ function catch_stack(task=current_task(); include_bt=true) - raw = ccall(:jl_get_exc_stack, Any, (Any,Cint,Cint), task, include_bt, typemax(Cint)) + raw = ccall(:jl_get_excstack, Any, (Any,Cint,Cint), task, include_bt, typemax(Cint)) formatted = Any[] stride = include_bt ? 3 : 1 for i = reverse(1:stride:length(raw)) diff --git a/src/ast.c b/src/ast.c index 0f420849cfbb7..a84bc2f47b9cb 100644 --- a/src/ast.c +++ b/src/ast.c @@ -889,7 +889,7 @@ 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 + goto finally; // skip jl_restore_excstack } finally: jl_get_ptls_states()->world_age = last_age; diff --git a/src/codegen.cpp b/src/codegen.cpp index d43c16d3e9b52..02f08393993e2 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -287,10 +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 *jl_current_exception_func; static Function *jlleave_func; -static Function *jl_restore_exc_stack_func; -static Function *jl_exc_stack_state_func; +static Function *jl_restore_excstack_func; +static Function *jl_excstack_state_func; static Function *jlegal_func; static Function *jl_alloc_obj_func; static Function *jl_newbits_func; @@ -3775,9 +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_exception_sym) { - jl_cgval_t exc_stack_state = emit_expr(ctx, jl_exprarg(expr, 0)); - assert(exc_stack_state.V && exc_stack_state.V->getType() == T_size); - ctx.builder.CreateCall(prepare_call(jl_restore_exc_stack_func), exc_stack_state.V); + jl_cgval_t excstack_state = emit_expr(ctx, jl_exprarg(expr, 0)); + assert(excstack_state.V && excstack_state.V->getType() == T_size); + ctx.builder.CreateCall(prepare_call(jl_restore_excstack_func), excstack_state.V); return; } else { @@ -4003,7 +4003,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) } else if (head == exc_sym) { return mark_julia_type(ctx, - ctx.builder.CreateCall(prepare_call(jlcurrent_exception_func)), + ctx.builder.CreateCall(prepare_call(jl_current_exception_func)), true, jl_any_type); } else if (head == copyast_sym) { @@ -6195,10 +6195,10 @@ 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_exception - Value *exc_stack_state = - ctx.builder.CreateCall(prepare_call(jl_exc_stack_state_func)); + Value *excstack_state = + ctx.builder.CreateCall(prepare_call(jl_excstack_state_func)); assert(!ctx.ssavalue_assigned.at(cursor)); - ctx.SAvalues.at(cursor) = jl_cgval_t(exc_stack_state, NULL, false, + ctx.SAvalues.at(cursor) = jl_cgval_t(excstack_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)); @@ -7102,11 +7102,11 @@ static void init_julia_llvm_env(Module *m) "jl_enter_handler", m); add_named_global(jlenter_func, &jl_enter_handler); - jlcurrent_exception_func = + jl_current_exception_func = Function::Create(FunctionType::get(T_prjlvalue, false), Function::ExternalLinkage, "jl_current_exception", m); - add_named_global(jlcurrent_exception_func, &jl_current_exception); + add_named_global(jl_current_exception_func, &jl_current_exception); #ifdef _OS_WINDOWS_ #if defined(_CPU_X86_64_) @@ -7147,17 +7147,17 @@ 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 = + jl_restore_excstack_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_restore_excstack", m); + add_named_global(jl_restore_excstack_func, &jl_restore_excstack); - jl_exc_stack_state_func = + jl_excstack_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); + "jl_excstack_state", m); + add_named_global(jl_excstack_state_func, &jl_excstack_state); std::vector args_2vals_callee_rooted(0); args_2vals_callee_rooted.push_back(PointerType::get(T_jlvalue, AddressSpace::CalleeRooted)); diff --git a/src/gc.c b/src/gc.c index 3c7efb734a939..e5fb03cf95cc0 100644 --- a/src/gc.c +++ b/src/gc.c @@ -2070,13 +2070,13 @@ 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; + gc_mark_excstack_t *stackitr = gc_pop_markdata(&sp, gc_mark_excstack_t); + jl_excstack_t *excstack = 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); + size_t bt_size = jl_excstack_bt_size(excstack, itr); + uintptr_t *bt_data = jl_excstack_bt_data(excstack, itr); while (i+2 < bt_size) { if (bt_data[i] != JL_BT_INTERP_FRAME) { i++; @@ -2089,19 +2089,19 @@ excstack: { if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) { stackitr->i = i; stackitr->itr = itr; - gc_repush_markdata(&sp, gc_mark_exc_stack_t); + gc_repush_markdata(&sp, gc_mark_excstack_t); goto mark; } } // mark the exception - new_obj = jl_exc_stack_exception(exc_stack, itr); - itr = jl_exc_stack_next(exc_stack, itr); + new_obj = jl_excstack_exception(excstack, itr); + itr = jl_excstack_next(excstack, 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); + gc_repush_markdata(&sp, gc_mark_excstack_t); goto mark; } } @@ -2367,10 +2367,10 @@ 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}; + if (ta->excstack) { + gc_setmark_buf_(ptls, ta->excstack, bits, sizeof(jl_excstack_t) + + sizeof(uintptr_t)*ta->excstack->reserved_size); + gc_mark_excstack_t stackdata = {ta->excstack, ta->excstack->top, 0}; gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(excstack), &stackdata, sizeof(stackdata), 1); } diff --git a/src/gc.h b/src/gc.h index 9a46a0959b1e8..e99e6b9b8cfd6 100644 --- a/src/gc.h +++ b/src/gc.h @@ -153,10 +153,10 @@ typedef struct { // Exception stack data typedef struct { - jl_exc_stack_t *s; // Stack of exceptions + jl_excstack_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; +} gc_mark_excstack_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 @@ -185,7 +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_excstack_t excstackframe; gc_mark_binding_t binding; gc_mark_finlist_t finlist; }; diff --git a/src/interpreter.c b/src/interpreter.c index b56a05e8055ce..e9f1bb3acd67c 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -682,7 +682,7 @@ SECT_INTERP static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s catch_ip += 1; } // store current top of exception stack for restore in pop_exception. - s->locals[jl_source_nslots(s->src) + ip] = jl_box_ulong(jl_exc_stack_state()); + s->locals[jl_source_nslots(s->src) + ip] = jl_box_ulong(jl_excstack_state()); if (!jl_setjmp(__eh.eh_ctx, 1)) { return eval_body(stmts, s, next_ip, toplevel); } @@ -712,7 +712,7 @@ SECT_INTERP static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s } else if (head == pop_exception_sym) { size_t prev_state = jl_unbox_ulong(eval_value(jl_exprarg(stmt, 0), s)); - jl_restore_exc_stack(prev_state); + jl_restore_excstack(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 8bfb433d960c7..478c8884b334d 100644 --- a/src/jlapi.c +++ b/src/jlapi.c @@ -104,8 +104,8 @@ JL_DLLEXPORT jl_value_t *jl_eval_string(const char *str) JL_DLLEXPORT jl_value_t *jl_current_exception(void) JL_GLOBALLY_ROOTED { - 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_excstack_t *s = jl_get_ptls_states()->current_task->excstack; + return s && s->top != 0 ? jl_excstack_exception(s, s->top) : jl_nothing; } JL_DLLEXPORT jl_value_t *jl_exception_occurred(void) diff --git a/src/julia.h b/src/julia.h index 4bb92a47b2fff..fbc1eb2461b66 100644 --- a/src/julia.h +++ b/src/julia.h @@ -1629,7 +1629,7 @@ typedef struct _jl_task_t { // saved gc stack top for context switches jl_gcframe_t *gcstack; // saved exception stack - jl_exc_stack_t *exc_stack; + jl_excstack_t *excstack; // current world age size_t world_age; @@ -1656,8 +1656,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); +JL_DLLEXPORT size_t jl_excstack_state(void); +JL_DLLEXPORT void jl_restore_excstack(size_t state); #if defined(_OS_WINDOWS_) #if defined(_COMPILER_MINGW_) @@ -1697,14 +1697,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(); \ + size_t __excstack_state = jl_excstack_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, jl_restore_exc_stack(__exc_stack_state)) + for (i__ca=1, jl_eh_restore_state(&__eh); i__ca; i__ca=0, jl_restore_excstack(__excstack_state)) #endif diff --git a/src/julia_internal.h b/src/julia_internal.h index 6af402d78ae70..94e00331d8417 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -661,46 +661,46 @@ JL_DLLEXPORT size_t jl_capture_interp_frame(uintptr_t *data, uintptr_t sp, uintp // Exception stack: a stack of pairs of (exception,raw_backtrace). // The stack may be traversed and accessed with the functions below. -typedef struct _jl_exc_stack_t { +typedef struct _jl_excstack_t { size_t top; size_t reserved_size; // Pack all stack entries into a growable buffer to amortize allocation // across repeated exception handling. // Layout: [bt_data1... bt_size1 exc1 bt_data2... bt_size2 exc2 ..] - // uintptr_t data[]; // Access with jl_excstk_raw -} jl_exc_stack_t; + // uintptr_t data[]; // Access with jl_excstack_raw +} jl_excstack_t; -STATIC_INLINE uintptr_t *jl_excstk_raw(jl_exc_stack_t* stack) JL_NOTSAFEPOINT +STATIC_INLINE uintptr_t *jl_excstack_raw(jl_excstack_t* stack) JL_NOTSAFEPOINT { return (uintptr_t*)(stack + 1); } // Exception stack access -STATIC_INLINE jl_value_t *jl_exc_stack_exception(jl_exc_stack_t *stack JL_PROPAGATES_ROOT, +STATIC_INLINE jl_value_t *jl_excstack_exception(jl_excstack_t *stack JL_PROPAGATES_ROOT, size_t itr) JL_NOTSAFEPOINT { - return (jl_value_t*)(jl_excstk_raw(stack)[itr-1]); + return (jl_value_t*)(jl_excstack_raw(stack)[itr-1]); } -STATIC_INLINE size_t jl_exc_stack_bt_size(jl_exc_stack_t *stack, size_t itr) JL_NOTSAFEPOINT +STATIC_INLINE size_t jl_excstack_bt_size(jl_excstack_t *stack, size_t itr) JL_NOTSAFEPOINT { - return jl_excstk_raw(stack)[itr-2]; + return jl_excstack_raw(stack)[itr-2]; } -STATIC_INLINE uintptr_t *jl_exc_stack_bt_data(jl_exc_stack_t *stack, size_t itr) JL_NOTSAFEPOINT +STATIC_INLINE uintptr_t *jl_excstack_bt_data(jl_excstack_t *stack, size_t itr) JL_NOTSAFEPOINT { - return jl_excstk_raw(stack) + itr-2 - jl_exc_stack_bt_size(stack, itr); + return jl_excstack_raw(stack) + itr-2 - jl_excstack_bt_size(stack, itr); } // Exception stack iteration (start at itr=stack->top, stop at itr=0) -STATIC_INLINE size_t jl_exc_stack_next(jl_exc_stack_t *stack, size_t itr) JL_NOTSAFEPOINT +STATIC_INLINE size_t jl_excstack_next(jl_excstack_t *stack, size_t itr) JL_NOTSAFEPOINT { - return itr-2 - jl_exc_stack_bt_size(stack, itr); + return itr-2 - jl_excstack_bt_size(stack, itr); } // Exception stack manipulation -void jl_reserve_exc_stack(jl_exc_stack_t **stack JL_REQUIRE_ROOTED_SLOT, +void jl_reserve_excstack(jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT, size_t reserved_size); -void jl_push_exc_stack(jl_exc_stack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, +void jl_push_excstack(jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, jl_value_t *exception JL_ROOTED_ARGUMENT, uintptr_t *bt_data, size_t bt_size); -void jl_copy_exc_stack(jl_exc_stack_t *dest, jl_exc_stack_t *src) JL_NOTSAFEPOINT; +void jl_copy_excstack(jl_excstack_t *dest, jl_excstack_t *src) JL_NOTSAFEPOINT; // timers // Returns time in nanosec diff --git a/src/julia_threads.h b/src/julia_threads.h index 97a3a57cf8a82..8d22d1871e55e 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -132,7 +132,7 @@ typedef struct { jl_gc_mark_data_t *data_stack; } jl_gc_mark_cache_t; -typedef struct _jl_exc_stack_t jl_exc_stack_t; +typedef struct _jl_excstack_t jl_excstack_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 diff --git a/src/rtutils.c b/src/rtutils.c index 3dce025e06dde..5a8430b03ff06 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -280,55 +280,55 @@ JL_DLLEXPORT void jl_pop_handler(int n) jl_eh_restore_state(eh); } -JL_DLLEXPORT size_t jl_exc_stack_state(void) +JL_DLLEXPORT size_t jl_excstack_state(void) { jl_ptls_t ptls = jl_get_ptls_states(); - jl_exc_stack_t *s = ptls->current_task->exc_stack; + jl_excstack_t *s = ptls->current_task->excstack; return s ? s->top : 0; } -JL_DLLEXPORT void jl_restore_exc_stack(size_t state) +JL_DLLEXPORT void jl_restore_excstack(size_t state) { jl_ptls_t ptls = jl_get_ptls_states(); - jl_exc_stack_t *s = ptls->current_task->exc_stack; + jl_excstack_t *s = ptls->current_task->excstack; if (s) { assert(s->top >= state); s->top = state; } } -void jl_copy_exc_stack(jl_exc_stack_t *dest, jl_exc_stack_t *src) JL_NOTSAFEPOINT +void jl_copy_excstack(jl_excstack_t *dest, jl_excstack_t *src) JL_NOTSAFEPOINT { assert(dest->reserved_size >= src->top); - memcpy(jl_excstk_raw(dest), jl_excstk_raw(src), sizeof(uintptr_t)*src->top); + memcpy(jl_excstack_raw(dest), jl_excstack_raw(src), sizeof(uintptr_t)*src->top); dest->top = src->top; } -void jl_reserve_exc_stack(jl_exc_stack_t **stack JL_REQUIRE_ROOTED_SLOT, +void jl_reserve_excstack(jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT, size_t reserved_size) { - jl_exc_stack_t *s = *stack; + jl_excstack_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); + size_t bufsz = sizeof(jl_excstack_t) + sizeof(uintptr_t)*reserved_size; + jl_excstack_t *new_s = (jl_excstack_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); + jl_copy_excstack(new_s, s); *stack = new_s; } -void jl_push_exc_stack(jl_exc_stack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, +void jl_push_excstack(jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, jl_value_t *exception JL_ROOTED_ARGUMENT, 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); + jl_reserve_excstack(stack, (*stack ? (*stack)->top : 0) + bt_size + 2); + jl_excstack_t *s = *stack; + memcpy(jl_excstack_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; + jl_excstack_raw(s)[s->top-2] = bt_size; + jl_excstack_raw(s)[s->top-1] = (uintptr_t)exception; } // misc ----------------------------------------------------------------------- diff --git a/src/stackwalk.c b/src/stackwalk.c index fe3e6f5c60d20..3c9f463ced994 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -167,12 +167,12 @@ void decode_backtrace(uintptr_t *bt_data, size_t bt_size, 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; + jl_excstack_t *s = jl_get_ptls_states()->current_task->excstack; 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); + bt_data = jl_excstack_bt_data(s, s->top); + bt_size = jl_excstack_bt_size(s, s->top); } decode_backtrace(bt_data, bt_size, btout, bt2out); } @@ -181,7 +181,7 @@ JL_DLLEXPORT void jl_get_backtrace(jl_array_t **btout, jl_array_t **bt2out) // with the top of the stack and returning up to `max_entries`. If requested by // setting the `include_bt` flag, backtrace data in bt,bt2 format is // interleaved. -JL_DLLEXPORT jl_value_t *jl_get_exc_stack(jl_value_t* task, int include_bt, int max_entries) +JL_DLLEXPORT jl_value_t *jl_get_excstack(jl_value_t* task, int include_bt, int max_entries) { JL_TYPECHK(catch_stack, task, task); jl_array_t *stack = NULL; @@ -189,19 +189,19 @@ JL_DLLEXPORT jl_value_t *jl_get_exc_stack(jl_value_t* task, int include_bt, int jl_array_t *bt2 = NULL; JL_GC_PUSH3(&stack, &bt, &bt2); stack = jl_alloc_array_1d(jl_array_any_type, 0); - jl_exc_stack_t *exc_stack = ((jl_task_t*)task)->exc_stack; - size_t itr = exc_stack ? exc_stack->top : 0; + jl_excstack_t *excstack = ((jl_task_t*)task)->excstack; + size_t itr = excstack ? excstack->top : 0; int i = 0; while (itr > 0 && i < max_entries) { - jl_array_ptr_1d_push(stack, jl_exc_stack_exception(exc_stack, itr)); + jl_array_ptr_1d_push(stack, jl_excstack_exception(excstack, itr)); if (include_bt) { - decode_backtrace(jl_exc_stack_bt_data(exc_stack, itr), - jl_exc_stack_bt_size(exc_stack, itr), + decode_backtrace(jl_excstack_bt_data(excstack, itr), + jl_excstack_bt_size(excstack, itr), &bt, &bt2); jl_array_ptr_1d_push(stack, (jl_value_t*)bt); jl_array_ptr_1d_push(stack, (jl_value_t*)bt2); } - itr = jl_exc_stack_next(exc_stack, itr); + itr = jl_excstack_next(excstack, itr); i++; } JL_GC_POP(); @@ -513,11 +513,11 @@ JL_DLLEXPORT void jl_gdblookup(uintptr_t ip) JL_DLLEXPORT void jlbacktrace(void) { - jl_exc_stack_t *s = jl_get_ptls_states()->current_task->exc_stack; + jl_excstack_t *s = jl_get_ptls_states()->current_task->excstack; 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); + size_t bt_size = jl_excstack_bt_size(s, s->top); + uintptr_t *bt_data = jl_excstack_bt_data(s, s->top); for (size_t i = 0; i < bt_size; ) { if (bt_data[i] == JL_BT_INTERP_FRAME) { jl_safe_printf("Interpreter frame (ip: %d)\n", (int)bt_data[i+2]); diff --git a/src/task.c b/src/task.c index 24a7fd4f01127..acf19b4879116 100644 --- a/src/task.c +++ b/src/task.c @@ -371,11 +371,11 @@ void JL_NORETURN throw_internal(jl_value_t *exception JL_MAYBE_UNROOTED) // GC. This exists only for the purpose of preserving bt_data until we // set ptls->bt_size=0 below. assert(ptls->current_task); - jl_push_exc_stack(&ptls->current_task->exc_stack, exception, + jl_push_excstack(&ptls->current_task->excstack, exception, ptls->bt_data, ptls->bt_size); ptls->bt_size = 0; } - assert(ptls->current_task->exc_stack && ptls->current_task->exc_stack->top); + assert(ptls->current_task->excstack && ptls->current_task->excstack->top); jl_handler_t *eh = ptls->current_task->eh; if (eh != NULL) { #ifdef ENABLE_TIMINGS @@ -404,11 +404,11 @@ JL_DLLEXPORT void jl_throw(jl_value_t *e JL_MAYBE_UNROOTED) throw_internal(e); } -// rethrow with current exc_stack state +// rethrow with current excstack 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_excstack_t *excstack = jl_get_ptls_states()->current_task->excstack; + if (!excstack || excstack->top == 0) jl_error("rethrow() not allowed outside a catch block"); throw_internal(NULL); } @@ -429,11 +429,11 @@ JL_DLLEXPORT void jl_rethrow_other(jl_value_t *e JL_MAYBE_UNROOTED) { // 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_excstack_t *excstack = jl_get_ptls_states()->current_task->excstack; + if (!excstack || excstack->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; + // overwrite exception on top of stack. see jl_excstack_exception + jl_excstack_raw(excstack)[excstack->top-1] = (uintptr_t)e; JL_GC_PROMISE_ROOTED(e); throw_internal(NULL); } @@ -469,7 +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->excstack = NULL; t->stkbuf = NULL; t->tid = 0; t->started = 0; @@ -538,7 +538,7 @@ static void NOINLINE JL_NORETURN start_task(void) t->started = 1; if (t->exception != jl_nothing) { record_backtrace(ptls); - jl_push_exc_stack(&t->exc_stack, t->exception, + jl_push_excstack(&t->excstack, t->exception, ptls->bt_data, ptls->bt_size); res = t->exception; } @@ -881,7 +881,7 @@ 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->excstack = NULL; ptls->current_task->tid = ptls->tid; #ifdef JULIA_ENABLE_THREADING arraylist_new(&ptls->current_task->locks, 0); From 48e8f0fd038016992e9aa9c5934ac6d4d59dd6c7 Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Thu, 18 Oct 2018 07:47:05 +1000 Subject: [PATCH 14/14] Documentation + NEWS for exception stacks --- NEWS.md | 1 + doc/src/base/base.md | 1 + doc/src/manual/control-flow.md | 4 +-- doc/src/manual/stacktraces.md | 47 ++++++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index b50c29cd2b337..720b52421295e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,6 +5,7 @@ New language features --------------------- * `CartesianIndices` can now be constructed from two `CartesianIndex`es `I` and `J` with `I:J` ([#29440]). + * An *exception stack* is maintained on each task to make exception handling more robust and enable root cause analysis using `catch_stack` ([#28878]). Language changes ---------------- diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 0567e934a5944..f5b894e7cd2e7 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -303,6 +303,7 @@ Core.throw Base.rethrow Base.backtrace Base.catch_backtrace +Base.catch_stack Base.@assert Base.ArgumentError Base.AssertionError diff --git a/doc/src/manual/control-flow.md b/doc/src/manual/control-flow.md index f73e523c35343..9de3b8206c8a7 100644 --- a/doc/src/manual/control-flow.md +++ b/doc/src/manual/control-flow.md @@ -793,8 +793,8 @@ end The power of the `try/catch` construct lies in the ability to unwind a deeply nested computation immediately to a much higher level in the stack of calling functions. There are situations where no error has occurred, but the ability to unwind the stack and pass a value to a higher level -is desirable. Julia provides the [`rethrow`](@ref), [`backtrace`](@ref) and [`catch_backtrace`](@ref) -functions for more advanced error handling. +is desirable. Julia provides the [`rethrow`](@ref), [`backtrace`](@ref), [`catch_backtrace`](@ref) +and [`catch_stack`](@ref) functions for more advanced error handling. ### `finally` Clauses diff --git a/doc/src/manual/stacktraces.md b/doc/src/manual/stacktraces.md index 776570d6b0c26..a7da7a6464cd7 100644 --- a/doc/src/manual/stacktraces.md +++ b/doc/src/manual/stacktraces.md @@ -187,6 +187,53 @@ ERROR: Whoops! [...] ``` +## Exception stacks and [`catch_stack`](@ref) + +While handling an exception further exceptions may be thrown. It can be useful to inspect all these exceptions to +identify the root cause of a problem. The julia runtime supports this by pushing each exception onto an internal +*exception stack* as it occurs. When the code exits a `catch` normally, any exceptions which were pushed onto the stack +in the associated `try` are considered to be successfully handled and are removed from the stack. + +The stack of current exceptions can be accessed using the [`catch_stack`](@ref) function. For example, + +```julia-repl +julia> try + error("(A) The root cause") + catch + try + error("(B) An exception while handling the exception") + catch + for (exc, bt) in catch_stack() + showerror(stdout, exc, bt) + println() + end + end + end +(A) The root cause +Stacktrace: + [1] error(::String) at error.jl:33 + [2] top-level scope at REPL[7]:2 + [3] eval(::Module, ::Any) at boot.jl:319 + [4] eval_user_input(::Any, ::REPL.REPLBackend) at REPL.jl:85 + [5] macro expansion at REPL.jl:117 [inlined] + [6] (::getfield(REPL, Symbol("##26#27")){REPL.REPLBackend})() at task.jl:259 +(B) An exception while handling the exception +Stacktrace: + [1] error(::String) at error.jl:33 + [2] top-level scope at REPL[7]:5 + [3] eval(::Module, ::Any) at boot.jl:319 + [4] eval_user_input(::Any, ::REPL.REPLBackend) at REPL.jl:85 + [5] macro expansion at REPL.jl:117 [inlined] + [6] (::getfield(REPL, Symbol("##26#27")){REPL.REPLBackend})() at task.jl:259 +``` + +In this example the root cause exception (A) is first on the stack, with a further exception (B) following it. After +exiting both catch blocks normally (i.e., without throwing a further exception) all exceptions are removed from the stack +and are no longer accessible. + +The exception stack is stored on the `Task` where the exceptions occurred. When a task fails with uncaught exceptions, +`catch_stack(task)` may be used to inspect the exception stack for that task. + ## Comparison with [`backtrace`](@ref) A call to [`backtrace`](@ref) returns a vector of `Union{Ptr{Nothing}, Base.InterpreterIP}`, which may then be passed into