8000 GH-113710: Add a "globals to constants" pass (GH-114592) · python/cpython@0e71a29 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0e71a29

Browse files
authored
GH-113710: Add a "globals to constants" pass (GH-114592)
Converts specializations of `LOAD_GLOBAL` into constants during tier 2 optimization.
1 parent 2091fb2 commit 0e71a29

16 files changed

+375
-55
lines changed

Include/cpython/dictobject.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ typedef struct {
1717
/* Dictionary version: globally unique, value change each time
1818
the dictionary is modified */
1919
#ifdef Py_BUILD_CORE
20+
/* Bits 0-7 are for dict watchers.
21+
* Bits 8-11 are for the watched mutation counter (used by tier2 optimization)
22+
* The remaining bits (12-63) are the actual version tag. */
2023
uint64_t ma_version_tag;
2124
#else
2225
Py_DEPRECATED(3.12) uint64_t ma_version_tag;

Include/cpython/optimizer.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ typedef struct _PyExecutorObject {
4747
typedef struct _PyOptimizerObject _PyOptimizerObject;
4848

4949
/* Should return > 0 if a new executor is created. O if no executor is produced and < 0 if an error occurred. */
50-
typedef int (*optimize_func)(_PyOptimizerObject* self, PyCodeObject *code, _Py_CODEUNIT *instr, _PyExecutorObject **, int curr_stackentries);
50+
typedef int (*optimize_func)(
51+
_PyOptimizerObject* self, struct _PyInterpreterFrame *frame,
52+
_Py_CODEUNIT *instr, _PyExecutorObject **exec_ptr,
53+
int curr_stackentries);
5154

5255
typedef struct _PyOptimizerObject {
5356
PyObject_HEAD
@@ -94,6 +97,9 @@ PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewUOpOptimizer(void);
9497
/* Minimum of 16 additional executions before retry */
9598
#define MINIMUM_TIER2_BACKOFF 4
9699

100+
#define _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS 3
101+
#define _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS 6
102+
97103
#ifdef __cplusplus
98104
}
99105
#endif

Include/internal/pycore_dict.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,8 @@ static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) {
207207

208208
#define DK_IS_UNICODE(dk) ((dk)->dk_kind != DICT_KEYS_GENERAL)
209209

210-
#define DICT_VERSION_INCREMENT (1 << DICT_MAX_WATCHERS)
211-
#define DICT_VERSION_MASK (DICT_VERSION_INCREMENT - 1)
210+
#define DICT_VERSION_INCREMENT (1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS))
211+
#define DICT_WATCHER_MASK ((1 << DICT_MAX_WATCHERS) - 1)
212212

213213
#ifdef Py_GIL_DISABLED
214214
#define DICT_NEXT_VERSION(INTERP) \
@@ -234,7 +234,7 @@ _PyDict_NotifyEvent(PyInterpreterState *interp,
234234
PyObject *value)
235235
{
236236
assert(Py_REFCNT((PyObject*)mp) > 0);
237-
int watcher_bits = mp->ma_version_tag & DICT_VERSION_MASK;
237+
int watcher_bits = mp->ma_version_tag & DICT_WATCHER_MASK;
238238
if (watcher_bits) {
239239
_PyDict_SendEvent(watcher_bits, event, mp, key, value);
240240
return DICT_NEXT_VERSION(interp) | watcher_bits;

Include/internal/pycore_dict_state.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ extern "C" {
99
#endif
1010

1111
#define DICT_MAX_WATCHERS 8
12+
#define DICT_WATCHED_MUTATION_BITS 4
1213

1314
struct _Py_dict_state {
1415
/*Global counter used to set ma_version_tag field of dictionary.

Include/internal/pycore_interp.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ typedef struct _rare_events {
7272
uint8_t set_eval_frame_func;
7373
/* Modifying the builtins, __builtins__.__dict__[var] = ... */
7474
uint8_t builtin_dict;
75-
int builtins_dict_watcher_id;
7675
/* Modifying a function, e.g. func.__defaults__ = ..., etc. */
7776
uint8_t func_modification;
7877
} _rare_events;
@@ -243,6 +242,7 @@ struct _is {
243242
uint16_t optimizer_backedge_threshold;
244243
uint32_t next_func_version;
245244
_rare_events rare_events;
245+
PyDict_WatchCallback builtins_dict_watcher;
246246

247247
_Py_GlobalMonitors monitors;
248248
bool sys_profile_initialized;

Include/internal/pycore_optimizer.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11-
int _Py_uop_analyze_and_optimize(PyCodeObject *code,
12-
_PyUOpInstruction *trace, int trace_len, int curr_stackentries);
11+
int _Py_uop_analyze_and_optimize(_PyInterpreterFrame *frame,
12+
_PyUOpInstruction *trace, int trace_len, int curr_stackentries,
13+
_PyBloomFilter *dependencies);
1314

1415
extern PyTypeObject _PyCounterExecutor_Type;
1516
extern PyTypeObject _PyCounterOptimizer_Type;

Include/internal/pycore_uop_ids.h

Lines changed: 6 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,10 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
204204
[_CHECK_VALIDITY] = HAS_DEOPT_FLAG,
205205
[_LOAD_CONST_INLINE] = 0,
206206
[_LOAD_CONST_INLINE_BORROW] = 0,
207+
[_LOAD_CONST_INLINE_WITH_NULL] = 0,
208+
[_LOAD_CONST_INLINE_BORROW_WITH_NULL] = 0,
209+
[_CHECK_GLOBALS] = HAS_DEOPT_FLAG,
210+
[_CHECK_BUILTINS] = HAS_DEOPT_FLAG,
207211
[_INTERNAL_INCREMENT_OPT_COUNTER] = 0,
208212
};
209213

@@ -250,10 +254,12 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {
250254
[_CHECK_ATTR_METHOD_LAZY_DICT] = "_CHECK_ATTR_METHOD_LAZY_DICT",
251255
[_CHECK_ATTR_MODULE] = "_CHECK_ATTR_MODULE",
252256
[_CHECK_ATTR_WITH_HINT] = "_CHECK_ATTR_WITH_HINT",
257+
[_CHECK_BUILTINS] = "_CHECK_BUILTINS",
253258
[_CHECK_CALL_BOUND_METHOD_EXACT_ARGS] = "_CHECK_CALL_BOUND_METHOD_EXACT_ARGS",
254259
[_CHECK_EG_MATCH] = "_CHECK_EG_MATCH",
255260
[_CHECK_EXC_MATCH] = "_CHECK_EXC_MATCH",
256261
[_CHECK_FUNCTION_EXACT_ARGS] = "_CHECK_FUNCTION_EXACT_ARGS",
262+
[_CHECK_GLOBALS] = "_CHECK_GLOBALS",
257263
[_CHECK_MANAGED_OBJECT_HAS_VALUES] = "_CHECK_MANAGED_OBJECT_HAS_VALUES",
258264
[_CHECK_PEP_523] = "_CHECK_PEP_523",
259265
[_CHECK_STACK_SPACE] = "_CHECK_STACK_SPACE",
@@ -332,6 +338,8 @@ const char *const _PyOpcode_uop_name[MAX_UOP_ID+1] = {
332338
[_LOAD_CONST] = "_LOAD_CONST",
333339
[_LOAD_CONST_INLINE] = "_LOAD_CONST_INLINE",
334340
[_LOAD_CONST_INLINE_BORROW] = "_LOAD_CONST_INLINE_BORROW",
341+
[_LOAD_CONST_INLINE_BORROW_WITH_NULL] = "_LOAD_CONST_INLINE_BORROW_WITH_NULL",
342+
[_LOAD_CONST_INLINE_WITH_NULL] = "_LOAD_CONST_INLINE_WITH_NULL",
335343
[_LOAD_DEREF] = "_LOAD_DEREF",
336344
[_LOAD_FAST] = "_LOAD_FAST",
337345
[_LOAD_FAST_AND_CLEAR] = "_LOAD_FAST_AND_CLEAR",

Lib/test/test_capi/test_watchers.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ def test_watch_out_of_range_watcher_id(self):
151151

152152
def test_watch_unassigned_watcher_id(self):
153153
d = {}
154-
with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"):
155-
self.watch(1, d)
154+
with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 3"):
155+
self.watch(3, d)
156156

157157
def test_unwatch_non_dict(self):
158158
with self.watcher() as wid:
@@ -168,8 +168,8 @@ def test_unwatch_out_of_range_watcher_id(self):
168168

169169
def test_unwatch_unassigned_watcher_id(self):
170170
d = {}
171-
with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"):
172-
self.unwatch(1, d)
171+
with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 3"):
172+
self.unwatch(3, d)
173173

174174
def test_clear_out_of_range_watcher_id(self):
175175
with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID -1"):
@@ -178,8 +178,8 @@ def test_clear_out_of_range_watcher_id(self):
178178
self.clear_watcher(8) # DICT_MAX_WATCHERS = 8
179179

180180
def test_clear_unassigned_watcher_id(self):
181-
with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"):
182-
self.clear_watcher(1)
181+
with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 3"):
182+
self.clear_watcher(3)
183183

184184

185185
class TestTypeWatchers(unittest.TestCase):

Modules/_testcapi/watchers.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ module _testcapi
1515
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6361033e795369fc]*/
1616

1717
// Test dict watching
18-
static PyObject *g_dict_watch_events;
19-
static int g_dict_watchers_installed;
18+
static PyObject *g_dict_watch_events = NULL;
19+
static int g_dict_watchers_installed = 0;
2020

2121
static int
2222
dict_watch_callback(PyDict_WatchEvent event,

Objects/dictobject.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5943,7 +5943,8 @@ PyDict_AddWatcher(PyDict_WatchCallback callback)
59435943
{
59445944
PyInterpreterState *interp = _PyInterpreterState_GET();
59455945

5946-
for (int i = 0; i < DICT_MAX_WATCHERS; i++) {
5946+
/* Start at 2, as 0 and 1 are reserved for CPython */
5947+
for (int i = 2; i < DICT_MAX_WATCHERS; i++) {
59475948
if (!interp->dict_state.watchers[i]) {
59485949
interp->dict_state.watchers[i] = callback;
59495950
return i;

Python/bytecodes.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4071,11 +4071,35 @@ dummy_func(
40714071
}
40724072

40734073
op(_LOAD_CONST_INLINE, (ptr/4 -- value)) {
4074+
TIER_TWO_ONLY
40744075
value = Py_NewRef(ptr);
40754076
}
40764077

40774078
op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) {
4079+
TIER_TWO_ONLY
4080+
value = ptr;
4081+
}
4082+
4083+
op(_LOAD_CONST_INLINE_WITH_NULL, (ptr/4 -- value, null)) {
4084+
TIER_TWO_ONLY
4085+
value = Py_NewRef(ptr);
4086+
null = NULL;
4087+
}
4088+
4089+
op(_LOAD_CONST_INLINE_BORROW_WITH_NULL, (ptr/4 -- value, null)) {
4090+
TIER_TWO_ONLY
40784091
value = ptr;
4092+
null = NULL;
4093+
}
4094+
4095+
op(_CHECK_GLOBALS, (dict/4 -- )) {
4096+
TIER_TWO_ONLY
4097+
DEOPT_IF(GLOBALS() != dict);
4098+
}
4099+
4100+
op(_CHECK_BUILTINS, (dict/4 -- )) {
4101+
TIER_TWO_ONLY
4102+
DEOPT_IF(BUILTINS() != dict);
40794103
}
40804104

40814105
/* Internal -- for testing executors */

Python/executor_cases.c.h

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
0