Skip to content

Commit c3e77d1

Browse files
williamwen42pytorchmergebot
authored andcommitted
[3.12, 3.13, dynamo] simplified construction for frame f_locals/localsplus (pytorch#129185)
Construct frame localsplus in 3.12+ using our own simplified way rather than copypasting from CPython. This is necessary for 3.13 since we can no longer generate frame `f_locals` before executing the interpreter frame. We also enable this for 3.12 since the `f_locals` construction between 3.12 and 3.13 is the same, so we can test for correctness with 3.12. This is also one of the first steps to completing pytorch#93753 - we will implement simplified f_locals generation of previous Python versions in the future. Pull Request resolved: pytorch#129185 Approved by: https://github.com/jansel
1 parent b0a597f commit c3e77d1

20 files changed

+189
-245
lines changed

build_variables.bzl

+1
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,7 @@ libtorch_python_core_sources = [
828828
"torch/csrc/dynamo/cpython_defs.c",
829829
"torch/csrc/dynamo/eval_frame.c",
830830
"torch/csrc/dynamo/extra_state.cpp",
831+
"torch/csrc/dynamo/framelocals_mapping.cpp",
831832
"torch/csrc/dynamo/guards.cpp",
832833
"torch/csrc/dynamo/init.cpp",
833834
"torch/csrc/functorch/init.cpp",

torch/csrc/dynamo/cpython_defs.c

+5-205
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,12 @@
11
#include <torch/csrc/dynamo/cpython_defs.h>
2-
3-
#ifdef _WIN32
4-
#define unlikely(x) (x)
5-
#else
6-
#define unlikely(x) __builtin_expect((x), 0)
7-
#endif
8-
9-
#define CHECK(cond) \
10-
if (unlikely(!(cond))) { \
11-
fprintf(stderr, "DEBUG CHECK FAILED: %s:%d\n", __FILE__, __LINE__); \
12-
abort(); \
13-
} else { \
14-
}
2+
#include <torch/csrc/dynamo/cpython_includes.h>
3+
#include <torch/csrc/dynamo/debug_macros.h>
154

165
#if IS_PYTHON_3_11_PLUS
176

18-
// Problem in CPython includes when mixing core and non-core build
19-
// The fix was not backported to 3.12 so this is needed here
20-
// https://github.com/python/cpython/issues/105268
21-
#if IS_PYTHON_3_12_PLUS
22-
#undef _PyGC_FINALIZED
23-
#endif
24-
257
#define Py_BUILD_CORE
26-
#include <internal/pycore_pystate.h>
27-
288
#define NEED_OPCODE_TABLES // To get _PyOpcode_Deopt, _PyOpcode_Caches
9+
2910
#if IS_PYTHON_3_13_PLUS
3011
#include <cpython/code.h> // To get PyUnstable_Code_GetFirstFree
3112
#define NEED_OPCODE_METADATA
@@ -34,10 +15,8 @@
3415
#else
3516
#include <internal/pycore_opcode.h>
3617
#endif
37-
#undef NEED_OPCODE_TABLES
38-
39-
#include <internal/pycore_frame.h>
4018

19+
#undef NEED_OPCODE_TABLES
4120
#undef Py_BUILD_CORE
4221

4322
// As a simple way to reduce the impact of ABI changes on the CPython side, this check forces
@@ -74,189 +53,10 @@ THP_PyFrame_OpAlreadyRan(_PyInterpreterFrame *frame, int opcode, int oparg)
7453

7554
#if IS_PYTHON_3_12_PLUS
7655

77-
// https://github.com/python/cpython/blob/0325a8a8cdba6c091bcbbb3c995f3bf1d1217012/Objects/frameobject.c#L1136
78-
// Initialize frame free variables if needed
79-
// free_vars_copied argument added in order to let caller know that the COPY_FREE_VARS
80-
// codepath occurred.
81-
static void
82-
frame_init_get_vars(_PyInterpreterFrame *frame, int *free_vars_copied)
83-
{
84-
// COPY_FREE_VARS has no quickened forms, so no need to use _PyOpcode_Deopt
85-
// here:
86-
PyCodeObject *co = F_CODE(frame);
87-
int lasti = _PyInterpreterFrame_LASTI(frame);
88-
if (!(lasti < 0 && _PyCode_CODE(co)->op.code == COPY_FREE_VARS
89-
&& PyFunction_Check(frame->f_funcobj)))
90-
{
91-
/* Free vars are initialized */
92-
return;
93-
}
94-
95-
/* Free vars have not been initialized -- Do that */
96-
PyObject *closure = ((PyFunctionObject *)frame->f_funcobj)->func_closure;
97-
#if IS_PYTHON_3_13_PLUS
98-
int offset = PyUnstable_Code_GetFirstFree(co);
99-
#else
100-
int offset = PyCode_GetFirstFree(co);
101-
#endif
102-
for (int i = 0; i < co->co_nfreevars; ++i) {
103-
PyObject *o = PyTuple_GET_ITEM(closure, i);
104-
frame->localsplus[offset + i] = Py_NewRef(o);
105-
}
106-
// COPY_FREE_VARS doesn't have inline CACHEs, either:
107-
PREV_INSTR(frame) = _PyCode_CODE(F_CODE(frame));
108-
109-
*free_vars_copied = 1;
110-
}
111-
112-
// https://github.com/python/cpython/blob/0325a8a8cdba6c091bcbbb3c995f3bf1d1217012/Objects/frameobject.c#L1162
113-
static int
114-
frame_get_var(_PyInterpreterFrame *frame, PyCodeObject *co, int i,
115-
PyObject **pvalue)
116-
{
117-
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
118-
119-
/* If the namespace is unoptimized, then one of the
120-
following cases applies:
121-
1. It does not contain free variables, because it
122-
uses import * or is a top-level namespace.
123-
2. It is a class namespace.
124-
We don't want to accidentally copy free variables
125-
into the locals dict used by the class.
126-
*/
127-
if (kind & CO_FAST_FREE && !(co->co_flags & CO_OPTIMIZED)) {
128-
return 0;
129-
}
130-
131-
PyObject *value = frame->localsplus[i];
132-
if (frame->stacktop) {
133-
if (kind & CO_FAST_FREE) {
134-
// The cell was set by COPY_FREE_VARS.
135-
CHECK(value != NULL && PyCell_Check(value));
136-
value = PyCell_GET(value);
137-
}
138-
else if (kind & CO_FAST_CELL) {
139-
// Note that no *_DEREF ops can happen before MAKE_CELL
140-
// executes. So there's no need to duplicate the work
141-
// that MAKE_CELL would otherwise do later, if it hasn't
142-
// run yet.
143-
if (value != NULL) {
144-
if (PyCell_Check(value) &&
145-
THP_PyFrame_OpAlreadyRan(frame, MAKE_CELL, i)) {
146-
// (likely) MAKE_CELL must have executed already.
147-
value = PyCell_GET(value);
148-
}
149-
// (likely) Otherwise it it is an arg (kind & CO_FAST_LOCAL),
150-
// with the initial value set when the frame was created...
151-
// (unlikely) ...or it was set to some initial value by
152-
// an earlier call to PyFrame_LocalsToFast().
153-
}
154-
}
155-
}
156-
else {
157-
CHECK(value == NULL);
158-
}
159-
*pvalue = value;
160-
return 1;
161-
}
162-
163-
// https://github.com/python/cpython/blob/0325a8a8cdba6c091bcbbb3c995f3bf1d1217012/Objects/frameobject.c#L1213
164-
static PyObject *
165-
THP_PyFrame_GetLocals(_PyInterpreterFrame *frame, int include_hidden, int *free_vars_copied)
166-
{
167-
/* Merge fast locals into f->f_locals */
168-
PyObject *locals = frame->f_locals;
169-
if (locals == NULL) {
170-
locals = frame->f_locals = PyDict_New();
171-
if (locals == NULL) {
172-
return NULL;
173-
}
174-
}
175-
PyObject *hidden = NULL;
176-
177-
/* If include_hidden, "hidden" fast locals (from inlined comprehensions in
178-
module/class scopes) will be included in the returned dict, but not in
179-
frame->f_locals; the returned dict will be a modified copy. Non-hidden
180-
locals will still be updated in frame->f_locals. */
181-
if (include_hidden) {
182-
hidden = PyDict_New();
183-
if (hidden == NULL) {
184-
return NULL;
185-
}
186-
}
187-
188-
frame_init_get_vars(frame, free_vars_copied);
189-
190-
PyCodeObject *co = F_CODE(frame);
191-
for (int i = 0; i < co->co_nlocalsplus; i++) {
192-
PyObject *value; // borrowed reference
193-
if (!frame_get_var(frame, co, i, &value)) {
194-
continue;
195-
}
196-
197-
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
198-
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
199-
if (kind & CO_FAST_HIDDEN) {
200-
if (include_hidden && value != NULL) {
201-
if (PyObject_SetItem(hidden, name, value) != 0) {
202-
goto error;
203-
}
204-
}
205-
continue;
206-
}
207-
if (value == NULL) {
208-
if (PyObject_DelItem(locals, name) != 0) {
209-
if (PyErr_ExceptionMatches(PyExc_KeyError)) {
210-
PyErr_Clear();
211-
}
212-
else {
213-
goto error;
214-
}
215-
}
216-
}
217-
else {
218-
if (PyObject_SetItem(locals, name, value) != 0) {
219-
goto error;
220-
}
221-
}
222-
}
223-
224-
if (include_hidden && PyDict_Size(hidden)) {
225-
PyObject *innerlocals = PyDict_New();
226-
if (innerlocals == NULL) {
227-
goto error;
228-
}
229-
if (PyDict_Merge(innerlocals, locals, 1) != 0) {
230-
Py_DECREF(innerlocals);
231-
goto error;
232-
}
233-
if (PyDict_Merge(innerlocals, hidden, 1) != 0) {
234-
Py_DECREF(innerlocals);
235-
goto error;
236-
}
237-
locals = innerlocals;
238-
}
239-
else {
240-
Py_INCREF(locals);
241-
}
242-
Py_CLEAR(hidden);
243-
244-
return locals;
245-
246-
error:
247-
Py_XDECREF(hidden);
248-
return NULL;
249-
}
250-
251-
// https://github.com/python/cpython/blob/0325a8a8cdba6c091bcbbb3c995f3bf1d1217012/Objects/frameobject.c#L1301
25256
int
25357
THP_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame, int *free_vars_copied)
25458
{
255-
PyObject *locals = THP_PyFrame_GetLocals(frame, 0, free_vars_copied);
256-
if (locals == NULL) {
257-
return -1;
258-
}
259-
Py_DECREF(locals);
59+
// functionality moved to framelocals_mapping.cpp
26060
return 0;
26161
}
26262

torch/csrc/dynamo/cpython_defs.h

+1-11
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,9 @@
66
// should go in cpython_defs.c. Copying is required when, e.g.,
77
// we need to call internal CPython functions that are not exposed.
88

9-
#if IS_PYTHON_3_13_PLUS
10-
#define F_CODE(x) ((PyCodeObject*)(x)->f_executable)
11-
#define PREV_INSTR(x) (x)->instr_ptr
12-
#else
13-
#define F_CODE(x) ((PyCodeObject*)(x)->f_code)
14-
#define PREV_INSTR(x) (x)->prev_instr
15-
#endif
16-
179
#if IS_PYTHON_3_11_PLUS
1810

19-
#define Py_BUILD_CORE
20-
#include <internal/pycore_frame.h>
21-
#undef Py_BUILD_CORE
11+
typedef struct _PyInterpreterFrame _PyInterpreterFrame;
2212

2313
int THP_PyFrame_FastToLocalsWithError(
2414
_PyInterpreterFrame* frame,

torch/csrc/dynamo/cpython_includes.h

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#pragma once
2+
3+
#include <torch/csrc/utils/python_compat.h>
4+
5+
// Problem in CPython includes when mixing core and non-core build
6+
// The fix was not backported to 3.12 so this is needed here
7+
// https://github.com/python/cpython/issues/105268
8+
#if IS_PYTHON_3_12_PLUS
9+
#undef _PyGC_FINALIZED
10+
#endif
11+
12+
// see https://bugs.python.org/issue35886
13+
#if PY_VERSION_HEX >= 0x03080000
14+
#define Py_BUILD_CORE
15+
16+
#ifndef __cplusplus
17+
// C-only headers
18+
#include <internal/pycore_pystate.h>
19+
20+
#endif // __cplusplus
21+
22+
#if IS_PYTHON_3_11_PLUS
23+
#include <internal/pycore_frame.h>
24+
#endif
25+
26+
#undef Py_BUILD_CORE
27+
#endif // PY_VERSION_HEX >= 0x03080000
28+
29+
#ifdef __cplusplus
30+
extern "C" {
31+
#endif
32+
33+
#if IS_PYTHON_3_13_PLUS
34+
#define F_CODE(x) ((PyCodeObject*)(x)->f_executable)
35+
#define PREV_INSTR(x) (x)->instr_ptr
36+
#else
37+
#define F_CODE(x) ((PyCodeObject*)(x)->f_code)
38+
#define PREV_INSTR(x) (x)->prev_instr
39+
#endif
40+
41+
#if IS_PYTHON_3_12_PLUS
42+
#define FUNC(x) ((x)->f_funcobj)
43+
#else
44+
#define FUNC(x) ((x)->f_func)
45+
#endif
46+
47+
#ifdef __cplusplus
48+
} // extern "C"
49+
#endif

torch/csrc/dynamo/debug_macros.h

+12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
#pragma once
22

3+
#ifdef __cplusplus
4+
#include <cstdio>
5+
#else
36
#include <stdio.h>
7+
#endif
8+
9+
#ifdef __cplusplus
10+
extern "C" {
11+
#endif
412

513
#ifdef _WIN32
614
#define unlikely(x) (x)
@@ -44,3 +52,7 @@
4452
#define DEBUG_TRACE0(msg)
4553

4654
#endif
55+
56+
#ifdef __cplusplus
57+
} // extern "C"
58+
#endif

0 commit comments

Comments
 (0)