You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
Copy file name to clipboardExpand all lines: Objects/exception_handling_notes.txt
+45-25Lines changed: 45 additions & 25 deletions
Original file line number
Diff line number
Diff line change
@@ -1,13 +1,13 @@
1
1
Description of exception handling in Python 3.11
2
2
------------------------------------------------
3
3
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".
6
6
7
7
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.
11
11
12
12
The following code:
13
13
@@ -40,8 +40,8 @@ compiles as follows in 3.10:
40
40
Note the explicit instructions to push and pop from the "block" stack:
41
41
SETUP_FINALLY and POP_BLOCK.
42
42
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.
45
45
46
46
1 0 RESUME 0
47
47
@@ -68,19 +68,23 @@ ExceptionTable:
68
68
4 to 32 -> 38 [0]
69
69
38 to 40 -> 48 [1] lasti
70
70
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.)
72
73
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.
75
76
So, if g() raises an exception, then control jumps to offset 38.
76
77
77
78
78
79
Unwinding
79
80
---------
80
81
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.
84
88
85
89
This information is stored in the exception table, described below.
86
90
@@ -92,6 +96,16 @@ If there is an entry, then:
92
96
3. push the exception to the stack.
93
97
4. jump to the target offset and resume execution.
94
98
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
+
95
109
96
110
Format of the exception table
97
111
-----------------------------
@@ -105,16 +119,19 @@ Conceptually, the exception table consists of a sequence of 5-tuples:
105
119
106
120
All offsets and lengths are in instructions, not bytes.
107
121
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.
112
128
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:
114
131
start, size, target, depth, push-lasti
115
132
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.
118
135
119
136
So, we need to encode:
120
137
start (up to 30 bits)
@@ -123,12 +140,15 @@ So, we need to encode:
123
140
depth (up to ~8 bits)
124
141
lasti (1 bit)
125
142
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.
130
149
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,
0 commit comments