370 likes | 547 Views
Exception. Compiler Baojian Hua bjhua@ustc.edu.cn. Exception. Exception is for error-handling Invalid input Invalid resource state file not exists, network error, … error execution condition divide-by-zero, … In real production code, error-handling code may be a large part
E N D
Exception Compiler Baojian Hua bjhua@ustc.edu.cn
Exception • Exception is for error-handling • Invalid input • Invalid resource state • file not exists, network error, … • error execution condition • divide-by-zero, … • In real production code, error-handling code may be a large part • 30%-50% or more
Scale SLP with Exceptions func -> id(args){ s } s -> x := e | print (e) | return e | throw | try s catch s // Example: try {x := 3} catch { print(5);} try {throw; print(4);} catch 5
Semantics throw: // abort the current execution, and notify the // system some bad things happen! try s1 catch s2: // first execute s1, // 1. if s1 runs smoothly, then // the statement finished executing; // 2. else, some exception is thrown in s1, // then run s2. // Q: what about s2 “throw”?
Two strategies • Setjmp/longjmp-based • global “goto” • C’s primitive exception (poor-man method) • Table-driven method • faster • but more complex, use more space
Setjmp/longjmp #include <setjmp.h> // C standard library jmp_buf buf; void f () { if (0==setjmp (buf)) g (); else; } void g () { h (); } void h () { longjmp (buf, 1); }
Setjmp/longjmp // What’s going on under the hood? struct context { int ebx; int edi; int esi; int ebp; int esp; int eip; }; typedef struct context jmp_buf[1]; Callee-saved registers! Saved pc!
Setjmp/longjmp buf jmp_buf buf; void f () { if (0==setjmp (buf)) g (); else; } void g () { h (); } void h () { longjmp (buf, 1); } frame f frame g
Setjmp/longjmp buf jmp_buf buf; void f () { if (0==setjmp (buf)) g (); else; } void g () { h (); } void h () { longjmp (buf, 1); } frame f frame g frame h movl $1, %eax movl buf->eip, -4(buf->esp) // restore ebx, edi, …
Setjmp/longjmp buf jmp_buf buf; void f () { if (0==setjmp (buf)) g (); else; } void g () { h (); } void h () { longjmp (buf, 1); } frame f frame g frame h movl $1, %eax movl buf->eip, -4(buf->esp) // restore ebx, edi, …
Compiling to setjmp/longjmp • Basic idea: • try s1 catch s2 ==> setjmp • save the context (callee-saved registers, s2’s code label, etc.) • the context is also called a handler • throw ==> longjmp • pop the topmost handler, restore machine states from the handler and jump to the handler’s saved pc • “Try” may nest, so all handlers should be organized as a stack
Machine configuration • M = (C, S, X): • C: code heap • S: operand stack • pointed by “top” • as we did for the stack machine • X: exception handler stack • pointed by “xsp” • “top” points to top of some frame • “eip” points to code heap old_top xsp old_top top
Compiling “throw” gen_s (throw) = top = xsp->top jmp xsp->eip // Essentially the same as // “longjmp”. // To simplify things, we omit // the callee-saved regs here. old_top xsp old_top top
Compiling “try…catch…” gen_s (try s1 catch s2) = push a new handler xsp->eip = .Handler xsp->top = top gen_s (s1) pop a handler jmp .End .Handler: pop a handler gen_s (s2) jmp .End .End: old_top xsp top
Compiling “try…catch…” gen_s (try s1 catch s2) = push a new handler xsp->eip = .Handler xsp->top = top gen_s (s1) pop a handler jmp .End .Handler: pop a handler gen_s (s2) jmp .End .End: if s1 does NOT throw old_top top xsp
Compiling “try…catch…” gen_s (try s1 catch s2) = push a new handler xsp->eip = .Handler xsp->top = top gen_s (s1) pop a handler jmp .End .Handler: pop a handler gen_s (s2) jmp .End .End: if s1 does NOT throw old_top top xsp
Compiling “try…catch…” gen_s (try s1 catch s2) = push a new handler xsp->eip = .Handler xsp->top = top gen_s (s1) pop a handler jmp .End .Handler: pop a handler gen_s (s2) jmp .End .End: if s1 does NOT throw old_top xsp top
Compiling “try…catch…” gen_s (try s1 catch s2) = push a new handler xsp->eip = .Handler xsp->top = top gen_s (s1) pop a handler jmp .End .Handler: pop a handler gen_s (s2) jmp .End .End: if s1 does throw! old_top top xsp gen_s (throw) = top = xsp->top jmp xsp->eip
Compiling “try…catch…” gen_s (try s1 catch s2) = push a new handler xsp->eip = .Handler xsp->top = top gen_s (s1) pop a handler jmp .End .Handler: pop a handler gen_s (s2) jmp .End .End: if s1 does throw! old_top top xsp gen_s (throw) = top = xsp->top jmp xsp->eip
Example #1 gen_s (try {x:=3;} catch {print(5);}) = push a new handler xsp->eip = .Handler xsp->top = top push 3 store x pop a handler jmp .End .Handler: pop a handler push 5 call print jmp .End .End: 3 old_top xsp top
Example #1 gen_s (try {x:=3;} catch {print(5);}) = push a new handler xsp->eip = .Handler xsp->top = top push 3 store x pop a handler jmp .End .Handler: pop a handler push 5 call print jmp .End .End: old_top top xsp
Example #2 gen_s (try {throw;} catch {print(5);}) = push a new handler xsp->eip = .Handler xsp->top = top top = xsp->top jmp xsp->eip pop a handler jmp .End .Handler: pop a handler push 5 call print jmp .End .End: old_top xsp top gen_s (throw) = top = xsp->top jmp xsp->eip
Example #2 gen_s (try {throw;} catch {print(5);}) = push a new handler xsp->eip = .Handler xsp->top = top top = xsp->top jmp xsp->eip pop a handler jmp .End .Handler: pop a handler push 5 call print jmp .End .End: 5 old_top top xsp gen_s (throw) = top = xsp->top jmp xsp->eip
Example #3 gen_s (try {f();} catch {print(5);}) = push a new handler xsp->eip = .Handler xsp->top = top f() pop a handler jmp .End .Handler: pop a handler push 5 call print jmp .End .End: old_top xsp f(){ g(); print(6); } g(){ throw; } top old_top
Example #3 gen_s (try {f();} catch {print(5);}) = push a new handler xsp->eip = .Handler xsp->top = top f() pop a handler jmp .End .Handler: pop a handler push 5 call print jmp .End .End: old_top f(){ g(); print(6); } g(){ throw; } old_top xsp top old_top
Example #3 gen_s (try {f();} catch {print(5);}) = push a new handler xsp->eip = .Handler xsp->top = top f() pop a handler jmp .End .Handler: pop a handler push 5 call print jmp .End .End: 5 old_top f(){ g(); print(6); } g(){ throw; } old_top xsp old_top top
Example #4 // What about a wild // “throw”? main(){ throw; } gen_f (main(){throw;}) = main: top = xsp->top jmp xsp->eip // Where does xsp->eip // point to? // Do the assigned homework! old_top xsp top
Moral • Relatively easy to implement • Many C++ compilers use this scheme, e.g. VC from MS (the so-called SEH) • the exception handler stack can be combined with operand stack (and further with the call stack) • Disadvantage: • performance penalty at each “try” • even if the try body does not “throw” an exception • slogan: “pay as you go”
Table-driven approach gen_s (try s1 catch s2) = .L1: gen_s (s1) jmp .End .L2 gen_s (s2) jmp .End .L3 gen_s (throw) = search_table(eip)
Example #1 gen_s (try {x:=3;} catch {print(5);}) = .L1 push 3 store x jmp .End .L2: push 5 call print jmp .End .End: old_top top
Example #2 gen_s (try {throw;} catch {print(5);}) = .L1 search_table(eip) jmp .End .L2: push 5 call print jmp .End .End: old_top top
Example #3 f(){ g(); print(6); } g(){ throw; } gen_s (try {f();} catch {print(5);}) = .L1 f() jmp .End .L2: push 5 call print jmp .End .End: Ooops!! Current eip is not in the table!! old_top It’s time to unwind the stack! top old_top old_top
Example #3 f(){ g(); print(6); } g(){ throw; } gen_s (try {f();} catch {print(5);}) = .L1 f() jmp .End .L2: push 5 call print jmp .End .End: Ooops!! Current eip is not in the table!! old_top It’s time to unwind the stack! Saved eip still not in stack old_top old_top top
Example #3 f(){ g(); print(6); } g(){ throw; } gen_s (try {f();} catch {print(5);}) = .L1 f() jmp .End .L2: push 5 call print jmp .End .End: Ooops!! Current eip is not in the table!! old_top It’s time to unwind the stack! Saved eip still not in stack old_top top
Moral • Compilers produce an exception table, which is referred to when an exception is raised • based on the value of current and saved “eip”s • Normal execution can proceed at full speed
Moral • Table-driven scheme has no cost with normal execution • exceptions are exceptional, pay as you go • both Sun’s HotSpot JVM and GNU g++ use this scheme • Disadvantage: • exception table takes extra space
try…finally… • A little bit tricky • Sun’s old version JDK compiler uses a fancy but wrong technique---subroutine • has been fixed in JDK version after 1.4 • Read the assigned paper: • The Costs and Benefits of Java Bytecode Subroutines