-
-
Notifications
You must be signed in to change notification settings - Fork 32k
gh-109052: Use the base opcode when comparing code objects #109107
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
Conversation
@@ -1797,9 +1797,9 @@ code_richcompare(PyObject *self, PyObject *other, int op) | |||
for (int i = 0; i < Py_SIZE(co); i++) { | |||
_Py_CODEUNIT co_instr = _PyCode_CODE(co)[i]; | |||
_Py_CODEUNIT cp_instr = _PyCode_CODE(cp)[i]; | |||
uint8_t co_code = co_instr.op.code; | |||
uint8_t co_code = _Py_GetBaseOpcode(co, i); |
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.
I am not sure that calling this API at this moment is correct.
Because it will call _PyOpcode_Deopt twice in the end.
See L1812, what will happen
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.
Or move co_code or cp_code = _PyOpcode_Deopt[cocode or cp_code]; only if cp_code or co_code is ENTER_EXECUTOR at L1805 or L1814
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.
_PyOpcode_Deopt[]
is idempotent, so it is harmless.
I think we should change this code to be exactly the same as what deopt_code()
does, since that's what we're after -- we want to compare co.co_code
to cp.co_code
without creating those objects.
I also think that _Py_GetBaseOpcode()
should map ENTER_EXECUTOR
so we wouldn't have to worry about it here at all.
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.
FYI, If we decide to fix _Py_GetBaseOpcode()
, we can update #107265 too
cc @brandtbucher
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.
And maybe we can close #107265 in one short.
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.
IIRC there are also cases where we absolutely want the actual opcode, so in those cases we still need to handle ENTER_EXECUTOR explicitly. But yeah, fixing this in _Py_GetBaseOpcode()
might reduce the special cases elsewhere. In a new PR, please. :-)
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.
IMPORTANT! Do not attempt to "fix" _Py_GetBaseOpcode()
by adding ENTER_EXECUTOR
handling. This will leave the oparg
incorrect. That's why that bullet in gh-107265 uses strikethrough.
uint8_t co_arg = co_instr.op.arg; | ||
uint8_t cp_code = cp_instr.op.code; | ||
uint8_t cp_code = _Py_GetBaseOpcode(cp, i); |
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.
ditto
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.
Looks better?
diff --git a/Objects/codeobject.c b/Objects/codeobject.c
index 70a0c2ebd6..4180d216a3 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -1788,20 +1788,24 @@ code_richcompare(PyObject *self, PyObject *other, int op)
if (co_code == ENTER_EXECUTOR) {
const int exec_index = co_arg;
_PyExecutorObject *exec = co->co_executors->executors[exec_index];
- co_code = exec->vm_data.opcode;
+ co_code = _PyOpcode_Deopt[exec->vm_data.opcode];
co_arg = exec->vm_data.oparg;
}
+ else {
+ co_code = _Py_GetBaseOpcode(co, i);
+ }
assert(co_code != ENTER_EXECUTOR);
- co_code = _PyOpcode_Deopt[co_code];
if (cp_code == ENTER_EXECUTOR) {
const int exec_index = cp_arg;
_PyExecutorObject *exec = cp->co_executors->executors[exec_index];
- cp_code = exec->vm_data.opcode;
+ cp_code = _PyOpcode_Deopt[exec->vm_data.opcode];
cp_arg = exec->vm_data.oparg;
}
+ else {
+ cp_code = _Py_GetBaseOpcode(cp, i);
+ }
assert(cp_code != ENTER_EXECUTOR);
- cp_code = _PyOpcode_Deopt[cp_code];
if (co_code != cp_code || co_arg != cp_arg) {
goto unequal;
A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated. Once you have made the requested changes, please leave a comment on this pull request containing the phrase And if you don't make the requested changes, you will be put in the comfy chair! |
I don't think the current code is "wrong" functionality wise, as I'm not sure if |
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.
I dislike this approach, iterating on each bytecode at a specific index, deoptimize it, and then look for the next bytecode. The problem is that there are cases where i++ doesn't give you a bytecode but a cache :-( You have to take care of ENTER_EXECUTOR. The exact implementation of bytecode changes often these days, and it's hard to keep all functions consuming bytecode to remain correct.
Would it be possible instead to write a function which creates a copy of the deoptimized code in one shot? Something like PyCode_GetOriginalBytecode() which would create a bytes object.
I'm thinking at bug gh-107082 which has been fixed by commit 233b878. cc @gvanrossum |
I'm not saying this is the best way to do it (it was there already to compare the code objects), but the logic here is correct (at least for now). A function like |
Correct, but it's easier to implement when you iterate on a single code object, and this function can be reused in other places, and it can be moved closer to functions which modify bytecode. |
Also I believe this piece of code was originally written to avoid allocating a new piece of memory. A new function returning bytes would contradict that - not saying that's the wrong way to go, just to mention. |
Honestly, I don't think that code1==code2 operation matters for Python performance. It's usually used in the compiler/parser, not "at runtime". It shouldn't be part of "hot code". |
By the way, for me, it's surprising that _Py_GetBaseOpcode() can still return ENTER_EXECUTOR and require to get the executor (opcode, oparg). Each caller has to take care of that. Would it be possible to have a function which returns the original base case, so don't return ENTER_EXECUTOR? Maybe an "iterator-like" API which also gives an offset to the next "original" instruction (the next instruction which, one decoded, will give the original bytecode)? |
I'm just saying, this code is explicitly converted to the current status from what you wanted in bd2e47c . Do you think we should revert that change? I would guess there might be reasons behind it. |
I don't think that copying the original object is not beneficial for the scenario as you commented. |
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.
Fixing itself looks good to me.
For overall policy, it would be a different issue.
I will leave the rest of review to @gvanrossum
I prefer to abstraint myself from reviewing this PR :-) I didn't follow recent developments about optimization, so I don't have a strong opinion. I just shared my opinion and feedback on the recent code changes and issues that I saw ;-) |
I confirm that this change fix issue #109052 crash. Without this change, Python does crash with the following command:
With this change, the test does pass as expected, and Python doesn't crash.
|
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.
I think this is a good fix for the problem at hand. Zooming out, I agree that we should put all of this "deopting" logic in one place (we're pretty close with _Py_GetBaseOpcode
) and actually use it everywhere.
I'll merge in a couple of hours if nobody has any other concerns.
uint8_t cp_arg = cp_instr.op.arg; | ||
|
||
if (co_code == ENTER_EXECUTOR) { | ||
const int exec_index = co_arg; | ||
_PyExecutorObject *exec = co->co_executors->executors[exec_index]; | ||
co_code = exec->vm_data.opcode; | ||
co_code = _PyOpcode_Deopt[exec->vm_data.opcode]; |
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.
Nice catch. I think deopt_code
should be doing this as well (it can't happen yet because these are all JUMP_BACKWARD
s, which don't have specializations, but it could later when we start sticking executors in arbitrary locations).
I agree with @gvanrossum though that _Py_GetBaseOpcode
should just be the one place where all of this logic is kept. @gaogaotiantian, would you be interested in teaching _Py_GetBaseOpcode
about ENTER_EXECUTOR
and cleaning up all of the places where we're using that and _PyOpcode_Deopt
to do the right thing? I can do it if not.
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.
Yes, I can work on that.
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.
So @gvanrossum mentioned we should NOT handle ENTER_EXECUTOR
in _Py_GetBaseOpcode
because of oparg
. For this matter, it there any work to do? _Py_GetBaseOpcode
already handles instrumentation and _PyOpcode_Deopt
.
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.
See the list of bullets in gh-107265.
Let's just merge! |
GH-112329 is a backport of this pull request to the 3.12 branch. |
After instrumentation, the opcode of the code object would become
INSTRUMENTED_LINE
orINSTRUMENTED_INSTRUCTION
, we should make sure to get the actual opcode when we compare them.This also belongs to gh-107265.