Portable usage of PyFrameObject

Anselm Kruis edited this page Aug 17, 2018 · 2 revisions

Stackless Python is API and ABI compatible with corresponding versions of C-Python, but undocumented implementation details are not compatible. One important difference is the C structure PyFrameObject. In Stackless, it contains one additional field and the order of fields differs slightly. Some extension modules and Python compilers need to set up frames. Here is a demo how to access the fields of PyFrameObject in a portable way. It is a minimal, Python extension module.

#include <Python.h>

#include <stdio.h>
#include <stddef.h>  
#include <frameobject.h>


/* back port from Python 3 */
#ifndef Py_BUILD_ASSERT_EXPR
/* Assert a build-time dependency, as an expression.

   Your compile will fail if the condition isn't true, or can't be evaluated
   by the compiler. This can be used in an expression: its value is 0.

   Example:

   #define foo_to_char(foo)  \
       ((char *)(foo)        \
        + Py_BUILD_ASSERT_EXPR(offsetof(struct foo, string) == 0))

   Written by Rusty Russell, public domain, http://ccodearchive.net/ */
#define Py_BUILD_ASSERT_EXPR(cond) \
    (sizeof(char [1 - 2*!(cond)]) - 1)
#endif

#ifndef Py_MEMBER_SIZE
/* Get the size of a structure member in bytes */
#define Py_MEMBER_SIZE(type, member) sizeof(((type *)0)->member)
#endif

/* A definition from Stackless Python */
#ifndef Py_TPFLAGS_HAVE_STACKLESS_CALL
#define Py_TPFLAGS_HAVE_STACKLESS_CALL (1UL<<15)
#endif

/* A portable test, that does not initialize the Stackless subsystem. 
 */
#define IS_STACKLESS_PYTHON() PyType_HasFeature(&PyFunction_Type, Py_TPFLAGS_HAVE_STACKLESS_CALL)


/* The offset of the PyFrameObject fields "f_code" and "f_localsplus" differs between 
 * C-Python and Stackless Python. Therefore the offset has to be computed at run-time.
 */
#define PYFRAME_LOCALSPLUS_OFFSET \
  ((void)Py_BUILD_ASSERT_EXPR(sizeof(PyFrameObject) == offsetof(PyFrameObject, f_localsplus) + Py_MEMBER_SIZE(PyFrameObject, f_localsplus)), \
   (PyFrame_Type.tp_basicsize - Py_MEMBER_SIZE(PyFrameObject, f_localsplus)))
#define PYFRAME_CODE_OFFSET \
  ((void)Py_BUILD_ASSERT_EXPR(sizeof(PyFrameObject) == offsetof(PyFrameObject, f_localsplus) + Py_MEMBER_SIZE(PyFrameObject, f_localsplus)), \
   (IS_STACKLESS_PYTHON() ? (PyFrame_Type.tp_basicsize - Py_MEMBER_SIZE(PyFrameObject, f_localsplus) - Py_MEMBER_SIZE(PyFrameObject, f_code)) : \
    (offsetof(PyFrameObject, f_back) + Py_MEMBER_SIZE(PyFrameObject, f_back))))

/* Portable (between C-Python and Stackless) macros for PyFrameObject */
#define PYFRAME_BACK(frame)          ((frame)->f_back)
#define PYFRAME_CODE(frame)          (*((PyCodeObject **)(((char *)(frame)) + PYFRAME_CODE_OFFSET)))
#define PYFRAME_BUILTINS(frame)      ((frame)->f_builtins)
#define PYFRAME_GLOBALS(frame)       ((frame)->f_globals)
#define PYFRAME_LOCALS(frame)        ((frame)->f_locals)
#define PYFRAME_VALUESTACK(frame)    ((frame)->f_valuestack)
#define PYFRAME_STACKTOP(frame)      ((frame)->f_stacktop)
#define PYFRAME_TRACE(frame)         ((frame)->f_trace)
#define PYFRAME_TRACE_LINES(frame)   ((frame)->f_trace_opcodes)
#define PYFRAME_TRACE_OPCODES(frame) ((frame)->f_trace_lines)
#define PYFRAME_GEN(frame)           ((frame)->f_gen)
#define PYFRAME_EXC_TYPE(frame)      ((frame)->f_exc_type)
#define PYFRAME_EXC_VALUE(frame)     ((frame)->f_exc_value)
#define PYFRAME_EXC_TRACEBACK(frame) ((frame)->f_exc_traceback)
#define PYFRAME_TSTATE(frame)        ((frame)->f_tstate)
#define PYFRAME_LASTI(frame)         ((frame)->f_lasti)
#define PYFRAME_LINENO(frame)        ((frame)->f_lineno)
#define PYFRAME_IBLOCK(frame)        ((frame)->f_iblock)
#define PYFRAME_EXECUTING(frame)     ((frame)->f_executing)
#define PYFRAME_BLOCKSTACK(frame)    ((frame)->f_blockstack)
#define PYFRAME_LOCALSPLUS(frame)    ((PyObject **)(((char *)(frame)) + PYFRAME_LOCALSPLUS_OFFSET))
#ifdef STACKLESS
#define PYFRAME_EXECUTE(frame)       ((frame)->f_execute)
#endif

/*****************************************************
 * Minimal extension module code below this line
 *****************************************************/

#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef framedemomodule = {
    PyModuleDef_HEAD_INIT,
    "framedemo",   /* name of module */
    NULL,          /* module documentation, may be NULL */
    -1,            /* size of per-interpreter state of the module,
                      or -1 if the module keeps state in global variables. */
};

PyMODINIT_FUNC
PyInit_framedemo(void)
#else
void
initframedemo(void)
#endif
{
    printf("offsetof(PyFrame_Object, f_localsplus) = %zu\n", PYFRAME_LOCALSPLUS_OFFSET);
    printf("offsetof(PyFrame_Object, f_code) = %zu\n", PYFRAME_CODE_OFFSET);

#if PY_MAJOR_VERSION >= 3
    return PyModule_Create(&framedemomodule);
#else
    Py_InitModule("framedemo", NULL);
#endif
}

To build this module, copy the following lines to "setup.py"

from distutils.core import setup, Extension
module1 = Extension('framedemo', sources = ['framedemomodule.c'])
setup (name = 'FrameDemo',
       version = '0.0',
       description = 'This demo shows how to access a PyFrameObject',
       ext_modules = [module1])

and run python setup.py build_ext.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.