[BOLT][BTI] Add MCPlusBuilder::insertBTI (#167329)

This function contains most of the logic for BTI:
- it takes the BasicBlock and the instruction used to jump to it.
- Then it checks if the first non-pseudo instruction is a sufficient
landing pad for the used call.
- if not, it generates the correct BTI instruction.

Also introduce the isCallCoveredByBTI helper to simplify the logic.
This commit is contained in:
Gergely Bálint
2025-12-11 11:58:24 +01:00
committed by GitHub
parent 8af88a45ca
commit fbc121ce1e
3 changed files with 204 additions and 0 deletions

View File

@@ -1894,6 +1894,19 @@ public:
llvm_unreachable("not implemented");
}
/// Checks if the indirect call / jump is accepted by the landing pad at the
/// start of the target BasicBlock.
virtual bool isCallCoveredByBTI(MCInst &Call, MCInst &Pad) const {
llvm_unreachable("not implemented");
return false;
}
/// Inserts a BTI landing pad to the start of the BB, that matches the
/// indirect call inst used to call the BB.
virtual void insertBTI(BinaryBasicBlock &BB, MCInst &Call) const {
llvm_unreachable("not implemented");
}
/// Store \p Target absolute address to \p RegName
virtual InstructionListType materializeAddress(const MCSymbol *Target,
MCContext *Ctx,

View File

@@ -2806,6 +2806,81 @@ public:
Inst.addOperand(MCOperand::createImm(HintNum));
}
bool isCallCoveredByBTI(MCInst &Call, MCInst &Pad) const override {
assert((isIndirectCall(Call) || isIndirectBranch(Call)) &&
"Not an indirect call or branch.");
// A BLR can be accepted by a BTI c.
if (isIndirectCall(Call))
return isBTILandingPad(Pad, true, false) ||
isBTILandingPad(Pad, true, true);
// A BR can be accepted by a BTI j or BTI c (and BTI jc) IF the operand is
// x16 or x17. If the operand is not x16 or x17, it can be accepted by a BTI
// j or BTI jc (and not BTI c).
if (isIndirectBranch(Call)) {
assert(Call.getNumOperands() == 1 &&
"Indirect branch needs to have 1 operand.");
assert(Call.getOperand(0).isReg() &&
"Indirect branch does not have a register operand.");
MCPhysReg Reg = Call.getOperand(0).getReg();
if (Reg == AArch64::X16 || Reg == AArch64::X17)
return isBTILandingPad(Pad, true, false) ||
isBTILandingPad(Pad, false, true) ||
isBTILandingPad(Pad, true, true);
return isBTILandingPad(Pad, false, true) ||
isBTILandingPad(Pad, true, true);
}
return false;
}
void insertBTI(BinaryBasicBlock &BB, MCInst &Call) const override {
auto II = BB.getFirstNonPseudo();
// Only check the first instruction for non-empty BasicBlocks
bool Empty = (II == BB.end());
if (!Empty && isCallCoveredByBTI(Call, *II))
return;
// A BLR can be accepted by a BTI c.
if (isIndirectCall(Call)) {
// if we have a BTI j at the start, extend it to a BTI jc,
// otherwise insert a new BTI c.
if (!Empty && isBTILandingPad(*II, false, true)) {
updateBTIVariant(*II, true, true);
} else {
MCInst BTIInst;
createBTI(BTIInst, true, false);
BB.insertInstruction(II, BTIInst);
}
}
// A BR can be accepted by a BTI j or BTI c (and BTI jc) IF the operand is
// x16 or x17. If the operand is not x16 or x17, it can be accepted by a
// BTI j or BTI jc (and not BTI c).
if (isIndirectBranch(Call)) {
assert(Call.getNumOperands() == 1 &&
"Indirect branch needs to have 1 operand.");
assert(Call.getOperand(0).isReg() &&
"Indirect branch does not have a register operand.");
MCPhysReg Reg = Call.getOperand(0).getReg();
if (Reg == AArch64::X16 || Reg == AArch64::X17) {
// Add a new BTI c
MCInst BTIInst;
createBTI(BTIInst, true, false);
BB.insertInstruction(II, BTIInst);
} else {
// If BB starts with a BTI c, extend it to BTI jc,
// otherwise insert a new BTI j.
if (!Empty && isBTILandingPad(*II, true, false)) {
updateBTIVariant(*II, true, true);
} else {
MCInst BTIInst;
createBTI(BTIInst, false, true);
BB.insertInstruction(II, BTIInst);
}
}
}
}
InstructionListType materializeAddress(const MCSymbol *Target, MCContext *Ctx,
MCPhysReg RegName,
int64_t Addend = 0) const override {

View File

@@ -198,6 +198,122 @@ TEST_P(MCPlusBuilderTester, AArch64_BTI) {
ASSERT_TRUE(BC->MIB->isImplicitBTIC(*II));
}
TEST_P(MCPlusBuilderTester, AArch64_insertBTI_empty) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();
BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true);
std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock();
MCInst CallInst = MCInstBuilder(AArch64::BR).addReg(AArch64::X16);
BC->MIB->insertBTI(*BB, CallInst);
// Check that BTI c is added to the empty block.
auto II = BB->begin();
ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false));
}
TEST_P(MCPlusBuilderTester, AArch64_insertBTI_0) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();
BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true);
std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock();
MCInst Inst = MCInstBuilder(AArch64::RET).addReg(AArch64::LR);
BB->addInstruction(Inst);
// BR x16 needs BTI c or BTI j. We prefer adding a BTI c.
MCInst CallInst = MCInstBuilder(AArch64::BR).addReg(AArch64::X16);
BC->MIB->insertBTI(*BB, CallInst);
auto II = BB->begin();
ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false));
}
TEST_P(MCPlusBuilderTester, AArch64_insertBTI_1) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();
BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true);
std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock();
MCInst BTIc;
BC->MIB->createBTI(BTIc, true, false);
BB->addInstruction(BTIc);
// BR x16 needs BTI c or BTI j. We have a BTI c, no change is needed.
MCInst CallInst = MCInstBuilder(AArch64::BR).addReg(AArch64::X16);
BC->MIB->insertBTI(*BB, CallInst);
auto II = BB->begin();
ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false));
}
TEST_P(MCPlusBuilderTester, AArch64_insertBTI_2) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();
BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true);
std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock();
MCInst BTIc;
BC->MIB->createBTI(BTIc, true, false);
BB->addInstruction(BTIc);
// BR x5 needs BTI j
// we have BTI c -> extend it to BTI jc.
MCInst CallInst = MCInstBuilder(AArch64::BR).addReg(AArch64::X5);
BC->MIB->insertBTI(*BB, CallInst);
auto II = BB->begin();
ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, true));
}
TEST_P(MCPlusBuilderTester, AArch64_insertBTI_3) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();
BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true);
std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock();
MCInst Inst = MCInstBuilder(AArch64::RET).addReg(AArch64::LR);
BB->addInstruction(Inst);
// BR x5 needs BTI j
MCInst CallInst = MCInstBuilder(AArch64::BR).addReg(AArch64::X5);
BC->MIB->insertBTI(*BB, CallInst);
auto II = BB->begin();
ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, false, true));
}
TEST_P(MCPlusBuilderTester, AArch64_insertBTI_4) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();
BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true);
std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock();
MCInst Inst = MCInstBuilder(AArch64::RET).addReg(AArch64::LR);
BB->addInstruction(Inst);
// BLR needs BTI c, regardless of the register used.
MCInst CallInst = MCInstBuilder(AArch64::BLR).addReg(AArch64::X5);
BC->MIB->insertBTI(*BB, CallInst);
auto II = BB->begin();
ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false));
}
TEST_P(MCPlusBuilderTester, AArch64_insertBTI_5) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();
BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true);
std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock();
MCInst BTIj;
BC->MIB->createBTI(BTIj, false, true);
BB->addInstruction(BTIj);
// BLR needs BTI c, regardless of the register used.
// We have a BTI j -> extend it to BTI jc.
MCInst CallInst = MCInstBuilder(AArch64::BLR).addReg(AArch64::X5);
BC->MIB->insertBTI(*BB, CallInst);
auto II = BB->begin();
ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, true));
}
TEST_P(MCPlusBuilderTester, AArch64_insertBTI_6) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();
BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true);
std::unique_ptr<BinaryBasicBlock> BB = BF->createBasicBlock();
MCInst Paciasp =
MCInstBuilder(AArch64::PACIASP).addReg(AArch64::LR).addReg(AArch64::SP);
BB->addInstruction(Paciasp);
// PACI(AB)SP are implicit BTI c, no change needed.
MCInst CallInst = MCInstBuilder(AArch64::BR).addReg(AArch64::X17);
BC->MIB->insertBTI(*BB, CallInst);
auto II = BB->begin();
ASSERT_TRUE(BC->MIB->isBTILandingPad(*II, true, false));
ASSERT_TRUE(BC->MIB->isPSignOnLR(*II));
}
TEST_P(MCPlusBuilderTester, AArch64_CmpJNE) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();