Skip to content

Decoding instructions should handle ENTER_EXECUTOR #107265

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

Open
8 of 10 tasks
gvanrossum opened this issue Jul 25, 2023 · 15 comments
Open
8 of 10 tasks

Decoding instructions should handle ENTER_EXECUTOR #107265

gvanrossum opened this issue Jul 25, 2023 · 15 comments
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error

Comments

@gvanrossum
Copy link
Member

gvanrossum commented Jul 25, 2023

There's a variety of places where we walk over an array of instructions, using some variant of

for (int i = 0; i < num_instructions;) {
    int opcode = instructions[i].op.code;
    // <decode EXTENDED_ARG>
    opcode = _PyOpcode_Deopt[opcode];  // or _Py_GetBaseOpcode(code, i)
    // <handle opcode>
    i += 1 + _PyOpcode_Caches[opcode];  // or _PyInstruction_GetLength(code, i)
}

All these make the mistake that if opcode is ENTER_EXECUTOR, it may obscure an underlying JUMP_BACKWARD instruction, which has a cache entry.

I fixed this in gh-107256 for _PyInstruction_GetLength() and it was first reported in gh-107082, but there are a number of other occurrences. Basically every time we consult _PyOpcode_Caches we should ensure that the index is not ENTER_EXECUTOR.

CC: @markshannon


Places where I found this:

  • _PyInstruction_GetLength
  • code_richcompare
  • code_hash
  • _Py_GetBaseOpcode [should not be "fixed", it would break callers that need the oparg]
  • mark_stacks
  • _PyFrame_OpAlreadyRan
  • de_instrument?
  • initialize_tools?
  • remove_tools?
  • _PyCode_Quicken?

There are possibly others.

Linked PRs

@gvanrossum
Copy link
Member Author

Open question. Should ENTER_EXECUTOR be replaced by _Py_GetBaseOpcode() or is that its caller's responsibility? In deopt_code() the caller handles it, but in other cases the caller doesn't, but (probably) should. But you could also argue that the fix is to switch to using _PyInstruction_GetLength() instead of 1 + _PyOpcode_Caches[opcode].

@markshannon Do you have an opinion?

@corona10
Copy link
Member

I am working on code_hash too

@markshannon
Copy link
Member

Ideally, everything would use the same set of functions to get instruction lengths, opcodes and opargs.

The interaction between instrumentation and ENTER_EXECUTOR is more than just the ENTER_EXECUTOR itself, as we need to invalidate any executor if any of the instructions that it covers is instrumented.

@markshannon
Copy link
Member

I think we can remove _PyFrame_OpAlreadyRan altogether. If not now, definitely when PEP 667 is implemented.

@gvanrossum
Copy link
Member Author

gvanrossum commented Aug 21, 2023

I think we can remove _PyFrame_OpAlreadyRan altogether. If not now, definitely when PEP 667 is implemented.

But PEP 667 seems in limbo (and rather ambitious, so I wouldn't want to wait for it if we can drop _PyFrame_OpAlreadyRan).

I don't understand at all what _PyFrame_OpAlreadyRan does or why, or if there's a test for it. I added

assert(check_opcode != ENTER_EXECUTOR);

to it and am now running the test suite with -Xuops always on; we'll see in about an hour how that goes. (Hum, well, if at least one standard CI job runs runs with assertions enabled.)

@gvanrossum
Copy link
Member Author

So the entire test suite run with -Xuops hard-coded and a hard-coded check in _PyFrame_OpAlreadyRan that check_opcode != ENTER_EXECUTOR found nothing, so maybe there's some good reason why that function isn't a problem. In any case it's not a priority.

@corona10
Copy link
Member

corona10 commented Aug 23, 2023

Guido, IIUC, we don't need to fix de_instrument.
Because de_instrument is only called from removetools and it requires that opcode should have event.
I will send a PR to add one line of assert statement for the context.

Please let me know if I miss something.

static void
remove_tools(PyCodeObject * code, int offset, int event, int tools)
{
assert(event != PY_MONITORING_EVENT_LINE);
assert(event != PY_MONITORING_EVENT_INSTRUCTION);
assert(PY_MONITORING_IS_INSTRUMENTED_EVENT(event));
assert(opcode_has_event(_Py_GetBaseOpcode(code, offset)));
_PyCoMonitoringData *monitoring = code->_co_monitoring;
if (monitoring && monitoring->tools) {
monitoring->tools[offset] &= ~tools;
if (monitoring->tools[offset] == 0) {
de_instrument(code, offset, event);
}
}
else {
/* Single tool */
uint8_t single_tool = code->_co_monitoring->active_monitors.tools[event];
assert(_Py_popcount32(single_tool) <= 1);
if (((single_tool & tools) == single_tool)) {
de_instrument(code, offset, event);
}
}
}

opcode_has_event(int opcode)
{
return (
opcode != INSTRUMENTED_LINE &&
INSTRUMENTED_OPCODES[opcode] > 0
);
}

@corona10
Copy link
Member

corona10 commented Aug 24, 2023

IIUC, we don't need to handle ENTER_EXECUTOR from _PyCode_Quicken too.
AFAIK, the role of _PyCode_Quicken is marking the initialization stat for specialization.
From the execution view, now the ENTER_EXECUTOR will be decoded from the tier2 interpreter execution only,
so we don't have to validate accurate stat for specialization from tier 2.

Please let me know if I miss something :)

@gvanrossum
Copy link
Member Author

Yeah, _PyCode_Quicken is called when initializing a fresh code object, there won't be any ENTER_EXECUTOR instructions there. Maybe just add an assert that opcode != ENTER_EXECUTOR?

@corona10
Copy link
Member

corona10 commented Aug 25, 2023

Maybe just add an assert that opcode != ENTER_EXECUTOR?

Sent #108460 and merged :)

@corona10
Copy link
Member

initialize_tools needs actual fix.
JUMP_BACKWARD should be handled appropriately from the following code line, but it looks not.

if (opcode_has_event(opcode)) {
if (instrumented) {
int8_t event;
if (opcode == RESUME) {
event = instr->op.arg != 0;
}
else {
event = EVENT_FOR_OPCODE[opcode];
assert(event > 0);
}
assert(event >= 0);
assert(PY_MONITORING_IS_INSTRUMENTED_EVENT(event));
tools[i] = code->_co_monitoring->active_monitors.tools[event];
CHECK(tools[i] != 0);
}

I was able to reproduce the situation by executing the following command.

./python.exe -Xuops -m test test_monitoring

Assertion failed: (opcode != ENTER_EXECUTOR), function initialize_tools, file instrumentation.c, line 1298.
Fatal Python error: Aborted

corona10 added a commit to corona10/cpython that referenced this issue Aug 25, 2023
@corona10
Copy link
Member

And plus
#107265 (comment) was wrong,
since the remove_tools should decode ENTER_EXECUTOR to JUMP_BACKWARD, there needs some fix too

corona10 added a commit to corona10/cpython that referenced this issue Aug 25, 2023
gvanrossum pushed a commit that referenced this issue Aug 25, 2023
…TOR" (#108485)

This reverts commit d6ac5c7.

Reason: the assert we just added could be triggered (see issue).
@gvanrossum
Copy link
Member Author

gvanrossum commented Aug 25, 2023

Working from the other end, in main the following two commands crash:

(.venv) ~/cpython$  ./python.exe -Xuops -m test test_import
0:00:00 load avg: 3.18 Run tests sequentially
0:00:00 load avg: 3.18 [1/1] test_import
Assertion failed: (opcode != ENTER_EXECUTOR), function _PyCode_Quicken, file specialize.c, line 304.
Fatal Python error: Aborted

Current thread 0x00007ff851463640 (most recent call first):
  <no Python frame>

Extension modules: _testinternalcapi, _testmultiphase, _xxsubinterpreters, _testcapi (total: 4)
Abort trap: 6
(.venv) ~/cpython$ 

and

(.venv) ~/cpython$  ./python.exe -Xuops -m test test_bdb
0:00:00 load avg: 3.42 Run tests sequentially
0:00:00 load avg: 3.42 [1/1] test_bdb
Assertion failed: (is_version_up_to_date(code, tstate->interp)), function _Py_call_instrumentation_line, file instrumentation.c, line 1117.
Fatal Python error: Aborted
[...]

I think both are related to ENTER_EXECUTOR.

@corona10
Copy link
Member

corona10 commented Sep 13, 2023

About _PyFrame_OpAlreadyRan we don't have to do anything if it is removed: faster-cpython/ideas#623 (comment)

@ncoghlan
Copy link
Contributor

ncoghlan commented Aug 1, 2024

PEP 667 landed for 3.13, and a code search indicates _PyFrame_OpAlreadyRan is gone, so is there anything left to resolve for this issue?

(This issue came up while doing a search for "PEP 667" to check for issues that could be closed now that has been implemented)

Glyphack pushed a commit to Glyphack/cpython that referenced this issue Sep 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
interpreter-core (Objects, Python, Grammar, and Parser dirs) type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

5 participants