Skip to content

Commit f7b17f0

Browse files
committed
Track more than one location per variable in stackmaps.
Stackmaps only track one location per live variable, e.g. in a register, on the stack, etc. However, when deoptimising to native stack frames values may exist in multiple locations, e.g. in a register as well as on the stack or in two registers. The main reason for this, at least from observation, are spills before function calls. For example, if a value is held in $rdi and that register needs to be freed up to execute a call, the register allocator may spill it to a register or onto the stack. After the call is done the value is reloaded. A simple optimisation is to cache the spills so the value can be reloaded throughout a function without having to respill it every time. Depending on where the stackmap call is located we can only see the restored register but not the spill. During deoptimisation we then only populate the tracked register with the right value. When the program then decides to restore the register again (e.g. because it has executed another call) loading it from the stack won't recover the value since we haven't pushed it during optimisation. This commit adds an analysis to the compiler chain that attempts to find additional locations for live variables and encode them in stackmaps. It does so but traversing the control flow of a function and creating a mapping on any register moves, stack stores or reloads. For simplicity (and so we don't need to change the stackmap format) we assume that only one such additional location may exist (which may very well turn out to be wrong in the future). These changes enable us to run much further in some of the lua tests, though there are still some bugs to be fixed.
1 parent b2c5bcf commit f7b17f0

File tree

6 files changed

+164
-11
lines changed

6 files changed

+164
-11
lines changed

llvm/include/llvm/CodeGen/StackMaps.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ class StackMaps {
285285
/// Generate a stackmap record for a stackmap instruction.
286286
///
287287
/// MI must be a raw STACKMAP, not a PATCHPOINT.
288-
void recordStackMap(const MCSymbol &L, const MachineInstr &MI);
288+
void recordStackMap(const MCSymbol &L, const MachineInstr &MI, std::map<Register, int64_t> SpillsOffsets = {});
289289

290290
/// Generate a stackmap record for a patchpoint instruction.
291291
void recordPatchPoint(const MCSymbol &L, const MachineInstr &MI);
@@ -316,7 +316,8 @@ class StackMaps {
316316
MachineInstr::const_mop_iterator
317317
parseOperand(MachineInstr::const_mop_iterator MOI,
318318
MachineInstr::const_mop_iterator MOE, LiveVarsVec &LiveVars,
319-
LiveOutVec &LiveOuts) const;
319+
LiveOutVec &LiveOuts,
320+
std::map<Register, int64_t> SpillOffsets = {}) const;
320321

321322
/// Specialized parser of statepoint operands.
322323
/// They do not directly correspond to StackMap record entries.
@@ -343,6 +344,7 @@ class StackMaps {
343344
void recordStackMapOpers(const MCSymbol &L, const MachineInstr &MI,
344345
uint64_t ID, MachineInstr::const_mop_iterator MOI,
345346
MachineInstr::const_mop_iterator MOE,
347+
std::map<Register, int64_t> SpillOffsets = {},
346348
bool recordResult = false);
347349

348350
/// Emit the stackmap header.

llvm/lib/CodeGen/StackMaps.cpp

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "llvm/CodeGen/TargetFrameLowering.h"
1919
#include "llvm/CodeGen/TargetOpcodes.h"
2020
#include "llvm/CodeGen/TargetRegisterInfo.h"
21+
#include "llvm/CodeGen/TargetInstrInfo.h"
2122
#include "llvm/CodeGen/TargetSubtargetInfo.h"
2223
#include "llvm/IR/DataLayout.h"
2324
#include "llvm/MC/MCContext.h"
@@ -219,7 +220,8 @@ static unsigned getDwarfRegNum(unsigned Reg, const TargetRegisterInfo *TRI) {
219220
MachineInstr::const_mop_iterator
220221
StackMaps::parseOperand(MachineInstr::const_mop_iterator MOI,
221222
MachineInstr::const_mop_iterator MOE,
222-
LiveVarsVec &LiveVars, LiveOutVec &LiveOuts) const {
223+
LiveVarsVec &LiveVars, LiveOutVec &LiveOuts,
224+
std::map<Register, int64_t> SpillOffsets) const {
223225
LocationVec &Locs = LiveVars.back();
224226
const TargetRegisterInfo *TRI = AP.MF->getSubtarget().getRegisterInfo();
225227
if (MOI->isImm()) {
@@ -282,7 +284,27 @@ StackMaps::parseOperand(MachineInstr::const_mop_iterator MOI,
282284
const TargetRegisterClass *RC = TRI->getMinimalPhysRegClass(MOI->getReg());
283285
assert(!MOI->getSubReg() && "Physical subreg still around.");
284286

285-
unsigned Offset = 0;
287+
288+
signed Offset = 0;
289+
// Check if there are any mappings for this register in the spillmap. If so,
290+
// encode the additional location in the offset field of the stackmap record
291+
// (which is unused for register locations). Note that this assumes that
292+
// there can only be one additional location for each value, which may turn
293+
// out to be false.
294+
if (MOI->isReg()) {
295+
Register R = MOI->getReg();
296+
if (SpillOffsets.count(R) > 0) {
297+
Offset = SpillOffsets[R];
298+
assert(SpillOffsets[R] != 0);
299+
if (Offset > 0) {
300+
// If the additional location is another register encode its DWARF id.
301+
// Also temporarily add 1 since 0 is used to mean there is no
302+
// additional location.
303+
Offset = getDwarfRegNum(Offset, TRI) + 1;
304+
}
305+
}
306+
}
307+
286308
unsigned DwarfRegNum = getDwarfRegNum(MOI->getReg(), TRI);
287309
unsigned LLVMRegNum = *TRI->getLLVMRegNum(DwarfRegNum, false);
288310
unsigned SubRegIdx = TRI->getSubRegIndex(LLVMRegNum, MOI->getReg());
@@ -520,6 +542,7 @@ void StackMaps::recordStackMapOpers(const MCSymbol &MILabel,
520542
const MachineInstr &MI, uint64_t ID,
521543
MachineInstr::const_mop_iterator MOI,
522544
MachineInstr::const_mop_iterator MOE,
545+
std::map<Register, int64_t> SpillOffsets,
523546
bool recordResult) {
524547
MCContext &OutContext = AP.OutStreamer->getContext();
525548

@@ -529,7 +552,7 @@ void StackMaps::recordStackMapOpers(const MCSymbol &MILabel,
529552
if (recordResult) {
530553
assert(PatchPointOpers(&MI).hasDef() && "Stackmap has no return value.");
531554
parseOperand(MI.operands_begin(), std::next(MI.operands_begin()), LiveVars,
532-
LiveOuts);
555+
LiveOuts, SpillOffsets);
533556
LiveVars.push_back(LocationVec());
534557
}
535558

@@ -538,7 +561,7 @@ void StackMaps::recordStackMapOpers(const MCSymbol &MILabel,
538561
parseStatepointOpers(MI, MOI, MOE, LiveVars, LiveOuts);
539562
else
540563
while (MOI != MOE)
541-
MOI = parseOperand(MOI, MOE, LiveVars, LiveOuts);
564+
MOI = parseOperand(MOI, MOE, LiveVars, LiveOuts, SpillOffsets);
542565

543566
// Move large constants into the constant pool.
544567
for (auto &Locations : LiveVars) {
@@ -611,14 +634,14 @@ void StackMaps::recordStackMapOpers(const MCSymbol &MILabel,
611634
}
612635
}
613636

614-
void StackMaps::recordStackMap(const MCSymbol &L, const MachineInstr &MI) {
637+
void StackMaps::recordStackMap(const MCSymbol &L, const MachineInstr &MI, std::map<Register, int64_t> SpillOffsets) {
615638
assert(MI.getOpcode() == TargetOpcode::STACKMAP && "expected stackmap");
616639

617640
StackMapOpers opers(&MI);
618641
const int64_t ID = MI.getOperand(PatchPointOpers::IDPos).getImm();
619642
recordStackMapOpers(L, MI, ID,
620643
std::next(MI.operands_begin(), opers.getVarIdx()),
621-
MI.operands_end());
644+
MI.operands_end(), SpillOffsets);
622645
}
623646

624647
void StackMaps::recordPatchPoint(const MCSymbol &L, const MachineInstr &MI) {
@@ -627,7 +650,7 @@ void StackMaps::recordPatchPoint(const MCSymbol &L, const MachineInstr &MI) {
627650
PatchPointOpers opers(&MI);
628651
const int64_t ID = opers.getID();
629652
auto MOI = std::next(MI.operands_begin(), opers.getStackMapStartIdx());
630-
recordStackMapOpers(L, MI, ID, MOI, MI.operands_end(),
653+
recordStackMapOpers(L, MI, ID, MOI, MI.operands_end(), {},
631654
opers.isAnyReg() && opers.hasDef());
632655

633656
#ifndef NDEBUG
@@ -648,7 +671,7 @@ void StackMaps::recordStatepoint(const MCSymbol &L, const MachineInstr &MI) {
648671
StatepointOpers opers(&MI);
649672
const unsigned StartIdx = opers.getVarIdx();
650673
recordStackMapOpers(L, MI, opers.getID(), MI.operands_begin() + StartIdx,
651-
MI.operands_end(), false);
674+
MI.operands_end(), {}, false);
652675
}
653676

654677
/// Emit the stackmap header.

llvm/lib/Support/Yk.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,9 @@ static cl::opt<bool, true> YkStackMapOffsetFixParser(
2525
cl::desc("Apply a fix to stackmaps that corrects the reported instruction "
2626
"offset in the presence of calls."),
2727
cl::NotHidden, cl::location(YkStackMapOffsetFix));
28+
29+
bool YkStackMapAdditionalLocs;
30+
static cl::opt<bool, true> YkStackMapAdditionalLocsParser(
31+
"yk-stackmap-add-locs",
32+
cl::desc("Encode additional locations for registers into stackmaps."),
33+
cl::NotHidden, cl::location(YkStackMapAdditionalLocs));

llvm/lib/Target/X86/X86AsmPrinter.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include "llvm/Target/TargetMachine.h"
4747

4848
using namespace llvm;
49+
extern bool YkStackMapAdditionalLocs;
4950

5051
X86AsmPrinter::X86AsmPrinter(TargetMachine &TM,
5152
std::unique_ptr<MCStreamer> Streamer)
@@ -55,6 +56,116 @@ X86AsmPrinter::X86AsmPrinter(TargetMachine &TM,
5556
// Primitive Helper Functions.
5657
//===----------------------------------------------------------------------===//
5758

59+
const TargetInstrInfo *TII;
60+
61+
/// Clear any mappings that map to the given register.
62+
void clearRhs(Register Reg, std::map<Register, int64_t> &SpillMap) {
63+
auto I = SpillMap.begin();
64+
while (I != SpillMap.end()) {
65+
if (I->second == Reg) {
66+
I = SpillMap.erase(I);
67+
} else {
68+
++I;
69+
}
70+
}
71+
}
72+
73+
/// Given a MachineBasicBlock, analyse its instructions to build a mapping of
74+
/// where duplicate values live. This can be either in another register or on
75+
/// the stack. Since registers are always positive and stack offsets negative,
76+
/// we can store both in the same map while still being able to distinguish
77+
/// them. This allows us to keep the changes in Stackmaps.cpp to a minimum and
78+
/// avoids having to alter the stackmap format.
79+
void processInstructions(
80+
const MachineBasicBlock *MBB,
81+
std::map<Register, int64_t> &SpillMap,
82+
std::map<const MachineInstr *, std::map<Register, int64_t>> &StackmapSpillMaps
83+
) {
84+
for (const MachineInstr &Instr : MBB->instrs()) {
85+
// At each stackmap call, save the current mapping so it can later be
86+
// encoded in the stackmap when it is lowered.
87+
if (Instr.getOpcode() == TargetOpcode::STACKMAP) {
88+
StackmapSpillMaps[&Instr] = SpillMap;
89+
continue;
90+
}
91+
92+
// Copying a value from one register B to another A, creates a mapping from
93+
// A to B. If A is tracked by the stackmap, then B will also be tracked and
94+
// assigned the same value during deoptimisation.
95+
if (Instr.isMoveReg()) {
96+
const MachineOperand Lhs = Instr.getOperand(0);
97+
const MachineOperand Rhs = Instr.getOperand(1);
98+
assert(Lhs.isReg() && "Is register.");
99+
assert(Rhs.isReg() && "Is register.");
100+
SpillMap[Lhs.getReg()] = Rhs.getReg();
101+
// Reassigning a new value to Lhs means any mappings to Lhs are now void
102+
// and need to be removed.
103+
clearRhs(Lhs.getReg(), SpillMap);
104+
continue;
105+
}
106+
107+
int FI;
108+
// A value from a register was spilled to the stack. Create a mapping from
109+
// that register to the stack offset.
110+
if (TII->isStoreToStackSlotPostFE(Instr, FI)) {
111+
const MachineOperand OffsetOp = Instr.getOperand(3);
112+
const MachineOperand MO = Instr.getOperand(5);
113+
assert(MO.isReg() && "Is register.");
114+
const Register Reg = MO.getReg();
115+
if (OffsetOp.isImm()) {
116+
const int64_t Offset = OffsetOp.getImm();
117+
SpillMap[Reg] = Offset;
118+
clearRhs(Reg, SpillMap);
119+
}
120+
continue;
121+
}
122+
123+
// A value from the stack was loaded back into a register. Create a mapping from
124+
// that register to the stack offset.
125+
if (TII->isLoadFromStackSlotPostFE(Instr, FI)) {
126+
const MachineOperand OffsetOp = Instr.getOperand(4);
127+
const MachineOperand Lhs = Instr.getOperand(0);
128+
assert(Lhs.isReg() && "Is register.");
129+
const Register Reg = Lhs.getReg();
130+
if (OffsetOp.isImm()) {
131+
const int64_t Offset = OffsetOp.getImm();
132+
SpillMap[Reg] = Offset;
133+
clearRhs(Reg, SpillMap);
134+
}
135+
continue;
136+
}
137+
138+
// Any other assignments to tracked registers removes their mapping.
139+
for (const MachineOperand MO : Instr.defs()) {
140+
assert(MO.isReg() && "Is register.");
141+
SpillMap.erase(MO.getReg());
142+
clearRhs(MO.getReg(), SpillMap);
143+
}
144+
}
145+
}
146+
147+
/// Walk through the control flow of a function starting at the entry block
148+
/// using depth first search. This makes sure the mappings are are generated in
149+
/// the correct order.
150+
/// YKFIXME: Can be updated to use an iterative approach.
151+
void findSpillLocations(
152+
const MachineBasicBlock *MBB,
153+
std::set<const MachineBasicBlock *> &Seen,
154+
std::map<Register, int64_t> SpillMap,
155+
std::map<const MachineInstr *, std::map<Register, int64_t>> &StackmapSpillMaps
156+
) {
157+
158+
// Block has already been processed.
159+
if (Seen.count(MBB) > 0) {
160+
return;
161+
}
162+
Seen.insert(MBB);
163+
processInstructions(MBB, SpillMap, StackmapSpillMaps);
164+
for (MachineBasicBlock *Succ : MBB->successors()) {
165+
findSpillLocations(Succ, Seen, SpillMap, StackmapSpillMaps);
166+
}
167+
}
168+
58169
/// runOnMachineFunction - Emit the function body.
59170
///
60171
bool X86AsmPrinter::runOnMachineFunction(MachineFunction &MF) {
@@ -82,6 +193,15 @@ bool X86AsmPrinter::runOnMachineFunction(MachineFunction &MF) {
82193
OutStreamer->endCOFFSymbolDef();
83194
}
84195

196+
// Analyse control flow to find out the location of register spills to the
197+
// stack or to other registers, so we can also encode those in stackmaps.
198+
TII = MF.getSubtarget().getInstrInfo();
199+
std::set<const MachineBasicBlock *> S;
200+
std::map<Register, int64_t> SpillMap;
201+
if (YkStackMapAdditionalLocs) {
202+
findSpillLocations(&*MF.begin(), S, SpillMap, StackmapSpillMaps);
203+
}
204+
85205
// Emit the rest of the function body.
86206
emitFunctionBody();
87207

llvm/lib/Target/X86/X86AsmPrinter.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ class LLVM_LIBRARY_VISIBILITY X86AsmPrinter : public AsmPrinter {
7171
};
7272

7373
StackMapShadowTracker SMShadowTracker;
74+
// Map holding information about register spills.
75+
std::map<const MachineInstr *, std::map<Register, int64_t>> StackmapSpillMaps;
7476

7577
// All instructions emitted by the X86AsmPrinter should use this helper
7678
// method.

llvm/lib/Target/X86/X86MCInstLower.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1502,7 +1502,7 @@ void X86AsmPrinter::LowerSTACKMAP(const MachineInstr &MI) {
15021502
MILabel = Ctx.createTempSymbol();
15031503
OutStreamer->emitLabel(MILabel);
15041504
}
1505-
SM.recordStackMap(*MILabel, MI);
1505+
SM.recordStackMap(*MILabel, MI, StackmapSpillMaps[&MI]);
15061506
unsigned NumShadowBytes = MI.getOperand(1).getImm();
15071507
SMShadowTracker.reset(NumShadowBytes);
15081508
}

0 commit comments

Comments
 (0)