Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34k
bpo-46939: Specialize calls to Python classes#31707
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
43112e009a7180d74d2940583f6bbb78e9cb570e92efad70f4d6a06b5b526afde3a40630a0659db09eef0d7f59e28f9dbbd11362fc04f5daFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Calls to Python classes are now specialized. Creating objects from Python | ||
| classes should now be faster. Patch by Ken Jin. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1463,6 +1463,13 @@ eval_frame_handle_pending(PyThreadState *tstate) | ||
| STAT_INC(LOAD_##attr_or_method, hit); \ | ||
| Py_INCREF(res); | ||
| #define CALL_PY_FRAME_PASS_SELF() \ | ||
| if (call_shape.init_pass_self){\ | ||
| assert(frame->self == NULL); \ | ||
| frame->self = Py_NewRef(frame->localsplus[0]); \ | ||
| call_shape.init_pass_self = false; \ | ||
| } | ||
| #define TRACE_FUNCTION_EXIT() \ | ||
| if (cframe.use_tracing){\ | ||
| if (trace_function_exit(tstate, frame, retval)){\ | ||
| @@ -1588,6 +1595,11 @@ pop_frame(PyThreadState *tstate, _PyInterpreterFrame *frame) | ||
| */ | ||
| typedef struct{ | ||
| PyObject *kwnames; | ||
| /* __init__ is special because while it returns None, we need to return self | ||
| This tells CALL to pass the current self to the new frame (the __init__ frame). | ||
| Where it is eventually consumed by RETURN_VALUE. | ||
| */ | ||
| bool init_pass_self; | ||
| } CallShape; | ||
| static inline bool | ||
| @@ -1619,6 +1631,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | ||
| _PyCFrame cframe; | ||
| CallShape call_shape; | ||
| call_shape.kwnames = NULL; // Borrowed reference. Reset by CALL instructions. | ||
| call_shape.init_pass_self = 0; | ||
| /* WARNING: Because the _PyCFrame lives on the C stack, | ||
| * but can be accessed from a heap allocated object (tstate) | ||
| @@ -2391,6 +2404,18 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | ||
| TARGET(RETURN_VALUE){ | ||
| PyObject *retval = POP(); | ||
| if (frame->self != NULL){ | ||
| if (Py_IsNone(retval)){ | ||
| Py_SETREF(retval, frame->self); | ||
| frame->self = NULL; | ||
| } | ||
| /* We need this to continue raising errors when bad-practice | ||
| __init__s return their non-None values. This is later | ||
| caught by the interpreter. */ | ||
| else{ | ||
| Py_CLEAR(frame->self); | ||
| } | ||
| } | ||
| assert(EMPTY()); | ||
| frame->f_state = FRAME_RETURNED; | ||
| _PyFrame_SetStackPointer(frame, stack_pointer); | ||
| @@ -4611,6 +4636,37 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | ||
| DISPATCH(); | ||
| } | ||
| TARGET(PRECALL_PY_CLASS){ | ||
| _PyPrecallCache *cache = (_PyPrecallCache *)next_instr; | ||
| int is_method = (PEEK(oparg + 2) != NULL); | ||
| DEOPT_IF(is_method, PRECALL); | ||
| PyObject *cls = PEEK(oparg + 1); | ||
| DEOPT_IF(!PyType_Check(cls), PRECALL); | ||
| PyTypeObject *cls_t = (PyTypeObject *)cls; | ||
| DEOPT_IF(cls_t->tp_version_tag != read_u32(cache->type_version), PRECALL); | ||
| assert(cls_t->tp_flags & Py_TPFLAGS_HEAPTYPE); | ||
| PyObject *init = ((PyHeapTypeObject *)cls_t)->_spec_cache.init; | ||
| assert(PyFunction_Check(init)); | ||
| DEOPT_IF(cls_t->tp_new != PyBaseObject_Type.tp_new, PRECALL); | ||
Fidget-Spinner marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading. Please reload this page. | ||
| STAT_INC(PRECALL, hit); | ||
| PyObject *self = _PyObject_New_Vector(cls_t, &PEEK(oparg), | ||
| (Py_ssize_t)oparg, call_shape.kwnames); | ||
| if (self == NULL){ | ||
| goto error; | ||
| } | ||
| Py_INCREF(init); | ||
| PEEK(oparg+1) = self; | ||
| PEEK(oparg+2) = init; | ||
| Py_DECREF(cls); | ||
| /* For use in RETURN_VALUE later */ | ||
| assert(call_shape.init_pass_self == false); | ||
| call_shape.init_pass_self = true; | ||
| JUMPBY(INLINE_CACHE_ENTRIES_PRECALL); | ||
| DISPATCH(); | ||
| } | ||
| TARGET(KW_NAMES){ | ||
| assert(call_shape.kwnames == NULL); | ||
| assert(oparg < PyTuple_GET_SIZE(consts)); | ||
| @@ -4646,6 +4702,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | ||
| frame->f_lasti += INLINE_CACHE_ENTRIES_CALL; | ||
| new_frame->previous = frame; | ||
| cframe.current_frame = frame = new_frame; | ||
| CALL_PY_FRAME_PASS_SELF(); | ||
| CALL_STAT_INC(inlined_py_calls); | ||
| goto start_frame; | ||
| } | ||
| @@ -4751,6 +4808,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | ||
| frame->f_lasti += INLINE_CACHE_ENTRIES_CALL; | ||
| new_frame->previous = frame; | ||
| frame = cframe.current_frame = new_frame; | ||
| CALL_PY_FRAME_PASS_SELF(); | ||
| goto start_frame; | ||
| } | ||
| @@ -4791,6 +4849,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int | ||
| frame->f_lasti += INLINE_CACHE_ENTRIES_CALL; | ||
| new_frame->previous = frame; | ||
| frame = cframe.current_frame = new_frame; | ||
| CALL_PY_FRAME_PASS_SELF(); | ||
| goto start_frame; | ||
| } | ||
| @@ -5557,6 +5616,7 @@ MISS_WITH_INLINE_CACHE(STORE_SUBSCR) | ||
| error: | ||
| call_shape.kwnames = NULL; | ||
| call_shape.init_pass_self = false; | ||
MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note to reviewers: We don't set classA: def__init__(self): try: A.a# Kaboom!exceptAttributeError: passfor_inrange(10): print(A()) | ||
| /* Double-check exception status. */ | ||
| #ifdef NDEBUG | ||
| if (!_PyErr_Occurred(tstate)){ | ||
| @@ -5598,6 +5658,7 @@ MISS_WITH_INLINE_CACHE(STORE_SUBSCR) | ||
| assert(STACK_LEVEL() == 0); | ||
| _PyFrame_SetStackPointer(frame, stack_pointer); | ||
| frame->f_state = FRAME_RAISED; | ||
| Py_CLEAR(frame->self); | ||
| TRACE_FUNCTION_UNWIND(); | ||
| DTRACE_FUNCTION_EXIT(); | ||
| goto exit_unwind; | ||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to reviewers: tied to frame state instead of some cache/call_shape so that subsequent nested calls don't destroy
self(and we can identify which frame theselfbelongs to). Consider the following code: