Skip to content

Commit cec2d5c

Browse files
committed
Reland [AArch64][MachineOutliner] Return address signing for outlined functions
Summary: Reland after fixing an ASan failure by stopping outlining early if the constraints for return address signing removed too many outlining candidates. During AArch64 frame lowering instructions to enable return address signing are inserted into functions if needed. Functions generated during machine outlining don't run through target frame lowering and hence are missing such instructions. This patch introduces the following changes: 1. If not all functions that potentially participate in function outlining agree on their return address signing scope and their return address signing key, outlining is disabled for these functions. 2. If not all functions that potentially participate in function outlining agree on their support for v8.3A features, outlining is disabled for these functions. 3. If an outlining candidate would outline instructions that modify sp in a way that invalidates return address signing, outlining is disabled for that particular candidate. 4. If all candidate functions agree on the signing scope, signing key and their support for v8.3 features, the outlined function behaves as if it had the same scope and key attributes and as if it would provide the same v8.3A support as the original functions. Reviewers: ostannard, paquette Reviewed By: ostannard Subscribers: kristof.beyls, hiraditya, llvm-commits Tags: #llvm Differential Revision: https://reviews.llvm.org/D70635
1 parent 28f5ad5 commit cec2d5c

12 files changed

+1275
-8
lines changed

llvm/lib/Target/AArch64/AArch64InstrInfo.cpp

Lines changed: 288 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5412,21 +5412,199 @@ AArch64InstrInfo::findRegisterToSaveLRTo(const outliner::Candidate &C) const {
54125412
return 0u;
54135413
}
54145414

5415-
outliner::OutlinedFunction
5416-
AArch64InstrInfo::getOutliningCandidateInfo(
5415+
static bool
5416+
outliningCandidatesSigningScopeConsensus(const outliner::Candidate &a,
5417+
const outliner::Candidate &b) {
5418+
const Function &Fa = a.getMF()->getFunction();
5419+
const Function &Fb = b.getMF()->getFunction();
5420+
5421+
// If none of the functions have the "sign-return-address" attribute their
5422+
// signing behaviour is equal
5423+
if (!Fa.hasFnAttribute("sign-return-address") &&
5424+
!Fb.hasFnAttribute("sign-return-address")) {
5425+
return true;
5426+
}
5427+
5428+
// If both functions have the "sign-return-address" attribute their signing
5429+
// behaviour is equal, if the values of the attributes are equal
5430+
if (Fa.hasFnAttribute("sign-return-address") &&
5431+
Fb.hasFnAttribute("sign-return-address")) {
5432+
StringRef ScopeA =
5433+
Fa.getFnAttribute("sign-return-address").getValueAsString();
5434+
StringRef ScopeB =
5435+
Fb.getFnAttribute("sign-return-address").getValueAsString();
5436+
return ScopeA.equals(ScopeB);
5437+
}
5438+
5439+
// If function B doesn't have the "sign-return-address" attribute but A does,
5440+
// the functions' signing behaviour is equal if A's value for
5441+
// "sign-return-address" is "none" and vice versa.
5442+
if (Fa.hasFnAttribute("sign-return-address")) {
5443+
StringRef ScopeA =
5444+
Fa.getFnAttribute("sign-return-address").getValueAsString();
5445+
return ScopeA.equals("none");
5446+
}
5447+
5448+
if (Fb.hasFnAttribute("sign-return-address")) {
5449+
StringRef ScopeB =
5450+
Fb.getFnAttribute("sign-return-address").getValueAsString();
5451+
return ScopeB.equals("none");
5452+
}
5453+
5454+
llvm_unreachable("Unkown combination of sign-return-address attributes");
5455+
}
5456+
5457+
static bool
5458+
outliningCandidatesSigningKeyConsensus(const outliner::Candidate &a,
5459+
const outliner::Candidate &b) {
5460+
const Function &Fa = a.getMF()->getFunction();
5461+
const Function &Fb = b.getMF()->getFunction();
5462+
5463+
// If none of the functions have the "sign-return-address-key" attribute
5464+
// their keys are equal
5465+
if (!Fa.hasFnAttribute("sign-return-address-key") &&
5466+
!Fb.hasFnAttribute("sign-return-address-key")) {
5467+
return true;
5468+
}
5469+
5470+
// If both functions have the "sign-return-address-key" attribute their
5471+
// keys are equal if the values of "sign-return-address-key" are equal
5472+
if (Fa.hasFnAttribute("sign-return-address-key") &&
5473+
Fb.hasFnAttribute("sign-return-address-key")) {
5474+
StringRef KeyA =
5475+
Fa.getFnAttribute("sign-return-address-key").getValueAsString();
5476+
StringRef KeyB =
5477+
Fb.getFnAttribute("sign-return-address-key").getValueAsString();
5478+
return KeyA.equals(KeyB);
5479+
}
5480+
5481+
// If B doesn't have the "sign-return-address-key" attribute, both keys are
5482+
// equal, if function a has the default key (a_key)
5483+
if (Fa.hasFnAttribute("sign-return-address-key")) {
5484+
StringRef KeyA =
5485+
Fa.getFnAttribute("sign-return-address-key").getValueAsString();
5486+
return KeyA.equals_lower("a_key");
5487+
}
5488+
5489+
if (Fb.hasFnAttribute("sign-return-address-key")) {
5490+
StringRef KeyB =
5491+
Fb.getFnAttribute("sign-return-address-key").getValueAsString();
5492+
return KeyB.equals_lower("a_key");
5493+
}
5494+
5495+
llvm_unreachable("Unkown combination of sign-return-address-key attributes");
5496+
}
5497+
5498+
static bool outliningCandidatesV8_3OpsConsensus(const outliner::Candidate &a,
5499+
const outliner::Candidate &b) {
5500+
const AArch64Subtarget &SubtargetA =
5501+
a.getMF()->getSubtarget<AArch64Subtarget>();
5502+
const AArch64Subtarget &SubtargetB =
5503+
b.getMF()->getSubtarget<AArch64Subtarget>();
5504+
return SubtargetA.hasV8_3aOps() == SubtargetB.hasV8_3aOps();
5505+
}
5506+
5507+
outliner::OutlinedFunction AArch64InstrInfo::getOutliningCandidateInfo(
54175508
std::vector<outliner::Candidate> &RepeatedSequenceLocs) const {
54185509
outliner::Candidate &FirstCand = RepeatedSequenceLocs[0];
54195510
unsigned SequenceSize =
54205511
std::accumulate(FirstCand.front(), std::next(FirstCand.back()), 0,
54215512
[this](unsigned Sum, const MachineInstr &MI) {
54225513
return Sum + getInstSizeInBytes(MI);
54235514
});
5515+
unsigned NumBytesToCreateFrame = 0;
5516+
5517+
// We only allow outlining for functions having exactly matching return
5518+
// address signing attributes, i.e., all share the same value for the
5519+
// attribute "sign-return-address" and all share the same type of key they
5520+
// are signed with.
5521+
// Additionally we require all functions to simultaniously either support
5522+
// v8.3a features or not. Otherwise an outlined function could get signed
5523+
// using dedicated v8.3 instructions and a call from a function that doesn't
5524+
// support v8.3 instructions would therefore be invalid.
5525+
if (std::adjacent_find(
5526+
RepeatedSequenceLocs.begin(), RepeatedSequenceLocs.end(),
5527+
[](const outliner::Candidate &a, const outliner::Candidate &b) {
5528+
// Return true if a and b are non-equal w.r.t. return address
5529+
// signing or support of v8.3a features
5530+
if (outliningCandidatesSigningScopeConsensus(a, b) &&
5531+
outliningCandidatesSigningKeyConsensus(a, b) &&
5532+
outliningCandidatesV8_3OpsConsensus(a, b)) {
5533+
return false;
5534+
}
5535+
return true;
5536+
}) != RepeatedSequenceLocs.end()) {
5537+
return outliner::OutlinedFunction();
5538+
}
5539+
5540+
// Since at this point all candidates agree on their return address signing
5541+
// picking just one is fine. If the candidate functions potentially sign their
5542+
// return addresses, the outlined function should do the same. Note that in
5543+
// the case of "sign-return-address"="non-leaf" this is an assumption: It is
5544+
// not certainly true that the outlined function will have to sign its return
5545+
// address but this decision is made later, when the decision to outline
5546+
// has already been made.
5547+
// The same holds for the number of additional instructions we need: On
5548+
// v8.3a RET can be replaced by RETAA/RETAB and no AUT instruction is
5549+
// necessary. However, at this point we don't know if the outlined function
5550+
// will have a RET instruction so we assume the worst.
5551+
const Function &FCF = FirstCand.getMF()->getFunction();
5552+
const TargetRegisterInfo &TRI = getRegisterInfo();
5553+
if (FCF.hasFnAttribute("sign-return-address")) {
5554+
// One PAC and one AUT instructions
5555+
NumBytesToCreateFrame += 8;
5556+
5557+
// We have to check if sp modifying instructions would get outlined.
5558+
// If so we only allow outlining if sp is unchanged overall, so matching
5559+
// sub and add instructions are okay to outline, all other sp modifications
5560+
// are not
5561+
auto hasIllegalSPModification = [&TRI](outliner::Candidate &C) {
5562+
int SPValue = 0;
5563+
MachineBasicBlock::iterator MBBI = C.front();
5564+
for (;;) {
5565+
if (MBBI->modifiesRegister(AArch64::SP, &TRI)) {
5566+
switch (MBBI->getOpcode()) {
5567+
case AArch64::ADDXri:
5568+
case AArch64::ADDWri:
5569+
assert(MBBI->getNumOperands() == 4 && "Wrong number of operands");
5570+
assert(MBBI->getOperand(2).isImm() &&
5571+
"Expected operand to be immediate");
5572+
SPValue += MBBI->getOperand(2).getImm();
5573+
break;
5574+
case AArch64::SUBXri:
5575+
case AArch64::SUBWri:
5576+
assert(MBBI->getNumOperands() == 4 && "Wrong number of operands");
5577+
assert(MBBI->getOperand(2).isImm() &&
5578+
"Expected operand to be immediate");
5579+
SPValue -= MBBI->getOperand(2).getImm();
5580+
break;
5581+
default:
5582+
return true;
5583+
}
5584+
}
5585+
if (MBBI == C.back())
5586+
break;
5587+
++MBBI;
5588+
}
5589+
if (SPValue)
5590+
return true;
5591+
return false;
5592+
};
5593+
// Remove candidates with illegal stack modifying instructions
5594+
RepeatedSequenceLocs.erase(std::remove_if(RepeatedSequenceLocs.begin(),
5595+
RepeatedSequenceLocs.end(),
5596+
hasIllegalSPModification),
5597+
RepeatedSequenceLocs.end());
5598+
5599+
// If the sequence doesn't have enough candidates left, then we're done.
5600+
if (RepeatedSequenceLocs.size() < 2)
5601+
return outliner::OutlinedFunction();
5602+
}
54245603

54255604
// Properties about candidate MBBs that hold for all of them.
54265605
unsigned FlagsSetInAll = 0xF;
54275606

54285607
// Compute liveness information for each candidate, and set FlagsSetInAll.
5429-
const TargetRegisterInfo &TRI = getRegisterInfo();
54305608
std::for_each(RepeatedSequenceLocs.begin(), RepeatedSequenceLocs.end(),
54315609
[&FlagsSetInAll](outliner::Candidate &C) {
54325610
FlagsSetInAll &= C.Flags;
@@ -5482,7 +5660,7 @@ AArch64InstrInfo::getOutliningCandidateInfo(
54825660
};
54835661

54845662
unsigned FrameID = MachineOutlinerDefault;
5485-
unsigned NumBytesToCreateFrame = 4;
5663+
NumBytesToCreateFrame += 4;
54865664

54875665
bool HasBTI = any_of(RepeatedSequenceLocs, [](outliner::Candidate &C) {
54885666
return C.getMF()->getFunction().hasFnAttribute("branch-target-enforcement");
@@ -5751,6 +5929,19 @@ AArch64InstrInfo::getOutliningType(MachineBasicBlock::iterator &MIT,
57515929
MachineFunction *MF = MBB->getParent();
57525930
AArch64FunctionInfo *FuncInfo = MF->getInfo<AArch64FunctionInfo>();
57535931

5932+
// Don't outline anything used for return address signing. The outlined
5933+
// function will get signed later if needed
5934+
switch (MI.getOpcode()) {
5935+
case AArch64::PACIASP:
5936+
case AArch64::PACIBSP:
5937+
case AArch64::AUTIASP:
5938+
case AArch64::AUTIBSP:
5939+
case AArch64::RETAA:
5940+
case AArch64::RETAB:
5941+
case AArch64::EMITBKEY:
5942+
return outliner::InstrType::Illegal;
5943+
}
5944+
57545945
// Don't outline LOHs.
57555946
if (FuncInfo->getLOHRelated().count(&MI))
57565947
return outliner::InstrType::Illegal;
@@ -5903,6 +6094,59 @@ void AArch64InstrInfo::fixupPostOutline(MachineBasicBlock &MBB) const {
59036094
}
59046095
}
59056096

6097+
static void signOutlinedFunction(MachineFunction &MF, MachineBasicBlock &MBB,
6098+
bool ShouldSignReturnAddr,
6099+
bool ShouldSignReturnAddrWithAKey) {
6100+
if (ShouldSignReturnAddr) {
6101+
MachineBasicBlock::iterator MBBPAC = MBB.begin();
6102+
MachineBasicBlock::iterator MBBAUT = MBB.getFirstTerminator();
6103+
const AArch64Subtarget &Subtarget = MF.getSubtarget<AArch64Subtarget>();
6104+
const TargetInstrInfo *TII = Subtarget.getInstrInfo();
6105+
DebugLoc DL;
6106+
6107+
if (MBBAUT != MBB.end())
6108+
DL = MBBAUT->getDebugLoc();
6109+
6110+
// At the very beginning of the basic block we insert the following
6111+
// depending on the key type
6112+
//
6113+
// a_key: b_key:
6114+
// PACIASP EMITBKEY
6115+
// CFI_INSTRUCTION PACIBSP
6116+
// CFI_INSTRUCTION
6117+
if (ShouldSignReturnAddrWithAKey) {
6118+
BuildMI(MBB, MBBPAC, DebugLoc(), TII->get(AArch64::PACIASP))
6119+
.setMIFlag(MachineInstr::FrameSetup);
6120+
} else {
6121+
BuildMI(MBB, MBBPAC, DebugLoc(), TII->get(AArch64::EMITBKEY))
6122+
.setMIFlag(MachineInstr::FrameSetup);
6123+
BuildMI(MBB, MBBPAC, DebugLoc(), TII->get(AArch64::PACIBSP))
6124+
.setMIFlag(MachineInstr::FrameSetup);
6125+
}
6126+
unsigned CFIIndex =
6127+
MF.addFrameInst(MCCFIInstruction::createNegateRAState(nullptr));
6128+
BuildMI(MBB, MBBPAC, DebugLoc(), TII->get(AArch64::CFI_INSTRUCTION))
6129+
.addCFIIndex(CFIIndex)
6130+
.setMIFlags(MachineInstr::FrameSetup);
6131+
6132+
// If v8.3a features are available we can replace a RET instruction by
6133+
// RETAA or RETAB and omit the AUT instructions
6134+
if (Subtarget.hasV8_3aOps() && MBBAUT != MBB.end() &&
6135+
MBBAUT->getOpcode() == AArch64::RET) {
6136+
BuildMI(MBB, MBBAUT, DL,
6137+
TII->get(ShouldSignReturnAddrWithAKey ? AArch64::RETAA
6138+
: AArch64::RETAB))
6139+
.copyImplicitOps(*MBBAUT);
6140+
MBB.erase(MBBAUT);
6141+
} else {
6142+
BuildMI(MBB, MBBAUT, DL,
6143+
TII->get(ShouldSignReturnAddrWithAKey ? AArch64::AUTIASP
6144+
: AArch64::AUTIBSP))
6145+
.setMIFlag(MachineInstr::FrameDestroy);
6146+
}
6147+
}
6148+
}
6149+
59066150
void AArch64InstrInfo::buildOutlinedFrame(
59076151
MachineBasicBlock &MBB, MachineFunction &MF,
59086152
const outliner::OutlinedFunction &OF) const {
@@ -5918,23 +6162,28 @@ void AArch64InstrInfo::buildOutlinedFrame(
59186162
TailOpcode = AArch64::TCRETURNriALL;
59196163
}
59206164
MachineInstr *TC = BuildMI(MF, DebugLoc(), get(TailOpcode))
5921-
.add(Call->getOperand(0))
5922-
.addImm(0);
6165+
.add(Call->getOperand(0))
6166+
.addImm(0);
59236167
MBB.insert(MBB.end(), TC);
59246168
Call->eraseFromParent();
59256169
}
59266170

6171+
bool IsLeafFunction = true;
6172+
59276173
// Is there a call in the outlined range?
5928-
auto IsNonTailCall = [](MachineInstr &MI) {
6174+
auto IsNonTailCall = [](const MachineInstr &MI) {
59296175
return MI.isCall() && !MI.isReturn();
59306176
};
6177+
59316178
if (std::any_of(MBB.instr_begin(), MBB.instr_end(), IsNonTailCall)) {
59326179
// Fix up the instructions in the range, since we're going to modify the
59336180
// stack.
59346181
assert(OF.FrameConstructionID != MachineOutlinerDefault &&
59356182
"Can only fix up stack references once");
59366183
fixupPostOutline(MBB);
59376184

6185+
IsLeafFunction = false;
6186+
59386187
// LR has to be a live in so that we can save it.
59396188
MBB.addLiveIn(AArch64::LR);
59406189

@@ -5981,16 +6230,47 @@ void AArch64InstrInfo::buildOutlinedFrame(
59816230
Et = MBB.insert(Et, LDRXpost);
59826231
}
59836232

6233+
// If a bunch of candidates reach this point they must agree on their return
6234+
// address signing. It is therefore enough to just consider the signing
6235+
// behaviour of one of them
6236+
const Function &CF = OF.Candidates.front().getMF()->getFunction();
6237+
bool ShouldSignReturnAddr = false;
6238+
if (CF.hasFnAttribute("sign-return-address")) {
6239+
StringRef Scope =
6240+
CF.getFnAttribute("sign-return-address").getValueAsString();
6241+
if (Scope.equals("all"))
6242+
ShouldSignReturnAddr = true;
6243+
else if (Scope.equals("non-leaf") && !IsLeafFunction)
6244+
ShouldSignReturnAddr = true;
6245+
}
6246+
6247+
// a_key is the default
6248+
bool ShouldSignReturnAddrWithAKey = true;
6249+
if (CF.hasFnAttribute("sign-return-address-key")) {
6250+
const StringRef Key =
6251+
CF.getFnAttribute("sign-return-address-key").getValueAsString();
6252+
// Key can either be a_key or b_key
6253+
assert((Key.equals_lower("a_key") || Key.equals_lower("b_key")) &&
6254+
"Return address signing key must be either a_key or b_key");
6255+
ShouldSignReturnAddrWithAKey = Key.equals_lower("a_key");
6256+
}
6257+
59846258
// If this is a tail call outlined function, then there's already a return.
59856259
if (OF.FrameConstructionID == MachineOutlinerTailCall ||
5986-
OF.FrameConstructionID == MachineOutlinerThunk)
6260+
OF.FrameConstructionID == MachineOutlinerThunk) {
6261+
signOutlinedFunction(MF, MBB, ShouldSignReturnAddr,
6262+
ShouldSignReturnAddrWithAKey);
59876263
return;
6264+
}
59886265

59896266
// It's not a tail call, so we have to insert the return ourselves.
59906267
MachineInstr *ret = BuildMI(MF, DebugLoc(), get(AArch64::RET))
59916268
.addReg(AArch64::LR, RegState::Undef);
59926269
MBB.insert(MBB.end(), ret);
59936270

6271+
signOutlinedFunction(MF, MBB, ShouldSignReturnAddr,
6272+
ShouldSignReturnAddrWithAKey);
6273+
59946274
// Did we have to modify the stack by saving the link register?
59956275
if (OF.FrameConstructionID != MachineOutlinerDefault)
59966276
return;

0 commit comments

Comments
 (0)