Skip to content

Commit 36bca42

Browse files
committed
Add ability for external executor
1 parent 4810bed commit 36bca42

File tree

25 files changed

+437
-60
lines changed

25 files changed

+437
-60
lines changed

Include/cpython/pyframe.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ 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-
#define PyUnstable_EXECUTABLE_KINDS 5
43+
#define PyUnstable_EXECUTABLE_KIND_JIT 5
44+
#define PyUnstable_EXECUTABLE_KINDS 6
4445

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

Include/internal/pycore_genobject.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11-
#include "pycore_interpframe_structs.h" // _PyGenObject
11+
#include "pycore_interpframe_structs.h" // _PyInterpreterFrame
1212

1313
#include <stddef.h> // offsetof()
1414

1515

1616
static inline
1717
PyGenObject *_PyGen_GetGeneratorFromFrame(_PyInterpreterFrame *frame)
1818
{
19-
assert(frame->owner == FRAME_OWNED_BY_GENERATOR);
19+
assert(frame->owner & FRAME_OWNED_BY_GENERATOR);
2020
size_t offset_in_gen = offsetof(PyGenObject, gi_iframe);
2121
return (PyGenObject *)(((char *)frame) - offset_in_gen);
2222
}

Include/internal/pycore_interpframe.h

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

20+
PyAPI_DATA(PyTypeObject) PyUnstable_ExternalExecutable_Type;
21+
22+
#define PyUnstable_ExternalExecutable_Check(op) Py_IS_TYPE((op), &PyUnstable_ExternalExecutable_Type)
23+
24+
// Initialize a potentially external frame and make it safe to access the
25+
// all of the members of the returned _PyInterpreterFrame. The returned
26+
// value will be the same address as the passed in pointer.
27+
PyAPI_FUNC(void) _PyFrame_InitializeExternalFrame(_PyInterpreterFrame *frame);
28+
29+
PyAPI_FUNC(PyObject *) PyUnstable_MakeExternalExecutable(_PyFrame_Reifier reifier, PyCodeObject *code, PyObject *state);
30+
31+
static bool _PyFrame_IsExternalFrame(_PyInterpreterFrame *frame)
32+
{
33+
return frame->owner & FRAME_OWNED_EXTERNALLY;
34+
}
35+
36+
static inline void
37+
_PyFrame_EnsureFrameFullyInitialized(_PyInterpreterFrame *frame)
38+
{
39+
if (_PyFrame_IsExternalFrame(frame)) {
40+
_PyFrame_InitializeExternalFrame(frame);
41+
}
42+
}
43+
2044
static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) {
2145
assert(!PyStackRef_IsNull(f->f_executable));
2246
PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable);
47+
if (f->owner & FRAME_OWNED_EXTERNALLY) {
48+
assert(PyUnstable_ExternalExecutable_Check(executable));
49+
return ((PyUnstable_PyExternalExecutable *)executable)->ef_code;
50+
}
2351
assert(PyCode_Check(executable));
2452
return (PyCodeObject *)executable;
2553
}
@@ -30,12 +58,6 @@ static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) {
3058
static inline PyCodeObject* _Py_NO_SANITIZE_THREAD
3159
_PyFrame_SafeGetCode(_PyInterpreterFrame *f)
3260
{
33-
// globals and builtins may be NULL on a legit frame, but it's unlikely.
34-
// It's more likely that it's a sign of an invalid frame.
35-
if (f->f_globals == NULL || f->f_builtins == NULL) {
36-
return NULL;
37-
}
38-
3961
if (PyStackRef_IsNull(f->f_executable)) {
4062
return NULL;
4163
}
@@ -48,6 +70,18 @@ _PyFrame_SafeGetCode(_PyInterpreterFrame *f)
4870
if (_PyObject_IsFreed(executable)) {
4971
return NULL;
5072
}
73+
if (_PyFrame_IsExternalFrame(f)) {
74+
executable = (PyObject *)((PyUnstable_PyExternalExecutable *)executable)->ef_code;
75+
if (_PyObject_IsFreed(executable)) {
76+
return NULL;
77+
}
78+
} else {
79+
// globals and builtins may be NULL on a legit frame, but it's unlikely.
80+
// It's more likely that it's a sign of an invalid frame.
81+
if (f->f_globals == NULL || f->f_builtins == NULL) {
82+
return NULL;
83+
}
84+
}
5185
if (!PyCode_Check(executable)) {
5286
return NULL;
5387
}
@@ -59,6 +93,7 @@ _PyFrame_GetBytecode(_PyInterpreterFrame *f)
5993
{
6094
#ifdef Py_GIL_DISABLED
6195
PyCodeObject *co = _PyFrame_GetCode(f);
96+
_PyFrame_EnsureFrameFullyInitialized(f);
6297
_PyCodeArray *tlbc = _PyCode_GetTLBCArray(co);
6398
assert(f->tlbc_index >= 0 && f->tlbc_index < tlbc->size);
6499
return (_Py_CODEUNIT *)tlbc->entries[f->tlbc_index];
@@ -81,6 +116,7 @@ _PyFrame_SafeGetLasti(struct _PyInterpreterFrame *f)
81116
}
82117

83118
_Py_CODEUNIT *bytecode;
119+
_PyFrame_EnsureFrameFullyInitialized(f);
84120
#ifdef Py_GIL_DISABLED
85121
_PyCodeArray *tlbc = _PyCode_GetTLBCArray(co);
86122
assert(f->tlbc_index >= 0 && f->tlbc_index < tlbc->size);
@@ -256,10 +292,11 @@ _PyFrame_IsIncomplete(_PyInterpreterFrame *frame)
256292
{
257293
if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) {
258294
return true;
295+
} else if (frame->owner & (FRAME_OWNED_BY_GENERATOR|FRAME_OWNED_EXTERNALLY)) {
296+
return false;
259297
}
260-
return frame->owner != FRAME_OWNED_BY_GENERATOR &&
261-
frame->instr_ptr < _PyFrame_GetBytecode(frame) +
262-
_PyFrame_GetCode(frame)->_co_firsttraceable;
298+
return frame->instr_ptr < _PyFrame_GetBytecode(frame) +
299+
_PyFrame_GetCode(frame)->_co_firsttraceable;
263300
}
264301

265302
static inline _PyInterpreterFrame *
@@ -271,12 +308,66 @@ _PyFrame_GetFirstComplete(_PyInterpreterFrame *frame)
271308
return frame;
272309
}
273310

311+
#if Py_DEBUG
312+
313+
static inline bool _Py_NO_SANITIZE_THREAD
314+
_PyFrame_IsIncompleteOrUninitialized(_PyInterpreterFrame *frame)
315+
{
316+
if (frame->owner >= FRAME_OWNED_BY_INTERPRETER || _PyFrame_IsExternalFrame(frame)) {
317+
return true;
318+
}
319+
return !(frame->owner & FRAME_OWNED_BY_GENERATOR) &&
320+
frame->instr_ptr < _PyFrame_GetBytecode(frame) +
321+
_PyFrame_GetCode(frame)->_co_firsttraceable;
322+
}
323+
324+
static inline _PyInterpreterFrame *
325+
_PyFrame_GetFirstCompleteInitialized(_PyInterpreterFrame *frame)
326+
{
327+
while (frame && _PyFrame_IsIncompleteOrUninitialized(frame)) {
328+
frame = frame->previous;
329+
}
330+
return frame;
331+
}
332+
333+
#endif
334+
335+
static inline bool
336+
_PyFrame_StackpointerSaved(void)
337+
{
338+
#if Py_DEBUG
339+
PyThreadState *tstate = PyThreadState_GET();
340+
return _PyFrame_GetFirstCompleteInitialized(tstate->current_frame) == NULL ||
341+
_PyFrame_GetFirstCompleteInitialized(tstate->current_frame)->stackpointer != NULL;
342+
#else
343+
return true;
344+
#endif
345+
}
346+
347+
348+
274349
static inline _PyInterpreterFrame *
275350
_PyThreadState_GetFrame(PyThreadState *tstate)
276351
{
277352
return _PyFrame_GetFirstComplete(tstate->current_frame);
278353
}
279354

355+
static inline PyObject *
356+
_PyFrame_GetGlobals(_PyInterpreterFrame *frame) {
357+
if (frame->f_globals == NULL) {
358+
frame->f_globals = _PyFrame_GetFunction(frame)->func_globals;
359+
}
360+
return frame->f_globals;
361+
}
362+
363+
static inline PyObject *
364+
_PyFrame_GetBuiltins(_PyInterpreterFrame *frame) {
365+
if (frame->f_builtins == NULL) {
366+
frame->f_builtins = _PyFrame_GetFunction(frame)->func_builtins;
367+
}
368+
return frame->f_builtins;
369+
}
370+
280371
/* For use by _PyFrame_GetFrameObject
281372
Do not call directly. */
282373
PyAPI_FUNC(PyFrameObject *)
@@ -288,9 +379,9 @@ _PyFrame_MakeAndSetFrameObject(_PyInterpreterFrame *frame);
288379
static inline PyFrameObject *
289380
_PyFrame_GetFrameObject(_PyInterpreterFrame *frame)
290381
{
291-
382+
_PyFrame_EnsureFrameFullyInitialized(frame);
292383
assert(!_PyFrame_IsIncomplete(frame));
293-
PyFrameObject *res = frame->frame_obj;
384+
PyFrameObject *res = frame->frame_obj;
294385
if (res != NULL) {
295386
return res;
296387
}
@@ -309,7 +400,7 @@ _PyFrame_ClearLocals(_PyInterpreterFrame *frame);
309400
* take should be set to 1 for heap allocated
310401
* frames like the ones in generators and coroutines.
311402
*/
312-
void
403+
PyAPI_FUNC(void)
313404
_PyFrame_ClearExceptCode(_PyInterpreterFrame * frame);
314405

315406
int
@@ -358,7 +449,7 @@ _PyFrame_PushUnchecked(PyThreadState *tstate, _PyStackRef func, int null_locals_
358449
/* Pushes a trampoline frame without checking for space.
359450
* Must be guarded by _PyThreadState_HasStackSpace() */
360451
static inline _PyInterpreterFrame *
361-
_PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int stackdepth, _PyInterpreterFrame * previous)
452+
_PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int stackdepth, _PyInterpreterFrame *previous)
362453
{
363454
CALL_STAT_INC(frames_pushed);
364455
_PyInterpreterFrame *frame = (_PyInterpreterFrame *)tstate->datastack_top;

Include/internal/pycore_interpframe_structs.h

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,20 @@ extern "C" {
2020
#endif
2121

2222
enum _frameowner {
23-
FRAME_OWNED_BY_THREAD = 0,
24-
FRAME_OWNED_BY_GENERATOR = 1,
25-
FRAME_OWNED_BY_FRAME_OBJECT = 2,
26-
FRAME_OWNED_BY_INTERPRETER = 3,
23+
// The frame is allocated on per-thread memory that will be freed or transferred when
24+
// the frame unwinds.
25+
FRAME_OWNED_BY_THREAD = 0x00,
26+
// The frame is allocated in a generator and may out-live the execution.
27+
FRAME_OWNED_BY_GENERATOR = 0x01,
28+
// A flag which indicates the frame is owned externally. May be combined with
29+
// FRAME_OWNED_BY_THREAD or FRAME_OWNED_BY_GENERATOR. The frame may only have
30+
// _PyInterpreterFrameFields. To access other fields and ensure they are up to
31+
// date _PyFrame_EnsureFrameFullyInitialized must be called first.
32+
FRAME_OWNED_EXTERNALLY = 0x02,
33+
// The frame is owned by the frame object (indicating the frame has unwound).
34+
FRAME_OWNED_BY_FRAME_OBJECT = 0x04,
35+
// The frame is a sentinel frame for entry to the interpreter loop
36+
FRAME_OWNED_BY_INTERPRETER = 0x08,
2737
};
2838

2939
struct _PyInterpreterFrame {
@@ -85,6 +95,15 @@ struct _PyAsyncGenObject {
8595
_PyGenObject_HEAD(ag)
8696
};
8797

98+
typedef void (*_PyFrame_Reifier)(struct _PyInterpreterFrame *, PyObject *reifier);
99+
100+
typedef struct {
101+
PyObject_HEAD
102+
PyCodeObject *ef_code;
103+
PyObject *ef_state;
104+
_PyFrame_Reifier ef_reifier;
105+
} PyUnstable_PyExternalExecutable;
106+
88107
#undef _PyGenObject_HEAD
89108

90109

Lib/test/test_capi/test_misc.py

Lines changed: 56 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
@@ -2869,6 +2870,61 @@ def func():
28692870
names = ["func", "outer", "outer", "inner", "inner", "outer", "inner"]
28702871
self.do_test(func, names)
28712872

2873+
def test_jit_frame(self):
2874+
def fakefunc():
2875+
pass
2876+
2877+
def f():
2878+
return sys._getframe(1)
2879+
2880+
res = _testinternalcapi.call_with_jit_frame(fakefunc, f, ())
2881+
2882+
def test_jit_frame_instr_ptr(self):
2883+
"""jit executable can fill in the instr ptr each time the frame is queried"""
2884+
def fakefunc():
2885+
pass
2886+
pass
2887+
pass
2888+
pass
2889+
2890+
offset = 0
2891+
linenos = []
2892+
def test():
2893+
for op in dis.get_instructions(fakefunc):
2894+
if op.opname in ("RESUME", "NOP", "RETURN_VALUE"):
2895+
nonlocal offset
2896+
offset = op.offset//2
2897+
linenos.append(sys._getframe(1).f_lineno)
2898+
2899+
def callback():
2900+
return {"instr_ptr": offset}
2901+
2902+
_testinternalcapi.call_with_jit_frame(fakefunc, test, (), callback)
2903+
base = fakefunc.__code__.co_firstlineno
2904+
self.assertEqual(linenos, [base, base + 1, base + 2, base + 3, base + 4])
2905+
2906+
def test_jit_frame_code(self):
2907+
"""internal C api checks the for a code executor"""
2908+
def fakefunc():
2909+
pass
2910+
2911+
def callback():
2912+
return _testinternalcapi.iframe_getcode(sys._getframe(1))
2913+
2914+
res = _testinternalcapi.call_with_jit_frame(fakefunc, callback, ())
2915+
self.assertEqual(res, fakefunc.__code__)
2916+
2917+
def test_jit_frame_line(self):
2918+
"""internal C api checks the for a code executor"""
2919+
def fakefunc():
2920+
pass
2921+
2922+
def callback():
2923+
return _testinternalcapi.iframe_getline(sys._getframe(1))
2924+
2925+
res = _testinternalcapi.call_with_jit_frame(fakefunc, callback, ())
2926+
self.assertEqual(res, fakefunc.__code__.co_firstlineno)
2927+
28722928

28732929
@unittest.skipUnless(support.Py_GIL_DISABLED, 'need Py_GIL_DISABLED')
28742930
class TestPyThreadId(unittest.TestCase):

0 commit comments

Comments
 (0)