[Python-checkins] bpo-44590: Lazily allocate frame objects (GH-27077)

markshannon webhook-mailer at python.org
Mon Jul 26 06:22:29 EDT 2021


https://github.com/python/cpython/commit/ae0a2b756255629140efcbe57fc2e714f0267aa3
commit: ae0a2b756255629140efcbe57fc2e714f0267aa3
branch: main
author: Mark Shannon <mark at hotpy.org>
committer: markshannon <mark at hotpy.org>
date: 2021-07-26T11:22:16+01:00
summary:

bpo-44590: Lazily allocate frame objects (GH-27077)

* Convert "specials" array to InterpreterFrame struct, adding f_lasti, f_state and other non-debug FrameObject fields to it.

* Refactor, calls pushing the call to the interpreter upward toward _PyEval_Vector.

* Compute f_back when on thread stack, only filling in value when frame object outlives stack invocation.

* Move ownership of InterpreterFrame in generator from frame object to generator object.

* Do not create frame objects for Python calls.

* Do not create frame objects for generators.

files:
A Misc/NEWS.d/next/Core and Builtins/2021-07-09-12-08-17.bpo-44590.a2ntVX.rst
A Python/frame.c
M Include/cpython/ceval.h
M Include/cpython/frameobject.h
M Include/cpython/pystate.h
M Include/genobject.h
M Include/internal/pycore_ceval.h
M Include/internal/pycore_frame.h
M Include/internal/pycore_pystate.h
M Lib/test/test_faulthandler.py
M Lib/test/test_sys.py
M Makefile.pre.in
M Modules/_tracemalloc.c
M Modules/_xxsubinterpretersmodule.c
M Modules/signalmodule.c
M Objects/frameobject.c
M Objects/genobject.c
M Objects/typeobject.c
M PCbuild/pythoncore.vcxproj
M Python/_warnings.c
M Python/ceval.c
M Python/errors.c
M Python/pystate.c
M Python/suggestions.c
M Python/sysmodule.c
M Python/traceback.c
M Tools/gdb/libpython.py

diff --git a/Include/cpython/ceval.h b/Include/cpython/ceval.h
index 06338928f6738b..44b78f6d223120 100644
--- a/Include/cpython/ceval.h
+++ b/Include/cpython/ceval.h
@@ -19,7 +19,7 @@ PyAPI_FUNC(PyObject *) _PyEval_GetBuiltinId(_Py_Identifier *);
    flag was set, else return 0. */
 PyAPI_FUNC(int) PyEval_MergeCompilerFlags(PyCompilerFlags *cf);
 
-PyAPI_FUNC(PyObject *) _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int exc);
+PyAPI_FUNC(PyObject *) _PyEval_EvalFrameDefault(PyThreadState *tstate, struct _interpreter_frame *f, int exc);
 
 PyAPI_FUNC(void) _PyEval_SetSwitchInterval(unsigned long microseconds);
 PyAPI_FUNC(unsigned long) _PyEval_GetSwitchInterval(void);
diff --git a/Include/cpython/frameobject.h b/Include/cpython/frameobject.h
index 2bf458cab35451..e4cfac518bb58c 100644
--- a/Include/cpython/frameobject.h
+++ b/Include/cpython/frameobject.h
@@ -4,50 +4,17 @@
 #  error "this header file must not be included directly"
 #endif
 
-/* These values are chosen so that the inline functions below all
- * compare f_state to zero.
- */
-enum _framestate {
-    FRAME_CREATED = -2,
-    FRAME_SUSPENDED = -1,
-    FRAME_EXECUTING = 0,
-    FRAME_RETURNED = 1,
-    FRAME_UNWINDING = 2,
-    FRAME_RAISED = 3,
-    FRAME_CLEARED = 4
-};
-
-typedef signed char PyFrameState;
-
 struct _frame {
     PyObject_HEAD
     struct _frame *f_back;      /* previous frame, or NULL */
-    PyObject **f_valuestack;    /* points after the last local */
+    struct _interpreter_frame *f_frame; /* points to the frame data */
     PyObject *f_trace;          /* Trace function */
-    /* Borrowed reference to a generator, or NULL */
-    PyObject *f_gen;
-    int f_stackdepth;           /* Depth of value stack */
-    int f_lasti;                /* Last instruction if called */
     int f_lineno;               /* Current line number. Only valid if non-zero */
-    PyFrameState f_state;       /* What state the frame is in */
     char f_trace_lines;         /* Emit per-line trace events? */
     char f_trace_opcodes;       /* Emit per-opcode trace events? */
     char f_own_locals_memory;   /* This frame owns the memory for the locals */
-    PyObject **f_localsptr;     /* Pointer to locals, cells, free */
 };
 
-static inline int _PyFrame_IsRunnable(struct _frame *f) {
-    return f->f_state < FRAME_EXECUTING;
-}
-
-static inline int _PyFrame_IsExecuting(struct _frame *f) {
-    return f->f_state == FRAME_EXECUTING;
-}
-
-static inline int _PyFrameHasCompleted(struct _frame *f) {
-    return f->f_state > FRAME_EXECUTING;
-}
-
 /* Standard object interface */
 
 PyAPI_DATA(PyTypeObject) PyFrame_Type;
@@ -59,7 +26,7 @@ PyAPI_FUNC(PyFrameObject *) PyFrame_New(PyThreadState *, PyCodeObject *,
 
 /* only internal use */
 PyFrameObject*
-_PyFrame_New_NoTrack(PyThreadState *, PyFrameConstructor *, PyObject *, PyObject **);
+_PyFrame_New_NoTrack(struct _interpreter_frame *, int);
 
 
 /* The rest of the interface is specific for frame objects */
diff --git a/Include/cpython/pystate.h b/Include/cpython/pystate.h
index 56ea8d03453c10..ab4bf8bf8483c7 100644
--- a/Include/cpython/pystate.h
+++ b/Include/cpython/pystate.h
@@ -78,7 +78,7 @@ struct _ts {
     PyInterpreterState *interp;
 
     /* Borrowed reference to the current frame (it can be NULL) */
-    PyFrameObject *frame;
+    struct _interpreter_frame *frame;
     int recursion_depth;
     int recursion_headroom; /* Allow 50 more calls to handle any errors. */
     int stackcheck_counter;
@@ -223,7 +223,7 @@ PyAPI_FUNC(void) PyThreadState_DeleteCurrent(void);
 
 /* Frame evaluation API */
 
-typedef PyObject* (*_PyFrameEvalFunction)(PyThreadState *tstate, PyFrameObject *, int);
+typedef PyObject* (*_PyFrameEvalFunction)(PyThreadState *tstate, struct _interpreter_frame *, int);
 
 PyAPI_FUNC(_PyFrameEvalFunction) _PyInterpreterState_GetEvalFrameFunc(
     PyInterpreterState *interp);
diff --git a/Include/genobject.h b/Include/genobject.h
index 094d4e14fbe7cf..55a8b34afd60ed 100644
--- a/Include/genobject.h
+++ b/Include/genobject.h
@@ -16,9 +16,9 @@ extern "C" {
 #define _PyGenObject_HEAD(prefix)                                           \
     PyObject_HEAD                                                           \
     /* Note: gi_frame can be NULL if the generator is "finished" */         \
-    PyFrameObject *prefix##_frame;                                          \
+    struct _interpreter_frame *prefix##_xframe;                             \
     /* The code object backing the generator */                             \
-    PyCodeObject *prefix##_code;                                                \
+    PyCodeObject *prefix##_code;                                            \
     /* List of weak reference. */                                           \
     PyObject *prefix##_weakreflist;                                         \
     /* Name of the generator. */                                            \
diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h
index f573c3e5086807..66ddc991a9b11a 100644
--- a/Include/internal/pycore_ceval.h
+++ b/Include/internal/pycore_ceval.h
@@ -41,9 +41,9 @@ extern PyObject *_PyEval_BuiltinsFromGlobals(
 
 
 static inline PyObject*
-_PyEval_EvalFrame(PyThreadState *tstate, PyFrameObject *f, int throwflag)
+_PyEval_EvalFrame(PyThreadState *tstate, struct _interpreter_frame *frame, int throwflag)
 {
-    return tstate->interp->eval_frame(tstate, f, throwflag);
+    return tstate->interp->eval_frame(tstate, frame, throwflag);
 }
 
 extern PyObject *
@@ -107,6 +107,9 @@ static inline void _Py_LeaveRecursiveCall_inline(void)  {
 
 #define Py_LeaveRecursiveCall() _Py_LeaveRecursiveCall_inline()
 
+struct _interpreter_frame *_PyEval_GetFrame(void);
+
+PyObject *_Py_MakeCoro(PyFrameConstructor *, struct _interpreter_frame *);
 
 #ifdef __cplusplus
 }
diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h
index e30e3c89bfb62b..cd465d73bc6bdb 100644
--- a/Include/internal/pycore_frame.h
+++ b/Include/internal/pycore_frame.h
@@ -4,41 +4,123 @@
 extern "C" {
 #endif
 
-enum {
-    FRAME_SPECIALS_GLOBALS_OFFSET = 0,
-    FRAME_SPECIALS_BUILTINS_OFFSET = 1,
-    FRAME_SPECIALS_LOCALS_OFFSET = 2,
-    FRAME_SPECIALS_CODE_OFFSET = 3,
-    FRAME_SPECIALS_SIZE = 4
+/* These values are chosen so that the inline functions below all
+ * compare f_state to zero.
+ */
+enum _framestate {
+    FRAME_CREATED = -2,
+    FRAME_SUSPENDED = -1,
+    FRAME_EXECUTING = 0,
+    FRAME_RETURNED = 1,
+    FRAME_UNWINDING = 2,
+    FRAME_RAISED = 3,
+    FRAME_CLEARED = 4
 };
 
-static inline PyObject **
-_PyFrame_Specials(PyFrameObject *f) {
-    return &f->f_valuestack[-FRAME_SPECIALS_SIZE];
+typedef signed char PyFrameState;
+
+typedef struct _interpreter_frame {
+    PyObject *f_globals;
+    PyObject *f_builtins;
+    PyObject *f_locals;
+    PyCodeObject *f_code;
+    PyFrameObject *frame_obj;
+    /* Borrowed reference to a generator, or NULL */
+    PyObject *generator;
+    struct _interpreter_frame *previous;
+    int f_lasti;       /* Last instruction if called */
+    int stackdepth;  /* Depth of value stack */
+    int nlocalsplus;
+    PyFrameState f_state;       /* What state the frame is in */
+    PyObject *stack[1];
+} InterpreterFrame;
+
+static inline int _PyFrame_IsRunnable(InterpreterFrame *f) {
+    return f->f_state < FRAME_EXECUTING;
+}
+
+static inline int _PyFrame_IsExecuting(InterpreterFrame *f) {
+    return f->f_state == FRAME_EXECUTING;
 }
 
-/* Returns a *borrowed* reference. */
-static inline PyObject *
-_PyFrame_GetGlobals(PyFrameObject *f)
+static inline int _PyFrameHasCompleted(InterpreterFrame *f) {
+    return f->f_state > FRAME_EXECUTING;
+}
+
+#define FRAME_SPECIALS_SIZE ((sizeof(InterpreterFrame)-1)/sizeof(PyObject *))
+
+InterpreterFrame *
+_PyInterpreterFrame_HeapAlloc(PyFrameConstructor *con, PyObject *locals);
+
+static inline void
+_PyFrame_InitializeSpecials(
+    InterpreterFrame *frame, PyFrameConstructor *con,
+    PyObject *locals, int nlocalsplus)
 {
-    return _PyFrame_Specials(f)[FRAME_SPECIALS_GLOBALS_OFFSET];
+    frame->f_code = (PyCodeObject *)Py_NewRef(con->fc_code);
+    frame->f_builtins = Py_NewRef(con->fc_builtins);
+    frame->f_globals = Py_NewRef(con->fc_globals);
+    frame->f_locals = Py_XNewRef(locals);
+    frame->nlocalsplus = nlocalsplus;
+    frame->stackdepth = 0;
+    frame->frame_obj = NULL;
+    frame->generator = NULL;
+    frame->f_lasti = -1;
+    frame->f_state = FRAME_CREATED;
 }
 
-/* Returns a *borrowed* reference. */
-static inline PyObject *
-_PyFrame_GetBuiltins(PyFrameObject *f)
+/* Gets the pointer to the locals array
+ * that precedes this frame.
+ */
+static inline PyObject**
+_PyFrame_GetLocalsArray(InterpreterFrame *frame)
 {
-    return _PyFrame_Specials(f)[FRAME_SPECIALS_BUILTINS_OFFSET];
+    return ((PyObject **)frame) - frame->nlocalsplus;
 }
 
-/* Returns a *borrowed* reference. */
-static inline PyCodeObject *
-_PyFrame_GetCode(PyFrameObject *f)
+/* For use by _PyFrame_GetFrameObject
+  Do not call directly. */
+PyFrameObject *
+_PyFrame_MakeAndSetFrameObject(InterpreterFrame *frame);
+
+/* Gets the PyFrameObject for this frame, lazily
+ * creating it if necessary.
+ * Returns a borrowed referennce */
+static inline PyFrameObject *
+_PyFrame_GetFrameObject(InterpreterFrame *frame)
 {
-    return (PyCodeObject *)_PyFrame_Specials(f)[FRAME_SPECIALS_CODE_OFFSET];
+    PyFrameObject *res =  frame->frame_obj;
+    if (res != NULL) {
+        return res;
+    }
+    return _PyFrame_MakeAndSetFrameObject(frame);
 }
 
-int _PyFrame_TakeLocals(PyFrameObject *f);
+/* Clears all references in the frame.
+ * If take is non-zero, then the InterpreterFrame frame
+ * may be transfered to the frame object it references
+ * instead of being cleared. Either way
+ * the caller no longer owns the references
+ * in the frame.
+ * take should  be set to 1 for heap allocated
+ * frames like the ones in generators and coroutines.
+ */
+int
+_PyFrame_Clear(InterpreterFrame * frame, int take);
+
+int
+_PyFrame_Traverse(InterpreterFrame *frame, visitproc visit, void *arg);
+
+int
+_PyFrame_FastToLocalsWithError(InterpreterFrame *frame);
+
+void
+_PyFrame_LocalsToFast(InterpreterFrame *frame, int clear);
+
+InterpreterFrame *_PyThreadState_PushFrame(
+    PyThreadState *tstate, PyFrameConstructor *con, PyObject *locals);
+
+void _PyThreadState_PopFrame(PyThreadState *tstate, InterpreterFrame *frame);
 
 #ifdef __cplusplus
 }
diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h
index 6601ce2f80bf7e..4b894f3eff4967 100644
--- a/Include/internal/pycore_pystate.h
+++ b/Include/internal/pycore_pystate.h
@@ -147,9 +147,6 @@ PyAPI_FUNC(int) _PyState_AddModule(
 
 PyAPI_FUNC(int) _PyOS_InterruptOccurred(PyThreadState *tstate);
 
-PyObject **_PyThreadState_PushLocals(PyThreadState *, int size);
-void _PyThreadState_PopLocals(PyThreadState *, PyObject **);
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/Lib/test/test_faulthandler.py b/Lib/test/test_faulthandler.py
index ee3f41a108a14c..de986a3cbea97a 100644
--- a/Lib/test/test_faulthandler.py
+++ b/Lib/test/test_faulthandler.py
@@ -56,6 +56,7 @@ def temporary_filename():
         os_helper.unlink(filename)
 
 class FaultHandlerTests(unittest.TestCase):
+
     def get_output(self, code, filename=None, fd=None):
         """
         Run the specified code in Python (in a new child process) and read the
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 7d9a36a8366a7d..dba4928ec261ac 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -1275,7 +1275,7 @@ class C(object): pass
         # frame
         import inspect
         x = inspect.currentframe()
-        check(x, size('4P3i4cP'))
+        check(x, size('3Pi3c'))
         # function
         def func(): pass
         check(func, size('14Pi'))
diff --git a/Makefile.pre.in b/Makefile.pre.in
index 5d48500f9e03ea..bf023a084dc478 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -350,6 +350,7 @@ PYTHON_OBJS=	\
 		Python/context.o \
 		Python/dynamic_annotations.o \
 		Python/errors.o \
+		Python/frame.o \
 		Python/frozenmain.o \
 		Python/future.o \
 		Python/getargs.o \
diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-07-09-12-08-17.bpo-44590.a2ntVX.rst b/Misc/NEWS.d/next/Core and Builtins/2021-07-09-12-08-17.bpo-44590.a2ntVX.rst
new file mode 100644
index 00000000000000..ed4d969a11ab43
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2021-07-09-12-08-17.bpo-44590.a2ntVX.rst	
@@ -0,0 +1,5 @@
+All necessary data for executing a Python function (local variables, stack,
+etc) is now kept in a per-thread stack. Frame objects are lazily allocated
+on demand. This increases performance by about 7% on the standard benchmark
+suite. Introspection and debugging are unaffected as frame objects are
+always available when needed. Patch by Mark Shannon.
diff --git a/Modules/_tracemalloc.c b/Modules/_tracemalloc.c
index 04f6c243b5ca4d..fc3d7f51ee29a1 100644
--- a/Modules/_tracemalloc.c
+++ b/Modules/_tracemalloc.c
@@ -3,7 +3,7 @@
 #include "pycore_pymem.h"         // _Py_tracemalloc_config
 #include "pycore_traceback.h"
 #include "pycore_hashtable.h"
-#include "frameobject.h"          // PyFrame_GetBack()
+#include <pycore_frame.h>
 
 #include "clinic/_tracemalloc.c.h"
 /*[clinic input]
@@ -299,18 +299,16 @@ hashtable_compare_traceback(const void *key1, const void *key2)
 
 
 static void
-tracemalloc_get_frame(PyFrameObject *pyframe, frame_t *frame)
+tracemalloc_get_frame(InterpreterFrame *pyframe, frame_t *frame)
 {
     frame->filename = unknown_filename;
-    int lineno = PyFrame_GetLineNumber(pyframe);
+    int lineno = PyCode_Addr2Line(pyframe->f_code, pyframe->f_lasti*2);
     if (lineno < 0) {
         lineno = 0;
     }
     frame->lineno = (unsigned int)lineno;
 
-    PyCodeObject *code = PyFrame_GetCode(pyframe);
-    PyObject *filename = code->co_filename;
-    Py_DECREF(code);
+    PyObject *filename = pyframe->f_code->co_filename;
 
     if (filename == NULL) {
 #ifdef TRACE_DEBUG
@@ -395,7 +393,7 @@ traceback_get_frames(traceback_t *traceback)
         return;
     }
 
-    PyFrameObject *pyframe = PyThreadState_GetFrame(tstate);
+    InterpreterFrame *pyframe = tstate->frame;
     for (; pyframe != NULL;) {
         if (traceback->nframe < _Py_tracemalloc_config.max_nframe) {
             tracemalloc_get_frame(pyframe, &traceback->frames[traceback->nframe]);
@@ -406,8 +404,7 @@ traceback_get_frames(traceback_t *traceback)
             traceback->total_nframe++;
         }
 
-        PyFrameObject *back = PyFrame_GetBack(pyframe);
-        Py_DECREF(pyframe);
+        InterpreterFrame *back = pyframe->previous;
         pyframe = back;
     }
 }
diff --git a/Modules/_xxsubinterpretersmodule.c b/Modules/_xxsubinterpretersmodule.c
index 9290255c66c3fa..11b55f7f0c7d1b 100644
--- a/Modules/_xxsubinterpretersmodule.c
+++ b/Modules/_xxsubinterpretersmodule.c
@@ -4,6 +4,7 @@
 
 #include "Python.h"
 #include "frameobject.h"
+#include "pycore_frame.h"
 #include "interpreteridobject.h"
 
 
@@ -1834,13 +1835,12 @@ _is_running(PyInterpreterState *interp)
     }
 
     assert(!PyErr_Occurred());
-    PyFrameObject *frame = PyThreadState_GetFrame(tstate);
+    InterpreterFrame *frame = tstate->frame;
     if (frame == NULL) {
         return 0;
     }
 
     int executing = _PyFrame_IsExecuting(frame);
-    Py_DECREF(frame);
 
     return executing;
 }
diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c
index a4eeec9807c919..243c8c82cdc647 100644
--- a/Modules/signalmodule.c
+++ b/Modules/signalmodule.c
@@ -7,6 +7,7 @@
 #include "pycore_atomic.h"        // _Py_atomic_int
 #include "pycore_call.h"          // _PyObject_Call()
 #include "pycore_ceval.h"         // _PyEval_SignalReceived()
+#include "pycore_frame.h"
 #include "pycore_moduleobject.h"  // _PyModule_GetState()
 #include "pycore_pyerrors.h"      // _PyErr_SetString()
 #include "pycore_pylifecycle.h"   // NSIG
@@ -1786,11 +1787,7 @@ _PyErr_CheckSignalsTstate(PyThreadState *tstate)
      */
     _Py_atomic_store(&is_tripped, 0);
 
-    PyObject *frame = (PyObject *)tstate->frame;
-    if (!frame) {
-        frame = Py_None;
-    }
-
+    InterpreterFrame *frame = tstate->frame;
     signal_state_t *state = &signal_global_state;
     for (int i = 1; i < NSIG; i++) {
         if (!_Py_atomic_load_relaxed(&Handlers[i].tripped)) {
@@ -1821,8 +1818,16 @@ _PyErr_CheckSignalsTstate(PyThreadState *tstate)
             PyErr_WriteUnraisable(Py_None);
             continue;
         }
-
-        PyObject *arglist = Py_BuildValue("(iO)", i, frame);
+        PyObject *arglist = NULL;
+        if (frame == NULL) {
+            arglist = Py_BuildValue("(iO)", i, Py_None);
+        }
+        else {
+            PyFrameObject *f = _PyFrame_GetFrameObject(frame);
+            if (f != NULL) {
+                arglist = Py_BuildValue("(iO)", i, f);
+            }
+        }
         PyObject *result;
         if (arglist) {
             result = _PyObject_Call(tstate, func, arglist, NULL);
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index 813ec561463ba5..d1b8048fceadf7 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -14,7 +14,6 @@
 #define OFF(x) offsetof(PyFrameObject, x)
 
 static PyMemberDef frame_memberlist[] = {
-    {"f_back",          T_OBJECT,       OFF(f_back),      READONLY},
     {"f_trace_lines",   T_BOOL,         OFF(f_trace_lines), 0},
     {"f_trace_opcodes", T_BOOL,         OFF(f_trace_opcodes), 0},
     {NULL}      /* Sentinel */
@@ -33,7 +32,7 @@ frame_getlocals(PyFrameObject *f, void *closure)
 {
     if (PyFrame_FastToLocalsWithError(f) < 0)
         return NULL;
-    PyObject *locals = _PyFrame_Specials(f)[FRAME_SPECIALS_LOCALS_OFFSET];
+    PyObject *locals = f->f_frame->f_locals;
     Py_INCREF(locals);
     return locals;
 }
@@ -46,7 +45,7 @@ PyFrame_GetLineNumber(PyFrameObject *f)
         return f->f_lineno;
     }
     else {
-        return PyCode_Addr2Line(_PyFrame_GetCode(f), f->f_lasti*2);
+        return PyCode_Addr2Line(f->f_frame->f_code, f->f_frame->f_lasti*2);
     }
 }
 
@@ -65,16 +64,16 @@ frame_getlineno(PyFrameObject *f, void *closure)
 static PyObject *
 frame_getlasti(PyFrameObject *f, void *closure)
 {
-    if (f->f_lasti < 0) {
+    if (f->f_frame->f_lasti < 0) {
         return PyLong_FromLong(-1);
     }
-    return PyLong_FromLong(f->f_lasti*2);
+    return PyLong_FromLong(f->f_frame->f_lasti*2);
 }
 
 static PyObject *
 frame_getglobals(PyFrameObject *f, void *closure)
 {
-    PyObject *globals = _PyFrame_GetGlobals(f);
+    PyObject *globals = f->f_frame->f_globals;
     if (globals == NULL) {
         globals = Py_None;
     }
@@ -85,7 +84,7 @@ frame_getglobals(PyFrameObject *f, void *closure)
 static PyObject *
 frame_getbuiltins(PyFrameObject *f, void *closure)
 {
-    PyObject *builtins = _PyFrame_GetBuiltins(f);
+    PyObject *builtins = f->f_frame->f_builtins;
     if (builtins == NULL) {
         builtins = Py_None;
     }
@@ -102,6 +101,16 @@ frame_getcode(PyFrameObject *f, void *closure)
     return (PyObject *)PyFrame_GetCode(f);
 }
 
+static PyObject *
+frame_getback(PyFrameObject *f, void *closure)
+{
+    PyObject *res = (PyObject *)PyFrame_GetBack(f);
+    if (res == NULL) {
+        Py_RETURN_NONE;
+    }
+    return res;
+}
+
 /* Given the index of the effective opcode,
    scan back to construct the oparg with EXTENDED_ARG */
 static unsigned int
@@ -388,9 +397,9 @@ first_line_not_before(int *lines, int len, int line)
 static void
 frame_stack_pop(PyFrameObject *f)
 {
-    assert(f->f_stackdepth > 0);
-    f->f_stackdepth--;
-    PyObject *v = f->f_valuestack[f->f_stackdepth];
+    assert(f->f_frame->stackdepth > 0);
+    f->f_frame->stackdepth--;
+    PyObject *v = f->f_frame->stack[f->f_frame->stackdepth];
     Py_DECREF(v);
 }
 
@@ -430,7 +439,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
      * In addition, jumps are forbidden when not tracing,
      * as this is a debugging feature.
      */
-    switch(f->f_state) {
+    switch(f->f_frame->f_state) {
         case FRAME_CREATED:
             PyErr_Format(PyExc_ValueError,
                      "can't jump from the 'call' trace event of a new frame");
@@ -472,7 +481,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
     }
     new_lineno = (int)l_new_lineno;
 
-    if (new_lineno < _PyFrame_GetCode(f)->co_firstlineno) {
+    if (new_lineno < f->f_frame->f_code->co_firstlineno) {
         PyErr_Format(PyExc_ValueError,
                     "line %d comes before the current code block",
                     new_lineno);
@@ -481,8 +490,8 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
 
     /* PyCode_NewWithPosOnlyArgs limits co_code to be under INT_MAX so this
      * should never overflow. */
-    int len = (int)(PyBytes_GET_SIZE(_PyFrame_GetCode(f)->co_code) / sizeof(_Py_CODEUNIT));
-    int *lines = marklines(_PyFrame_GetCode(f), len);
+    int len = (int)(PyBytes_GET_SIZE(f->f_frame->f_code->co_code) / sizeof(_Py_CODEUNIT));
+    int *lines = marklines(f->f_frame->f_code, len);
     if (lines == NULL) {
         return -1;
     }
@@ -496,7 +505,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
         return -1;
     }
 
-    int64_t *stacks = mark_stacks(_PyFrame_GetCode(f), len);
+    int64_t *stacks = mark_stacks(f->f_frame->f_code, len);
     if (stacks == NULL) {
         PyMem_Free(lines);
         return -1;
@@ -504,7 +513,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
 
     int64_t best_stack = OVERFLOWED;
     int best_addr = -1;
-    int64_t start_stack = stacks[f->f_lasti];
+    int64_t start_stack = stacks[f->f_frame->f_lasti];
     int err = -1;
     const char *msg = "cannot find bytecode for specified line";
     for (int i = 0; i < len; i++) {
@@ -538,7 +547,7 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
         return -1;
     }
     /* Unwind block stack. */
-    if (f->f_state == FRAME_SUSPENDED) {
+    if (f->f_frame->f_state == FRAME_SUSPENDED) {
         /* Account for value popped by yield */
         start_stack = pop_value(start_stack);
     }
@@ -546,9 +555,9 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
         frame_stack_pop(f);
         start_stack = pop_value(start_stack);
     }
-    /* Finally set the new f_lasti and return OK. */
+    /* Finally set the new lasti and return OK. */
     f->f_lineno = 0;
-    f->f_lasti = best_addr;
+    f->f_frame->f_lasti = best_addr;
     return 0;
 }
 
@@ -579,6 +588,7 @@ frame_settrace(PyFrameObject *f, PyObject* v, void *closure)
 
 
 static PyGetSetDef frame_getsetlist[] = {
+    {"f_back",          (getter)frame_getback, NULL, NULL},
     {"f_locals",        (getter)frame_getlocals, NULL, NULL},
     {"f_lineno",        (getter)frame_getlineno,
                     (setter)frame_setlineno, NULL},
@@ -612,29 +622,27 @@ frame_dealloc(PyFrameObject *f)
     Py_TRASHCAN_SAFE_BEGIN(f)
     PyCodeObject *co = NULL;
 
-    /* Kill all local variables including specials. */
-    if (f->f_localsptr) {
+    /* Kill all local variables including specials, if we own them */
+    if (f->f_own_locals_memory) {
+        f->f_own_locals_memory = 0;
+        InterpreterFrame *frame = f->f_frame;
         /* Don't clear code object until the end */
-        co = _PyFrame_GetCode(f);
-        PyObject **specials = _PyFrame_Specials(f);
-        Py_CLEAR(specials[FRAME_SPECIALS_GLOBALS_OFFSET]);
-        Py_CLEAR(specials[FRAME_SPECIALS_BUILTINS_OFFSET]);
-        Py_CLEAR(specials[FRAME_SPECIALS_LOCALS_OFFSET]);
+        co = frame->f_code;
+        frame->f_code = NULL;
+        Py_CLEAR(frame->f_globals);
+        Py_CLEAR(frame->f_builtins);
+        Py_CLEAR(frame->f_locals);
+        PyObject **locals = _PyFrame_GetLocalsArray(frame);
         for (int i = 0; i < co->co_nlocalsplus; i++) {
-            Py_CLEAR(f->f_localsptr[i]);
-        }
-        /* Free items on stack */
-        for (int i = 0; i < f->f_stackdepth; i++) {
-            Py_XDECREF(f->f_valuestack[i]);
+            Py_CLEAR(locals[i]);
         }
-        if (f->f_own_locals_memory) {
-            PyMem_Free(f->f_localsptr);
-            f->f_own_locals_memory = 0;
+        /* stack */
+        for (int i = 0; i < frame->stackdepth; i++) {
+            Py_CLEAR(frame->stack[i]);
         }
-        f->f_localsptr = NULL;
+        PyMem_Free(locals);
     }
-    f->f_stackdepth = 0;
-    Py_XDECREF(f->f_back);
+    Py_CLEAR(f->f_back);
     Py_CLEAR(f->f_trace);
     struct _Py_frame_state *state = get_frame_state();
 #ifdef Py_DEBUG
@@ -654,29 +662,16 @@ frame_dealloc(PyFrameObject *f)
     Py_TRASHCAN_SAFE_END(f)
 }
 
-static inline Py_ssize_t
-frame_nslots(PyFrameObject *frame)
-{
-    return frame->f_valuestack - frame->f_localsptr;
-}
-
 static int
 frame_traverse(PyFrameObject *f, visitproc visit, void *arg)
 {
     Py_VISIT(f->f_back);
     Py_VISIT(f->f_trace);
-
-    /* locals */
-    PyObject **localsplus = f->f_localsptr;
-    for (Py_ssize_t i = frame_nslots(f); --i >= 0; ++localsplus) {
-        Py_VISIT(*localsplus);
-    }
-
-    /* stack */
-    for (int i = 0; i < f->f_stackdepth; i++) {
-        Py_VISIT(f->f_valuestack[i]);
+    if (f->f_own_locals_memory == 0) {
+        return 0;
     }
-    return 0;
+    assert(f->f_frame->frame_obj == NULL);
+    return _PyFrame_Traverse(f->f_frame, visit, arg);
 }
 
 static int
@@ -687,34 +682,35 @@ frame_tp_clear(PyFrameObject *f)
      * frame may also point to this frame, believe itself to still be
      * active, and try cleaning up this frame again.
      */
-    f->f_state = FRAME_CLEARED;
+    f->f_frame->f_state = FRAME_CLEARED;
 
     Py_CLEAR(f->f_trace);
-    PyCodeObject *co = _PyFrame_GetCode(f);
+
     /* locals */
-    for (int i = 0; i < co->co_nlocalsplus; i++) {
-        Py_CLEAR(f->f_localsptr[i]);
+    PyObject **locals = _PyFrame_GetLocalsArray(f->f_frame);
+    for (int i = 0; i < f->f_frame->nlocalsplus; i++) {
+        Py_CLEAR(locals[i]);
     }
 
     /* stack */
-    for (int i = 0; i < f->f_stackdepth; i++) {
-        Py_CLEAR(f->f_valuestack[i]);
+    for (int i = 0; i < f->f_frame->stackdepth; i++) {
+        Py_CLEAR(f->f_frame->stack[i]);
     }
-    f->f_stackdepth = 0;
+    f->f_frame->stackdepth = 0;
     return 0;
 }
 
 static PyObject *
 frame_clear(PyFrameObject *f, PyObject *Py_UNUSED(ignored))
 {
-    if (_PyFrame_IsExecuting(f)) {
+    if (_PyFrame_IsExecuting(f->f_frame)) {
         PyErr_SetString(PyExc_RuntimeError,
                         "cannot clear an executing frame");
         return NULL;
     }
-    if (f->f_gen) {
-        _PyGen_Finalize(f->f_gen);
-        assert(f->f_gen == NULL);
+    if (f->f_frame->generator) {
+        _PyGen_Finalize(f->f_frame->generator);
+        assert(f->f_frame->generator == NULL);
     }
     (void)frame_tp_clear(f);
     Py_RETURN_NONE;
@@ -729,7 +725,7 @@ frame_sizeof(PyFrameObject *f, PyObject *Py_UNUSED(ignored))
     Py_ssize_t res;
     res = sizeof(PyFrameObject);
     if (f->f_own_locals_memory) {
-        PyCodeObject *code = _PyFrame_GetCode(f);
+        PyCodeObject *code = f->f_frame->f_code;
         res += (code->co_nlocalsplus+code->co_stacksize) * sizeof(PyObject *);
     }
     return PyLong_FromSsize_t(res);
@@ -742,7 +738,7 @@ static PyObject *
 frame_repr(PyFrameObject *f)
 {
     int lineno = PyFrame_GetLineNumber(f);
-    PyCodeObject *code = _PyFrame_GetCode(f);
+    PyCodeObject *code = f->f_frame->f_code;
     return PyUnicode_FromFormat(
         "<frame at %p, file %R, line %d, code %S>",
         f, code->co_filename, lineno, code->co_name);
@@ -793,33 +789,39 @@ PyTypeObject PyFrame_Type = {
 
 _Py_IDENTIFIER(__builtins__);
 
-static inline PyFrameObject*
-frame_alloc(PyCodeObject *code, PyObject **localsarray)
+static InterpreterFrame *
+allocate_heap_frame(PyFrameConstructor *con, PyObject *locals)
 {
-    int owns;
-    PyFrameObject *f;
+    PyCodeObject *code = (PyCodeObject *)con->fc_code;
+    int size = code->co_nlocalsplus+code->co_stacksize + FRAME_SPECIALS_SIZE;
+    PyObject **localsarray = PyMem_Malloc(sizeof(PyObject *)*size);
     if (localsarray == NULL) {
-        int size = code->co_nlocalsplus+code->co_stacksize + FRAME_SPECIALS_SIZE;
-        localsarray = PyMem_Malloc(sizeof(PyObject *)*size);
-        if (localsarray == NULL) {
-            PyErr_NoMemory();
-            return NULL;
-        }
-        for (Py_ssize_t i=0; i < code->co_nlocalsplus; i++) {
-            localsarray[i] = NULL;
-        }
-        owns = 1;
+        PyErr_NoMemory();
+        return NULL;
     }
-    else {
-        owns = 0;
+    for (Py_ssize_t i=0; i < code->co_nlocalsplus; i++) {
+        localsarray[i] = NULL;
     }
+    InterpreterFrame *frame = (InterpreterFrame *)(localsarray + code->co_nlocalsplus);
+    _PyFrame_InitializeSpecials(frame, con, locals, code->co_nlocalsplus);
+    return frame;
+}
+
+static inline PyFrameObject*
+frame_alloc(InterpreterFrame *frame, int owns)
+{
+    PyFrameObject *f;
     struct _Py_frame_state *state = get_frame_state();
     if (state->free_list == NULL)
     {
         f = PyObject_GC_New(PyFrameObject, &PyFrame_Type);
         if (f == NULL) {
             if (owns) {
-                PyMem_Free(localsarray);
+                Py_XDECREF(frame->f_code);
+                Py_XDECREF(frame->f_builtins);
+                Py_XDECREF(frame->f_globals);
+                Py_XDECREF(frame->f_locals);
+                PyMem_Free(frame);
             }
             return NULL;
         }
@@ -835,66 +837,23 @@ frame_alloc(PyCodeObject *code, PyObject **localsarray)
         state->free_list = state->free_list->f_back;
         _Py_NewReference((PyObject *)f);
     }
-    f->f_localsptr = localsarray;
+    f->f_frame = frame;
     f->f_own_locals_memory = owns;
     return f;
 }
 
-int
-_PyFrame_TakeLocals(PyFrameObject *f)
-{
-    assert(f->f_own_locals_memory == 0);
-    assert(f->f_stackdepth == 0);
-    Py_ssize_t size = frame_nslots(f);
-    PyObject **copy = PyMem_Malloc(sizeof(PyObject *)*size);
-    if (copy == NULL) {
-        for (int i = 0; i < size; i++) {
-            PyObject *o = f->f_localsptr[i];
-            Py_XDECREF(o);
-        }
-        PyErr_NoMemory();
-        return -1;
-    }
-    for (int i = 0; i < size; i++) {
-        PyObject *o = f->f_localsptr[i];
-        copy[i] = o;
-    }
-    f->f_own_locals_memory = 1;
-    f->f_localsptr = copy;
-    f->f_valuestack = copy + size;
-    return 0;
-}
-
 PyFrameObject* _Py_HOT_FUNCTION
-_PyFrame_New_NoTrack(PyThreadState *tstate, PyFrameConstructor *con, PyObject *locals, PyObject **localsarray)
+_PyFrame_New_NoTrack(InterpreterFrame *frame, int owns)
 {
-    assert(con != NULL);
-    assert(con->fc_globals != NULL);
-    assert(con->fc_builtins != NULL);
-    assert(con->fc_code != NULL);
-    assert(locals == NULL || PyMapping_Check(locals));
-    PyCodeObject *code = (PyCodeObject *)con->fc_code;
-
-    PyFrameObject *f = frame_alloc(code, localsarray);
+    PyFrameObject *f = frame_alloc(frame, owns);
     if (f == NULL) {
         return NULL;
     }
-
-    PyObject **specials = f->f_localsptr + code->co_nlocalsplus;
-    f->f_valuestack = specials + FRAME_SPECIALS_SIZE;
-    f->f_back = (PyFrameObject*)Py_XNewRef(tstate->frame);
-    specials[FRAME_SPECIALS_CODE_OFFSET] = Py_NewRef(con->fc_code);
-    specials[FRAME_SPECIALS_BUILTINS_OFFSET] = Py_NewRef(con->fc_builtins);
-    specials[FRAME_SPECIALS_GLOBALS_OFFSET] = Py_NewRef(con->fc_globals);
-    specials[FRAME_SPECIALS_LOCALS_OFFSET] = Py_XNewRef(locals);
+    f->f_back = NULL;
     f->f_trace = NULL;
-    f->f_stackdepth = 0;
     f->f_trace_lines = 1;
     f->f_trace_opcodes = 0;
-    f->f_gen = NULL;
-    f->f_lasti = -1;
     f->f_lineno = 0;
-    f->f_state = FRAME_CREATED;
     return f;
 }
 
@@ -917,7 +876,11 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
         .fc_kwdefaults = NULL,
         .fc_closure = NULL
     };
-    PyFrameObject *f = _PyFrame_New_NoTrack(tstate, &desc, locals, NULL);
+    InterpreterFrame *frame = allocate_heap_frame(&desc, locals);
+    if (frame == NULL) {
+        return NULL;
+    }
+    PyFrameObject *f = _PyFrame_New_NoTrack(frame, 1);
     if (f) {
         _PyObject_GC_TRACK(f);
     }
@@ -925,11 +888,11 @@ PyFrame_New(PyThreadState *tstate, PyCodeObject *code,
 }
 
 static int
-_PyFrame_OpAlreadyRan(PyFrameObject *f, int opcode, int oparg)
+_PyFrame_OpAlreadyRan(InterpreterFrame *frame, int opcode, int oparg)
 {
     const _Py_CODEUNIT *code =
-        (const _Py_CODEUNIT *)PyBytes_AS_STRING(_PyFrame_GetCode(f)->co_code);
-    for (int i = 0; i < f->f_lasti; i++) {
+        (const _Py_CODEUNIT *)PyBytes_AS_STRING(frame->f_code->co_code);
+    for (int i = 0; i < frame->f_lasti; i++) {
         if (_Py_OPCODE(code[i]) == opcode && _Py_OPARG(code[i]) == oparg) {
             return 1;
         }
@@ -938,25 +901,19 @@ _PyFrame_OpAlreadyRan(PyFrameObject *f, int opcode, int oparg)
 }
 
 int
-PyFrame_FastToLocalsWithError(PyFrameObject *f)
-{
+_PyFrame_FastToLocalsWithError(InterpreterFrame *frame) {
     /* Merge fast locals into f->f_locals */
     PyObject *locals;
     PyObject **fast;
     PyCodeObject *co;
-
-    if (f == NULL) {
-        PyErr_BadInternalCall();
-        return -1;
-    }
-    locals = _PyFrame_Specials(f)[FRAME_SPECIALS_LOCALS_OFFSET];
+    locals = frame->f_locals;
     if (locals == NULL) {
-        locals = _PyFrame_Specials(f)[FRAME_SPECIALS_LOCALS_OFFSET] = PyDict_New();
+        locals = frame->f_locals = PyDict_New();
         if (locals == NULL)
             return -1;
     }
-    co = _PyFrame_GetCode(f);
-    fast = f->f_localsptr;
+    co = frame->f_code;
+    fast = _PyFrame_GetLocalsArray(frame);
     for (int i = 0; i < co->co_nlocalsplus; i++) {
         _PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
 
@@ -974,9 +931,9 @@ PyFrame_FastToLocalsWithError(PyFrameObject *f)
 
         PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
         PyObject *value = fast[i];
-        if (f->f_state != FRAME_CLEARED) {
+        if (frame->f_state != FRAME_CLEARED) {
             if (kind & CO_FAST_FREE) {
-                // The cell was set by _PyEval_MakeFrameVector() from
+                // The cell was set when the frame was created from
                 // the function's closure.
                 assert(value != NULL && PyCell_Check(value));
                 value = PyCell_GET(value);
@@ -988,12 +945,12 @@ PyFrame_FastToLocalsWithError(PyFrameObject *f)
                 // run yet.
                 if (value != NULL) {
                     if (PyCell_Check(value) &&
-                            _PyFrame_OpAlreadyRan(f, MAKE_CELL, i)) {
+                            _PyFrame_OpAlreadyRan(frame, MAKE_CELL, i)) {
                         // (likely) MAKE_CELL must have executed already.
                         value = PyCell_GET(value);
                     }
                     // (likely) Otherwise it it is an arg (kind & CO_FAST_LOCAL),
-                    // with the initial value set by _PyEval_MakeFrameVector()...
+                    // with the initial value set when the frame was created...
                     // (unlikely) ...or it was set to some initial value by
                     // an earlier call to PyFrame_LocalsToFast().
                 }
@@ -1021,6 +978,16 @@ PyFrame_FastToLocalsWithError(PyFrameObject *f)
     return 0;
 }
 
+int
+PyFrame_FastToLocalsWithError(PyFrameObject *f)
+{
+    if (f == NULL) {
+        PyErr_BadInternalCall();
+        return -1;
+    }
+    return _PyFrame_FastToLocalsWithError(f->f_frame);
+}
+
 void
 PyFrame_FastToLocals(PyFrameObject *f)
 {
@@ -1034,21 +1001,18 @@ PyFrame_FastToLocals(PyFrameObject *f)
 }
 
 void
-PyFrame_LocalsToFast(PyFrameObject *f, int clear)
+_PyFrame_LocalsToFast(InterpreterFrame *frame, int clear)
 {
     /* Merge locals into fast locals */
     PyObject *locals;
     PyObject **fast;
     PyObject *error_type, *error_value, *error_traceback;
     PyCodeObject *co;
-    if (f == NULL || f->f_state == FRAME_CLEARED) {
-        return;
-    }
-    locals = _PyFrame_Specials(f)[FRAME_SPECIALS_LOCALS_OFFSET];
+    locals = frame->f_locals;
     if (locals == NULL)
         return;
-    fast = f->f_localsptr;
-    co = _PyFrame_GetCode(f);
+    fast = _PyFrame_GetLocalsArray(frame);
+    co = frame->f_code;
 
     PyErr_Fetch(&error_type, &error_value, &error_traceback);
     for (int i = 0; i < co->co_nlocalsplus; i++) {
@@ -1070,7 +1034,7 @@ PyFrame_LocalsToFast(PyFrameObject *f, int clear)
         PyObject *oldvalue = fast[i];
         PyObject *cell = NULL;
         if (kind == CO_FAST_FREE) {
-            // The cell was set by _PyEval_MakeFrameVector() from
+            // The cell was set when the frame was created from
             // the function's closure.
             assert(oldvalue != NULL && PyCell_Check(oldvalue));
             cell = oldvalue;
@@ -1078,7 +1042,7 @@ PyFrame_LocalsToFast(PyFrameObject *f, int clear)
         else if (kind & CO_FAST_CELL && oldvalue != NULL) {
             /* Same test as in PyFrame_FastToLocals() above. */
             if (PyCell_Check(oldvalue) &&
-                    _PyFrame_OpAlreadyRan(f, MAKE_CELL, i)) {
+                    _PyFrame_OpAlreadyRan(frame, MAKE_CELL, i)) {
                 // (likely) MAKE_CELL must have executed already.
                 cell = oldvalue;
             }
@@ -1102,6 +1066,15 @@ PyFrame_LocalsToFast(PyFrameObject *f, int clear)
     PyErr_Restore(error_type, error_value, error_traceback);
 }
 
+void
+PyFrame_LocalsToFast(PyFrameObject *f, int clear)
+{
+    if (f == NULL || f->f_frame->f_state == FRAME_CLEARED) {
+        return;
+    }
+    _PyFrame_LocalsToFast(f->f_frame, clear);
+}
+
 /* Clear out the free list */
 void
 _PyFrame_ClearFreeList(PyInterpreterState *interp)
@@ -1141,7 +1114,7 @@ PyCodeObject *
 PyFrame_GetCode(PyFrameObject *frame)
 {
     assert(frame != NULL);
-    PyCodeObject *code = _PyFrame_GetCode(frame);
+    PyCodeObject *code = frame->f_frame->f_code;
     assert(code != NULL);
     Py_INCREF(code);
     return code;
@@ -1153,6 +1126,9 @@ PyFrame_GetBack(PyFrameObject *frame)
 {
     assert(frame != NULL);
     PyFrameObject *back = frame->f_back;
+    if (back == NULL && frame->f_frame->previous != NULL) {
+        back = _PyFrame_GetFrameObject(frame->f_frame->previous);
+    }
     Py_XINCREF(back);
     return back;
 }
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 8cc965a51bb5e9..86cd9cf7254589 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -6,6 +6,7 @@
 #include "pycore_pyerrors.h"      // _PyErr_ClearExcState()
 #include "pycore_pystate.h"       // _PyThreadState_GET()
 #include "frameobject.h"
+#include "pycore_frame.h"
 #include "structmember.h"         // PyMemberDef
 #include "opcode.h"
 
@@ -31,10 +32,17 @@ exc_state_traverse(_PyErr_StackItem *exc_state, visitproc visit, void *arg)
 static int
 gen_traverse(PyGenObject *gen, visitproc visit, void *arg)
 {
-    Py_VISIT((PyObject *)gen->gi_frame);
     Py_VISIT(gen->gi_code);
     Py_VISIT(gen->gi_name);
     Py_VISIT(gen->gi_qualname);
+    InterpreterFrame *frame = gen->gi_xframe;
+    if (frame != NULL) {
+        assert(frame->frame_obj == NULL || frame->frame_obj->f_own_locals_memory == 0);
+        int err = _PyFrame_Traverse(frame, visit, arg);
+        if (err) {
+            return err;
+        }
+    }
     /* No need to visit cr_origin, because it's just tuples/str/int, so can't
        participate in a reference cycle. */
     return exc_state_traverse(&gen->gi_exc_state, visit, arg);
@@ -47,7 +55,7 @@ _PyGen_Finalize(PyObject *self)
     PyObject *res = NULL;
     PyObject *error_type, *error_value, *error_traceback;
 
-    if (gen->gi_frame == NULL ||  _PyFrameHasCompleted(gen->gi_frame)) {
+    if (gen->gi_xframe == NULL ||  _PyFrameHasCompleted(gen->gi_xframe)) {
         /* Generator isn't paused, so no need to close */
         return;
     }
@@ -79,7 +87,7 @@ _PyGen_Finalize(PyObject *self)
        issue a RuntimeWarning. */
     if (gen->gi_code != NULL &&
         ((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE &&
-        gen->gi_frame->f_lasti == -1)
+        gen->gi_xframe->f_lasti == -1)
     {
         _PyErr_WarnUnawaitedCoroutine((PyObject *)gen);
     }
@@ -122,9 +130,11 @@ gen_dealloc(PyGenObject *gen)
            and GC_Del. */
         Py_CLEAR(((PyAsyncGenObject*)gen)->ag_finalizer);
     }
-    if (gen->gi_frame != NULL) {
-        gen->gi_frame->f_gen = NULL;
-        Py_CLEAR(gen->gi_frame);
+    InterpreterFrame *frame = gen->gi_xframe;
+    if (frame != NULL) {
+        gen->gi_xframe = NULL;
+        frame->previous = NULL;
+        _PyFrame_Clear(frame, 1);
     }
     if (((PyCodeObject *)gen->gi_code)->co_flags & CO_COROUTINE) {
         Py_CLEAR(((PyCoroObject *)gen)->cr_origin);
@@ -141,11 +151,11 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
              int exc, int closing)
 {
     PyThreadState *tstate = _PyThreadState_GET();
-    PyFrameObject *f = gen->gi_frame;
+    InterpreterFrame *frame = gen->gi_xframe;
     PyObject *result;
 
     *presult = NULL;
-    if (f != NULL && _PyFrame_IsExecuting(f)) {
+    if (frame != NULL && _PyFrame_IsExecuting(frame)) {
         const char *msg = "generator already executing";
         if (PyCoro_CheckExact(gen)) {
             msg = "coroutine already executing";
@@ -156,7 +166,7 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
         PyErr_SetString(PyExc_ValueError, msg);
         return PYGEN_ERROR;
     }
-    if (f == NULL || _PyFrameHasCompleted(f)) {
+    if (frame == NULL || _PyFrameHasCompleted(frame)) {
         if (PyCoro_CheckExact(gen) && !closing) {
             /* `gen` is an exhausted coroutine: raise an error,
                except when called from gen_close(), which should
@@ -175,19 +185,15 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
         return PYGEN_ERROR;
     }
 
-    assert(_PyFrame_IsRunnable(f));
-    assert(f->f_lasti >= 0 || ((unsigned char *)PyBytes_AS_STRING(gen->gi_code->co_code))[0] == GEN_START);
+    assert(_PyFrame_IsRunnable(frame));
+    assert(frame->f_lasti >= 0 || ((unsigned char *)PyBytes_AS_STRING(gen->gi_code->co_code))[0] == GEN_START);
     /* Push arg onto the frame's value stack */
     result = arg ? arg : Py_None;
     Py_INCREF(result);
-    gen->gi_frame->f_valuestack[gen->gi_frame->f_stackdepth] = result;
-    gen->gi_frame->f_stackdepth++;
+    frame->stack[frame->stackdepth] = result;
+    frame->stackdepth++;
 
-    /* Generators always return to their most recent caller, not
-     * necessarily their creator. */
-    Py_XINCREF(tstate->frame);
-    assert(f->f_back == NULL);
-    f->f_back = tstate->frame;
+    frame->previous = tstate->frame;
 
     gen->gi_exc_state.previous_item = tstate->exc_info;
     tstate->exc_info = &gen->gi_exc_state;
@@ -197,20 +203,20 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
         _PyErr_ChainStackItem(NULL);
     }
 
-    result = _PyEval_EvalFrame(tstate, f, exc);
+    result = _PyEval_EvalFrame(tstate, frame, exc);
     tstate->exc_info = gen->gi_exc_state.previous_item;
     gen->gi_exc_state.previous_item = NULL;
 
-    /* Don't keep the reference to f_back any longer than necessary.  It
+    assert(tstate->frame == frame->previous);
+    /* Don't keep the reference to previous any longer than necessary.  It
      * may keep a chain of frames alive or it could create a reference
      * cycle. */
-    assert(f->f_back == tstate->frame);
-    Py_CLEAR(f->f_back);
+    frame->previous = NULL;
 
     /* If the generator just returned (as opposed to yielding), signal
      * that the generator is exhausted. */
     if (result) {
-        if (!_PyFrameHasCompleted(f)) {
+        if (!_PyFrameHasCompleted(frame)) {
             *presult = result;
             return PYGEN_NEXT;
         }
@@ -245,10 +251,10 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
     /* generator can't be rerun, so release the frame */
     /* first clean reference cycle through stored exception traceback */
     _PyErr_ClearExcState(&gen->gi_exc_state);
-    gen->gi_frame->f_gen = NULL;
-    gen->gi_frame = NULL;
-    Py_DECREF(f);
 
+    frame->generator = NULL;
+    gen->gi_xframe = NULL;
+    _PyFrame_Clear(frame, 1);
     *presult = result;
     return result ? PYGEN_RETURN : PYGEN_ERROR;
 }
@@ -328,13 +334,13 @@ PyObject *
 _PyGen_yf(PyGenObject *gen)
 {
     PyObject *yf = NULL;
-    PyFrameObject *f = gen->gi_frame;
 
-    if (f) {
+    if (gen->gi_xframe) {
+        InterpreterFrame *frame = gen->gi_xframe;
         PyObject *bytecode = gen->gi_code->co_code;
         unsigned char *code = (unsigned char *)PyBytes_AS_STRING(bytecode);
 
-        if (f->f_lasti < 0) {
+        if (frame->f_lasti < 0) {
             /* Return immediately if the frame didn't start yet. YIELD_FROM
                always come after LOAD_CONST: a code object should not start
                with YIELD_FROM */
@@ -342,10 +348,10 @@ _PyGen_yf(PyGenObject *gen)
             return NULL;
         }
 
-        if (code[(f->f_lasti+1)*sizeof(_Py_CODEUNIT)] != YIELD_FROM)
+        if (code[(frame->f_lasti+1)*sizeof(_Py_CODEUNIT)] != YIELD_FROM)
             return NULL;
-        assert(f->f_stackdepth > 0);
-        yf = f->f_valuestack[f->f_stackdepth-1];
+        assert(frame->stackdepth > 0);
+        yf = frame->stack[frame->stackdepth-1];
         Py_INCREF(yf);
     }
 
@@ -360,10 +366,10 @@ gen_close(PyGenObject *gen, PyObject *args)
     int err = 0;
 
     if (yf) {
-        PyFrameState state = gen->gi_frame->f_state;
-        gen->gi_frame->f_state = FRAME_EXECUTING;
+        PyFrameState state = gen->gi_xframe->f_state;
+        gen->gi_xframe->f_state = FRAME_EXECUTING;
         err = gen_close_iter(yf);
-        gen->gi_frame->f_state = state;
+        gen->gi_xframe->f_state = state;
         Py_DECREF(yf);
     }
     if (err == 0)
@@ -410,10 +416,10 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
                We have to allow some awaits to work it through, hence the
                `close_on_genexit` parameter here.
             */
-            PyFrameState state = gen->gi_frame->f_state;
-            gen->gi_frame->f_state = FRAME_EXECUTING;
+            PyFrameState state = gen->gi_xframe->f_state;
+            gen->gi_xframe->f_state = FRAME_EXECUTING;
             err = gen_close_iter(yf);
-            gen->gi_frame->f_state = state;
+            gen->gi_xframe->f_state = state;
             Py_DECREF(yf);
             if (err < 0)
                 return gen_send_ex(gen, Py_None, 1, 0);
@@ -422,22 +428,26 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
         if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {
             /* `yf` is a generator or a coroutine. */
             PyThreadState *tstate = _PyThreadState_GET();
-            PyFrameObject *f = tstate->frame;
+            InterpreterFrame *frame = gen->gi_xframe;
+
 
             /* Since we are fast-tracking things by skipping the eval loop,
                we need to update the current frame so the stack trace
                will be reported correctly to the user. */
             /* XXX We should probably be updating the current frame
                somewhere in ceval.c. */
-            tstate->frame = gen->gi_frame;
+            InterpreterFrame *prev = tstate->frame;
+            frame->previous = prev;
+            tstate->frame = frame;
             /* Close the generator that we are currently iterating with
                'yield from' or awaiting on with 'await'. */
-            PyFrameState state = gen->gi_frame->f_state;
-            gen->gi_frame->f_state = FRAME_EXECUTING;
+            PyFrameState state = gen->gi_xframe->f_state;
+            gen->gi_xframe->f_state = FRAME_EXECUTING;
             ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
                              typ, val, tb);
-            gen->gi_frame->f_state = state;
-            tstate->frame = f;
+            gen->gi_xframe->f_state = state;
+            tstate->frame = prev;
+            frame->previous = NULL;
         } else {
             /* `yf` is an iterator or a coroutine-like object. */
             PyObject *meth;
@@ -449,24 +459,24 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
                 Py_DECREF(yf);
                 goto throw_here;
             }
-            PyFrameState state = gen->gi_frame->f_state;
-            gen->gi_frame->f_state = FRAME_EXECUTING;
+            PyFrameState state = gen->gi_xframe->f_state;
+            gen->gi_xframe->f_state = FRAME_EXECUTING;
             ret = PyObject_CallFunctionObjArgs(meth, typ, val, tb, NULL);
-            gen->gi_frame->f_state = state;
+            gen->gi_xframe->f_state = state;
             Py_DECREF(meth);
         }
         Py_DECREF(yf);
         if (!ret) {
             PyObject *val;
             /* Pop subiterator from stack */
-            assert(gen->gi_frame->f_stackdepth > 0);
-            gen->gi_frame->f_stackdepth--;
-            ret = gen->gi_frame->f_valuestack[gen->gi_frame->f_stackdepth];
+            assert(gen->gi_xframe->stackdepth > 0);
+            gen->gi_xframe->stackdepth--;
+            ret = gen->gi_xframe->stack[gen->gi_xframe->stackdepth];
             assert(ret == yf);
             Py_DECREF(ret);
             /* Termination repetition of YIELD_FROM */
-            assert(gen->gi_frame->f_lasti >= 0);
-            gen->gi_frame->f_lasti += 1;
+            assert(gen->gi_xframe->f_lasti >= 0);
+            gen->gi_xframe->f_lasti += 1;
             if (_PyGen_FetchStopIterationValue(&val) == 0) {
                 ret = gen_send(gen, val);
                 Py_DECREF(val);
@@ -723,10 +733,28 @@ gen_getyieldfrom(PyGenObject *gen, void *Py_UNUSED(ignored))
 static PyObject *
 gen_getrunning(PyGenObject *gen, void *Py_UNUSED(ignored))
 {
-    if (gen->gi_frame == NULL) {
+    if (gen->gi_xframe == NULL) {
         Py_RETURN_FALSE;
     }
-    return PyBool_FromLong(_PyFrame_IsExecuting(gen->gi_frame));
+    return PyBool_FromLong(_PyFrame_IsExecuting(gen->gi_xframe));
+}
+
+static PyObject *
+_gen_getframe(PyGenObject *gen, const char *const name)
+{
+    if (PySys_Audit("object.__getattr__", "Os", gen, name) < 0) {
+        return NULL;
+    }
+    if (gen->gi_xframe == NULL) {
+        Py_RETURN_NONE;
+    }
+    return _Py_XNewRef((PyObject *)_PyFrame_GetFrameObject(gen->gi_xframe));
+}
+
+static PyObject *
+gen_getframe(PyGenObject *gen, void *Py_UNUSED(ignored))
+{
+    return _gen_getframe(gen, "gi_frame");
 }
 
 static PyGetSetDef gen_getsetlist[] = {
@@ -737,11 +765,11 @@ static PyGetSetDef gen_getsetlist[] = {
     {"gi_yieldfrom", (getter)gen_getyieldfrom, NULL,
      PyDoc_STR("object being iterated by yield from, or None")},
     {"gi_running", (getter)gen_getrunning, NULL, NULL},
+    {"gi_frame", (getter)gen_getframe,  NULL, NULL},
     {NULL} /* Sentinel */
 };
 
 static PyMemberDef gen_memberlist[] = {
-    {"gi_frame",     T_OBJECT, offsetof(PyGenObject, gi_frame),    READONLY|PY_AUDIT_READ},
     {"gi_code",      T_OBJECT, offsetof(PyGenObject, gi_code),     READONLY|PY_AUDIT_READ},
     {NULL}      /* Sentinel */
 };
@@ -814,6 +842,84 @@ PyTypeObject PyGen_Type = {
     _PyGen_Finalize,                            /* tp_finalize */
 };
 
+static PyObject *
+make_gen(PyTypeObject *type, PyFrameConstructor *con, InterpreterFrame *frame)
+{
+    PyGenObject *gen = PyObject_GC_New(PyGenObject, type);
+    if (gen == NULL) {
+        assert(frame->frame_obj == NULL);
+        _PyFrame_Clear(frame, 1);
+        return NULL;
+    }
+    gen->gi_xframe = frame;
+    frame->generator = (PyObject *)gen;
+    gen->gi_code = frame->f_code;
+    Py_INCREF(gen->gi_code);
+    gen->gi_weakreflist = NULL;
+    gen->gi_exc_state.exc_type = NULL;
+    gen->gi_exc_state.exc_value = NULL;
+    gen->gi_exc_state.exc_traceback = NULL;
+    gen->gi_exc_state.previous_item = NULL;
+    if (con->fc_name != NULL)
+        gen->gi_name = con->fc_name;
+    else
+        gen->gi_name = gen->gi_code->co_name;
+    Py_INCREF(gen->gi_name);
+    if (con->fc_qualname != NULL)
+        gen->gi_qualname = con->fc_qualname;
+    else
+        gen->gi_qualname = gen->gi_name;
+    Py_INCREF(gen->gi_qualname);
+    _PyObject_GC_TRACK(gen);
+    return (PyObject *)gen;
+}
+
+static PyObject *
+compute_cr_origin(int origin_depth);
+
+PyObject *
+_Py_MakeCoro(PyFrameConstructor *con, InterpreterFrame *frame)
+{
+    int coro_flags = ((PyCodeObject *)con->fc_code)->co_flags &
+        (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR);
+    assert(coro_flags);
+    if (coro_flags == CO_GENERATOR) {
+        return make_gen(&PyGen_Type, con, frame);
+    }
+    if (coro_flags == CO_ASYNC_GENERATOR) {
+        PyAsyncGenObject *o;
+        o = (PyAsyncGenObject *)make_gen(&PyAsyncGen_Type, con, frame);
+        if (o == NULL) {
+            return NULL;
+        }
+        o->ag_finalizer = NULL;
+        o->ag_closed = 0;
+        o->ag_hooks_inited = 0;
+        o->ag_running_async = 0;
+        return (PyObject*)o;
+    }
+    assert (coro_flags == CO_COROUTINE);
+    PyObject *coro = make_gen(&PyCoro_Type, con, frame);
+    if (!coro) {
+        return NULL;
+    }
+    PyThreadState *tstate = _PyThreadState_GET();
+    int origin_depth = tstate->coroutine_origin_tracking_depth;
+
+    if (origin_depth == 0) {
+        ((PyCoroObject *)coro)->cr_origin = NULL;
+    } else {
+        PyObject *cr_origin = compute_cr_origin(origin_depth);
+        ((PyCoroObject *)coro)->cr_origin = cr_origin;
+        if (!cr_origin) {
+            Py_DECREF(coro);
+            return NULL;
+        }
+    }
+
+    return coro;
+}
+
 static PyObject *
 gen_new_with_qualname(PyTypeObject *type, PyFrameObject *f,
                       PyObject *name, PyObject *qualname)
@@ -823,8 +929,16 @@ gen_new_with_qualname(PyTypeObject *type, PyFrameObject *f,
         Py_DECREF(f);
         return NULL;
     }
-    gen->gi_frame = f;
-    f->f_gen = (PyObject *) gen;
+
+    /* Take ownership of the frame */
+    assert(f->f_frame->frame_obj == NULL);
+    assert(f->f_own_locals_memory);
+    gen->gi_xframe = f->f_frame;
+    gen->gi_xframe->frame_obj = f;
+    f->f_own_locals_memory = 0;
+    gen->gi_xframe->generator = (PyObject *) gen;
+    assert(PyObject_GC_IsTracked((PyObject *)f));
+
     gen->gi_code = PyFrame_GetCode(f);
     gen->gi_weakreflist = NULL;
     gen->gi_exc_state.exc_type = NULL;
@@ -958,12 +1072,19 @@ coro_get_cr_await(PyCoroObject *coro, void *Py_UNUSED(ignored))
 static PyObject *
 cr_getrunning(PyCoroObject *coro, void *Py_UNUSED(ignored))
 {
-    if (coro->cr_frame == NULL) {
+    if (coro->cr_xframe == NULL) {
         Py_RETURN_FALSE;
     }
-    return PyBool_FromLong(_PyFrame_IsExecuting(coro->cr_frame));
+    return PyBool_FromLong(_PyFrame_IsExecuting(coro->cr_xframe));
+}
+
+static PyObject *
+cr_getframe(PyCoroObject *coro, void *Py_UNUSED(ignored))
+{
+    return _gen_getframe((PyGenObject *)coro, "cr_frame");
 }
 
+
 static PyGetSetDef coro_getsetlist[] = {
     {"__name__", (getter)gen_get_name, (setter)gen_set_name,
      PyDoc_STR("name of the coroutine")},
@@ -972,11 +1093,11 @@ static PyGetSetDef coro_getsetlist[] = {
     {"cr_await", (getter)coro_get_cr_await, NULL,
      PyDoc_STR("object being awaited on, or None")},
     {"cr_running", (getter)cr_getrunning, NULL, NULL},
+    {"cr_frame", (getter)cr_getframe, NULL, NULL},
     {NULL} /* Sentinel */
 };
 
 static PyMemberDef coro_memberlist[] = {
-    {"cr_frame",     T_OBJECT, offsetof(PyCoroObject, cr_frame),    READONLY|PY_AUDIT_READ},
     {"cr_code",      T_OBJECT, offsetof(PyCoroObject, cr_code),     READONLY|PY_AUDIT_READ},
     {"cr_origin",    T_OBJECT, offsetof(PyCoroObject, cr_origin),   READONLY},
     {NULL}      /* Sentinel */
@@ -1150,11 +1271,11 @@ PyTypeObject _PyCoroWrapper_Type = {
 static PyObject *
 compute_cr_origin(int origin_depth)
 {
-    PyFrameObject *frame = PyEval_GetFrame();
+    InterpreterFrame *frame = _PyEval_GetFrame();
     /* First count how many frames we have */
     int frame_count = 0;
     for (; frame && frame_count < origin_depth; ++frame_count) {
-        frame = frame->f_back;
+        frame = frame->previous;
     }
 
     /* Now collect them */
@@ -1162,20 +1283,19 @@ compute_cr_origin(int origin_depth)
     if (cr_origin == NULL) {
         return NULL;
     }
-    frame = PyEval_GetFrame();
+    frame = _PyEval_GetFrame();
     for (int i = 0; i < frame_count; ++i) {
-        PyCodeObject *code = PyFrame_GetCode(frame);
+        PyCodeObject *code = frame->f_code;
         PyObject *frameinfo = Py_BuildValue("OiO",
                                             code->co_filename,
-                                            PyFrame_GetLineNumber(frame),
+                                            PyCode_Addr2Line(frame->f_code, frame->f_lasti*2),
                                             code->co_name);
-        Py_DECREF(code);
         if (!frameinfo) {
             Py_DECREF(cr_origin);
             return NULL;
         }
         PyTuple_SET_ITEM(cr_origin, i, frameinfo);
-        frame = frame->f_back;
+        frame = frame->previous;
     }
 
     return cr_origin;
@@ -1346,6 +1466,11 @@ async_gen_athrow(PyAsyncGenObject *o, PyObject *args)
     return async_gen_athrow_new(o, args);
 }
 
+static PyObject *
+ag_getframe(PyAsyncGenObject *ag, void *Py_UNUSED(ignored))
+{
+    return _gen_getframe((PyGenObject *)ag, "ag_frame");
+}
 
 static PyGetSetDef async_gen_getsetlist[] = {
     {"__name__", (getter)gen_get_name, (setter)gen_set_name,
@@ -1354,11 +1479,11 @@ static PyGetSetDef async_gen_getsetlist[] = {
      PyDoc_STR("qualified name of the async generator")},
     {"ag_await", (getter)coro_get_cr_await, NULL,
      PyDoc_STR("object being awaited on, or None")},
+     {"ag_frame",  (getter)ag_getframe, NULL, NULL},
     {NULL} /* Sentinel */
 };
 
 static PyMemberDef async_gen_memberlist[] = {
-    {"ag_frame",   T_OBJECT, offsetof(PyAsyncGenObject, ag_frame),   READONLY|PY_AUDIT_READ},
     {"ag_running", T_BOOL,   offsetof(PyAsyncGenObject, ag_running_async),
         READONLY},
     {"ag_code",    T_OBJECT, offsetof(PyAsyncGenObject, ag_code),    READONLY|PY_AUDIT_READ},
@@ -1865,7 +1990,7 @@ static PyObject *
 async_gen_athrow_send(PyAsyncGenAThrow *o, PyObject *arg)
 {
     PyGenObject *gen = (PyGenObject*)o->agt_gen;
-    PyFrameObject *f = gen->gi_frame;
+    InterpreterFrame *frame = gen->gi_xframe;
     PyObject *retval;
 
     if (o->agt_state == AWAITABLE_STATE_CLOSED) {
@@ -1875,7 +2000,7 @@ async_gen_athrow_send(PyAsyncGenAThrow *o, PyObject *arg)
         return NULL;
     }
 
-    if (f == NULL || _PyFrameHasCompleted(f)) {
+    if (frame == NULL || _PyFrameHasCompleted(frame)) {
         o->agt_state = AWAITABLE_STATE_CLOSED;
         PyErr_SetNone(PyExc_StopIteration);
         return NULL;
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 42a9978ef300bd..43b4d0725a4146 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -11,6 +11,7 @@
 #include "pycore_pystate.h"       // _PyThreadState_GET()
 #include "pycore_unionobject.h"   // _Py_union_type_or
 #include "frameobject.h"
+#include "pycore_frame.h"
 #include "opcode.h"               // MAKE_CELL
 #include "structmember.h"         // PyMemberDef
 
@@ -8867,12 +8868,13 @@ super_init_without_args(PyFrameObject *f, PyCodeObject *co,
         return -1;
     }
 
-    PyObject *firstarg = f->f_localsptr[0];
+    assert(f->f_frame->nlocalsplus > 0);
+    PyObject *firstarg = _PyFrame_GetLocalsArray(f->f_frame)[0];
     // The first argument might be a cell.
     if (firstarg != NULL && (_PyLocals_GetKind(co->co_localspluskinds, 0) & CO_FAST_CELL)) {
         // "firstarg" is a cell here unless (very unlikely) super()
         // was called from the C-API before the first MAKE_CELL op.
-        if (f->f_lasti >= 0) {
+        if (f->f_frame->f_lasti >= 0) {
             assert(_Py_OPCODE(*co->co_firstinstr) == MAKE_CELL);
             assert(PyCell_Check(firstarg));
             firstarg = PyCell_GET(firstarg);
@@ -8892,7 +8894,7 @@ super_init_without_args(PyFrameObject *f, PyCodeObject *co,
         PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
         assert(PyUnicode_Check(name));
         if (_PyUnicode_EqualToASCIIId(name, &PyId___class__)) {
-            PyObject *cell = f->f_localsptr[i];
+            PyObject *cell = _PyFrame_GetLocalsArray(f->f_frame)[i];
             if (cell == NULL || !PyCell_Check(cell)) {
                 PyErr_SetString(PyExc_RuntimeError,
                   "super(): bad __class__ cell");
diff --git a/PCbuild/pythoncore.vcxproj b/PCbuild/pythoncore.vcxproj
index 7b2a9b5913bc8d..ebc0f2879f9595 100644
--- a/PCbuild/pythoncore.vcxproj
+++ b/PCbuild/pythoncore.vcxproj
@@ -456,6 +456,7 @@
     <ClCompile Include="..\Python\errors.c" />
     <ClCompile Include="..\Python\fileutils.c" />
     <ClCompile Include="..\Python\formatter_unicode.c" />
+    <ClCompile Include="..\Python\frame.c" />
     <ClCompile Include="..\Python\frozen.c" />
     <ClCompile Include="..\Python\future.c" />
     <ClCompile Include="..\Python\getargs.c" />
diff --git a/Python/_warnings.c b/Python/_warnings.c
index 9f68da208731e7..cf2110d31c3b5e 100644
--- a/Python/_warnings.c
+++ b/Python/_warnings.c
@@ -854,10 +854,8 @@ setup_context(Py_ssize_t stack_level, PyObject **filename, int *lineno,
         *lineno = 1;
     }
     else {
-        globals = _PyFrame_GetGlobals(f);
-        PyCodeObject *code = PyFrame_GetCode(f);
-        *filename = code->co_filename;
-        Py_DECREF(code);
+        globals = f->f_frame->f_globals;
+        *filename = f->f_frame->f_code->co_filename;
         Py_INCREF(*filename);
         *lineno = PyFrame_GetLineNumber(f);
         Py_DECREF(f);
diff --git a/Python/ceval.c b/Python/ceval.c
index 90112aa3f9476e..02eef9bc1c2722 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -62,27 +62,27 @@ static int lltrace;
 static int prtrace(PyThreadState *, PyObject *, const char *);
 #endif
 static int call_trace(Py_tracefunc, PyObject *,
-                      PyThreadState *, PyFrameObject *,
+                      PyThreadState *, InterpreterFrame *,
                       int, PyObject *);
 static int call_trace_protected(Py_tracefunc, PyObject *,
-                                PyThreadState *, PyFrameObject *,
+                                PyThreadState *, InterpreterFrame *,
                                 int, PyObject *);
 static void call_exc_trace(Py_tracefunc, PyObject *,
-                           PyThreadState *, PyFrameObject *);
+                           PyThreadState *, InterpreterFrame *);
 static int maybe_call_line_trace(Py_tracefunc, PyObject *,
-                                 PyThreadState *, PyFrameObject *, int);
-static void maybe_dtrace_line(PyFrameObject *, PyTraceInfo *, int);
-static void dtrace_function_entry(PyFrameObject *);
-static void dtrace_function_return(PyFrameObject *);
+                                 PyThreadState *, InterpreterFrame *, int);
+static void maybe_dtrace_line(InterpreterFrame *, PyTraceInfo *, int);
+static void dtrace_function_entry(InterpreterFrame *);
+static void dtrace_function_return(InterpreterFrame *);
 
-static PyObject * import_name(PyThreadState *, PyFrameObject *,
+static PyObject * import_name(PyThreadState *, InterpreterFrame *,
                               PyObject *, PyObject *, PyObject *);
 static PyObject * import_from(PyThreadState *, PyObject *, PyObject *);
 static int import_all_from(PyThreadState *, PyObject *, PyObject *);
 static void format_exc_check_arg(PyThreadState *, PyObject *, const char *, PyObject *);
 static void format_exc_unbound(PyThreadState *tstate, PyCodeObject *co, int oparg);
 static PyObject * unicode_concatenate(PyThreadState *, PyObject *, PyObject *,
-                                      PyFrameObject *, const _Py_CODEUNIT *);
+                                      InterpreterFrame *, const _Py_CODEUNIT *);
 static int check_args_iterable(PyThreadState *, PyObject *func, PyObject *vararg);
 static void format_kwargs_error(PyThreadState *, PyObject *func, PyObject *kwargs);
 static void format_awaitable_error(PyThreadState *, PyTypeObject *, int, int);
@@ -1065,14 +1065,14 @@ PyEval_EvalFrame(PyFrameObject *f)
 {
     /* Function kept for backward compatibility */
     PyThreadState *tstate = _PyThreadState_GET();
-    return _PyEval_EvalFrame(tstate, f, 0);
+    return _PyEval_EvalFrame(tstate, f->f_frame, 0);
 }
 
 PyObject *
 PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
 {
     PyThreadState *tstate = _PyThreadState_GET();
-    return _PyEval_EvalFrame(tstate, f, throwflag);
+    return _PyEval_EvalFrame(tstate, f->f_frame, throwflag);
 }
 
 
@@ -1231,7 +1231,7 @@ eval_frame_handle_pending(PyThreadState *tstate)
         if (cframe.use_tracing OR_DTRACE_LINE OR_LLTRACE) { \
             goto tracing_dispatch; \
         } \
-        f->f_lasti = INSTR_OFFSET(); \
+        frame->f_lasti = INSTR_OFFSET(); \
         NEXTOPARG(); \
         DISPATCH_GOTO(); \
     }
@@ -1320,7 +1320,7 @@ eval_frame_handle_pending(PyThreadState *tstate)
 
 /* The stack can grow at most MAXINT deep, as co_nlocals and
    co_stacksize are ints. */
-#define STACK_LEVEL()     ((int)(stack_pointer - f->f_valuestack))
+#define STACK_LEVEL()     ((int)(stack_pointer - frame->stack))
 #define EMPTY()           (STACK_LEVEL() == 0)
 #define TOP()             (stack_pointer[-1])
 #define SECOND()          (stack_pointer[-2])
@@ -1388,12 +1388,12 @@ eval_frame_handle_pending(PyThreadState *tstate)
 
 #define UPDATE_PREV_INSTR_OPARG(instr, oparg) ((uint8_t*)(instr))[-1] = (oparg)
 
-#define GLOBALS() specials[FRAME_SPECIALS_GLOBALS_OFFSET]
-#define BUILTINS() specials[FRAME_SPECIALS_BUILTINS_OFFSET]
-#define LOCALS() specials[FRAME_SPECIALS_LOCALS_OFFSET]
+#define GLOBALS() frame->f_globals
+#define BUILTINS() frame->f_builtins
+#define LOCALS() frame->f_locals
 
 PyObject* _Py_HOT_FUNCTION
-_PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
+_PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int throwflag)
 {
     _Py_EnsureTstateNotNULL(tstate);
 
@@ -1409,7 +1409,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
     _Py_CODEUNIT *next_instr;
     int opcode;        /* Current opcode */
     int oparg;         /* Current opcode argument, if any */
-    PyObject **localsplus, **specials;
+    PyObject **localsplus;
     PyObject *retval = NULL;            /* Return value */
     _Py_atomic_int * const eval_breaker = &tstate->interp->ceval.eval_breaker;
     PyCodeObject *co;
@@ -1438,9 +1438,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
     tstate->cframe = &cframe;
 
     /* push frame */
-    tstate->frame = f;
-    specials = f->f_valuestack - FRAME_SPECIALS_SIZE;
-    co = (PyCodeObject *)specials[FRAME_SPECIALS_CODE_OFFSET];
+    tstate->frame = frame;
+    co = frame->f_code;
 
     if (cframe.use_tracing) {
         if (tstate->c_tracefunc != NULL) {
@@ -1459,7 +1458,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
                whenever an exception is detected. */
             if (call_trace_protected(tstate->c_tracefunc,
                                      tstate->c_traceobj,
-                                     tstate, f,
+                                     tstate, frame,
                                      PyTrace_CALL, Py_None)) {
                 /* Trace function raised an error */
                 goto exit_eval_frame;
@@ -1470,7 +1469,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
                return itself and isn't called for "line" events */
             if (call_trace_protected(tstate->c_profilefunc,
                                      tstate->c_profileobj,
-                                     tstate, f,
+                                     tstate, frame,
                                      PyTrace_CALL, Py_None)) {
                 /* Profile function raised an error */
                 goto exit_eval_frame;
@@ -1479,7 +1478,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
     }
 
     if (PyDTrace_FUNCTION_ENTRY_ENABLED())
-        dtrace_function_entry(f);
+        dtrace_function_entry(frame);
 
     /* Increment the warmup counter and quicken if warm enough
      * _Py_Quicken is idempotent so we don't worry about overflow */
@@ -1495,34 +1494,34 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
 
     names = co->co_names;
     consts = co->co_consts;
-    localsplus = f->f_localsptr;
+    localsplus = _PyFrame_GetLocalsArray(frame);
     first_instr = co->co_firstinstr;
     /*
-       f->f_lasti refers to the index of the last instruction,
+       frame->f_lasti refers to the index of the last instruction,
        unless it's -1 in which case next_instr should be first_instr.
 
-       YIELD_FROM sets f_lasti to itself, in order to repeatedly yield
+       YIELD_FROM sets frame->f_lasti to itself, in order to repeatedly yield
        multiple values.
 
        When the PREDICT() macros are enabled, some opcode pairs follow in
-       direct succession without updating f->f_lasti.  A successful
+       direct succession without updating frame->f_lasti.  A successful
        prediction effectively links the two codes together as if they
-       were a single new opcode; accordingly,f->f_lasti will point to
+       were a single new opcode; accordingly,frame->f_lasti will point to
        the first code in the pair (for instance, GET_ITER followed by
-       FOR_ITER is effectively a single opcode and f->f_lasti will point
+       FOR_ITER is effectively a single opcode and frame->f_lasti will point
        to the beginning of the combined pair.)
     */
-    assert(f->f_lasti >= -1);
-    next_instr = first_instr + f->f_lasti + 1;
-    stack_pointer = f->f_valuestack + f->f_stackdepth;
-    /* Set f->f_stackdepth to -1.
+    assert(frame->f_lasti >= -1);
+    next_instr = first_instr + frame->f_lasti + 1;
+    stack_pointer = frame->stack + frame->stackdepth;
+    /* Set stackdepth to -1.
      * Update when returning or calling trace function.
        Having f_stackdepth <= 0 ensures that invalid
        values are not visible to the cycle GC.
        We choose -1 rather than 0 to assist debugging.
      */
-    f->f_stackdepth = -1;
-    f->f_state = FRAME_EXECUTING;
+    frame->stackdepth = -1;
+    frame->f_state = FRAME_EXECUTING;
 
 #ifdef LLTRACE
     {
@@ -1546,7 +1545,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
 #endif
 
     for (;;) {
-        assert(stack_pointer >= f->f_valuestack); /* else underflow */
+        assert(STACK_LEVEL() >= 0); /* else underflow */
         assert(STACK_LEVEL() <= co->co_stacksize);  /* else overflow */
         assert(!_PyErr_Occurred(tstate));
 
@@ -1586,12 +1585,12 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
 
     tracing_dispatch:
     {
-        int instr_prev = f->f_lasti;
-        f->f_lasti = INSTR_OFFSET();
+        int instr_prev = frame->f_lasti;
+        frame->f_lasti = INSTR_OFFSET();
         TRACING_NEXTOPARG();
 
         if (PyDTrace_LINE_ENABLED())
-            maybe_dtrace_line(f, &tstate->trace_info, instr_prev);
+            maybe_dtrace_line(frame, &tstate->trace_info, instr_prev);
 
         /* line-by-line tracing support */
 
@@ -1600,19 +1599,19 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
             int err;
             /* see maybe_call_line_trace()
                for expository comments */
-            f->f_stackdepth = (int)(stack_pointer - f->f_valuestack);
+            frame->stackdepth = (int)(stack_pointer - frame->stack);
 
             err = maybe_call_line_trace(tstate->c_tracefunc,
                                         tstate->c_traceobj,
-                                        tstate, f, instr_prev);
+                                        tstate, frame, instr_prev);
             if (err) {
                 /* trace function raised an exception */
                 goto error;
             }
             /* Reload possibly changed frame fields */
-            JUMPTO(f->f_lasti);
-            stack_pointer = f->f_valuestack+f->f_stackdepth;
-            f->f_stackdepth = -1;
+            JUMPTO(frame->f_lasti);
+            stack_pointer = frame->stack+frame->stackdepth;
+            frame->stackdepth = -1;
             TRACING_NEXTOPARG();
         }
     }
@@ -1623,11 +1622,11 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
         if (lltrace) {
             if (HAS_ARG(opcode)) {
                 printf("%d: %d, %d\n",
-                       f->f_lasti, opcode, oparg);
+                       frame->f_lasti, opcode, oparg);
             }
             else {
                 printf("%d: %d\n",
-                       f->f_lasti, opcode);
+                       frame->f_lasti, opcode);
             }
         }
 #endif
@@ -1876,7 +1875,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
                speedup on microbenchmarks. */
             if (PyUnicode_CheckExact(left) &&
                      PyUnicode_CheckExact(right)) {
-                sum = unicode_concatenate(tstate, left, right, f, next_instr);
+                sum = unicode_concatenate(tstate, left, right, frame, next_instr);
                 /* unicode_concatenate consumed the ref to left */
             }
             else {
@@ -2162,7 +2161,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
             PyObject *left = TOP();
             PyObject *sum;
             if (PyUnicode_CheckExact(left) && PyUnicode_CheckExact(right)) {
-                sum = unicode_concatenate(tstate, left, right, f, next_instr);
+                sum = unicode_concatenate(tstate, left, right, frame, next_instr);
                 /* unicode_concatenate consumed the ref to left */
             }
             else {
@@ -2322,8 +2321,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
         case TARGET(RETURN_VALUE): {
             retval = POP();
             assert(EMPTY());
-            f->f_state = FRAME_RETURNED;
-            f->f_stackdepth = 0;
+            frame->f_state = FRAME_RETURNED;
+            frame->stackdepth = 0;
             goto exiting;
         }
 
@@ -2480,7 +2479,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
                 if (retval == NULL) {
                     if (tstate->c_tracefunc != NULL
                             && _PyErr_ExceptionMatches(tstate, PyExc_StopIteration))
-                        call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, tstate, f);
+                        call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, tstate, frame);
                     if (_PyGen_FetchStopIterationValue(&retval) == 0) {
                         gen_status = PYGEN_RETURN;
                     }
@@ -2508,10 +2507,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
             assert (gen_status == PYGEN_NEXT);
             /* receiver remains on stack, retval is value to be yielded */
             /* and repeat... */
-            assert(f->f_lasti > 0);
-            f->f_lasti -= 1;
-            f->f_state = FRAME_SUSPENDED;
-            f->f_stackdepth = (int)(stack_pointer - f->f_valuestack);
+            assert(frame->f_lasti > 0);
+            frame->f_lasti -= 1;
+            frame->f_state = FRAME_SUSPENDED;
+            frame->stackdepth = (int)(stack_pointer - frame->stack);
             goto exiting;
         }
 
@@ -2527,8 +2526,8 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
                 }
                 retval = w;
             }
-            f->f_state = FRAME_SUSPENDED;
-            f->f_stackdepth = (int)(stack_pointer - f->f_valuestack);
+            frame->f_state = FRAME_SUSPENDED;
+            frame->stackdepth = (int)(stack_pointer - frame->stack);
             goto exiting;
         }
 
@@ -2575,7 +2574,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
         case TARGET(POP_EXCEPT_AND_RERAISE): {
             PyObject *lasti = PEEK(4);
             if (PyLong_Check(lasti)) {
-                f->f_lasti = PyLong_AsLong(lasti);
+                frame->f_lasti = PyLong_AsLong(lasti);
                 assert(!_PyErr_Occurred(tstate));
             }
             else {
@@ -2606,7 +2605,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
             if (oparg) {
                 PyObject *lasti = PEEK(oparg+3);
                 if (PyLong_Check(lasti)) {
-                    f->f_lasti = PyLong_AsLong(lasti);
+                    frame->f_lasti = PyLong_AsLong(lasti);
                     assert(!_PyErr_Occurred(tstate));
                 }
                 else {
@@ -3579,7 +3578,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
             PyObject *fromlist = POP();
             PyObject *level = TOP();
             PyObject *res;
-            res = import_name(tstate, f, name, fromlist, level);
+            res = import_name(tstate, frame, name, fromlist, level);
             Py_DECREF(level);
             Py_DECREF(fromlist);
             SET_TOP(res);
@@ -3591,7 +3590,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
         case TARGET(IMPORT_STAR): {
             PyObject *from = POP(), *locals;
             int err;
-            if (PyFrame_FastToLocalsWithError(f) < 0) {
+            if (_PyFrame_FastToLocalsWithError(frame) < 0) {
                 Py_DECREF(from);
                 goto error;
             }
@@ -3604,7 +3603,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
                 goto error;
             }
             err = import_all_from(tstate, locals, from);
-            PyFrame_LocalsToFast(f, 0);
+            _PyFrame_LocalsToFast(frame, 0);
             Py_DECREF(from);
             if (err != 0)
                 goto error;
@@ -3918,7 +3917,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
                     goto error;
                 }
                 else if (tstate->c_tracefunc != NULL) {
-                    call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, tstate, f);
+                    call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj, tstate, frame);
                 }
                 _PyErr_Clear(tstate);
             }
@@ -4381,7 +4380,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
         default:
             fprintf(stderr,
                 "XXX lineno: %d, opcode: %d\n",
-                PyFrame_GetLineNumber(f),
+                PyCode_Addr2Line(frame->f_code, frame->f_lasti*2),
                 opcode);
             _PyErr_SetString(tstate, PyExc_SystemError, "unknown opcode");
             goto error;
@@ -4450,19 +4449,22 @@ MISS_WITH_OPARG_COUNTER(BINARY_SUBSCR)
 #endif
 
         /* Log traceback info. */
-        PyTraceBack_Here(f);
+        PyFrameObject *f = _PyFrame_GetFrameObject(frame);
+        if (f != NULL) {
+            PyTraceBack_Here(f);
+        }
 
         if (tstate->c_tracefunc != NULL) {
             /* Make sure state is set to FRAME_EXECUTING for tracing */
-            assert(f->f_state == FRAME_EXECUTING);
-            f->f_state = FRAME_UNWINDING;
+            assert(frame->f_state == FRAME_EXECUTING);
+            frame->f_state = FRAME_UNWINDING;
             call_exc_trace(tstate->c_tracefunc, tstate->c_traceobj,
-                           tstate, f);
+                           tstate, frame);
         }
 
 exception_unwind:
-        f->f_state = FRAME_UNWINDING;
-        /* We can't use f->f_lasti here, as RERAISE may have set it */
+        frame->f_state = FRAME_UNWINDING;
+        /* We can't use frame->f_lasti here, as RERAISE may have set it */
         int offset = INSTR_OFFSET()-1;
         int level, handler, lasti;
         if (get_exception_handler(co, offset, &level, &handler, &lasti) == 0) {
@@ -4477,7 +4479,7 @@ MISS_WITH_OPARG_COUNTER(BINARY_SUBSCR)
         }
         PyObject *exc, *val, *tb;
         if (lasti) {
-            PyObject *lasti = PyLong_FromLong(f->f_lasti);
+            PyObject *lasti = PyLong_FromLong(frame->f_lasti);
             if (lasti == NULL) {
                 goto exception_unwind;
             }
@@ -4502,8 +4504,8 @@ MISS_WITH_OPARG_COUNTER(BINARY_SUBSCR)
         PUSH(exc);
         JUMPTO(handler);
         /* Resume normal execution */
-        f->f_state = FRAME_EXECUTING;
-        f->f_lasti = handler;
+        frame->f_state = FRAME_EXECUTING;
+        frame->f_lasti = handler;
         NEXTOPARG();
         goto dispatch_opcode;
     } /* main loop */
@@ -4516,19 +4518,19 @@ MISS_WITH_OPARG_COUNTER(BINARY_SUBSCR)
         PyObject *o = POP();
         Py_XDECREF(o);
     }
-    f->f_stackdepth = 0;
-    f->f_state = FRAME_RAISED;
+    frame->stackdepth = 0;
+    frame->f_state = FRAME_RAISED;
 exiting:
     if (cframe.use_tracing) {
         if (tstate->c_tracefunc) {
             if (call_trace_protected(tstate->c_tracefunc, tstate->c_traceobj,
-                                     tstate, f, PyTrace_RETURN, retval)) {
+                                     tstate, frame, PyTrace_RETURN, retval)) {
                 Py_CLEAR(retval);
             }
         }
         if (tstate->c_profilefunc) {
             if (call_trace_protected(tstate->c_profilefunc, tstate->c_profileobj,
-                                     tstate, f, PyTrace_RETURN, retval)) {
+                                     tstate, frame, PyTrace_RETURN, retval)) {
                 Py_CLEAR(retval);
             }
         }
@@ -4541,10 +4543,9 @@ MISS_WITH_OPARG_COUNTER(BINARY_SUBSCR)
     tstate->cframe->use_tracing = cframe.use_tracing;
 
     if (PyDTrace_FUNCTION_RETURN_ENABLED())
-        dtrace_function_return(f);
+        dtrace_function_return(frame);
     _Py_LeaveRecursiveCall(tstate);
-    tstate->frame = f->f_back;
-
+    tstate->frame = frame->previous;
     return _Py_CheckFunctionResult(tstate, NULL, retval, __func__);
 }
 
@@ -5051,116 +5052,110 @@ initialize_locals(PyThreadState *tstate, PyFrameConstructor *con,
 
 }
 
-
-PyFrameObject *
-_PyEval_MakeFrameVector(PyThreadState *tstate,
+static InterpreterFrame *
+make_coro_frame(PyThreadState *tstate,
            PyFrameConstructor *con, PyObject *locals,
            PyObject *const *args, Py_ssize_t argcount,
-           PyObject *kwnames, PyObject** localsarray)
+           PyObject *kwnames)
 {
     assert(is_tstate_valid(tstate));
     assert(con->fc_defaults == NULL || PyTuple_CheckExact(con->fc_defaults));
-
-    /* Create the frame */
-    PyFrameObject *f = _PyFrame_New_NoTrack(tstate, con, locals, localsarray);
-    if (f == NULL) {
+    PyCodeObject *code = (PyCodeObject *)con->fc_code;
+    int size = code->co_nlocalsplus+code->co_stacksize + FRAME_SPECIALS_SIZE;
+    PyObject **localsarray = PyMem_Malloc(sizeof(PyObject *)*size);
+    if (localsarray == NULL) {
+        PyErr_NoMemory();
         return NULL;
     }
-    if (initialize_locals(tstate, con, f->f_localsptr, args, argcount, kwnames)) {
-        Py_DECREF(f);
+    for (Py_ssize_t i=0; i < code->co_nlocalsplus; i++) {
+        localsarray[i] = NULL;
+    }
+    InterpreterFrame *frame = (InterpreterFrame *)(localsarray + code->co_nlocalsplus);
+    _PyFrame_InitializeSpecials(frame, con, locals, code->co_nlocalsplus);
+    assert(frame->frame_obj == NULL);
+    if (initialize_locals(tstate, con, localsarray, args, argcount, kwnames)) {
+        _PyFrame_Clear(frame, 1);
         return NULL;
     }
-    return f;
+    return frame;
 }
 
 static PyObject *
-make_coro(PyFrameConstructor *con, PyFrameObject *f)
+make_coro(PyThreadState *tstate, PyFrameConstructor *con,
+          PyObject *locals,
+          PyObject* const* args, size_t argcount,
+          PyObject *kwnames)
 {
     assert (((PyCodeObject *)con->fc_code)->co_flags & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR));
-    PyObject *gen;
-    int is_coro = ((PyCodeObject *)con->fc_code)->co_flags & CO_COROUTINE;
-
-    /* Don't need to keep the reference to f_back, it will be set
-        * when the generator is resumed. */
-    Py_CLEAR(f->f_back);
-
-    /* Create a new generator that owns the ready to run frame
-        * and return that as the value. */
-    if (is_coro) {
-            gen = PyCoro_New(f, con->fc_name, con->fc_qualname);
-    } else if (((PyCodeObject *)con->fc_code)->co_flags & CO_ASYNC_GENERATOR) {
-            gen = PyAsyncGen_New(f, con->fc_name, con->fc_qualname);
-    } else {
-            gen = PyGen_NewWithQualName(f, con->fc_name, con->fc_qualname);
+    InterpreterFrame *frame = make_coro_frame(tstate, con, locals, args, argcount, kwnames);
+    if (frame == NULL) {
+        return NULL;
     }
+    PyObject *gen = _Py_MakeCoro(con, frame);
     if (gen == NULL) {
         return NULL;
     }
 
-    _PyObject_GC_TRACK(f);
-
     return gen;
 }
 
+static InterpreterFrame *
+_PyEvalFramePushAndInit(PyThreadState *tstate, PyFrameConstructor *con,
+                        PyObject *locals, PyObject* const* args,
+                        size_t argcount, PyObject *kwnames)
+{
+    InterpreterFrame * frame = _PyThreadState_PushFrame(tstate, con, locals);
+    if (frame == NULL) {
+        return NULL;
+    }
+    PyObject **localsarray = _PyFrame_GetLocalsArray(frame);
+    if (initialize_locals(tstate, con, localsarray, args, argcount, kwnames)) {
+        _PyFrame_Clear(frame, 0);
+        return NULL;
+    }
+    frame->previous = tstate->frame;
+    tstate->frame = frame;
+    return frame;
+}
+
+static int
+_PyEvalFrameClearAndPop(PyThreadState *tstate, InterpreterFrame * frame)
+{
+    ++tstate->recursion_depth;
+    assert(frame->frame_obj == NULL || frame->frame_obj->f_own_locals_memory == 0);
+    if (_PyFrame_Clear(frame, 0)) {
+        return -1;
+    }
+    assert(frame->frame_obj == NULL);
+    --tstate->recursion_depth;
+    tstate->frame = frame->previous;
+    _PyThreadState_PopFrame(tstate, frame);
+    return 0;
+}
+
 PyObject *
 _PyEval_Vector(PyThreadState *tstate, PyFrameConstructor *con,
                PyObject *locals,
                PyObject* const* args, size_t argcount,
                PyObject *kwnames)
 {
-    PyObject **localsarray;
     PyCodeObject *code = (PyCodeObject *)con->fc_code;
     int is_coro = code->co_flags &
         (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR);
     if (is_coro) {
-        localsarray = NULL;
-    }
-    else {
-        int size = code->co_nlocalsplus + code->co_stacksize +
-            FRAME_SPECIALS_SIZE;
-        localsarray = _PyThreadState_PushLocals(tstate, size);
-        if (localsarray == NULL) {
-            return NULL;
-        }
+        return make_coro(tstate, con, locals, args, argcount, kwnames);
     }
-    PyFrameObject *f = _PyEval_MakeFrameVector(
-        tstate, con, locals, args, argcount, kwnames, localsarray);
-    if (f == NULL) {
-        if (!is_coro) {
-            _PyThreadState_PopLocals(tstate, localsarray);
-        }
+    InterpreterFrame *frame = _PyEvalFramePushAndInit(
+        tstate, con, locals, args, argcount, kwnames);
+    if (frame == NULL) {
         return NULL;
     }
-    if (is_coro) {
-        return make_coro(con, f);
-    }
-    PyObject *retval = _PyEval_EvalFrame(tstate, f, 0);
-    assert(f->f_stackdepth == 0);
-
-    /* decref'ing the frame can cause __del__ methods to get invoked,
-       which can call back into Python.  While we're done with the
-       current Python frame (f), the associated C stack is still in use,
-       so recursion_depth must be boosted for the duration.
-    */
-    assert (!is_coro);
-    assert(f->f_own_locals_memory == 0);
-    if (Py_REFCNT(f) > 1) {
-        Py_DECREF(f);
-        _PyObject_GC_TRACK(f);
-        if (_PyFrame_TakeLocals(f)) {
-            Py_CLEAR(retval);
-        }
-    }
-    else {
-        ++tstate->recursion_depth;
-        f->f_localsptr = NULL;
-        for (int i = 0; i < code->co_nlocalsplus + FRAME_SPECIALS_SIZE; i++) {
-            Py_XDECREF(localsarray[i]);
-        }
-        Py_DECREF(f);
-        --tstate->recursion_depth;
+    assert (tstate->interp->eval_frame != NULL);
+    PyObject *retval = _PyEval_EvalFrame(tstate, frame, 0);
+    assert(frame->stackdepth == 0);
+    if (_PyEvalFrameClearAndPop(tstate, frame)) {
+        retval = NULL;
     }
-    _PyThreadState_PopLocals(tstate, localsarray);
     return retval;
 }
 
@@ -5467,7 +5462,7 @@ prtrace(PyThreadState *tstate, PyObject *v, const char *str)
 static void
 call_exc_trace(Py_tracefunc func, PyObject *self,
                PyThreadState *tstate,
-               PyFrameObject *f)
+               InterpreterFrame *f)
 {
     PyObject *type, *value, *traceback, *orig_traceback, *arg;
     int err;
@@ -5497,7 +5492,7 @@ call_exc_trace(Py_tracefunc func, PyObject *self,
 
 static int
 call_trace_protected(Py_tracefunc func, PyObject *obj,
-                     PyThreadState *tstate, PyFrameObject *frame,
+                     PyThreadState *tstate, InterpreterFrame *frame,
                      int what, PyObject *arg)
 {
     PyObject *type, *value, *traceback;
@@ -5518,9 +5513,9 @@ call_trace_protected(Py_tracefunc func, PyObject *obj,
 }
 
 static void
-initialize_trace_info(PyTraceInfo *trace_info, PyFrameObject *frame)
+initialize_trace_info(PyTraceInfo *trace_info, InterpreterFrame *frame)
 {
-    PyCodeObject *code = _PyFrame_GetCode(frame);
+    PyCodeObject *code = frame->f_code;
     if (trace_info->code != code) {
         trace_info->code = code;
         _PyCode_InitAddressRange(code, &trace_info->bounds);
@@ -5529,7 +5524,7 @@ initialize_trace_info(PyTraceInfo *trace_info, PyFrameObject *frame)
 
 static int
 call_trace(Py_tracefunc func, PyObject *obj,
-           PyThreadState *tstate, PyFrameObject *frame,
+           PyThreadState *tstate, InterpreterFrame *frame,
            int what, PyObject *arg)
 {
     int result;
@@ -5537,15 +5532,19 @@ call_trace(Py_tracefunc func, PyObject *obj,
         return 0;
     tstate->tracing++;
     tstate->cframe->use_tracing = 0;
+    PyFrameObject *f = _PyFrame_GetFrameObject(frame);
+    if (f == NULL) {
+        return -1;
+    }
     if (frame->f_lasti < 0) {
-        frame->f_lineno = _PyFrame_GetCode(frame)->co_firstlineno;
+        f->f_lineno = frame->f_code->co_firstlineno;
     }
     else {
         initialize_trace_info(&tstate->trace_info, frame);
-        frame->f_lineno = _PyCode_CheckLineNumber(frame->f_lasti*2, &tstate->trace_info.bounds);
+        f->f_lineno = _PyCode_CheckLineNumber(frame->f_lasti*2, &tstate->trace_info.bounds);
     }
-    result = func(obj, frame, what, arg);
-    frame->f_lineno = 0;
+    result = func(obj, f, what, arg);
+    f->f_lineno = 0;
     tstate->cframe->use_tracing = ((tstate->c_tracefunc != NULL)
                            || (tstate->c_profilefunc != NULL));
     tstate->tracing--;
@@ -5572,7 +5571,7 @@ _PyEval_CallTracing(PyObject *func, PyObject *args)
 /* See Objects/lnotab_notes.txt for a description of how tracing works. */
 static int
 maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
-                      PyThreadState *tstate, PyFrameObject *frame, int instr_prev)
+                      PyThreadState *tstate, InterpreterFrame *frame, int instr_prev)
 {
     int result = 0;
 
@@ -5583,14 +5582,18 @@ maybe_call_line_trace(Py_tracefunc func, PyObject *obj,
     initialize_trace_info(&tstate->trace_info, frame);
     int lastline = _PyCode_CheckLineNumber(instr_prev*2, &tstate->trace_info.bounds);
     int line = _PyCode_CheckLineNumber(frame->f_lasti*2, &tstate->trace_info.bounds);
-    if (line != -1 && frame->f_trace_lines) {
+    PyFrameObject *f = _PyFrame_GetFrameObject(frame);
+    if (f == NULL) {
+        return -1;
+    }
+    if (line != -1 && f->f_trace_lines) {
         /* Trace backward edges or if line number has changed */
         if (frame->f_lasti < instr_prev || line != lastline) {
             result = call_trace(func, obj, tstate, frame, PyTrace_LINE, Py_None);
         }
     }
     /* Always emit an opcode event if we're tracing all opcodes. */
-    if (frame->f_trace_opcodes) {
+    if (f->f_trace_opcodes) {
         result = call_trace(func, obj, tstate, frame, PyTrace_OPCODE, Py_None);
     }
     return result;
@@ -5737,19 +5740,33 @@ _PyEval_GetAsyncGenFinalizer(void)
     return tstate->async_gen_finalizer;
 }
 
+InterpreterFrame *
+_PyEval_GetFrame(void)
+{
+    PyThreadState *tstate = _PyThreadState_GET();
+    return tstate->frame;
+}
+
 PyFrameObject *
 PyEval_GetFrame(void)
 {
     PyThreadState *tstate = _PyThreadState_GET();
-    return tstate->frame;
+    if (tstate->frame == NULL) {
+        return NULL;
+    }
+    PyFrameObject *f = _PyFrame_GetFrameObject(tstate->frame);
+    if (f == NULL) {
+        PyErr_Clear();
+    }
+    return f;
 }
 
 PyObject *
 _PyEval_GetBuiltins(PyThreadState *tstate)
 {
-    PyFrameObject *frame = tstate->frame;
+    InterpreterFrame *frame = tstate->frame;
     if (frame != NULL) {
-        return _PyFrame_GetBuiltins(frame);
+        return frame->f_builtins;
     }
     return tstate->interp->builtins;
 }
@@ -5780,18 +5797,17 @@ PyObject *
 PyEval_GetLocals(void)
 {
     PyThreadState *tstate = _PyThreadState_GET();
-    PyFrameObject *current_frame = tstate->frame;
+     InterpreterFrame *current_frame = tstate->frame;
     if (current_frame == NULL) {
         _PyErr_SetString(tstate, PyExc_SystemError, "frame does not exist");
         return NULL;
     }
 
-    if (PyFrame_FastToLocalsWithError(current_frame) < 0) {
+    if (_PyFrame_FastToLocalsWithError(current_frame) < 0) {
         return NULL;
     }
 
-    PyObject *locals = current_frame->f_valuestack[
-        FRAME_SPECIALS_LOCALS_OFFSET-FRAME_SPECIALS_SIZE];
+    PyObject *locals = current_frame->f_locals;
     assert(locals != NULL);
     return locals;
 }
@@ -5800,22 +5816,22 @@ PyObject *
 PyEval_GetGlobals(void)
 {
     PyThreadState *tstate = _PyThreadState_GET();
-    PyFrameObject *current_frame = tstate->frame;
+    InterpreterFrame *current_frame = tstate->frame;
     if (current_frame == NULL) {
         return NULL;
     }
-    return _PyFrame_GetGlobals(current_frame);
+    return current_frame->f_globals;
 }
 
 int
 PyEval_MergeCompilerFlags(PyCompilerFlags *cf)
 {
     PyThreadState *tstate = _PyThreadState_GET();
-    PyFrameObject *current_frame = tstate->frame;
+    InterpreterFrame *current_frame = tstate->frame;
     int result = cf->cf_flags != 0;
 
     if (current_frame != NULL) {
-        const int codeflags = _PyFrame_GetCode(current_frame)->co_flags;
+        const int codeflags = current_frame->f_code->co_flags;
         const int compilerflags = codeflags & PyCF_MASK;
         if (compilerflags) {
             result = 1;
@@ -6049,22 +6065,21 @@ _PyEval_SliceIndexNotNone(PyObject *v, Py_ssize_t *pi)
 }
 
 static PyObject *
-import_name(PyThreadState *tstate, PyFrameObject *f,
+import_name(PyThreadState *tstate, InterpreterFrame *frame,
             PyObject *name, PyObject *fromlist, PyObject *level)
 {
     _Py_IDENTIFIER(__import__);
     PyObject *import_func, *res;
     PyObject* stack[5];
 
-    import_func = _PyDict_GetItemIdWithError(_PyFrame_GetBuiltins(f), &PyId___import__);
+    import_func = _PyDict_GetItemIdWithError(frame->f_builtins, &PyId___import__);
     if (import_func == NULL) {
         if (!_PyErr_Occurred(tstate)) {
             _PyErr_SetString(tstate, PyExc_ImportError, "__import__ not found");
         }
         return NULL;
     }
-    PyObject *locals = f->f_valuestack[
-        FRAME_SPECIALS_LOCALS_OFFSET-FRAME_SPECIALS_SIZE];
+    PyObject *locals = frame->f_locals;
     /* Fast path for not overloaded __import__. */
     if (import_func == tstate->interp->import_func) {
         int ilevel = _PyLong_AsInt(level);
@@ -6073,7 +6088,7 @@ import_name(PyThreadState *tstate, PyFrameObject *f,
         }
         res = PyImport_ImportModuleLevelObject(
                         name,
-                        _PyFrame_GetGlobals(f),
+                        frame->f_globals,
                         locals == NULL ? Py_None :locals,
                         fromlist,
                         ilevel);
@@ -6083,7 +6098,7 @@ import_name(PyThreadState *tstate, PyFrameObject *f,
     Py_INCREF(import_func);
 
     stack[0] = name;
-    stack[1] = _PyFrame_GetGlobals(f);
+    stack[1] = frame->f_globals;
     stack[2] = locals == NULL ? Py_None : locals;
     stack[3] = fromlist;
     stack[4] = level;
@@ -6389,7 +6404,7 @@ format_awaitable_error(PyThreadState *tstate, PyTypeObject *type, int prevprevop
 
 static PyObject *
 unicode_concatenate(PyThreadState *tstate, PyObject *v, PyObject *w,
-                    PyFrameObject *f, const _Py_CODEUNIT *next_instr)
+                    InterpreterFrame *frame, const _Py_CODEUNIT *next_instr)
 {
     PyObject *res;
     if (Py_REFCNT(v) == 2) {
@@ -6404,14 +6419,14 @@ unicode_concatenate(PyThreadState *tstate, PyObject *v, PyObject *w,
         switch (opcode) {
         case STORE_FAST:
         {
-            PyObject **localsplus = f->f_localsptr;
+            PyObject **localsplus = _PyFrame_GetLocalsArray(frame);
             if (GETLOCAL(oparg) == v)
                 SETLOCAL(oparg, NULL);
             break;
         }
         case STORE_DEREF:
         {
-            PyObject *c = f->f_localsptr[oparg];
+            PyObject *c = _PyFrame_GetLocalsArray(frame)[oparg];
             if (PyCell_GET(c) ==  v) {
                 PyCell_SET(c, NULL);
                 Py_DECREF(v);
@@ -6420,10 +6435,9 @@ unicode_concatenate(PyThreadState *tstate, PyObject *v, PyObject *w,
         }
         case STORE_NAME:
         {
-            PyObject *names = _PyFrame_GetCode(f)->co_names;
+            PyObject *names = frame->f_code->co_names;
             PyObject *name = GETITEM(names, oparg);
-            PyObject *locals = f->f_valuestack[
-                FRAME_SPECIALS_LOCALS_OFFSET-FRAME_SPECIALS_SIZE];
+            PyObject *locals = frame->f_locals;
             if (locals && PyDict_CheckExact(locals)) {
                 PyObject *w = PyDict_GetItemWithError(locals, name);
                 if ((w == v && PyDict_DelItem(locals, name) != 0) ||
@@ -6501,38 +6515,38 @@ _PyEval_RequestCodeExtraIndex(freefunc free)
 }
 
 static void
-dtrace_function_entry(PyFrameObject *f)
+dtrace_function_entry(InterpreterFrame *frame)
 {
     const char *filename;
     const char *funcname;
     int lineno;
 
-    PyCodeObject *code = _PyFrame_GetCode(f);
+    PyCodeObject *code = frame->f_code;
     filename = PyUnicode_AsUTF8(code->co_filename);
     funcname = PyUnicode_AsUTF8(code->co_name);
-    lineno = PyFrame_GetLineNumber(f);
+    lineno = PyCode_Addr2Line(frame->f_code, frame->f_lasti*2);
 
     PyDTrace_FUNCTION_ENTRY(filename, funcname, lineno);
 }
 
 static void
-dtrace_function_return(PyFrameObject *f)
+dtrace_function_return(InterpreterFrame *frame)
 {
     const char *filename;
     const char *funcname;
     int lineno;
 
-    PyCodeObject *code = _PyFrame_GetCode(f);
+    PyCodeObject *code = frame->f_code;
     filename = PyUnicode_AsUTF8(code->co_filename);
     funcname = PyUnicode_AsUTF8(code->co_name);
-    lineno = PyFrame_GetLineNumber(f);
+    lineno = PyCode_Addr2Line(frame->f_code, frame->f_lasti*2);
 
     PyDTrace_FUNCTION_RETURN(filename, funcname, lineno);
 }
 
 /* DTrace equivalent of maybe_call_line_trace. */
 static void
-maybe_dtrace_line(PyFrameObject *frame,
+maybe_dtrace_line(InterpreterFrame *frame,
                   PyTraceInfo *trace_info,
                   int instr_prev)
 {
@@ -6542,25 +6556,26 @@ maybe_dtrace_line(PyFrameObject *frame,
        instruction window, reset the window.
     */
     initialize_trace_info(trace_info, frame);
+    int lastline = _PyCode_CheckLineNumber(instr_prev*2, &trace_info->bounds);
     int line = _PyCode_CheckLineNumber(frame->f_lasti*2, &trace_info->bounds);
-    /* If the last instruction falls at the start of a line or if
-       it represents a jump backwards, update the frame's line
-       number and call the trace function. */
-    if (line != frame->f_lineno || frame->f_lasti < instr_prev) {
-        if (line != -1) {
-            frame->f_lineno = line;
-            co_filename = PyUnicode_AsUTF8(_PyFrame_GetCode(frame)->co_filename);
-            if (!co_filename)
+    if (line != -1) {
+        /* Trace backward edges or first instruction of a new line */
+        if (frame->f_lasti < instr_prev ||
+            (line != lastline && frame->f_lasti*2 == trace_info->bounds.ar_start))
+        {
+            co_filename = PyUnicode_AsUTF8(frame->f_code->co_filename);
+            if (!co_filename) {
                 co_filename = "?";
-            co_name = PyUnicode_AsUTF8(_PyFrame_GetCode(frame)->co_name);
-            if (!co_name)
+            }
+            co_name = PyUnicode_AsUTF8(frame->f_code->co_name);
+            if (!co_name) {
                 co_name = "?";
+            }
             PyDTrace_LINE(co_filename, co_name, line);
         }
     }
 }
 
-
 /* Implement Py_EnterRecursiveCall() and Py_LeaveRecursiveCall() as functions
    for the limited API. */
 
diff --git a/Python/errors.c b/Python/errors.c
index 118118ffe90b44..1f84215a136a49 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -1406,12 +1406,13 @@ _PyErr_WriteUnraisableMsg(const char *err_msg_str, PyObject *obj)
     }
 
     if (exc_tb == NULL) {
-        PyFrameObject *frame = tstate->frame;
+        PyFrameObject *frame = PyThreadState_GetFrame(tstate);
         if (frame != NULL) {
             exc_tb = _PyTraceBack_FromFrame(NULL, frame);
             if (exc_tb == NULL) {
                 _PyErr_Clear(tstate);
             }
+            Py_DECREF(frame);
         }
     }
 
diff --git a/Python/frame.c b/Python/frame.c
new file mode 100644
index 00000000000000..ae4284346ea12f
--- /dev/null
+++ b/Python/frame.c
@@ -0,0 +1,135 @@
+
+#include "Python.h"
+#include "frameobject.h"
+#include "pycore_frame.h"
+#include "pycore_object.h"        // _PyObject_GC_UNTRACK()
+
+int
+_PyFrame_Traverse(InterpreterFrame *frame, visitproc visit, void *arg)
+{
+    Py_VISIT(frame->frame_obj);
+    Py_VISIT(frame->f_globals);
+    Py_VISIT(frame->f_builtins);
+    Py_VISIT(frame->f_locals);
+    Py_VISIT(frame->f_code);
+   /* locals */
+    PyObject **locals = _PyFrame_GetLocalsArray(frame);
+    for (int i = 0; i < frame->nlocalsplus; i++) {
+        Py_VISIT(locals[i]);
+    }
+    /* stack */
+    for (int i = 0; i <frame->stackdepth; i++) {
+        Py_VISIT(frame->stack[i]);
+    }
+    return 0;
+}
+
+PyFrameObject *
+_PyFrame_MakeAndSetFrameObject(InterpreterFrame *frame)
+{
+    assert(frame->frame_obj == NULL);
+    PyObject *error_type, *error_value, *error_traceback;
+    PyErr_Fetch(&error_type, &error_value, &error_traceback);
+    PyFrameObject *f = _PyFrame_New_NoTrack(frame, 0);
+    if (f == NULL) {
+        Py_XDECREF(error_type);
+        Py_XDECREF(error_value);
+        Py_XDECREF(error_traceback);
+    }
+    else {
+        PyErr_Restore(error_type, error_value, error_traceback);
+    }
+    frame->frame_obj = f;
+    return f;
+}
+
+
+static InterpreterFrame *
+copy_frame_to_heap(InterpreterFrame *frame)
+{
+
+    Py_ssize_t size = ((char*)&frame->stack[frame->stackdepth]) - (char *)_PyFrame_GetLocalsArray(frame);
+    PyObject **copy = PyMem_Malloc(size);
+    if (copy == NULL) {
+        PyErr_NoMemory();
+        return NULL;
+    }
+    PyObject **locals = _PyFrame_GetLocalsArray(frame);
+    memcpy(copy, locals, size);
+    InterpreterFrame *res = (InterpreterFrame *)(copy + frame->nlocalsplus);
+    return res;
+}
+
+static inline void
+clear_specials(InterpreterFrame *frame)
+{
+    frame->generator = NULL;
+    Py_XDECREF(frame->frame_obj);
+    Py_XDECREF(frame->f_locals);
+    Py_DECREF(frame->f_globals);
+    Py_DECREF(frame->f_builtins);
+    Py_DECREF(frame->f_code);
+}
+
+static void
+take_ownership(PyFrameObject *f, InterpreterFrame *frame)
+{
+    assert(f->f_own_locals_memory == 0);
+    assert(frame->frame_obj == NULL);
+
+    f->f_own_locals_memory = 1;
+    f->f_frame = frame;
+    assert(f->f_back == NULL);
+    if (frame->previous != NULL) {
+        /* Link PyFrameObjects.f_back and remove link through InterpreterFrame.previous */
+        PyFrameObject *back = _PyFrame_GetFrameObject(frame->previous);
+        if (back == NULL) {
+            /* Memory error here. */
+            assert(PyErr_ExceptionMatches(PyExc_MemoryError));
+            /* Nothing we can do about it */
+            PyErr_Clear();
+            _PyErr_WriteUnraisableMsg("Out of memory lazily allocating frame->f_back", NULL);
+        }
+        else {
+            f->f_back = (PyFrameObject *)Py_NewRef(back);
+        }
+        frame->previous = NULL;
+    }
+    if (!_PyObject_GC_IS_TRACKED((PyObject *)f)) {
+        _PyObject_GC_TRACK((PyObject *)f);
+    }
+}
+
+int
+_PyFrame_Clear(InterpreterFrame * frame, int take)
+{
+    PyObject **localsarray = ((PyObject **)frame)-frame->nlocalsplus;
+    if (frame->frame_obj) {
+        PyFrameObject *f = frame->frame_obj;
+        frame->frame_obj = NULL;
+        if (Py_REFCNT(f) > 1) {
+            if (!take) {
+                frame = copy_frame_to_heap(frame);
+                if (frame == NULL) {
+                    return -1;
+                }
+            }
+            take_ownership(f, frame);
+            Py_DECREF(f);
+            return 0;
+        }
+        Py_DECREF(f);
+    }
+    for (int i = 0; i < frame->nlocalsplus; i++) {
+        Py_XDECREF(localsarray[i]);
+    }
+    assert(frame->stackdepth >= 0);
+    for (int i = 0; i < frame->stackdepth; i++) {
+        Py_DECREF(frame->stack[i]);
+    }
+    clear_specials(frame);
+    if (take) {
+        PyMem_Free(_PyFrame_GetLocalsArray(frame));
+    }
+    return 0;
+}
diff --git a/Python/pystate.c b/Python/pystate.c
index a94a615f39569f..6a15b54d1dd84f 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -3,6 +3,7 @@
 
 #include "Python.h"
 #include "pycore_ceval.h"
+#include "pycore_frame.h"
 #include "pycore_initconfig.h"
 #include "pycore_object.h"        // _PyType_InitCache()
 #include "pycore_pyerrors.h"
@@ -685,7 +686,7 @@ new_threadstate(PyInterpreterState *interp, int init)
         PyMem_RawFree(tstate);
         return NULL;
     }
-    /* If top points to entry 0, then _PyThreadState_PopLocals will try to pop this chunk */
+    /* If top points to entry 0, then _PyThreadState_PopFrame will try to pop this chunk */
     tstate->datastack_top = &tstate->datastack_chunk->data[1];
     tstate->datastack_limit = (PyObject **)(((char *)tstate->datastack_chunk) + DATA_STACK_CHUNK_SIZE);
     /* Mark trace_info as uninitialized */
@@ -872,7 +873,7 @@ PyThreadState_Clear(PyThreadState *tstate)
           "PyThreadState_Clear: warning: thread still has a frame\n");
     }
 
-    /* Don't clear tstate->frame: it is a borrowed reference */
+    /* Don't clear tstate->pyframe: it is a borrowed reference */
 
     Py_CLEAR(tstate->dict);
     Py_CLEAR(tstate->async_exc);
@@ -1133,7 +1134,13 @@ PyFrameObject*
 PyThreadState_GetFrame(PyThreadState *tstate)
 {
     assert(tstate != NULL);
-    PyFrameObject *frame = tstate->frame;
+    if (tstate->frame == NULL) {
+        return NULL;
+    }
+    PyFrameObject *frame = _PyFrame_GetFrameObject(tstate->frame);
+    if (frame == NULL) {
+        PyErr_Clear();
+    }
     Py_XINCREF(frame);
     return frame;
 }
@@ -1254,7 +1261,7 @@ _PyThread_CurrentFrames(void)
     for (i = runtime->interpreters.head; i != NULL; i = i->next) {
         PyThreadState *t;
         for (t = i->tstate_head; t != NULL; t = t->next) {
-            PyFrameObject *frame = t->frame;
+            InterpreterFrame *frame = t->frame;
             if (frame == NULL) {
                 continue;
             }
@@ -1262,7 +1269,7 @@ _PyThread_CurrentFrames(void)
             if (id == NULL) {
                 goto fail;
             }
-            int stat = PyDict_SetItem(result, id, (PyObject *)frame);
+            int stat = PyDict_SetItem(result, id, (PyObject *)_PyFrame_GetFrameObject(frame));
             Py_DECREF(id);
             if (stat < 0) {
                 goto fail;
@@ -2009,42 +2016,58 @@ _Py_GetConfig(void)
 
 #define MINIMUM_OVERHEAD 1000
 
-PyObject **
-_PyThreadState_PushLocals(PyThreadState *tstate, int size)
+static PyObject **
+push_chunk(PyThreadState *tstate, int size)
+{
+    assert(tstate->datastack_top + size >= tstate->datastack_limit);
+
+    int allocate_size = DATA_STACK_CHUNK_SIZE;
+    while (allocate_size < (int)sizeof(PyObject*)*(size + MINIMUM_OVERHEAD)) {
+        allocate_size *= 2;
+    }
+    _PyStackChunk *new = allocate_chunk(allocate_size, tstate->datastack_chunk);
+    if (new == NULL) {
+        return NULL;
+    }
+    tstate->datastack_chunk->top = tstate->datastack_top - &tstate->datastack_chunk->data[0];
+    tstate->datastack_chunk = new;
+    tstate->datastack_limit = (PyObject **)(((char *)new) + allocate_size);
+    PyObject **res = &new->data[0];
+    tstate->datastack_top = res + size;
+    return res;
+}
+
+InterpreterFrame *
+_PyThreadState_PushFrame(PyThreadState *tstate, PyFrameConstructor *con, PyObject *locals)
 {
-    assert(((unsigned)size) < INT_MAX/sizeof(PyObject*)/2);
-    PyObject **res = tstate->datastack_top;
-    PyObject **top = res + size;
+    PyCodeObject *code = (PyCodeObject *)con->fc_code;
+    int nlocalsplus = code->co_nlocalsplus;
+    size_t size = nlocalsplus + code->co_stacksize +
+        FRAME_SPECIALS_SIZE;
+    assert(size < INT_MAX/sizeof(PyObject *));
+    PyObject **localsarray = tstate->datastack_top;
+    PyObject **top = localsarray + size;
     if (top >= tstate->datastack_limit) {
-        int allocate_size = DATA_STACK_CHUNK_SIZE;
-        while (allocate_size < (int)sizeof(PyObject*)*(size + MINIMUM_OVERHEAD)) {
-            allocate_size *= 2;
-        }
-        _PyStackChunk *new = allocate_chunk(allocate_size, tstate->datastack_chunk);
-        if (new == NULL) {
-            goto error;
+        localsarray = push_chunk(tstate, (int)size);
+        if (localsarray == NULL) {
+            return NULL;
         }
-        tstate->datastack_chunk->top = tstate->datastack_top - &tstate->datastack_chunk->data[0];
-        tstate->datastack_chunk = new;
-        tstate->datastack_limit = (PyObject **)(((char *)new) + allocate_size);
-        res = &new->data[0];
-        tstate->datastack_top = res + size;
     }
     else {
         tstate->datastack_top = top;
     }
-    for (int i=0; i < size; i++) {
-        res[i] = NULL;
+    InterpreterFrame * frame = (InterpreterFrame *)(localsarray + nlocalsplus);
+    _PyFrame_InitializeSpecials(frame, con, locals, nlocalsplus);
+    for (int i=0; i < nlocalsplus; i++) {
+        localsarray[i] = NULL;
     }
-    return res;
-error:
-    _PyErr_SetString(tstate, PyExc_MemoryError, "Out of memory");
-    return NULL;
+    return frame;
 }
 
 void
-_PyThreadState_PopLocals(PyThreadState *tstate, PyObject **locals)
+_PyThreadState_PopFrame(PyThreadState *tstate, InterpreterFrame * frame)
 {
+    PyObject **locals = _PyFrame_GetLocalsArray(frame);
     if (locals == &tstate->datastack_chunk->data[0]) {
         _PyStackChunk *chunk = tstate->datastack_chunk;
         _PyStackChunk *previous = chunk->previous;
diff --git a/Python/suggestions.c b/Python/suggestions.c
index 77bdb6c1658618..81976ff4f2eb41 100644
--- a/Python/suggestions.c
+++ b/Python/suggestions.c
@@ -232,7 +232,7 @@ offer_suggestions_for_name_error(PyNameErrorObject *exc)
         return suggestions;
     }
 
-    dir = PySequence_List(_PyFrame_GetGlobals(frame));
+    dir = PySequence_List(frame->f_frame->f_globals);
     if (dir == NULL) {
         return NULL;
     }
@@ -242,7 +242,7 @@ offer_suggestions_for_name_error(PyNameErrorObject *exc)
         return suggestions;
     }
 
-    dir = PySequence_List(_PyFrame_GetBuiltins(frame));
+    dir = PySequence_List(frame->f_frame->f_builtins);
     if (dir == NULL) {
         return NULL;
     }
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index f809e2fda56044..5dfa917e8ffb20 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -29,6 +29,7 @@ Data members:
 
 #include "code.h"
 #include "frameobject.h"          // PyFrame_GetBack()
+#include "pycore_frame.h"
 #include "pydtrace.h"
 #include "osdefs.h"               // DELIM
 #include "stdlib_module_names.h"  // _Py_stdlib_module_names
@@ -1814,25 +1815,22 @@ sys__getframe_impl(PyObject *module, int depth)
 /*[clinic end generated code: output=d438776c04d59804 input=c1be8a6464b11ee5]*/
 {
     PyThreadState *tstate = _PyThreadState_GET();
-    PyFrameObject *f = PyThreadState_GetFrame(tstate);
+    InterpreterFrame *frame = tstate->frame;
 
-    if (_PySys_Audit(tstate, "sys._getframe", "O", f) < 0) {
-        Py_DECREF(f);
+    if (_PySys_Audit(tstate, "sys._getframe", NULL) < 0) {
         return NULL;
     }
 
-    while (depth > 0 && f != NULL) {
-        PyFrameObject *back = PyFrame_GetBack(f);
-        Py_DECREF(f);
-        f = back;
+    while (depth > 0 && frame != NULL) {
+        frame = frame->previous;
         --depth;
     }
-    if (f == NULL) {
+    if (frame == NULL) {
         _PyErr_SetString(tstate, PyExc_ValueError,
                          "call stack is not deep enough");
         return NULL;
     }
-    return (PyObject*)f;
+    return _Py_XNewRef((PyObject *)_PyFrame_GetFrameObject(frame));
 }
 
 /*[clinic input]
diff --git a/Python/traceback.c b/Python/traceback.c
index 6d230138a3c777..204121ba66d96e 100644
--- a/Python/traceback.c
+++ b/Python/traceback.c
@@ -240,7 +240,7 @@ _PyTraceBack_FromFrame(PyObject *tb_next, PyFrameObject *frame)
     assert(tb_next == NULL || PyTraceBack_Check(tb_next));
     assert(frame != NULL);
 
-    return tb_create_raw((PyTracebackObject *)tb_next, frame, frame->f_lasti*2,
+    return tb_create_raw((PyTracebackObject *)tb_next, frame, frame->f_frame->f_lasti*2,
                          PyFrame_GetLineNumber(frame));
 }
 
@@ -521,7 +521,7 @@ _Py_DisplaySourceLine(PyObject *f, PyObject *filename, int lineno, int indent, i
  * When displaying a new traceback line, for certain syntactical constructs
  * (e.g a subscript, an arithmetic operation) we try to create a representation
  * that separates the primary source of error from the rest.
- * 
+ *
  * Example specialization of BinOp nodes:
  *  Traceback (most recent call last):
  *    File "/home/isidentical/cpython/cpython/t.py", line 10, in <module>
@@ -710,7 +710,7 @@ tb_displayline(PyTracebackObject* tb, PyObject *f, PyObject *filename, int linen
     }
 
     int code_offset = tb->tb_lasti;
-    PyCodeObject* code = _PyFrame_GetCode(frame);
+    PyCodeObject* code = frame->f_frame->f_code;
 
     int start_line;
     int end_line;
@@ -1024,9 +1024,9 @@ _Py_DumpASCII(int fd, PyObject *text)
    This function is signal safe. */
 
 static void
-dump_frame(int fd, PyFrameObject *frame)
+dump_frame(int fd, InterpreterFrame *frame)
 {
-    PyCodeObject *code = PyFrame_GetCode(frame);
+    PyCodeObject *code = frame->f_code;
     PUTS(fd, "  File ");
     if (code->co_filename != NULL
         && PyUnicode_Check(code->co_filename))
@@ -1038,7 +1038,7 @@ dump_frame(int fd, PyFrameObject *frame)
         PUTS(fd, "???");
     }
 
-    int lineno = PyFrame_GetLineNumber(frame);
+    int lineno = PyCode_Addr2Line(code, frame->f_lasti*2);
     PUTS(fd, ", line ");
     if (lineno >= 0) {
         _Py_DumpDecimal(fd, (size_t)lineno);
@@ -1057,20 +1057,19 @@ dump_frame(int fd, PyFrameObject *frame)
     }
 
     PUTS(fd, "\n");
-    Py_DECREF(code);
 }
 
 static void
 dump_traceback(int fd, PyThreadState *tstate, int write_header)
 {
-    PyFrameObject *frame;
+    InterpreterFrame *frame;
     unsigned int depth;
 
     if (write_header) {
         PUTS(fd, "Stack (most recent call first):\n");
     }
 
-    frame = PyThreadState_GetFrame(tstate);
+    frame = tstate->frame;
     if (frame == NULL) {
         PUTS(fd, "<no Python frame>\n");
         return;
@@ -1079,22 +1078,14 @@ dump_traceback(int fd, PyThreadState *tstate, int write_header)
     depth = 0;
     while (1) {
         if (MAX_FRAME_DEPTH <= depth) {
-            Py_DECREF(frame);
             PUTS(fd, "  ...\n");
             break;
         }
-        if (!PyFrame_Check(frame)) {
-            Py_DECREF(frame);
-            break;
-        }
         dump_frame(fd, frame);
-        PyFrameObject *back = PyFrame_GetBack(frame);
-        Py_DECREF(frame);
-
-        if (back == NULL) {
+        frame = frame->previous;
+        if (frame == NULL) {
             break;
         }
-        frame = back;
         depth++;
     }
 }
diff --git a/Tools/gdb/libpython.py b/Tools/gdb/libpython.py
index 0198500265be41..8b09563c18c9b1 100755
--- a/Tools/gdb/libpython.py
+++ b/Tools/gdb/libpython.py
@@ -854,28 +854,102 @@ class PyNoneStructPtr(PyObjectPtr):
     def proxyval(self, visited):
         return None
 
-FRAME_SPECIALS_GLOBAL_OFFSET = 0
-FRAME_SPECIALS_BUILTINS_OFFSET = 1
-FRAME_SPECIALS_CODE_OFFSET = 3
-FRAME_SPECIALS_SIZE = 4
-
 class PyFrameObjectPtr(PyObjectPtr):
     _typename = 'PyFrameObject'
 
     def __init__(self, gdbval, cast_to=None):
         PyObjectPtr.__init__(self, gdbval, cast_to)
 
+        if not self.is_optimized_out():
+            self._frame = PyFramePtr(self.field('f_frame'))
+
+    def iter_locals(self):
+        '''
+        Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
+        the local variables of this frame
+        '''
+        if self.is_optimized_out():
+            return
+        return self._frame.iter_locals()
+
+    def iter_globals(self):
+        '''
+        Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
+        the global variables of this frame
+        '''
+        if self.is_optimized_out():
+            return ()
+        return self._frame.iter_globals()
+
+    def iter_builtins(self):
+        '''
+        Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
+        the builtin variables
+        '''
+        if self.is_optimized_out():
+            return ()
+        return self._frame.iter_builtins()
+
+    def get_var_by_name(self, name):
+
+        if self.is_optimized_out():
+            return None, None
+        return self._frame.get_var_by_name(name)
+
+    def filename(self):
+        '''Get the path of the current Python source file, as a string'''
+        if self.is_optimized_out():
+            return FRAME_INFO_OPTIMIZED_OUT
+        return self._frame.filename()
+
+    def current_line_num(self):
+        '''Get current line number as an integer (1-based)
+
+        Translated from PyFrame_GetLineNumber and PyCode_Addr2Line
+
+        See Objects/lnotab_notes.txt
+        '''
+        if self.is_optimized_out():
+            return None
+        return self._frame.current_line_num()
+
+    def current_line(self):
+        '''Get the text of the current source line as a string, with a trailing
+        newline character'''
+        if self.is_optimized_out():
+            return FRAME_INFO_OPTIMIZED_OUT
+        return self._frame.current_line()
+
+    def write_repr(self, out, visited):
+        if self.is_optimized_out():
+            out.write(FRAME_INFO_OPTIMIZED_OUT)
+            return
+        return self._frame.write_repr(out, visited)
+
+    def print_traceback(self):
+        if self.is_optimized_out():
+            sys.stdout.write('  %s\n' % FRAME_INFO_OPTIMIZED_OUT)
+            return
+        return self._frame.print_traceback()
+
+class PyFramePtr:
+
+    def __init__(self, gdbval):
+        self._gdbval = gdbval
+
         if not self.is_optimized_out():
             self.co = self._f_code()
             self.co_name = self.co.pyop_field('co_name')
             self.co_filename = self.co.pyop_field('co_filename')
 
-            self.f_lineno = int_from_int(self.field('f_lineno'))
-            self.f_lasti = int_from_int(self.field('f_lasti'))
+            self.f_lasti = self._f_lasti()
             self.co_nlocals = int_from_int(self.co.field('co_nlocals'))
             pnames = self.co.field('co_localsplusnames')
             self.co_localsplusnames = PyTupleObjectPtr.from_pyobject_ptr(pnames)
 
+    def is_optimized_out(self):
+        return self._gdbval.is_optimized_out
+
     def iter_locals(self):
         '''
         Yield a sequence of (name,value) pairs of PyObjectPtr instances, for
@@ -884,26 +958,34 @@ def iter_locals(self):
         if self.is_optimized_out():
             return
 
-        f_localsplus = self.field('f_localsptr')
+        obj_ptr_ptr = gdb.lookup_type("PyObject").pointer().pointer()
+        base = self._gdbval.cast(obj_ptr_ptr)
+        localsplus = base - self._f_nlocalsplus()
         for i in safe_range(self.co_nlocals):
-            pyop_value = PyObjectPtr.from_pyobject_ptr(f_localsplus[i])
+            pyop_value = PyObjectPtr.from_pyobject_ptr(localsplus[i])
             if pyop_value.is_null():
                 continue
             pyop_name = PyObjectPtr.from_pyobject_ptr(self.co_localsplusnames[i])
             yield (pyop_name, pyop_value)
 
-    def _f_specials(self, index, cls=PyObjectPtr):
-        f_valuestack = self.field('f_valuestack')
-        return cls.from_pyobject_ptr(f_valuestack[index - FRAME_SPECIALS_SIZE])
+    def _f_special(self, name, convert=PyObjectPtr.from_pyobject_ptr):
+        return convert(self._gdbval[name])
 
     def _f_globals(self):
-        return self._f_specials(FRAME_SPECIALS_GLOBAL_OFFSET)
+        return self._f_special("f_globals")
 
     def _f_builtins(self):
-        return self._f_specials(FRAME_SPECIALS_BUILTINS_OFFSET)
+        return self._f_special("f_builtins")
 
     def _f_code(self):
-        return self._f_specials(FRAME_SPECIALS_CODE_OFFSET, PyCodeObjectPtr)
+        return self._f_special("f_code", PyCodeObjectPtr.from_pyobject_ptr)
+
+    def _f_nlocalsplus(self):
+        return self._f_special("nlocalsplus", int_from_int)
+
+    def _f_lasti(self):
+        return self._f_special("f_lasti", int_from_int)
+
 
     def iter_globals(self):
         '''
@@ -960,11 +1042,6 @@ def current_line_num(self):
         '''
         if self.is_optimized_out():
             return None
-        f_trace = self.field('f_trace')
-        if long(f_trace) != 0:
-            # we have a non-NULL f_trace:
-            return self.f_lineno
-
         try:
             return self.co.addr2line(self.f_lasti*2)
         except Exception:
@@ -1021,6 +1098,9 @@ def write_repr(self, out, visited):
 
         out.write(')')
 
+    def as_address(self):
+        return long(self._gdbval)
+
     def print_traceback(self):
         if self.is_optimized_out():
             sys.stdout.write('  %s\n' % FRAME_INFO_OPTIMIZED_OUT)
@@ -1033,6 +1113,21 @@ def print_traceback(self):
                      lineno,
                      self.co_name.proxyval(visited)))
 
+    def get_truncated_repr(self, maxlen):
+        '''
+        Get a repr-like string for the data, but truncate it at "maxlen" bytes
+        (ending the object graph traversal as soon as you do)
+        '''
+        out = TruncatedStringIO(maxlen)
+        try:
+            self.write_repr(out, set())
+        except StringTruncated:
+            # Truncation occurred:
+            return out.getvalue() + '...(truncated)'
+
+        # No truncation occurred:
+        return out.getvalue()
+
 class PySetObjectPtr(PyObjectPtr):
     _typename = 'PySetObject'
 
@@ -1638,18 +1733,18 @@ def is_gc_collect(self):
 
     def get_pyop(self):
         try:
-            f = self._gdbframe.read_var('f')
-            frame = PyFrameObjectPtr.from_pyobject_ptr(f)
+            frame = self._gdbframe.read_var('frame')
+            frame = PyFramePtr(frame)
             if not frame.is_optimized_out():
                 return frame
-            # gdb is unable to get the "f" argument of PyEval_EvalFrameEx()
-            # because it was "optimized out". Try to get "f" from the frame
-            # of the caller, PyEval_EvalCodeEx().
+            # gdb is unable to get the "frame" argument of PyEval_EvalFrameEx()
+            # because it was "optimized out". Try to get "frame" from the frame
+            # of the caller, _PyEval_Vector().
             orig_frame = frame
             caller = self._gdbframe.older()
             if caller:
-                f = caller.read_var('f')
-                frame = PyFrameObjectPtr.from_pyobject_ptr(f)
+                frame = caller.read_var('frame')
+                frame = PyFramePtr(frame)
                 if not frame.is_optimized_out():
                     return frame
             return orig_frame



More information about the Python-checkins mailing list