From 57b46e79ceccb67f7d9ef64fa267dc2eff350c7b Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Thu, 19 Jul 2018 18:08:17 +1000 Subject: [PATCH] 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;