Skip to content

Commit ccaa673

Browse files
bors[bot]ptersilie
andauthored
53: Use shadowstack to share variables between AOT and JIT. r=ltratt a=ptersilie Co-authored-by: Lukas Diekmann <[email protected]>
2 parents deb3936 + 29a7d57 commit ccaa673

File tree

4 files changed

+273
-2
lines changed

4 files changed

+273
-2
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#ifndef LLVM_TRANSFORMS_YK_SHADOWSTACK_H
2+
#define LLVM_TRANSFORMS_YK_SHADOWSTACK_H
3+
4+
#include "llvm/Pass.h"
5+
6+
namespace llvm {
7+
ModulePass *createYkShadowStackPass();
8+
} // namespace llvm
9+
10+
#endif

llvm/lib/CodeGen/TargetPassConfig.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
#include "llvm/Transforms/Utils.h"
5151
#include "llvm/Transforms/Yk/BlockDisambiguate.h"
5252
#include "llvm/Transforms/Yk/ControlPoint.h"
53+
#include "llvm/Transforms/Yk/ShadowStack.h"
5354
#include "llvm/Transforms/Yk/Stackmaps.h"
5455
#include <cassert>
5556
#include <string>
@@ -275,6 +276,10 @@ static cl::opt<bool> YkPatchCtrlPoint("yk-patch-control-point", cl::init(false),
275276
cl::NotHidden,
276277
cl::desc("Patch yk_mt_control_point()"));
277278

279+
static cl::opt<bool>
280+
YkShadowStack("yk-shadow-stack", cl::init(false), cl::NotHidden,
281+
cl::desc("Use a shadow stack for stack values."));
282+
278283
static cl::opt<bool>
279284
YkInsertStackMaps("yk-insert-stackmaps", cl::init(false), cl::NotHidden,
280285
cl::desc("Insert stackmaps for JIT deoptimisation"));
@@ -1134,6 +1139,9 @@ bool TargetPassConfig::addISelPasses() {
11341139
if (YkBlockDisambiguate)
11351140
addPass(createYkBlockDisambiguatePass());
11361141

1142+
if (YkShadowStack) {
1143+
addPass(createYkShadowStackPass());
1144+
}
11371145
// We insert the yk control point pass as late as possible. It has to run
11381146
// before instruction selection (or the machine IR won't reflect our
11391147
// patching), but after other passes which mutate the IR (e.g.
@@ -1145,11 +1153,13 @@ bool TargetPassConfig::addISelPasses() {
11451153
// point could be changed, e.g. the control point's struct argument could be
11461154
// decomposed into scalar arguments. The JIT runtime relies on the interface
11471155
// *not* being changed.
1148-
if (YkPatchCtrlPoint)
1156+
if (YkPatchCtrlPoint) {
11491157
addPass(createYkControlPointPass());
1158+
}
11501159

1151-
if (YkInsertStackMaps)
1160+
if (YkInsertStackMaps) {
11521161
addPass(createYkStackmapsPass());
1162+
}
11531163

11541164
addISelPrepare();
11551165
return addCoreISelPasses();

llvm/lib/Transforms/Yk/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ add_llvm_component_library(LLVMYkPasses
33
ControlPoint.cpp
44
LivenessAnalysis.cpp
55
StackMaps.cpp
6+
ShadowStack.cpp
67

78
DEPENDS
89
intrinsics_gen
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
//===- ShadowStack.cpp - Pass to add shadow stacks to the AOT module --===//
2+
//
3+
// Add shadow stacks to store variables that may have their references taken.
4+
// Storing such variables on a shadow stack allows AOT to share them with
5+
// compiled traces, and back (i.e. references created inside a trace will still
6+
// be valid when we return from the trace via deoptimisation).
7+
// YKFIXME: This can be optimised by only putting variables on the shadow stack
8+
// that actually have their reference taken.
9+
10+
#include "llvm/Transforms/Yk/ShadowStack.h"
11+
#include "llvm/IR/BasicBlock.h"
12+
#include "llvm/IR/DataLayout.h"
13+
#include "llvm/IR/Function.h"
14+
#include "llvm/IR/IRBuilder.h"
15+
#include "llvm/IR/Instructions.h"
16+
#include "llvm/IR/Module.h"
17+
#include "llvm/IR/Value.h"
18+
#include "llvm/IR/Verifier.h"
19+
#include "llvm/InitializePasses.h"
20+
#include "llvm/Pass.h"
21+
#include "llvm/Transforms/Yk/LivenessAnalysis.h"
22+
23+
#include <map>
24+
25+
#define DEBUG_TYPE "yk-shadowstack"
26+
#define YK_MT_NEW "yk_mt_new"
27+
#define G_SHADOW_STACK "shadowstack_0"
28+
// The size of the shadow stack. Defaults to 1MB.
29+
// YKFIXME: Make this adjustable by a compiler flag.
30+
#define SHADOW_STACK_SIZE 1000000
31+
32+
using namespace llvm;
33+
34+
namespace llvm {
35+
void initializeYkShadowStackPass(PassRegistry &);
36+
} // namespace llvm
37+
38+
namespace {
39+
class YkShadowStack : public ModulePass {
40+
public:
41+
static char ID;
42+
YkShadowStack() : ModulePass(ID) {
43+
initializeYkShadowStackPass(*PassRegistry::getPassRegistry());
44+
}
45+
46+
// Checks whether the given instruction is the alloca of the call to
47+
// `yk_mt_new`.
48+
bool isYkMTNewAlloca(Instruction *I) {
49+
for (User *U : I->users()) {
50+
if (U && isa<StoreInst>(U)) {
51+
Value *V = cast<StoreInst>(U)->getValueOperand();
52+
if (isa<CallInst>(V)) {
53+
CallInst *CI = cast<CallInst>(V);
54+
if (CI->isInlineAsm())
55+
return false;
56+
if (!CI->getCalledFunction())
57+
return false;
58+
return (CI->getCalledFunction()->getName() == YK_MT_NEW);
59+
}
60+
}
61+
}
62+
return false;
63+
}
64+
65+
bool runOnModule(Module &M) override {
66+
LLVMContext &Context = M.getContext();
67+
68+
DataLayout DL(&M);
69+
Type *Int8Ty = Type::getInt8Ty(Context);
70+
Type *Int32Ty = Type::getInt32Ty(Context);
71+
Type *PointerSizedIntTy = DL.getIntPtrType(Context);
72+
Type *Int8PtrTy = Type::getInt8PtrTy(Context);
73+
74+
// Create a global variable which will store the pointer to the heap memory
75+
// allocated for the shadow stack.
76+
Constant *GShadowStackPtr = M.getOrInsertGlobal(G_SHADOW_STACK, Int8PtrTy);
77+
GlobalVariable *GVar = M.getNamedGlobal(G_SHADOW_STACK);
78+
GVar->setInitializer(
79+
ConstantPointerNull::get(cast<PointerType>(Int8PtrTy)));
80+
81+
// We only need to create one shadow stack per module so we'll do this
82+
// inside the module's entry point.
83+
// YKFIXME: Investigate languages that don't have/use main as the first
84+
// entry point.
85+
Function *Main = M.getFunction("main");
86+
if (Main == nullptr) {
87+
Context.emitError(
88+
"Unable to add shadow stack: could not find \"main\" function!");
89+
return false;
90+
}
91+
Instruction *First = Main->getEntryBlock().getFirstNonPHI();
92+
IRBuilder<> Builder(First);
93+
94+
// Now create some memory on the heap for the shadow stack.
95+
FunctionCallee MF =
96+
M.getOrInsertFunction("malloc", Int8PtrTy, PointerSizedIntTy);
97+
CallInst *Malloc = Builder.CreateCall(
98+
MF, {ConstantInt::get(PointerSizedIntTy, SHADOW_STACK_SIZE)}, "");
99+
Builder.CreateStore(Malloc, GShadowStackPtr);
100+
101+
Value *SSPtr;
102+
for (Function &F : M) {
103+
if (F.empty()) // skip declarations.
104+
continue;
105+
106+
if (&F != Main) {
107+
// At the top of each function in the module, load the heap pointer
108+
// from the global shadow stack variable.
109+
Builder.SetInsertPoint(F.getEntryBlock().getFirstNonPHI());
110+
SSPtr = Builder.CreateLoad(Int8PtrTy, GShadowStackPtr);
111+
} else {
112+
SSPtr = cast<Value>(Malloc);
113+
}
114+
115+
size_t Offset = 0;
116+
// Remember which allocas were replaced, so we can remove them later in
117+
// one swoop. Removing them here messes up the loop.
118+
std::vector<Instruction *> RemoveAllocas;
119+
for (BasicBlock &BB : F) {
120+
for (Instruction &I : BB) {
121+
if (isa<AllocaInst>(I)) {
122+
// Replace allocas with pointers into the shadow stack.
123+
AllocaInst &AI = cast<AllocaInst>(I);
124+
if (isYkMTNewAlloca(&AI)) {
125+
// The variable created by `yk_mt_new` will never be traced, so
126+
// there's no need to store it on the shadow stack.
127+
continue;
128+
}
129+
if (isa<StructType>(AI.getAllocatedType())) {
130+
StructType *ST = cast<StructType>(AI.getAllocatedType());
131+
// Some yk specific variables that will never be traced and thus
132+
// can live happily on the normal stack.
133+
// YKFIXME: This is somewhat fragile since `struct.YkLocation` is
134+
// a name given by LLVM which could theoretically change. Luckily,
135+
// this should all go away once we only move variables to the
136+
// shadowstack that have their reference taken.
137+
if (ST->getName() == "YkCtrlPointVars" ||
138+
ST->getName() == "struct.YkLocation") {
139+
continue;
140+
}
141+
}
142+
Builder.SetInsertPoint(&I);
143+
auto AllocaSizeInBits = AI.getAllocationSizeInBits(DL);
144+
if (!AllocaSizeInBits) {
145+
// YKFIXME: Deal with functions where the stack size isn't know at
146+
// compile time, e.g. when `alloca` is used.
147+
Context.emitError("Unable to add shadow stack: function has "
148+
"dynamically sized stack!");
149+
return false;
150+
}
151+
// Calculate this `AllocaInst`s size, aligning its pointer if
152+
// necessary, and create a replacement pointer into the shadow
153+
// stack.
154+
size_t AllocaSize = *AllocaSizeInBits / sizeof(uintptr_t);
155+
size_t Align = AI.getAlign().value();
156+
Offset = int((Offset + (Align - 1)) / Align) * Align;
157+
GetElementPtrInst *GEP = GetElementPtrInst::Create(
158+
Int8Ty, SSPtr, {ConstantInt::get(Int32Ty, Offset)}, "",
159+
cast<Instruction>(&AI));
160+
Builder.SetInsertPoint(GEP);
161+
Builder.CreateBitCast(GEP, AI.getAllocatedType()->getPointerTo());
162+
cast<Value>(I).replaceAllUsesWith(GEP);
163+
RemoveAllocas.push_back(cast<Instruction>(&AI));
164+
Offset += AllocaSize;
165+
} else if (isa<CallInst>(I)) {
166+
// When we see a call, we need make space for a new stack frame. We
167+
// do this by simply adjusting the pointer stored in the global
168+
// shadow stack. When the function returns the global is reset. This
169+
// is similar to how the RSP is adjusted inside the
170+
// prologue/epilogue of a function, but here the prologue/epilogue
171+
// are handled by the caller.
172+
CallInst &CI = cast<CallInst>(I);
173+
if (&CI == Malloc) {
174+
// Don't do this for the `malloc` that created the shadow stack.
175+
continue;
176+
}
177+
// Inline asm can't be traced.
178+
if (CI.isInlineAsm()) {
179+
continue;
180+
}
181+
182+
// YKFIXME: Skip functions that are marked with `yk_outline`
183+
// (as those won't be traced and thus don't require a shadow
184+
// stack).
185+
// YKFIXME: Skip functions (direct or indirect) that we don't have
186+
// IR for.
187+
188+
if (CI.getCalledFunction()) {
189+
if (CI.getCalledFunction()->getName() == "pthread_create") {
190+
// The global shadow stack needs to be thread local. In each new
191+
// thread created we need to malloc a new shadow stack and
192+
// assign it to that threads shadow stack global. Note: it's not
193+
// enough to look for `pthread_create` in here, as this call
194+
// could be hidden inside an external library.
195+
Context.emitError(
196+
"Unable to add shadow stack: No support for threads yet!");
197+
return false;
198+
}
199+
// Skip some known intrinsics. YKFIXME: Is there a more general
200+
// solution, e.g. skip all intrinsics?
201+
else if (CI.getCalledFunction()->getName() ==
202+
"llvm.experimental.stackmap") {
203+
continue;
204+
} else if (CI.getCalledFunction()->getName() ==
205+
"llvm.dbg.declare") {
206+
continue;
207+
}
208+
}
209+
210+
// Adjust shadow stack pointer before a call, and reset it back to
211+
// its previous value upon returning. Make sure to align the shadow
212+
// stack to a 16 byte boundary before calling, as required by the
213+
// calling convention.
214+
#ifdef __x86_64__
215+
Offset = int((Offset + (16 - 1)) / 16) * 16;
216+
#else
217+
#error unknown platform
218+
#endif
219+
GetElementPtrInst *GEP = GetElementPtrInst::Create(
220+
Int8Ty, SSPtr, {ConstantInt::get(Int32Ty, Offset)}, "", &I);
221+
Builder.SetInsertPoint(&I);
222+
Builder.CreateStore(GEP, GShadowStackPtr);
223+
Builder.SetInsertPoint(I.getNextNonDebugInstruction());
224+
Builder.CreateStore(SSPtr, GShadowStackPtr);
225+
}
226+
}
227+
}
228+
for (Instruction *I : RemoveAllocas) {
229+
I->removeFromParent();
230+
}
231+
RemoveAllocas.clear();
232+
}
233+
234+
#ifndef NDEBUG
235+
// Our pass runs after LLVM normally does its verify pass. In debug builds
236+
// we run it again to check that our pass is generating valid IR.
237+
if (verifyModule(M, &errs())) {
238+
Context.emitError("ShadowStack insertion pass generated invalid IR!");
239+
return false;
240+
}
241+
#endif
242+
return true;
243+
}
244+
};
245+
} // namespace
246+
247+
char YkShadowStack::ID = 0;
248+
INITIALIZE_PASS(YkShadowStack, DEBUG_TYPE, "yk shadowstack", false, false)
249+
250+
ModulePass *llvm::createYkShadowStackPass() { return new YkShadowStack(); }

0 commit comments

Comments
 (0)