bpo-44889: Specialize LOAD_METHOD with PEP 659 adaptive interpreter (GH-27722)

Adds four new instructions:

* LOAD_METHOD_ADAPTIVE
* LOAD_METHOD_CACHED
* LOAD_METHOD_MODULE
* LOAD_METHOD_CLASS
This commit is contained in:
Ken Jin 2021-08-17 22:55:55 +08:00 committed by GitHub
parent fcd651d16f
commit 96346cb6d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 351 additions and 46 deletions

View file

@ -1439,6 +1439,27 @@ eval_frame_handle_pending(PyThreadState *tstate)
#define BUILTINS() frame->f_builtins
#define LOCALS() frame->f_locals
/* Shared opcode macros */
// shared by LOAD_ATTR_MODULE and LOAD_METHOD_MODULE
#define LOAD_MODULE_ATTR_OR_METHOD(attr_or_method) \
SpecializedCacheEntry *caches = GET_CACHE(); \
_PyAdaptiveEntry *cache0 = &caches[0].adaptive; \
_PyAttrCache *cache1 = &caches[-1].attr; \
DEOPT_IF(!PyModule_CheckExact(owner), LOAD_##attr_or_method); \
PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict; \
assert(dict != NULL); \
DEOPT_IF(dict->ma_keys->dk_version != cache1->dk_version_or_hint, \
LOAD_##attr_or_method); \
assert(dict->ma_keys->dk_kind == DICT_KEYS_UNICODE); \
assert(cache0->index < dict->ma_keys->dk_nentries); \
PyDictKeyEntry *ep = DK_ENTRIES(dict->ma_keys) + cache0->index; \
res = ep->me_value; \
DEOPT_IF(res == NULL, LOAD_##attr_or_method); \
STAT_INC(LOAD_##attr_or_method, hit); \
record_cache_hit(cache0); \
Py_INCREF(res);
static int
trace_function_entry(PyThreadState *tstate, InterpreterFrame *frame)
{
@ -3511,23 +3532,10 @@ check_eval_breaker:
TARGET(LOAD_ATTR_MODULE): {
assert(cframe.use_tracing == 0);
// shared with LOAD_METHOD_MODULE
PyObject *owner = TOP();
PyObject *res;
SpecializedCacheEntry *caches = GET_CACHE();
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
_PyAttrCache *cache1 = &caches[-1].attr;
DEOPT_IF(!PyModule_CheckExact(owner), LOAD_ATTR);
PyDictObject *dict = (PyDictObject *)((PyModuleObject *)owner)->md_dict;
assert(dict != NULL);
DEOPT_IF(dict->ma_keys->dk_version != cache1->dk_version_or_hint, LOAD_ATTR);
assert(dict->ma_keys->dk_kind == DICT_KEYS_UNICODE);
assert(cache0->index < dict->ma_keys->dk_nentries);
PyDictKeyEntry *ep = DK_ENTRIES(dict->ma_keys) + cache0->index;
res = ep->me_value;
DEOPT_IF(res == NULL, LOAD_ATTR);
STAT_INC(LOAD_ATTR, hit);
record_cache_hit(cache0);
Py_INCREF(res);
LOAD_MODULE_ATTR_OR_METHOD(ATTR);
SET_TOP(res);
Py_DECREF(owner);
DISPATCH();
@ -4282,6 +4290,8 @@ check_eval_breaker:
}
TARGET(LOAD_METHOD): {
PREDICTED(LOAD_METHOD);
STAT_INC(LOAD_METHOD, unquickened);
/* Designed to work in tandem with CALL_METHOD. */
PyObject *name = GETITEM(names, oparg);
PyObject *obj = TOP();
@ -4318,6 +4328,107 @@ check_eval_breaker:
DISPATCH();
}
TARGET(LOAD_METHOD_ADAPTIVE): {
assert(cframe.use_tracing == 0);
SpecializedCacheEntry *cache = GET_CACHE();
if (cache->adaptive.counter == 0) {
PyObject *owner = TOP();
PyObject *name = GETITEM(names, cache->adaptive.original_oparg);
next_instr--;
if (_Py_Specialize_LoadMethod(owner, next_instr, name, cache) < 0) {
goto error;
}
DISPATCH();
}
else {
STAT_INC(LOAD_METHOD, deferred);
cache->adaptive.counter--;
oparg = cache->adaptive.original_oparg;
STAT_DEC(LOAD_METHOD, unquickened);
JUMP_TO_INSTRUCTION(LOAD_METHOD);
}
}
TARGET(LOAD_METHOD_CACHED): {
/* LOAD_METHOD, with cached method object */
assert(cframe.use_tracing == 0);
PyObject *self = TOP();
PyTypeObject *self_cls = Py_TYPE(self);
SpecializedCacheEntry *caches = GET_CACHE();
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
_PyAttrCache *cache1 = &caches[-1].attr;
_PyObjectCache *cache2 = &caches[-2].obj;
DEOPT_IF(self_cls->tp_version_tag != cache1->tp_version, LOAD_METHOD);
assert(cache1->dk_version_or_hint != 0);
assert(cache1->tp_version != 0);
assert(self_cls->tp_dictoffset >= 0);
assert(Py_TYPE(self_cls)->tp_dictoffset > 0);
// inline version of _PyObject_GetDictPtr for offset >= 0
PyObject *dict = self_cls->tp_dictoffset != 0 ?
*(PyObject **) ((char *)self + self_cls->tp_dictoffset) : NULL;
// Ensure self.__dict__ didn't modify keys.
// Don't care if self has no dict, it could be builtin or __slots__.
DEOPT_IF(dict != NULL &&
((PyDictObject *)dict)->ma_keys->dk_version !=
cache1->dk_version_or_hint, LOAD_METHOD);
STAT_INC(LOAD_METHOD, hit);
record_cache_hit(cache0);
PyObject *res = cache2->obj;
assert(res != NULL);
assert(_PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR));
Py_INCREF(res);
SET_TOP(res);
PUSH(self);
DISPATCH();
}
TARGET(LOAD_METHOD_MODULE): {
assert(cframe.use_tracing == 0);
PyObject *owner = TOP();
PyObject *res;
LOAD_MODULE_ATTR_OR_METHOD(METHOD);
SET_TOP(NULL);
Py_DECREF(owner);
PUSH(res);
DISPATCH();
}
TARGET(LOAD_METHOD_CLASS): {
/* LOAD_METHOD, for class methods */
assert(cframe.use_tracing == 0);
SpecializedCacheEntry *caches = GET_CACHE();
_PyAdaptiveEntry *cache0 = &caches[0].adaptive;
_PyAttrCache *cache1 = &caches[-1].attr;
_PyObjectCache *cache2 = &caches[-2].obj;
PyObject *cls = TOP();
PyTypeObject *cls_type = Py_TYPE(cls);
assert(cls_type->tp_dictoffset > 0);
PyObject *dict = *(PyObject **) ((char *)cls + cls_type->tp_dictoffset);
// Don't care if no dict -- tp_version_tag should catch anything wrong.
DEOPT_IF(dict != NULL && ((PyDictObject *)dict)->ma_keys->dk_version !=
cache1->dk_version_or_hint, LOAD_METHOD);
DEOPT_IF(((PyTypeObject *)cls)->tp_version_tag != cache1->tp_version,
LOAD_METHOD);
assert(cache1->dk_version_or_hint != 0);
assert(cache1->tp_version != 0);
STAT_INC(LOAD_METHOD, hit);
record_cache_hit(cache0);
PyObject *res = cache2->obj;
assert(res != NULL);
assert(_PyType_HasFeature(Py_TYPE(res), Py_TPFLAGS_METHOD_DESCRIPTOR));
Py_INCREF(res);
SET_TOP(NULL);
Py_DECREF(cls);
PUSH(res);
DISPATCH();
}
TARGET(CALL_METHOD): {
/* Designed to work in tamdem with LOAD_METHOD. */
PyObject **sp, *res;
@ -4648,6 +4759,7 @@ opname ## _miss: \
MISS_WITH_CACHE(LOAD_ATTR)
MISS_WITH_CACHE(STORE_ATTR)
MISS_WITH_CACHE(LOAD_GLOBAL)
MISS_WITH_CACHE(LOAD_METHOD)
MISS_WITH_OPARG_COUNTER(BINARY_SUBSCR)
binary_subscr_dict_error: