Exception Handling Transform
Many mypyc ops can raise an exception, and by default these need to be propagated to the caller (if there aren't any enclosing try statements). Mypyc has a pass that inserts code needed to propagate exceptions, and to add traceback entries so that CPython can show stack tracebacks as expected. The implementation is in mypyc.transform.exceptions
.
When generating IR in mypyc.irbuild
, checking exceptions generated by ops is implicit -- it's described by the error_kind
attributes of ops, but otherwise can be mostly ignored. This makes it easy and less error-prone to generate IR.
For example, consider this function which has two calls which could raise exceptions:
def f(x: int) -> int:
return g(h(x))
The initial IR mypyc generates in mypyc.irbuild
only generates a single basic block with no error handling:
def f(x):
x, r0, r1 :: int
L0:
r0 = h(x)
r1 = g(r0)
return r1
However, the two Call
ops in the generated IR have their error_kind
attribute set to ERR_MAGIC
, i.e. errors are signaled via a special error value (which is in this case 1 for int
values).
The exception handling transform generates this IR for the above example (this also includes reference counting ops, which we won't discuss here):
def f(x):
x, r0, r1, r2 :: int
L0:
r0 = h(x)
if is_error(r0) goto L3 (error at f:5) else goto L1
L1:
r1 = g(r0)
dec_ref r0 :: int
if is_error(r1) goto L3 (error at f:5) else goto L2
L2:
return r1
L3:
r2 = <error> :: int
return r2
Note that additional error checking ops were added based on the error_kind
attributes. The original basic block was split into three basic blocks. There is also a new basic block L3
that propagates the error value to the caller.
Let's examine one of the error checking ops in more detail:
if is_error(r0) goto L3 (error at f:5) else goto L1
This checks if r0
has the error value, and if so, it branches to L3
and adds a traceback entry pointing to line 5 in function f
. If there is no error, we continue to L1
.