Skip to content

Commit 4bfa01b

Browse files
authored
GH-104584: Plugin optimizer API (GH-105100)
1 parent 601ae09 commit 4bfa01b

31 files changed

+950
-501
lines changed

Include/Python.h

+1
Original file line numberDiff line numberDiff line change
@@ -105,5 +105,6 @@
105105
#include "fileutils.h"
106106
#include "cpython/pyfpe.h"
107107
#include "tracemalloc.h"
108+
#include "cpython/optimizer.h"
108109

109110
#endif /* !Py_PYTHON_H */

Include/cpython/code.h

+8
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ typedef struct {
7676
int8_t line_delta;
7777
} _PyCoLineInstrumentationData;
7878

79+
80+
typedef struct {
81+
int size;
82+
int capacity;
83+
struct _PyExecutorObject *executors[1];
84+
} _PyExecutorArray;
85+
7986
/* Main data structure used for instrumentation.
8087
* This is allocated when needed for instrumentation
8188
*/
@@ -153,6 +160,7 @@ typedef struct {
153160
PyObject *co_qualname; /* unicode (qualname, for reference) */ \
154161
PyObject *co_linetable; /* bytes object that holds location info */ \
155162
PyObject *co_weakreflist; /* to support weakrefs to code objects */ \
163+
_PyExecutorArray *co_executors; /* executors from optimizer */ \
156164
_PyCoCached *_co_cached; /* cached co_* attributes */ \
157165
uint64_t _co_instrumentation_version; /* current instrumentation version */ \
158166
_PyCoMonitoringData *_co_monitoring; /* Monitoring data */ \

Include/cpython/optimizer.h

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
2+
#ifndef Py_LIMITED_API
3+
#ifndef Py_OPTIMIZER_H
4+
#define Py_OPTIMIZER_H
5+
#ifdef __cplusplus
6+
extern "C" {
7+
#endif
8+
9+
typedef struct {
10+
uint8_t opcode;
11+
uint8_t oparg;
12+
} _PyVMData;
13+
14+
typedef struct _PyExecutorObject {
15+
PyObject_HEAD
16+
/* WARNING: execute consumes a reference to self. This is necessary to allow executors to tail call into each other. */
17+
struct _PyInterpreterFrame *(*execute)(struct _PyExecutorObject *self, struct _PyInterpreterFrame *frame, PyObject **stack_pointer);
18+
_PyVMData vm_data; /* Used by the VM, but opaque to the optimizer */
19+
/* Data needed by the executor goes here, but is opaque to the VM */
20+
} _PyExecutorObject;
21+
22+
typedef struct _PyOptimizerObject _PyOptimizerObject;
23+
24+
typedef _PyExecutorObject *(*optimize_func)(_PyOptimizerObject* self, PyCodeObject *code, _Py_CODEUNIT *instr);
25+
26+
typedef struct _PyOptimizerObject {
27+
PyObject_HEAD
28+
optimize_func optimize;
29+
uint16_t resume_threshold;
30+
uint16_t backedge_threshold;
31+
/* Data needed by the optimizer goes here, but is opaque to the VM */
32+
} _PyOptimizerObject;
33+
34+
PyAPI_FUNC(int) PyUnstable_Replace_Executor(PyCodeObject *code, _Py_CODEUNIT *instr, _PyExecutorObject *executor);
35+
36+
PyAPI_FUNC(void) PyUnstable_SetOptimizer(_PyOptimizerObject* optimizer);
37+
38+
PyAPI_FUNC(_PyOptimizerObject *) PyUnstable_GetOptimizer(void);
39+
40+
struct _PyInterpreterFrame *
41+
_PyOptimizer_BackEdge(struct _PyInterpreterFrame *frame, _Py_CODEUNIT *src, _Py_CODEUNIT *dest, PyObject **stack_pointer);
42+
43+
extern _PyOptimizerObject _PyOptimizer_Default;
44+
45+
/* For testing */
46+
PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewCounter(void);
47+
48+
#define OPTIMIZER_BITS_IN_COUNTER 4
49+
50+
#ifdef __cplusplus
51+
}
52+
#endif
53+
#endif /* !Py_OPTIMIZER_H */
54+
#endif /* Py_LIMITED_API */

Include/internal/pycore_code.h

+1
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ extern int _Py_Instrument(PyCodeObject *co, PyInterpreterState *interp);
487487

488488
extern int _Py_GetBaseOpcode(PyCodeObject *code, int offset);
489489

490+
extern int _PyInstruction_GetLength(PyCodeObject *code, int offset);
490491

491492
#ifdef __cplusplus
492493
}

Include/internal/pycore_interp.h

+3
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ struct _is {
160160
struct types_state types;
161161
struct callable_cache callable_cache;
162162
PyCodeObject *interpreter_trampoline;
163+
_PyOptimizerObject *optimizer;
164+
uint16_t optimizer_resume_threshold;
165+
uint16_t optimizer_backedge_threshold;
163166

164167
_Py_Monitors monitors;
165168
bool f_opcode_trace_set;

Include/internal/pycore_opcode.h

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

Include/opcode.h

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

Include/pystats.h

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ typedef struct _object_stats {
7070
uint64_t type_cache_dunder_hits;
7171
uint64_t type_cache_dunder_misses;
7272
uint64_t type_cache_collisions;
73+
uint64_t optimization_attempts;
7374
} ObjectStats;
7475

7576
typedef struct _stats {

Lib/importlib/_bootstrap_external.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -445,8 +445,9 @@ def _write_atomic(path, data, mode=0o666):
445445
# Python 3.12b1 3529 (Inline list/dict/set comprehensions)
446446
# Python 3.12b1 3530 (Shrink the LOAD_SUPER_ATTR caches)
447447
# Python 3.12b1 3531 (Add PEP 695 changes)
448+
# Python 3.13a1 3550 (Plugin optimizer support)
448449

449-
# Python 3.13 will start with 3550
450+
# Python 3.14 will start with 3600
450451

451452
# Please don't copy-paste the same pre-release tag for new entries above!!!
452453
# You should always use the *upcoming* tag. For example, if 3.12a6 came out
@@ -461,7 +462,7 @@ def _write_atomic(path, data, mode=0o666):
461462
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
462463
# in PC/launcher.c must also be updated.
463464

464-
MAGIC_NUMBER = (3531).to_bytes(2, 'little') + b'\r\n'
465+
MAGIC_NUMBER = (3550).to_bytes(2, 'little') + b'\r\n'
465466

466467
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
467468

Lib/opcode.py

+6
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ def pseudo_op(name, op, real_ops):
232232
def_op('LOAD_FROM_DICT_OR_DEREF', 176)
233233
hasfree.append(176)
234234

235+
# Optimizer hook
236+
def_op('ENTER_EXECUTOR', 230)
237+
235238
# Instrumented instructions
236239
MIN_INSTRUMENTED_OPCODE = 237
237240

@@ -486,6 +489,9 @@ def pseudo_op(name, op, real_ops):
486489
"SEND": {
487490
"counter": 1,
488491
},
492+
"JUMP_BACKWARD": {
493+
"counter": 1,
494+
},
489495
}
490496

491497
_inline_cache_entries = [

Lib/test/test_capi/test_misc.py

+13
Original file line numberDiff line numberDiff line change
@@ -1916,6 +1916,19 @@ def func():
19161916
names = ["func", "outer", "outer", "inner", "inner", "outer", "inner"]
19171917
self.do_test(func, names)
19181918

1919+
class TestOptimizerAPI(unittest.TestCase):
1920+
1921+
def test_counter_optimizer(self):
1922+
opt = _testinternalcapi.get_counter_optimizer()
1923+
self.assertEqual(opt.get_count(), 0)
1924+
try:
1925+
_testinternalcapi.set_optimizer(opt)
1926+
self.assertEqual(opt.get_count(), 0)
1927+
for _ in range(1000):
1928+
pass
1929+
self.assertEqual(opt.get_count(), 1000)
1930+
finally:
1931+
_testinternalcapi.set_optimizer(None)
19191932

19201933
if __name__ == "__main__":
19211934
unittest.main()

0 commit comments

Comments
 (0)