Skip to content

Commit 317b5bc

Browse files
committed
chore: improve exception handling notes around lasti
This change improves the description of the new exception table, focusing on describing the meaning of the lasti flag, and when one should expect it to be set.
1 parent 7fc7909 commit 317b5bc

1 file changed

Lines changed: 45 additions & 25 deletions

File tree

Objects/exception_handling_notes.txt

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
Description of exception handling in Python 3.11
22
------------------------------------------------
33

4-
Python 3.11 uses what is known as "zero-cost" exception handling.
5-
Prior to 3.11, exceptions were handled by a runtime stack of "blocks".
4+
Python 3.11 uses what is known as "zero-cost" exception handling. Prior to 3.11,
5+
exceptions were handled by a runtime stack of "blocks".
66

77
In zero-cost exception handling, the cost of supporting exceptions is minimized.
8-
In the common case (where no exception is raised) the cost is reduced
9-
to zero (or close to zero).
10-
The cost of raising an exception is increased, but not by much.
8+
In the common case (where no exception is raised) the cost is reduced to zero
9+
(or close to zero). The cost of raising an exception is increased, but not by
10+
much.
1111

1212
The following code:
1313

@@ -40,8 +40,8 @@ compiles as follows in 3.10:
4040
Note the explicit instructions to push and pop from the "block" stack:
4141
SETUP_FINALLY and POP_BLOCK.
4242

43-
In 3.11, the SETUP_FINALLY and POP_BLOCK are eliminated, replaced with
44-
a table to determine where to jump to when an exception is raised.
43+
In 3.11, the SETUP_FINALLY and POP_BLOCK are eliminated, replaced with a table
44+
to determine where to jump to when an exception is raised.
4545

4646
1 0 RESUME 0
4747

@@ -68,19 +68,23 @@ ExceptionTable:
6868
4 to 32 -> 38 [0]
6969
38 to 40 -> 48 [1] lasti
7070

71-
(Note this code is from 3.11, later versions may have slightly different bytecode.)
71+
(Note this code is from 3.11, later versions may have slightly different
72+
bytecode.)
7273

73-
If an instruction raises an exception then its offset is used to find the target to jump to.
74-
For example, the CALL at offset 22, falls into the range 4 to 32.
74+
If an instruction raises an exception then its offset is used to find the target
75+
to jump to. For example, the CALL at offset 22, falls into the range 4 to 32.
7576
So, if g() raises an exception, then control jumps to offset 38.
7677

7778

7879
Unwinding
7980
---------
8081

81-
When an exception is raised, the current instruction offset is used to find following:
82-
target to jump to, stack depth, and 'lasti', which determines whether the instruction
83-
offset of the raising instruction should be pushed.
82+
When an exception is raised, the current instruction offset is used to find the
83+
following information:
84+
- target to jump to;
85+
- stack depth;
86+
- 'lasti' flag, which determines whether the instruction offset of the raising
87+
instruction should be pushed to the TOS.
8488

8589
This information is stored in the exception table, described below.
8690

@@ -92,6 +96,16 @@ If there is an entry, then:
9296
3. push the exception to the stack.
9397
4. jump to the target offset and resume execution.
9498

99+
Currently, the only opcode that requires the lasti flag to be set is RERAISE.
100+
That is, if the jump described by an exception table points to an offset where
101+
a RERAISE (with a non-zero oparg) occurs, the lasti is expected to be on the
102+
stack, and therefore the lasti flag of the exception table entry must be set.
103+
The last example gives an illustration of this. The first table entry describes
104+
a jump where no RERAISE is executed. The second entry describes a jump where a
105+
RERAISE with oparg 1 is executed. This means that the RERAISE will consume the
106+
TOS and assume it is the lasti that was pushed when the exception occurred, as
107+
instructed by the lasti flag.
108+
95109

96110
Format of the exception table
97111
-----------------------------
@@ -105,16 +119,19 @@ Conceptually, the exception table consists of a sequence of 5-tuples:
105119

106120
All offsets and lengths are in instructions, not bytes.
107121

108-
We want the format to be compact, but quickly searchable.
109-
For 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.
110-
For it to be searchable quickly, we need to support binary search giving us log(n) performance in all cases.
111-
Binary search typically assumes fixed size entries, but that is not necessary, as long as we can identify the start of an entry.
122+
We want the format to be compact, but quickly searchable. For it to be compact,
123+
it needs to have variable-sized entries so that we can store common (small)
124+
offsets compactly, but handle large offsets if needed. For it to be searchable
125+
quickly, we need to support binary search giving us log(n) performance in all
126+
cases. Binary search typically assumes fixed-size entries, but that is not
127+
necessary, as long as we can identify the start of an entry.
112128

113-
It is worth noting that the size (end-start) is always smaller than the end, so we encode the entries as:
129+
It is worth noting that the size (end-start) is always smaller than the end, so
130+
we encode the entries as:
114131
start, size, target, depth, push-lasti
115132

116-
Also, sizes are limited to 2**30 as the code length cannot exceed 2**31 and each instruction takes 2 bytes.
117-
It also happens that depth is generally quite small.
133+
Also, sizes are limited to 2**30 as the code length cannot exceed 2**31 and each
134+
instruction takes 2 bytes. It also happens that depth is generally quite small.
118135

119136
So, we need to encode:
120137
start (up to 30 bits)
@@ -123,12 +140,15 @@ So, we need to encode:
123140
depth (up to ~8 bits)
124141
lasti (1 bit)
125142

126-
We need a marker for the start of the entry, so the first byte of entry will have the most significant bit set.
127-
Since the most significant bit is reserved for marking the start of an entry, we have 7 bits per byte to encode offsets.
128-
Encoding uses a standard varint encoding, but with only 7 bits instead of the usual 8.
129-
The 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.
143+
We need a marker for the start of the entry, so the first byte of entry will
144+
have the most significant bit set. Since the most significant bit is reserved
145+
for marking the start of an entry, we have 7 bits per byte to encode offsets.
146+
Encoding uses a standard varint encoding, but with only 7 bits instead of the
147+
usual 8. The 8 bits of a bit are (msb left) SXdddddd where S is the start bit. X
148+
is the extend bit meaning that the next byte is required to extend the offset.
130149

131-
In addition, we will combine depth and lasti into a single value, ((depth<<1)+lasti), before encoding.
150+
In addition, we will combine depth and lasti into a single value,
151+
((depth<<1)+lasti), before encoding.
132152

133153
For example, the exception entry:
134154
start: 20

0 commit comments

Comments
 (0)