Jit/frame.cpp (450 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #include "Jit/frame.h" #include "Python.h" #include "internal/pycore_pystate.h" #include "internal/pycore_shadow_frame.h" #include "Jit/codegen/gen_asm.h" #include "Jit/log.h" #include "Jit/runtime.h" #include "Jit/util.h" #include <optional> #include <unordered_set> static bool is_shadow_frame_for_gen(_PyShadowFrame* shadow_frame) { // TODO(bsimmers): This condition will need to change when we support eager // coroutine execution in the JIT, since there is no PyGenObject* for the // frame while executing eagerly (but isGen() will still return true). // TODO(T110700318): Collapse into RTFS case bool is_jit_gen = _PyShadowFrame_GetPtrKind(shadow_frame) == PYSF_CODE_RT && static_cast<jit::CodeRuntime*>(_PyShadowFrame_GetPtr(shadow_frame)) ->frameState() ->isGen(); // Note this may be JIT or interpreted. bool is_gen_with_frame = _PyShadowFrame_GetPtrKind(shadow_frame) == PYSF_PYFRAME && _PyShadowFrame_GetPyFrame(shadow_frame)->f_gen != nullptr; return is_jit_gen || is_gen_with_frame; } namespace jit { namespace { PyObject* getModuleName(_PyShadowFrame* shadow_frame) { PyObject* globals; PyObject* result; switch (_PyShadowFrame_GetPtrKind(shadow_frame)) { case PYSF_PYFRAME: { PyFrameObject* pyframe = static_cast<PyFrameObject*>(_PyShadowFrame_GetPtr(shadow_frame)); globals = pyframe->f_globals; JIT_DCHECK( globals != nullptr, "Python frame (%p) has NULL globals", pyframe); result = PyDict_GetItemString(globals, "__name__"); break; } // TODO(T110700318): Collapse into RTFS case case PYSF_CODE_RT: { jit::CodeRuntime* code_rt = static_cast<CodeRuntime*>(_PyShadowFrame_GetPtr(shadow_frame)); globals = code_rt->frameState()->globals(); JIT_DCHECK( globals != nullptr, "JIT Runtime frame (%p) has NULL globals", code_rt); result = PyDict_GetItemString(globals, "__name__"); break; } case PYSF_RTFS: { auto frame_state = static_cast<RuntimeFrameState*>(_PyShadowFrame_GetPtr(shadow_frame)); globals = frame_state->globals(); JIT_DCHECK( globals != nullptr, "JIT Runtime frame (%p) has NULL globals", frame_state); result = PyDict_GetItemString(globals, "__name__"); break; } default: { JIT_CHECK(false, "unknown ptr kind"); } } Py_XINCREF(result); return result; } // Return the base of the stack frame given its shadow frame. uintptr_t getFrameBaseFromOnStackShadowFrame(_PyShadowFrame* shadow_frame) { // The shadow frame is embedded in the frame header at the beginning of the // stack frame. return reinterpret_cast<uintptr_t>(shadow_frame) + offsetof(FrameHeader, shadow_frame) + sizeof(_PyShadowFrame); } CodeRuntime* getCodeRuntime(_PyShadowFrame* shadow_frame) { JIT_CHECK( _PyShadowFrame_GetOwner(shadow_frame) == PYSF_JIT, "shadow frame not owned by the JIT"); if (is_shadow_frame_for_gen(shadow_frame)) { // The shadow frame belongs to a generator; retrieve the CodeRuntime // directly from the generator. PyGenObject* gen = _PyShadowFrame_GetGen(shadow_frame); return reinterpret_cast<GenDataFooter*>(gen->gi_jit_data)->code_rt; } // The shadow frame belongs to a JIT-compiled function that is on the stack; // read the CodeRuntime from the stack. uintptr_t frame_base = getFrameBaseFromOnStackShadowFrame(shadow_frame); CodeRuntime* ret = nullptr; uintptr_t code_rt_loc = frame_base - offsetof(FrameHeader, code_rt) - kPointerSize; memcpy(&ret, reinterpret_cast<CodeRuntime*>(code_rt_loc), kPointerSize); return ret; } // Find a shadow frame in the call stack. If the frame was found, returns the // last Python frame seen during the search, or nullptr if there was none. std::optional<PyFrameObject*> findInnermostPyFrameForShadowFrame( PyThreadState* tstate, _PyShadowFrame* needle) { PyFrameObject* prev_py_frame = nullptr; _PyShadowFrame* shadow_frame = tstate->shadow_frame; while (shadow_frame) { if (_PyShadowFrame_GetPtrKind(shadow_frame) == PYSF_PYFRAME) { prev_py_frame = _PyShadowFrame_GetPyFrame(shadow_frame); } else if (shadow_frame == needle) { return prev_py_frame; } shadow_frame = shadow_frame->prev; } return {}; } // Return whether or not needle is linked into a call stack bool isShadowFrameLinked(PyThreadState* tstate, _PyShadowFrame* needle) { if (tstate->shadow_frame == needle || needle->prev != nullptr) { return true; } // Handle the case where needle is the last frame on the call stack return findInnermostPyFrameForShadowFrame(tstate, needle).has_value(); } // Return the instruction pointer for the JIT-compiled function that is // executing shadow_frame. uintptr_t getIP(PyThreadState* tstate, _PyShadowFrame* shadow_frame, int frame_size) { JIT_CHECK( _PyShadowFrame_GetOwner(shadow_frame) == PYSF_JIT, "shadow frame not executed by the JIT"); uintptr_t frame_base; if (is_shadow_frame_for_gen(shadow_frame)) { PyGenObject* gen = _PyShadowFrame_GetGen(shadow_frame); auto footer = reinterpret_cast<GenDataFooter*>(gen->gi_jit_data); if (gen->gi_running && isShadowFrameLinked(tstate, shadow_frame)) { // The generator is running. Under rare circumstances the generater will // be marked as running but won't yet be resumed. We check to make sure // the shadow frame in linked into the call stack to account for this // case. See the comment in materializePyFrameForGen() for details. frame_base = footer->originalRbp; } else { // The generator is suspended. return footer->yieldPoint->resumeTarget(); } } else { frame_base = getFrameBaseFromOnStackShadowFrame(shadow_frame); } // Read the saved IP from the stack uintptr_t ip; auto saved_ip = reinterpret_cast<uintptr_t*>(frame_base - frame_size - kPointerSize); memcpy(&ip, saved_ip, kPointerSize); return ip; } // If shadow_frame is being executed by the JIT, update py_frame to reflect the // state of the Python function being executed. void updatePyFrame( PyThreadState* tstate, BorrowedRef<PyFrameObject> py_frame, _PyShadowFrame* shadow_frame) { if (_PyShadowFrame_GetOwner(shadow_frame) != PYSF_JIT) { // Interpreter is executing this frame; don't touch the PyFrameObject. return; } // TODO(emacs): Support fetching code object and line number for inlined // frames in the JIT. return; CodeRuntime* code_rt = getCodeRuntime(shadow_frame); uintptr_t ip = getIP(tstate, shadow_frame, code_rt->frame_size()); std::optional<int> bc_off = code_rt->getBCOffForIP(ip); if (!bc_off.has_value()) { // This can happen if we forget to record the address following a call made // by JIT-compiled code. Just emit a warning instead of crashing since this // should only result in incorrect bytecode offsets being reported. auto qn = Ref<>::steal(_PyShadowFrame_GetFullyQualifiedName(shadow_frame)); const char* fqname = qn != nullptr ? PyUnicode_AsUTF8(qn) : "<unknown>"; JIT_LOG("WARNING: Couldn't find bc off for ip %lx in %s", ip, fqname); return; } py_frame->f_lasti = bc_off.value(); } // Create an unlinked PyFrameObject for the given shadow frame. Ref<PyFrameObject> createPyFrame( PyThreadState* tstate, _PyShadowFrame* shadow_frame) { const RuntimeFrameState* frame_state; // TODO(T110700318): Collapse into RTFS case if (_PyShadowFrame_GetPtrKind(shadow_frame) == PYSF_CODE_RT) { frame_state = static_cast<CodeRuntime*>(_PyShadowFrame_GetPtr(shadow_frame)) ->frameState(); } else { JIT_CHECK( _PyShadowFrame_GetPtrKind(shadow_frame) == PYSF_RTFS, "Unexpected shadow frame type"); frame_state = static_cast<RuntimeFrameState*>(_PyShadowFrame_GetPtr(shadow_frame)); JIT_CHECK(!frame_state->isGen(), "unexpected generator in inlined frame"); } Ref<PyFrameObject> py_frame = Ref<PyFrameObject>::steal(PyFrame_New( tstate, frame_state->code(), frame_state->globals(), nullptr)); JIT_CHECK(py_frame != nullptr, "failed allocating frame"); // PyFrame_New links the frame into the thread stack. Py_CLEAR(py_frame->f_back); py_frame->f_executing = 1; if (frame_state->isGen()) { // Transfer ownership of the new reference to frame to the generator // epilogue. It handles detecting and unlinking the frame if the generator // is present in the `data` field of the shadow frame. // // A generator may be resumed multiple times. If a frame is materialized in // one activation, all subsequent activations must link/unlink the // materialized frame on function entry/exit. There's no active signal in // these cases, so we're forced to check for the presence of the // frame. Linking is handled by `_PyJIT_GenSend`, while unlinking is // handled by either the epilogue or, in the event that the generator // deopts, the interpreter loop. In the future we may refactor things so // that `_PyJIT_GenSend` handles both linking and unlinking. PyGenObject* gen = _PyShadowFrame_GetGen(shadow_frame); // f_gen is borrowed py_frame->f_gen = reinterpret_cast<PyObject*>(gen); // gi_frame is owned gen->gi_frame = py_frame.get(); Py_INCREF(py_frame); } bool is_inlined_function = _PyShadowFrame_GetPtrKind(shadow_frame) == PYSF_RTFS; shadow_frame->data = _PyShadowFrame_MakeData(py_frame, PYSF_PYFRAME, PYSF_JIT); if (!is_inlined_function) { // TODO(emacs): Support fetching code object and line number for inlined // frames in the JIT. updatePyFrame(tstate, py_frame, shadow_frame); } return py_frame; } void insertPyFrameBefore( PyThreadState* tstate, BorrowedRef<PyFrameObject> frame, BorrowedRef<PyFrameObject> cursor) { if (cursor == nullptr) { // Insert frame at the top of the call stack Py_XINCREF(tstate->frame); frame->f_back = tstate->frame; // ThreadState holds a borrowed reference tstate->frame = frame; return; } // Insert frame immediately before cursor in the call stack // New frame steals reference for cursor->f_back frame->f_back = cursor->f_back; // Need to create a new reference for cursor to the newly created frame. Py_INCREF(frame); cursor->f_back = frame; } // Get the PyFrameObject for shadow_frame or create and insert one before // cursor if no PyFrameObject exists. BorrowedRef<PyFrameObject> materializePyFrame( PyThreadState* tstate, _PyShadowFrame* shadow_frame, PyFrameObject* cursor) { if (_PyShadowFrame_GetPtrKind(shadow_frame) == PYSF_PYFRAME) { PyFrameObject* py_frame = _PyShadowFrame_GetPyFrame(shadow_frame); updatePyFrame(tstate, py_frame, shadow_frame); return py_frame; } // Python frame doesn't exist yet, create it and insert it into the // call stack. Ref<PyFrameObject> frame = createPyFrame(tstate, shadow_frame); insertPyFrameBefore(tstate, frame, cursor); // Ownership of the new reference is transferred to whomever unlinks the // frame (either the JIT epilogue or the interpreter loop). return frame.release(); } int getLineNo(_PyShadowFrame* shadow_frame) { _PyShadowFrame_PtrKind ptr_kind = _PyShadowFrame_GetPtrKind(shadow_frame); void* ptr = _PyShadowFrame_GetPtr(shadow_frame); int lasti = 0; PyCodeObject* code = nullptr; switch (ptr_kind) { case PYSF_CODE_RT: { auto code_rt = static_cast<jit::CodeRuntime*>(ptr); uintptr_t ip = getIP(_PyThreadState_GET(), shadow_frame, code_rt->frame_size()); std::optional<int> bc_off = code_rt->getBCOffForIP(ip); if (bc_off.has_value()) { lasti = bc_off.value(); } code = code_rt->frameState()->code(); break; } case PYSF_PYFRAME: { PyFrameObject* frame = static_cast<PyFrameObject*>(ptr); lasti = frame->f_lasti; code = frame->f_code; break; } case PYSF_RTFS: { // inlined code code = static_cast<RuntimeFrameState*>(ptr)->code(); break; } case PYSF_DUMMY: JIT_CHECK(false, "Unsupported ptr kind: PYSF_DUMMY"); } JIT_DCHECK(code != nullptr, "code object must be found"); return PyCode_Addr2Line(code, lasti); } const char* codeName(PyCodeObject* code) { if (code->co_qualname == nullptr) { return "<null>"; } return PyUnicode_AsUTF8(code->co_qualname); } const char* shadowFrameKind(_PyShadowFrame* sf) { switch (_PyShadowFrame_GetPtrKind(sf)) { case PYSF_PYFRAME: return "fra"; case PYSF_CODE_RT: return "crt"; case PYSF_RTFS: return "inl"; case PYSF_DUMMY: return "<dummy>"; } JIT_CHECK( false, "Unknown shadow frame kind %d", _PyShadowFrame_GetPtrKind(sf)); } } // namespace Ref<PyFrameObject> materializePyFrameForDeopt(PyThreadState* tstate) { PyFrameObject* prev_py_frame = nullptr; // Start from the deepest frame. _PyShadowFrame* shadow_frame = tstate->shadow_frame; // Materialize all the inlined frames. while (_PyShadowFrame_GetPtrKind(shadow_frame) == PYSF_RTFS) { prev_py_frame = materializePyFrame(tstate, shadow_frame, prev_py_frame); shadow_frame = shadow_frame->prev; } // If running in shadow frame mode, we need to materialize a frame for the // caller of the inlined functions. Otherwise, the frame will already be // there. if (_PyShadowFrame_GetPtrKind(shadow_frame) == PYSF_CODE_RT) { materializePyFrame(tstate, shadow_frame, prev_py_frame); } if (py_debug) { assertShadowCallStackConsistent(tstate); } return Ref<PyFrameObject>::steal(tstate->frame); } void assertShadowCallStackConsistent(PyThreadState* tstate) { PyFrameObject* py_frame = tstate->frame; _PyShadowFrame* shadow_frame = tstate->shadow_frame; std::vector<_PyShadowFrame*> frames; while (shadow_frame) { frames.push_back(shadow_frame); if (_PyShadowFrame_GetPtrKind(shadow_frame) == PYSF_PYFRAME) { if (py_frame != _PyShadowFrame_GetPyFrame(shadow_frame)) { std::fprintf(stderr, "topmost:\n"); for (size_t i = 0; i < frames.size(); i++) { _PyShadowFrame* sf = frames.at(i); Ref<> sf_name = Ref<>::steal(_PyShadowFrame_GetFullyQualifiedName(sf)); const char* sf_name_str = sf_name == nullptr ? "<null>" : PyUnicode_AsUTF8(sf_name); if (sf_name_str == nullptr) { sf_name_str = "<null>"; } std::fprintf( stderr, " %s prev=%p data=%p name=%s\n", shadowFrameKind(sf), shadow_frame->prev, reinterpret_cast<void*>(shadow_frame->data), sf_name_str); } } JIT_CHECK( py_frame == _PyShadowFrame_GetPyFrame(shadow_frame), "Inconsistent shadow and py frame (%s vs %s)", codeName(py_frame->f_code), codeName(_PyShadowFrame_GetPyFrame(shadow_frame)->f_code)); py_frame = py_frame->f_back; } shadow_frame = shadow_frame->prev; } if (py_frame != nullptr) { std::unordered_set<PyFrameObject*> seen; JIT_LOG( "Stack walk didn't consume entire python stack! Here's what's left:"); PyFrameObject* left = py_frame; while (left && !seen.count(left)) { JIT_LOG("%s", PyUnicode_AsUTF8(left->f_code->co_name)); seen.insert(left); left = left->f_back; } JIT_CHECK(false, "stack walk didn't consume entire python stack"); } } BorrowedRef<PyFrameObject> materializeShadowCallStack(PyThreadState* tstate) { PyFrameObject* prev_py_frame = nullptr; _PyShadowFrame* shadow_frame = tstate->shadow_frame; while (shadow_frame) { prev_py_frame = materializePyFrame(tstate, shadow_frame, prev_py_frame); shadow_frame = shadow_frame->prev; } if (py_debug) { assertShadowCallStackConsistent(tstate); } return tstate->frame; } BorrowedRef<PyFrameObject> materializePyFrameForGen( PyThreadState* tstate, PyGenObject* gen) { _PyShadowFrame* shadow_frame = &gen->gi_shadow_frame; if (gen->gi_frame) { updatePyFrame(tstate, gen->gi_frame, shadow_frame); return gen->gi_frame; } if (!gen->gi_running) { auto gen_footer = reinterpret_cast<GenDataFooter*>(gen->gi_jit_data); if (gen_footer->state == _PyJitGenState_Completed) { return nullptr; } Ref<PyFrameObject> py_frame = createPyFrame(tstate, shadow_frame); py_frame->f_executing = 0; // It's safe to destroy our reference to the frame; gen holds a strong // reference to the frame which keeps the frame alive. return py_frame; } // Check if the generator's shadow frame is on the call stack. The generator // will be marked as running but will not be on the stack when it appears as // a predecessor in a chain of generators into which an exception was // thrown. For example, given an "await stack" of coroutines like the // following, where ` a <- b` indicates a `a` awaits `b`, // // coro0 <- coro1 <- coro2 // // if someone does `coro0.throw(...)`, then `coro0` and `coro1` will be // marked as running but will not appear on the stack while `coro2` is // handling the exception. std::optional<PyFrameObject*> cursor = findInnermostPyFrameForShadowFrame(tstate, shadow_frame); if (cursor.has_value()) { return materializePyFrame(tstate, shadow_frame, cursor.value()); } // It's safe to destroy our reference to the frame; gen holds a strong // reference to the frame which keeps the frame alive. return createPyFrame(tstate, shadow_frame); } } // namespace jit int _PyShadowFrame_HasGen(_PyShadowFrame* shadow_frame) { return is_shadow_frame_for_gen(shadow_frame); } PyGenObject* _PyShadowFrame_GetGen(_PyShadowFrame* shadow_frame) { JIT_DCHECK( is_shadow_frame_for_gen(shadow_frame), "Not shadow-frame for a generator"); // For generators, shadow frame is embedded in generator object. Thus we // can recover the generator object pointer from the shadow frame pointer. return reinterpret_cast<PyGenObject*>( reinterpret_cast<uintptr_t>(shadow_frame) - offsetof(PyGenObject, gi_shadow_frame)); } PyCodeObject* _PyShadowFrame_GetCode(_PyShadowFrame* shadow_frame) { _PyShadowFrame_PtrKind ptr_kind = _PyShadowFrame_GetPtrKind(shadow_frame); void* ptr = _PyShadowFrame_GetPtr(shadow_frame); switch (ptr_kind) { // TODO(T110700318): Collapse into RTFS case case PYSF_CODE_RT: return static_cast<jit::CodeRuntime*>(ptr)->frameState()->code(); case PYSF_PYFRAME: return static_cast<PyFrameObject*>(ptr)->f_code; case PYSF_RTFS: return static_cast<jit::RuntimeFrameState*>(ptr)->code(); default: JIT_CHECK(false, "Unsupported ptr kind %d:", ptr_kind); } } PyObject* _PyShadowFrame_GetFullyQualifiedName(_PyShadowFrame* shadow_frame) { PyObject* mod_name = jit::getModuleName(shadow_frame); if (!mod_name) { return NULL; } PyCodeObject* code = _PyShadowFrame_GetCode(shadow_frame); PyObject* result = PyUnicode_FromFormat("%U:%U", mod_name, code->co_qualname); Py_DECREF(mod_name); return result; } _PyShadowFrame* _PyShadowFrame_GetAwaiterFrame(_PyShadowFrame* shadow_frame) { _PyShadowFrame* result; if (_PyShadowFrame_HasGen(shadow_frame)) { PyGenObject* gen = _PyShadowFrame_GetGen(shadow_frame); if (!PyCoro_CheckExact((PyObject*)gen)) { // This means we have a real generator, so it cannot have awaiter frames. // but we also did not fail. return nullptr; } PyCoroObject* awaiter = ((PyCoroObject*)gen)->cr_awaiter; if (!awaiter) { // This is fine, not every coroutine needs to have an awaiter return nullptr; } result = &(awaiter->cr_shadow_frame); return result; } return nullptr; } int _PyShadowFrame_WalkAndPopulate( PyCodeObject** async_stack, int* async_linenos, PyCodeObject** sync_stack, int* sync_linenos, int array_capacity, int* async_stack_len_out, int* sync_stack_len_out) { _PyShadowFrame* shadow_frame = PyThreadState_GET()->shadow_frame; // Don't assume the inputs are clean *async_stack_len_out = 0; *sync_stack_len_out = 0; // First walk the async stack (through awaiter pointers) int i = 0; _PyShadowFrame* awaiter_frame = NULL; while (shadow_frame != NULL && i < array_capacity) { async_stack[i] = _PyShadowFrame_GetCode(shadow_frame); async_linenos[i] = jit::getLineNo(shadow_frame); awaiter_frame = _PyShadowFrame_GetAwaiterFrame((_PyShadowFrame*)shadow_frame); shadow_frame = shadow_frame->prev; if (awaiter_frame != NULL) { shadow_frame = awaiter_frame; } i++; } *async_stack_len_out = i; // Next walk the sync stack (shadow frames only) int j = 0; shadow_frame = PyThreadState_GET()->shadow_frame; while (shadow_frame != NULL && j < array_capacity) { sync_stack[j] = _PyShadowFrame_GetCode(shadow_frame); sync_linenos[j] = jit::getLineNo(shadow_frame); shadow_frame = shadow_frame->prev; j++; } *sync_stack_len_out = j; return 0; }