Files
2026-07-13 13:39:38 +08:00

720 lines
21 KiB
C

#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#if PY_MAJOR_VERSION != 3 || (PY_MINOR_VERSION < 10 || PY_MINOR_VERSION > 14)
#error Python 3.10-3.14 is required
#endif
/* only for internal headers */
#define Py_BUILD_CORE
#include "internal/pycore_code.h"
#if PY_MINOR_VERSION == 14
#include "internal/pycore_interpframe_structs.h"
#include "internal/pycore_stackref.h"
#endif
#if PY_MINOR_VERSION >= 11
#include "internal/pycore_frame.h"
#endif
#undef Py_BUILD_CORE
// This is a redefinition of the private PyTryBlock from <= 3.10.
// https://github.com/python/cpython/blob/3.8/Include/frameobject.h#L10
// https://github.com/python/cpython/blob/3.9/Include/cpython/frameobject.h#L11
// https://github.com/python/cpython/blob/3.10/Include/cpython/frameobject.h#L22
typedef struct {
int b_type;
int b_handler;
int b_level;
} PyTryBlock;
#if PY_MINOR_VERSION >= 11
typedef _PyInterpreterFrame Frame;
#elif PY_MINOR_VERSION == 10
typedef signed char PyFrameState;
// https://github.com/python/cpython/blob/3.10/Include/cpython/frameobject.h#L28
struct _frame {
PyObject_VAR_HEAD struct _frame *f_back; /* previous frame, or NULL */
PyCodeObject *f_code; /* code segment */
PyObject *f_builtins; /* builtin symbol table (PyDictObject) */
PyObject *f_globals; /* global symbol table (PyDictObject) */
PyObject *f_locals; /* local symbol table (any mapping) */
PyObject **f_valuestack; /* points after the last local */
PyObject *f_trace; /* Trace function */
int f_stackdepth; /* Depth of value stack */
char f_trace_lines; /* Emit per-line trace events? */
char f_trace_opcodes; /* Emit per-opcode trace events? */
/* Borrowed reference to a generator, or NULL */
PyObject *f_gen;
int f_lasti; /* Last instruction if called */
int f_lineno; /* Current line number. Only valid if non-zero */
int f_iblock; /* index in f_blockstack */
PyFrameState f_state; /* What state the frame is in */
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
};
typedef struct _frame Frame;
enum _framestate {
FRAME_CREATED = -2,
FRAME_SUSPENDED = -1,
FRAME_EXECUTING = 0,
FRAME_RETURNED = 1,
FRAME_UNWINDING = 2,
FRAME_RAISED = 3,
FRAME_CLEARED = 4
};
#endif
// This is a redefinition of the private PyCoroWrapper from 3.8-3.13.
// https://github.com/python/cpython/blob/3.8/Objects/genobject.c#L840
// https://github.com/python/cpython/blob/3.9/Objects/genobject.c#L830
// https://github.com/python/cpython/blob/3.10/Objects/genobject.c#L884
// https://github.com/python/cpython/blob/3.11/Objects/genobject.c#L1016
// https://github.com/python/cpython/blob/3.12/Objects/genobject.c#L1003
// https://github.com/python/cpython/blob/v3.13.0a5/Objects/genobject.c#L985
typedef struct {
PyObject_HEAD PyCoroObject *cw_coroutine;
} PyCoroWrapper;
#if PY_MINOR_VERSION <= 10
static int get_frame_iblock_limit(Frame *frame) { return CO_MAXBLOCKS; }
static int get_frame_iblock(Frame *frame) { return frame->f_iblock; }
static void set_frame_iblock(Frame *frame, int iblock) {
assert(iblock >= 0 && iblock < get_frame_iblock_limit(frame));
frame->f_iblock = iblock;
}
static PyTryBlock *get_frame_blockstack(Frame *frame) {
PyTryBlock *blockstack = frame->f_blockstack;
assert(blockstack);
return blockstack;
}
#else
// iblock not applicable for pythom >= 3.11
static int get_frame_iblock_limit(Frame *frame) { return 1; }
static int get_frame_iblock(Frame *frame) { return 0; }
static void set_frame_iblock(Frame *frame, int iblock) { assert(!iblock); }
static PyTryBlock *get_frame_blockstack(Frame *frame) { return NULL; }
#endif
static Frame *get_frame(PyGenObject *gen_like) {
#if PY_MINOR_VERSION >= 14
Frame *frame = (Frame *)(&gen_like->gi_iframe);
#elif PY_MINOR_VERSION >= 11
Frame *frame = (Frame *)(struct _PyInterpreterFrame *)(gen_like->gi_iframe);
#elif PY_MINOR_VERSION == 10
Frame *frame = (Frame *)(gen_like->gi_frame);
#endif
assert(frame);
return frame;
}
static PyCodeObject *get_frame_code(Frame *frame) {
#if PY_MINOR_VERSION >= 14
PyObject *executable = PyStackRef_AsPyObjectBorrow(frame->f_executable);
PyCodeObject *code = (PyCodeObject *)executable;
#elif PY_MINOR_VERSION >= 13
PyCodeObject *code = (PyCodeObject *)frame->f_executable;
#elif PY_MINOR_VERSION >= 10
PyCodeObject *code = (PyCodeObject *)frame->f_code;
#endif
assert(code);
return code;
}
static int get_frame_lasti(Frame *frame) {
PyCodeObject *code = get_frame_code(frame);
#if PY_MINOR_VERSION == 11 || PY_MINOR_VERSION == 12
assert(frame->prev_instr);
return (int)((intptr_t)frame->prev_instr - (intptr_t)_PyCode_CODE(code));
#elif PY_MINOR_VERSION == 10
return frame->f_lasti;
#else
assert(frame->instr_ptr);
return (int)((intptr_t)frame->instr_ptr - (intptr_t)_PyCode_CODE(code));
#endif
}
static void set_frame_lasti(Frame *frame, int lasti) {
PyCodeObject *code = get_frame_code(frame);
#if PY_MINOR_VERSION == 11 || PY_MINOR_VERSION == 12
frame->prev_instr =
(_Py_CODEUNIT *)((intptr_t)_PyCode_CODE(code) + (intptr_t)lasti);
#elif PY_MINOR_VERSION == 10
frame->f_lasti = lasti;
#else
frame->instr_ptr =
(_Py_CODEUNIT *)((intptr_t)_PyCode_CODE(code) + (intptr_t)lasti);
#endif
}
static int get_frame_state(PyGenObject *gen_like) {
#if PY_MINOR_VERSION == 10
Frame *frame = (Frame *)(gen_like->gi_frame);
if (!frame)
return FRAME_CLEARED;
return frame->f_state;
#else
return gen_like->gi_frame_state;
#endif
}
static void set_frame_state(PyGenObject *gen_like, int fs) {
#if PY_MINOR_VERSION == 10
Frame *frame = get_frame(gen_like);
frame->f_state = (PyFrameState)fs;
#else
gen_like->gi_frame_state = (int8_t)fs;
#endif
}
static int valid_frame_state(int fs) {
#if PY_MINOR_VERSION >= 13
return fs == FRAME_CREATED || fs == FRAME_SUSPENDED ||
fs == FRAME_EXECUTING || fs == FRAME_COMPLETED ||
fs == FRAME_CLEARED || fs == FRAME_SUSPENDED_YIELD_FROM;
#elif PY_MINOR_VERSION >= 11
return fs == FRAME_CREATED || fs == FRAME_SUSPENDED ||
fs == FRAME_EXECUTING || fs == FRAME_COMPLETED || fs == FRAME_CLEARED;
#elif PY_MINOR_VERSION == 10
return fs == FRAME_CREATED || fs == FRAME_SUSPENDED ||
fs == FRAME_EXECUTING || fs == FRAME_RETURNED ||
fs == FRAME_UNWINDING || fs == FRAME_RAISED || fs == FRAME_CLEARED;
#endif
}
static int get_frame_stacktop_limit(Frame *frame) {
PyCodeObject *code = get_frame_code(frame);
#if PY_MINOR_VERSION == 10
return code->co_stacksize + code->co_nlocals;
#else
return code->co_stacksize + code->co_nlocalsplus;
#endif
}
static int get_frame_stacktop(Frame *frame) {
#if PY_MINOR_VERSION >= 14
ptrdiff_t stacktop = frame->stackpointer - frame->localsplus;
assert(stacktop >= 0 && stacktop < get_frame_stacktop_limit(frame));
return (int)stacktop;
#elif PY_MINOR_VERSION >= 11
int stacktop = frame->stacktop;
assert(stacktop >= 0 && stacktop < get_frame_stacktop_limit(frame));
return stacktop;
#elif PY_MINOR_VERSION == 10
assert(frame->f_localsplus);
assert(frame->f_valuestack);
int stacktop =
(int)(frame->f_valuestack - frame->f_localsplus) + frame->f_stackdepth;
assert(stacktop >= 0 && stacktop < get_frame_stacktop_limit(frame));
return stacktop;
#endif
}
static void set_frame_stacktop(Frame *frame, int stacktop) {
#if PY_MINOR_VERSION >= 14
assert(stacktop >= 0 && stacktop < get_frame_stacktop_limit(frame));
frame->stackpointer = frame->localsplus + stacktop;
#elif PY_MINOR_VERSION >= 11
assert(stacktop >= 0 && stacktop < get_frame_stacktop_limit(frame));
frame->stacktop = stacktop;
#elif PY_MINOR_VERSION == 10
assert(stacktop >= 0 && stacktop < get_frame_stacktop_limit(frame));
assert(frame->f_localsplus);
assert(frame->f_valuestack);
int base = (int)(frame->f_valuestack - frame->f_localsplus);
assert(stacktop >= base);
frame->f_stackdepth = stacktop - base;
#endif
}
static const char *get_type_name(PyObject *obj) {
PyObject *type = PyObject_Type(obj);
if (!type) {
return NULL;
}
PyObject *name = PyObject_GetAttrString(type, "__name__");
Py_DECREF(type);
if (!name) {
return NULL;
}
const char *name_str = PyUnicode_AsUTF8(name);
Py_DECREF(name);
return name_str;
}
static PyGenObject *get_generator_like_object(PyObject *obj) {
if (PyGen_Check(obj) || PyCoro_CheckExact(obj) ||
PyAsyncGen_CheckExact(obj)) {
// Note: In Python 3.9-3.13, the PyGenObject, PyCoroObject and
// PyAsyncGenObject have the same layout, they just have different field
// prefixes (gi_, cr_, ag_). We cast to PyGenObject here so that the
// remainder of the code can use the gi_ prefix for all three cases.
return (PyGenObject *)obj;
}
// If the object isn't a PyGenObject, PyCoroObject or PyAsyncGenObject, it may
// still be a coroutine, for example a PyCoroWrapper. CPython unfortunately
// does not export a function that checks whether a PyObject is a
// PyCoroWrapper. We need to check the type name string.
const char *type_name = get_type_name(obj);
if (!type_name) {
return NULL;
}
if (strcmp(type_name, "coroutine_wrapper") == 0) {
// FIXME: improve safety here, e.g. by checking that the obj type matches a
// known size
PyCoroWrapper *wrapper = (PyCoroWrapper *)obj;
// Cast the inner PyCoroObject to a PyGenObject. See the comment above.
return (PyGenObject *)wrapper->cw_coroutine;
}
PyErr_SetString(PyExc_TypeError,
"Input object is not a generator or coroutine");
return NULL;
}
static PyObject *ext_get_frame_state(PyObject *self, PyObject *args) {
PyObject *arg;
if (!PyArg_ParseTuple(args, "O", &arg)) {
return NULL;
}
PyGenObject *gen_like = get_generator_like_object(arg);
if (!gen_like) {
return NULL;
}
int fs = get_frame_state(gen_like);
return PyLong_FromLong((long)fs);
}
static PyObject *ext_get_frame_ip(PyObject *self, PyObject *args) {
PyObject *obj;
if (!PyArg_ParseTuple(args, "O", &obj)) {
return NULL;
}
PyGenObject *gen_like = get_generator_like_object(obj);
if (!gen_like) {
return NULL;
}
if (get_frame_state(gen_like) >= FRAME_CLEARED) {
PyErr_SetString(PyExc_RuntimeError, "Cannot access cleared frame");
return NULL;
}
Frame *frame = get_frame(gen_like);
if (!frame) {
return NULL;
}
int ip = get_frame_lasti(frame);
return PyLong_FromLong((long)ip);
}
static PyObject *ext_get_frame_sp(PyObject *self, PyObject *args) {
PyObject *obj;
if (!PyArg_ParseTuple(args, "O", &obj)) {
return NULL;
}
PyGenObject *gen_like = get_generator_like_object(obj);
if (!gen_like) {
return NULL;
}
if (get_frame_state(gen_like) >= FRAME_CLEARED) {
PyErr_SetString(PyExc_RuntimeError, "Cannot access cleared frame");
return NULL;
}
Frame *frame = get_frame(gen_like);
if (!frame) {
return NULL;
}
int sp = get_frame_stacktop(frame);
return PyLong_FromLong((long)sp);
}
static PyObject *ext_get_frame_bp(PyObject *self, PyObject *args) {
PyObject *obj;
if (!PyArg_ParseTuple(args, "O", &obj)) {
return NULL;
}
PyGenObject *gen_like = get_generator_like_object(obj);
if (!gen_like) {
return NULL;
}
if (get_frame_state(gen_like) >= FRAME_CLEARED) {
PyErr_SetString(PyExc_RuntimeError, "Cannot access cleared frame");
return NULL;
}
Frame *frame = get_frame(gen_like);
if (!frame) {
return NULL;
}
int bp = get_frame_iblock(frame);
return PyLong_FromLong((long)bp);
}
static PyObject *ext_get_frame_stack_at(PyObject *self, PyObject *args) {
PyObject *obj;
int index;
if (!PyArg_ParseTuple(args, "Oi", &obj, &index)) {
return NULL;
}
PyGenObject *gen_like = get_generator_like_object(obj);
if (!gen_like) {
return NULL;
}
if (get_frame_state(gen_like) >= FRAME_CLEARED) {
PyErr_SetString(PyExc_RuntimeError, "Cannot access cleared frame");
return NULL;
}
Frame *frame = get_frame(gen_like);
if (!frame) {
return NULL;
}
int sp = get_frame_stacktop(frame);
if (index < 0 || index >= sp) {
PyErr_SetString(PyExc_IndexError, "Index out of bounds");
return NULL;
}
// NULL in C != None in Python. We need to preserve the fact that some items
// on the stack are NULL (not yet available).
PyObject *is_null = Py_False;
#if PY_MINOR_VERSION >= 14
PyObject *stack_obj = PyStackRef_AsPyObjectBorrow(frame->localsplus[index]);
#elif PY_MINOR_VERSION >= 11
PyObject *stack_obj = frame->localsplus[index];
#elif PY_MINOR_VERSION == 10
PyObject *stack_obj = frame->f_localsplus[index];
#endif
if (!stack_obj) {
is_null = Py_True;
stack_obj = Py_None;
}
return PyTuple_Pack(2, is_null, stack_obj);
}
static PyObject *ext_get_frame_block_at(PyObject *self, PyObject *args) {
PyObject *obj;
int index;
if (!PyArg_ParseTuple(args, "Oi", &obj, &index)) {
return NULL;
}
PyGenObject *gen_like = get_generator_like_object(obj);
if (!gen_like) {
return NULL;
}
if (get_frame_state(gen_like) >= FRAME_CLEARED) {
PyErr_SetString(PyExc_RuntimeError, "Cannot access cleared frame");
return NULL;
}
Frame *frame = get_frame(gen_like);
if (!frame) {
return NULL;
}
int bp = get_frame_iblock(frame);
if (index < 0 || index >= bp) {
PyErr_SetString(PyExc_IndexError, "Index out of bounds");
return NULL;
}
PyTryBlock *blockstack = get_frame_blockstack(frame);
PyTryBlock *block = &blockstack[index];
return Py_BuildValue("(iii)", block->b_type, block->b_handler,
block->b_level);
}
static PyObject *ext_set_frame_ip(PyObject *self, PyObject *args) {
PyObject *obj;
int ip;
if (!PyArg_ParseTuple(args, "Oi", &obj, &ip)) {
return NULL;
}
PyGenObject *gen_like = get_generator_like_object(obj);
if (!gen_like) {
return NULL;
}
if (get_frame_state(gen_like) >= FRAME_CLEARED) {
PyErr_SetString(PyExc_RuntimeError, "Cannot mutate cleared frame");
return NULL;
}
Frame *frame = get_frame(gen_like);
if (!frame) {
return NULL;
}
set_frame_lasti(frame, ip);
Py_RETURN_NONE;
}
static PyObject *ext_set_frame_sp(PyObject *self, PyObject *args) {
PyObject *obj;
int sp;
if (!PyArg_ParseTuple(args, "Oi", &obj, &sp)) {
return NULL;
}
PyGenObject *gen_like = get_generator_like_object(obj);
if (!gen_like) {
return NULL;
}
if (get_frame_state(gen_like) >= FRAME_CLEARED) {
PyErr_SetString(PyExc_RuntimeError, "Cannot mutate cleared frame");
return NULL;
}
Frame *frame = get_frame(gen_like);
if (!frame) {
return NULL;
}
int limit = get_frame_stacktop_limit(frame);
if (sp < 0 || sp >= limit) {
PyErr_SetString(PyExc_IndexError, "Stack pointer out of bounds");
return NULL;
}
int current_sp = get_frame_stacktop(frame);
#if PY_MINOR_VERSION >= 14
_PyStackRef *localsplus = frame->localsplus;
if (sp > current_sp) {
for (int i = current_sp; i < sp; i++) {
localsplus[i] = PyStackRef_NULL;
}
} else if (sp < current_sp) {
for (int i = sp; i < current_sp; i++) {
PyStackRef_XCLOSE(localsplus[i]);
localsplus[i] = PyStackRef_NULL;
}
}
#else
#if PY_MINOR_VERSION >= 11
PyObject **localsplus = frame->localsplus;
#elif PY_MINOR_VERSION == 10
PyObject **localsplus = frame->f_localsplus;
#endif
if (sp > current_sp) {
for (int i = current_sp; i < sp; i++) {
localsplus[i] = NULL;
}
} else if (sp < current_sp) {
for (int i = sp; i < current_sp; i++) {
Py_XDECREF(localsplus[i]);
localsplus[i] = NULL;
}
}
#endif
set_frame_stacktop(frame, sp);
Py_RETURN_NONE;
}
static PyObject *ext_set_frame_bp(PyObject *self, PyObject *args) {
PyObject *obj;
int bp;
if (!PyArg_ParseTuple(args, "Oi", &obj, &bp)) {
return NULL;
}
PyGenObject *gen_like = get_generator_like_object(obj);
if (!gen_like) {
return NULL;
}
if (get_frame_state(gen_like) >= FRAME_CLEARED) {
PyErr_SetString(PyExc_RuntimeError, "Cannot mutate cleared frame");
return NULL;
}
Frame *frame = get_frame(gen_like);
if (!frame) {
return NULL;
}
int limit = get_frame_iblock_limit(frame);
if (bp < 0 || bp >= limit) {
PyErr_SetString(PyExc_IndexError, "Block pointer out of bounds");
return NULL;
}
set_frame_iblock(frame, bp);
Py_RETURN_NONE;
}
static PyObject *ext_set_frame_state(PyObject *self, PyObject *args) {
PyObject *obj;
int fs;
if (!PyArg_ParseTuple(args, "Oi", &obj, &fs)) {
return NULL;
}
if (fs == FRAME_CLEARED) {
PyErr_SetString(PyExc_RuntimeError,
"Cannot set frame state to FRAME_CLEARED");
return NULL;
}
PyGenObject *gen_like = get_generator_like_object(obj);
if (!gen_like) {
return NULL;
}
if (get_frame_state(gen_like) >= FRAME_CLEARED) {
PyErr_SetString(PyExc_RuntimeError, "Cannot mutate cleared frame");
return NULL;
}
Frame *frame = get_frame(gen_like);
if (!frame) {
return NULL;
}
if (!valid_frame_state(fs)) {
PyErr_SetString(PyExc_ValueError, "Invalid frame state");
return NULL;
}
set_frame_state(gen_like, fs);
Py_RETURN_NONE;
}
static PyObject *ext_set_frame_stack_at(PyObject *self, PyObject *args) {
PyObject *obj;
int index;
PyObject *unset;
PyObject *stack_obj;
if (!PyArg_ParseTuple(args, "OiOO", &obj, &index, &unset, &stack_obj)) {
return NULL;
}
if (!PyBool_Check(unset)) {
PyErr_SetString(
PyExc_TypeError,
"Expected a boolean indicating whether to unset the stack object");
return NULL;
}
PyGenObject *gen_like = get_generator_like_object(obj);
if (!gen_like) {
return NULL;
}
if (get_frame_state(gen_like) >= FRAME_CLEARED) {
PyErr_SetString(PyExc_RuntimeError, "Cannot mutate cleared frame");
return NULL;
}
Frame *frame = get_frame(gen_like);
if (!frame) {
return NULL;
}
int sp = get_frame_stacktop(frame);
if (index < 0 || index >= sp) {
PyErr_SetString(PyExc_IndexError, "Index out of bounds");
return NULL;
}
#if PY_MINOR_VERSION >= 14
_PyStackRef *localsplus = frame->localsplus;
PyStackRef_XCLOSE(localsplus[index]);
if (PyObject_IsTrue(unset)) {
localsplus[index] = PyStackRef_NULL;
} else {
Py_INCREF(stack_obj);
localsplus[index] = PyStackRef_FromPyObjectSteal(stack_obj);
}
#else
#if PY_MINOR_VERSION >= 11
PyObject **localsplus = frame->localsplus;
#elif PY_MINOR_VERSION == 10
PyObject **localsplus = frame->f_localsplus;
#endif
PyObject *prev = localsplus[index];
if (PyObject_IsTrue(unset)) {
localsplus[index] = NULL;
} else {
Py_INCREF(stack_obj);
localsplus[index] = stack_obj;
}
Py_XDECREF(prev);
#endif
Py_RETURN_NONE;
}
static PyObject *ext_set_frame_block_at(PyObject *self, PyObject *args) {
PyObject *obj;
int index;
int b_type;
int b_handler;
int b_level;
if (!PyArg_ParseTuple(args, "Oi(iii)", &obj, &index, &b_type, &b_handler,
&b_level)) {
return NULL;
}
PyGenObject *gen_like = get_generator_like_object(obj);
if (!gen_like) {
return NULL;
}
if (get_frame_state(gen_like) >= FRAME_CLEARED) {
PyErr_SetString(PyExc_RuntimeError, "Cannot mutate cleared frame");
return NULL;
}
Frame *frame = get_frame(gen_like);
if (!frame) {
return NULL;
}
int bp = get_frame_iblock(frame);
if (index < 0 || index >= bp) {
PyErr_SetString(PyExc_IndexError, "Index out of bounds");
return NULL;
}
PyTryBlock *blockstack = get_frame_blockstack(frame);
PyTryBlock *block = &blockstack[index];
block->b_type = b_type;
block->b_handler = b_handler;
block->b_level = b_level;
Py_RETURN_NONE;
}
static PyMethodDef methods[] = {
{"get_frame_ip", ext_get_frame_ip, METH_VARARGS,
"Get instruction pointer of a generator or coroutine."},
{"set_frame_ip", ext_set_frame_ip, METH_VARARGS,
"Set instruction pointer of a generator or coroutine."},
{"get_frame_sp", ext_get_frame_sp, METH_VARARGS,
"Get stack pointer of a generator or coroutine."},
{"set_frame_sp", ext_set_frame_sp, METH_VARARGS,
"Set stack pointer of a generator or coroutine."},
{"get_frame_bp", ext_get_frame_bp, METH_VARARGS,
"Get block pointer of a generator or coroutine."},
{"set_frame_bp", ext_set_frame_bp, METH_VARARGS,
"Set block pointer of a generator or coroutine."},
{"get_frame_stack_at", ext_get_frame_stack_at, METH_VARARGS,
"Get an object from a generator or coroutine's stack, as an (is_null, "
"obj) tuple."},
{"set_frame_stack_at", ext_set_frame_stack_at, METH_VARARGS,
"Set or unset an object on the stack of a generator or coroutine."},
{"get_frame_block_at", ext_get_frame_block_at, METH_VARARGS,
"Get a block from a generator or coroutine."},
{"set_frame_block_at", ext_set_frame_block_at, METH_VARARGS,
"Restore a block of a generator or coroutine."},
{"get_frame_state", ext_get_frame_state, METH_VARARGS,
"Get frame state of a generator or coroutine."},
{"set_frame_state", ext_set_frame_state, METH_VARARGS,
"Set frame state of a generator or coroutine."},
{NULL, NULL, 0, NULL}};
static struct PyModuleDef module = {PyModuleDef_HEAD_INIT, "lk_durable", NULL,
-1, methods};
PyMODINIT_FUNC PyInit_lk_durable(void) { return PyModule_Create(&module); }