Skip to content

Commit 2a18e80

Browse files
authored
GH-128534: Instrument branches for async for loops. (GH-130569)
1 parent fda056e commit 2a18e80

11 files changed

+140
-35
lines changed

Include/internal/pycore_magic_number.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ Known values:
269269
Python 3.14a5 3614 (Add BINARY_OP_EXTEND)
270270
Python 3.14a5 3615 (CALL_FUNCTION_EX always take a kwargs argument)
271271
Python 3.14a5 3616 (Remove BINARY_SUBSCR and family. Make them BINARY_OPs)
272+
Python 3.14a6 3617 (Branch monitoring for async for loops)
272273
273274
Python 3.15 will start with 3650
274275
@@ -281,7 +282,7 @@ PC/launcher.c must also be updated.
281282
282283
*/
283284

284-
#define PYC_MAGIC_NUMBER 3616
285+
#define PYC_MAGIC_NUMBER 3617
285286
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
286287
(little-endian) and then appending b'\r\n'. */
287288
#define PYC_MAGIC_NUMBER_TOKEN \

Include/internal/pycore_opcode_metadata.h

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

Include/opcode_ids.h

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

Lib/_opcode_metadata.py

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

Lib/test/test_monitoring.py

+23
Original file line numberDiff line numberDiff line change
@@ -1657,6 +1657,29 @@ def foo(n=0):
16571657
in_loop,
16581658
exit_loop])
16591659

1660+
def test_async_for(self):
1661+
1662+
def func():
1663+
async def gen():
1664+
yield 2
1665+
yield 3
1666+
1667+
async def foo():
1668+
async for y in gen():
1669+
2
1670+
pass # line 3
1671+
1672+
try:
1673+
foo().send(None)
1674+
except StopIteration:
1675+
pass
1676+
1677+
self.check_events(func, recorders = BRANCHES_RECORDERS, expected = [
1678+
('branch left', 'foo', 1, 1),
1679+
('branch left', 'foo', 1, 1),
1680+
('branch right', 'foo', 1, 3),
1681+
('branch left', 'func', 12, 12)])
1682+
16601683

16611684
class TestBranchConsistency(MonitoringTestBase, unittest.TestCase):
16621685

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add branch monitoring (``BRANCH_LEFT`` and ``BRANCH_RIGHT`` events) for
2+
``async for`` loops.

Python/bytecodes.c

+11-1
Original file line numberDiff line numberDiff line change
@@ -1339,7 +1339,7 @@ dummy_func(
13391339
goto exception_unwind;
13401340
}
13411341

1342-
tier1 inst(END_ASYNC_FOR, (awaitable_st, exc_st -- )) {
1342+
tier1 op(_END_ASYNC_FOR, (awaitable_st, exc_st -- )) {
13431343
PyObject *exc = PyStackRef_AsPyObjectBorrow(exc_st);
13441344

13451345
assert(exc && PyExceptionInstance_Check(exc));
@@ -1355,6 +1355,16 @@ dummy_func(
13551355
}
13561356
}
13571357

1358+
tier1 op(_MONITOR_BRANCH_RIGHT, ( -- )) {
1359+
INSTRUMENTED_JUMP(prev_instr, this_instr+1, PY_MONITORING_EVENT_BRANCH_RIGHT);
1360+
}
1361+
1362+
macro(INSTRUMENTED_END_ASYNC_FOR) =
1363+
_MONITOR_BRANCH_RIGHT +
1364+
_END_ASYNC_FOR;
1365+
1366+
macro(END_ASYNC_FOR) = _END_ASYNC_FOR;
1367+
13581368
tier1 inst(CLEANUP_THROW, (sub_iter, last_sent_val, exc_value_st -- none, value)) {
13591369
PyObject *exc_value = PyStackRef_AsPyObjectBorrow(exc_value_st);
13601370
#if !Py_TAIL_CALL_INTERP

Python/codegen.c

+1
Original file line numberDiff line numberDiff line change
@@ -2041,6 +2041,7 @@ codegen_async_for(compiler *c, stmt_ty s)
20412041
ADDOP_LOAD_CONST(c, loc, Py_None);
20422042
ADD_YIELD_FROM(c, loc, 1);
20432043
ADDOP(c, loc, POP_BLOCK); /* for SETUP_FINALLY */
2044+
ADDOP(c, loc, NOT_TAKEN);
20442045

20452046
/* Success block for __anext__ */
20462047
VISIT(c, expr, s->v.AsyncFor.target);

Python/generated_cases.c.h

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

Python/instrumentation.c

+4
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ static const int8_t EVENT_FOR_OPCODE[256] = {
106106
[INSTRUMENTED_END_SEND] = PY_MONITORING_EVENT_STOP_ITERATION,
107107
[NOT_TAKEN] = PY_MONITORING_EVENT_BRANCH_LEFT,
108108
[INSTRUMENTED_NOT_TAKEN] = PY_MONITORING_EVENT_BRANCH_LEFT,
109+
[END_ASYNC_FOR] = PY_MONITORING_EVENT_BRANCH_RIGHT,
109110
};
110111

111112
static const uint8_t DE_INSTRUMENT[256] = {
@@ -127,6 +128,7 @@ static const uint8_t DE_INSTRUMENT[256] = {
127128
[INSTRUMENTED_END_SEND] = END_SEND,
128129
[INSTRUMENTED_LOAD_SUPER_ATTR] = LOAD_SUPER_ATTR,
129130
[INSTRUMENTED_NOT_TAKEN] = NOT_TAKEN,
131+
[INSTRUMENTED_END_ASYNC_FOR] = END_ASYNC_FOR,
130132
};
131133

132134
static const uint8_t INSTRUMENTED_OPCODES[256] = {
@@ -166,6 +168,8 @@ static const uint8_t INSTRUMENTED_OPCODES[256] = {
166168
[INSTRUMENTED_LOAD_SUPER_ATTR] = INSTRUMENTED_LOAD_SUPER_ATTR,
167169
[NOT_TAKEN] = INSTRUMENTED_NOT_TAKEN,
168170
[INSTRUMENTED_NOT_TAKEN] = INSTRUMENTED_NOT_TAKEN,
171+
[END_ASYNC_FOR] = INSTRUMENTED_END_ASYNC_FOR,
172+
[INSTRUMENTED_END_ASYNC_FOR] = INSTRUMENTED_END_ASYNC_FOR,
169173

170174
[INSTRUMENTED_LINE] = INSTRUMENTED_LINE,
171175
[INSTRUMENTED_INSTRUCTION] = INSTRUMENTED_INSTRUCTION,

Python/opcode_targets.h

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

0 commit comments

Comments
 (0)