Skip to content

Commit f5f28d5

Browse files
committed
[ARM] Implement BTI placement pass for PACBTI-M
This patch implements a new MachineFunction in the ARM backend for placing BTI instructions. It is similar to the existing AArch64 aarch64-branch-targets pass. BTI instructions are inserted into basic blocks that: - Have their address taken - Are the entry block of a function, if the function has external linkage or has its address taken - Are mentioned in jump tables - Are exception/cleanup landing pads Each BTI instructions is placed in the beginning of a BB after the so-called meta instructions (e.g. exception handler labels). Each outlining candidate and the outlined function need to be in agreement about whether BTI placement is enabled or not. If branch target enforcement is disabled for a function, the outliner should not covertly enable it by emitting a call to an outlined function, which begins with BTI. The cost mode of the outliner is adjusted to account for the extra BTI instructions in the outlined function. The ARM Constant Islands pass will maintain the count of the jump tables, which reference a block. A `BTI` instruction is removed from a block only if the reference count reaches zero. PAC instructions in entry blocks are replaced with PACBTI instructions (tests for this case will be added in a later patch because the compiler currently does not generate PAC instructions). The ARM Constant Island pass is adjusted to handle BTI instructions correctly. Functions with static linkage that don't have their address taken can still be called indirectly by linker-generated veneers and thus their entry points need be marked with BTI or PACBTI. The changes are tested using "LLVM IR -> assembly" tests, jump tables also have a MIR test. Unfortunately it is not possible add MIR tests for exception handling and computed gotos because of MIR parser limitations. This patch is part of a series that adds support for the PACBTI-M extension of the Armv8.1-M architecture, as detailed here: https://community.arm.com/arm-community-blogs/b/architectures-and-processors-blog/posts/armv8-1-m-pointer-authentication-and-branch-target-identification-extension The PACBTI-M specification can be found in the Armv8-M Architecture Reference Manual: https://developer.arm.com/documentation/ddi0553/latest The following people contributed to this patch: - Mikhail Maltsev - Momchil Velikov - Ties Stuij Reviewed By: ostannard Differential Revision: https://reviews.llvm.org/D112426
1 parent 72f9f06 commit f5f28d5

20 files changed

+1345
-29
lines changed

llvm/include/llvm/CodeGen/TargetInstrInfo.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1913,6 +1913,12 @@ class TargetInstrInfo : public MCInstrInfo {
19131913
"Target didn't implement TargetInstrInfo::getOutliningCandidateInfo!");
19141914
}
19151915

1916+
/// Optional target hook to create the LLVM IR attributes for the outlined
1917+
/// function. If overridden, the overriding function must call the default
1918+
/// implementation.
1919+
virtual void mergeOutliningCandidateAttributes(
1920+
Function &F, std::vector<outliner::Candidate> &Candidates) const;
1921+
19161922
/// Returns how or if \p MI should be outlined.
19171923
virtual outliner::InstrType
19181924
getOutliningType(MachineBasicBlock::iterator &MIT, unsigned Flags) const {

llvm/lib/CodeGen/MachineOutliner.cpp

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -617,20 +617,11 @@ MachineFunction *MachineOutliner::createOutlinedFunction(
617617
F->addFnAttr(Attribute::OptimizeForSize);
618618
F->addFnAttr(Attribute::MinSize);
619619

620-
// Include target features from an arbitrary candidate for the outlined
621-
// function. This makes sure the outlined function knows what kinds of
622-
// instructions are going into it. This is fine, since all parent functions
623-
// must necessarily support the instructions that are in the outlined region.
624620
Candidate &FirstCand = OF.Candidates.front();
625-
const Function &ParentFn = FirstCand.getMF()->getFunction();
626-
if (ParentFn.hasFnAttribute("target-features"))
627-
F->addFnAttr(ParentFn.getFnAttribute("target-features"));
621+
const TargetInstrInfo &TII =
622+
*FirstCand.getMF()->getSubtarget().getInstrInfo();
628623

629-
// Set nounwind, so we don't generate eh_frame.
630-
if (llvm::all_of(OF.Candidates, [](const outliner::Candidate &C) {
631-
return C.getMF()->getFunction().hasFnAttribute(Attribute::NoUnwind);
632-
}))
633-
F->addFnAttr(Attribute::NoUnwind);
624+
TII.mergeOutliningCandidateAttributes(*F, OF.Candidates);
634625

635626
BasicBlock *EntryBB = BasicBlock::Create(C, "entry", F);
636627
IRBuilder<> Builder(EntryBB);
@@ -639,8 +630,6 @@ MachineFunction *MachineOutliner::createOutlinedFunction(
639630
MachineModuleInfo &MMI = getAnalysis<MachineModuleInfoWrapperPass>().getMMI();
640631
MachineFunction &MF = MMI.getOrCreateMachineFunction(*F);
641632
MachineBasicBlock &MBB = *MF.CreateMachineBasicBlock();
642-
const TargetSubtargetInfo &STI = MF.getSubtarget();
643-
const TargetInstrInfo &TII = *STI.getInstrInfo();
644633

645634
// Insert the new function into the module.
646635
MF.insert(MF.begin(), &MBB);

llvm/lib/CodeGen/TargetInstrInfo.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1400,3 +1400,21 @@ std::string TargetInstrInfo::createMIROperandComment(
14001400
}
14011401

14021402
TargetInstrInfo::PipelinerLoopInfo::~PipelinerLoopInfo() {}
1403+
1404+
void TargetInstrInfo::mergeOutliningCandidateAttributes(
1405+
Function &F, std::vector<outliner::Candidate> &Candidates) const {
1406+
// Include target features from an arbitrary candidate for the outlined
1407+
// function. This makes sure the outlined function knows what kinds of
1408+
// instructions are going into it. This is fine, since all parent functions
1409+
// must necessarily support the instructions that are in the outlined region.
1410+
outliner::Candidate &FirstCand = Candidates.front();
1411+
const Function &ParentFn = FirstCand.getMF()->getFunction();
1412+
if (ParentFn.hasFnAttribute("target-features"))
1413+
F.addFnAttr(ParentFn.getFnAttribute("target-features"));
1414+
1415+
// Set nounwind, so we don't generate eh_frame.
1416+
if (llvm::all_of(Candidates, [](const outliner::Candidate &C) {
1417+
return C.getMF()->getFunction().hasFnAttribute(Attribute::NoUnwind);
1418+
}))
1419+
F.addFnAttr(Attribute::NoUnwind);
1420+
}

llvm/lib/Target/ARM/ARM.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ FunctionPass *createARMISelDag(ARMBaseTargetMachine &TM,
4444
FunctionPass *createA15SDOptimizerPass();
4545
FunctionPass *createARMLoadStoreOptimizationPass(bool PreAlloc = false);
4646
FunctionPass *createARMExpandPseudoPass();
47+
FunctionPass *createARMBranchTargetsPass();
4748
FunctionPass *createARMConstantIslandPass();
4849
FunctionPass *createMLxExpansionPass();
4950
FunctionPass *createThumb2ITBlockPass();
@@ -66,6 +67,7 @@ void LowerARMMachineInstrToMCInst(const MachineInstr *MI, MCInst &OutMI,
6667
void initializeARMParallelDSPPass(PassRegistry &);
6768
void initializeARMLoadStoreOptPass(PassRegistry &);
6869
void initializeARMPreAllocLoadStoreOptPass(PassRegistry &);
70+
void initializeARMBranchTargetsPass(PassRegistry &);
6971
void initializeARMConstantIslandsPass(PassRegistry &);
7072
void initializeARMExpandPseudoPass(PassRegistry &);
7173
void initializeThumb2SizeReducePass(PassRegistry &);

llvm/lib/Target/ARM/ARMBaseInstrInfo.cpp

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5737,17 +5737,17 @@ enum MachineOutlinerMBBFlags {
57375737
};
57385738

57395739
struct OutlinerCosts {
5740-
const int CallTailCall;
5741-
const int FrameTailCall;
5742-
const int CallThunk;
5743-
const int FrameThunk;
5744-
const int CallNoLRSave;
5745-
const int FrameNoLRSave;
5746-
const int CallRegSave;
5747-
const int FrameRegSave;
5748-
const int CallDefault;
5749-
const int FrameDefault;
5750-
const int SaveRestoreLROnStack;
5740+
int CallTailCall;
5741+
int FrameTailCall;
5742+
int CallThunk;
5743+
int FrameThunk;
5744+
int CallNoLRSave;
5745+
int FrameNoLRSave;
5746+
int CallRegSave;
5747+
int FrameRegSave;
5748+
int CallDefault;
5749+
int FrameDefault;
5750+
int SaveRestoreLROnStack;
57515751

57525752
OutlinerCosts(const ARMSubtarget &target)
57535753
: CallTailCall(target.isThumb() ? 4 : 4),
@@ -5868,6 +5868,24 @@ outliner::OutlinedFunction ARMBaseInstrInfo::getOutliningCandidateInfo(
58685868
return outliner::OutlinedFunction();
58695869
}
58705870

5871+
// Partition the candidates in two sets: one with BTI enabled and one with BTI
5872+
// disabled. Remove the candidates from the smaller set. We expect the
5873+
// majority of the candidates to be in consensus with regard to branch target
5874+
// enforcement with just a few oddballs, but if they are the same number
5875+
// prefer the non-BTI ones for outlining, since they have less overhead.
5876+
auto NoBTI =
5877+
llvm::partition(RepeatedSequenceLocs, [](const outliner::Candidate &C) {
5878+
const ARMFunctionInfo &AFI = *C.getMF()->getInfo<ARMFunctionInfo>();
5879+
return AFI.branchTargetEnforcement();
5880+
});
5881+
if (std::distance(RepeatedSequenceLocs.begin(), NoBTI) >
5882+
std::distance(NoBTI, RepeatedSequenceLocs.end()))
5883+
RepeatedSequenceLocs.erase(NoBTI, RepeatedSequenceLocs.end());
5884+
else
5885+
RepeatedSequenceLocs.erase(RepeatedSequenceLocs.begin(), NoBTI);
5886+
if (RepeatedSequenceLocs.size() < 2)
5887+
return outliner::OutlinedFunction();
5888+
58715889
// At this point, we have only "safe" candidates to outline. Figure out
58725890
// frame + call instruction information.
58735891

@@ -5881,6 +5899,16 @@ outliner::OutlinedFunction ARMBaseInstrInfo::getOutliningCandidateInfo(
58815899
};
58825900

58835901
OutlinerCosts Costs(Subtarget);
5902+
const auto &SomeMFI =
5903+
*RepeatedSequenceLocs.front().getMF()->getInfo<ARMFunctionInfo>();
5904+
// Adjust costs to account for the BTI instructions.
5905+
if (SomeMFI.branchTargetEnforcement()) {
5906+
Costs.FrameDefault += 4;
5907+
Costs.FrameNoLRSave += 4;
5908+
Costs.FrameRegSave += 4;
5909+
Costs.FrameTailCall += 4;
5910+
Costs.FrameThunk += 4;
5911+
}
58845912
unsigned FrameID = MachineOutlinerDefault;
58855913
unsigned NumBytesToCreateFrame = Costs.FrameDefault;
58865914

@@ -6078,7 +6106,18 @@ bool ARMBaseInstrInfo::checkAndUpdateStackOffset(MachineInstr *MI,
60786106
}
60796107

60806108
return false;
6109+
}
6110+
6111+
void ARMBaseInstrInfo::mergeOutliningCandidateAttributes(
6112+
Function &F, std::vector<outliner::Candidate> &Candidates) const {
6113+
outliner::Candidate &C = Candidates.front();
6114+
// branch-target-enforcement is guaranteed to be consistent between all
6115+
// candidates, so we only need to look at one.
6116+
const Function &CFn = C.getMF()->getFunction();
6117+
if (CFn.hasFnAttribute("branch-target-enforcement"))
6118+
F.addFnAttr(CFn.getFnAttribute("branch-target-enforcement"));
60816119

6120+
ARMGenInstrInfo::mergeOutliningCandidateAttributes(F, Candidates);
60826121
}
60836122

60846123
bool ARMBaseInstrInfo::isFunctionSafeToOutlineFrom(

llvm/lib/Target/ARM/ARMBaseInstrInfo.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,8 @@ class ARMBaseInstrInfo : public ARMGenInstrInfo {
349349
bool OutlineFromLinkOnceODRs) const override;
350350
outliner::OutlinedFunction getOutliningCandidateInfo(
351351
std::vector<outliner::Candidate> &RepeatedSequenceLocs) const override;
352+
void mergeOutliningCandidateAttributes(
353+
Function &F, std::vector<outliner::Candidate> &Candidates) const override;
352354
outliner::InstrType getOutliningType(MachineBasicBlock::iterator &MIT,
353355
unsigned Flags) const override;
354356
bool isMBBSafeToOutlineFrom(MachineBasicBlock &MBB,
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
//===-- ARMBranchTargets.cpp -- Harden code using v8.1-M BTI extension -----==//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// This pass inserts BTI instructions at the start of every function and basic
10+
// block which could be indirectly called. The hardware will (when enabled)
11+
// trap when an indirect branch or call instruction targets an instruction
12+
// which is not a valid BTI instruction. This is intended to guard against
13+
// control-flow hijacking attacks.
14+
//
15+
//===----------------------------------------------------------------------===//
16+
17+
#include "ARM.h"
18+
#include "ARMInstrInfo.h"
19+
#include "ARMMachineFunctionInfo.h"
20+
#include "llvm/CodeGen/MachineFunctionPass.h"
21+
#include "llvm/CodeGen/MachineInstrBuilder.h"
22+
#include "llvm/CodeGen/MachineJumpTableInfo.h"
23+
#include "llvm/CodeGen/MachineModuleInfo.h"
24+
#include "llvm/Support/Debug.h"
25+
26+
using namespace llvm;
27+
28+
#define DEBUG_TYPE "arm-branch-targets"
29+
#define ARM_BRANCH_TARGETS_NAME "ARM Branch Targets"
30+
31+
namespace {
32+
class ARMBranchTargets : public MachineFunctionPass {
33+
public:
34+
static char ID;
35+
ARMBranchTargets() : MachineFunctionPass(ID) {}
36+
void getAnalysisUsage(AnalysisUsage &AU) const override;
37+
bool runOnMachineFunction(MachineFunction &MF) override;
38+
StringRef getPassName() const override { return ARM_BRANCH_TARGETS_NAME; }
39+
40+
private:
41+
void addBTI(const ARMInstrInfo &TII, MachineBasicBlock &MBB, bool IsFirstBB);
42+
};
43+
} // end anonymous namespace
44+
45+
char ARMBranchTargets::ID = 0;
46+
47+
INITIALIZE_PASS(ARMBranchTargets, "arm-branch-targets", ARM_BRANCH_TARGETS_NAME,
48+
false, false)
49+
50+
void ARMBranchTargets::getAnalysisUsage(AnalysisUsage &AU) const {
51+
AU.setPreservesCFG();
52+
MachineFunctionPass::getAnalysisUsage(AU);
53+
}
54+
55+
FunctionPass *llvm::createARMBranchTargetsPass() {
56+
return new ARMBranchTargets();
57+
}
58+
59+
bool ARMBranchTargets::runOnMachineFunction(MachineFunction &MF) {
60+
if (!MF.getInfo<ARMFunctionInfo>()->branchTargetEnforcement())
61+
return false;
62+
63+
LLVM_DEBUG(dbgs() << "********** ARM Branch Targets **********\n"
64+
<< "********** Function: " << MF.getName() << '\n');
65+
const ARMInstrInfo &TII =
66+
*static_cast<const ARMInstrInfo *>(MF.getSubtarget().getInstrInfo());
67+
68+
// LLVM does not consider basic blocks which are the targets of jump tables
69+
// to be address-taken (the address can't escape anywhere else), but they are
70+
// used for indirect branches, so need BTI instructions.
71+
SmallPtrSet<const MachineBasicBlock *, 8> JumpTableTargets;
72+
if (const MachineJumpTableInfo *JTI = MF.getJumpTableInfo())
73+
for (const MachineJumpTableEntry &JTE : JTI->getJumpTables())
74+
for (const MachineBasicBlock *MBB : JTE.MBBs)
75+
JumpTableTargets.insert(MBB);
76+
77+
bool MadeChange = false;
78+
for (MachineBasicBlock &MBB : MF) {
79+
bool NeedBTI = false;
80+
bool IsFirstBB = &MBB == &MF.front();
81+
82+
// Every function can potentially be called indirectly (even if it has
83+
// static linkage, due to linker-generated veneers).
84+
if (IsFirstBB)
85+
NeedBTI = true;
86+
87+
// If the block itself is address-taken, or is an exception landing pad, it
88+
// could be indirectly branched to.
89+
if (MBB.hasAddressTaken() || MBB.isEHPad() || JumpTableTargets.count(&MBB))
90+
NeedBTI = true;
91+
92+
if (NeedBTI) {
93+
addBTI(TII, MBB, IsFirstBB);
94+
MadeChange = true;
95+
}
96+
}
97+
98+
return MadeChange;
99+
}
100+
101+
/// Insert a BTI/PACBTI instruction into a given basic block \c MBB. If
102+
/// \c IsFirstBB is true (meaning that this is the first BB in a function) try
103+
/// to find a PAC instruction and replace it with PACBTI. Otherwise just insert
104+
/// a BTI instruction.
105+
/// The point of insertion is in the beginning of the BB, immediately after meta
106+
/// instructions (such labels in exception handling landing pads).
107+
void ARMBranchTargets::addBTI(const ARMInstrInfo &TII, MachineBasicBlock &MBB,
108+
bool IsFirstBB) {
109+
// Which instruction to insert: BTI or PACBTI
110+
unsigned OpCode = ARM::t2BTI;
111+
112+
// Skip meta instructions, including EH labels
113+
auto MBBI = llvm::find_if_not(MBB.instrs(), [](const MachineInstr &MI) {
114+
return MI.isMetaInstruction();
115+
});
116+
117+
// If this is the first BB in a function, check if it starts with a PAC
118+
// instruction and in that case remove the PAC instruction.
119+
if (IsFirstBB) {
120+
if (MBBI != MBB.instr_end() && MBBI->getOpcode() == ARM::t2PAC) {
121+
LLVM_DEBUG(dbgs() << "Removing a 'PAC' instr from BB '" << MBB.getName()
122+
<< "' to replace with PACBTI\n");
123+
OpCode = ARM::t2PACBTI;
124+
auto NextMBBI = std::next(MBBI);
125+
MBBI->eraseFromParent();
126+
MBBI = NextMBBI;
127+
}
128+
}
129+
130+
LLVM_DEBUG(dbgs() << "Inserting a '"
131+
<< (OpCode == ARM::t2BTI ? "BTI" : "PACBTI")
132+
<< "' instr into BB '" << MBB.getName() << "'\n");
133+
// Finally, insert a new instruction (either PAC or PACBTI)
134+
BuildMI(MBB, MBBI, MBB.findDebugLoc(MBBI), TII.get(OpCode));
135+
}

0 commit comments

Comments
 (0)