Skip to content

Commit fa9862d

Browse files
DinoVmeta-codesync[bot]
authored andcommitted
Add patch '18-lightweight-frame-support' to third-party/python/3.14
Summary: This diff adds a patch to the `third-party/python/3.14` Meta-internal fork. Add lightweight frame support to CPython Reviewed By: alexmalyshev Differential Revision: D87386838 fbshipit-source-id: a1eb39c79d31d7db23f26b3ca91ba1c0259e316d
1 parent b5f6a24 commit fa9862d

10 files changed

Lines changed: 424 additions & 5 deletions

File tree

Include/cpython/pyframe.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ PyAPI_FUNC(int) PyUnstable_InterpreterFrame_GetLine(struct _PyInterpreterFrame *
4040
#define PyUnstable_EXECUTABLE_KIND_PY_FUNCTION 1
4141
#define PyUnstable_EXECUTABLE_KIND_BUILTIN_FUNCTION 3
4242
#define PyUnstable_EXECUTABLE_KIND_METHOD_DESCRIPTOR 4
43+
44+
#ifdef META_PYTHON
45+
#define PyUnstable_EXECUTABLE_KIND_JIT 5
46+
#define PyUnstable_EXECUTABLE_KINDS 6
47+
#else
4348
#define PyUnstable_EXECUTABLE_KINDS 5
49+
#endif
4450

4551
PyAPI_DATA(const PyTypeObject *) const PyUnstable_ExecutableKinds[PyUnstable_EXECUTABLE_KINDS+1];

Include/internal/pycore_interpframe.h

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,35 @@ extern "C" {
1717
#define _PyInterpreterFrame_LASTI(IF) \
1818
((int)((IF)->instr_ptr - _PyFrame_GetBytecode((IF))))
1919

20+
#ifdef META_PYTHON
21+
22+
__attribute__((weak)) PyAPI_DATA(PyTypeObject) PyUnstable_JITExecutable_Type;
23+
24+
#define PyUnstable_JITExecutable_Check(op) Py_IS_TYPE((op), &PyUnstable_JITExecutable_Type)
25+
26+
PyAPI_FUNC(PyObject *) PyUnstable_MakeJITExecutable(_PyFrame_Reifier reifier, PyCodeObject *code, PyObject *state);
27+
28+
PyAPI_FUNC(int) _PyFrame_InitializeExternalFrame(_PyInterpreterFrame *frame);
29+
30+
static inline int
31+
_PyFrame_EnsureFrameFullyInitialized(_PyInterpreterFrame *frame)
32+
{
33+
if (PyUnstable_JITExecutable_Check(PyStackRef_AsPyObjectBorrow(frame->f_executable))) {
34+
return _PyFrame_InitializeExternalFrame(frame);
35+
}
36+
return 0;
37+
}
38+
39+
#endif
40+
2041
static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) {
2142
assert(!PyStackRef_IsNull(f->f_executable));
2243
PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable);
44+
#ifdef META_PYTHON
45+
if (PyUnstable_JITExecutable_Check(executable)) {
46+
return ((PyUnstable_PyJitExecutable *)executable)->je_code;
47+
}
48+
#endif
2349
assert(PyCode_Check(executable));
2450
return (PyCodeObject *)executable;
2551
}
@@ -48,6 +74,14 @@ _PyFrame_SafeGetCode(_PyInterpreterFrame *f)
4874
if (_PyObject_IsFreed(executable)) {
4975
return NULL;
5076
}
77+
#ifdef META_PYTHON
78+
if (PyUnstable_JITExecutable_Check(executable)) {
79+
executable = (PyObject *)((PyUnstable_PyJitExecutable *)executable)->je_code;
80+
if (_PyObject_IsFreed(executable)) {
81+
return NULL;
82+
}
83+
}
84+
#endif
5185
if (!PyCode_Check(executable)) {
5286
return NULL;
5387
}
@@ -58,6 +92,12 @@ static inline _Py_CODEUNIT *
5892
_PyFrame_GetBytecode(_PyInterpreterFrame *f)
5993
{
6094
#ifdef Py_GIL_DISABLED
95+
#ifdef META_PYTHON
96+
if (_PyFrame_EnsureFrameFullyInitialized(f) < 0) {
97+
return NULL;
98+
}
99+
#endif
100+
61101
PyCodeObject *co = _PyFrame_GetCode(f);
62102
_PyCodeArray *tlbc = _PyCode_GetTLBCArray(co);
63103
assert(f->tlbc_index >= 0 && f->tlbc_index < tlbc->size);
@@ -80,6 +120,12 @@ _PyFrame_SafeGetLasti(struct _PyInterpreterFrame *f)
80120
return -1;
81121
}
82122

123+
#ifdef META_PYTHON
124+
if (_PyFrame_EnsureFrameFullyInitialized(f) < 0) {
125+
return -1;
126+
}
127+
#endif
128+
83129
_Py_CODEUNIT *bytecode;
84130
#ifdef Py_GIL_DISABLED
85131
_PyCodeArray *tlbc = _PyCode_GetTLBCArray(co);
@@ -277,6 +323,26 @@ _PyThreadState_GetFrame(PyThreadState *tstate)
277323
return _PyFrame_GetFirstComplete(tstate->current_frame);
278324
}
279325

326+
static inline PyObject *
327+
_PyFrame_GetGlobals(_PyInterpreterFrame *frame) {
328+
#ifdef META_PYTHON
329+
if (_PyFrame_EnsureFrameFullyInitialized(frame) < 0) {
330+
return NULL;
331+
}
332+
#endif
333+
return frame->f_globals;
334+
}
335+
336+
static inline PyObject *
337+
_PyFrame_GetBuiltins(_PyInterpreterFrame *frame) {
338+
#ifdef META_PYTHON
339+
if (_PyFrame_EnsureFrameFullyInitialized(frame) < 0) {
340+
return NULL;
341+
}
342+
#endif
343+
return frame->f_builtins;
344+
}
345+
280346
/* For use by _PyFrame_GetFrameObject
281347
Do not call directly. */
282348
PyFrameObject *
@@ -288,6 +354,11 @@ _PyFrame_MakeAndSetFrameObject(_PyInterpreterFrame *frame);
288354
static inline PyFrameObject *
289355
_PyFrame_GetFrameObject(_PyInterpreterFrame *frame)
290356
{
357+
#ifdef META_PYTHON
358+
if (PyUnstable_JITExecutable_Check(PyStackRef_AsPyObjectBorrow(frame->f_executable))) {
359+
return _PyFrame_MakeAndSetFrameObject(frame);
360+
}
361+
#endif
291362

292363
assert(!_PyFrame_IsIncomplete(frame));
293364
PyFrameObject *res = frame->frame_obj;
@@ -309,7 +380,7 @@ _PyFrame_ClearLocals(_PyInterpreterFrame *frame);
309380
* take should be set to 1 for heap allocated
310381
* frames like the ones in generators and coroutines.
311382
*/
312-
void
383+
PyAPI_FUNC(void)
313384
_PyFrame_ClearExceptCode(_PyInterpreterFrame * frame);
314385

315386
int

Include/internal/pycore_interpframe_structs.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,19 @@ struct _PyAsyncGenObject {
8686
_PyGenObject_HEAD(ag)
8787
};
8888

89+
#ifdef META_PYTHON
90+
91+
typedef int (*_PyFrame_Reifier)(struct _PyInterpreterFrame *, PyObject *reifier);
92+
93+
typedef struct {
94+
PyObject_HEAD
95+
PyCodeObject *je_code;
96+
PyObject *je_state;
97+
_PyFrame_Reifier je_reifier;
98+
} PyUnstable_PyJitExecutable;
99+
100+
#endif
101+
89102
#undef _PyGenObject_HEAD
90103

91104

Lib/test/test_capi/test_misc.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import _thread
55
from collections import deque
66
import contextlib
7+
import dis
78
import importlib.machinery
89
import importlib.util
910
import json
@@ -2794,6 +2795,85 @@ def func():
27942795
names = ["func", "outer", "outer", "inner", "inner", "outer", "inner"]
27952796
self.do_test(func, names)
27962797

2798+
def test_jit_frame(self):
2799+
def fakefunc():
2800+
pass
2801+
2802+
def f():
2803+
return sys._getframe(1)
2804+
2805+
res = _testinternalcapi.call_with_jit_frame(fakefunc, f, ())
2806+
2807+
def test_jit_frame_globals(self):
2808+
"""jit executable can fill in globals when accessed"""
2809+
def fakefunc():
2810+
pass
2811+
2812+
fake_globals = {"abc":42}
2813+
def callback():
2814+
return {"globals": fake_globals}
2815+
2816+
res = _testinternalcapi.call_with_jit_frame(fakefunc, globals, (), callback)
2817+
self.assertEqual(res, fake_globals)
2818+
2819+
def test_jit_frame_builtins(self):
2820+
"""jit executable can fill in builtins when accessed"""
2821+
def fakefunc():
2822+
pass
2823+
2824+
fake_builtins = {"abc":42}
2825+
def callback():
2826+
return {"builtins": fake_builtins}
2827+
2828+
res = _testinternalcapi.call_with_jit_frame(fakefunc, _testlimitedcapi.eval_getbuiltins, (), callback)
2829+
self.assertEqual(res, fake_builtins)
2830+
2831+
def test_jit_frame_instr_ptr(self):
2832+
"""jit executable can fill in the instr ptr each time the frame is queried"""
2833+
def fakefunc():
2834+
pass
2835+
pass
2836+
pass
2837+
pass
2838+
2839+
offset = 0
2840+
linenos = []
2841+
def test():
2842+
for op in dis.get_instructions(fakefunc):
2843+
if op.opname in ("RESUME", "NOP", "RETURN_VALUE"):
2844+
nonlocal offset
2845+
offset = op.offset//2
2846+
linenos.append(sys._getframe(1).f_lineno)
2847+
2848+
def callback():
2849+
return {"instr_ptr": offset}
2850+
2851+
_testinternalcapi.call_with_jit_frame(fakefunc, test, (), callback)
2852+
base = fakefunc.__code__.co_firstlineno
2853+
self.assertEqual(linenos, [base, base + 1, base + 2, base + 3, base + 4])
2854+
2855+
def test_jit_frame_code(self):
2856+
"""internal C api checks the for a code executor"""
2857+
def fakefunc():
2858+
pass
2859+
2860+
def callback():
2861+
return _testinternalcapi.iframe_getcode(sys._getframe(1))
2862+
2863+
res = _testinternalcapi.call_with_jit_frame(fakefunc, callback, ())
2864+
self.assertEqual(res, fakefunc.__code__)
2865+
2866+
def test_jit_frame_line(self):
2867+
"""internal C api checks the for a code executor"""
2868+
def fakefunc():
2869+
pass
2870+
2871+
def callback():
2872+
return _testinternalcapi.iframe_getline(sys._getframe(1))
2873+
2874+
res = _testinternalcapi.call_with_jit_frame(fakefunc, callback, ())
2875+
self.assertEqual(res, fakefunc.__code__.co_firstlineno)
2876+
27972877

27982878
@unittest.skipUnless(support.Py_GIL_DISABLED, 'need Py_GIL_DISABLED')
27992879
class TestPyThreadId(unittest.TestCase):

Modules/_testinternalcapi.c

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "pycore_initconfig.h" // _Py_GetConfigsAsDict()
2929
#include "pycore_instruction_sequence.h" // _PyInstructionSequence_New()
3030
#include "pycore_interpframe.h" // _PyFrame_GetFunction()
31+
#include "pycore_interpframe_structs.h" // _PyInterpreterFrame
3132
#include "pycore_object.h" // _PyObject_IsFreed()
3233
#include "pycore_optimizer.h" // _Py_Executor_DependsOn
3334
#include "pycore_pathconfig.h" // _PyPathConfig_ClearGlobal()
@@ -692,6 +693,117 @@ set_eval_frame_record(PyObject *self, PyObject *list)
692693
Py_RETURN_NONE;
693694
}
694695

696+
#ifdef META_PYTHON
697+
698+
typedef struct {
699+
bool initialized;
700+
_PyInterpreterFrame frame;
701+
} JitFrame;
702+
703+
int
704+
reifier(_PyInterpreterFrame *frame, PyObject *executable)
705+
{
706+
JitFrame *jitframe = (JitFrame*)((char *)frame - offsetof(JitFrame, frame));
707+
PyFunctionObject *func = (PyFunctionObject *)PyStackRef_AsPyObjectBorrow(frame->f_funcobj);
708+
if (!jitframe->initialized) {
709+
jitframe->initialized = true;
710+
frame->f_locals = NULL;
711+
frame->f_globals = func->func_globals; // borrowed
712+
frame->f_builtins = func->func_builtins; // borrowed
713+
frame->frame_obj = NULL;
714+
#ifdef Py_GIL_DISABLED
715+
frame->tlbc_index = 0;
716+
#endif
717+
}
718+
PyUnstable_PyJitExecutable *jit_exec = (PyUnstable_PyJitExecutable*)executable;
719+
if (jit_exec->je_state == NULL) {
720+
return 0;
721+
}
722+
723+
PyObject *res = PyObject_CallNoArgs(jit_exec->je_state);
724+
if (res == NULL) {
725+
return -1;
726+
}
727+
728+
// let the test-state function fill in details on the frame
729+
if (PyDict_Check(res)) {
730+
PyObject *globals = PyDict_GetItemString(res, "globals");
731+
if (globals != NULL) {
732+
frame->f_globals = globals;
733+
}
734+
PyObject *builtins = PyDict_GetItemString(res, "builtins");
735+
if (builtins != NULL) {
736+
frame->f_builtins = builtins;
737+
}
738+
PyObject *instr_ptr = PyDict_GetItemString(res, "instr_ptr");
739+
if (instr_ptr != NULL) {
740+
frame->instr_ptr = _PyCode_CODE((PyCodeObject *)func->func_code) +
741+
PyLong_AsLong(instr_ptr);
742+
}
743+
}
744+
Py_DECREF(res);
745+
return 0;
746+
}
747+
748+
static PyObject *
749+
call_with_jit_frame(PyObject *self, PyObject *args)
750+
{
751+
PyObject *fakefunc; // used for f_funcobj as-if we were that JITed function
752+
PyObject *call; // the thing to call for testing purposes
753+
PyObject *callargs; // the arguments to provide for the test call
754+
PyObject *state = NULL; // a state object provided to the reifier, for tests we
755+
// callback on it to populate fields.
756+
if (!PyArg_ParseTuple(args, "OOO|O", &fakefunc, &call, &callargs, &state)) {
757+
return NULL;
758+
}
759+
if (!PyTuple_Check(callargs)) {
760+
PyErr_SetString(PyExc_TypeError, "callargs must be a tuple");
761+
return NULL;
762+
}
763+
764+
PyThreadState *tstate = PyThreadState_Get();
765+
PyCodeObject *code = (PyCodeObject *)((PyFunctionObject *)fakefunc)->func_code;
766+
PyObject *executable = PyUnstable_MakeJITExecutable(reifier, code, state);
767+
if (executable == NULL) {
768+
return NULL;
769+
}
770+
771+
// Create JIT frame and push onto the _PyInterprerFrame stack.
772+
JitFrame frame;
773+
frame.initialized = false;
774+
// Initialize minimal set of fields
775+
frame.frame.previous = tstate->current_frame;
776+
frame.frame.f_executable = PyStackRef_FromPyObjectSteal(executable);
777+
frame.frame.f_funcobj = PyStackRef_FromPyObjectNew(fakefunc);
778+
frame.frame.instr_ptr = _PyCode_CODE(code) + code->_co_firsttraceable;
779+
frame.frame.stackpointer = &frame.frame.localsplus[0];
780+
frame.frame.owner = FRAME_OWNED_BY_THREAD;
781+
tstate->current_frame = &frame.frame;
782+
783+
// call the test function
784+
PyObject *res = PyObject_Call(call, callargs, NULL);
785+
786+
tstate->current_frame = frame.frame.previous;
787+
// the test function may have caused the frame to get reified.
788+
if (frame.initialized && frame.frame.frame_obj != NULL) {
789+
// remove our reifier
790+
PyStackRef_CLOSE(frame.frame.f_executable);
791+
frame.frame.f_executable = PyStackRef_FromPyObjectNew(code);
792+
793+
// Transfer ownership to the reified frame object
794+
_PyFrame_ClearExceptCode(&frame.frame);
795+
PyStackRef_CLOSE(frame.frame.f_executable);
796+
}
797+
else {
798+
// Pop frame from the stack
799+
PyStackRef_CLOSE(frame.frame.f_executable);
800+
PyStackRef_CLOSE(frame.frame.f_funcobj);
801+
}
802+
return res;
803+
}
804+
805+
#endif
806+
695807
/*[clinic input]
696808
697809
_testinternalcapi.compiler_cleandoc -> object
@@ -2479,6 +2591,9 @@ static PyMethodDef module_functions[] = {
24792591
{"DecodeLocaleEx", decode_locale_ex, METH_VARARGS},
24802592
{"set_eval_frame_default", set_eval_frame_default, METH_NOARGS, NULL},
24812593
{"set_eval_frame_record", set_eval_frame_record, METH_O, NULL},
2594+
#ifdef META_PYTHON
2595+
{"call_with_jit_frame", call_with_jit_frame, METH_VARARGS, NULL},
2596+
#endif
24822597
_TESTINTERNALCAPI_COMPILER_CLEANDOC_METHODDEF
24832598
_TESTINTERNALCAPI_NEW_INSTRUCTION_SEQUENCE_METHODDEF
24842599
_TESTINTERNALCAPI_COMPILER_CODEGEN_METHODDEF

0 commit comments

Comments
 (0)