[BOLT][AArch64] Add minimal support for liveness analysis. (#183298)

In this patch I am adding the missing target hooks required for the
liveness analysis to run on AArch64. These are
 - getFlagsReg()
 - getRegsUsedAsParams()
 - getDefaultLiveOut()
 - getGPRegs()
 - isCleanRegXOR()

I am also introducing the following API in LivenessAnalysis
 - BitVector getLiveIn/Out(const MCInst &)
 - MCPhysReg scavengeRegFromState(BitVector &)
 
My intention is to allow the LongJmp pass scavenge usable registers when
injecting code.
This commit is contained in:
Alexandros Lamprineas
2026-04-02 11:59:59 +01:00
committed by GitHub
parent 3468ee025e
commit 64b728128d
7 changed files with 403 additions and 10 deletions

View File

@@ -1595,7 +1595,7 @@ public:
llvm_unreachable("not implemented");
}
/// Similar to getDefaultDefIn
/// Registers which may contain a meaningful value after a function returns.
virtual void getDefaultLiveOut(BitVector &Regs) const {
llvm_unreachable("not implemented");
}
@@ -1605,6 +1605,11 @@ public:
llvm_unreachable("not implemented");
}
/// Remove non scavengeable special registers from \p Regs
virtual void removeNonScavengeableRegs(BitVector &Regs) const {
llvm_unreachable("not implemented");
}
/// Change \p Regs with a bitmask with all general purpose regs that can be
/// encoded without extra prefix bytes. For x86 only.
virtual void getClassicGPRegs(BitVector &Regs) const {

View File

@@ -36,6 +36,16 @@ public:
NumRegs(BF.getBinaryContext().MRI->getNumRegs()) {}
virtual ~LivenessAnalysis();
// Return the state before the execution of an Instruction.
BitVector getLiveIn(const MCInst &Inst) const {
return *this->getStateAt(Inst);
}
// Return the state after the execution of an Instruction.
BitVector getLiveOut(const MCInst &Inst) const {
return *this->getStateBefore(Inst);
}
bool isAlive(ProgramPoint PP, MCPhysReg Reg) const {
const BitVector &BV = *this->getStateAt(PP);
const BitVector &RegAliases = BC.MIB->getAliases(Reg);
@@ -46,18 +56,22 @@ public:
// Return a usable general-purpose reg after point P. Return 0 if no reg is
// available.
MCPhysReg scavengeRegAfter(ProgramPoint P) {
MCPhysReg scavengeRegAfter(ProgramPoint P) const {
BitVector BV = *this->getStateAt(P);
BV.flip();
return scavengeRegFromState(BV);
}
// Return a usable general-purpose reg given a liveness state. Return 0 if
// no reg is available.
MCPhysReg scavengeRegFromState(BitVector &LiveRegs) const {
BitVector GPRegs(NumRegs, false);
this->BC.MIB->getGPRegs(GPRegs, /*IncludeAlias=*/false);
// Ignore the register used for frame pointer even if it is not alive (it
// may be used by CFI which is not represented in our dataflow).
BitVector FP = BC.MIB->getAliases(BC.MIB->getFramePointer());
FP.flip();
BV &= GPRegs;
BV &= FP;
int Reg = BV.find_first();
LiveRegs.flip();
LiveRegs &= GPRegs;
// Ignore target-specific special registers even if they are dead
// (they may be used by CFI which is not represented in our dataflow).
BC.MIB->removeNonScavengeableRegs(LiveRegs);
int Reg = LiveRegs.find_first();
return Reg != -1 ? Reg : 0;
}

View File

@@ -158,6 +158,7 @@ public:
MCPhysReg getStackPointer() const override { return AArch64::SP; }
MCPhysReg getFramePointer() const override { return AArch64::FP; }
MCPhysReg getFlagsReg() const override { return AArch64::NZCV; }
bool isBreakpoint(const MCInst &Inst) const override {
return Inst.getOpcode() == AArch64::BRK;
@@ -1238,6 +1239,19 @@ public:
return true;
}
BitVector getRegsUsedAsParams() const override {
BitVector Regs = BitVector(RegInfo->getNumRegs(), false);
Regs |= getAliases(AArch64::X0);
Regs |= getAliases(AArch64::X1);
Regs |= getAliases(AArch64::X2);
Regs |= getAliases(AArch64::X3);
Regs |= getAliases(AArch64::X4);
Regs |= getAliases(AArch64::X5);
Regs |= getAliases(AArch64::X6);
Regs |= getAliases(AArch64::X7);
return Regs;
}
void getCalleeSavedRegs(BitVector &Regs) const override {
Regs |= getAliases(AArch64::X18);
Regs |= getAliases(AArch64::X19);
@@ -1254,6 +1268,88 @@ public:
Regs |= getAliases(AArch64::FP);
}
void getDefaultLiveOut(BitVector &Regs) const override {
// According to the AArch64 ABI the return registers are X0 to X7,
// which happen to be the same as the parameter registers.
Regs |= getRegsUsedAsParams();
}
void getGPRegs(BitVector &Regs, bool IncludeAlias = true) const override {
if (IncludeAlias) {
Regs |= getAliases(AArch64::X0);
Regs |= getAliases(AArch64::X1);
Regs |= getAliases(AArch64::X2);
Regs |= getAliases(AArch64::X3);
Regs |= getAliases(AArch64::X4);
Regs |= getAliases(AArch64::X5);
Regs |= getAliases(AArch64::X6);
Regs |= getAliases(AArch64::X7);
Regs |= getAliases(AArch64::X8);
Regs |= getAliases(AArch64::X9);
Regs |= getAliases(AArch64::X10);
Regs |= getAliases(AArch64::X11);
Regs |= getAliases(AArch64::X12);
Regs |= getAliases(AArch64::X13);
Regs |= getAliases(AArch64::X14);
Regs |= getAliases(AArch64::X15);
Regs |= getAliases(AArch64::X16);
Regs |= getAliases(AArch64::X17);
Regs |= getAliases(AArch64::X18);
Regs |= getAliases(AArch64::X19);
Regs |= getAliases(AArch64::X20);
Regs |= getAliases(AArch64::X21);
Regs |= getAliases(AArch64::X22);
Regs |= getAliases(AArch64::X23);
Regs |= getAliases(AArch64::X24);
Regs |= getAliases(AArch64::X25);
Regs |= getAliases(AArch64::X26);
Regs |= getAliases(AArch64::X27);
Regs |= getAliases(AArch64::X28);
Regs |= getAliases(AArch64::LR);
Regs |= getAliases(AArch64::FP);
return;
}
Regs.set(AArch64::X0);
Regs.set(AArch64::X1);
Regs.set(AArch64::X2);
Regs.set(AArch64::X3);
Regs.set(AArch64::X4);
Regs.set(AArch64::X5);
Regs.set(AArch64::X6);
Regs.set(AArch64::X7);
Regs.set(AArch64::X8);
Regs.set(AArch64::X9);
Regs.set(AArch64::X10);
Regs.set(AArch64::X11);
Regs.set(AArch64::X12);
Regs.set(AArch64::X13);
Regs.set(AArch64::X14);
Regs.set(AArch64::X15);
Regs.set(AArch64::X16);
Regs.set(AArch64::X17);
Regs.set(AArch64::X18);
Regs.set(AArch64::X19);
Regs.set(AArch64::X20);
Regs.set(AArch64::X21);
Regs.set(AArch64::X22);
Regs.set(AArch64::X23);
Regs.set(AArch64::X24);
Regs.set(AArch64::X25);
Regs.set(AArch64::X26);
Regs.set(AArch64::X27);
Regs.set(AArch64::X28);
Regs.set(AArch64::LR);
Regs.set(AArch64::FP);
}
void removeNonScavengeableRegs(BitVector &Regs) const override {
BitVector ExclusionMask = getAliases(AArch64::LR);
ExclusionMask |= getAliases(AArch64::FP);
ExclusionMask |= getAliases(AArch64::X18); // platform register
ExclusionMask.flip();
Regs &= ExclusionMask;
}
const MCExpr *getTargetExprFor(MCInst &Inst, const MCExpr *Expr,
MCContext &Ctx,
uint32_t RelType) const override {
@@ -2412,6 +2508,29 @@ public:
isAArch64ExclusiveStore(Inst);
}
bool isCleanRegXOR(const MCInst &Inst) const override {
switch (Inst.getOpcode()) {
case AArch64::EORXrs:
case AArch64::EORWrs:
return Inst.getOperand(1).getReg() == Inst.getOperand(2).getReg() &&
Inst.getOperand(3).getImm() == 0;
case AArch64::ORRXrs:
return Inst.getOperand(1).getReg() == AArch64::XZR &&
Inst.getOperand(2).getReg() == AArch64::XZR &&
Inst.getOperand(3).getImm() == 0;
case AArch64::ORRWrs:
return Inst.getOperand(1).getReg() == AArch64::WZR &&
Inst.getOperand(2).getReg() == AArch64::WZR &&
Inst.getOperand(3).getImm() == 0;
case AArch64::MOVZXi:
case AArch64::MOVZWi:
return Inst.getOperand(1).isImm() && Inst.getOperand(1).getImm() == 0 &&
Inst.getOperand(2).getImm() == 0;
default:
return false;
}
}
bool isStoreToStack(const MCInst &Inst) const {
if (!mayStore(Inst))
return false;

View File

@@ -827,6 +827,12 @@ public:
Regs.set(X86::R15);
}
void removeNonScavengeableRegs(BitVector &Regs) const override {
BitVector FP = getAliases(X86::RBP);
FP.flip();
Regs &= FP;
}
void getClassicGPRegs(BitVector &Regs) const override {
Regs |= getAliases(X86::RAX);
Regs |= getAliases(X86::RBX);

View File

@@ -8,6 +8,7 @@
#ifdef AARCH64_AVAILABLE
#include "AArch64Subtarget.h"
#include "MCTargetDesc/AArch64MCAsmInfo.h"
#include "MCTargetDesc/AArch64MCTargetDesc.h"
#endif // AARCH64_AVAILABLE
@@ -625,6 +626,65 @@ TEST_P(MCPlusBuilderTester, AArch64_Psign_Pauth_variants) {
ASSERT_TRUE(BC->MIB->isPAuthAndRet(Retab));
}
TEST_P(MCPlusBuilderTester, AArch64_isCleanRegXOR) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();
BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true);
BinaryBasicBlock *BB = BF->addBasicBlock();
// eor x0, x0, x0
MCInst EORXrs = MCInstBuilder(AArch64::EORXrs)
.addReg(AArch64::X0)
.addReg(AArch64::X0)
.addReg(AArch64::X0)
.addImm(0);
ASSERT_TRUE(BC->MIB->isCleanRegXOR(EORXrs));
// eor w0, w0, w0
MCInst EORWrs = MCInstBuilder(AArch64::EORWrs)
.addReg(AArch64::W0)
.addReg(AArch64::W0)
.addReg(AArch64::W0)
.addImm(0);
ASSERT_TRUE(BC->MIB->isCleanRegXOR(EORWrs));
// mov x0, xzr
MCInst ORRXrs = MCInstBuilder(AArch64::ORRXrs)
.addReg(AArch64::X0)
.addReg(AArch64::XZR)
.addReg(AArch64::XZR)
.addImm(0);
ASSERT_TRUE(BC->MIB->isCleanRegXOR(ORRXrs));
// mov w0, wzr
MCInst ORRWrs = MCInstBuilder(AArch64::ORRWrs)
.addReg(AArch64::W0)
.addReg(AArch64::WZR)
.addReg(AArch64::WZR)
.addImm(0);
ASSERT_TRUE(BC->MIB->isCleanRegXOR(ORRWrs));
// mov x0, #0
MCInst MOVZXi =
MCInstBuilder(AArch64::MOVZXi).addReg(AArch64::X0).addImm(0).addImm(0);
ASSERT_TRUE(BC->MIB->isCleanRegXOR(MOVZXi));
// mov w0, #0
MCInst MOVZWi =
MCInstBuilder(AArch64::MOVZWi).addReg(AArch64::W0).addImm(0).addImm(0);
ASSERT_TRUE(BC->MIB->isCleanRegXOR(MOVZWi));
// movz x0, #:abs_g3:symbol
MCInst MOVZXiWithExpr =
MCInstBuilder(AArch64::MOVZXi)
.addReg(AArch64::X0)
.addExpr(MCSpecifierExpr::create(BB->getLabel(), AArch64::S_ABS_G3,
*BC->Ctx.get()))
.addImm(48);
ASSERT_FALSE(BC->MIB->isCleanRegXOR(MOVZXiWithExpr));
}
#endif // AARCH64_AVAILABLE
#ifdef X86_AVAILABLE

View File

@@ -6,6 +6,7 @@ set(LLVM_LINK_COMPONENTS
)
add_bolt_unittest(PassTests
LivenessAnalysis.cpp
PointerAuthCFIFixup.cpp
DISABLE_LLVM_LINK_LLVM_DYLIB

View File

@@ -0,0 +1,188 @@
//===- bolt/unittest/Passes/LivenessAnalysis.cpp --------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifdef AARCH64_AVAILABLE
#include "AArch64Subtarget.h"
#include "MCTargetDesc/AArch64MCTargetDesc.h"
#endif // AARCH64_AVAILABLE
#include "bolt/Core/BinaryBasicBlock.h"
#include "bolt/Core/BinaryFunction.h"
#include "bolt/Core/BinaryFunctionCallGraph.h"
#include "bolt/Passes/DataflowInfoManager.h"
#include "bolt/Passes/RegAnalysis.h"
#include "bolt/Rewrite/RewriteInstance.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
#include "llvm/MC/MCInstBuilder.h"
#include "llvm/Support/TargetSelect.h"
#include "gtest/gtest.h"
using namespace llvm;
using namespace llvm::object;
using namespace llvm::ELF;
using namespace bolt;
namespace opts {
extern cl::opt<bool> AssumeABI;
} // namespace opts
namespace {
struct LivenessAnalysisTester
: public testing::TestWithParam<Triple::ArchType> {
void SetUp() override {
initalizeLLVM();
prepareElf();
initializeBolt();
}
protected:
void initalizeLLVM() {
#define BOLT_TARGET(target) \
LLVMInitialize##target##TargetInfo(); \
LLVMInitialize##target##TargetMC(); \
LLVMInitialize##target##AsmParser(); \
LLVMInitialize##target##Disassembler(); \
LLVMInitialize##target##Target(); \
LLVMInitialize##target##AsmPrinter();
#include "bolt/Core/TargetConfig.def"
}
void prepareElf() {
memcpy(ElfBuf, "\177ELF", 4);
ELF64LE::Ehdr *EHdr = reinterpret_cast<typename ELF64LE::Ehdr *>(ElfBuf);
EHdr->e_ident[llvm::ELF::EI_CLASS] = llvm::ELF::ELFCLASS64;
EHdr->e_ident[llvm::ELF::EI_DATA] = llvm::ELF::ELFDATA2LSB;
EHdr->e_machine = GetParam() == Triple::aarch64 ? EM_AARCH64 : EM_X86_64;
MemoryBufferRef Source(StringRef(ElfBuf, sizeof(ElfBuf)), "ELF");
ObjFile = cantFail(ObjectFile::createObjectFile(Source));
}
void initializeBolt() {
Relocation::Arch = ObjFile->makeTriple().getArch();
BC = cantFail(BinaryContext::createBinaryContext(
ObjFile->makeTriple(), std::make_shared<orc::SymbolStringPool>(),
ObjFile->getFileName(), nullptr, true, DWARFContext::create(*ObjFile),
{llvm::outs(), llvm::errs()}));
ASSERT_FALSE(!BC);
BC->initializeTarget(std::unique_ptr<MCPlusBuilder>(
createMCPlusBuilder(GetParam(), BC->MIA.get(), BC->MII.get(),
BC->MRI.get(), BC->STI.get())));
}
char ElfBuf[sizeof(typename ELF64LE::Ehdr)] = {};
std::unique_ptr<ObjectFile> ObjFile;
std::unique_ptr<BinaryContext> BC;
};
} // namespace
#ifdef AARCH64_AVAILABLE
INSTANTIATE_TEST_SUITE_P(AArch64, LivenessAnalysisTester,
::testing::Values(Triple::aarch64));
TEST_P(LivenessAnalysisTester, AArch64_scavengeRegFromState) {
if (GetParam() != Triple::aarch64)
GTEST_SKIP();
opts::AssumeABI = true;
BinaryFunction *BF = BC->createInjectedBinaryFunction("BF", true);
BinaryBasicBlock *EntryBB = BF->addBasicBlock();
BinaryBasicBlock *FallThroughBB = BF->addBasicBlock();
BinaryBasicBlock *TargetBB = BF->addBasicBlock();
BF->addEntryPoint(*EntryBB);
EntryBB->addSuccessor(FallThroughBB);
EntryBB->addSuccessor(TargetBB);
FallThroughBB->addSuccessor(TargetBB);
EntryBB->setCFIState(0);
FallThroughBB->setCFIState(0);
TargetBB->setCFIState(0);
// mov x8, #1
MCInst MOVZXi =
MCInstBuilder(AArch64::MOVZXi).addReg(AArch64::X8).addImm(1).addImm(0);
// cbgt x0, #0, target
MCInst CBGTXri = MCInstBuilder(AArch64::CBGTXri)
.addReg(AArch64::X0)
.addImm(0)
.addExpr(MCSymbolRefExpr::create(TargetBB->getLabel(),
*BC->Ctx.get()));
// add x0, x8, #1
MCInst ADDXri = MCInstBuilder(AArch64::ADDXri)
.addReg(AArch64::X0)
.addReg(AArch64::X8)
.addImm(1)
.addImm(0);
// ret
MCInst RET = MCInstBuilder(AArch64::RET).addReg(AArch64::LR);
EntryBB->addInstruction(MOVZXi);
EntryBB->addInstruction(CBGTXri);
FallThroughBB->addInstruction(ADDXri);
TargetBB->addInstruction(RET);
BinaryFunctionCallGraph CG(buildCallGraph(*BC));
RegAnalysis RA(*BC, &BC->getBinaryFunctions(), &CG);
DataflowInfoManager Info(*BF, &RA, nullptr);
auto II = EntryBB->begin();
// Test that parameter registers are LiveIn.
BitVector ParamRegs = BC->MIB->getRegsUsedAsParams();
ASSERT_TRUE(ParamRegs.subsetOf(Info.getLivenessAnalysis().getLiveIn(*II)));
BitVector LiveIn, LiveOut;
// mov x8, #1 -> LiveIn = {x0}, LiveOut = {x0, x8}
LiveIn = Info.getLivenessAnalysis().getLiveIn(*II);
LiveOut = Info.getLivenessAnalysis().getLiveOut(*II);
ASSERT_TRUE(LiveIn.test(AArch64::X0));
ASSERT_FALSE(LiveIn.test(AArch64::X8));
ASSERT_TRUE(LiveOut.test(AArch64::X0));
ASSERT_TRUE(LiveOut.test(AArch64::X8));
ASSERT_EQ(Info.getLivenessAnalysis().scavengeRegFromState(LiveOut),
AArch64::X9);
II++;
// cbgt x0, #0, target -> LiveIn = {x0, x8}, LiveOut = {x0, x8}
LiveIn = Info.getLivenessAnalysis().getLiveIn(*II);
LiveOut = Info.getLivenessAnalysis().getLiveOut(*II);
ASSERT_TRUE(LiveIn.test(AArch64::X0));
ASSERT_TRUE(LiveIn.test(AArch64::X8));
ASSERT_TRUE(LiveOut.test(AArch64::X0));
ASSERT_TRUE(LiveOut.test(AArch64::X8));
ASSERT_EQ(Info.getLivenessAnalysis().scavengeRegFromState(LiveOut),
AArch64::X9);
II = FallThroughBB->begin();
// add x0, x8, #1 -> LiveIn = {x8}, LiveOut = {x0}
LiveIn = Info.getLivenessAnalysis().getLiveIn(*II);
LiveOut = Info.getLivenessAnalysis().getLiveOut(*II);
ASSERT_FALSE(LiveIn.test(AArch64::X0));
ASSERT_TRUE(LiveIn.test(AArch64::X8));
ASSERT_TRUE(LiveOut.test(AArch64::X0));
ASSERT_FALSE(LiveOut.test(AArch64::X8));
ASSERT_EQ(Info.getLivenessAnalysis().scavengeRegFromState(LiveOut),
AArch64::X8);
II = TargetBB->begin();
// ret -> LiveIn = {x0}, LiveOut = {x0}
LiveIn = Info.getLivenessAnalysis().getLiveIn(*II);
LiveOut = Info.getLivenessAnalysis().getLiveOut(*II);
ASSERT_TRUE(LiveIn.test(AArch64::X0));
ASSERT_FALSE(LiveIn.test(AArch64::X8));
ASSERT_TRUE(LiveOut.test(AArch64::X0));
ASSERT_FALSE(LiveOut.test(AArch64::X8));
ASSERT_EQ(Info.getLivenessAnalysis().scavengeRegFromState(LiveOut),
AArch64::X8);
// Test that return registers are LiveOut.
BitVector DefaultLiveOutRegs;
BC->MIB->getDefaultLiveOut(DefaultLiveOutRegs);
ASSERT_TRUE(
DefaultLiveOutRegs.subsetOf(Info.getLivenessAnalysis().getLiveOut(*II)));
}
#endif // AARCH64_AVAILABLE