mirror of
https://github.com/zebrajr/pytorch.git
synced 2025-12-07 00:21:07 +01:00
Apply fixes to some found issues by clang-tidy in torch/csrc. Pull Request resolved: https://github.com/pytorch/pytorch/pull/108024 Approved by: https://github.com/Skylion007, https://github.com/albanD, https://github.com/malfet
363 lines
13 KiB
C
363 lines
13 KiB
C
#include <torch/csrc/dynamo/cpython_defs.h>
|
|
|
|
#ifdef _WIN32
|
|
#define unlikely(x) (x)
|
|
#else
|
|
#define unlikely(x) __builtin_expect((x), 0)
|
|
#endif
|
|
|
|
#define CHECK(cond) \
|
|
if (unlikely(!(cond))) { \
|
|
fprintf(stderr, "DEBUG CHECK FAILED: %s:%d\n", __FILE__, __LINE__); \
|
|
abort(); \
|
|
} else { \
|
|
}
|
|
|
|
// NOTE: all `assert`s below are converted to `CHECK`s
|
|
|
|
#if IS_PYTHON_3_11_PLUS
|
|
|
|
// Problem in CPython includes when mixing core and non-core build
|
|
// The fix was not backported to 3.12 so this is needed here
|
|
// https://github.com/python/cpython/issues/105268
|
|
#if IS_PYTHON_3_12_PLUS
|
|
#undef _PyGC_FINALIZED
|
|
#endif
|
|
|
|
#define Py_BUILD_CORE
|
|
#include <internal/pycore_pystate.h>
|
|
#define NEED_OPCODE_TABLES // To get _PyOpcode_Deopt
|
|
#include <internal/pycore_opcode.h>
|
|
#undef NEED_OPCODE_TABLES
|
|
#undef Py_BUILD_CORE
|
|
#include <internal/pycore_frame.h>
|
|
|
|
// As a simple way to reduce the impact of ABI changes on the CPython side, this check forces
|
|
// us to manually re-check that the function didn't change on the next major version
|
|
#if PY_VERSION_HEX >= 0x030C0000 // 3.12
|
|
// Spoiler alert: They don't! This will be done in a follow up.
|
|
// #error "Please ensure that the functions below still match the CPython implementation for 3.12"
|
|
#endif
|
|
|
|
// https://github.com/python/cpython/blob/a7715ccfba5b86ab09f86ec56ac3755c93b46b48/Objects/frameobject.c#L1079
|
|
static int
|
|
THP_PyFrame_OpAlreadyRan(_PyInterpreterFrame *frame, int opcode, int oparg)
|
|
{
|
|
// This only works when opcode is a non-quickened form:
|
|
CHECK(_PyOpcode_Deopt[opcode] == opcode);
|
|
int check_oparg = 0;
|
|
for (_Py_CODEUNIT *instruction = _PyCode_CODE(frame->f_code);
|
|
instruction < frame->prev_instr; instruction++)
|
|
{
|
|
int check_opcode = _PyOpcode_Deopt[_Py_OPCODE(*instruction)];
|
|
check_oparg |= _Py_OPARG(*instruction);
|
|
if (check_opcode == opcode && check_oparg == oparg) {
|
|
return 1;
|
|
}
|
|
if (check_opcode == EXTENDED_ARG) {
|
|
check_oparg <<= 8;
|
|
}
|
|
else {
|
|
check_oparg = 0;
|
|
}
|
|
instruction += _PyOpcode_Caches[check_opcode];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// https://github.com/python/cpython/blob/a7715ccfba5b86ab09f86ec56ac3755c93b46b48/Objects/frameobject.c#L1182
|
|
int
|
|
THP_PyFrame_FastToLocalsWithError(_PyInterpreterFrame *frame) {
|
|
/* Merge fast locals into f->f_locals */
|
|
PyObject *locals = NULL;
|
|
PyObject **fast = NULL;
|
|
PyCodeObject *co = NULL;
|
|
locals = frame->f_locals;
|
|
if (locals == NULL) {
|
|
locals = frame->f_locals = PyDict_New();
|
|
if (locals == NULL)
|
|
return -1;
|
|
}
|
|
co = frame->f_code;
|
|
fast = _PyFrame_GetLocalsArray(frame);
|
|
// COPY_FREE_VARS has no quickened forms, so no need to use _PyOpcode_Deopt
|
|
// here:
|
|
int lasti = _PyInterpreterFrame_LASTI(frame);
|
|
if (lasti < 0 && _Py_OPCODE(_PyCode_CODE(co)[0]) == COPY_FREE_VARS) {
|
|
/* Free vars have not been initialized -- Do that */
|
|
PyCodeObject *co = frame->f_code;
|
|
#if IS_PYTHON_3_12_PLUS
|
|
PyObject *closure = ((PyFunctionObject *)frame->f_funcobj)->func_closure;
|
|
int offset = co->co_nlocals + co->co_ncellvars;
|
|
#else
|
|
PyObject *closure = frame->f_func->func_closure;
|
|
int offset = co->co_nlocals + co->co_nplaincellvars;
|
|
#endif
|
|
for (int i = 0; i < co->co_nfreevars; ++i) {
|
|
PyObject *o = PyTuple_GET_ITEM(closure, i);
|
|
Py_INCREF(o);
|
|
frame->localsplus[offset + i] = o;
|
|
}
|
|
// COPY_FREE_VARS doesn't have inline CACHEs, either:
|
|
frame->prev_instr = _PyCode_CODE(frame->f_code);
|
|
}
|
|
for (int i = 0; i < co->co_nlocalsplus; i++) {
|
|
_PyLocals_Kind kind = _PyLocals_GetKind(co->co_localspluskinds, i);
|
|
|
|
/* If the namespace is unoptimized, then one of the
|
|
following cases applies:
|
|
1. It does not contain free variables, because it
|
|
uses import * or is a top-level namespace.
|
|
2. It is a class namespace.
|
|
We don't want to accidentally copy free variables
|
|
into the locals dict used by the class.
|
|
*/
|
|
if (kind & CO_FAST_FREE && !(co->co_flags & CO_OPTIMIZED)) {
|
|
continue;
|
|
}
|
|
|
|
PyObject *name = PyTuple_GET_ITEM(co->co_localsplusnames, i);
|
|
PyObject *value = fast[i];
|
|
if (frame->stacktop) {
|
|
if (kind & CO_FAST_FREE) {
|
|
// The cell was set by COPY_FREE_VARS.
|
|
CHECK(value != NULL && PyCell_Check(value));
|
|
value = PyCell_GET(value);
|
|
}
|
|
else if (kind & CO_FAST_CELL) {
|
|
// Note that no *_DEREF ops can happen before MAKE_CELL
|
|
// executes. So there's no need to duplicate the work
|
|
// that MAKE_CELL would otherwise do later, if it hasn't
|
|
// run yet.
|
|
if (value != NULL) {
|
|
if (PyCell_Check(value) &&
|
|
THP_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 when the frame was created...
|
|
// (unlikely) ...or it was set to some initial value by
|
|
// an earlier call to PyFrame_LocalsToFast().
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
CHECK(value == NULL);
|
|
}
|
|
if (value == NULL) {
|
|
if (PyObject_DelItem(locals, name) != 0) {
|
|
if (PyErr_ExceptionMatches(PyExc_KeyError)) {
|
|
PyErr_Clear();
|
|
}
|
|
else {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (PyObject_SetItem(locals, name, value) != 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// e.g. COPY_FIELD(op, o, globals) becomes
|
|
// PY_XINCREF((o)->func_globals);
|
|
// (op)->func_globals = (o)->func_globals;
|
|
#define COPY_FIELD(f1, f2, field) \
|
|
Py_XINCREF((f2)->func_##field); \
|
|
(f1)->func_##field = (f2)->func_##field;
|
|
|
|
// Not actually copied from CPython, but loosely based on
|
|
// https://github.com/python/cpython/blob/e715da6db1d1d70cd779dc48e1ba8110c51cc1bf/Objects/funcobject.c
|
|
// Makes a new PyFunctionObject copy of `o`, but with the code object fields
|
|
// determined from `code`.
|
|
// Ensure that all fields defined in the PyFunctionObject struct in
|
|
// https://github.com/python/cpython/blob/e715da6db1d1d70cd779dc48e1ba8110c51cc1bf/Include/cpython/funcobject.h
|
|
// are accounted for.
|
|
PyFunctionObject *
|
|
_PyFunction_CopyWithNewCode(PyFunctionObject *o, PyCodeObject* code)
|
|
{
|
|
PyFunctionObject *op = PyObject_GC_New(PyFunctionObject, &PyFunction_Type);
|
|
if (op == NULL) {
|
|
return NULL;
|
|
}
|
|
Py_XINCREF(code);
|
|
op->func_code = (PyObject *) code;
|
|
Py_XINCREF(code->co_name);
|
|
op->func_name = code->co_name;
|
|
Py_XINCREF(code->co_qualname);
|
|
op->func_qualname = code->co_qualname;
|
|
COPY_FIELD(op, o, globals);
|
|
COPY_FIELD(op, o, builtins);
|
|
COPY_FIELD(op, o, defaults);
|
|
COPY_FIELD(op, o, kwdefaults);
|
|
COPY_FIELD(op, o, closure);
|
|
COPY_FIELD(op, o, doc);
|
|
COPY_FIELD(op, o, dict);
|
|
op->func_weakreflist = NULL;
|
|
COPY_FIELD(op, o, module);
|
|
COPY_FIELD(op, o, annotations);
|
|
op->vectorcall = o->vectorcall;
|
|
op->func_version = o->func_version;
|
|
PyObject_GC_Track(op);
|
|
return op;
|
|
}
|
|
|
|
// From https://github.com/python/cpython/blob/e715da6db1d1d70cd779dc48e1ba8110c51cc1bf/Objects/frameobject.c#L1020
|
|
PyFrameObject*
|
|
THP_PyFrame_New_NoTrack(PyCodeObject *code)
|
|
{
|
|
// DYNAMO: commented out
|
|
// CALL_STAT_INC(frame_objects_created);
|
|
int slots = code->co_nlocalsplus + code->co_stacksize;
|
|
PyFrameObject *f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, slots);
|
|
if (f == NULL) {
|
|
return NULL;
|
|
}
|
|
f->f_back = NULL;
|
|
f->f_trace = NULL;
|
|
f->f_trace_lines = 1;
|
|
f->f_trace_opcodes = 0;
|
|
f->f_fast_as_locals = 0;
|
|
f->f_lineno = 0;
|
|
return f;
|
|
}
|
|
|
|
// From https://github.com/python/cpython/blob/e715da6db1d1d70cd779dc48e1ba8110c51cc1bf/Python/frame.c#L27
|
|
PyFrameObject *
|
|
THP_PyFrame_MakeAndSetFrameObject(_PyInterpreterFrame *frame)
|
|
{
|
|
CHECK(frame->frame_obj == NULL);
|
|
PyObject *error_type = NULL, *error_value = NULL, *error_traceback = NULL;
|
|
PyErr_Fetch(&error_type, &error_value, &error_traceback);
|
|
|
|
PyFrameObject *f = THP_PyFrame_New_NoTrack(frame->f_code);
|
|
if (f == NULL) {
|
|
Py_XDECREF(error_type);
|
|
Py_XDECREF(error_value);
|
|
Py_XDECREF(error_traceback);
|
|
return NULL;
|
|
}
|
|
PyErr_Restore(error_type, error_value, error_traceback);
|
|
if (frame->frame_obj) {
|
|
// GH-97002: How did we get into this horrible situation? Most likely,
|
|
// allocating f triggered a GC collection, which ran some code that
|
|
// *also* created the same frame... while we were in the middle of
|
|
// creating it! See test_sneaky_frame_object in test_frame.py for a
|
|
// concrete example.
|
|
//
|
|
// Regardless, just throw f away and use that frame instead, since it's
|
|
// already been exposed to user code. It's actually a bit tricky to do
|
|
// this, since we aren't backed by a real _PyInterpreterFrame anymore.
|
|
// Just pretend that we have an owned, cleared frame so frame_dealloc
|
|
// doesn't make the situation worse:
|
|
f->f_frame = (_PyInterpreterFrame *)f->_f_frame_data;
|
|
f->f_frame->owner = FRAME_CLEARED;
|
|
f->f_frame->frame_obj = f;
|
|
Py_DECREF(f);
|
|
return frame->frame_obj;
|
|
}
|
|
CHECK(frame->owner != FRAME_OWNED_BY_FRAME_OBJECT);
|
|
CHECK(frame->owner != FRAME_CLEARED);
|
|
f->f_frame = frame;
|
|
frame->frame_obj = f;
|
|
return f;
|
|
}
|
|
|
|
// From https://github.com/python/cpython/blob/e715da6db1d1d70cd779dc48e1ba8110c51cc1bf/Include/internal/pycore_frame.h#L163
|
|
static inline PyFrameObject *
|
|
THP_PyFrame_GetFrameObject(_PyInterpreterFrame *frame)
|
|
{
|
|
|
|
CHECK(!_PyFrame_IsIncomplete(frame));
|
|
PyFrameObject *res = frame->frame_obj;
|
|
if (res != NULL) {
|
|
return res;
|
|
}
|
|
return THP_PyFrame_MakeAndSetFrameObject(frame);
|
|
}
|
|
|
|
// From https://github.com/python/cpython/blob/e715da6db1d1d70cd779dc48e1ba8110c51cc1bf/Python/frame.c#L79
|
|
static void
|
|
THP_take_ownership(PyFrameObject *f, _PyInterpreterFrame *frame)
|
|
{
|
|
CHECK(frame->owner != FRAME_OWNED_BY_FRAME_OBJECT);
|
|
CHECK(frame->owner != FRAME_CLEARED);
|
|
Py_ssize_t size = ((char*)&frame->localsplus[frame->stacktop]) - (char *)frame;
|
|
memcpy((_PyInterpreterFrame *)f->_f_frame_data, frame, size);
|
|
frame = (_PyInterpreterFrame *)f->_f_frame_data;
|
|
f->f_frame = frame;
|
|
frame->owner = FRAME_OWNED_BY_FRAME_OBJECT;
|
|
if (_PyFrame_IsIncomplete(frame)) {
|
|
// This may be a newly-created generator or coroutine frame. Since it's
|
|
// dead anyways, just pretend that the first RESUME ran:
|
|
PyCodeObject *code = frame->f_code;
|
|
frame->prev_instr = _PyCode_CODE(code) + code->_co_firsttraceable;
|
|
}
|
|
CHECK(!_PyFrame_IsIncomplete(frame));
|
|
CHECK(f->f_back == NULL);
|
|
_PyInterpreterFrame *prev = frame->previous;
|
|
while (prev && _PyFrame_IsIncomplete(prev)) {
|
|
prev = prev->previous;
|
|
}
|
|
if (prev) {
|
|
/* Link PyFrameObjects.f_back and remove link through _PyInterpreterFrame.previous */
|
|
PyFrameObject *back = THP_PyFrame_GetFrameObject(prev);
|
|
if (back == NULL) {
|
|
/* Memory error here. */
|
|
CHECK(PyErr_ExceptionMatches(PyExc_MemoryError));
|
|
/* Nothing we can do about it */
|
|
PyErr_Clear();
|
|
}
|
|
else {
|
|
f->f_back = (PyFrameObject *)Py_NewRef(back);
|
|
}
|
|
frame->previous = NULL;
|
|
}
|
|
// DYNAMO: use public GC functions instead of internal ones
|
|
if (!PyObject_GC_IsTracked((PyObject *) f)) {
|
|
PyObject_GC_Track((PyObject *) f);
|
|
}
|
|
}
|
|
|
|
// From https://github.com/python/cpython/blob/e715da6db1d1d70cd779dc48e1ba8110c51cc1bf/Python/frame.c#L120
|
|
void
|
|
THP_PyFrame_Clear(_PyInterpreterFrame *frame)
|
|
{
|
|
/* It is the responsibility of the owning generator/coroutine
|
|
* to have cleared the enclosing generator, if any. */
|
|
CHECK(frame->owner != FRAME_OWNED_BY_GENERATOR ||
|
|
_PyFrame_GetGenerator(frame)->gi_frame_state == FRAME_CLEARED);
|
|
// GH-99729: Clearing this frame can expose the stack (via finalizers). It's
|
|
// crucial that this frame has been unlinked, and is no longer visible:
|
|
CHECK(_PyThreadState_GET()->cframe->current_frame != frame);
|
|
if (frame->frame_obj) {
|
|
PyFrameObject *f = frame->frame_obj;
|
|
frame->frame_obj = NULL;
|
|
if (Py_REFCNT(f) > 1) {
|
|
THP_take_ownership(f, frame);
|
|
Py_DECREF(f);
|
|
return;
|
|
}
|
|
Py_DECREF(f);
|
|
}
|
|
CHECK(frame->stacktop >= 0);
|
|
for (int i = 0; i < frame->stacktop; i++) {
|
|
Py_XDECREF(frame->localsplus[i]);
|
|
}
|
|
Py_XDECREF(frame->frame_obj);
|
|
Py_XDECREF(frame->f_locals);
|
|
#if IS_PYTHON_3_12_PLUS
|
|
Py_DECREF(frame->f_funcobj);
|
|
#else
|
|
Py_DECREF(frame->f_func);
|
|
#endif
|
|
Py_DECREF(frame->f_code);
|
|
}
|
|
|
|
#endif
|