Skip to content

Commit e028ae9

Browse files
authored
bpo-45923: Handle call events in bytecode (GH-30364)
* Add a RESUME instruction to handle "call" events.
1 parent 3e43fac commit e028ae9

File tree

13 files changed

+678
-529
lines changed

13 files changed

+678
-529
lines changed

Doc/library/dis.rst

+14
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,20 @@ All of the following opcodes use their arguments.
12061206
.. versionadded:: 3.11
12071207

12081208

1209+
.. opcode:: RESUME (where)
1210+
1211+
A no-op. Performs internal tracing, debugging and optimization checks.
1212+
1213+
The ``where`` operand marks where the ``RESUME`` occurs:
1214+
1215+
* ``0`` The start of a function
1216+
* ``1`` After a ``yield`` expression
1217+
* ``2`` After a ``yield from`` expression
1218+
* ``3`` After an ``await`` expression
1219+
1220+
.. versionadded:: 3.11
1221+
1222+
12091223
.. opcode:: HAVE_ARGUMENT
12101224

12111225
This is not really an opcode. It identifies the dividing line between

Include/opcode.h

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/importlib/_bootstrap_external.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -379,16 +379,21 @@ def _write_atomic(path, data, mode=0o666):
379379
# Python 3.11a4 3471 (bpo-46202: remove pop POP_EXCEPT_AND_RERAISE)
380380
# Python 3.11a4 3472 (bpo-46009: replace GEN_START with POP_TOP)
381381
# Python 3.11a4 3473 (Add POP_JUMP_IF_NOT_NONE/POP_JUMP_IF_NONE opcodes)
382+
# Python 3.11a4 3474 (Add RESUME opcode)
383+
384+
# Python 3.12 will start with magic number 3500
382385

383386
#
384387
# MAGIC must change whenever the bytecode emitted by the compiler may no
385388
# longer be understood by older implementations of the eval loop (usually
386389
# due to the addition of new opcodes).
387390
#
391+
# Starting with Python 3.11, Python 3.n starts with magic number 2900+50n.
392+
#
388393
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
389394
# in PC/launcher.c must also be updated.
390395

391-
MAGIC_NUMBER = (3473).to_bytes(2, 'little') + b'\r\n'
396+
MAGIC_NUMBER = (3474).to_bytes(2, 'little') + b'\r\n'
392397
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
393398

394399
_PYCACHE = '__pycache__'

Lib/opcode.py

+1
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ def jabs_op(name, op):
178178
hasfree.append(148)
179179
def_op('COPY_FREE_VARS', 149)
180180

181+
def_op('RESUME', 151)
181182
def_op('MATCH_CLASS', 152)
182183

183184
def_op('FORMAT_VALUE', 155)

Lib/test/test_code.py

+11-4
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ def test_co_positions_artificial_instructions(self):
367367
# get assigned the first_lineno but they don't have other positions.
368368
# There is no easy way of inferring them at that stage, so for now
369369
# we don't support it.
370-
self.assertTrue(positions.count(None) in [0, 4])
370+
self.assertIn(positions.count(None), [0, 3, 4])
371371

372372
if not any(positions):
373373
artificial_instructions.append(instr)
@@ -378,6 +378,7 @@ def test_co_positions_artificial_instructions(self):
378378
for instruction in artificial_instructions
379379
],
380380
[
381+
('RESUME', 0),
381382
("PUSH_EXC_INFO", None),
382383
("LOAD_CONST", None), # artificial 'None'
383384
("STORE_NAME", "e"), # XX: we know the location for this
@@ -419,7 +420,9 @@ def test_co_positions_empty_linetable(self):
419420
def func():
420421
x = 1
421422
new_code = func.__code__.replace(co_linetable=b'')
422-
for line, end_line, column, end_column in new_code.co_positions():
423+
positions = new_code.co_positions()
424+
next(positions) # Skip RESUME at start
425+
for line, end_line, column, end_column in positions:
423426
self.assertIsNone(line)
424427
self.assertEqual(end_line, new_code.co_firstlineno + 1)
425428

@@ -428,7 +431,9 @@ def test_co_positions_empty_endlinetable(self):
428431
def func():
429432
x = 1
430433
new_code = func.__code__.replace(co_endlinetable=b'')
431-
for line, end_line, column, end_column in new_code.co_positions():
434+
positions = new_code.co_positions()
435+
next(positions) # Skip RESUME at start
436+
for line, end_line, column, end_column in positions:
432437
self.assertEqual(line, new_code.co_firstlineno + 1)
433438
self.assertIsNone(end_line)
434439

@@ -437,7 +442,9 @@ def test_co_positions_empty_columntable(self):
437442
def func():
438443
x = 1
439444
new_code = func.__code__.replace(co_columntable=b'')
440-
for line, end_line, column, end_column in new_code.co_positions():
445+
positions = new_code.co_positions()
446+
next(positions) # Skip RESUME at start
447+
for line, end_line, column, end_column in positions:
441448
self.assertEqual(line, new_code.co_firstlineno + 1)
442449
self.assertEqual(end_line, new_code.co_firstlineno + 1)
443450
self.assertIsNone(column)

Lib/test/test_compile.py

+16-15
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ def test_leading_newlines(self):
158158
s256 = "".join(["\n"] * 256 + ["spam"])
159159
co = compile(s256, 'fn', 'exec')
160160
self.assertEqual(co.co_firstlineno, 1)
161-
self.assertEqual(list(co.co_lines()), [(0, 8, 257)])
161+
self.assertEqual(list(co.co_lines()), [(0, 2, None), (2, 10, 257)])
162162

163163
def test_literals_with_leading_zeroes(self):
164164
for arg in ["077787", "0xj", "0x.", "0e", "090000000000000",
@@ -759,7 +759,7 @@ def unused_block_while_else():
759759

760760
for func in funcs:
761761
opcodes = list(dis.get_instructions(func))
762-
self.assertLessEqual(len(opcodes), 3)
762+
self.assertLessEqual(len(opcodes), 4)
763763
self.assertEqual('LOAD_CONST', opcodes[-2].opname)
764764
self.assertEqual(None, opcodes[-2].argval)
765765
self.assertEqual('RETURN_VALUE', opcodes[-1].opname)
@@ -778,10 +778,10 @@ def continue_in_while():
778778
# Check that we did not raise but we also don't generate bytecode
779779
for func in funcs:
780780
opcodes = list(dis.get_instructions(func))
781-
self.assertEqual(2, len(opcodes))
782-
self.assertEqual('LOAD_CONST', opcodes[0].opname)
783-
self.assertEqual(None, opcodes[0].argval)
784-
self.assertEqual('RETURN_VALUE', opcodes[1].opname)
781+
self.assertEqual(3, len(opcodes))
782+
self.assertEqual('LOAD_CONST', opcodes[1].opname)
783+
self.assertEqual(None, opcodes[1].argval)
784+
self.assertEqual('RETURN_VALUE', opcodes[2].opname)
785785

786786
def test_consts_in_conditionals(self):
787787
def and_true(x):
@@ -802,9 +802,9 @@ def or_false(x):
802802
for func in funcs:
803803
with self.subTest(func=func):
804804
opcodes = list(dis.get_instructions(func))
805-
self.assertEqual(2, len(opcodes))
806-
self.assertIn('LOAD_', opcodes[0].opname)
807-
self.assertEqual('RETURN_VALUE', opcodes[1].opname)
805+
self.assertLessEqual(len(opcodes), 3)
806+
self.assertIn('LOAD_', opcodes[-2].opname)
807+
self.assertEqual('RETURN_VALUE', opcodes[-1].opname)
808808

809809
def test_imported_load_method(self):
810810
sources = [
@@ -906,7 +906,7 @@ def load_attr():
906906
o.
907907
a
908908
)
909-
load_attr_lines = [ 2, 3, 1 ]
909+
load_attr_lines = [ 0, 2, 3, 1 ]
910910

911911
def load_method():
912912
return (
@@ -915,7 +915,7 @@ def load_method():
915915
0
916916
)
917917
)
918-
load_method_lines = [ 2, 3, 4, 3, 1 ]
918+
load_method_lines = [ 0, 2, 3, 4, 3, 1 ]
919919

920920
def store_attr():
921921
(
@@ -924,7 +924,7 @@ def store_attr():
924924
) = (
925925
v
926926
)
927-
store_attr_lines = [ 5, 2, 3 ]
927+
store_attr_lines = [ 0, 5, 2, 3 ]
928928

929929
def aug_store_attr():
930930
(
@@ -933,7 +933,7 @@ def aug_store_attr():
933933
) += (
934934
v
935935
)
936-
aug_store_attr_lines = [ 2, 3, 5, 1, 3 ]
936+
aug_store_attr_lines = [ 0, 2, 3, 5, 1, 3 ]
937937

938938
funcs = [ load_attr, load_method, store_attr, aug_store_attr]
939939
func_lines = [ load_attr_lines, load_method_lines,
@@ -942,7 +942,8 @@ def aug_store_attr():
942942
for func, lines in zip(funcs, func_lines, strict=True):
943943
with self.subTest(func=func):
944944
code_lines = [ line-func.__code__.co_firstlineno
945-
for (_, _, line) in func.__code__.co_lines() ]
945+
for (_, _, line) in func.__code__.co_lines()
946+
if line is not None ]
946947
self.assertEqual(lines, code_lines)
947948

948949
def test_line_number_genexp(self):
@@ -966,7 +967,7 @@ async def test(aseq):
966967
async for i in aseq:
967968
body
968969

969-
expected_lines = [None, 1, 2, 1]
970+
expected_lines = [None, 0, 1, 2, 1]
970971
code_lines = [ None if line is None else line-test.__code__.co_firstlineno
971972
for (_, _, line) in test.__code__.co_lines() ]
972973
self.assertEqual(expected_lines, code_lines)

0 commit comments

Comments
 (0)