Skip to content

Commit fc4dc44

Browse files
mpageefimov-mikhail
authored andcommitted
pythongh-115999: Stop the world when invalidating function versions (python#124997)
Stop the world when invalidating function versions The tier1 interpreter specializes `CALL` instructions based on the values of certain function attributes (e.g. `__code__`, `__defaults__`). The tier1 interpreter uses function versions to verify that the attributes of a function during execution of a specialization match those seen during specialization. A function's version is initialized in `MAKE_FUNCTION` and is invalidated when any of the critical function attributes are changed. The tier1 interpreter stores the function version in the inline cache during specialization. A guard is used by the specialized instruction to verify that the version of the function on the operand stack matches the cached version (and therefore has all of the expected attributes). It is assumed that once the guard passes, all attributes will remain unchanged while executing the rest of the specialized instruction. Stopping the world when invalidating function versions ensures that all critical function attributes will remain unchanged after the function version guard passes in free-threaded builds. It's important to note that this is only true if the remainder of the specialized instruction does not enter and exit a stop-the-world point. We will stop the world the first time any of the following function attributes are mutated: - defaults - vectorcall - kwdefaults - closure - code This should happen rarely and only happens once per function, so the performance impact on majority of code should be minimal. Additionally, refactor the API for manipulating function versions to more clearly match the stated semantics.
1 parent b926c8e commit fc4dc44

File tree

4 files changed

+72
-38
lines changed

4 files changed

+72
-38
lines changed

Include/internal/pycore_function.h

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ extern PyObject* _PyFunction_Vectorcall(
1818

1919
#define FUNC_MAX_WATCHERS 8
2020

21+
#define FUNC_VERSION_UNSET 0
22+
#define FUNC_VERSION_CLEARED 1
23+
#define FUNC_VERSION_FIRST_VALID 2
24+
2125
#define FUNC_VERSION_CACHE_SIZE (1<<12) /* Must be a power of 2 */
2226

2327
struct _func_version_cache_item {
@@ -41,6 +45,12 @@ struct _py_func_state {
4145

4246
extern PyFunctionObject* _PyFunction_FromConstructor(PyFrameConstructor *constr);
4347

48+
static inline int
49+
_PyFunction_IsVersionValid(uint32_t version)
50+
{
51+
return version >= FUNC_VERSION_FIRST_VALID;
52+
}
53+
4454
extern uint32_t _PyFunction_GetVersionForCurrentState(PyFunctionObject *func);
4555
PyAPI_FUNC(void) _PyFunction_SetVersion(PyFunctionObject *func, uint32_t version);
4656
void _PyFunction_ClearCodeByVersion(uint32_t version);

Include/internal/pycore_runtime_init.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ extern "C" {
1111
#include "pycore_ceval_state.h" // _PyEval_RUNTIME_PERF_INIT
1212
#include "pycore_faulthandler.h" // _faulthandler_runtime_state_INIT
1313
#include "pycore_floatobject.h" // _py_float_format_unknown
14+
#include "pycore_function.h"
1415
#include "pycore_object.h" // _PyObject_HEAD_INIT
1516
#include "pycore_obmalloc_init.h" // _obmalloc_global_state_INIT
1617
#include "pycore_parser.h" // _parser_runtime_state_INIT
@@ -243,7 +244,7 @@ extern PyTypeObject _PyExc_MemoryError;
243244
.dict_state = _dict_state_INIT, \
244245
.mem_free_queue = _Py_mem_free_queue_INIT(INTERP.mem_free_queue), \
245246
.func_state = { \
246-
.next_version = 1, \
247+
.next_version = FUNC_VERSION_FIRST_VALID, \
247248
}, \
248249
.types = { \
249250
.next_version_tag = _Py_TYPE_BASE_VERSION_TAG, \

Objects/funcobject.c

+56-33
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ _PyFunction_FromConstructor(PyFrameConstructor *constr)
128128
op->func_annotate = NULL;
129129
op->func_typeparams = NULL;
130130
op->vectorcall = _PyFunction_Vectorcall;
131-
op->func_version = 0;
131+
op->func_version = FUNC_VERSION_UNSET;
132132
// NOTE: functions created via FrameConstructor do not use deferred
133133
// reference counting because they are typically not part of cycles
134134
// nor accessed by multiple threads.
@@ -207,7 +207,7 @@ PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname
207207
op->func_annotate = NULL;
208208
op->func_typeparams = NULL;
209209
op->vectorcall = _PyFunction_Vectorcall;
210-
op->func_version = 0;
210+
op->func_version = FUNC_VERSION_UNSET;
211211
if ((code_obj->co_flags & CO_NESTED) == 0) {
212212
// Use deferred reference counting for top-level functions, but not
213213
// nested functions because they are more likely to capture variables,
@@ -287,41 +287,67 @@ functions is running.
287287
288288
*/
289289

290+
static inline struct _func_version_cache_item *
291+
get_cache_item(PyInterpreterState *interp, uint32_t version)
292+
{
293+
return interp->func_state.func_version_cache +
294+
(version % FUNC_VERSION_CACHE_SIZE);
295+
}
296+
290297
void
291298
_PyFunction_SetVersion(PyFunctionObject *func, uint32_t version)
292299
{
300+
assert(func->func_version == FUNC_VERSION_UNSET);
301+
assert(version >= FUNC_VERSION_FIRST_VALID);
302+
// This should only be called from MAKE_FUNCTION. No code is specialized
303+
// based on the version, so we do not need to stop the world to set it.
304+
func->func_version = version;
293305
#ifndef Py_GIL_DISABLED
294306
PyInterpreterState *interp = _PyInterpreterState_GET();
295-
if (func->func_version != 0) {
296-
struct _func_version_cache_item *slot =
297-
interp->func_state.func_version_cache
298-
+ (func->func_version % FUNC_VERSION_CACHE_SIZE);
299-
if (slot->func == func) {
300-
slot->func = NULL;
301-
// Leave slot->code alone, there may be use for it.
302-
}
303-
}
307+
struct _func_version_cache_item *slot = get_cache_item(interp, version);
308+
slot->func = func;
309+
slot->code = func->func_code;
304310
#endif
305-
func->func_version = version;
311+
}
312+
313+
static void
314+
func_clear_version(PyInterpreterState *interp, PyFunctionObject *func)
315+
{
316+
if (func->func_version < FUNC_VERSION_FIRST_VALID) {
317+
// Version was never set or has already been cleared.
318+
return;
319+
}
306320
#ifndef Py_GIL_DISABLED
307-
if (version != 0) {
308-
struct _func_version_cache_item *slot =
309-
interp->func_state.func_version_cache
310-
+ (version % FUNC_VERSION_CACHE_SIZE);
311-
slot->func = func;
312-
slot->code = func->func_code;
321+
struct _func_version_cache_item *slot =
322+
get_cache_item(interp, func->func_version);
323+
if (slot->func == func) {
324+
slot->func = NULL;
325+
// Leave slot->code alone, there may be use for it.
313326
}
314327
#endif
328+
func->func_version = FUNC_VERSION_CLEARED;
329+
}
330+
331+
// Called when any of the critical function attributes are changed
332+
static void
333+
_PyFunction_ClearVersion(PyFunctionObject *func)
334+
{
335+
if (func->func_version < FUNC_VERSION_FIRST_VALID) {
336+
// Version was never set or has already been cleared.
337+
return;
338+
}
339+
PyInterpreterState *interp = _PyInterpreterState_GET();
340+
_PyEval_StopTheWorld(interp);
341+
func_clear_version(interp, func);
342+
_PyEval_StartTheWorld(interp);
315343
}
316344

317345
void
318346
_PyFunction_ClearCodeByVersion(uint32_t version)
319347
{
320348
#ifndef Py_GIL_DISABLED
321349
PyInterpreterState *interp = _PyInterpreterState_GET();
322-
struct _func_version_cache_item *slot =
323-
interp->func_state.func_version_cache
324-
+ (version % FUNC_VERSION_CACHE_SIZE);
350+
struct _func_version_cache_item *slot = get_cache_item(interp, version);
325351
if (slot->code) {
326352
assert(PyCode_Check(slot->code));
327353
PyCodeObject *code = (PyCodeObject *)slot->code;
@@ -340,9 +366,7 @@ _PyFunction_LookupByVersion(uint32_t version, PyObject **p_code)
340366
return NULL;
341367
#else
342368
PyInterpreterState *interp = _PyInterpreterState_GET();
343-
struct _func_version_cache_item *slot =
344-
interp->func_state.func_version_cache
345-
+ (version % FUNC_VERSION_CACHE_SIZE);
369+
struct _func_version_cache_item *slot = get_cache_item(interp, version);
346370
if (slot->code) {
347371
assert(PyCode_Check(slot->code));
348372
PyCodeObject *code = (PyCodeObject *)slot->code;
@@ -431,7 +455,7 @@ PyFunction_SetDefaults(PyObject *op, PyObject *defaults)
431455
}
432456
handle_func_event(PyFunction_EVENT_MODIFY_DEFAULTS,
433457
(PyFunctionObject *) op, defaults);
434-
_PyFunction_SetVersion((PyFunctionObject *)op, 0);
458+
_PyFunction_ClearVersion((PyFunctionObject *)op);
435459
Py_XSETREF(((PyFunctionObject *)op)->func_defaults, defaults);
436460
return 0;
437461
}
@@ -440,7 +464,7 @@ void
440464
PyFunction_SetVectorcall(PyFunctionObject *func, vectorcallfunc vectorcall)
441465
{
442466
assert(func != NULL);
443-
_PyFunction_SetVersion(func, 0);
467+
_PyFunction_ClearVersion(func);
444468
func->vectorcall = vectorcall;
445469
}
446470

@@ -473,7 +497,7 @@ PyFunction_SetKwDefaults(PyObject *op, PyObject *defaults)
473497
}
474498
handle_func_event(PyFunction_EVENT_MODIFY_KWDEFAULTS,
475499
(PyFunctionObject *) op, defaults);
476-
_PyFunction_SetVersion((PyFunctionObject *)op, 0);
500+
_PyFunction_ClearVersion((PyFunctionObject *)op);
477501
Py_XSETREF(((PyFunctionObject *)op)->func_kwdefaults, defaults);
478502
return 0;
479503
}
@@ -506,7 +530,7 @@ PyFunction_SetClosure(PyObject *op, PyObject *closure)
506530
Py_TYPE(closure)->tp_name);
507531
return -1;
508532
}
509-
_PyFunction_SetVersion((PyFunctionObject *)op, 0);
533+
_PyFunction_ClearVersion((PyFunctionObject *)op);
510534
Py_XSETREF(((PyFunctionObject *)op)->func_closure, closure);
511535
return 0;
512536
}
@@ -658,7 +682,7 @@ func_set_code(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
658682
}
659683

660684
handle_func_event(PyFunction_EVENT_MODIFY_CODE, op, value);
661-
_PyFunction_SetVersion(op, 0);
685+
_PyFunction_ClearVersion(op);
662686
Py_XSETREF(op->func_code, Py_NewRef(value));
663687
return 0;
664688
}
@@ -744,7 +768,7 @@ func_set_defaults(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
744768
}
745769

746770
handle_func_event(PyFunction_EVENT_MODIFY_DEFAULTS, op, value);
747-
_PyFunction_SetVersion(op, 0);
771+
_PyFunction_ClearVersion(op);
748772
Py_XSETREF(op->func_defaults, Py_XNewRef(value));
749773
return 0;
750774
}
@@ -787,7 +811,7 @@ func_set_kwdefaults(PyObject *self, PyObject *value, void *Py_UNUSED(ignored))
787811
}
788812

789813
handle_func_event(PyFunction_EVENT_MODIFY_KWDEFAULTS, op, value);
790-
_PyFunction_SetVersion(op, 0);
814+
_PyFunction_ClearVersion(op);
791815
Py_XSETREF(op->func_kwdefaults, Py_XNewRef(value));
792816
return 0;
793817
}
@@ -1030,7 +1054,7 @@ static int
10301054
func_clear(PyObject *self)
10311055
{
10321056
PyFunctionObject *op = _PyFunction_CAST(self);
1033-
_PyFunction_SetVersion(op, 0);
1057+
func_clear_version(_PyInterpreterState_GET(), op);
10341058
Py_CLEAR(op->func_globals);
10351059
Py_CLEAR(op->func_builtins);
10361060
Py_CLEAR(op->func_module);
@@ -1068,7 +1092,6 @@ func_dealloc(PyObject *self)
10681092
if (op->func_weakreflist != NULL) {
10691093
PyObject_ClearWeakRefs((PyObject *) op);
10701094
}
1071-
_PyFunction_SetVersion(op, 0);
10721095
(void)func_clear((PyObject*)op);
10731096
// These aren't cleared by func_clear().
10741097
Py_DECREF(op->func_code);

Python/specialize.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -1599,7 +1599,7 @@ function_get_version(PyObject *o, int opcode)
15991599
assert(Py_IS_TYPE(o, &PyFunction_Type));
16001600
PyFunctionObject *func = (PyFunctionObject *)o;
16011601
uint32_t version = _PyFunction_GetVersionForCurrentState(func);
1602-
if (version == 0) {
1602+
if (!_PyFunction_IsVersionValid(version)) {
16031603
SPECIALIZATION_FAIL(opcode, SPEC_FAIL_OUT_OF_VERSIONS);
16041604
return 0;
16051605
}
@@ -1692,7 +1692,7 @@ _Py_Specialize_BinarySubscr(
16921692
goto fail;
16931693
}
16941694
uint32_t version = _PyFunction_GetVersionForCurrentState(func);
1695-
if (version == 0) {
1695+
if (!_PyFunction_IsVersionValid(version)) {
16961696
SPECIALIZATION_FAIL(BINARY_SUBSCR, SPEC_FAIL_OUT_OF_VERSIONS);
16971697
goto fail;
16981698
}
@@ -1977,7 +1977,7 @@ specialize_py_call(PyFunctionObject *func, _Py_CODEUNIT *instr, int nargs,
19771977
argcount = code->co_argcount;
19781978
}
19791979
int version = _PyFunction_GetVersionForCurrentState(func);
1980-
if (version == 0) {
1980+
if (!_PyFunction_IsVersionValid(version)) {
19811981
SPECIALIZATION_FAIL(CALL, SPEC_FAIL_OUT_OF_VERSIONS);
19821982
return -1;
19831983
}
@@ -2009,7 +2009,7 @@ specialize_py_call_kw(PyFunctionObject *func, _Py_CODEUNIT *instr, int nargs,
20092009
return -1;
20102010
}
20112011
int version = _PyFunction_GetVersionForCurrentState(func);
2012-
if (version == 0) {
2012+
if (!_PyFunction_IsVersionValid(version)) {
20132013
SPECIALIZATION_FAIL(CALL, SPEC_FAIL_OUT_OF_VERSIONS);
20142014
return -1;
20152015
}

0 commit comments

Comments
 (0)