Hi all,
I am the current maintainer of bytecode (
https://github.com/MatthieuDartiailh/bytecode)
which is a library to perform assembly and disassembly of
Python bytecode. The library was created by V. Stinner.
I started looking in Python 3.11 support in bytecode, I
read Objects/exception_handling_notes.txt and I have a
couple of questions regarding the exception table:
Currently bytecode exposes three level of abstractions:
- the concrete level in which one deals with instruction
offset for jumps and explicit indexing into the known
constants and names
- the bytecode level which uses labels for jumps and
allow non integer argument to instructions
- the cfg level which provides basic blocks delineation
over the bytecode level
So my first idea was to directly expose the unpacked
exception table (start, stop, target, stack_depth, last_i)
at the concrete level and use pseudo-instruction and
labels at the bytecode level. At this point of my
reflections, I saw
https://github.com/python/cpython/commit/c57aad777afc6c0b382981ee9e4bc94c03bf5f68
about adding pseudo-instructionto dis output in 3.12 and
though it would line up quite nicely. Reading through, I
got curious about how
SETUP_WITH handled
popping one extra item from the stack so I went to
look at dis results on a couple of small examples. I
tried on 3.10 and 3.11b3 (for some reasons I cannot
compile main at a391b74d on windows).
I looked at simple things and got a bit surprised:
Disassembling:
def f():
try:
a = 1
except:
raise
I get on 3.11:
1 0 RESUME 0
2 2 NOP
3 4 LOAD_CONST 1 (1)
6 STORE_FAST 0 (a)
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
>> 12 PUSH_EXC_INFO
4 14 POP_TOP
5 16 RAISE_VARARGS 0
>> 18 COPY 3
20 POP_EXCEPT
22 RERAISE 1
ExceptionTable:
4 to 6 -> 12 [0]
12 to 16 -> 18 [1] lasti
On 3.10:
2 0 SETUP_FINALLY 5 (to 12)
3 2 LOAD_CONST 1 (1)
4 STORE_FAST 0 (a)
6 POP_BLOCK
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
4 >> 12 POP_TOP
14 POP_TOP
16 POP_TOP
5 18 RAISE_VARARGS 0
This surprised me on two levels:
- first I have never seen the RESUME opcode and it is
currently not documented
- my second surprise comes from the second entry in
the exception table. At first I failed to see why it
was needed but writing this I realize it corresponds
to the explicit handling of exception propagation to
the caller. Since I cannot compile 3.12 ATM I am
wondering how this plays with pseudo-instruction: in
particular are pseudo-instructions generated for all
entries in the exception table ?
My initial idea was to have a
SETUP_FINALLY/SETUP_CLEANUP - POP_BLOCK pair for each
line in the exception table and label for the jump
target. But I realize it means we will have many such
pairs than in 3.10. It is fine by me but I wondered
what choice was made in 3.12 dis and if this approach
made sense.
Best regards
Matthieu
_______________________________________________