Exception Compiler Baojian Hua bjhua@ustc.edu.cn.

Post on 04-Jan-2016

221 views 0 download

Transcript of Exception Compiler Baojian Hua bjhua@ustc.edu.cn.

Exception

CompilerBaojian 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 Exceptionsfunc -> 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

Semanticsthrow:// 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 meth

od) Table-driven method

faster but more complex, use more space

Setjmp/longjmp#include <setjmp.h> // C standard libraryjmp_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/longjmpjmp_buf buf;

void f () { if (0==setjmp (buf)) g (); else;}void g () { h ();}void h () { longjmp (buf, 1);}

frame f

ebxediesiebpespeip

buf

frame g

Setjmp/longjmpjmp_buf buf;

void f () { if (0==setjmp (buf)) g (); else;}void g () { h ();}void h () { longjmp (buf, 1);}

frame f

ebxediesiebpespeip

buf

frame g

frame h

movl $1, %eax

movl buf->eip, -4(buf->esp)

// restore ebx, edi, …

Setjmp/longjmpjmp_buf buf;

void f () { if (0==setjmp (buf)) g (); else;}void g () { h ();}void h () { longjmp (buf, 1);}

frame f

ebxediesiebpespeip

buf

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 la

bel, 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

eiptopold_xsp

eiptopold_xsp

old_top

xsp

top

old_top

Compiling “throw”eiptopold_xsp

eiptopold_xsp

old_top

xsp

top

old_top

gen_s (throw) =

top = xsp->top

jmp xsp->eip

// Essentially the same as

// “longjmp”.

// To simplify things, we omit

// the callee-saved regs here.

Compiling “try…catch…”eiptopold_xsp

eiptopold_xsp

old_topxsp

top

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:

Compiling “try…catch…”eiptopold_xsp

eiptopold_xsp

old_top

xsp

top

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

Compiling “try…catch…”eiptopold_xsp

eiptopold_xsp

old_top

xsp

top

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

Compiling “try…catch…”eiptopold_xsp

old_topxsp

top

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

Compiling “try…catch…”eiptopold_xsp

eiptopold_xsp

old_top

xsp

top

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!

gen_s (throw) =

top = xsp->top

jmp xsp->eip

Compiling “try…catch…”eiptopold_xsp

eiptopold_xsp

old_top

xsp

top

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!

gen_s (throw) =

top = xsp->top

jmp xsp->eip

eiptopold_xsp

eiptopold_xsp

old_topxsp

top

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

Example #1

eiptopold_xsp

eiptopold_xsp

old_top

xsptop

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:

Example #1

eiptopold_xsp

eiptopold_xsp

old_topxsp

top

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: gen_s (throw) =

top = xsp->top

jmp xsp->eip

Example #2

eiptopold_xsp

eiptopold_xsp

old_top

xsp

top

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: gen_s (throw) =

top = xsp->top

jmp xsp->eip

Example #2

5

eiptopold_xsp

eiptopold_xsp

old_topxsp

top

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:

Example #3

f(){

g();

print(6);

}

g(){

throw;

}

old_top

eiptopold_xsp

eiptopold_xsp

old_top

xsp

top

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:

Example #3

f(){

g();

print(6);

}

g(){

throw;

}

old_top

old_top

eiptopold_xsp

eiptopold_xsp

old_top

xsp

top

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:

Example #3

f(){

g();

print(6);

}

g(){

throw;

}

old_top

old_top

5

eiptopold_xsp

old_topxsp

top

// 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!

Example #4

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 approachgen_s (try s1 catch s2) =

.L1:

gen_s (s1)

jmp .End

.L2

gen_s (s2)

jmp .End

.L3

gen_s (throw) =

search_table(eip)

From To Handler

.L1 .L2 .L2

.L2 .L3 ?

old_top

top

gen_s (try {x:=3;}

catch {print(5);}) =

.L1

push 3

store x

jmp .End

.L2:

push 5

call print

jmp .End

.End:

From To Handler

.L1 .L2 .L2

.L2 .L3 ?

Example #1

old_top

top

gen_s (try {throw;}

catch {print(5);}) =

.L1

search_table(eip)

jmp .End

.L2:

push 5

call print

jmp .End

.End:

From To Handler

.L1 .L2 .L2

.L2 .L3 ?

Example #2

old_top

top

gen_s (try {f();}

catch {print(5);}) =

.L1

f()

jmp .End

.L2:

push 5

call print

jmp .End

.End:

From To Handler

.L1 .L2 .L2

.L2 .L3 ?

Example #3

f(){

g();

print(6);

}

g(){

throw;

}

old_top

old_top

Ooops!! Current eip is not in the table!!It’s time to unwind the stack!

old_top

top

gen_s (try {f();}

catch {print(5);}) =

.L1

f()

jmp .End

.L2:

push 5

call print

jmp .End

.End:

From To Handler

.L1 .L2 .L2

.L2 .L3 ?

Example #3

f(){

g();

print(6);

}

g(){

throw;

}

old_top

old_top

Ooops!! Current eip is not in the table!!It’s time to unwind the stack!Saved eip still not in stack

old_top

top

gen_s (try {f();}

catch {print(5);}) =

.L1

f()

jmp .End

.L2:

push 5

call print

jmp .End

.End:

From To Handler

.L1 .L2 .L2

.L2 .L3 ?

Example #3

f(){

g();

print(6);

}

g(){

throw;

}

old_top

Ooops!! Current eip is not in the table!!It’s time to unwind the stack!Saved eip still not in stack

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++ u

se this scheme Disadvantage:

exception table takes extra space

try…finally…

A little bit tricky Sun’s old version JDK compiler uses a f

ancy 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