Skip to content

Commit 1ee52ed

Browse files
committed
[BOLT] Improve InsertNegateRAStatePass::inferUnknownStates
Previous implementation used a simple heuristic. This can be improved in several ways: - If a BasicBlock has instruction both with known RAState and unknown RAState, use the known states to work out the unknown ones. - If a BasicBlock only consists of instructions with unknown RAState, use the last known RAState from its predecessors, or the first known from its successors to set the RAStates in the BasicBlock. This includes error checking: all predecessors/successors should have the same RAState. - Some BasicBlocks may only contain instructions with unknown RAState, and have no CFG neighbors. These already have incorrect unwind info. For these, we copy the last known RAState based on the layout order. Updated bolt/docs/PacRetDesign.md to reflect changes.
1 parent e8149e4 commit 1ee52ed

File tree

3 files changed

+265
-32
lines changed

3 files changed

+265
-32
lines changed

bolt/docs/PacRetDesign.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,16 +200,29 @@ This pass runs after optimizations. It performns the _inverse_ of MarkRAState pa
200200
Some BOLT passes can add new Instructions. In InsertNegateRAStatePass, we have
201201
to know what RA state these have.
202202

203-
The current solution has the `inferUnknownStates` function to cover these, using
204-
a fairly simple strategy: unknown states inherit the last known state.
205-
206-
This will be updated to a more robust solution.
207-
208203
> [!important]
209204
> As issue #160989 describes, unwind info is incorrect in stubs with multiple callers.
210205
> For this same reason, we cannot generate correct pac-specific unwind info: the signess
211206
> of the _incorrect_ return address is meaningless.
212207
208+
Assignment of RAStates to newly generated instructions is done in `inferUnknownStates`.
209+
We have three different cases to cover:
210+
211+
1. If a BasicBlock has some instructions with known RA state, and some without, we
212+
can copy the RAState of known instructions to the unknown ones. As the control
213+
flow only changes between BasicBlocks, instructions in the same BasicBlock have the
214+
same return address.
215+
216+
2. If all instructions in a BasicBlock are unknown, we can look at all CFG neighbors
217+
(that is predecessors/successors). The RAState should be the same as of the
218+
neighboring blocks. Conflicting RAStates in neighbors indicate an error. Such
219+
functions should be ignored.
220+
221+
3. If a BasicBlock has no CFG neighbors, we have to copy the RAState of the previous
222+
BasicBlock in layout order.
223+
224+
If any BasicBlocks remain with unknown instructions, the function will be ignored.
225+
213226
### Optimizations requiring special attention
214227

215228
Marking states before optimizations ensure that instructions can be moved around

bolt/include/bolt/Passes/InsertNegateRAStatePass.h

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//===- bolt/Passes/InsertNegateRAStatePass.cpp ----------------------------===//
1+
//===- bolt/Passes/InsertNegateRAStatePass.h ------------------------------===//
22
//
33
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
44
// See https://llvm.org/LICENSE.txt for license information.
@@ -30,9 +30,39 @@ class InsertNegateRAState : public BinaryFunctionPass {
3030
private:
3131
/// Because states are tracked as MCAnnotations on individual instructions,
3232
/// newly inserted instructions do not have a state associated with them.
33-
/// New states are "inherited" from the last known state.
3433
void inferUnknownStates(BinaryFunction &BF);
3534

35+
/// Simple case: copy RAStates to unknown insts from previous inst.
36+
/// Account for signing and authenticating insts.
37+
void fillUnknownStateInBB(BinaryContext &BC, BinaryBasicBlock &BB);
38+
39+
/// Fill unknown RAStates in BBs with no successors/predecessors. These are
40+
/// Stubs inserted by LongJmp. As of #160989, we have to copy the RAState from
41+
/// the previous BB in the layout, because CFIs are already incorrect here.
42+
void fillUnknownStubs(BinaryFunction &BF);
43+
44+
/// Fills unknowns RAStates of BBs with successors/predecessors. Uses
45+
/// getRAStateByCFG to determine the RAState. Does more than one iteration if
46+
/// needed. Reports an error, if it cannot find the RAState for all BBs with
47+
/// predecessors/successors.
48+
void fillUnknownBlocksInCFG(BinaryFunction &BF);
49+
50+
/// For BBs which only hold instructions with unknown RAState, we check
51+
/// CFG neighbors (successors, predecessors) of the BB. If they have different
52+
/// RAStates, we report an inconsistency. Otherwise, we return the found
53+
/// RAState.
54+
std::optional<bool> getRAStateByCFG(BinaryBasicBlock &BB, BinaryFunction &BF);
55+
/// Returns the first known RAState from \p BB, or std::nullopt if all are
56+
/// unknown.
57+
std::optional<bool> getFirstKnownRAState(BinaryContext &BC,
58+
BinaryBasicBlock &BB);
59+
60+
/// \p Return true if all instructions have unknown RAState.
61+
bool isUnknownBlock(BinaryContext &BC, BinaryBasicBlock &BB);
62+
63+
/// Set all instructions in \p BB to \p State.
64+
void markUnknownBlock(BinaryContext &BC, BinaryBasicBlock &BB, bool State);
65+
3666
/// Support for function splitting:
3767
/// if two consecutive BBs with Signed state are going to end up in different
3868
/// functions (so are held by different FunctionFragments), we have to add a

bolt/lib/Passes/InsertNegateRAStatePass.cpp

Lines changed: 215 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ void InsertNegateRAState::runOnFunction(BinaryFunction &BF) {
5050
continue;
5151
auto RAState = BC.MIB->getRAState(Inst);
5252
if (!RAState) {
53-
BC.errs() << "BOLT-ERROR: unknown RAState after inferUnknownStates "
54-
<< " in function " << BF.getPrintName() << "\n";
53+
BC.errs() << "BOLT-ERROR: unknown RAState after inferUnknownStates"
54+
<< " in function " << BF.getPrintName()
55+
<< ". Function will not be optimized.\n";
56+
BF.setIgnored();
5557
}
5658
if (!FirstIter) {
5759
// Consecutive instructions with different RAState means we need to
@@ -69,6 +71,20 @@ void InsertNegateRAState::runOnFunction(BinaryFunction &BF) {
6971
}
7072
}
7173

74+
void InsertNegateRAState::inferUnknownStates(BinaryFunction &BF) {
75+
BinaryContext &BC = BF.getBinaryContext();
76+
77+
// Fill in missing RAStates in simple cases (inside BBs).
78+
for (BinaryBasicBlock &BB : BF) {
79+
fillUnknownStateInBB(BC, BB);
80+
}
81+
// Some stubs have no predecessors. For those, we iterate once in the layout
82+
// order to fill their RAState.
83+
fillUnknownStubs(BF);
84+
85+
fillUnknownBlocksInCFG(BF);
86+
}
87+
7288
void InsertNegateRAState::coverFunctionFragmentStart(BinaryFunction &BF,
7389
FunctionFragment &FF) {
7490
BinaryContext &BC = BF.getBinaryContext();
@@ -86,43 +102,217 @@ void InsertNegateRAState::coverFunctionFragmentStart(BinaryFunction &BF,
86102
});
87103
// If a function is already split in the input, the first FF can also start
88104
// with Signed state. This covers that scenario as well.
89-
auto RAState = BC.MIB->getRAState(*(*FirstNonEmpty)->begin());
105+
auto II = (*FirstNonEmpty)->getFirstNonPseudo();
106+
auto RAState = BC.MIB->getRAState(*II);
90107
if (!RAState) {
91-
BC.errs() << "BOLT-ERROR: unknown RAState after inferUnknownStates "
92-
<< " in function " << BF.getPrintName() << "\n";
108+
BC.errs() << "BOLT-ERROR: unknown RAState after inferUnknownStates"
109+
<< " in function " << BF.getPrintName()
110+
<< ". Function will not be optimized.\n";
111+
BF.setIgnored();
93112
return;
94113
}
95114
if (*RAState)
96-
BF.addCFIInstruction(*FirstNonEmpty, (*FirstNonEmpty)->begin(),
115+
BF.addCFIInstruction(*FirstNonEmpty, II,
97116
MCCFIInstruction::createNegateRAState(nullptr));
98117
}
99118

100-
void InsertNegateRAState::inferUnknownStates(BinaryFunction &BF) {
119+
std::optional<bool>
120+
InsertNegateRAState::getFirstKnownRAState(BinaryContext &BC,
121+
BinaryBasicBlock &BB) {
122+
for (const MCInst &Inst : BB) {
123+
if (BC.MIB->isCFI(Inst))
124+
continue;
125+
auto RAStateOpt = BC.MIB->getRAState(Inst);
126+
if (RAStateOpt)
127+
return RAStateOpt;
128+
}
129+
return std::nullopt;
130+
}
131+
132+
void InsertNegateRAState::fillUnknownStateInBB(BinaryContext &BC,
133+
BinaryBasicBlock &BB) {
134+
135+
auto First = BB.getFirstNonPseudo();
136+
if (First == BB.end())
137+
return;
138+
// If the first instruction has unknown RAState, we should copy the first
139+
// known RAState.
140+
auto RAStateOpt = BC.MIB->getRAState(*First);
141+
if (!RAStateOpt) {
142+
auto FirstRAState = getFirstKnownRAState(BC, BB);
143+
if (!FirstRAState)
144+
// We fill unknown BBs later.
145+
return;
146+
147+
BC.MIB->setRAState(*First, *FirstRAState);
148+
}
149+
150+
// At this point we know the RAState of the first instruction,
151+
// so we can propagate the RAStates to all subsequent unknown instructions.
152+
MCInst Prev = *First;
153+
for (auto It = BB.begin() + 1; It != BB.end(); ++It) {
154+
MCInst &Inst = *It;
155+
if (BC.MIB->isCFI(Inst))
156+
continue;
157+
158+
auto PrevRAState = BC.MIB->getRAState(Prev);
159+
if (!PrevRAState)
160+
llvm_unreachable("Previous Instruction has no RAState.");
161+
162+
auto RAState = BC.MIB->getRAState(Inst);
163+
if (!RAState) {
164+
if (BC.MIB->isPSignOnLR(Prev))
165+
PrevRAState = true;
166+
else if (BC.MIB->isPAuthOnLR(Prev))
167+
PrevRAState = false;
168+
BC.MIB->setRAState(Inst, *PrevRAState);
169+
}
170+
Prev = Inst;
171+
}
172+
}
173+
174+
bool InsertNegateRAState::isUnknownBlock(BinaryContext &BC,
175+
BinaryBasicBlock &BB) {
176+
for (const MCInst &Inst : BB) {
177+
if (BC.MIB->isCFI(Inst))
178+
continue;
179+
auto RAState = BC.MIB->getRAState(Inst);
180+
if (RAState)
181+
return false;
182+
}
183+
return true;
184+
}
185+
186+
void InsertNegateRAState::markUnknownBlock(BinaryContext &BC,
187+
BinaryBasicBlock &BB, bool State) {
188+
// If we call this when an Instruction has either kRASigned or kRAUnsigned
189+
// annotation, setRASigned or setRAUnsigned would fail.
190+
assert(isUnknownBlock(BC, BB) &&
191+
"markUnknownBlock should only be called on unknown blocks");
192+
for (MCInst &Inst : BB) {
193+
if (BC.MIB->isCFI(Inst))
194+
continue;
195+
BC.MIB->setRAState(Inst, State);
196+
}
197+
}
198+
199+
std::optional<bool> InsertNegateRAState::getRAStateByCFG(BinaryBasicBlock &BB,
200+
BinaryFunction &BF) {
101201
BinaryContext &BC = BF.getBinaryContext();
102-
bool FirstIter = true;
103-
MCInst PrevInst;
104-
for (BinaryBasicBlock &BB : BF) {
105-
for (MCInst &Inst : BB) {
106-
if (BC.MIB->isCFI(Inst))
202+
203+
auto checkRAState = [&](std::optional<bool> &NeighborRAState, MCInst &Inst) {
204+
auto RAState = BC.MIB->getRAState(Inst);
205+
if (!RAState)
206+
return;
207+
if (!NeighborRAState) {
208+
NeighborRAState = *RAState;
209+
return;
210+
}
211+
if (NeighborRAState != *RAState) {
212+
BC.outs() << "BOLT-WARNING: Conflicting RAState found in function "
213+
<< BF.getPrintName() << ". Function will not be optimized.\n";
214+
BF.setIgnored();
215+
}
216+
};
217+
218+
// Holds the first found RAState from CFG neighbors.
219+
std::optional<bool> NeighborRAState = std::nullopt;
220+
if (BB.pred_size() != 0) {
221+
for (BinaryBasicBlock *PredBB : BB.predecessors()) {
222+
// find last inst of Predecessor with known RA State.
223+
auto LI = PredBB->getLastNonPseudo();
224+
if (LI == PredBB->rend())
225+
continue;
226+
MCInst &LastInst = *LI;
227+
checkRAState(NeighborRAState, LastInst);
228+
}
229+
} else if (BB.succ_size() != 0) {
230+
for (BinaryBasicBlock *SuccBB : BB.successors()) {
231+
// find first inst of Successor with known RA State.
232+
auto FI = SuccBB->getFirstNonPseudo();
233+
if (FI == SuccBB->end())
107234
continue;
235+
MCInst &FirstInst = *FI;
236+
checkRAState(NeighborRAState, FirstInst);
237+
}
238+
} else {
239+
llvm_unreachable("Called getRAStateByCFG on a BB with no preds or succs.");
240+
}
241+
242+
return NeighborRAState;
243+
}
108244

109-
auto RAState = BC.MIB->getRAState(Inst);
110-
if (!FirstIter && !RAState) {
111-
if (BC.MIB->isPSignOnLR(PrevInst))
112-
RAState = true;
113-
else if (BC.MIB->isPAuthOnLR(PrevInst))
114-
RAState = false;
115-
else {
245+
void InsertNegateRAState::fillUnknownStubs(BinaryFunction &BF) {
246+
BinaryContext &BC = BF.getBinaryContext();
247+
bool FirstIter = true;
248+
MCInst PrevInst;
249+
for (FunctionFragment &FF : BF.getLayout().fragments()) {
250+
for (BinaryBasicBlock *BB : FF) {
251+
if (!FirstIter && isUnknownBlock(BC, *BB)) {
252+
// If we have no predecessors or successors, current BB is a Stub called
253+
// from another BinaryFunction. As of #160989, we have to copy the
254+
// PrevInst's RAState, because CFIs are already incorrect here.
255+
if (BB->pred_size() == 0 && BB->succ_size() == 0) {
116256
auto PrevRAState = BC.MIB->getRAState(PrevInst);
117-
RAState = PrevRAState ? *PrevRAState : false;
257+
if (!PrevRAState) {
258+
BF.dump();
259+
BB->dump();
260+
llvm_unreachable(
261+
"Previous Instruction has no RAState in fillUnknownStubs.");
262+
continue;
263+
}
264+
265+
if (BC.MIB->isPSignOnLR(PrevInst)) {
266+
PrevRAState = true;
267+
} else if (BC.MIB->isPAuthOnLR(PrevInst)) {
268+
PrevRAState = false;
269+
}
270+
markUnknownBlock(BC, *BB, *PrevRAState);
118271
}
119-
BC.MIB->setRAState(Inst, *RAState);
120-
} else {
272+
}
273+
if (FirstIter) {
121274
FirstIter = false;
122-
if (!RAState)
123-
BC.MIB->setRAState(Inst, BF.getInitialRAState());
275+
if (isUnknownBlock(BC, *BB))
276+
markUnknownBlock(BC, *BB, false);
124277
}
125-
PrevInst = Inst;
278+
auto Last = BB->getLastNonPseudo();
279+
if (Last != BB->rend())
280+
PrevInst = *Last;
281+
}
282+
}
283+
}
284+
void InsertNegateRAState::fillUnknownBlocksInCFG(BinaryFunction &BF) {
285+
BinaryContext &BC = BF.getBinaryContext();
286+
287+
auto fillUnknowns = [&](BinaryFunction &BF) -> std::pair<int, bool> {
288+
int Unknowns = 0;
289+
bool Updated = false;
290+
for (BinaryBasicBlock &BB : BF) {
291+
// Only try to iterate if the BB has either predecessors or successors.
292+
if (isUnknownBlock(BC, BB) &&
293+
(BB.pred_size() != 0 || BB.succ_size() != 0)) {
294+
auto RAStateOpt = getRAStateByCFG(BB, BF);
295+
if (RAStateOpt) {
296+
markUnknownBlock(BC, BB, *RAStateOpt);
297+
Updated = true;
298+
} else {
299+
Unknowns++;
300+
}
301+
}
302+
}
303+
return std::pair<int, bool>{Unknowns, Updated};
304+
};
305+
306+
while (true) {
307+
std::pair<int, bool> Iter = fillUnknowns(BF);
308+
if (Iter.first == 0)
309+
break;
310+
if (!Iter.second) {
311+
BC.errs() << "BOLT-WARNING: Could not infer RAState for " << Iter.first
312+
<< " BBs in function " << BF.getPrintName()
313+
<< ". Function will not be optimized.\n";
314+
BF.setIgnored();
315+
break;
126316
}
127317
}
128318
}

0 commit comments

Comments
 (0)