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..0063fafbd4e83a 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() @@ -16,7 +16,7 @@ extern "C" { static inline PyGenObject *_PyGen_GetGeneratorFromFrame(_PyInterpreterFrame *frame) { - assert(frame->owner == FRAME_OWNED_BY_GENERATOR); + assert(frame->owner & FRAME_OWNED_BY_GENERATOR); size_t offset_in_gen = offsetof(PyGenObject, gi_iframe); return (PyGenObject *)(((char *)frame) - offset_in_gen); } diff --git a/Include/internal/pycore_interpframe.h b/Include/internal/pycore_interpframe.h index 8db1aebdc11401..b3f069f2d0ca2b 100644 --- a/Include/internal/pycore_interpframe.h +++ b/Include/internal/pycore_interpframe.h @@ -17,9 +17,37 @@ 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 bool _PyFrame_IsExternalFrame(_PyInterpreterFrame *frame) +{ + return frame->owner & FRAME_OWNED_EXTERNALLY; +} + +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 (f->owner & FRAME_OWNED_EXTERNALLY) { + assert(PyUnstable_ExternalExecutable_Check(executable)); + return ((PyUnstable_PyExternalExecutable *)executable)->ef_code; + } assert(PyCode_Check(executable)); return (PyCodeObject *)executable; } @@ -30,12 +58,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 +70,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; } @@ -59,6 +93,7 @@ _PyFrame_GetBytecode(_PyInterpreterFrame *f) { #ifdef Py_GIL_DISABLED PyCodeObject *co = _PyFrame_GetCode(f); + _PyFrame_EnsureFrameFullyInitialized(f); _PyCodeArray *tlbc = _PyCode_GetTLBCArray(co); assert(f->tlbc_index >= 0 && f->tlbc_index < tlbc->size); return (_Py_CODEUNIT *)tlbc->entries[f->tlbc_index]; @@ -81,6 +116,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); @@ -256,10 +292,11 @@ _PyFrame_IsIncomplete(_PyInterpreterFrame *frame) { if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) { return true; + } else if (frame->owner & (FRAME_OWNED_BY_GENERATOR|FRAME_OWNED_EXTERNALLY)) { + return false; } - return frame->owner != FRAME_OWNED_BY_GENERATOR && - frame->instr_ptr < _PyFrame_GetBytecode(frame) + - _PyFrame_GetCode(frame)->_co_firsttraceable; + return frame->instr_ptr < _PyFrame_GetBytecode(frame) + + _PyFrame_GetCode(frame)->_co_firsttraceable; } static inline _PyInterpreterFrame * @@ -271,12 +308,66 @@ _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; + } + 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 *) @@ -288,9 +379,9 @@ _PyFrame_MakeAndSetFrameObject(_PyInterpreterFrame *frame); static inline PyFrameObject * _PyFrame_GetFrameObject(_PyInterpreterFrame *frame) { - + _PyFrame_EnsureFrameFullyInitialized(frame); assert(!_PyFrame_IsIncomplete(frame)); - PyFrameObject *res = frame->frame_obj; + PyFrameObject *res = frame->frame_obj; if (res != NULL) { return res; } @@ -309,7 +400,7 @@ _PyFrame_ClearLocals(_PyInterpreterFrame *frame); * take should be set to 1 for heap allocated * frames like the ones in generators and coroutines. */ -void +PyAPI_FUNC(void) _PyFrame_ClearExceptCode(_PyInterpreterFrame * frame); int @@ -358,7 +449,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..9af2dba22243da 100644 --- a/Include/internal/pycore_interpframe_structs.h +++ b/Include/internal/pycore_interpframe_structs.h @@ -20,10 +20,20 @@ extern "C" { #endif enum _frameowner { - FRAME_OWNED_BY_THREAD = 0, - FRAME_OWNED_BY_GENERATOR = 1, - FRAME_OWNED_BY_FRAME_OBJECT = 2, - FRAME_OWNED_BY_INTERPRETER = 3, + // The frame is allocated on per-thread memory that will be freed or transferred when + // the frame unwinds. + FRAME_OWNED_BY_THREAD = 0x00, + // The frame is allocated in a generator and may out-live the execution. + FRAME_OWNED_BY_GENERATOR = 0x01, + // A flag which indicates the frame is owned externally. May be combined with + // FRAME_OWNED_BY_THREAD or FRAME_OWNED_BY_GENERATOR. The frame may only have + // _PyInterpreterFrameFields. To access other fields and ensure they are up to + // date _PyFrame_EnsureFrameFullyInitialized must be called first. + FRAME_OWNED_EXTERNALLY = 0x02, + // The frame is owned by the frame object (indicating the frame has unwound). + FRAME_OWNED_BY_FRAME_OBJECT = 0x04, + // The frame is a sentinel frame for entry to the interpreter loop + FRAME_OWNED_BY_INTERPRETER = 0x08, }; struct _PyInterpreterFrame { @@ -85,6 +95,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 db06719919535f..8847ca6ea5a256 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) + @unittest.skipUnless(support.Py_GIL_DISABLED, 'need Py_GIL_DISABLED') class TestPyThreadId(unittest.TestCase): 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 c00bad46a54907..e9021f93f83cf4 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 @@ -970,6 +971,14 @@ extern PyObject* Test_EvalFrame(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag); extern int Test_EvalFrame_Resumes, Test_EvalFrame_Loads; +static PyObject * +set_eval_frame_interp(PyObject *self, PyObject *Py_UNUSED(args)) +{ + _PyInterpreterState_SetEvalFrameFunc(_PyInterpreterState_GET(), Test_EvalFrame); + Py_RETURN_NONE; +} + +// Defined in interpreter.c static PyObject * get_eval_frame_stats(PyObject *self, PyObject *Py_UNUSED(args)) { @@ -995,11 +1004,103 @@ get_eval_frame_stats(PyObject *self, PyObject *Py_UNUSED(args)) return res; } +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; + } + 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 * -set_eval_frame_interp(PyObject *self, PyObject *Py_UNUSED(args)) +call_with_jit_frame(PyObject *self, PyObject *args) { - _PyInterpreterState_SetEvalFrameFunc(_PyInterpreterState_GET(), Test_EvalFrame); - Py_RETURN_NONE; + 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 = _PyCode_CODE(code) + code->_co_firsttraceable; + 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_EXTERNALLY; +#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; } /*[clinic input] @@ -2877,6 +2978,7 @@ static PyMethodDef module_functions[] = { {"set_eval_frame_default", set_eval_frame_default, METH_NOARGS, NULL}, {"set_eval_frame_interp", set_eval_frame_interp, METH_NOARGS, NULL}, {"set_eval_frame_record", set_eval_frame_record, METH_O, 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 2cd23fa3c58849..76579e60174e74 100644 --- a/Modules/_testinternalcapi/interpreter.c +++ b/Modules/_testinternalcapi/interpreter.c @@ -47,7 +47,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/frameobject.c b/Objects/frameobject.c index 5ae85c5bca61b9..c616edf0dc2e4f 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -1621,7 +1621,7 @@ first_line_not_before(int *lines, int len, int line) static bool frame_is_suspended(PyFrameObject *frame) { assert(!_PyFrame_IsIncomplete(frame->f_frame)); - if (frame->f_frame->owner == FRAME_OWNED_BY_GENERATOR) { + if (frame->f_frame->owner & FRAME_OWNED_BY_GENERATOR) { PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame->f_frame); return FRAME_STATE_SUSPENDED(gen->gi_frame_state); } @@ -1901,7 +1901,7 @@ static PyObject * frame_generator_get_impl(PyFrameObject *self) /*[clinic end generated code: output=97aeb2392562e55b input=00a2bd008b239ab0]*/ { - if (self->f_frame->owner == FRAME_OWNED_BY_GENERATOR) { + if (self->f_frame->owner & FRAME_OWNED_BY_GENERATOR) { PyObject *gen = (PyObject *)_PyGen_GetGeneratorFromFrame(self->f_frame); return Py_NewRef(gen); } @@ -2008,13 +2008,14 @@ static PyObject * frame_clear_impl(PyFrameObject *self) /*[clinic end generated code: output=864c662f16e9bfcc input=c358f9cff5f9b681]*/ { - if (self->f_frame->owner == FRAME_OWNED_BY_GENERATOR) { + if (self->f_frame->owner & FRAME_OWNED_BY_GENERATOR) { PyGenObject *gen = _PyGen_GetGeneratorFromFrame(self->f_frame); if (_PyGen_ClearFrame(gen) < 0) { return NULL; } } - else if (self->f_frame->owner == FRAME_OWNED_BY_THREAD) { + else if (self->f_frame->owner == FRAME_OWNED_BY_THREAD || + _PyFrame_IsExternalFrame(self->f_frame)) { PyErr_SetString(PyExc_RuntimeError, "cannot clear an executing frame"); return NULL; @@ -2274,6 +2275,8 @@ _PyFrame_HasHiddenLocals(_PyInterpreterFrame *frame) PyObject * _PyFrame_GetLocals(_PyInterpreterFrame *frame) { + _PyFrame_EnsureFrameFullyInitialized(frame); + // We should try to avoid creating the FrameObject if possible. // So we check if the frame is a module or class level scope PyCodeObject *co = _PyFrame_GetCode(frame); diff --git a/Objects/genobject.c b/Objects/genobject.c index 2895833b4ff933..4ec412326a3959 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -87,7 +87,7 @@ gen_traverse(PyObject *self, visitproc visit, void *arg) if (gen->gi_frame_state != FRAME_CLEARED) { _PyInterpreterFrame *frame = &gen->gi_iframe; assert(frame->frame_obj == NULL || - frame->frame_obj->f_frame->owner == FRAME_OWNED_BY_GENERATOR); + frame->frame_obj->f_frame->owner & FRAME_OWNED_BY_GENERATOR); int err = _PyFrame_Traverse(frame, visit, arg); if (err) { return err; diff --git a/Objects/object.c b/Objects/object.c index 4db22f372ec3f7..d802676f968c88 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2606,6 +2606,7 @@ static PyTypeObject* static_types[] = { &PyTuple_Type, &PyUnicodeIter_Type, &PyUnicode_Type, + &PyUnstable_ExternalExecutable_Type, &PyWrapperDescr_Type, &PyZip_Type, &Py_GenericAliasType, @@ -2836,7 +2837,7 @@ PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *op) } _PyInterpreterFrame *frame = _PyEval_GetFrame(); - if (frame == NULL) { + if (frame == NULL || _PyFrame_IsExternalFrame(frame)) { return 0; } @@ -3283,7 +3284,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/Objects/typeobject.c b/Objects/typeobject.c index b19aee6338dcc0..c4b2582a767ba8 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -12639,6 +12639,7 @@ super_init_without_args(_PyInterpreterFrame *cframe, PyTypeObject **type_p, } assert(_PyFrame_GetCode(cframe)->co_nlocalsplus > 0); + _PyFrame_EnsureFrameFullyInitialized(cframe); PyObject *firstarg = PyStackRef_AsPyObjectBorrow(_PyFrame_GetLocalsArray(cframe)[0]); if (firstarg == NULL) { PyErr_SetString(PyExc_RuntimeError, "super(): arg[0] deleted"); diff --git a/Objects/typevarobject.c b/Objects/typevarobject.c index b5413ee37a9358..007c7766cc3f4d 100644 --- a/Objects/typevarobject.c +++ b/Objects/typevarobject.c @@ -390,7 +390,8 @@ caller(void) if (f == NULL) { Py_RETURN_NONE; } - if (f == NULL || PyStackRef_IsNull(f->f_funcobj)) { + _PyFrame_EnsureFrameFullyInitialized(f); + if (PyStackRef_IsNull(f->f_funcobj)) { Py_RETURN_NONE; } PyObject *r = PyFunction_GetModule(PyStackRef_AsPyObjectBorrow(f->f_funcobj)); diff --git a/Programs/test_frozenmain.h b/Programs/test_frozenmain.h index d550740b1105dd..e96f159f9d855a 100644 --- a/Programs/test_frozenmain.h +++ b/Programs/test_frozenmain.h @@ -13,10 +13,10 @@ unsigned char M_test_frozenmain[] = { 31,0,81,5,92,6,12,0,81,6,92,5,92,6,42,26, 0,0,0,0,0,0,0,0,0,0,12,0,48,4,50,1, 0,0,0,0,0,0,29,0,74,26,0,0,9,0,28,0, - 81,1,33,0,41,8,233,0,0,0,0,78,122,18,70,114, + 81,1,33,0,41,8,233,0,0,0,0,78,218,18,70,114, 111,122,101,110,32,72,101,108,108,111,32,87,111,114,108,100, - 122,8,115,121,115,46,97,114,103,118,218,6,99,111,110,102, - 105,103,122,7,99,111,110,102,105,103,32,122,2,58,32,41, + 218,8,115,121,115,46,97,114,103,118,218,6,99,111,110,102, + 105,103,218,7,99,111,110,102,105,103,32,218,2,58,32,41, 5,218,12,112,114,111,103,114,97,109,95,110,97,109,101,218, 10,101,120,101,99,117,116,97,98,108,101,218,15,117,115,101, 95,101,110,118,105,114,111,110,109,101,110,116,218,17,99,111, @@ -25,15 +25,15 @@ unsigned char M_test_frozenmain[] = { 7,218,3,115,121,115,218,17,95,116,101,115,116,105,110,116, 101,114,110,97,108,99,97,112,105,218,5,112,114,105,110,116, 218,4,97,114,103,118,218,11,103,101,116,95,99,111,110,102, - 105,103,115,114,3,0,0,0,218,3,107,101,121,169,0,243, + 105,103,115,114,5,0,0,0,218,3,107,101,121,169,0,243, 0,0,0,0,218,18,116,101,115,116,95,102,114,111,122,101, 110,109,97,105,110,46,112,121,218,8,60,109,111,100,117,108, - 101,62,114,18,0,0,0,1,0,0,0,115,94,0,0,0, + 101,62,114,22,0,0,0,1,0,0,0,115,94,0,0,0, 241,3,1,1,1,243,8,0,1,11,219,0,24,225,0,5, 208,6,26,212,0,27,217,0,5,128,106,144,35,151,40,145, 40,212,0,27,216,9,26,215,9,38,210,9,38,211,9,40, 168,24,213,9,50,128,6,243,2,6,12,2,128,67,241,14, 0,5,10,136,71,144,67,144,53,152,2,152,54,160,35,157, - 59,152,45,208,10,40,214,4,41,243,15,6,12,2,114,16, + 59,152,45,208,10,40,214,4,41,243,15,6,12,2,114,20, 0,0,0, }; diff --git a/Python/ceval.c b/Python/ceval.c index d4bc45f9c9130e..988f63cd5798ea 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -1226,7 +1226,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; @@ -1969,7 +1969,7 @@ clear_thread_frame(PyThreadState *tstate, _PyInterpreterFrame * frame) static void clear_gen_frame(PyThreadState *tstate, _PyInterpreterFrame * frame) { - assert(frame->owner == FRAME_OWNED_BY_GENERATOR); + assert(frame->owner & FRAME_OWNED_BY_GENERATOR); PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame); FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_CLEARED); assert(tstate->exc_info == &gen->gi_exc_state); @@ -1985,7 +1985,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. @@ -2434,7 +2434,6 @@ _PyEval_UnpackIterableStackRef(PyThreadState *tstate, PyObject *v, } - void _PyEval_MonitorRaise(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *instr) @@ -2608,7 +2607,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; } @@ -2722,7 +2721,7 @@ _PyEval_GetGlobals(PyThreadState *tstate) if (current_frame == NULL) { return NULL; } - return current_frame->f_globals; + return _PyFrame_GetGlobals(current_frame); } PyObject * @@ -2856,7 +2855,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..eebdb2dad4b1f2 100644 --- a/Python/frame.c +++ b/Python/frame.c @@ -108,7 +108,7 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame *frame) { /* It is the responsibility of the owning generator/coroutine * to have cleared the enclosing generator, if any. */ - assert(frame->owner != FRAME_OWNED_BY_GENERATOR || + assert(!(frame->owner & FRAME_OWNED_BY_GENERATOR) || FT_ATOMIC_LOAD_INT8_RELAXED(_PyGen_GetGeneratorFromFrame(frame)->gi_frame_state) == FRAME_CLEARED); // GH-99729: Clearing this frame can expose the stack (via finalizers). It's // crucial that this frame has been unlinked, and is no longer visible: @@ -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); } @@ -150,10 +165,77 @@ PyUnstable_InterpreterFrame_GetLine(_PyInterpreterFrame *frame) 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 7bca40f6e3f58e..c4fd20d4f4cafb 100644 --- a/Python/gc.c +++ b/Python/gc.c @@ -1623,12 +1623,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); @@ -1659,7 +1662,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); @@ -2154,7 +2157,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 7ad60a73a56a69..cb8a967952649b 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -273,13 +273,16 @@ 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->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) { + if (frame->owner & FRAME_OWNED_BY_GENERATOR) { PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame); if (gen->gi_frame_state == FRAME_CLEARED) { // gh-124068: if the generator is cleared, then most fields other @@ -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 e298fbee536c1b..a98f3edc7935bc 100644 --- a/Python/import.c +++ b/Python/import.c @@ -4425,6 +4425,7 @@ register_lazy_on_parent(PyThreadState *tstate, PyObject *name, goto done; } if (!contains) { + _PyFrame_EnsureFrameFullyInitialized(tstate->current_frame); PyObject *lazy_module_attr = _PyLazyImport_New( tstate->current_frame, builtins, parent, child ); @@ -4493,6 +4494,9 @@ _PyImport_LazyImportModuleLevelObject(PyThreadState *tstate, PyInterpreterState *interp = tstate->interp; _PyInterpreterFrame *frame = _PyEval_GetFrame(); + if (frame != NULL) { + _PyFrame_EnsureFrameFullyInitialized(frame); + } if (frame == NULL || frame->f_globals != frame->f_locals) { Py_DECREF(abs_name); PyErr_SetString(PyExc_SyntaxError, @@ -5600,6 +5604,7 @@ publish_lazy_imports_on_module(PyThreadState *tstate, } // Create a new lazy module attr for the subpackage which was // previously lazily imported. + _PyFrame_EnsureFrameFullyInitialized(tstate->current_frame); PyObject *lazy_module_attr = _PyLazyImport_New(tstate->current_frame, builtins, name, attr_name); if (lazy_module_attr == NULL) { diff --git a/Python/intrinsics.c b/Python/intrinsics.c index 9cfc285c6a5925..47659bb5db8a0f 100644 --- a/Python/intrinsics.c +++ b/Python/intrinsics.c @@ -141,7 +141,7 @@ static PyObject * stopiteration_error(PyThreadState* tstate, PyObject *exc) { _PyInterpreterFrame *frame = tstate->current_frame; - assert(frame->owner == FRAME_OWNED_BY_GENERATOR); + assert(frame->owner & FRAME_OWNED_BY_GENERATOR); assert(PyExceptionInstance_Check(exc)); const char *msg = NULL; if (PyErr_GivenExceptionMatches(exc, PyExc_StopIteration)) { diff --git a/Python/legacy_tracing.c b/Python/legacy_tracing.c index 594d5c5ead5021..b92fa806314ae5 100644 --- a/Python/legacy_tracing.c +++ b/Python/legacy_tracing.c @@ -676,7 +676,7 @@ static int maybe_set_opcode_trace(PyThreadState *tstate) { _PyInterpreterFrame *iframe = tstate->current_frame; - if (iframe == NULL) { + if (iframe == NULL || _PyFrame_IsExternalFrame(iframe)) { return 0; } PyFrameObject *frame = iframe->frame_obj; diff --git a/Python/sysmodule.c b/Python/sysmodule.c index ce9c03bda7bd57..9aa9a0e806ee8f 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2609,7 +2609,11 @@ 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; + } + _PyFrame_EnsureFrameFullyInitialized(f); + 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 d645d2b6150d34..9e37c859b9f43c 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 f3a3612b84947a..4fe0e2ba7a5a2a 100644 --- a/Tools/check-c-api-docs/ignored_c_api.txt +++ b/Tools/check-c-api-docs/ignored_c_api.txt @@ -52,6 +52,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 diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py index ba52ea2a30e0be..a36c8eb627d53b 100755 --- a/Tools/gdb/libpython.py +++ b/Tools/gdb/libpython.py @@ -99,7 +99,7 @@ def interp_frame_has_tlbc_index(): Py_TPFLAGS_TYPE_SUBCLASS = (1 << 31) #From pycore_frame.h -FRAME_OWNED_BY_INTERPRETER = 3 +FRAME_OWNED_BY_INTERPRETER = 8 MAX_OUTPUT_LEN=1024