Skip to content

Exception Handling Transform

Jukka Lehtosalo edited this page Jan 5, 2023 · 4 revisions

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.