8000 bpo-44889: Specialize LOAD_METHOD with PEP 659 adaptive interpreter by Fidget-Spinner · Pull Request #27722 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-44889: Specialize LOAD_METHOD with PEP 659 adaptive interpreter #27722

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 43 commits into from
Aug 17, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
60ab351
WIP: implement LOAD_METHOD_HINT
Fidget-Spinner Aug 6, 2021
e0f7ebe
fix most tests: validate object __dict__ didn't modify keys
Fidget-Spinner Aug 6, 2021
ffd7ff3
fix all tests and allow no owner.__dict__ to be specialized!
Fidget-Spinner Aug 7, 2021
2b014a2
specialize builtins too
Fidget-Spinner Aug 7, 2021
b200887
turn off specialization stats
Fidget-Spinner Aug 7, 2021
d977986
improve specialization
Fidget-Spinner Aug 7, 2021
187fddb
fix a comment
Fidget-Spinner Aug 7, 2021
c71f142
better attribute detection - fix all tests
Fidget-Spinner Aug 7, 2021
f3082f4
always check attr inside instance __dict__
Fidget-Spinner Aug 7, 2021
0f32d60
backoff immediately for LOAD_METHOD, fail on dict subclasses, optimiz…
Fidget-Spinner Aug 8, 2021
8a31db3
Improve comments and specialization stats
Fidget-Spinner Aug 9, 2021
06d606e
Merge remote-tracking branch 'upstream/main' into specialize_load_method
Fidget-Spinner Aug 10, 2021
608f6d0
regen opcode
Fidget-Spinner Aug 10, 2021
1f5e64a
simplify code, use new spec_fail stats style
Fidget-Spinner Aug 10, 2021
29daf44
LOAD_METHOD_WITH_HINT now supports inherited methods 8000
Fidget-Spinner Aug 10, 2021
a3d6dd3
remove unneeded deopt
Fidget-Spinner Aug 10, 2021
d7ee4ac
remove experiments, rename to LOAD_METHOD_CACHED
Fidget-Spinner Aug 11, 2021
c944af0
move deopt earlier
Fidget-Spinner Aug 11, 2021
2f5cc63
Apply Mark's suggestions, fix up comments
Fidget-Spinner Aug 11, 2021
70ad1ea
Create 2021-08-11-20-45-02.bpo-44889.2T3nTn.rst
Fidget-Spinner Aug 11, 2021
f4487fc
remove unused variable
Fidget-Spinner Aug 11, 2021
afcf51e
round two: apply mark's suggestions
Fidget-Spinner Aug 11, 2021
e551d7a
Merge remote-tracking branch 'upstream/main' into specialize_load_method
Fidget-Spinner Aug 11, 2021
6f1f1f0
remove TARGET
Fidget-Spinner Aug 11, 2021
dc152ed
add to specialization stats dict
Fidget-Spinner Aug 11, 2021
dd77f75
improve borrowed ref safety explanation
Fidget-Spinner Aug 11, 2021
601e9b8
implement LOAD_METHOD_MODULE, and LOAD_METHOD_CLASS (for python classes)
Fidget-Spinner Aug 12, 2021
94b6106
fix macro
Fidget-Spinner Aug 12, 2021
1d87e53
Partially address Mark's review
Fidget-Spinner Aug 12, 2021
3697d4f
refactor, fully address review
Fidget-Spinner Aug 12, 2021
1d83667
fix check for classes
Fidget-Spinner Aug 12, 2021
5db1d3b
update comments, LOAD_METHOD_BUILTIN isn't worth it
Fidget-Spinner Aug 12, 2021
d12205b
apply mark's refactoring suggestions
Fidget-Spinner Aug 13, 2021
8af701d
more refactoring
Fidget-Spinner Aug 13, 2021
ad4ff6e
refactor and address review comments
Fidget-Spinner Aug 13, 2021
a3d1b50
add cases, use pytype_check only
Fidget-Spinner Aug 13, 2021
d8384bc
address review number 100
Fidget-Spinner Aug 13, 2021
ae2a520
remove usless comment
Fidget-Spinner Aug 13, 2021
a12406b
Fix buildbot errors: cache the type/class and compare that too
Fidget-Spinner Aug 13, 2021
33445d5
Revert "Fix buildbot errors: cache the type/class and compare that too"
Fidget-Spinner Aug 15, 2021
6d806a2
add a few more checks to safeguard specializations
Fidget-Spinner Aug 16, 2021
020a326
Merge remote-tracking branch 'upstream/main' into specialize_load_method
Fidget-Spinner Aug 16, 2021
d27e388
Fix for types with no dict
Fidget-Spinner Aug 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
implement LOAD_METHOD_MODULE, and LOAD_METHOD_CLASS (for python classes)
  • Loading branch information
Fidget-Spinner committed Aug 12, 2021
commit 601e9b800491cf9bbf5333cf08bdd03db44dd2a8
10 changes: 6 additions & 4 deletions Include/opcode.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Lib/opcode.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,8 @@ def jabs_op(name, op):
"LOAD_GLOBAL_BUILTIN",
"LOAD_METHOD_ADAPTIVE",
"LOAD_METHOD_CACHED",
"LOAD_METHOD_CLASS",
"LOAD_METHOD_MODULE",
"STORE_ATTR_ADAPTIVE",
"STORE_ATTR_SPLIT_KEYS",
"STORE_ATTR_SLOT",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
Initial implementation of adaptive specialization of ``LOAD_METHOD``. One
specialized form ``LOAD_METHOD_CACHED`` added.
Initial implementation of adaptive specialization of ``LOAD_METHOD``. The
following specialized forms were added:

* ``LOAD_METHOD_CACHED``

* ``LOAD_METHOD_MODULE``

* ``LOAD_METHOD_CLASS``
71 changes: 55 additions & 16 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -3442,22 +3442,26 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr

TARGET(LOAD_ATTR_MODULE): {
assert(cframe.use_tracing == 0);
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 = 8000 ep->me_value;
DEOPT_IF(res == NULL, LOAD_ATTR);
STAT_INC(LOAD_ATTR, hit);
// shared with LOAD_METHOD_MODULE
#define LOAD_MODULE_ATTR_OR_METHOD(attr_or_method) \
PyObject *owner = TOP(); \
PyObject *res; \
SpecializedCacheEntry *caches = GET_CACHE(); \
_PyAdaptiveEntry *cache0 = &caches[0].adaptive; \
_PyAttrCache *cache1 = &caches[-1].attr; \
DEOPT_IF(!PyModule_CheckExact(owner), ##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, ##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, ##attr_or_method); \
STAT_INC(##attr_or_method, hit); \
record_cache_hit(cache0);

LOAD_MODULE_ATTR_OR_METHOD(LOAD_ATTR);
Py_INCREF(res);
SET_TOP(res);
Py_DECREF(owner);
Expand Down Expand Up @@ -4273,7 +4277,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
}

TARGET(LOAD_METHOD_CACHED): {
/* Designed to work in tandem with CALL_METHOD. */
/* LOAD_METHOD, with cached method object */
assert(cframe.use_tracing == 0);
PyObject *self = TOP();
PyTypeObject *self_cls = Py_TYPE(self);
Expand Down Expand Up @@ -4309,6 +4313,41 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
DISPATCH();
}

TARGET(LOAD_METHOD_MODULE): {
assert(cframe.use_tracing == 0);
LOAD_MODULE_ATTR_OR_METHOD(LOAD_METHOD);
Py_INCREF(res);
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;
_PyObjectCache *cache3 = &caches[-3].obj;

DEOPT_IF(TOP() != cache3->obj, LOAD_METHOD);
PyTypeObject *cls = (PyTypeObject *)TOP();
DEOPT_IF(cls->tp_version_tag != cache1->tp_version, 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(NULL);
Py_DECREF(cls);
PUSH(res);
DISPATCH();
}

TARGET(CALL_METHOD): {
/* Designed to work in tamdem with LOAD_METHOD. */
PyObject **sp, *res;
Expand Down
10 changes: 5 additions & 5 deletions Python/opcode_targets.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 59 additions & 22 deletions Python/specialize.c
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ static uint8_t adaptive_opcodes[256] = {
static uint8_t cache_requirements[256] = {
[LOAD_ATTR] = 2, /* _PyAdaptiveEntry and _PyAttrCache */
[LOAD_GLOBAL] = 2, /* _PyAdaptiveEntry and _PyLoadGlobalCache */
[LOAD_METHOD] = 3, /* _PyAdaptiveEntry, _PyAttrCache and _PyObjectCache */
[LOAD_METHOD] = 4, /* _PyAdaptiveEntry, _PyAttrCache and 2 _PyObjectCache */
[BINARY_SUBSCR] = 0,
[STORE_ATTR] = 2, /* _PyAdaptiveEntry and _PyAttrCache */
};
Expand Down Expand Up @@ -403,10 +403,10 @@ _Py_Quicken(PyCodeObject *code) {

/* Methods */

#define SPEC_FAIL_IS_ATTR 15
#define SPEC_FAIL_DICT_SUBCLASS 16
#define SPEC_FAIL_MODULE_METHOD 17
#define SPEC_FAIL_CLASS_METHOD 18
#define SPEC_FAIL_IS_ATTR 14
#define SPEC_FAIL_DICT_SUBCLASS 15
#define SPEC_FAIL_BUILTIN_CLASS_METHOD 17
#define SPEC_FAIL_CLASS_METHOD_OBJ 18
#define SPEC_FAIL_NOT_METHOD 19

/* Binary subscr */
Expand All @@ -419,47 +419,48 @@ _Py_Quicken(PyCodeObject *code) {
static int
specialize_module_load_attr(
PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
_PyAdaptiveEntry *cache0, _PyAttrCache *cache1)
_PyAdaptiveEntry *cache0, _PyAttrCache *cache1, int opcode,
int opcode_module)
{
PyModuleObject *m = (PyModuleObject *)owner;
PyObject *value = NULL;
PyObject *getattr;
_Py_IDENTIFIER(__getattr__);
PyDictObject *dict = (PyDictObject *)m->md_dict;
if (dict == NULL) {
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_NO_DICT);
SPECIALIZATION_FAIL(opcode, SPEC_FAIL_NO_DICT);
return -1;
}
if (dict->ma_keys->dk_kind != DICT_KEYS_UNICODE) {
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_NON_STRING_OR_SPLIT);
SPECIALIZATION_FAIL(opcode, SPEC_FAIL_NON_STRING_OR_SPLIT);
return -1;
}
getattr = _PyUnicode_FromId(&PyId___getattr__); /* borrowed */
if (getattr == NULL) {
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OVERRIDDEN);
SPECIALIZATION_FAIL(opcode, SPEC_FAIL_OVERRIDDEN);
PyErr_Clear();
return -1;
}
Py_ssize_t index = _PyDict_GetItemHint(dict, getattr, -1, &value);
assert(index != DKIX_ERROR);
if (index != DKIX_EMPTY) {
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_MODULE_ATTR_NOT_FOUND);
SPECIALIZATION_FAIL(opcode, SPEC_FAIL_MODULE_ATTR_NOT_FOUND);
return -1;
}
index = _PyDict_GetItemHint(dict, name, -1, &value);
assert (index != DKIX_ERROR);
if (index != (uint16_t)index) {
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OUT_OF_RANGE);
SPECIALIZATION_FAIL(opcode, SPEC_FAIL_OUT_OF_RANGE);
return -1;
}
uint32_t keys_version = _PyDictKeys_GetVersionForCurrentState(dict);
if (keys_version == 0) {
SPECIALIZATION_FAIL(LOAD_ATTR, SPEC_FAIL_OUT_OF_VERSIONS);
SPECIALIZATION_FAIL(opcode, SPEC_FAIL_OUT_OF_VERSIONS);
return -1;
}
cache1->dk_version_or_hint = keys_version;
cache0->index = (uint16_t)index;
*instr = _Py_MAKECODEUNIT(LOAD_ATTR_MODULE, _Py_OPARG(*instr));
*instr = _Py_MAKECODEUNIT(opcode_module, _Py_OPARG(*instr));
return 0;
}

Expand Down Expand Up @@ -620,7 +621,8 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, Sp
_PyAdaptiveEntry *cache0 = &cache->adaptive;
_PyAttrCache *cache1 = &cache[-1].attr;
if (PyModule_CheckExact(owner)) {
int err = specialize_module_load_attr(owner, instr, name, cache0, cache1);
int err = specialize_module_load_attr(owner, instr, name, cache0, cache1,
LOAD_ATTR, LOAD_ATTR_MODULE);
if (err) {
goto fail;
}
Expand Down Expand Up @@ -799,9 +801,12 @@ _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,

PyTypeObject *owner_cls = Py_TYPE(owner);
if (PyModule_CheckExact(owner)) {
// Mabe TODO: LOAD_METHOD_MODULE
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_MODULE_METHOD);
goto fail;
int err = specialize_module_load_attr(owner, instr, name, cache0, cache1,
LOAD_METHOD, LOAD_METHOD_MODULE);
if (err) {
goto fail;
}
goto success;
}
if (owner_cls->tp_dict == NULL) {
if (PyType_Ready(owner_cls) < 0) {
Expand All @@ -814,15 +819,47 @@ _Py_Specialize_LoadMethod(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name,
cache1->tp_version = owner_cls->tp_version_tag;

if (kind != METHOD || descr == NULL) {
#if SPECIALIZATION_STATS
if (kind == GETSET_OVERRIDDEN &&
PyObject_TypeCheck(owner, &PyBaseObject_Type)) {
// Maybe TODO: LOAD_METHOD_CLASS
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_CLASS_METHOD);
goto fail;
PyTypeObject *cls = (PyTypeObject *)owner;
kind = analyze_descriptor(cls, name, &descr, 0);
// Store the version right away, in case it's modified halfway through.
cache1->tp_version = cls->tp_version_tag;
if (descr == NULL) {
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_NOT_METHOD);
goto fail;
}
// Borrowed, only used for identity checks. Do not access the object.
_PyObjectCache *cache3 = &cache[-3].obj;

// Python class method
if (kind == METHOD) {
// borrowed, see below for why this is safe
cache2->obj = descr;
cache3->obj = owner;
*instr = _Py_MAKECODEUNIT(LOAD_METHOD_CLASS, _Py_OPARG(*instr));
goto success;
}
#if SPECIALIZATION_STATS
// Builtin class method -- ie wrapped PyCFunction
else if (kind == NON_OVERRIDING && Py_IS_TYPE(descr, &PyClassMethodDescr_Type)) {
// Unlike python classes, builtins don't hold a reference to the
// classmethod object (but instead the descriptor). Instead, a new
// object is created every time. So we can't use the borrowed
// object cache.
// TODO: make LOAD_METHOD_CLASS work with builtin classmethods
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_BUILTIN_CLASS_METHOD);
goto fail;
}
else if (Py_IS_TYPE(descr, &PyClassMethod_Type)) {
// Note: this is the actual @classmethod decorator object, not
// the X.y form.
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_CLASS_METHOD_OBJ);
goto fail;
}
#endif
}
SPECIALIZATION_FAIL(LOAD_METHOD, SPEC_FAIL_NOT_METHOD);
#endif
goto fail;
}
PyObject **cls_dictptr = _PyObject_GetDictPtr((PyObject *)owner_cls);
Expand Down
0