Skip to content

Commit d2f1d91

Browse files
authored
pythonGH-122548: Implement branch taken and not taken events for sys.monitoring (pythonGH-122564)
1 parent 7b811d0 commit d2f1d91

29 files changed

+998
-583
lines changed

Doc/c-api/monitoring.rst

+9-3
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,14 @@ See :mod:`sys.monitoring` for descriptions of the events.
7575
Fire a ``JUMP`` event.
7676
7777
78-
.. c:function:: int PyMonitoring_FireBranchEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *target_offset)
78+
.. c:function:: int PyMonitoring_FireBranchLeftEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *target_offset)
7979
80-
Fire a ``BRANCH`` event.
80+
Fire a ``BRANCH_LEFT`` event.
81+
82+
83+
.. c:function:: int PyMonitoring_FireBranchRightEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *target_offset)
84+
85+
Fire a ``BRANCH_RIGHT`` event.
8186
8287
8388
.. c:function:: int PyMonitoring_FireCReturnEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *retval)
@@ -168,7 +173,8 @@ would typically correspond to a python function.
168173
================================================== =====================================
169174
Macro Event
170175
================================================== =====================================
171-
.. c:macro:: PY_MONITORING_EVENT_BRANCH :monitoring-event:`BRANCH`
176+
.. c:macro:: PY_MONITORING_EVENT_BRANCH_LEFT :monitoring-event:`BRANCH_LEFT`
177+
.. c:macro:: PY_MONITORING_EVENT_BRANCH_RIGHT :monitoring-event:`BRANCH_RIGHT`
172178
.. c:macro:: PY_MONITORING_EVENT_CALL :monitoring-event:`CALL`
173179
.. c:macro:: PY_MONITORING_EVENT_C_RAISE :monitoring-event:`C_RAISE`
174180
.. c:macro:: PY_MONITORING_EVENT_C_RETURN :monitoring-event:`C_RETURN`

Doc/library/sys.monitoring.rst

+23-6
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,17 @@ Events
7979

8080
The following events are supported:
8181

82-
.. monitoring-event:: BRANCH
82+
.. monitoring-event:: BRANCH_LEFT
8383

84-
A conditional branch is taken (or not).
84+
A conditional branch goes left.
85+
86+
It is up to the tool to determine how to present "left" and "right" branches.
87+
There is no guarantee which branch is "left" and which is "right", except
88+
that it will be consistent for the duration of the program.
89+
90+
.. monitoring-event:: BRANCH_RIGHT
91+
92+
A conditional branch goes right.
8593

8694
.. monitoring-event:: CALL
8795

@@ -180,9 +188,20 @@ The local events are:
180188
* :monitoring-event:`LINE`
181189
* :monitoring-event:`INSTRUCTION`
182190
* :monitoring-event:`JUMP`
183-
* :monitoring-event:`BRANCH`
191+
* :monitoring-event:`BRANCH_LEFT`
192+
* :monitoring-event:`BRANCH_RIGHT`
184193
* :monitoring-event:`STOP_ITERATION`
185194

195+
Deprecated event
196+
''''''''''''''''
197+
198+
* ``BRANCH``
199+
200+
The ``BRANCH`` event is deprecated in 3.14.
201+
Using :monitoring-event:`BRANCH_LEFT` and :monitoring-event:`BRANCH_RIGHT`
202+
events will give much better performance as they can be disabled
203+
independently.
204+
186205
Ancillary events
187206
''''''''''''''''
188207

@@ -357,13 +376,11 @@ Different events will provide the callback function with different arguments, as
357376

358377
func(code: CodeType, line_number: int) -> DISABLE | Any
359378

360-
* :monitoring-event:`BRANCH` and :monitoring-event:`JUMP`::
379+
* :monitoring-event:`BRANCH_LEFT`, :monitoring-event:`BRANCH_RIGHT` and :monitoring-event:`JUMP`::
361380

362381
func(code: CodeType, instruction_offset: int, destination_offset: int) -> DISABLE | Any
363382

364383
Note that the *destination_offset* is where the code will next execute.
365-
For an untaken branch this will be the offset of the instruction following
366-
the branch.
367384

368385
* :monitoring-event:`INSTRUCTION`::
369386

Doc/whatsnew/3.13.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -1971,7 +1971,7 @@ New Features
19711971
* :c:func:`PyMonitoring_FireCallEvent`
19721972
* :c:func:`PyMonitoring_FireLineEvent`
19731973
* :c:func:`PyMonitoring_FireJumpEvent`
1974-
* :c:func:`PyMonitoring_FireBranchEvent`
1974+
* ``PyMonitoring_FireBranchEvent``
19751975
* :c:func:`PyMonitoring_FireCReturnEvent`
19761976
* :c:func:`PyMonitoring_FirePyThrowEvent`
19771977
* :c:func:`PyMonitoring_FireRaiseEvent`

Doc/whatsnew/3.14.rst

+14
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,11 @@ sys
603603
which only exists in specialized builds of Python, may now return objects
604604
from other interpreters than the one it's called in.
605605

606+
sys.monitoring
607+
--------------
608+
609+
Two new events are added: :monitoring-event:`BRANCH_LEFT` and
610+
:monitoring-event:`BRANCH_RIGHT`. The ``BRANCH`` event is deprecated.
606611

607612
tkinter
608613
-------
@@ -1144,6 +1149,11 @@ New features
11441149
a :exc:`UnicodeError` object.
11451150
(Contributed by Bénédikt Tran in :gh:`127691`.)
11461151

1152+
* Add :c:func:`PyMonitoring_FireBranchLeftEvent` and
1153+
:c:func:`PyMonitoring_FireBranchRightEvent` for generating
1154+
:monitoring-event:`BRANCH_LEFT` and :monitoring-event:`BRANCH_RIGHT`
1155+
events, respectively.
1156+
11471157

11481158
Porting to Python 3.14
11491159
----------------------
@@ -1177,6 +1187,10 @@ Deprecated
11771187

11781188
.. include:: ../deprecations/c-api-pending-removal-in-future.rst
11791189

1190+
* The ``PyMonitoring_FireBranchEvent`` function is deprecated and should
1191+
be replaced with calls to :c:func:`PyMonitoring_FireBranchLeftEvent`
1192+
and :c:func:`PyMonitoring_FireBranchRightEvent`.
1193+
11801194
Removed
11811195
-------
11821196

Include/cpython/code.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ extern "C" {
1111
/* Total tool ids available */
1212
#define _PY_MONITORING_TOOL_IDS 8
1313
/* Count of all local monitoring events */
14-
#define _PY_MONITORING_LOCAL_EVENTS 10
14+
#define _PY_MONITORING_LOCAL_EVENTS 11
1515
/* Count of all "real" monitoring events (not derived from other events) */
16-
#define _PY_MONITORING_UNGROUPED_EVENTS 15
16+
#define _PY_MONITORING_UNGROUPED_EVENTS 16
1717
/* Count of all monitoring events */
18-
#define _PY_MONITORING_EVENTS 17
18+
#define _PY_MONITORING_EVENTS 19
1919

2020
/* Tables of which tools are active for each monitored event. */
2121
typedef struct _Py_LocalMonitors {

Include/cpython/monitoring.h

+31-12
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,27 @@
1313
#define PY_MONITORING_EVENT_LINE 5
1414
#define PY_MONITORING_EVENT_INSTRUCTION 6
1515
#define PY_MONITORING_EVENT_JUMP 7
16-
#define PY_MONITORING_EVENT_BRANCH 8
17-
#define PY_MONITORING_EVENT_STOP_ITERATION 9
16+
#define PY_MONITORING_EVENT_BRANCH_LEFT 8
17+
#define PY_MONITORING_EVENT_BRANCH_RIGHT 9
18+
#define PY_MONITORING_EVENT_STOP_ITERATION 10
1819

1920
#define PY_MONITORING_IS_INSTRUMENTED_EVENT(ev) \
2021
((ev) < _PY_MONITORING_LOCAL_EVENTS)
2122

2223
/* Other events, mainly exceptions */
2324

24-
#define PY_MONITORING_EVENT_RAISE 10
25-
#define PY_MONITORING_EVENT_EXCEPTION_HANDLED 11
26-
#define PY_MONITORING_EVENT_PY_UNWIND 12
27-
#define PY_MONITORING_EVENT_PY_THROW 13
28-
#define PY_MONITORING_EVENT_RERAISE 14
25+
#define PY_MONITORING_EVENT_RAISE 11
26+
#define PY_MONITORING_EVENT_EXCEPTION_HANDLED 12
27+
#define PY_MONITORING_EVENT_PY_UNWIND 13
28+
#define PY_MONITORING_EVENT_PY_THROW 14
29+
#define PY_MONITORING_EVENT_RERAISE 15
2930

3031

3132
/* Ancillary events */
3233

33-
#define PY_MONITORING_EVENT_C_RETURN 15
34-
#define PY_MONITORING_EVENT_C_RAISE 16
34+
#define PY_MONITORING_EVENT_C_RETURN 16
35+
#define PY_MONITORING_EVENT_C_RAISE 17
36+
#define PY_MONITORING_EVENT_BRANCH 18
3537

3638

3739
typedef struct _PyMonitoringState {
@@ -74,10 +76,18 @@ PyAPI_FUNC(int)
7476
_PyMonitoring_FireJumpEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset,
7577
PyObject *target_offset);
7678

77-
PyAPI_FUNC(int)
79+
Py_DEPRECATED(3.14) PyAPI_FUNC(int)
7880
_PyMonitoring_FireBranchEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset,
7981
PyObject *target_offset);
8082

83+
PyAPI_FUNC(int)
84+
_PyMonitoring_FireBranchRightEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset,
85+
PyObject *target_offset);
86+
87+
PyAPI_FUNC(int)
88+
_PyMonitoring_FireBranchLeftEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset,
89+
PyObject *target_offset);
90+
8191
PyAPI_FUNC(int)
8292
_PyMonitoring_FireCReturnEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset,
8393
PyObject *retval);
@@ -174,12 +184,21 @@ PyMonitoring_FireJumpEvent(PyMonitoringState *state, PyObject *codelike, int32_t
174184
}
175185

176186
static inline int
177-
PyMonitoring_FireBranchEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset,
187+
PyMonitoring_FireBranchRightEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset,
188+
PyObject *target_offset)
189+
{
190+
_PYMONITORING_IF_ACTIVE(
191+
state,
192+
_PyMonitoring_FireBranchRightEvent(state, codelike, offset, target_offset));
193+
}
194+
195+
static inline int
196+
PyMonitoring_FireBranchLeftEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset,
178197
PyObject *target_offset)
179198
{
180199
_PYMONITORING_IF_ACTIVE(
181200
state,
182-
_PyMonitoring_FireBranchEvent(state, codelike, offset, target_offset));
201+
_PyMonitoring_FireBranchLeftEvent(state, codelike, offset, target_offset));
183202
}
184203

185204
static inline int

Include/internal/pycore_code.h

+2
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,8 @@ extern _Py_CODEUNIT _Py_GetBaseCodeUnit(PyCodeObject *code, int offset);
603603

604604
extern int _PyInstruction_GetLength(PyCodeObject *code, int offset);
605605

606+
extern PyObject *_PyInstrumentation_BranchesIterator(PyCodeObject *code);
607+
606608
struct _PyCode8 _PyCode_DEF(8);
607609

608610
PyAPI_DATA(const struct _PyCode8) _Py_InitCleanup;

Include/internal/pycore_magic_number.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ Known values:
262262
Python 3.14a1 3607 (Add pseudo instructions JUMP_IF_TRUE/FALSE)
263263
Python 3.14a1 3608 (Add support for slices)
264264
Python 3.14a2 3609 (Add LOAD_SMALL_INT and LOAD_CONST_IMMORTAL instructions, remove RETURN_CONST)
265+
Python 3.14a3 3610 (Add NOT_TAKEN instruction)
265266
266267
Python 3.15 will start with 3650
267268
@@ -274,7 +275,7 @@ PC/launcher.c must also be updated.
274275
275276
*/
276277

277-
#define PYC_MAGIC_NUMBER 3609
278+
#define PYC_MAGIC_NUMBER 3611
278279
/* This is equivalent to converting PYC_MAGIC_NUMBER to 2 bytes
279280
(little-endian) and then appending b'\r\n'. */
280281
#define PYC_MAGIC_NUMBER_TOKEN \

Include/internal/pycore_opcode_metadata.h

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

Include/internal/pycore_opcode_utils.h

+6
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ extern "C" {
4545
(opcode) == JUMP_BACKWARD || \
4646
(opcode) == JUMP_BACKWARD_NO_INTERRUPT)
4747

48+
#define IS_CONDITIONAL_JUMP_OPCODE(opcode) \
49+
((opcode) == POP_JUMP_IF_FALSE || \
50+
(opcode) == POP_JUMP_IF_TRUE || \
51+
(opcode) == POP_JUMP_IF_NONE || \
52+
(opcode) == POP_JUMP_IF_NOT_NONE)
53+
4854
#define IS_SCOPE_EXIT_OPCODE(opcode) \
4955
((opcode) == RETURN_VALUE || \
5056
(opcode) == RAISE_VARARGS || \

0 commit comments

Comments
 (0)