Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Portable usage of PyFrameObject

Anselm Kruis edited this page Aug 8, 2021 · 3 revisions

Stackless Python is API and ABI compatible with corresponding versions of C-Python, but undocumented implementation details are not always compatible.

Starting with version 3.8 Stackless-Python uses the same C structure PyFrameObject as regular C-Python (See issue 270). Therefore the following documentation applies to Stackless Python up and including version 3.7 only.

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.

Clone this wiki locally