diff --git a/Include/cpython/pyframe.h b/Include/cpython/pyframe.h index 51529763923ec3..555c642ac5bec8 100644 --- a/Include/cpython/pyframe.h +++ b/Include/cpython/pyframe.h @@ -40,6 +40,7 @@ PyAPI_FUNC(int) PyUnstable_InterpreterFrame_GetLine(struct _PyInterpreterFrame * #define PyUnstable_EXECUTABLE_KIND_PY_FUNCTION 1 #define PyUnstable_EXECUTABLE_KIND_BUILTIN_FUNCTION 3 #define PyUnstable_EXECUTABLE_KIND_METHOD_DESCRIPTOR 4 -#define PyUnstable_EXECUTABLE_KINDS 5 +#define PyUnstable_EXECUTABLE_KIND_JIT 5 +#define PyUnstable_EXECUTABLE_KINDS 6 PyAPI_DATA(const PyTypeObject *) const PyUnstable_ExecutableKinds[PyUnstable_EXECUTABLE_KINDS+1]; diff --git a/Include/internal/pycore_genobject.h b/Include/internal/pycore_genobject.h index a3badb59cb771a..103f048b11270b 100644 --- a/Include/internal/pycore_genobject.h +++ b/Include/internal/pycore_genobject.h @@ -8,7 +8,7 @@ extern "C" { # error "this header requires Py_BUILD_CORE define" #endif -#include "pycore_interpframe_structs.h" // _PyGenObject +#include "pycore_interpframe_structs.h" // _PyInterpreterFrame #include // offsetof() diff --git a/Include/internal/pycore_interp_structs.h b/Include/internal/pycore_interp_structs.h index 2bfb84da36cbc8..04030f75992190 100644 --- a/Include/internal/pycore_interp_structs.h +++ b/Include/internal/pycore_interp_structs.h @@ -528,7 +528,7 @@ struct _py_func_state { /* For now we hard-code this to a value for which we are confident all the static builtin types will fit (for all builds). */ -#define _Py_MAX_MANAGED_STATIC_BUILTIN_TYPES 202 +#define _Py_MAX_MANAGED_STATIC_BUILTIN_TYPES 210 #define _Py_MAX_MANAGED_STATIC_EXT_TYPES 10 #define _Py_MAX_MANAGED_STATIC_TYPES \ (_Py_MAX_MANAGED_STATIC_BUILTIN_TYPES + _Py_MAX_MANAGED_STATIC_EXT_TYPES) diff --git a/Include/internal/pycore_interpframe.h b/Include/internal/pycore_interpframe.h index 28370ababc47b9..47be32cb970c1d 100644 --- a/Include/internal/pycore_interpframe.h +++ b/Include/internal/pycore_interpframe.h @@ -17,9 +17,39 @@ extern "C" { #define _PyInterpreterFrame_LASTI(IF) \ ((int)((IF)->instr_ptr - _PyFrame_GetBytecode((IF)))) +PyAPI_DATA(PyTypeObject) PyUnstable_ExternalExecutable_Type; + +#define PyUnstable_ExternalExecutable_Check(op) Py_IS_TYPE((op), &PyUnstable_ExternalExecutable_Type) + +// Initialize a potentially external frame and make it safe to access the +// all of the members of the returned _PyInterpreterFrame. The returned +// value will be the same address as the passed in pointer. +PyAPI_FUNC(void) _PyFrame_InitializeExternalFrame(_PyInterpreterFrame *frame); + +PyAPI_FUNC(PyObject *) PyUnstable_MakeExternalExecutable(_PyFrame_Reifier reifier, PyCodeObject *code, PyObject *state); + +static inline bool _PyFrame_IsExternalFrame(_PyInterpreterFrame *frame) +{ + if (PyStackRef_IsNull(frame->f_executable)) { + return false; + } + return PyUnstable_ExternalExecutable_Check(PyStackRef_AsPyObjectBorrow(frame->f_executable)); +} + +static inline void +_PyFrame_EnsureFrameFullyInitialized(_PyInterpreterFrame *frame) +{ + if (_PyFrame_IsExternalFrame(frame)) { + _PyFrame_InitializeExternalFrame(frame); + } +} + static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) { assert(!PyStackRef_IsNull(f->f_executable)); PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable); + if (PyUnstable_ExternalExecutable_Check(executable)) { + return ((PyUnstable_PyExternalExecutable *)executable)->ef_code; + } assert(PyCode_Check(executable)); return (PyCodeObject *)executable; } @@ -30,12 +60,6 @@ static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) { static inline PyCodeObject* _Py_NO_SANITIZE_THREAD _PyFrame_SafeGetCode(_PyInterpreterFrame *f) { - // globals and builtins may be NULL on a legit frame, but it's unlikely. - // It's more likely that it's a sign of an invalid frame. - if (f->f_globals == NULL || f->f_builtins == NULL) { - return NULL; - } - if (PyStackRef_IsNull(f->f_executable)) { return NULL; } @@ -48,6 +72,18 @@ _PyFrame_SafeGetCode(_PyInterpreterFrame *f) if (_PyObject_IsFreed(executable)) { return NULL; } + if (_PyFrame_IsExternalFrame(f)) { + executable = (PyObject *)((PyUnstable_PyExternalExecutable *)executable)->ef_code; + if (_PyObject_IsFreed(executable)) { + return NULL; + } + } else { + // globals and builtins may be NULL on a legit frame, but it's unlikely. + // It's more likely that it's a sign of an invalid frame. + if (f->f_globals == NULL || f->f_builtins == NULL) { + return NULL; + } + } if (!PyCode_Check(executable)) { return NULL; } @@ -81,6 +117,7 @@ _PyFrame_SafeGetLasti(struct _PyInterpreterFrame *f) } _Py_CODEUNIT *bytecode; + _PyFrame_EnsureFrameFullyInitialized(f); #ifdef Py_GIL_DISABLED _PyCodeArray *tlbc = _PyCode_GetTLBCArray(co); assert(f->tlbc_index >= 0 && f->tlbc_index < tlbc->size); @@ -262,6 +299,9 @@ _PyFrame_IsIncomplete(_PyInterpreterFrame *frame) if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) { return true; } + if (frame->instr_ptr == NULL) { + return false; + } return frame->owner != FRAME_OWNED_BY_GENERATOR && frame->instr_ptr < _PyFrame_GetBytecode(frame) + _PyFrame_GetCode(frame)->_co_firsttraceable; @@ -276,12 +316,69 @@ _PyFrame_GetFirstComplete(_PyInterpreterFrame *frame) return frame; } +#if Py_DEBUG + +static inline bool _Py_NO_SANITIZE_THREAD +_PyFrame_IsIncompleteOrUninitialized(_PyInterpreterFrame *frame) +{ + if (frame->owner >= FRAME_OWNED_BY_INTERPRETER || _PyFrame_IsExternalFrame(frame)) { + return true; + } + if (frame->instr_ptr == NULL) { + return true; + } + return frame->owner != FRAME_OWNED_BY_GENERATOR && + frame->instr_ptr < _PyFrame_GetBytecode(frame) + + _PyFrame_GetCode(frame)->_co_firsttraceable; +} + +static inline _PyInterpreterFrame * +_PyFrame_GetFirstCompleteInitialized(_PyInterpreterFrame *frame) +{ + while (frame && _PyFrame_IsIncompleteOrUninitialized(frame)) { + frame = frame->previous; + } + return frame; +} + +#endif + +static inline bool +_PyFrame_StackpointerSaved(void) +{ +#if Py_DEBUG + PyThreadState *tstate = PyThreadState_GET(); + return _PyFrame_GetFirstCompleteInitialized(tstate->current_frame) == NULL || + _PyFrame_GetFirstCompleteInitialized(tstate->current_frame)->stackpointer != NULL; +#else + return true; +#endif +} + + + static inline _PyInterpreterFrame * _PyThreadState_GetFrame(PyThreadState *tstate) { return _PyFrame_GetFirstComplete(tstate->current_frame); } +static inline PyObject * +_PyFrame_GetGlobals(_PyInterpreterFrame *frame) { + if (frame->f_globals == NULL) { + frame->f_globals = _PyFrame_GetFunction(frame)->func_globals; + } + return frame->f_globals; +} + +static inline PyObject * +_PyFrame_GetBuiltins(_PyInterpreterFrame *frame) { + if (frame->f_builtins == NULL) { + frame->f_builtins = _PyFrame_GetFunction(frame)->func_builtins; + } + return frame->f_builtins; +} + /* For use by _PyFrame_GetFrameObject Do not call directly. */ PyAPI_FUNC(PyFrameObject *) @@ -293,9 +390,8 @@ _PyFrame_MakeAndSetFrameObject(_PyInterpreterFrame *frame); static inline PyFrameObject * _PyFrame_GetFrameObject(_PyInterpreterFrame *frame) { - assert(!_PyFrame_IsIncomplete(frame)); - PyFrameObject *res = frame->frame_obj; + PyFrameObject *res = frame->frame_obj; if (res != NULL) { return res; } @@ -317,7 +413,7 @@ _PyFrame_ClearLocals(_PyInterpreterFrame *frame); * * Exported for external JIT support */ - PyAPI_FUNC(void) +PyAPI_FUNC(void) _PyFrame_ClearExceptCode(_PyInterpreterFrame * frame); int @@ -367,7 +463,7 @@ _PyFrame_PushUnchecked(PyThreadState *tstate, _PyStackRef func, int null_locals_ /* Pushes a trampoline frame without checking for space. * Must be guarded by _PyThreadState_HasStackSpace() */ static inline _PyInterpreterFrame * -_PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int stackdepth, _PyInterpreterFrame * previous) +_PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int stackdepth, _PyInterpreterFrame *previous) { CALL_STAT_INC(frames_pushed); _PyInterpreterFrame *frame = (_PyInterpreterFrame *)tstate->datastack_top; diff --git a/Include/internal/pycore_interpframe_structs.h b/Include/internal/pycore_interpframe_structs.h index 38510685f4093c..56941b5e2fd3ed 100644 --- a/Include/internal/pycore_interpframe_structs.h +++ b/Include/internal/pycore_interpframe_structs.h @@ -85,6 +85,15 @@ struct _PyAsyncGenObject { _PyGenObject_HEAD(ag) }; +typedef void (*_PyFrame_Reifier)(struct _PyInterpreterFrame *, PyObject *reifier); + +typedef struct { + PyObject_HEAD + PyCodeObject *ef_code; + PyObject *ef_state; + _PyFrame_Reifier ef_reifier; +} PyUnstable_PyExternalExecutable; + #undef _PyGenObject_HEAD diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 4c16bbd4cb0acf..566c1415e2e77d 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -4,6 +4,7 @@ import _thread from collections import deque import contextlib +import dis import importlib.machinery import importlib.util import json @@ -2869,6 +2870,61 @@ def func(): names = ["func", "outer", "outer", "inner", "inner", "outer", "inner"] self.do_test(func, names) + def test_jit_frame(self): + def fakefunc(): + pass + + def f(): + return sys._getframe(1) + + res = _testinternalcapi.call_with_jit_frame(fakefunc, f, ()) + + def test_jit_frame_instr_ptr(self): + """jit executable can fill in the instr ptr each time the frame is queried""" + def fakefunc(): + pass + pass + pass + pass + + offset = 0 + linenos = [] + def test(): + for op in dis.get_instructions(fakefunc): + if op.opname in ("RESUME", "NOP", "RETURN_VALUE"): + nonlocal offset + offset = op.offset//2 + linenos.append(sys._getframe(1).f_lineno) + + def callback(): + return {"instr_ptr": offset} + + _testinternalcapi.call_with_jit_frame(fakefunc, test, (), callback) + base = fakefunc.__code__.co_firstlineno + self.assertEqual(linenos, [base, base + 1, base + 2, base + 3, base + 4]) + + def test_jit_frame_code(self): + """internal C api checks the for a code executor""" + def fakefunc(): + pass + + def callback(): + return _testinternalcapi.iframe_getcode(sys._getframe(1)) + + res = _testinternalcapi.call_with_jit_frame(fakefunc, callback, ()) + self.assertEqual(res, fakefunc.__code__) + + def test_jit_frame_line(self): + """internal C api checks the for a code executor""" + def fakefunc(): + pass + + def callback(): + return _testinternalcapi.iframe_getline(sys._getframe(1)) + + res = _testinternalcapi.call_with_jit_frame(fakefunc, callback, ()) + self.assertEqual(res, fakefunc.__code__.co_firstlineno) + class Test_Pep523AllowSpecialization(unittest.TestCase): """Tests for _PyInterpreterState_SetEvalFrameFunc with diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-12-11-20-23-56.gh-issue-142598.ftYjy7.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-11-20-23-56.gh-issue-142598.ftYjy7.rst new file mode 100644 index 00000000000000..61bab81ec2bb48 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-12-11-20-23-56.gh-issue-142598.ftYjy7.rst @@ -0,0 +1 @@ +Adds a new executable type for _PyInterpreterFrame.f_executable PEP 523 JITs to plug in and have their frames visible to external introspection tools. diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index deac8570fe3241..1ec647d8f4b8c1 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -29,6 +29,7 @@ #include "pycore_initconfig.h" // _Py_GetConfigsAsDict() #include "pycore_instruction_sequence.h" // _PyInstructionSequence_New() #include "pycore_interpframe.h" // _PyFrame_GetFunction() +#include "pycore_interpframe_structs.h" // _PyInterpreterFrame #include "pycore_jit.h" // _PyJIT_AddressInJitCode() #include "pycore_object.h" // _PyObject_IsFreed() #include "pycore_optimizer.h" // _Py_Executor_DependsOn @@ -1034,6 +1035,109 @@ set_eval_frame_interp(PyObject *self, PyObject *args) Py_RETURN_NONE; } +typedef struct { + bool initialized; + _PyInterpreterFrame frame; +} JitFrame; + +void +reifier(_PyInterpreterFrame *frame, PyObject *executable) +{ + JitFrame *jitframe = (JitFrame*)((char *)frame - offsetof(JitFrame, frame)); + PyFunctionObject *func = (PyFunctionObject *)PyStackRef_AsPyObjectBorrow(frame->f_funcobj); + if (!jitframe->initialized) { + jitframe->initialized = true; + if (frame->instr_ptr == NULL) { + PyCodeObject *code = (PyCodeObject *)func->func_code; + frame->instr_ptr = _PyCode_CODE(code) + code->_co_firsttraceable; + } + } + PyUnstable_PyExternalExecutable *ext_exec = (PyUnstable_PyExternalExecutable*)executable; + if (ext_exec->ef_state == NULL) { + return; + } + + PyObject *res = PyObject_CallNoArgs(ext_exec->ef_state); + if (res == NULL) { + // reifier cannot fail + PyErr_Clear(); + return; + } + + // let the test-state function fill in details on the frame + if (PyDict_Check(res)) { + PyObject *instr_ptr = PyDict_GetItemString(res, "instr_ptr"); + if (instr_ptr != NULL) { + frame->instr_ptr = _PyCode_CODE((PyCodeObject *)func->func_code) + + PyLong_AsLong(instr_ptr); + } + } + Py_DECREF(res); +} + +static PyObject * +call_with_jit_frame(PyObject *self, PyObject *args) +{ + PyObject *fakefunc; // used for f_funcobj as-if we were that JITed function + PyObject *call; // the thing to call for testing purposes + PyObject *callargs; // the arguments to provide for the test call + PyObject *state = NULL; // a state object provided to the reifier, for tests we + // callback on it to populate fields. + if (!PyArg_ParseTuple(args, "OOO|O", &fakefunc, &call, &callargs, &state)) { + return NULL; + } + if (!PyTuple_Check(callargs)) { + PyErr_SetString(PyExc_TypeError, "callargs must be a tuple"); + return NULL; + } + + PyThreadState *tstate = PyThreadState_Get(); + PyCodeObject *code = (PyCodeObject *)((PyFunctionObject *)fakefunc)->func_code; + PyObject *executable = PyUnstable_MakeExternalExecutable(reifier, code, state); + if (executable == NULL) { + return NULL; + } + + // Create JIT frame and push onto the _PyInterprerFrame stack. + JitFrame frame; + frame.initialized = false; + // Initialize minimal set of fields + frame.frame.f_executable = PyStackRef_FromPyObjectSteal(executable); + frame.frame.previous = tstate->current_frame; + frame.frame.f_funcobj = PyStackRef_FromPyObjectNew(fakefunc); + frame.frame.instr_ptr = NULL; + frame.frame.f_globals = NULL; + frame.frame.f_builtins = NULL; + frame.frame.f_locals = NULL; + frame.frame.frame_obj = NULL; + frame.frame.stackpointer = &frame.frame.localsplus[0]; + frame.frame.owner = FRAME_OWNED_BY_THREAD; +#ifdef Py_GIL_DISABLED + frame.frame.tlbc_index = 0; +#endif + tstate->current_frame = &frame.frame; + + // call the test function + PyObject *res = PyObject_Call(call, callargs, NULL); + + tstate->current_frame = frame.frame.previous; + // the test function may have caused the frame to get reified. + if (frame.initialized && frame.frame.frame_obj != NULL) { + // remove our reifier + PyStackRef_CLOSE(frame.frame.f_executable); + frame.frame.f_executable = PyStackRef_FromPyObjectNew(code); + + // Transfer ownership to the reified frame object + _PyFrame_ClearExceptCode(&frame.frame); + } + else { + // Pop frame from the stack + PyStackRef_CLOSE(frame.frame.f_funcobj); + } + PyStackRef_CLOSE(frame.frame.f_executable); + return res; +} + static PyObject * is_specialization_enabled(PyObject *self, PyObject *Py_UNUSED(args)) { @@ -2917,6 +3021,7 @@ static PyMethodDef module_functions[] = { {"set_eval_frame_interp", set_eval_frame_interp, METH_VARARGS, NULL}, {"set_eval_frame_record", set_eval_frame_record, METH_O, NULL}, {"is_specialization_enabled", is_specialization_enabled, METH_NOARGS, NULL}, + {"call_with_jit_frame", call_with_jit_frame, METH_VARARGS, NULL}, _TESTINTERNALCAPI_COMPILER_CLEANDOC_METHODDEF _TESTINTERNALCAPI_NEW_INSTRUCTION_SEQUENCE_METHODDEF _TESTINTERNALCAPI_COMPILER_CODEGEN_METHODDEF diff --git a/Modules/_testinternalcapi/interpreter.c b/Modules/_testinternalcapi/interpreter.c index 99dcd18393fb87..a75a27b20d9720 100644 --- a/Modules/_testinternalcapi/interpreter.c +++ b/Modules/_testinternalcapi/interpreter.c @@ -50,7 +50,7 @@ Test_EvalFrame(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag) #if !_Py_TAIL_CALL_INTERP uint8_t opcode; /* Current opcode */ int oparg; /* Current opcode argument, if any */ - assert(tstate->current_frame == NULL || tstate->current_frame->stackpointer != NULL); + assert(_PyFrame_StackpointerSaved()); #if !USE_COMPUTED_GOTOS uint8_t tracing_mode = 0; uint8_t dispatch_code; diff --git a/Objects/object.c b/Objects/object.c index 3166254f6f640b..36b1e1d0662626 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2608,6 +2608,7 @@ static PyTypeObject* static_types[] = { &PyTuple_Type, &PyUnicodeIter_Type, &PyUnicode_Type, + &PyUnstable_ExternalExecutable_Type, &PyWrapperDescr_Type, &PyZip_Type, &Py_GenericAliasType, @@ -3285,7 +3286,7 @@ _Py_Dealloc(PyObject *op) #if !defined(Py_GIL_DISABLED) && !defined(Py_STACKREF_DEBUG) /* This assertion doesn't hold for the free-threading build, as * PyStackRef_CLOSE_SPECIALIZED is not implemented */ - assert(tstate->current_frame == NULL || tstate->current_frame->stackpointer != NULL); + assert(_PyFrame_StackpointerSaved()); #endif PyObject *old_exc = tstate != NULL ? tstate->current_exception : NULL; // Keep the old exception type alive to prevent undefined behavior diff --git a/Python/ceval.c b/Python/ceval.c index 03bc5229565966..e87b2890f6bd94 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1193,7 +1193,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int #if !_Py_TAIL_CALL_INTERP uint8_t opcode; /* Current opcode */ int oparg; /* Current opcode argument, if any */ - assert(tstate->current_frame == NULL || tstate->current_frame->stackpointer != NULL); + assert(_PyFrame_StackpointerSaved()); #if !USE_COMPUTED_GOTOS uint8_t tracing_mode = 0; uint8_t dispatch_code; @@ -1952,7 +1952,7 @@ clear_gen_frame(PyThreadState *tstate, _PyInterpreterFrame * frame) } void -_PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame * frame) +_PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame) { // Update last_profiled_frame for remote profiler frame caching. // By this point, tstate->current_frame is already set to the parent frame. @@ -2401,7 +2401,6 @@ _PyEval_UnpackIterableStackRef(PyThreadState *tstate, PyObject *v, } - void _PyEval_MonitorRaise(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *instr) @@ -2575,7 +2574,7 @@ _PyEval_GetBuiltins(PyThreadState *tstate) { _PyInterpreterFrame *frame = _PyThreadState_GetFrame(tstate); if (frame != NULL) { - return frame->f_builtins; + return _PyFrame_GetBuiltins(frame); } return tstate->interp->builtins; } @@ -2689,7 +2688,7 @@ _PyEval_GetGlobals(PyThreadState *tstate) if (current_frame == NULL) { return NULL; } - return current_frame->f_globals; + return _PyFrame_GetGlobals(current_frame); } PyObject * @@ -2823,7 +2822,7 @@ PyObject* PyEval_GetFrameGlobals(void) if (current_frame == NULL) { return NULL; } - return Py_XNewRef(current_frame->f_globals); + return Py_XNewRef(_PyFrame_GetGlobals(current_frame)); } PyObject* PyEval_GetFrameBuiltins(void) diff --git a/Python/frame.c b/Python/frame.c index ff81eb0b3020c7..4ff2957d159603 100644 --- a/Python/frame.c +++ b/Python/frame.c @@ -127,11 +127,26 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame *frame) PyStackRef_CLEAR(frame->f_funcobj); } +// Calls the frame reifier to populate the frame's fields +void +_PyFrame_InitializeExternalFrame(_PyInterpreterFrame *frame) +{ + if (_PyFrame_IsExternalFrame(frame)) { + PyObject *executor = PyStackRef_AsPyObjectBorrow(frame->f_executable); + PyUnstable_PyExternalExecutable *jit_exec = (PyUnstable_PyExternalExecutable *)executor; + jit_exec->ef_reifier(frame, executor); + } +} + /* Unstable API functions */ PyObject * PyUnstable_InterpreterFrame_GetCode(struct _PyInterpreterFrame *frame) { + if (_PyFrame_IsExternalFrame(frame)) { + PyObject *executable = PyStackRef_AsPyObjectBorrow(frame->f_executable); + return Py_NewRef(((PyUnstable_PyExternalExecutable *)executable)->ef_code); + } return PyStackRef_AsPyObjectNew(frame->f_executable); } @@ -146,14 +161,82 @@ PyUnstable_InterpreterFrame_GetLasti(struct _PyInterpreterFrame *frame) int _Py_NO_SANITIZE_THREAD PyUnstable_InterpreterFrame_GetLine(_PyInterpreterFrame *frame) { + _PyFrame_EnsureFrameFullyInitialized(frame); int addr = _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT); return PyCode_Addr2Line(_PyFrame_GetCode(frame), addr); } +static int +jitexecutable_traverse(PyObject *self, visitproc visit, void *arg) +{ + PyUnstable_PyExternalExecutable *o = (PyUnstable_PyExternalExecutable *)self; + Py_VISIT(o->ef_code); + Py_VISIT(o->ef_state); + return 0; +} + +static int +jitexecutable_clear(PyObject *self) +{ + PyUnstable_PyExternalExecutable *o = (PyUnstable_PyExternalExecutable *)self; + Py_CLEAR(o->ef_code); + Py_CLEAR(o->ef_state); + return 0; +} + +static void +jitexecutable_dealloc(PyObject *self) +{ + PyUnstable_PyExternalExecutable *o = (PyUnstable_PyExternalExecutable *)self; + PyObject_GC_UnTrack(self); + Py_DECREF(o->ef_code); + Py_XDECREF(o->ef_state); + Py_TYPE(self)->tp_free(self); +} + +PyTypeObject PyUnstable_ExternalExecutable_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + .tp_name = "jit_executable", + .tp_basicsize = sizeof(PyUnstable_PyExternalExecutable), + .tp_dealloc = jitexecutable_dealloc, + .tp_traverse = jitexecutable_traverse, + .tp_clear = jitexecutable_clear, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_GC, + .tp_alloc = PyType_GenericAlloc, + .tp_free = PyObject_GC_Del, +}; + +PyObject * +PyUnstable_MakeExternalExecutable(_PyFrame_Reifier reifier, PyCodeObject *code, PyObject *state) +{ + if (reifier == NULL) { + PyErr_SetString(PyExc_ValueError, "need reifier"); + return NULL; + } else if (code == NULL) { + PyErr_SetString(PyExc_ValueError, "need code object"); + return NULL; + } + + PyUnstable_PyExternalExecutable *jit_exec = PyObject_GC_New(PyUnstable_PyExternalExecutable, + &PyUnstable_ExternalExecutable_Type); + if (jit_exec == NULL) { + return NULL; + } + + jit_exec->ef_reifier = reifier; + jit_exec->ef_code = (PyCodeObject *)Py_NewRef(code); + jit_exec->ef_state = Py_XNewRef(state); + if (state != NULL && PyObject_GC_IsTracked(state)) { + PyObject_GC_Track((PyObject *)jit_exec); + } + return (PyObject *)jit_exec; +} + const PyTypeObject *const PyUnstable_ExecutableKinds[PyUnstable_EXECUTABLE_KINDS+1] = { [PyUnstable_EXECUTABLE_KIND_SKIP] = &_PyNone_Type, [PyUnstable_EXECUTABLE_KIND_PY_FUNCTION] = &PyCode_Type, [PyUnstable_EXECUTABLE_KIND_BUILTIN_FUNCTION] = &PyMethod_Type, [PyUnstable_EXECUTABLE_KIND_METHOD_DESCRIPTOR] = &PyMethodDescr_Type, + [PyUnstable_EXECUTABLE_KIND_JIT] = &PyUnstable_ExternalExecutable_Type, [PyUnstable_EXECUTABLE_KINDS] = NULL, }; diff --git a/Python/gc.c b/Python/gc.c index 284ac725d37ac6..9d85ac4bcc544d 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -1628,12 +1628,15 @@ mark_stacks(PyInterpreterState *interp, PyGC_Head *visited, int visited_space, b PyThreadState* ts = PyInterpreterState_ThreadHead(interp); HEAD_UNLOCK(runtime); while (ts) { - _PyInterpreterFrame *frame = ts->current_frame; - while (frame) { - if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) { - frame = frame->previous; + _PyInterpreterFrame *f = ts->current_frame; + while (f) { + if (f->owner >= FRAME_OWNED_BY_INTERPRETER) { + f = f->previous; continue; } + _PyFrame_EnsureFrameFullyInitialized(f); + + _PyInterpreterFrame *frame = f; _PyStackRef *locals = frame->localsplus; _PyStackRef *sp = frame->stackpointer; objects_marked += move_to_reachable(frame->f_locals, &reachable, visited_space); @@ -1664,7 +1667,7 @@ mark_stacks(PyInterpreterState *interp, PyGC_Head *visited, int visited_space, b break; } frame->visited = 1; - frame = frame->previous; + f = f->previous; } HEAD_LOCK(runtime); ts = PyThreadState_Next(ts); @@ -2159,7 +2162,7 @@ Py_ssize_t _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason) { GCState *gcstate = &tstate->interp->gc; - assert(tstate->current_frame == NULL || tstate->current_frame->stackpointer != NULL); + assert(_PyFrame_StackpointerSaved()); int expected = 0; if (!_Py_atomic_compare_exchange_int(&gcstate->collecting, &expected, 1)) { diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 4b46ca04f56b20..ef02dc86adbd93 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -273,11 +273,14 @@ frame_disable_deferred_refcounting(_PyInterpreterFrame *frame) { // Convert locals, variables, and the executable object to strong // references from (possibly) deferred references. - assert(frame->stackpointer != NULL); assert(frame->owner == FRAME_OWNED_BY_FRAME_OBJECT || frame->owner == FRAME_OWNED_BY_GENERATOR); frame->f_executable = PyStackRef_AsStrongReference(frame->f_executable); + _PyFrame_EnsureFrameFullyInitialized(frame); + if (frame->stackpointer == NULL) { + return; + } if (frame->owner == FRAME_OWNED_BY_GENERATOR) { PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame); @@ -481,6 +484,10 @@ gc_visit_thread_stacks(PyInterpreterState *interp, struct collection_state *stat if (f->owner >= FRAME_OWNED_BY_INTERPRETER) { continue; } + _PyFrame_EnsureFrameFullyInitialized(f); + if (f->stackpointer == NULL) { + return; + } _PyStackRef *top = f->stackpointer; if (top == NULL) { @@ -878,6 +885,7 @@ gc_visit_thread_stacks_mark_alive(PyInterpreterState *interp, gc_mark_args_t *ar if (f->owner >= FRAME_OWNED_BY_INTERPRETER) { continue; } + _PyFrame_EnsureFrameFullyInitialized(f); if (f->stackpointer == NULL) { // GH-129236: The stackpointer may be NULL in cases where diff --git a/Python/import.c b/Python/import.c index 7aa96196ec1e10..60b4cfd62b429f 100644 --- a/Python/import.c +++ b/Python/import.c @@ -4493,7 +4493,7 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate, PyInterpreterState *interp = tstate->interp; _PyInterpreterFrame *frame = _PyEval_GetFrame(); - if (frame == NULL || frame->f_globals != frame->f_locals) { + if (frame == NULL || _PyFrame_GetGlobals(frame) != _PyFrame_GetLocals(frame)) { Py_DECREF(abs_name); PyErr_SetString(PyExc_SyntaxError, "'lazy import' is only allowed at module level"); diff --git a/Python/sysmodule.c b/Python/sysmodule.c index 1ee0b3bec684f9..ecabffd0018fd3 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2610,7 +2610,10 @@ sys__getframemodulename_impl(PyObject *module, int depth) while (f && (_PyFrame_IsIncomplete(f) || depth-- > 0)) { f = f->previous; } - if (f == NULL || PyStackRef_IsNull(f->f_funcobj)) { + if (f == NULL) { + Py_RETURN_NONE; + } + if (PyStackRef_IsNull(f->f_funcobj)) { Py_RETURN_NONE; } PyObject *func = PyStackRef_AsPyObjectBorrow(f->f_funcobj); diff --git a/Python/tracemalloc.c b/Python/tracemalloc.c index 0afc84e021817c..d00ec5f44f558e 100644 --- a/Python/tracemalloc.c +++ b/Python/tracemalloc.c @@ -221,7 +221,6 @@ hashtable_compare_traceback(const void *key1, const void *key2) static void tracemalloc_get_frame(_PyInterpreterFrame *pyframe, frame_t *frame) { - assert(PyStackRef_CodeCheck(pyframe->f_executable)); frame->filename = &_Py_STR(anon_unknown); int lineno = -1; diff --git a/Tools/c-analyzer/cpython/globals-to-fix.tsv b/Tools/c-analyzer/cpython/globals-to-fix.tsv index 74ca562824012b..96f4e2e07679ab 100644 --- a/Tools/c-analyzer/cpython/globals-to-fix.tsv +++ b/Tools/c-analyzer/cpython/globals-to-fix.tsv @@ -109,6 +109,7 @@ Python/bltinmodule.c - PyZip_Type - Python/context.c - PyContextToken_Type - Python/context.c - PyContextVar_Type - Python/context.c - PyContext_Type - +Python/frame.c - PyUnstable_ExternalExecutable_Type - Python/instruction_sequence.c - _PyInstructionSequence_Type - Python/instrumentation.c - _PyLegacyBranchEventHandler_Type - Python/instrumentation.c - _PyBranchesIterator - diff --git a/Tools/check-c-api-docs/ignored_c_api.txt b/Tools/check-c-api-docs/ignored_c_api.txt index dfec0524cfe016..fa07f10eed5368 100644 --- a/Tools/check-c-api-docs/ignored_c_api.txt +++ b/Tools/check-c-api-docs/ignored_c_api.txt @@ -50,6 +50,7 @@ PyUnstable_PerfTrampoline_SetPersistAfterFork # cpython/pyframe.h PyUnstable_EXECUTABLE_KINDS PyUnstable_EXECUTABLE_KIND_BUILTIN_FUNCTION +PyUnstable_EXECUTABLE_KIND_JIT PyUnstable_EXECUTABLE_KIND_METHOD_DESCRIPTOR PyUnstable_EXECUTABLE_KIND_PY_FUNCTION PyUnstable_EXECUTABLE_KIND_SKIP