mirror of
https://github.com/pytorch/pytorch.git
synced 2025-10-21 13:44:15 +08:00
Pull Request resolved: https://github.com/pytorch/pytorch/pull/164400 Approved by: https://github.com/Camyll, https://github.com/atalman
217 lines
6.8 KiB
C++
217 lines
6.8 KiB
C++
#include <torch/csrc/dynamo/framelocals_mapping.h>
|
|
|
|
#include <torch/csrc/dynamo/cpython_defs.h>
|
|
#include <torch/csrc/dynamo/cpython_includes.h>
|
|
#include <torch/csrc/dynamo/debug_macros.h>
|
|
|
|
#define Py_BUILD_CORE
|
|
#include <internal/pycore_code.h>
|
|
#undef Py_BUILD_CORE
|
|
|
|
#if IS_PYTHON_3_11_PLUS
|
|
|
|
// Our own version of PyFrame_GetLocals.
|
|
// Also combines functionality from frame_init_get_vars and frame_get_var.
|
|
// PyFrame_GetLocals:
|
|
// https://github.com/python/cpython/blob/0325a8a8cdba6c091bcbbb3c995f3bf1d1217012/Objects/frameobject.c#L1213
|
|
// frame_init_get_vars:
|
|
// https://github.com/python/cpython/blob/0325a8a8cdba6c091bcbbb3c995f3bf1d1217012/Objects/frameobject.c#L1136
|
|
// frame_get_var:
|
|
// https://github.com/python/cpython/blob/0325a8a8cdba6c091bcbbb3c995f3bf1d1217012/Objects/frameobject.c#L1162
|
|
// PyFrame_GetLocals returns the frame locals dict.
|
|
// frame_init_get_vars initializes free variables from the closure.
|
|
// frame_get_var fetches the variable value from the frame given the index
|
|
// NOTE: hidden variables are not included.
|
|
// Returns a new reference.
|
|
FrameLocalsMapping::FrameLocalsMapping(FrameLocalsFrameType* frame)
|
|
: _code_obj(py::cast<py::object>((PyObject*)F_CODE(frame))) {
|
|
PyCodeObject* co = F_CODE(frame);
|
|
_framelocals.resize(co->co_nlocalsplus, nullptr);
|
|
|
|
#if IS_PYTHON_3_15_PLUS || (IS_PYTHON_3_14_PLUS && defined(_WIN32))
|
|
TORCH_CHECK(false, "Python 3.15+ / 3.14 on Windows not supported");
|
|
#elif IS_PYTHON_3_14_PLUS
|
|
if (!frame->stackpointer) {
|
|
return;
|
|
}
|
|
#else
|
|
if (!frame->stacktop) {
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
auto update_framelocals = [&](int i, PyObject* value) {
|
|
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
|
|
|
|
if (kind & CO_FAST_FREE && !(co->co_flags & CO_OPTIMIZED)) {
|
|
return;
|
|
}
|
|
|
|
#if IS_PYTHON_3_12_PLUS
|
|
if (kind & CO_FAST_HIDDEN) {
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (kind & CO_FAST_FREE) {
|
|
CHECK(value != nullptr && PyCell_Check(value));
|
|
value = PyCell_GET(value);
|
|
}
|
|
|
|
DEBUG_CHECK(0 <= i && i < _framelocals.size());
|
|
_framelocals[i] = value;
|
|
};
|
|
|
|
auto offset = co->co_nlocalsplus - co->co_nfreevars;
|
|
#if IS_PYTHON_3_15_PLUS || (IS_PYTHON_3_14_PLUS && defined(_WIN32))
|
|
TORCH_CHECK(false, "Python 3.15+ / 3.14 on Windows not supported");
|
|
#elif IS_PYTHON_3_14_PLUS
|
|
for (int i = 0; i < offset; i++) {
|
|
update_framelocals(i, PyStackRef_AsPyObjectBorrow(frame->localsplus[i]));
|
|
}
|
|
#else
|
|
for (int i = 0; i < offset; i++) {
|
|
update_framelocals(i, frame->localsplus[i]);
|
|
}
|
|
#endif
|
|
|
|
// Get references to closure variables
|
|
#if IS_PYTHON_3_15_PLUS || (IS_PYTHON_3_14_PLUS && defined(_WIN32))
|
|
PyObject* closure;
|
|
TORCH_CHECK(false, "Python 3.15+ / 3.14 on Windows not supported");
|
|
#else
|
|
PyObject* closure = FUNC(frame)->func_closure;
|
|
#endif
|
|
for (int i = 0; i < co->co_nfreevars; i++) {
|
|
update_framelocals(offset + i, PyTuple_GET_ITEM(closure, i));
|
|
}
|
|
|
|
// NOTE no need to move the instruction pointer to after COPY_FREE_VARS
|
|
// since we don't actually copy free vars from the closure to the frame
|
|
// localsplus.
|
|
}
|
|
|
|
void FrameLocalsMapping::_realize_dict() {
|
|
_dict = py::dict();
|
|
py::tuple framelocals_names = code_framelocals_names(_code_obj);
|
|
|
|
auto nlocalsplus = ((PyCodeObject*)_code_obj.ptr())->co_nlocalsplus;
|
|
DEBUG_CHECK(nlocalsplus == _framelocals.size());
|
|
for (int i = 0; i < nlocalsplus; i++) {
|
|
if (_framelocals[i]) {
|
|
_dict[framelocals_names[i]] = _framelocals[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
py::tuple code_framelocals_names(py::handle code) {
|
|
CHECK(PyCode_Check(code.ptr()));
|
|
return py::cast<py::tuple>(((PyCodeObject*)code.ptr())->co_localsplusnames);
|
|
}
|
|
|
|
#else
|
|
|
|
// Based on
|
|
// https://github.com/python/cpython/blob/5f24da9d75bb0150781b17ee4706e93e6bb364ea/Objects/frameobject.c#L1016
|
|
FrameLocalsMapping::FrameLocalsMapping(FrameLocalsFrameType* frame)
|
|
: _code_obj(py::cast<py::object>((PyObject*)F_CODE(frame))) {
|
|
PyCodeObject* co = (PyCodeObject*)_code_obj.ptr();
|
|
auto nlocals =
|
|
std::min<int>(co->co_nlocals, (int)PyTuple_GET_SIZE(co->co_varnames));
|
|
auto ncells = PyCode_GetNCellvars(co);
|
|
auto nfree = PyCode_GetNFreevars(co);
|
|
|
|
_framelocals.resize(co->co_nlocals + ncells + nfree, nullptr);
|
|
|
|
auto update_framelocals = [&](int i, bool deref) {
|
|
DEBUG_CHECK(0 <= i && i < _framelocals.size());
|
|
PyObject* value = frame->f_localsplus[i];
|
|
if (deref) {
|
|
CHECK(value != nullptr && PyCell_Check(value));
|
|
value = PyCell_GET(value);
|
|
}
|
|
_framelocals[i] = value;
|
|
};
|
|
|
|
// locals
|
|
for (int i = 0; i < nlocals; i++) {
|
|
update_framelocals(i, false);
|
|
}
|
|
|
|
// cellvars
|
|
for (int i = 0; i < ncells; i++) {
|
|
update_framelocals(co->co_nlocals + i, true);
|
|
}
|
|
|
|
// freevars
|
|
if (co->co_flags & CO_OPTIMIZED) {
|
|
for (int i = 0; i < nfree; i++) {
|
|
update_framelocals(co->co_nlocals + ncells + i, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FrameLocalsMapping::_realize_dict() {
|
|
_dict = py::dict();
|
|
py::tuple framelocals_names = code_framelocals_names(_code_obj);
|
|
PyCodeObject* co = (PyCodeObject*)_code_obj.ptr();
|
|
|
|
auto update_mapping = [&](int i) {
|
|
DEBUG_CHECK(0 <= i && i < _framelocals.size());
|
|
PyObject* value = _framelocals[i].ptr();
|
|
// NOTE: CPython's PyFrame_FastToLocalsWithError/map_to_dict
|
|
// removes the local name from the locals dict if the value is NULL.
|
|
// This is likely so that if a local variable is deleted in the fastlocals,
|
|
// PyFrame_FastToLocalsWithError will also remove it from frame->f_locals.
|
|
// Since we create the locals dict from scratch every time (and only
|
|
// before a frame is run), we probably don't need to account for this
|
|
// codepath, saving us from unnecessarily calling _dict.pop().
|
|
// It is unexpected that multiple fastlocal values corresponding to
|
|
// the same variable name have both a null and non-null value.
|
|
if (value != nullptr) {
|
|
_dict[framelocals_names[i]] = value;
|
|
}
|
|
};
|
|
|
|
// locals
|
|
py::tuple varnames = _code_obj.attr("co_varnames");
|
|
auto nlocals = std::min(co->co_nlocals, (int)varnames.size());
|
|
for (int i = 0; i < nlocals; i++) {
|
|
update_mapping(i);
|
|
}
|
|
|
|
// cellvars
|
|
auto ncells = PyCode_GetNCellvars(co);
|
|
for (int i = 0; i < ncells; i++) {
|
|
update_mapping(co->co_nlocals + i);
|
|
}
|
|
|
|
// freevars
|
|
if (co->co_flags & CO_OPTIMIZED) {
|
|
auto nfree = PyCode_GetNFreevars(co);
|
|
for (int i = 0; i < nfree; i++) {
|
|
update_mapping(co->co_nlocals + ncells + i);
|
|
}
|
|
}
|
|
}
|
|
|
|
py::tuple code_framelocals_names(py::handle code) {
|
|
CHECK(PyCode_Check(code.ptr()));
|
|
py::tuple names = code.attr("co_varnames") + code.attr("co_cellvars");
|
|
if (((PyCodeObject*)code.ptr())->co_flags & CO_OPTIMIZED) {
|
|
names += code.attr("co_freevars");
|
|
}
|
|
return names;
|
|
}
|
|
|
|
#endif
|
|
|
|
PyObject* FrameLocalsMapping::get(int idx) {
|
|
DEBUG_CHECK(0 <= idx && idx < _framelocals.size());
|
|
return _framelocals[idx].ptr();
|
|
}
|
|
|
|
PyDictObject* framelocals_mapping_to_dict(FrameLocalsMapping* map) {
|
|
return map->to_dict();
|
|
}
|