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