Skip to content

Commit 4294758

Browse files
committed
Fix bug in FixStackmapsSpillReloads pass.
This pass replaces stackmap register operands referring to spill reloads with the stack offset of the spill. However, not all stack reloads come from spills. For example, a stack reload could simply try to reload an argument passed via the stack, which breaks this code. We now check the result of `getRestoreSize` which returns "None" if the reload isn't a spill. In such a case we leave the operand as is.
1 parent 1cd3745 commit 4294758

File tree

2 files changed

+183
-5
lines changed

2 files changed

+183
-5
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
// RUN: %clang -flto -Xclang -disable-O0-optnone -fuse-ld=lld -Wl,--mllvm=--yk-stackmap-spillreloads-fix -Wl,--mllvm=--yk-insert-stackmaps -Wl,--mllvm=--yk-optnone-after-ir-passes -Wl,--lto-newpm-passes=instcombine -Wl,--mllvm=--yk-shadow-stack %s
2+
3+
// Test case scenario extracted from CPython source code: license at
4+
// https://github.com/python/cpython/blob/main/LICENSE.
5+
//
6+
// Checks whether this program compiles as it previously led to an error in the
7+
// FixStackmapsSpillReloads pass.
8+
9+
#include <stdio.h>
10+
11+
struct PyObject {
12+
int val;
13+
};
14+
typedef struct PyObject PyObject;
15+
16+
struct PyFrameConstructor {
17+
PyObject *fc_globals;
18+
PyObject *fc_builtins;
19+
PyObject *fc_name;
20+
PyObject *fc_qualname;
21+
PyObject *fc_code;
22+
PyObject *fc_defaults;
23+
PyObject *fc_kwdefaults;
24+
PyObject *fc_closure;
25+
};
26+
typedef struct PyFrameConstructor PyFrameConstructor;
27+
28+
struct PyThreadState {
29+
int val;
30+
};
31+
typedef struct PyThreadState PyThreadState;
32+
33+
PyThreadState PYTS;
34+
PyObject PYO;
35+
PyObject **PYOS;
36+
37+
PyThreadState *_PyThreadState_GET() {
38+
return &PYTS;
39+
}
40+
41+
PyObject *_PyTuple_FromArray(PyObject *const *defs, int defcount) {
42+
PYO.val = defcount;
43+
return &PYO;
44+
}
45+
46+
void PyTuple_SET_ITEM(PyObject *o, int i, PyObject *P) {
47+
}
48+
49+
PyObject *_PyEval_BuiltinsFromGlobals(PyThreadState *tstate, PyObject *globals) {
50+
return globals;
51+
}
52+
53+
PyObject *PyTuple_New(int kwcount) {
54+
return &PYO;
55+
}
56+
57+
PyObject **PyMem_Malloc(int kwcount) {
58+
return PYOS;
59+
}
60+
61+
void PyMem_Free(PyObject **PO) {
62+
printf("%p", PO);
63+
}
64+
65+
void Py_DECREF(PyObject *defaults) {
66+
defaults->val++;
67+
}
68+
69+
PyObject *_PyEval_Vector(PyThreadState *PO, PyFrameConstructor *PFC, PyObject *locals, PyObject** allargs, int count, PyObject *kwnames) {
70+
return locals;
71+
}
72+
73+
/* Legacy API */
74+
PyObject *
75+
PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
76+
PyObject *const *args, int argcount,
77+
PyObject *const *kws, int kwcount,
78+
PyObject *const *defs, int defcount,
79+
PyObject *kwdefs, PyObject *closure)
80+
{
81+
PyThreadState *tstate = _PyThreadState_GET();
82+
PyObject *res;
83+
PyObject *defaults = _PyTuple_FromArray(defs, defcount);
84+
if (defaults == NULL) {
85+
return NULL;
86+
}
87+
PyObject *builtins = _PyEval_BuiltinsFromGlobals(tstate, globals); // borrowed ref
88+
if (builtins == NULL) {
89+
Py_DECREF(defaults);
90+
return NULL;
91+
}
92+
if (locals == NULL) {
93+
locals = globals;
94+
}
95+
PyObject *kwnames;
96+
PyObject *const *allargs;
97+
PyObject **newargs;
98+
if (kwcount == 0) {
99+
allargs = args;
100+
kwnames = NULL;
101+
}
102+
else {
103+
kwnames = PyTuple_New(kwcount);
104+
if (kwnames == NULL) {
105+
res = NULL;
106+
goto fail;
107+
}
108+
newargs = PyMem_Malloc(sizeof(PyObject *)*(kwcount+argcount));
109+
if (newargs == NULL) {
110+
res = NULL;
111+
Py_DECREF(kwnames);
112+
goto fail;
113+
}
114+
for (int i = 0; i < argcount; i++) {
115+
newargs[i] = args[i];
116+
}
117+
for (int i = 0; i < kwcount; i++) {
118+
Py_DECREF(kws[2*i]);
119+
PyTuple_SET_ITEM(kwnames, i, kws[2*i]);
120+
newargs[argcount+i] = kws[2*i+1];
121+
}
122+
allargs = newargs;
123+
}
124+
PyObject **kwargs = PyMem_Malloc(sizeof(PyObject *)*kwcount);
125+
if (kwargs == NULL) {
126+
res = NULL;
127+
Py_DECREF(kwnames);
128+
goto fail;
129+
}
130+
for (int i = 0; i < kwcount; i++) {
131+
Py_DECREF(kws[2*i]);
132+
PyTuple_SET_ITEM(kwnames, i, kws[2*i]);
133+
kwargs[i] = kws[2*i+1];
134+
}
135+
PyFrameConstructor constr = {
136+
.fc_globals = globals,
137+
.fc_builtins = builtins,
138+
.fc_name = _co,
139+
.fc_qualname = _co,
140+
.fc_code = _co,
141+
.fc_defaults = defaults,
142+
.fc_kwdefaults = kwdefs,
143+
.fc_closure = closure
144+
};
145+
res = _PyEval_Vector(tstate, &constr, locals,
146+
(PyObject **)allargs, argcount,
147+
kwnames);
148+
if (kwcount) {
149+
Py_DECREF(kwnames);
150+
PyMem_Free(newargs);
151+
}
152+
fail:
153+
Py_DECREF(defaults);
154+
return res;
155+
}
156+
157+
int main() {
158+
PyObject _co;
159+
PyObject globals;
160+
PyObject locals;
161+
PyObject args;
162+
PyObject kws;
163+
PyObject defs;
164+
PyObject kwdefs;
165+
PyObject *r = PyEval_EvalCodeEx(&_co, &globals, &locals, (PyObject *const *) &args, 0, (PyObject *const *) &kws, 0, (PyObject *const *) &defs, 5, &kwdefs, 0);
166+
printf("%p\n", r);
167+
}

llvm/lib/CodeGen/Yk/FixStackmapsSpillReloads.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,23 @@ bool FixStackmapsSpillReloads::runOnMachineFunction(MachineFunction &MF) {
149149
} else if (TII->isLoadFromStackSlotPostFE(*SMI, FI)) {
150150
// If the reload is a load from the stack, replace the operand
151151
// with multiple operands describing a stack location.
152-
MIB.addImm(StackMaps::IndirectMemRefOp);
153152
std::optional<unsigned> Size = SMI->getRestoreSize(TII);
154-
assert(Size.has_value() && "RestoreSize has no value.");
155-
MIB.addImm(Size.value()); // Size
156-
MIB.add(SMI->getOperand(1)); // Register
157-
MIB.add(SMI->getOperand(4)); // Offset
153+
if(!Size.has_value()) {
154+
// This reload isn't a spill (e.g. this could be loading an
155+
// argument passed via the stack), so we don't need to
156+
// replace it. Since registers in lower frames aren't reset
157+
// during deopt, this is only of consequence to the top stack
158+
// frame. And even there this will simply temporarily put a
159+
// value into the register MOI, only to then immediately
160+
// reload the same value into MOI once the reload instruction
161+
// `SMI` is executed after deopt returns to normal execution.
162+
MIB.add(*MOI);
163+
} else {
164+
MIB.addImm(StackMaps::IndirectMemRefOp);
165+
MIB.addImm(Size.value()); // Size
166+
MIB.add(SMI->getOperand(1)); // Register
167+
MIB.add(SMI->getOperand(4)); // Offset
168+
}
158169
} else {
159170
assert(false && "Unknown instruction found");
160171
}

0 commit comments

Comments
 (0)