Skip to content

Commit

Permalink
Lowering for exception stacks
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
c42f committed Oct 5, 2018
1 parent 0e8811a commit 57b46e7
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 39 deletions.
2 changes: 1 addition & 1 deletion base/compiler/ssair/ir.jl
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ function is_relevant_expr(e::Expr)
:gc_preserve_begin, :gc_preserve_end,
:foreigncall, :isdefined, :copyast,
:undefcheck, :throw_undef_if_not,
:cfunction, :method,
:cfunction, :method, :pop_exc,
#=legacy IR format support=# :gotoifnot, :return)
end

Expand Down
1 change: 1 addition & 0 deletions base/compiler/ssair/slot2ssa.jl
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,7 @@ function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::Do
end
elseif isexpr(stmt, :enter)
new_code[idx] = Expr(:enter, block_for_inst(cfg, stmt.args[1]))
ssavalmap[idx] = SSAValue(idx)
elseif isexpr(stmt, :leave) || isexpr(stmt, :(=)) || isexpr(stmt, :return) ||
isexpr(stmt, :meta) || isa(stmt, NewvarNode)
new_code[idx] = stmt
Expand Down
3 changes: 2 additions & 1 deletion base/compiler/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const VALID_EXPR_HEADS = IdDict{Any,Any}(
:the_exception => 0:0,
:enter => 1:1,
:leave => 1:1,
:pop_exc => 1:1,
:inbounds => 1:1,
:boundscheck => 0:0,
:copyast => 1:1,
Expand Down Expand Up @@ -139,7 +140,7 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo, is_top_
validate_val!(x.args[1])
elseif head === :call || head === :invoke || head == :gc_preserve_end || head === :meta ||
head === :inbounds || head === :foreigncall || head === :cfunction ||
head === :const || head === :enter || head === :leave ||
head === :const || head === :enter || head === :leave || head == :pop_exc ||
head === :method || head === :global || head === :static_parameter ||
head === :new || head === :thunk || head === :simdloop ||
head === :throw_undef_if_not || head === :unreachable
Expand Down
10 changes: 7 additions & 3 deletions doc/src/devdocs/ast.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,18 +142,22 @@ These symbols appear in the `head` field of `Expr`s in lowered form.

* `the_exception`

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

* `enter`

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

* `leave`

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

* `pop_exc`

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

* `inbounds`

Controls turning bounds checks on or off. A stack is maintained; if the first argument of this
Expand Down
2 changes: 2 additions & 0 deletions src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ jl_sym_t *lambda_sym; jl_sym_t *assign_sym;
jl_sym_t *globalref_sym; jl_sym_t *do_sym;
jl_sym_t *method_sym; jl_sym_t *core_sym;
jl_sym_t *enter_sym; jl_sym_t *leave_sym;
jl_sym_t *pop_exc_sym;
jl_sym_t *exc_sym; jl_sym_t *error_sym;
jl_sym_t *new_sym; jl_sym_t *using_sym;
jl_sym_t *const_sym; jl_sym_t *thunk_sym;
Expand Down Expand Up @@ -342,6 +343,7 @@ void jl_init_frontend(void)
exc_sym = jl_symbol("the_exception");
enter_sym = jl_symbol("enter");
leave_sym = jl_symbol("leave");
pop_exc_sym = jl_symbol("pop_exc");
new_sym = jl_symbol("new");
const_sym = jl_symbol("const");
global_sym = jl_symbol("global");
Expand Down
7 changes: 7 additions & 0 deletions src/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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");
}
Expand Down
3 changes: 3 additions & 0 deletions src/interpreter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
97 changes: 63 additions & 34 deletions src/julia-syntax.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -3425,13 +3426,25 @@ 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
(compile (convert-for-type-decl x rett) '() #t #f)
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)
Expand All @@ -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)))))))
Expand Down Expand Up @@ -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))
Expand All @@ -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))
Expand All @@ -3714,47 +3730,47 @@ 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))
(if (not finally) (set! endl #f)))
(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))
Expand All @@ -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)
Expand Down Expand Up @@ -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))))
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/julia_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 57b46e7

Please sign in to comment.