Skip to content

Commit 3997dc9

Browse files
committed
Add ability for external executor
1 parent 2faceee commit 3997dc9

File tree

18 files changed

+396
-31
lines changed

18 files changed

+396
-31
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ 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

Include/internal/pycore_interp_structs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ struct _py_func_state {
528528

529529
/* For now we hard-code this to a value for which we are confident
530530
all the static builtin types will fit (for all builds). */
531-
#define _Py_MAX_MANAGED_STATIC_BUILTIN_TYPES 202
531+
#define _Py_MAX_MANAGED_STATIC_BUILTIN_TYPES 210
532532
#define _Py_MAX_MANAGED_STATIC_EXT_TYPES 10
533533
#define _Py_MAX_MANAGED_STATIC_TYPES \
534534
(_Py_MAX_MANAGED_STATIC_BUILTIN_TYPES + _Py_MAX_MANAGED_STATIC_EXT_TYPES)

Include/internal/pycore_interpframe.h

Lines changed: 106 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,39 @@ 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 inline bool _PyFrame_IsExternalFrame(_PyInterpreterFrame *frame)
32+
{
33+
if (PyStackRef_IsNull(frame->f_executable)) {
34+
return false;
35+
}
36+
return PyUnstable_ExternalExecutable_Check(PyStackRef_AsPyObjectBorrow(frame->f_executable));
37+
}
38+
39+
static inline void
40+
_PyFrame_EnsureFrameFullyInitialized(_PyInterpreterFrame *frame)
41+
{
42+
if (_PyFrame_IsExternalFrame(frame)) {
43+
_PyFrame_InitializeExternalFrame(frame);
44+
}
45+
}
46+
2047
static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) {
2148
assert(!PyStackRef_IsNull(f->f_executable));
2249
PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable);
50+
if (PyUnstable_ExternalExecutable_Check(executable)) {
51+
return ((PyUnstable_PyExternalExecutable *)executable)->ef_code;
52+
}
2353
assert(PyCode_Check(executable));
2454
return (PyCodeObject *)executable;
2555
}
@@ -30,12 +60,6 @@ static inline PyCodeObject *_PyFrame_GetCode(_PyInterpreterFrame *f) {
3060
static inline PyCodeObject* _Py_NO_SANITIZE_THREAD
3161
_PyFrame_SafeGetCode(_PyInterpreterFrame *f)
3262
{
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-
3963
if (PyStackRef_IsNull(f->f_executable)) {
4064
return NULL;
4165
}
@@ -48,6 +72,18 @@ _PyFrame_SafeGetCode(_PyInterpreterFrame *f)
4872
if (_PyObject_IsFreed(executable)) {
4973
return NULL;
5074
}
75+
if (_PyFrame_IsExternalFrame(f)) {
76+
executable = (PyObject *)((PyUnstable_PyExternalExecutable *)executable)->ef_code;
77+
if (_PyObject_IsFreed(executable)) {
78+
return NULL;
79+
}
80+
} else {
81+
// globals and builtins may be NULL on a legit frame, but it's unlikely.
82+
// It's more likely that it's a sign of an invalid frame.
83+
if (f->f_globals == NULL || f->f_builtins == NULL) {
84+
return NULL;
85+
}
86+
}
5187
if (!PyCode_Check(executable)) {
5288
return NULL;
5389
}
@@ -81,6 +117,7 @@ _PyFrame_SafeGetLasti(struct _PyInterpreterFrame *f)
81117
}
82118

83119
_Py_CODEUNIT *bytecode;
120+
_PyFrame_EnsureFrameFullyInitialized(f);
84121
#ifdef Py_GIL_DISABLED
85122
_PyCodeArray *tlbc = _PyCode_GetTLBCArray(co);
86123
assert(f->tlbc_index >= 0 && f->tlbc_index < tlbc->size);
@@ -262,6 +299,9 @@ _PyFrame_IsIncomplete(_PyInterpreterFrame *frame)
262299
if (frame->owner >= FRAME_OWNED_BY_INTERPRETER) {
263300
return true;
264301
}
302+
if (frame->instr_ptr == NULL) {
303+
return false;
304+
}
265305
return frame->owner != FRAME_OWNED_BY_GENERATOR &&
266306
frame->instr_ptr < _PyFrame_GetBytecode(frame) +
267307
_PyFrame_GetCode(frame)->_co_firsttraceable;
@@ -276,12 +316,69 @@ _PyFrame_GetFirstComplete(_PyInterpreterFrame *frame)
276316
return frame;
277317
}
278318

319+
#if Py_DEBUG
320+
321+
static inline bool _Py_NO_SANITIZE_THREAD
322+
_PyFrame_IsIncompleteOrUninitialized(_PyInterpreterFrame *frame)
323+
{
324+
if (frame->owner >= FRAME_OWNED_BY_INTERPRETER || _PyFrame_IsExternalFrame(frame)) {
325+
return true;
326+
}
327+
if (frame->instr_ptr == NULL) {
328+
return true;
329+
}
330+
return frame->owner != FRAME_OWNED_BY_GENERATOR &&
331+
frame->instr_ptr < _PyFrame_GetBytecode(frame) +
332+
_PyFrame_GetCode(frame)->_co_firsttraceable;
333+
}
334+
335+
static inline _PyInterpreterFrame *
336+
_PyFrame_GetFirstCompleteInitialized(_PyInterpreterFrame *frame)
337+
{
338+
while (frame && _PyFrame_IsIncompleteOrUninitialized(frame)) {
339+
frame = frame->previous;
340+
}
341+
return frame;
342+
}
343+
344+
#endif
345+
346+
static inline bool
347+
_PyFrame_StackpointerSaved(void)
348+
{
349+
#if Py_DEBUG
350+
PyThreadState *tstate = PyThreadState_GET();
351+
return _PyFrame_GetFirstCompleteInitialized(tstate->current_frame) == NULL ||
352+
_PyFrame_GetFirstCompleteInitialized(tstate->current_frame)->stackpointer != NULL;
353+
#else
354+
return true;
355+
#endif
356+
}
357+
358+
359+
279360
static inline _PyInterpreterFrame *
280361
_PyThreadState_GetFrame(PyThreadState *tstate)
281362
{
282363
return _PyFrame_GetFirstComplete(tstate->current_frame);
283364
}
284365

366+
static inline PyObject *
367+
_PyFrame_GetGlobals(_PyInterpreterFrame *frame) {
368+
if (frame->f_globals == NULL) {
369+
frame->f_globals = _PyFrame_GetFunction(frame)->func_globals;
370+
}
371+
return frame->f_globals;
372+
}
373+
374+
static inline PyObject *
375+
_PyFrame_GetBuiltins(_PyInterpreterFrame *frame) {
376+
if (frame->f_builtins == NULL) {
377+
frame->f_builtins = _PyFrame_GetFunction(frame)->func_builtins;
378+
}
379+
return frame->f_builtins;
380+
}
381+
285382
/* For use by _PyFrame_GetFrameObject
286383
Do not call directly. */
287384
PyAPI_FUNC(PyFrameObject *)
@@ -293,9 +390,8 @@ _PyFrame_MakeAndSetFrameObject(_PyInterpreterFrame *frame);
293390
static inline PyFrameObject *
294391
_PyFrame_GetFrameObject(_PyInterpreterFrame *frame)
295392
{
296-
297393
assert(!_PyFrame_IsIncomplete(frame));
298-
PyFrameObject *res = frame->frame_obj;
394+
PyFrameObject *res = frame->frame_obj;
299395
if (res != NULL) {
300396
return res;
301397
}
@@ -314,7 +410,7 @@ _PyFrame_ClearLocals(_PyInterpreterFrame *frame);
314410
* take should be set to 1 for heap allocated
315411
* frames like the ones in generators and coroutines.
316412
*/
317-
void
413+
PyAPI_FUNC(void)
318414
_PyFrame_ClearExceptCode(_PyInterpreterFrame * frame);
319415

320416
int
@@ -363,7 +459,7 @@ _PyFrame_PushUnchecked(PyThreadState *tstate, _PyStackRef func, int null_locals_
363459
/* Pushes a trampoline frame without checking for space.
364460
* Must be guarded by _PyThreadState_HasStackSpace() */
365461
static inline _PyInterpreterFrame *
366-
_PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int stackdepth, _PyInterpreterFrame * previous)
462+
_PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int stackdepth, _PyInterpreterFrame *previous)
367463
{
368464
CALL_STAT_INC(frames_pushed);
369465
_PyInterpreterFrame *frame = (_PyInterpreterFrame *)tstate->datastack_top;

Include/internal/pycore_interpframe_structs.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ struct _PyAsyncGenObject {
8585
_PyGenObject_HEAD(ag)
8686
};
8787

88+
typedef void (*_PyFrame_Reifier)(struct _PyInterpreterFrame *, PyObject *reifier);
89+
90+
typedef struct {
91+
PyObject_HEAD
92+
PyCodeObject *ef_code;
93+
PyObject *ef_state;
94+
_PyFrame_Reifier ef_reifier;
95+
} PyUnstable_PyExternalExecutable;
96+
8897
#undef _PyGenObject_HEAD
8998

9099

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
class Test_Pep523AllowSpecialization(unittest.TestCase):
28742930
"""Tests for _PyInterpreterState_SetEvalFrameFunc with

0 commit comments

Comments
 (0)