17db96d56Sopenharmony_ciDescription of exception handling in Python 3.11
27db96d56Sopenharmony_ci------------------------------------------------
37db96d56Sopenharmony_ci
47db96d56Sopenharmony_ciPython 3.11 uses what is known as "zero-cost" exception handling.
57db96d56Sopenharmony_ciPrior to 3.11, exceptions were handled by a runtime stack of "blocks".
67db96d56Sopenharmony_ci
77db96d56Sopenharmony_ciIn zero-cost exception handling, the cost of supporting exceptions is minimized.
87db96d56Sopenharmony_ciIn the common case (where no exception is raised) the cost is reduced
97db96d56Sopenharmony_cito zero (or close to zero).
107db96d56Sopenharmony_ciThe cost of raising an exception is increased, but not by much.
117db96d56Sopenharmony_ci
127db96d56Sopenharmony_ciThe following code:
137db96d56Sopenharmony_ci
147db96d56Sopenharmony_cidef f():
157db96d56Sopenharmony_ci    try:
167db96d56Sopenharmony_ci        g(0)
177db96d56Sopenharmony_ci    except:
187db96d56Sopenharmony_ci        return "fail"
197db96d56Sopenharmony_ci
207db96d56Sopenharmony_cicompiles as follows in 3.10:
217db96d56Sopenharmony_ci
227db96d56Sopenharmony_ci  2           0 SETUP_FINALLY            7 (to 16)
237db96d56Sopenharmony_ci
247db96d56Sopenharmony_ci  3           2 LOAD_GLOBAL              0 (g)
257db96d56Sopenharmony_ci              4 LOAD_CONST               1 (0)
267db96d56Sopenharmony_ci              6 CALL_NO_KW               1
277db96d56Sopenharmony_ci              8 POP_TOP
287db96d56Sopenharmony_ci             10 POP_BLOCK
297db96d56Sopenharmony_ci             12 LOAD_CONST               0 (None)
307db96d56Sopenharmony_ci             14 RETURN_VALUE
317db96d56Sopenharmony_ci
327db96d56Sopenharmony_ci  4     >>   16 POP_TOP
337db96d56Sopenharmony_ci             18 POP_TOP
347db96d56Sopenharmony_ci             20 POP_TOP
357db96d56Sopenharmony_ci
367db96d56Sopenharmony_ci  5          22 POP_EXCEPT
377db96d56Sopenharmony_ci             24 LOAD_CONST               3 ('fail')
387db96d56Sopenharmony_ci             26 RETURN_VALUE
397db96d56Sopenharmony_ci
407db96d56Sopenharmony_ciNote the explicit instructions to push and pop from the "block" stack:
417db96d56Sopenharmony_ciSETUP_FINALLY and POP_BLOCK.
427db96d56Sopenharmony_ci
437db96d56Sopenharmony_ciIn 3.11, the SETUP_FINALLY and POP_BLOCK are eliminated, replaced with
447db96d56Sopenharmony_cia table to determine where to jump to when an exception is raised.
457db96d56Sopenharmony_ci
467db96d56Sopenharmony_ci  1           0 RESUME                   0
477db96d56Sopenharmony_ci
487db96d56Sopenharmony_ci  2           2 NOP
497db96d56Sopenharmony_ci
507db96d56Sopenharmony_ci  3           4 LOAD_GLOBAL              1 (NULL + g)
517db96d56Sopenharmony_ci             16 LOAD_CONST               1 (0)
527db96d56Sopenharmony_ci             18 PRECALL                  1
537db96d56Sopenharmony_ci             22 CALL                     1
547db96d56Sopenharmony_ci             32 POP_TOP
557db96d56Sopenharmony_ci             34 LOAD_CONST               0 (None)
567db96d56Sopenharmony_ci             36 RETURN_VALUE
577db96d56Sopenharmony_ci        >>   38 PUSH_EXC_INFO
587db96d56Sopenharmony_ci
597db96d56Sopenharmony_ci  4          40 POP_TOP
607db96d56Sopenharmony_ci
617db96d56Sopenharmony_ci  5          42 POP_EXCEPT
627db96d56Sopenharmony_ci             44 LOAD_CONST               2 ('fail')
637db96d56Sopenharmony_ci             46 RETURN_VALUE
647db96d56Sopenharmony_ci        >>   48 COPY                     3
657db96d56Sopenharmony_ci             50 POP_EXCEPT
667db96d56Sopenharmony_ci             52 RERAISE                  1
677db96d56Sopenharmony_ciExceptionTable:
687db96d56Sopenharmony_ci  4 to 32 -> 38 [0]
697db96d56Sopenharmony_ci  38 to 40 -> 48 [1] lasti
707db96d56Sopenharmony_ci
717db96d56Sopenharmony_ci(Note this code is from 3.11, later versions may have slightly different bytecode.)
727db96d56Sopenharmony_ci
737db96d56Sopenharmony_ciIf an instruction raises an exception then its offset is used to find the target to jump to.
747db96d56Sopenharmony_ciFor example, the CALL at offset 22, falls into the range 4 to 32.
757db96d56Sopenharmony_ciSo, if g() raises an exception, then control jumps to offset 38.
767db96d56Sopenharmony_ci
777db96d56Sopenharmony_ci
787db96d56Sopenharmony_ciUnwinding
797db96d56Sopenharmony_ci---------
807db96d56Sopenharmony_ci
817db96d56Sopenharmony_ciWhen an exception is raised, the current instruction offset is used to find following:
827db96d56Sopenharmony_citarget to jump to, stack depth, and 'lasti', which determines whether the instruction
837db96d56Sopenharmony_cioffset of the raising instruction should be pushed.
847db96d56Sopenharmony_ci
857db96d56Sopenharmony_ciThis information is stored in the exception table, described below.
867db96d56Sopenharmony_ci
877db96d56Sopenharmony_ciIf there is no relevant entry, the exception bubbles up to the caller.
887db96d56Sopenharmony_ci
897db96d56Sopenharmony_ciIf there is an entry, then:
907db96d56Sopenharmony_ci 1. pop values from the stack until it matches the stack depth for the handler.
917db96d56Sopenharmony_ci 2. if 'lasti' is true, then push the offset that the exception was raised at.
927db96d56Sopenharmony_ci 3. push the exception to the stack.
937db96d56Sopenharmony_ci 4. jump to the target offset and resume execution.
947db96d56Sopenharmony_ci
957db96d56Sopenharmony_ci
967db96d56Sopenharmony_ciFormat of the exception table
977db96d56Sopenharmony_ci-----------------------------
987db96d56Sopenharmony_ci
997db96d56Sopenharmony_ciConceptually, the exception table consists of a sequence of 5-tuples:
1007db96d56Sopenharmony_ci    1. start-offset (inclusive)
1017db96d56Sopenharmony_ci    2. end-offset (exclusive)
1027db96d56Sopenharmony_ci    3. target
1037db96d56Sopenharmony_ci    4. stack-depth
1047db96d56Sopenharmony_ci    5. push-lasti (boolean)
1057db96d56Sopenharmony_ci
1067db96d56Sopenharmony_ciAll offsets and lengths are in instructions, not bytes.
1077db96d56Sopenharmony_ci
1087db96d56Sopenharmony_ciWe want the format to be compact, but quickly searchable.
1097db96d56Sopenharmony_ciFor it to be compact, it needs to have variable sized entries so that we can store common (small) offsets compactly, but handle large offsets if needed.
1107db96d56Sopenharmony_ciFor it to be searchable quickly, we need to support binary search giving us log(n) performance in all cases.
1117db96d56Sopenharmony_ciBinary search typically assumes fixed size entries, but that is not necessary, as long as we can identify the start of an entry.
1127db96d56Sopenharmony_ci
1137db96d56Sopenharmony_ciIt is worth noting that the size (end-start) is always smaller than the end, so we encode the entries as:
1147db96d56Sopenharmony_ci    start, size, target, depth, push-lasti
1157db96d56Sopenharmony_ci
1167db96d56Sopenharmony_ciAlso, sizes are limited to 2**30 as the code length cannot exceed 2**31 and each instruction takes 2 bytes.
1177db96d56Sopenharmony_ciIt also happens that depth is generally quite small.
1187db96d56Sopenharmony_ci
1197db96d56Sopenharmony_ciSo, we need to encode:
1207db96d56Sopenharmony_ci    start (up to 30 bits)
1217db96d56Sopenharmony_ci    size (up to 30 bits)
1227db96d56Sopenharmony_ci    target (up to 30 bits)
1237db96d56Sopenharmony_ci    depth (up to ~8 bits)
1247db96d56Sopenharmony_ci    lasti (1 bit)
1257db96d56Sopenharmony_ci
1267db96d56Sopenharmony_ciWe need a marker for the start of the entry, so the first byte of entry will have the most significant bit set.
1277db96d56Sopenharmony_ciSince the most significant bit is reserved for marking the start of an entry, we have 7 bits per byte to encode offsets.
1287db96d56Sopenharmony_ciEncoding uses a standard varint encoding, but with only 7 bits instead of the usual 8.
1297db96d56Sopenharmony_ciThe 8 bits of a bit are (msb left) SXdddddd where S is the start bit. X is the extend bit meaning that the next byte is required to extend the offset.
1307db96d56Sopenharmony_ci
1317db96d56Sopenharmony_ciIn addition, we will combine depth and lasti into a single value, ((depth<<1)+lasti), before encoding.
1327db96d56Sopenharmony_ci
1337db96d56Sopenharmony_ciFor example, the exception entry:
1347db96d56Sopenharmony_ci    start:  20
1357db96d56Sopenharmony_ci    end:    28
1367db96d56Sopenharmony_ci    target: 100
1377db96d56Sopenharmony_ci    depth:  3
1387db96d56Sopenharmony_ci    lasti:  False
1397db96d56Sopenharmony_ci
1407db96d56Sopenharmony_ciis encoded first by converting to the more compact four value form:
1417db96d56Sopenharmony_ci    start:         20
1427db96d56Sopenharmony_ci    size:          8
1437db96d56Sopenharmony_ci    target:        100
1447db96d56Sopenharmony_ci  depth<<1+lasti:  6
1457db96d56Sopenharmony_ci
1467db96d56Sopenharmony_ciwhich is then encoded as:
1477db96d56Sopenharmony_ci    148 (MSB + 20 for start)
1487db96d56Sopenharmony_ci    8   (size)
1497db96d56Sopenharmony_ci    65  (Extend bit + 1)
1507db96d56Sopenharmony_ci    36  (Remainder of target, 100 == (1<<6)+36)
1517db96d56Sopenharmony_ci    6
1527db96d56Sopenharmony_ci
1537db96d56Sopenharmony_cifor a total of five bytes.
1547db96d56Sopenharmony_ci
1557db96d56Sopenharmony_ci
1567db96d56Sopenharmony_ci
1577db96d56Sopenharmony_ciScript to parse the exception table
1587db96d56Sopenharmony_ci-----------------------------------
1597db96d56Sopenharmony_ci
1607db96d56Sopenharmony_cidef parse_varint(iterator):
1617db96d56Sopenharmony_ci    b = next(iterator)
1627db96d56Sopenharmony_ci    val = b & 63
1637db96d56Sopenharmony_ci    while b&64:
1647db96d56Sopenharmony_ci        val <<= 6
1657db96d56Sopenharmony_ci        b = next(iterator)
1667db96d56Sopenharmony_ci        val |= b&63
1677db96d56Sopenharmony_ci    return val
1687db96d56Sopenharmony_ci
1697db96d56Sopenharmony_cidef parse_exception_table(code):
1707db96d56Sopenharmony_ci    iterator = iter(code.co_exceptiontable)
1717db96d56Sopenharmony_ci    try:
1727db96d56Sopenharmony_ci        while True:
1737db96d56Sopenharmony_ci            start = parse_varint(iterator)*2
1747db96d56Sopenharmony_ci            length = parse_varint(iterator)*2
1757db96d56Sopenharmony_ci            end = start + length - 2 # Present as inclusive, not exclusive
1767db96d56Sopenharmony_ci            target = parse_varint(iterator)*2
1777db96d56Sopenharmony_ci            dl = parse_varint(iterator)
1787db96d56Sopenharmony_ci            depth = dl >> 1
1797db96d56Sopenharmony_ci            lasti = bool(dl&1)
1807db96d56Sopenharmony_ci            yield start, end, target, depth, lasti
1817db96d56Sopenharmony_ci    except StopIteration:
1827db96d56Sopenharmony_ci        return
183