Skip to content

Decoding instructions should handle ENTER_EXECUTOR #107265

Description

@gvanrossum

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

Activity

  1. gvanrossum commented on Jul 25, 2023

    @gvanrossum
    MemberAuthor

    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?

  2. added 2 commits that reference this issue on Aug 20, 2023
  3. corona10 commented on Aug 21, 2023

    @corona10
    Member

    I am working on code_hash too

  4. added 4 commits that reference this issue on Aug 21, 2023
  5. markshannon commented on Aug 21, 2023

    @markshannon
    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.

  6. markshannon commented on Aug 21, 2023

    @markshannon
    Member

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

  7. added a commit that references this issue on Aug 21, 2023
  8. gvanrossum commented on Aug 21, 2023

    @gvanrossum
    MemberAuthor

    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.)

  9. gvanrossum commented on Aug 22, 2023

    @gvanrossum
    MemberAuthor

    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.

  10. corona10 commented on Aug 23, 2023

    @corona10
    Member

    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
    );
    }

  11. 14 remaining items

  12. added 6 commits that reference this issue on Aug 27, 2023
  13. corona10 commented on Sep 13, 2023

    @corona10
    Member

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

  14. added a commit that references this issue on Sep 22, 2023
  15. added a commit that references this issue on Sep 28, 2023
  16. added a commit that references this issue on Nov 2, 2023
  17. ncoghlan commented on Aug 1, 2024

    @ncoghlan
    Contributor

    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)

  18. added a commit that references this issue on Sep 2, 2024
  19. markshannon commented on Jan 7, 2026

    @markshannon
    Member

    We now have _Py_GetBaseCodeUnit which returns the underlying code unit, handling instrumentation and ENTER_EXECUTOR, and is used in (almost?) all places it is needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or error

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions