[llvm] [BOLT][Hexagon] Add Hexagon MCPlusBuilder, relocations (PR #192189)
Brian Cain via llvm-commits
llvm-commits at lists.llvm.org
Fri Apr 17 10:53:05 PDT 2026
https://github.com/androm3da updated https://github.com/llvm/llvm-project/pull/192189
>From a1f550ffe5a96257d1d05af28288356d91d34ce2 Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Tue, 14 Apr 2026 22:24:51 -0700
Subject: [PATCH 1/5] [BOLT][Hexagon] Add Hexagon relocation support
Add Hexagon ELF relocation handling to BOLT's Relocation class.
This includes support for branch relocations (B7/B9/B13/B15/B22),
extended relocations (*_X), GOT/GOTREL, TPREL (TLS), PC-relative,
and absolute relocations.
Functions added: isSupportedHexagon, getSizeForTypeHexagon,
skipRelocationTypeHexagon, extractValueHexagon, isGOTHexagon,
isTLSHexagon, isPCRelativeHexagon, plus all dispatcher cases.
This is a self-contained change with no dependencies on other
Hexagon-specific BOLT commits.
---
bolt/lib/Core/Relocation.cpp | 219 +++++++++++++++++++++++++++++++++++
1 file changed, 219 insertions(+)
diff --git a/bolt/lib/Core/Relocation.cpp b/bolt/lib/Core/Relocation.cpp
index f872db2cae0ce..5376b203efe4b 100644
--- a/bolt/lib/Core/Relocation.cpp
+++ b/bolt/lib/Core/Relocation.cpp
@@ -133,6 +133,51 @@ static bool isSupportedRISCV(uint32_t Type) {
}
}
+static bool isSupportedHexagon(uint32_t Type) {
+ switch (Type) {
+ default:
+ return false;
+ case ELF::R_HEX_B22_PCREL:
+ case ELF::R_HEX_B15_PCREL:
+ case ELF::R_HEX_B7_PCREL:
+ case ELF::R_HEX_B13_PCREL:
+ case ELF::R_HEX_B9_PCREL:
+ case ELF::R_HEX_B32_PCREL_X:
+ case ELF::R_HEX_B22_PCREL_X:
+ case ELF::R_HEX_B15_PCREL_X:
+ case ELF::R_HEX_B13_PCREL_X:
+ case ELF::R_HEX_B9_PCREL_X:
+ case ELF::R_HEX_B7_PCREL_X:
+ case ELF::R_HEX_32_6_X:
+ case ELF::R_HEX_16_X:
+ case ELF::R_HEX_12_X:
+ case ELF::R_HEX_11_X:
+ case ELF::R_HEX_10_X:
+ case ELF::R_HEX_9_X:
+ case ELF::R_HEX_8_X:
+ case ELF::R_HEX_7_X:
+ case ELF::R_HEX_6_X:
+ case ELF::R_HEX_32:
+ case ELF::R_HEX_16:
+ case ELF::R_HEX_8:
+ case ELF::R_HEX_LO16:
+ case ELF::R_HEX_HI16:
+ case ELF::R_HEX_32_PCREL:
+ case ELF::R_HEX_PLT_B22_PCREL:
+ case ELF::R_HEX_GOT_32_6_X:
+ case ELF::R_HEX_GOT_16_X:
+ case ELF::R_HEX_GOT_11_X:
+ case ELF::R_HEX_GOTREL_32_6_X:
+ case ELF::R_HEX_GOTREL_16_X:
+ case ELF::R_HEX_GOTREL_11_X:
+ case ELF::R_HEX_6_PCREL_X:
+ case ELF::R_HEX_TPREL_32_6_X:
+ case ELF::R_HEX_TPREL_16_X:
+ case ELF::R_HEX_TPREL_11_X:
+ return true;
+ }
+}
+
static size_t getSizeForTypeX86(uint32_t Type) {
switch (Type) {
default:
@@ -241,6 +286,54 @@ static size_t getSizeForTypeRISCV(uint32_t Type) {
}
}
+static size_t getSizeForTypeHexagon(uint32_t Type) {
+ switch (Type) {
+ default:
+ errs() << object::getELFRelocationTypeName(ELF::EM_HEXAGON, Type) << '\n';
+ llvm_unreachable("unsupported relocation type");
+ case ELF::R_HEX_8:
+ return 1;
+ case ELF::R_HEX_16:
+ return 2;
+ case ELF::R_HEX_B22_PCREL:
+ case ELF::R_HEX_B15_PCREL:
+ case ELF::R_HEX_B7_PCREL:
+ case ELF::R_HEX_B13_PCREL:
+ case ELF::R_HEX_B9_PCREL:
+ case ELF::R_HEX_B32_PCREL_X:
+ case ELF::R_HEX_B22_PCREL_X:
+ case ELF::R_HEX_B15_PCREL_X:
+ case ELF::R_HEX_B13_PCREL_X:
+ case ELF::R_HEX_B9_PCREL_X:
+ case ELF::R_HEX_B7_PCREL_X:
+ case ELF::R_HEX_32_6_X:
+ case ELF::R_HEX_16_X:
+ case ELF::R_HEX_12_X:
+ case ELF::R_HEX_11_X:
+ case ELF::R_HEX_10_X:
+ case ELF::R_HEX_9_X:
+ case ELF::R_HEX_8_X:
+ case ELF::R_HEX_7_X:
+ case ELF::R_HEX_6_X:
+ case ELF::R_HEX_32:
+ case ELF::R_HEX_32_PCREL:
+ case ELF::R_HEX_LO16:
+ case ELF::R_HEX_HI16:
+ case ELF::R_HEX_PLT_B22_PCREL:
+ case ELF::R_HEX_GOT_32_6_X:
+ case ELF::R_HEX_GOT_16_X:
+ case ELF::R_HEX_GOT_11_X:
+ case ELF::R_HEX_GOTREL_32_6_X:
+ case ELF::R_HEX_GOTREL_16_X:
+ case ELF::R_HEX_GOTREL_11_X:
+ case ELF::R_HEX_6_PCREL_X:
+ case ELF::R_HEX_TPREL_32_6_X:
+ case ELF::R_HEX_TPREL_16_X:
+ case ELF::R_HEX_TPREL_11_X:
+ return 4;
+ }
+}
+
static bool skipRelocationTypeX86(uint32_t Type) {
return Type == ELF::R_X86_64_NONE;
}
@@ -266,6 +359,19 @@ static bool skipRelocationTypeRISCV(uint32_t Type) {
}
}
+static bool skipRelocationTypeHexagon(uint32_t Type) {
+ switch (Type) {
+ default:
+ return false;
+ case ELF::R_HEX_NONE:
+ case ELF::R_HEX_GPREL16_0:
+ case ELF::R_HEX_GPREL16_1:
+ case ELF::R_HEX_GPREL16_2:
+ case ELF::R_HEX_GPREL16_3:
+ return true;
+ }
+}
+
static uint64_t encodeValueX86(uint32_t Type, uint64_t Value, uint64_t PC) {
switch (Type) {
default:
@@ -524,6 +630,13 @@ static uint64_t extractValueRISCV(uint32_t Type, uint64_t Contents,
}
}
+static uint64_t extractValueHexagon(uint32_t Type, uint64_t Contents,
+ uint64_t PC) {
+ // For binary analysis (read-only), return the raw contents. Full extraction
+ // of Hexagon instruction-encoded immediates is only needed for rewriting.
+ return Contents;
+}
+
static bool isGOTX86(uint32_t Type) {
switch (Type) {
default:
@@ -570,6 +683,20 @@ static bool isGOTRISCV(uint32_t Type) {
}
}
+static bool isGOTHexagon(uint32_t Type) {
+ switch (Type) {
+ default:
+ return false;
+ case ELF::R_HEX_GOT_32_6_X:
+ case ELF::R_HEX_GOT_16_X:
+ case ELF::R_HEX_GOT_11_X:
+ case ELF::R_HEX_GOTREL_32_6_X:
+ case ELF::R_HEX_GOTREL_16_X:
+ case ELF::R_HEX_GOTREL_11_X:
+ return true;
+ }
+}
+
static bool isTLSX86(uint32_t Type) {
switch (Type) {
default:
@@ -614,6 +741,17 @@ static bool isTLSRISCV(uint32_t Type) {
}
}
+static bool isTLSHexagon(uint32_t Type) {
+ switch (Type) {
+ case ELF::R_HEX_TPREL_32_6_X:
+ case ELF::R_HEX_TPREL_16_X:
+ case ELF::R_HEX_TPREL_11_X:
+ return true;
+ default:
+ return false;
+ }
+}
+
static bool isPCRelativeX86(uint32_t Type) {
switch (Type) {
default:
@@ -716,12 +854,60 @@ static bool isPCRelativeRISCV(uint32_t Type) {
}
}
+static bool isPCRelativeHexagon(uint32_t Type) {
+ switch (Type) {
+ default:
+ llvm_unreachable("Unknown relocation type");
+ case ELF::R_HEX_32:
+ case ELF::R_HEX_16:
+ case ELF::R_HEX_8:
+ case ELF::R_HEX_LO16:
+ case ELF::R_HEX_HI16:
+ case ELF::R_HEX_32_6_X:
+ case ELF::R_HEX_16_X:
+ case ELF::R_HEX_12_X:
+ case ELF::R_HEX_11_X:
+ case ELF::R_HEX_10_X:
+ case ELF::R_HEX_9_X:
+ case ELF::R_HEX_8_X:
+ case ELF::R_HEX_7_X:
+ case ELF::R_HEX_6_X:
+ case ELF::R_HEX_GOT_32_6_X:
+ case ELF::R_HEX_GOT_16_X:
+ case ELF::R_HEX_GOT_11_X:
+ case ELF::R_HEX_GOTREL_32_6_X:
+ case ELF::R_HEX_GOTREL_16_X:
+ case ELF::R_HEX_GOTREL_11_X:
+ case ELF::R_HEX_TPREL_32_6_X:
+ case ELF::R_HEX_TPREL_16_X:
+ case ELF::R_HEX_TPREL_11_X:
+ return false;
+ case ELF::R_HEX_6_PCREL_X:
+ case ELF::R_HEX_B22_PCREL:
+ case ELF::R_HEX_B15_PCREL:
+ case ELF::R_HEX_B7_PCREL:
+ case ELF::R_HEX_B13_PCREL:
+ case ELF::R_HEX_B9_PCREL:
+ case ELF::R_HEX_B32_PCREL_X:
+ case ELF::R_HEX_B22_PCREL_X:
+ case ELF::R_HEX_B15_PCREL_X:
+ case ELF::R_HEX_B13_PCREL_X:
+ case ELF::R_HEX_B9_PCREL_X:
+ case ELF::R_HEX_B7_PCREL_X:
+ case ELF::R_HEX_32_PCREL:
+ case ELF::R_HEX_PLT_B22_PCREL:
+ return true;
+ }
+}
+
bool Relocation::isSupported(uint32_t Type) {
switch (Arch) {
default:
return false;
case Triple::aarch64:
return isSupportedAArch64(Type);
+ case Triple::hexagon:
+ return isSupportedHexagon(Type);
case Triple::riscv64:
return isSupportedRISCV(Type);
case Triple::x86_64:
@@ -735,6 +921,8 @@ size_t Relocation::getSizeForType(uint32_t Type) {
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return getSizeForTypeAArch64(Type);
+ case Triple::hexagon:
+ return getSizeForTypeHexagon(Type);
case Triple::riscv64:
return getSizeForTypeRISCV(Type);
case Triple::x86_64:
@@ -748,6 +936,8 @@ bool Relocation::skipRelocationType(uint32_t Type) {
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return skipRelocationTypeAArch64(Type);
+ case Triple::hexagon:
+ return skipRelocationTypeHexagon(Type);
case Triple::riscv64:
return skipRelocationTypeRISCV(Type);
case Triple::x86_64:
@@ -761,6 +951,8 @@ uint64_t Relocation::encodeValue(uint32_t Type, uint64_t Value, uint64_t PC) {
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return encodeValueAArch64(Type, Value, PC);
+ case Triple::hexagon:
+ llvm_unreachable("not used in Hexagon analysis mode");
case Triple::riscv64:
return encodeValueRISCV(Type, Value, PC);
case Triple::x86_64:
@@ -774,6 +966,8 @@ bool Relocation::canEncodeValue(uint32_t Type, uint64_t Value, uint64_t PC) {
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return canEncodeValueAArch64(Type, Value, PC);
+ case Triple::hexagon:
+ return true;
case Triple::riscv64:
return canEncodeValueRISCV(Type, Value, PC);
case Triple::x86_64:
@@ -788,6 +982,8 @@ uint64_t Relocation::extractValue(uint32_t Type, uint64_t Contents,
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return extractValueAArch64(Type, Contents, PC);
+ case Triple::hexagon:
+ return extractValueHexagon(Type, Contents, PC);
case Triple::riscv64:
return extractValueRISCV(Type, Contents, PC);
case Triple::x86_64:
@@ -801,6 +997,8 @@ bool Relocation::isGOT(uint32_t Type) {
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return isGOTAArch64(Type);
+ case Triple::hexagon:
+ return isGOTHexagon(Type);
case Triple::riscv64:
return isGOTRISCV(Type);
case Triple::x86_64:
@@ -828,6 +1026,8 @@ bool Relocation::isRelative(uint32_t Type) {
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return Type == ELF::R_AARCH64_RELATIVE;
+ case Triple::hexagon:
+ return Type == ELF::R_HEX_RELATIVE;
case Triple::riscv64:
return Type == ELF::R_RISCV_RELATIVE;
case Triple::x86_64:
@@ -841,6 +1041,8 @@ bool Relocation::isIRelative(uint32_t Type) {
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return Type == ELF::R_AARCH64_IRELATIVE;
+ case Triple::hexagon:
+ return false;
case Triple::riscv64:
llvm_unreachable("not implemented");
case Triple::x86_64:
@@ -854,6 +1056,8 @@ bool Relocation::isTLS(uint32_t Type) {
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return isTLSAArch64(Type);
+ case Triple::hexagon:
+ return isTLSHexagon(Type);
case Triple::riscv64:
return isTLSRISCV(Type);
case Triple::x86_64:
@@ -880,6 +1084,8 @@ uint32_t Relocation::getNone() {
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return ELF::R_AARCH64_NONE;
+ case Triple::hexagon:
+ return ELF::R_HEX_NONE;
case Triple::riscv64:
return ELF::R_RISCV_NONE;
case Triple::x86_64:
@@ -893,6 +1099,8 @@ uint32_t Relocation::getPC32() {
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return ELF::R_AARCH64_PREL32;
+ case Triple::hexagon:
+ return ELF::R_HEX_32_PCREL;
case Triple::riscv64:
return ELF::R_RISCV_32_PCREL;
case Triple::x86_64:
@@ -906,6 +1114,8 @@ uint32_t Relocation::getPC64() {
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return ELF::R_AARCH64_PREL64;
+ case Triple::hexagon:
+ llvm_unreachable("not implemented for Hexagon");
case Triple::riscv64:
llvm_unreachable("not implemented");
case Triple::x86_64:
@@ -925,6 +1135,8 @@ bool Relocation::isPCRelative(uint32_t Type) {
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return isPCRelativeAArch64(Type);
+ case Triple::hexagon:
+ return isPCRelativeHexagon(Type);
case Triple::riscv64:
return isPCRelativeRISCV(Type);
case Triple::x86_64:
@@ -938,6 +1150,8 @@ uint32_t Relocation::getAbs64() {
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return ELF::R_AARCH64_ABS64;
+ case Triple::hexagon:
+ llvm_unreachable("Hexagon is 32-bit, no 64-bit absolute relocation");
case Triple::riscv64:
return ELF::R_RISCV_64;
case Triple::x86_64:
@@ -951,6 +1165,8 @@ uint32_t Relocation::getRelative() {
llvm_unreachable("Unsupported architecture");
case Triple::aarch64:
return ELF::R_AARCH64_RELATIVE;
+ case Triple::hexagon:
+ return ELF::R_HEX_RELATIVE;
case Triple::riscv64:
llvm_unreachable("not implemented");
case Triple::x86_64:
@@ -1021,6 +1237,9 @@ void Relocation::print(raw_ostream &OS) const {
case Triple::aarch64:
OS << object::getELFRelocationTypeName(ELF::EM_AARCH64, Type);
break;
+ case Triple::hexagon:
+ OS << object::getELFRelocationTypeName(ELF::EM_HEXAGON, Type);
+ break;
case Triple::riscv64:
OS << object::getELFRelocationTypeName(ELF::EM_RISCV, Type);
break;
>From a91421565bddff069e426d390158c2db3b55d5d8 Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Tue, 14 Apr 2026 22:25:29 -0700
Subject: [PATCH 2/5] [BOLT][Hexagon] Add target MCPlusBuilder and build
infrastructure
Add the Hexagon target backend for BOLT, enabling binary analysis and
optimization of Hexagon ELF executables.
Key components:
- HexagonMCPlusBuilder: target-specific MCPlusBuilder with instruction
analysis (branches, calls, returns, moves), annotation support,
and VLIW packet/bundle creation
- BinaryContext: add isHexagon() helper and Hexagon case in
createBinaryContext()
- MCPlusBuilder: add virtual methods for bundle emission
(requiresBundleEmission, isNewValueConsumer, createBundle) and
createHexagonMCPlusBuilder() factory declaration
- RewriteInstance: add Hexagon case in createMCPlusBuilder() and skip
instruction encoding verification for Hexagon
- CMake: add Hexagon to BOLT_TARGETS_TO_BUILD_all and add
bolt/lib/Target/Hexagon/ build directory
---
bolt/CMakeLists.txt | 2 +-
bolt/include/bolt/Core/BinaryContext.h | 4 +
bolt/include/bolt/Core/MCPlusBuilder.h | 22 +
bolt/lib/Core/BinaryContext.cpp | 9 +
bolt/lib/Rewrite/RewriteInstance.cpp | 12 +-
bolt/lib/Target/Hexagon/CMakeLists.txt | 34 +
.../Target/Hexagon/HexagonMCPlusBuilder.cpp | 643 ++++++++++++++++++
7 files changed, 722 insertions(+), 4 deletions(-)
create mode 100644 bolt/lib/Target/Hexagon/CMakeLists.txt
create mode 100644 bolt/lib/Target/Hexagon/HexagonMCPlusBuilder.cpp
diff --git a/bolt/CMakeLists.txt b/bolt/CMakeLists.txt
index 5c7d51e1e398c..5dbe9a2bf5957 100644
--- a/bolt/CMakeLists.txt
+++ b/bolt/CMakeLists.txt
@@ -62,7 +62,7 @@ endif() # standalone
# Determine default set of targets to build -- the intersection of
# those BOLT supports and those LLVM is targeting.
-set(BOLT_TARGETS_TO_BUILD_all "AArch64;X86;RISCV")
+set(BOLT_TARGETS_TO_BUILD_all "AArch64;X86;RISCV;Hexagon")
set(BOLT_TARGETS_TO_BUILD_default)
foreach (tgt ${BOLT_TARGETS_TO_BUILD_all})
if (tgt IN_LIST LLVM_TARGETS_TO_BUILD)
diff --git a/bolt/include/bolt/Core/BinaryContext.h b/bolt/include/bolt/Core/BinaryContext.h
index 30fcf072a6875..1fb210f185fd7 100644
--- a/bolt/include/bolt/Core/BinaryContext.h
+++ b/bolt/include/bolt/Core/BinaryContext.h
@@ -896,6 +896,10 @@ class BinaryContext {
bool isRISCV() const { return TheTriple->getArch() == llvm::Triple::riscv64; }
+ bool isHexagon() const {
+ return TheTriple->getArch() == llvm::Triple::hexagon;
+ }
+
// AArch64/RISC-V functions to check if symbol is used to delimit
// code/data in .text. Code is marked by $x, data by $d.
MarkerSymType getMarkerType(const SymbolRef &Symbol) const;
diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h
index f9acaa4ee55a3..7e21e0dfd5233 100644
--- a/bolt/include/bolt/Core/MCPlusBuilder.h
+++ b/bolt/include/bolt/Core/MCPlusBuilder.h
@@ -2521,6 +2521,23 @@ class MCPlusBuilder {
return 2;
}
+ /// Return true if this target requires instructions to be wrapped in
+ /// BUNDLE MCInsts before emission (e.g. Hexagon VLIW packets).
+ virtual bool requiresBundleEmission() const { return false; }
+
+ /// Return true if the instruction is a .new value consumer that requires
+ /// its producer to be in the same VLIW packet (e.g. Hexagon .new value
+ /// stores and .new compare-and-jump instructions).
+ virtual bool isNewValueConsumer(const MCInst &Inst) const { return false; }
+
+ /// Create a BUNDLE MCInst from a sequence of individual instructions.
+ /// Used by targets that require bundle emission (e.g. Hexagon).
+ virtual MCInst createBundle(MCContext &Ctx, ArrayRef<MCInst> Instrs,
+ bool InnerLoop = false,
+ bool OuterLoop = false) const {
+ llvm_unreachable("not implemented");
+ }
+
// AliasMap caches a mapping of registers to the set of registers that
// alias (are sub or superregs of itself, including itself).
std::vector<BitVector> AliasMap;
@@ -2544,6 +2561,11 @@ MCPlusBuilder *createRISCVMCPlusBuilder(const MCInstrAnalysis *,
const MCRegisterInfo *,
const MCSubtargetInfo *);
+MCPlusBuilder *createHexagonMCPlusBuilder(const MCInstrAnalysis *,
+ const MCInstrInfo *,
+ const MCRegisterInfo *,
+ const MCSubtargetInfo *);
+
} // namespace bolt
} // namespace llvm
diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
index 51fe292acae69..8830c59721dd5 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -208,6 +208,15 @@ Expected<std::unique_ptr<BinaryContext>> BinaryContext::createBinaryContext(
FeaturesStr = Features->getString();
break;
}
+ case llvm::Triple::hexagon:
+ ArchName = "hexagon";
+ if (!Features)
+ return createFatalBOLTError(
+ "Hexagon target needs SubtargetFeatures to determine the "
+ "target CPU; the input ELF may be missing a "
+ ".hexagon.attributes section");
+ FeaturesStr = Features->getString();
+ break;
default:
return createStringError(std::errc::not_supported,
"BOLT-ERROR: Unrecognized machine in ELF file");
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index 698281bc98f86..82b2dd8da1408 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -369,6 +369,11 @@ MCPlusBuilder *createMCPlusBuilder(const Triple::ArchType Arch,
return createRISCVMCPlusBuilder(Analysis, Info, RegInfo, STI);
#endif
+#ifdef HEXAGON_AVAILABLE
+ if (Arch == Triple::hexagon)
+ return createHexagonMCPlusBuilder(Analysis, Info, RegInfo, STI);
+#endif
+
llvm_unreachable("architecture unsupported by MCPlusBuilder");
}
@@ -424,10 +429,11 @@ RewriteInstance::RewriteInstance(ELFObjectFileBase *File, const int Argc,
Stderr.SetUnbuffered();
LLVM_DEBUG(dbgs().SetUnbuffered());
- // Read RISCV subtarget features from input file
+ // Read subtarget features from input ELF attributes
std::unique_ptr<SubtargetFeatures> Features;
Triple TheTriple = File->makeTriple();
- if (TheTriple.getArch() == llvm::Triple::riscv64) {
+ if (TheTriple.getArch() == llvm::Triple::riscv64 ||
+ TheTriple.getArch() == llvm::Triple::hexagon) {
Expected<SubtargetFeatures> FeaturesOrErr = File->getFeatures();
if (auto E = FeaturesOrErr.takeError()) {
Err = std::move(E);
@@ -2621,7 +2627,7 @@ bool RewriteInstance::analyzeRelocation(
if (SkipVerification)
return true;
- if (IsAArch64 || BC->isRISCV())
+ if (IsAArch64 || BC->isRISCV() || BC->isHexagon())
return true;
if (SymbolName == "__hot_start" || SymbolName == "__hot_end")
diff --git a/bolt/lib/Target/Hexagon/CMakeLists.txt b/bolt/lib/Target/Hexagon/CMakeLists.txt
new file mode 100644
index 0000000000000..67113556d60a2
--- /dev/null
+++ b/bolt/lib/Target/Hexagon/CMakeLists.txt
@@ -0,0 +1,34 @@
+set(LLVM_LINK_COMPONENTS
+ MC
+ Support
+ HexagonDesc
+ )
+
+if(BOLT_BUILT_STANDALONE)
+ # tablegen, copied from llvm/lib/Target/Hexagon/CMakeLists.txt
+ set(LLVM_TARGET_DEFINITIONS ${LLVM_MAIN_SRC_DIR}/lib/Target/Hexagon/Hexagon.td)
+ list(APPEND LLVM_TABLEGEN_FLAGS -I ${LLVM_MAIN_SRC_DIR}/lib/Target/Hexagon)
+ tablegen(LLVM HexagonGenInstrInfo.inc -gen-instr-info)
+ tablegen(LLVM HexagonGenRegisterInfo.inc -gen-register-info)
+ tablegen(LLVM HexagonGenSubtargetInfo.inc -gen-subtarget)
+
+ add_public_tablegen_target(HexagonCommonTableGen)
+ include_directories(${CMAKE_CURRENT_BINARY_DIR})
+endif()
+
+add_llvm_library(LLVMBOLTTargetHexagon
+ HexagonMCPlusBuilder.cpp
+
+ NO_EXPORT
+ DISABLE_LLVM_LINK_LLVM_DYLIB
+
+ DEPENDS
+ HexagonCommonTableGen
+ )
+
+target_link_libraries(LLVMBOLTTargetHexagon PRIVATE LLVMBOLTCore)
+
+include_directories(
+ ${LLVM_MAIN_SRC_DIR}/lib/Target/Hexagon
+ ${LLVM_BINARY_DIR}/lib/Target/Hexagon
+ )
diff --git a/bolt/lib/Target/Hexagon/HexagonMCPlusBuilder.cpp b/bolt/lib/Target/Hexagon/HexagonMCPlusBuilder.cpp
new file mode 100644
index 0000000000000..f3b23833f36ca
--- /dev/null
+++ b/bolt/lib/Target/Hexagon/HexagonMCPlusBuilder.cpp
@@ -0,0 +1,643 @@
+//===- bolt/Target/Hexagon/HexagonMCPlusBuilder.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file provides Hexagon-specific MCPlus builder.
+//
+//===----------------------------------------------------------------------===//
+
+#include "MCTargetDesc/HexagonMCExpr.h"
+#include "MCTargetDesc/HexagonMCInstrInfo.h"
+#include "MCTargetDesc/HexagonMCTargetDesc.h"
+#include "bolt/Core/BinaryBasicBlock.h"
+#include "bolt/Core/MCPlusBuilder.h"
+#include "llvm/BinaryFormat/ELF.h"
+#include "llvm/MC/MCContext.h"
+#include "llvm/MC/MCInst.h"
+#include "llvm/MC/MCInstBuilder.h"
+#include "llvm/MC/MCSubtargetInfo.h"
+#include "llvm/Support/ErrorHandling.h"
+
+#define DEBUG_TYPE "mcplus"
+
+using namespace llvm;
+using namespace bolt;
+
+namespace {
+
+class HexagonMCPlusBuilder : public MCPlusBuilder {
+ /// Mark an instruction as the end of a Hexagon packet. This helper
+ /// casts away const because addAnnotation is non-const (it lazily
+ /// registers annotation indices), but these create* methods are const
+ /// in the base class.
+ void setHexPacketEnd(MCInst &Inst) const {
+ const_cast<HexagonMCPlusBuilder *>(this)->addAnnotation(
+ Inst, "HexPacketEnd", true);
+ }
+
+ /// Create a symbol reference expression wrapped in HexagonMCExpr.
+ /// The Hexagon MC layer requires all expressions be wrapped this way.
+ const MCExpr *createHexExpr(const MCSymbol *Sym, MCContext &Ctx) const {
+ return HexagonMCExpr::create(MCSymbolRefExpr::create(Sym, Ctx), Ctx);
+ }
+
+public:
+ using MCPlusBuilder::MCPlusBuilder;
+
+ bool equals(const MCSpecifierExpr &A, const MCSpecifierExpr &B,
+ CompFuncTy Comp) const override {
+ // Hexagon uses MCTargetExpr (HexagonMCExpr) rather than MCSpecifierExpr
+ // for most expressions. Delegate to base class for any MCSpecifierExpr
+ // that may appear.
+ return MCPlusBuilder::equals(A, B, Comp);
+ }
+
+ void getCalleeSavedRegs(BitVector &Regs) const override {
+ // Hexagon ABI: R16-R27 are callee-saved.
+ // R29 (SP), R30 (FP), R31 (LR) are also callee-saved
+ // (saved/restored by allocframe/deallocframe).
+ Regs |= getAliases(Hexagon::R16);
+ Regs |= getAliases(Hexagon::R17);
+ Regs |= getAliases(Hexagon::R18);
+ Regs |= getAliases(Hexagon::R19);
+ Regs |= getAliases(Hexagon::R20);
+ Regs |= getAliases(Hexagon::R21);
+ Regs |= getAliases(Hexagon::R22);
+ Regs |= getAliases(Hexagon::R23);
+ Regs |= getAliases(Hexagon::R24);
+ Regs |= getAliases(Hexagon::R25);
+ Regs |= getAliases(Hexagon::R26);
+ Regs |= getAliases(Hexagon::R27);
+ Regs |= getAliases(Hexagon::R29);
+ Regs |= getAliases(Hexagon::R30);
+ Regs |= getAliases(Hexagon::R31);
+ }
+
+ bool shouldRecordCodeRelocation(uint32_t RelType) const override {
+ switch (RelType) {
+ case ELF::R_HEX_B22_PCREL:
+ case ELF::R_HEX_B15_PCREL:
+ case ELF::R_HEX_B7_PCREL:
+ case ELF::R_HEX_B13_PCREL:
+ case ELF::R_HEX_B9_PCREL:
+ case ELF::R_HEX_B32_PCREL_X:
+ case ELF::R_HEX_B22_PCREL_X:
+ case ELF::R_HEX_B15_PCREL_X:
+ case ELF::R_HEX_B13_PCREL_X:
+ case ELF::R_HEX_B9_PCREL_X:
+ case ELF::R_HEX_B7_PCREL_X:
+ case ELF::R_HEX_32_PCREL:
+ case ELF::R_HEX_6_PCREL_X:
+ case ELF::R_HEX_32_6_X:
+ case ELF::R_HEX_PLT_B22_PCREL:
+ case ELF::R_HEX_GOT_32_6_X:
+ case ELF::R_HEX_GOT_16_X:
+ case ELF::R_HEX_GOT_11_X:
+ case ELF::R_HEX_GOTREL_32_6_X:
+ case ELF::R_HEX_GOTREL_16_X:
+ case ELF::R_HEX_GOTREL_11_X:
+ case ELF::R_HEX_TPREL_32_6_X:
+ case ELF::R_HEX_TPREL_16_X:
+ case ELF::R_HEX_TPREL_11_X:
+ case ELF::R_HEX_LO16:
+ case ELF::R_HEX_HI16:
+ case ELF::R_HEX_16_X:
+ case ELF::R_HEX_12_X:
+ case ELF::R_HEX_11_X:
+ case ELF::R_HEX_10_X:
+ case ELF::R_HEX_9_X:
+ case ELF::R_HEX_8_X:
+ case ELF::R_HEX_7_X:
+ case ELF::R_HEX_6_X:
+ case ELF::R_HEX_32:
+ return true;
+ default:
+ llvm_unreachable("Unexpected Hexagon relocation type in code");
+ }
+ }
+
+ bool isNoop(const MCInst &Inst) const override {
+ return Inst.getOpcode() == Hexagon::A2_nop;
+ }
+
+ bool isPseudo(const MCInst &Inst) const override {
+ // V65 map-to-raw pseudos: the disassembler produces these for
+ // allocframe, deallocframe, and dealloc_return. They are marked
+ // Pseudo in tablegen but represent real instructions. Treat them
+ // as non-pseudo so BOLT tracks them; they get lowered back to raw
+ // forms during encoding (in lowerToRaw).
+ switch (Inst.getOpcode()) {
+ case Hexagon::S6_allocframe_to_raw:
+ case Hexagon::L6_deallocframe_map_to_raw:
+ case Hexagon::L6_return_map_to_raw:
+ case Hexagon::L4_return_map_to_raw_t:
+ case Hexagon::L4_return_map_to_raw_f:
+ case Hexagon::L4_return_map_to_raw_tnew_pt:
+ case Hexagon::L4_return_map_to_raw_fnew_pt:
+ case Hexagon::L4_return_map_to_raw_tnew_pnt:
+ case Hexagon::L4_return_map_to_raw_fnew_pnt:
+ return false;
+ default:
+ return MCPlusBuilder::isPseudo(Inst);
+ }
+ }
+
+ bool isIndirectCall(const MCInst &Inst) const override {
+ if (!isCall(Inst))
+ return false;
+
+ switch (Inst.getOpcode()) {
+ default:
+ return false;
+ case Hexagon::J2_callr:
+ case Hexagon::J2_callrt:
+ case Hexagon::J2_callrf:
+ return true;
+ }
+ }
+
+ bool hasPCRelOperand(const MCInst &Inst) const override {
+ // Any direct branch or call has a PC-relative target operand.
+ if ((isBranch(Inst) || isCall(Inst)) && !isIndirectBranch(Inst) &&
+ !isIndirectCall(Inst))
+ return HexagonMCInstrInfo::isExtendable(*Info, Inst);
+ return false;
+ }
+
+ unsigned getInvertedBranchOpcode(unsigned Opcode) const {
+ switch (Opcode) {
+ default:
+ llvm_unreachable("Failed to invert branch opcode");
+ return Opcode;
+ case Hexagon::J2_jumpt:
+ return Hexagon::J2_jumpf;
+ case Hexagon::J2_jumpf:
+ return Hexagon::J2_jumpt;
+ case Hexagon::J2_jumptnew:
+ return Hexagon::J2_jumpfnew;
+ case Hexagon::J2_jumpfnew:
+ return Hexagon::J2_jumptnew;
+ case Hexagon::J2_jumptnewpt:
+ return Hexagon::J2_jumpfnewpt;
+ case Hexagon::J2_jumpfnewpt:
+ return Hexagon::J2_jumptnewpt;
+ }
+ }
+
+ bool isReversibleBranch(const MCInst &Inst) const override {
+ // Only simple J2 predicate-based conditional branches can be inverted.
+ // J4 compound compare-and-jump instructions cannot be trivially inverted
+ // because the comparison is fused into the opcode.
+ switch (Inst.getOpcode()) {
+ case Hexagon::J2_jumpt:
+ case Hexagon::J2_jumpf:
+ case Hexagon::J2_jumptnew:
+ case Hexagon::J2_jumpfnew:
+ case Hexagon::J2_jumptnewpt:
+ case Hexagon::J2_jumpfnewpt:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ void reverseBranchCondition(MCInst &Inst, const MCSymbol *TBB,
+ MCContext *Ctx) const override {
+ auto Opcode = getInvertedBranchOpcode(Inst.getOpcode());
+ Inst.setOpcode(Opcode);
+ replaceBranchTarget(Inst, TBB, Ctx);
+ }
+
+ void replaceBranchTarget(MCInst &Inst, const MCSymbol *TBB,
+ MCContext *Ctx) const override {
+ unsigned SymOpIndex;
+ if (!getSymbolRefOperandNum(Inst, SymOpIndex))
+ return;
+
+ Inst.getOperand(SymOpIndex) =
+ MCOperand::createExpr(createHexExpr(TBB, *Ctx));
+ }
+
+ IndirectBranchType analyzeIndirectBranch(
+ MCInst &Instruction, InstructionIterator Begin, InstructionIterator End,
+ const unsigned PtrSize, MCInst *&MemLocInstr, unsigned &BaseRegNum,
+ unsigned &IndexRegNum, int64_t &DispValue, const MCExpr *&DispExpr,
+ MCInst *&PCRelBaseOut, MCInst *&FixedEntryLoadInst) const override {
+ MemLocInstr = nullptr;
+ BaseRegNum = 0;
+ IndexRegNum = 0;
+ DispValue = 0;
+ DispExpr = nullptr;
+ PCRelBaseOut = nullptr;
+ FixedEntryLoadInst = nullptr;
+
+ // J2_jumpr R31 is a return, not an indirect branch.
+ if (Instruction.getOpcode() == Hexagon::J2_jumpr &&
+ Instruction.getOperand(0).getReg() == Hexagon::R31)
+ return IndirectBranchType::UNKNOWN;
+
+ // Other J2_jumpr instructions could be tail calls.
+ if (Instruction.getOpcode() == Hexagon::J2_jumpr)
+ return IndirectBranchType::POSSIBLE_TAIL_CALL;
+
+ return IndirectBranchType::UNKNOWN;
+ }
+
+ bool convertJmpToTailCall(MCInst &Inst) override {
+ if (isTailCall(Inst))
+ return false;
+
+ // Only convert unconditional jumps. Return false for conditional
+ // branches so the caller can handle them as conditional tail calls.
+ if (isConditionalBranch(Inst))
+ return false;
+
+ setTailCall(Inst);
+ return true;
+ }
+
+ bool isEpilogue(const BinaryBasicBlock &BB) const override {
+ // A BB ending with jumpr r31 or dealloc_return is an epilogue.
+ for (auto I = BB.rbegin(), E = BB.rend(); I != E; ++I) {
+ const MCInst &Inst = *I;
+ if (isNoop(Inst))
+ continue;
+ // jumpr r31 is a return.
+ if (Inst.getOpcode() == Hexagon::J2_jumpr && Inst.getNumOperands() > 0 &&
+ Inst.getOperand(0).isReg() &&
+ Inst.getOperand(0).getReg() == Hexagon::R31)
+ return true;
+ // dealloc_return variants.
+ switch (Inst.getOpcode()) {
+ case Hexagon::L4_return:
+ case Hexagon::L6_return_map_to_raw:
+ case Hexagon::L4_return_t:
+ case Hexagon::L4_return_f:
+ case Hexagon::L4_return_tnew_pnt:
+ case Hexagon::L4_return_tnew_pt:
+ case Hexagon::L4_return_fnew_pnt:
+ case Hexagon::L4_return_fnew_pt:
+ case Hexagon::L4_return_map_to_raw_t:
+ case Hexagon::L4_return_map_to_raw_f:
+ case Hexagon::L4_return_map_to_raw_tnew_pnt:
+ case Hexagon::L4_return_map_to_raw_tnew_pt:
+ case Hexagon::L4_return_map_to_raw_fnew_pnt:
+ case Hexagon::L4_return_map_to_raw_fnew_pt:
+ return true;
+ default:
+ return false;
+ }
+ }
+ return false;
+ }
+
+ int getPCRelEncodingSize(const MCInst &Inst) const override {
+ // ExtentBits already includes the alignment bits. For example,
+ // J2_jump (B22_PCREL) has ExtentBits=24, ExtentAlign=2; the raw
+ // field width is 24-2=22 bits, and the byte-addressable range is
+ // 2^(24-1) = +-8MB. This matches the AArch64 convention where
+ // getPCRelEncodingSize returns the total byte-addressable bits.
+ return HexagonMCInstrInfo::getExtentBits(*Info, Inst);
+ }
+
+ int getUncondBranchEncodingSize() const override { return 24; }
+
+ void createReturn(MCInst &Inst) const override {
+ // jumpr R31
+ Inst.setOpcode(Hexagon::J2_jumpr);
+ Inst.clear();
+ Inst.addOperand(MCOperand::createReg(Hexagon::R31));
+ setHexPacketEnd(Inst);
+ }
+
+ void createUncondBranch(MCInst &Inst, const MCSymbol *TBB,
+ MCContext *Ctx) const override {
+ Inst.setOpcode(Hexagon::J2_jump);
+ Inst.clear();
+ Inst.addOperand(MCOperand::createExpr(createHexExpr(TBB, *Ctx)));
+ setHexPacketEnd(Inst);
+ }
+
+ StringRef getTrapFillValue() const override {
+ // trap0(#0) = 0x5400c000 (little-endian), with packet-end parse bits.
+ static const char Trap[] = "\x00\xc0\x00\x54";
+ return StringRef(Trap, 4);
+ }
+
+ void createCall(MCInst &Inst, const MCSymbol *Target,
+ MCContext *Ctx) override {
+ Inst.setOpcode(Hexagon::J2_call);
+ Inst.clear();
+ Inst.addOperand(MCOperand::createExpr(createHexExpr(Target, *Ctx)));
+ setHexPacketEnd(Inst);
+ }
+
+ void createTailCall(MCInst &Inst, const MCSymbol *Target,
+ MCContext *Ctx) override {
+ Inst.setOpcode(Hexagon::J2_jump);
+ Inst.clear();
+ Inst.addOperand(MCOperand::createExpr(createHexExpr(Target, *Ctx)));
+ setTailCall(Inst);
+ setHexPacketEnd(Inst);
+ }
+
+ void createLongTailCall(InstructionListType &Seq, const MCSymbol *Target,
+ MCContext *Ctx) override {
+ // Hexagon J2_jump with a constant extender covers the full 32-bit
+ // address space, so a "long" tail call is the same as a regular one.
+ MCInst Inst;
+ createTailCall(Inst, Target, Ctx);
+ Seq.push_back(Inst);
+ }
+
+ bool analyzeBranch(InstructionIterator Begin, InstructionIterator End,
+ const MCSymbol *&TBB, const MCSymbol *&FBB,
+ MCInst *&CondBranch,
+ MCInst *&UncondBranch) const override {
+ auto I = End;
+
+ while (I != Begin) {
+ --I;
+
+ // Ignore nops and CFIs
+ if (isPseudo(*I) || isNoop(*I))
+ continue;
+
+ // Stop when we find the first non-terminator
+ if (!isTerminator(*I) || isTailCall(*I) || !isBranch(*I))
+ break;
+
+ // Handle unconditional branches.
+ if (isUnconditionalBranch(*I)) {
+ // If any code was seen after this unconditional branch, we've seen
+ // unreachable code. Ignore them.
+ CondBranch = nullptr;
+ UncondBranch = &*I;
+ const MCSymbol *Sym = getTargetSymbol(*I);
+ assert(Sym != nullptr &&
+ "Couldn't extract BB symbol from jump operand");
+ TBB = Sym;
+ continue;
+ }
+
+ // Handle conditional branches and ignore indirect branches
+ if (isIndirectBranch(*I))
+ return false;
+
+ if (CondBranch == nullptr) {
+ const MCSymbol *TargetBB = getTargetSymbol(*I);
+ if (TargetBB == nullptr) {
+ // Unrecognized branch target
+ return false;
+ }
+ FBB = TBB;
+ TBB = TargetBB;
+ CondBranch = &*I;
+ continue;
+ }
+
+ llvm_unreachable("multiple conditional branches in one BB");
+ }
+
+ return true;
+ }
+
+ bool getSymbolRefOperandNum(const MCInst &Inst, unsigned &OpNum) const {
+ // For direct branches and calls, the branch target is the extendable
+ // operand. Use TSFlags to find it generically, covering J2 and J4
+ // compound compare-and-jump instructions.
+ if (HexagonMCInstrInfo::isExtendable(*Info, Inst)) {
+ OpNum = HexagonMCInstrInfo::getExtendableOp(*Info, Inst);
+ return true;
+ }
+ return false;
+ }
+
+ const MCSymbol *getTargetSymbol(const MCExpr *Expr) const override {
+ if (auto *HExpr = dyn_cast<HexagonMCExpr>(Expr))
+ return getTargetSymbol(HExpr->getExpr());
+
+ return MCPlusBuilder::getTargetSymbol(Expr);
+ }
+
+ const MCSymbol *getTargetSymbol(const MCInst &Inst,
+ unsigned OpNum = 0) const override {
+ if (!OpNum && !getSymbolRefOperandNum(Inst, OpNum))
+ return nullptr;
+
+ const MCOperand &Op = Inst.getOperand(OpNum);
+ if (!Op.isExpr())
+ return nullptr;
+
+ return getTargetSymbol(Op.getExpr());
+ }
+
+ bool lowerTailCall(MCInst &Inst) override {
+ removeAnnotation(Inst, MCPlus::MCAnnotation::kTailCall);
+ if (getConditionalTailCall(Inst))
+ unsetConditionalTailCall(Inst);
+ return true;
+ }
+
+ void createNoop(MCInst &Inst) const override {
+ Inst.clear();
+ Inst.setOpcode(Hexagon::A2_nop);
+ setHexPacketEnd(Inst);
+ }
+
+ void createTrap(MCInst &Inst) const override {
+ Inst.clear();
+ Inst.setOpcode(Hexagon::J2_trap0);
+ Inst.addOperand(MCOperand::createImm(0));
+ setHexPacketEnd(Inst);
+ }
+
+ bool isTrap(const MCInst &Inst) const override {
+ return Inst.getOpcode() == Hexagon::J2_trap0 ||
+ Inst.getOpcode() == Hexagon::J2_trap1;
+ }
+
+ MCPhysReg getStackPointer() const override { return Hexagon::R29; }
+
+ MCPhysReg getFramePointer() const override { return Hexagon::R30; }
+
+ MCPhysReg getFlagsReg() const override {
+ // Hexagon has no monolithic flags register; predicate registers P0-P3
+ // are used for conditional execution. Return 0 (no flags register).
+ return 0;
+ }
+
+ std::optional<uint32_t>
+ getInstructionSize(const MCInst &Inst) const override {
+ // All Hexagon instructions are 4 bytes. This avoids the MCCodeEmitter
+ // which asserts on non-BUNDLE instructions.
+ return 4;
+ }
+
+ bool requiresBundleEmission() const override { return true; }
+
+ bool isNewValueConsumer(const MCInst &Inst) const override {
+ return HexagonMCInstrInfo::isNewValue(*Info, Inst);
+ }
+
+ /// Lower map-to-raw pseudo instructions back to their real (raw)
+ /// forms. The Hexagon disassembler simplifies certain instructions
+ /// by removing implicit operands and using pseudo opcodes; we must
+ /// reverse this before encoding.
+ static void lowerToRaw(MCInst &Inst) {
+ switch (Inst.getOpcode()) {
+ default:
+ break;
+ case Hexagon::S6_allocframe_to_raw: {
+ // (#Ii) -> S2_allocframe (R29, R29, #Ii)
+ MCOperand Imm = Inst.getOperand(0);
+ Inst.clear();
+ Inst.setOpcode(Hexagon::S2_allocframe);
+ Inst.addOperand(MCOperand::createReg(Hexagon::R29));
+ Inst.addOperand(MCOperand::createReg(Hexagon::R29));
+ Inst.addOperand(Imm);
+ break;
+ }
+ case Hexagon::L6_return_map_to_raw:
+ // () -> L4_return (D15, R30)
+ Inst.clear();
+ Inst.setOpcode(Hexagon::L4_return);
+ Inst.addOperand(MCOperand::createReg(Hexagon::D15));
+ Inst.addOperand(MCOperand::createReg(Hexagon::R30));
+ break;
+ case Hexagon::L6_deallocframe_map_to_raw:
+ // () -> L2_deallocframe (D15, R30)
+ Inst.clear();
+ Inst.setOpcode(Hexagon::L2_deallocframe);
+ Inst.addOperand(MCOperand::createReg(Hexagon::D15));
+ Inst.addOperand(MCOperand::createReg(Hexagon::R30));
+ break;
+ // Conditional return variants: (Pv4) -> L4_return_* (D15, Pv4, R30)
+ // The disassembler erased D15 (index 0) and R30 (index 2), leaving (Pv4).
+ // Restore to (D15, Pv4, R30).
+ case Hexagon::L4_return_map_to_raw_t:
+ case Hexagon::L4_return_map_to_raw_f:
+ case Hexagon::L4_return_map_to_raw_tnew_pt:
+ case Hexagon::L4_return_map_to_raw_fnew_pt:
+ case Hexagon::L4_return_map_to_raw_tnew_pnt:
+ case Hexagon::L4_return_map_to_raw_fnew_pnt: {
+ static const unsigned CondReturnOpcodes[][2] = {
+ {Hexagon::L4_return_map_to_raw_t, Hexagon::L4_return_t},
+ {Hexagon::L4_return_map_to_raw_f, Hexagon::L4_return_f},
+ {Hexagon::L4_return_map_to_raw_tnew_pt, Hexagon::L4_return_tnew_pt},
+ {Hexagon::L4_return_map_to_raw_fnew_pt, Hexagon::L4_return_fnew_pt},
+ {Hexagon::L4_return_map_to_raw_tnew_pnt, Hexagon::L4_return_tnew_pnt},
+ {Hexagon::L4_return_map_to_raw_fnew_pnt, Hexagon::L4_return_fnew_pnt},
+ };
+ unsigned NewOpc = 0;
+ for (const auto &Pair : CondReturnOpcodes) {
+ if (Pair[0] == Inst.getOpcode()) {
+ NewOpc = Pair[1];
+ break;
+ }
+ }
+ assert(NewOpc && "Conditional return opcode not found in mapping");
+ MCOperand Pred = Inst.getOperand(0);
+ Inst.clear();
+ Inst.setOpcode(NewOpc);
+ Inst.addOperand(MCOperand::createReg(Hexagon::D15));
+ Inst.addOperand(Pred);
+ Inst.addOperand(MCOperand::createReg(Hexagon::R30));
+ break;
+ }
+ }
+ }
+
+ MCInst createBundle(MCContext &Ctx, ArrayRef<MCInst> Instrs,
+ bool InnerLoop = false,
+ bool OuterLoop = false) const override {
+ MCInst Bundle;
+ Bundle.setOpcode(Hexagon::BUNDLE);
+ Bundle.addOperand(MCOperand::createImm(0));
+
+ for (const MCInst &Inst : Instrs) {
+ MCInst *Copy = Ctx.createMCInst();
+ *Copy = Inst;
+ // Strip BOLT annotations before adding to bundle -- the streamer
+ // and code emitter do not expect them.
+ stripAnnotations(*Copy);
+ // Lower map-to-raw pseudos back to raw forms for encoding.
+ if (Inst.getOpcode() != Hexagon::A4_ext)
+ lowerToRaw(*Copy);
+ // The Hexagon MC code emitter expects all immediate operands to be
+ // wrapped in HexagonMCExpr. BOLT passes (e.g. peephole tail-call
+ // traps) may create instructions with raw MCOperand::createImm()
+ // immediates. Wrap them here so encoding succeeds.
+ for (unsigned OpI = 0, OpE = Copy->getNumOperands(); OpI < OpE; ++OpI) {
+ MCOperand &Op = Copy->getOperand(OpI);
+ if (Op.isImm()) {
+ const MCExpr *CE = MCConstantExpr::create(Op.getImm(), Ctx);
+ Op = MCOperand::createExpr(HexagonMCExpr::create(CE, Ctx));
+ }
+ }
+ assert((Copy->getNumOperands() == 0 || !Copy->getOperand(0).isImm()) &&
+ "All operands must be wrapped in HexagonMCExpr");
+ Bundle.addOperand(MCOperand::createInst(Copy));
+ }
+
+ // Set hardware loop end markers. The MC code emitter uses these
+ // flags to emit INST_PARSE_LOOP_END (0x8000) parse bits on the
+ // appropriate instruction words within the packet.
+ if (InnerLoop)
+ HexagonMCInstrInfo::setInnerLoop(Bundle);
+ if (OuterLoop)
+ HexagonMCInstrInfo::setOuterLoop(Bundle);
+
+ return Bundle;
+ }
+
+ bool evaluateMemOperandTarget(const MCInst &Inst, uint64_t &Target,
+ uint64_t Address,
+ uint64_t Size) const override {
+ return false;
+ }
+
+ uint16_t getMinFunctionAlignment() const override {
+ // Hexagon packets are 4-byte aligned.
+ return 4;
+ }
+
+ void createStackPointerIncrement(MCInst &Inst, int Imm,
+ bool NoFlagsClobber = false) const override {
+ // R29 = add(R29, #-Imm)
+ Inst = MCInstBuilder(Hexagon::A2_addi)
+ .addReg(Hexagon::R29)
+ .addReg(Hexagon::R29)
+ .addImm(-Imm);
+ setHexPacketEnd(Inst);
+ }
+
+ void createStackPointerDecrement(MCInst &Inst, int Imm,
+ bool NoFlagsClobber = false) const override {
+ // R29 = add(R29, #Imm)
+ Inst = MCInstBuilder(Hexagon::A2_addi)
+ .addReg(Hexagon::R29)
+ .addReg(Hexagon::R29)
+ .addImm(Imm);
+ setHexPacketEnd(Inst);
+ }
+};
+
+} // end anonymous namespace
+
+namespace llvm {
+namespace bolt {
+
+MCPlusBuilder *createHexagonMCPlusBuilder(const MCInstrAnalysis *Analysis,
+ const MCInstrInfo *Info,
+ const MCRegisterInfo *RegInfo,
+ const MCSubtargetInfo *STI) {
+ return new HexagonMCPlusBuilder(Analysis, Info, RegInfo, STI);
+}
+
+} // namespace bolt
+} // namespace llvm
>From 3250b443784f33a23f1895cba8a8f52f4129ca65 Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Tue, 14 Apr 2026 22:25:45 -0700
Subject: [PATCH 3/5] [BOLT][Hexagon] Add packet boundary detection during
disassembly
Annotate Hexagon VLIW packet boundaries during instruction
disassembly by inspecting bits [15:14] of each 32-bit instruction
word (the "parse bits"):
0b11 (0xc000) = packet end
0b00 (0x0000) = duplex (always last in packet)
0b01 = middle of packet (not end)
0b10 = hardware loop end (handled separately)
Instructions at packet boundaries receive a "HexPacketEnd"
annotation, which is used later by the emitter to reconstruct
VLIW bundles.
Also skip instruction encoding in scanExternalRefs for Hexagon,
since the MC encoder expects BUNDLE MCInsts and createRelocation
is not implemented for this target.
---
bolt/lib/Core/BinaryFunction.cpp | 38 ++++++++++++++++++++++++++++++++
1 file changed, 38 insertions(+)
diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index c5aefe685de34..f83dff6b183ac 100644
--- a/bolt/lib/Core/BinaryFunction.cpp
+++ b/bolt/lib/Core/BinaryFunction.cpp
@@ -35,6 +35,7 @@
#include "llvm/Object/ObjectFile.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
+#include "llvm/Support/Endian.h"
#include "llvm/Support/GenericDomTreeConstruction.h"
#include "llvm/Support/GenericLoopInfoImpl.h"
#include "llvm/Support/GraphWriter.h"
@@ -1543,6 +1544,31 @@ Error BinaryFunction::disassemble() {
MIB->setSize(Instruction, Size);
}
+ // Annotate Hexagon packet boundaries from parse bits in the raw
+ // instruction word. The Hexagon disassembler returns individual
+ // instructions but does not convey packet structure. We recover it
+ // by inspecting bits [15:14] of the 32-bit instruction word:
+ // 0b11 (0xc000) = packet end,
+ // 0b10 (0x8000) = hardware loop end,
+ // 0b01 (0x4000) = not end (middle of packet),
+ // 0b00 (0x0000) = duplex (always last word in packet).
+ if (BC.isHexagon() && Size >= 4) {
+ uint32_t RawWord =
+ support::endian::read32le(FunctionData.data() + Offset);
+ uint32_t ParseBits = RawWord & 0xc000;
+ // Packet end: 0xc000 (end) or 0x0000 (duplex, always last).
+ if (ParseBits == 0xc000 || ParseBits == 0x0000) {
+ if (!MIB->hasAnnotation(Instruction, "HexPacketEnd"))
+ MIB->addAnnotation(Instruction, "HexPacketEnd", true);
+ }
+ // Hardware loop end: 0x8000. The instruction at index 0 with
+ // this parse bit marks endloop0 (inner); at index 1, endloop1
+ // (outer). We annotate the instruction and resolve the loop
+ // type by position when building bundles for emission.
+ if (ParseBits == 0x8000)
+ MIB->addAnnotation(Instruction, "HexLoopEnd", true);
+ }
+
addInstruction(Offset, std::move(Instruction));
}
@@ -1828,6 +1854,13 @@ bool BinaryFunction::scanExternalRefs() {
}
}
+ // On Hexagon, the MC encoder expects BUNDLE MCInsts and the
+ // createRelocation method is not implemented, so skip instruction
+ // encoding for external reference scanning. The branch-target
+ // handling above (which doesn't need encoding) is sufficient.
+ if (BC.isHexagon())
+ continue;
+
// Emit the instruction using temp emitter and generate relocations.
SmallString<256> Code;
SmallVector<MCFixup, 4> Fixups;
@@ -2551,6 +2584,11 @@ Error BinaryFunction::buildCFG(MCPlusBuilder::AllocatorIdTy AllocatorId) {
setSimple(false);
}
+ // Hexagon VLIW: nops are structurally significant within packets,
+ // affecting packet size and hardware loop (endloop) marker positioning.
+ if (BC.isHexagon())
+ PreserveNops = true;
+
clearList(ExternallyReferencedOffsets);
clearList(UnknownIndirectBranchOffsets);
>From 0a6b42de34c5ef90c586f57189fa140de5b252df Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Tue, 14 Apr 2026 22:26:09 -0700
Subject: [PATCH 4/5] [BOLT][Hexagon] Add VLIW packet emission support
Add packet accumulation and bundle emission logic to BinaryEmitter
for Hexagon's VLIW architecture.
Key behaviors:
- Instructions are accumulated into a packet buffer until a
HexPacketEnd annotation is encountered, then emitted as a
single BUNDLE MCInst via MCPlusBuilder::createBundle()
- Cross-BB packet carry: when BOLT splits a packet at a branch
boundary, the pending packet carries across to the fallthrough
BB since all instructions in a Hexagon packet execute atomically
- Mid-packet labels are dropped since they are not meaningful on
Hexagon (packets execute atomically)
- .new value consumer detection prevents flushing a packet when
the next BB starts with an instruction that depends on a
producer in the current packet
- Constant extenders (immext/A4_ext) are kept in the packet to
ensure the MC code emitter correctly encodes extended immediates
- MC pseudo instructions are skipped since the Hexagon code
emitter cannot encode them
---
bolt/lib/Core/BinaryEmitter.cpp | 140 +++++++++++++++++++++++++++++++-
1 file changed, 138 insertions(+), 2 deletions(-)
diff --git a/bolt/lib/Core/BinaryEmitter.cpp b/bolt/lib/Core/BinaryEmitter.cpp
index 49604542cc7da..618c30ae27e10 100644
--- a/bolt/lib/Core/BinaryEmitter.cpp
+++ b/bolt/lib/Core/BinaryEmitter.cpp
@@ -454,18 +454,105 @@ void BinaryEmitter::emitFunctionBody(BinaryFunction &BF, FunctionFragment &FF,
BF.duplicateConstantIslands();
}
+ const bool NeedBundles = BC.MIB->requiresBundleEmission();
+
+ // Hexagon packet accumulator: collects instructions until a packet-end
+ // annotation is seen, then emits them as a single BUNDLE MCInst.
+ SmallVector<MCInst, 4> HexPacket;
+
+ // Helper: flush accumulated Hexagon packet as a BUNDLE.
+ auto flushHexPacket = [&]() {
+ if (HexPacket.empty())
+ return;
+ LLVM_DEBUG({
+ dbgs() << "BOLT-DEBUG: flushing packet (" << HexPacket.size()
+ << " instrs) in " << BF.getPrintName() << "\n";
+ });
+ // Detect hardware loop end markers. In the original encoding,
+ // INST_PARSE_LOOP_END on instruction index 0 means endloop0
+ // (inner loop), on index 1 means endloop1 (outer loop).
+ bool InnerLoop = false;
+ bool OuterLoop = false;
+ for (unsigned I = 0, E = HexPacket.size(); I < E; ++I) {
+ if (BC.MIB->hasAnnotation(HexPacket[I], "HexLoopEnd")) {
+ if (I == 0)
+ InnerLoop = true;
+ else if (I == 1)
+ OuterLoop = true;
+ }
+ }
+ MCInst Bundle = BC.MIB->createBundle(Streamer.getContext(), HexPacket,
+ InnerLoop, OuterLoop);
+ Streamer.emitInstruction(Bundle, *BC.STI);
+ HexPacket.clear();
+ };
+
// Track the first emitted instruction with debug info.
bool FirstInstr = true;
+ BinaryBasicBlock *PrevBB = nullptr;
for (BinaryBasicBlock *const BB : FF) {
if ((opts::AlignBlocks || opts::PreserveBlocksAlignment) &&
BB->getAlignment() > 1)
Streamer.emitCodeAlignment(BB->getAlign(), &*BC.STI,
BB->getAlignmentMaxBytes());
+
+ // On Hexagon, a VLIW packet may span BB boundaries because BOLT
+ // splits packets at branch instructions, placing post-branch
+ // instructions in the fallthrough BB. Since all instructions in a
+ // Hexagon packet execute atomically, we carry the pending packet
+ // across the BB boundary to keep them in the same bundle.
+ //
+ // When carrying across, we still emit the BB label (BOLT requires
+ // all emitted BB labels to be defined), but we do NOT flush the
+ // pending packet. The label will precede the combined bundle in
+ // the output, which is correct: on Hexagon, all instructions in
+ // the packet execute atomically, so the label points to the same
+ // execution point regardless of where it falls within the packet.
+ //
+ // Carry-across is safe when the BB is a pure fallthrough: either
+ // unreachable (pred_size == 0) or the only predecessor is the
+ // immediately preceding layout block. Branch targets (multiple
+ // predecessors, non-adjacent predecessor) must flush so the label
+ // starts a new packet.
+ //
+ // On Hexagon, entry points (including secondary entries) do not
+ // force a flush: the processor executes entire packets atomically,
+ // so a jump to a mid-packet label still runs all instructions from
+ // the packet start. This preserves .new dependencies that span an
+ // entry-point boundary within a packet.
+ if (NeedBundles && !HexPacket.empty()) {
+ bool CanCarryAcross =
+ (!BB->isEntryPoint() || BC.isHexagon()) &&
+ (BB->pred_size() == 0 ||
+ (BB->pred_size() == 1 && PrevBB && *BB->pred_begin() == PrevBB));
+ if (!CanCarryAcross || HexPacket.size() >= 4) {
+ // Before flushing, check whether the next BB starts with a
+ // .new value consumer (compare-and-jump or store). If so,
+ // keep the packet open: the producer in the current packet
+ // must stay in the same emitted packet as the consumer.
+ // Hexagon packets execute atomically, so the label pointing
+ // here runs all instructions from the packet start anyway.
+ bool NextIsNewValue = false;
+ if (BC.isHexagon() && !BB->empty()) {
+ auto First = BB->begin();
+ // Skip pseudos (CFI, etc.) to find the first real instruction.
+ while (First != BB->end() && BC.MIB->isPseudo(*First))
+ ++First;
+ if (First != BB->end())
+ NextIsNewValue = BC.MIB->isNewValueConsumer(*First);
+ }
+ if (!NextIsNewValue)
+ flushHexPacket();
+ }
+ } else if (NeedBundles) {
+ flushHexPacket();
+ }
Streamer.emitLabel(BB->getLabel());
if (!EmitCodeOnly) {
if (MCSymbol *EntrySymbol = BF.getSecondaryEntryPointSymbol(*BB))
Streamer.emitLabel(EntrySymbol);
}
+ PrevBB = BB;
SMLoc LastLocSeen;
for (auto I = BB->begin(), E = BB->end(); I != E; ++I) {
@@ -499,8 +586,21 @@ void BinaryEmitter::emitFunctionBody(BinaryFunction &BF, FunctionFragment &FF,
BB->getLocSyms().emplace_back(Offset, InstrLabel);
}
- if (InstrLabel)
- Streamer.emitLabel(InstrLabel);
+ if (InstrLabel) {
+ // On Hexagon, labels cannot fall mid-packet because all
+ // instructions in a packet execute atomically. Flushing the
+ // packet here would split .new producer/consumer pairs.
+ // Only emit the label if we are not in the middle of a packet.
+ if (NeedBundles && !HexPacket.empty()) {
+ // Mid-packet addresses are not meaningful on Hexagon
+ // because packets execute atomically. Drop the label.
+ LLVM_DEBUG(dbgs() << "BOLT-DEBUG: dropping mid-packet label "
+ << InstrLabel->getName() << " in "
+ << BF.getPrintName() << '\n');
+ } else {
+ Streamer.emitLabel(InstrLabel);
+ }
+ }
}
// Emit sized NOPs via MCAsmBackend::writeNopData() interface on x86.
@@ -515,10 +615,46 @@ void BinaryEmitter::emitFunctionBody(BinaryFunction &BF, FunctionFragment &FF,
}
}
+ // Hexagon: accumulate instructions into packets and emit as bundles.
+ if (NeedBundles) {
+ bool IsPacketEnd = BC.MIB->hasAnnotation(Instr, "HexPacketEnd");
+ // Skip MC pseudo instructions (e.g. A2_nop) that the Hexagon code
+ // emitter cannot encode. If the pseudo is at a packet end, flush
+ // the packet without it.
+ if (BC.MIB->isPseudo(Instr)) {
+ if (IsPacketEnd)
+ flushHexPacket();
+ continue;
+ }
+ // Constant extender (immext / A4_ext) instructions must be
+ // included in the packet. The MC code emitter requires them
+ // to set State.Extended, which tells it to encode only the
+ // lower bits of the next instruction's immediate (the upper
+ // bits come from the immext word).
+ // Safety: if the packet already has 4 instructions (Hexagon max),
+ // flush before adding more. This guards against pathological
+ // carry-across scenarios.
+ if (HexPacket.size() >= 4)
+ flushHexPacket();
+ HexPacket.push_back(Instr);
+ // Flush on packet end. Instructions with HexPacketEnd annotation
+ // (from disassembly or BOLT-generated) terminate the current packet.
+ // Note: all BOLT-generated instructions get HexPacketEnd added by
+ // the Hexagon MCPlusBuilder, so they become singleton packets.
+ if (IsPacketEnd) {
+ flushHexPacket();
+ }
+ continue;
+ }
+
Streamer.emitInstruction(Instr, *BC.STI);
}
}
+ // Flush any remaining Hexagon packet at end of function fragment.
+ if (NeedBundles)
+ flushHexPacket();
+
if (!EmitCodeOnly)
emitConstantIslands(BF, FF.isSplitFragment());
}
>From 36403d8abf42b0b89ad4836de637f572ea9faa49 Mon Sep 17 00:00:00 2001
From: Brian Cain <brian.cain at oss.qualcomm.com>
Date: Tue, 14 Apr 2026 22:27:10 -0700
Subject: [PATCH 5/5] [BOLT][Hexagon] Add lit tests
Add tests for Hexagon BOLT support covering:
- CFG construction: basic blocks, compound branches, conditional
tail calls, duplex instructions, indirect calls
- Binary rewriting: allocframe, branch targets, conditional returns,
constant extenders, new-value jumps, new-value stores
Each test assembles a Hexagon program, links it with --emit-relocs,
runs llvm-bolt to rewrite it, then disassembles and verifies the
output matches expected instruction patterns.
---
bolt/test/Hexagon/cfg-build.s | 30 ++++
bolt/test/Hexagon/cfg-compound-branch.s | 37 +++++
bolt/test/Hexagon/cfg-cond-tail-call.s | 38 +++++
bolt/test/Hexagon/cfg-duplex.s | 39 ++++++
bolt/test/Hexagon/cfg-indirect-call.s | 30 ++++
bolt/test/Hexagon/lit.local.cfg | 7 +
bolt/test/Hexagon/rewrite-allocframe.s | 75 ++++++++++
bolt/test/Hexagon/rewrite-branch-target.s | 110 +++++++++++++++
bolt/test/Hexagon/rewrite-cond-return.s | 52 +++++++
bolt/test/Hexagon/rewrite-const-extender.s | 33 +++++
bolt/test/Hexagon/rewrite-double-extender.s | 45 ++++++
bolt/test/Hexagon/rewrite-duplex.s | 58 ++++++++
bolt/test/Hexagon/rewrite-endloop-nested.s | 58 ++++++++
bolt/test/Hexagon/rewrite-full-packet-mixed.s | 129 +++++++++++++++++
bolt/test/Hexagon/rewrite-hvx-packet.s | 131 ++++++++++++++++++
bolt/test/Hexagon/rewrite-newvalue-jump.s | 40 ++++++
bolt/test/Hexagon/rewrite-newvalue-store.s | 35 +++++
bolt/test/Hexagon/rewrite-pair-registers.s | 64 +++++++++
18 files changed, 1011 insertions(+)
create mode 100644 bolt/test/Hexagon/cfg-build.s
create mode 100644 bolt/test/Hexagon/cfg-compound-branch.s
create mode 100644 bolt/test/Hexagon/cfg-cond-tail-call.s
create mode 100644 bolt/test/Hexagon/cfg-duplex.s
create mode 100644 bolt/test/Hexagon/cfg-indirect-call.s
create mode 100644 bolt/test/Hexagon/lit.local.cfg
create mode 100644 bolt/test/Hexagon/rewrite-allocframe.s
create mode 100644 bolt/test/Hexagon/rewrite-branch-target.s
create mode 100644 bolt/test/Hexagon/rewrite-cond-return.s
create mode 100644 bolt/test/Hexagon/rewrite-const-extender.s
create mode 100644 bolt/test/Hexagon/rewrite-double-extender.s
create mode 100644 bolt/test/Hexagon/rewrite-duplex.s
create mode 100644 bolt/test/Hexagon/rewrite-endloop-nested.s
create mode 100644 bolt/test/Hexagon/rewrite-full-packet-mixed.s
create mode 100644 bolt/test/Hexagon/rewrite-hvx-packet.s
create mode 100644 bolt/test/Hexagon/rewrite-newvalue-jump.s
create mode 100644 bolt/test/Hexagon/rewrite-newvalue-store.s
create mode 100644 bolt/test/Hexagon/rewrite-pair-registers.s
diff --git a/bolt/test/Hexagon/cfg-build.s b/bolt/test/Hexagon/cfg-build.s
new file mode 100644
index 0000000000000..a8764e9cbd943
--- /dev/null
+++ b/bolt/test/Hexagon/cfg-build.s
@@ -0,0 +1,30 @@
+## Verify that BOLT can build a CFG for simple Hexagon functions.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe --print-cfg --print-only=_start -o /dev/null \
+# RUN: > %t.log 2>&1
+# RUN: FileCheck %s --input-file=%t.log
+
+# CHECK: Binary Function "_start" after building cfg
+# CHECK: .LBB00 (2 instructions, align : 1)
+# CHECK-NEXT: Entry Point
+# CHECK-NEXT: {{.*}}: call foo
+# CHECK: {{.*}}: jumpr r31
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ call foo
+ jumpr r31
+ .size _start, .-_start
+
+ .globl foo
+ .type foo, at function
+ .p2align 4
+foo:
+ r0 = #0
+ jumpr r31
+ .size foo, .-foo
diff --git a/bolt/test/Hexagon/cfg-compound-branch.s b/bolt/test/Hexagon/cfg-compound-branch.s
new file mode 100644
index 0000000000000..c6f5b89be43e0
--- /dev/null
+++ b/bolt/test/Hexagon/cfg-compound-branch.s
@@ -0,0 +1,37 @@
+## Verify that BOLT can build a CFG for functions with J4 compound
+## compare-and-jump instructions. These instructions have the branch
+## target in a different operand position than J2 jumps; BOLT uses
+## TSFlags-based getExtendableOp to find the target generically.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe --print-cfg --print-only=test_compound -o /dev/null \
+# RUN: > %t.log 2>&1
+# RUN: FileCheck %s --input-file=%t.log
+
+# CHECK: Binary Function "test_compound" after building cfg
+# CHECK: if (p0.new) jump:t .Ltmp
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ call test_compound
+ jumpr r31
+ .size _start, .-_start
+
+ .globl test_compound
+ .type test_compound, at function
+ .p2align 4
+test_compound:
+ {
+ p0 = cmp.eq(r0, #0)
+ if (p0.new) jump:t .Ltarget
+ }
+ r0 = #1
+ jumpr r31
+.Ltarget:
+ r0 = #0
+ jumpr r31
+ .size test_compound, .-test_compound
diff --git a/bolt/test/Hexagon/cfg-cond-tail-call.s b/bolt/test/Hexagon/cfg-cond-tail-call.s
new file mode 100644
index 0000000000000..afdacd5e7737e
--- /dev/null
+++ b/bolt/test/Hexagon/cfg-cond-tail-call.s
@@ -0,0 +1,38 @@
+## Verify that BOLT handles conditional branches to external functions
+## without crashing. convertJmpToTailCall must return false for
+## conditional branches so they are treated as conditional tail calls.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe --print-cfg --print-only=test_cond_tc -o /dev/null \
+# RUN: > %t.log 2>&1
+# RUN: FileCheck %s --input-file=%t.log
+
+# CHECK: Binary Function "test_cond_tc" after building cfg
+# CHECK: if (p0) jump:nt
+# CHECK: jump external_func # TAILCALL
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ call test_cond_tc
+ jumpr r31
+ .size _start, .-_start
+
+ .globl test_cond_tc
+ .type test_cond_tc, at function
+ .p2align 4
+test_cond_tc:
+ p0 = cmp.eq(r0, #0)
+ if (p0) jump external_func
+ jumpr r31
+ .size test_cond_tc, .-test_cond_tc
+
+ .globl external_func
+ .type external_func, at function
+ .p2align 4
+external_func:
+ jumpr r31
+ .size external_func, .-external_func
diff --git a/bolt/test/Hexagon/cfg-duplex.s b/bolt/test/Hexagon/cfg-duplex.s
new file mode 100644
index 0000000000000..52f6ca09a5c0f
--- /dev/null
+++ b/bolt/test/Hexagon/cfg-duplex.s
@@ -0,0 +1,39 @@
+## Verify that BOLT can build a CFG for functions containing duplex
+## instructions. Hexagon duplex instructions encode two sub-instructions
+## in a single 32-bit word, producing MCInst operands of type kInst with
+## non-null values. BOLT must not confuse these with annotation sentinels.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe --print-cfg --print-only=_start -o /dev/null \
+# RUN: > %t.log 2>&1
+# RUN: FileCheck %s --input-file=%t.log
+
+# CHECK: Binary Function "_start" after building cfg
+# CHECK: .LBB00 (2 instructions, align : 1)
+# CHECK-NEXT: Entry Point
+# CHECK: {{.*}}: call foo
+# CHECK: {{.*}}: r0 = #0x0{{.*}}jumpr r31
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ call foo
+ // A duplex-eligible pair: small immediate + register transfer/return.
+ {
+ r0 = #0
+ jumpr r31
+ }
+ .size _start, .-_start
+
+ .globl foo
+ .type foo, at function
+ .p2align 4
+foo:
+ {
+ r0 = #1
+ jumpr r31
+ }
+ .size foo, .-foo
diff --git a/bolt/test/Hexagon/cfg-indirect-call.s b/bolt/test/Hexagon/cfg-indirect-call.s
new file mode 100644
index 0000000000000..b689953e55bc2
--- /dev/null
+++ b/bolt/test/Hexagon/cfg-indirect-call.s
@@ -0,0 +1,30 @@
+## Verify that BOLT can build a CFG for functions with indirect register
+## calls (callr). isIndirectCall identifies J2_callr and hasPCRelOperand
+## returns false. CFG building must not attempt to resolve a nonexistent
+## branch target symbol.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe --print-cfg --print-only=test_indirect_call \
+# RUN: -o /dev/null > %t.log 2>&1
+# RUN: FileCheck %s --input-file=%t.log
+
+# CHECK: Binary Function "test_indirect_call" after building cfg
+# CHECK: callr r0
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ call test_indirect_call
+ jumpr r31
+ .size _start, .-_start
+
+ .globl test_indirect_call
+ .type test_indirect_call, at function
+ .p2align 4
+test_indirect_call:
+ callr r0
+ jumpr r31
+ .size test_indirect_call, .-test_indirect_call
diff --git a/bolt/test/Hexagon/lit.local.cfg b/bolt/test/Hexagon/lit.local.cfg
new file mode 100644
index 0000000000000..ed8610aecc476
--- /dev/null
+++ b/bolt/test/Hexagon/lit.local.cfg
@@ -0,0 +1,7 @@
+if "Hexagon" not in config.root.targets:
+ config.unsupported = True
+
+flags = "--target=hexagon-unknown-linux-musl -nostdlib -ffreestanding -Wl,--emit-relocs"
+
+config.substitutions.insert(0, ("%cflags", f"%cflags {flags}"))
+config.substitutions.insert(0, ("%cxxflags", f"%cxxflags {flags}"))
diff --git a/bolt/test/Hexagon/rewrite-allocframe.s b/bolt/test/Hexagon/rewrite-allocframe.s
new file mode 100644
index 0000000000000..dc8fba3752b2e
--- /dev/null
+++ b/bolt/test/Hexagon/rewrite-allocframe.s
@@ -0,0 +1,75 @@
+## Verify that BOLT can fully rewrite a binary containing allocframe,
+## deallocframe, and dealloc_return instructions. These are disassembled
+## as map-to-raw pseudo-instructions that must be lowered back to their
+## raw forms during encoding. Multiple functions are included so that
+## calculateEmittedSize exercises the parallel code path.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck --check-prefix=BOLT %s
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# BOLT-NOT: BOLT-ERROR
+
+# CHECK-LABEL: <func_a>:
+# CHECK: allocframe
+# CHECK: dealloc_return
+
+# CHECK-LABEL: <func_b>:
+# CHECK: allocframe
+# CHECK: dealloc_return
+
+# CHECK-LABEL: <func_c>:
+# CHECK: allocframe
+# CHECK: deallocframe
+# CHECK: jumpr r31
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ call func_a
+ call func_b
+ call func_c
+ call func_d
+ jumpr r31
+ .size _start, .-_start
+
+ .globl func_a
+ .type func_a, at function
+ .p2align 4
+func_a:
+ allocframe(#8)
+ r0 = #0
+ r31:30 = dealloc_return(r30):raw
+ .size func_a, .-func_a
+
+ .globl func_b
+ .type func_b, at function
+ .p2align 4
+func_b:
+ allocframe(#16)
+ r0 = memw(r30 + #0)
+ r31:30 = dealloc_return(r30):raw
+ .size func_b, .-func_b
+
+ .globl func_c
+ .type func_c, at function
+ .p2align 4
+func_c:
+ allocframe(#0)
+ r0 = #42
+ r31:30 = deallocframe(r30):raw
+ jumpr r31
+ .size func_c, .-func_c
+
+ .globl func_d
+ .type func_d, at function
+ .p2align 4
+func_d:
+ allocframe(#8)
+ memw(r30 + #-4) = r0
+ r0 = memw(r30 + #-4)
+ r31:30 = dealloc_return(r30):raw
+ .size func_d, .-func_d
diff --git a/bolt/test/Hexagon/rewrite-branch-target.s b/bolt/test/Hexagon/rewrite-branch-target.s
new file mode 100644
index 0000000000000..44e110281f341
--- /dev/null
+++ b/bolt/test/Hexagon/rewrite-branch-target.s
@@ -0,0 +1,110 @@
+## Verify that BOLT correctly replaces branch targets during rewriting.
+## This exercises replaceBranchTarget for conditional branches, unconditional
+## branches, and compound compare-and-jump instructions.
+##
+## BOLT's fixBranches() calls replaceBranchTarget to update branch target
+## symbols after CFG construction and optimization. The function must handle
+## both extendable instructions (conditional/unconditional jumps, compound
+## CJ) and gracefully skip non-extendable instructions (indirect jumps,
+## dealloc_return). The latter case triggers an early return in
+## replaceBranchTarget when getSymbolRefOperandNum returns false.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck --check-prefix=BOLT %s
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# BOLT-NOT: BOLT-ERROR
+
+##============================================================================
+## Diamond CFG: conditional branch to .Ltrue, fallthrough to .Lfalse,
+## both merge at .Ljoin. Tests that replaceBranchTarget correctly
+## updates the conditional branch target after block layout.
+##============================================================================
+# CHECK-LABEL: <test_diamond>:
+# CHECK: p0 = cmp.eq(r0,#0x0)
+# CHECK: if (p0) jump:nt
+# CHECK: r0 = #0x2
+# CHECK: jump
+# CHECK: r0 = #0x1
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ call test_diamond
+ call test_multi_branch
+ call test_compound_diamond
+ jumpr r31
+ .size _start, .-_start
+
+ .globl test_diamond
+ .type test_diamond, at function
+ .p2align 4
+test_diamond:
+ p0 = cmp.eq(r0, #0)
+ if (p0) jump:nt .Ltrue
+.Lfalse:
+ r0 = #2
+ jump .Ljoin
+.Ltrue:
+ r0 = #1
+.Ljoin:
+ jumpr r31
+ .size test_diamond, .-test_diamond
+
+##============================================================================
+## Function with conditional + unconditional branch. The conditional branch
+## target and the unconditional jump target both go through
+## replaceBranchTarget during fixBranches.
+##============================================================================
+# CHECK-LABEL: <test_multi_branch>:
+# CHECK: if (!p0) jump:nt
+# CHECK: r0 = #0xa
+# CHECK: jump
+# CHECK: r0 = #0x14
+
+ .globl test_multi_branch
+ .type test_multi_branch, at function
+ .p2align 4
+test_multi_branch:
+ p0 = cmp.gt(r0, #5)
+ if (!p0) jump:nt .Lsmall
+.Lbig:
+ r0 = #10
+ jump .Lmb_done
+.Lsmall:
+ r0 = #20
+.Lmb_done:
+ jumpr r31
+ .size test_multi_branch, .-test_multi_branch
+
+##============================================================================
+## Compound compare-and-jump (J4 CJ instruction) in a diamond. Compound
+## CJ instructions are extendable (B9_PCREL) so replaceBranchTarget
+## must update the symbol operand. The non-branch instructions in the
+## same packet (r1 = r0) must not be affected.
+##============================================================================
+# CHECK-LABEL: <test_compound_diamond>:
+# CHECK: cmp.eq(r0,#0x0); if (p0.new) jump:nt
+# CHECK: r0 = #0x4
+# CHECK: jump
+# CHECK: r0 = #0x3
+
+ .globl test_compound_diamond
+ .type test_compound_diamond, at function
+ .p2align 4
+test_compound_diamond:
+ {
+ r1 = r0
+ p0 = cmp.eq(r0, #0); if (p0.new) jump:nt .Lcd_true
+ }
+.Lcd_false:
+ r0 = #4
+ jump .Lcd_join
+.Lcd_true:
+ r0 = #3
+.Lcd_join:
+ jumpr r31
+ .size test_compound_diamond, .-test_compound_diamond
diff --git a/bolt/test/Hexagon/rewrite-cond-return.s b/bolt/test/Hexagon/rewrite-cond-return.s
new file mode 100644
index 0000000000000..84a635420d7aa
--- /dev/null
+++ b/bolt/test/Hexagon/rewrite-cond-return.s
@@ -0,0 +1,52 @@
+## Verify that BOLT can fully rewrite conditional dealloc_return
+## instructions. The disassembler produces pseudo opcodes like
+## L4_return_map_to_raw_t and L4_return_map_to_raw_f which lowerToRaw
+## must restore to their real forms with implicit D15 and R30 operands.
+## Only unconditional dealloc_return is tested in rewrite-allocframe.s.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck --check-prefix=BOLT %s
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# BOLT-NOT: BOLT-ERROR
+
+# CHECK-LABEL: <func_ret_t>:
+# CHECK: allocframe
+# CHECK: if (p0) dealloc_return
+
+# CHECK-LABEL: <func_ret_f>:
+# CHECK: allocframe
+# CHECK: if (!p0) dealloc_return
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ call func_ret_t
+ call func_ret_f
+ jumpr r31
+ .size _start, .-_start
+
+ .globl func_ret_t
+ .type func_ret_t, at function
+ .p2align 4
+func_ret_t:
+ allocframe(#0)
+ p0 = cmp.eq(r0, #0)
+ if (p0) dealloc_return
+ r0 = #1
+ r31:30 = dealloc_return(r30):raw
+ .size func_ret_t, .-func_ret_t
+
+ .globl func_ret_f
+ .type func_ret_f, at function
+ .p2align 4
+func_ret_f:
+ allocframe(#0)
+ p0 = cmp.eq(r0, #0)
+ if (!p0) dealloc_return
+ r0 = #0
+ r31:30 = dealloc_return(r30):raw
+ .size func_ret_f, .-func_ret_f
diff --git a/bolt/test/Hexagon/rewrite-const-extender.s b/bolt/test/Hexagon/rewrite-const-extender.s
new file mode 100644
index 0000000000000..b137e50c25341
--- /dev/null
+++ b/bolt/test/Hexagon/rewrite-const-extender.s
@@ -0,0 +1,33 @@
+## Verify that BOLT correctly handles constant extender (immext / A4_ext)
+## instructions. During disassembly BOLT sees A4_ext as a real instruction.
+## During emission, createBundle skips it because the MC code emitter
+## auto-regenerates immext from ## operands. If the skip logic breaks,
+## packets exceed 4 slots or double-extend.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck --check-prefix=BOLT %s
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# BOLT-NOT: BOLT-ERROR
+
+# CHECK-LABEL: <test_immext>:
+# CHECK: immext(
+# CHECK: r0 = add(r1,##0x10000)
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ call test_immext
+ jumpr r31
+ .size _start, .-_start
+
+ .globl test_immext
+ .type test_immext, at function
+ .p2align 4
+test_immext:
+ r0 = add(r1, ##65536)
+ jumpr r31
+ .size test_immext, .-test_immext
diff --git a/bolt/test/Hexagon/rewrite-double-extender.s b/bolt/test/Hexagon/rewrite-double-extender.s
new file mode 100644
index 0000000000000..761f3f01331c7
--- /dev/null
+++ b/bolt/test/Hexagon/rewrite-double-extender.s
@@ -0,0 +1,45 @@
+## Verify that BOLT can rewrite packets containing two constant extenders.
+## A packet with two extended immediates requires two immext (A4_ext)
+## instructions, totaling 4 words (the maximum packet size). The MC code
+## emitter auto-generates immext when encoding extended immediates, so
+## BOLT's createBundle must properly include both A4_ext instructions
+## from the disassembly.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck --check-prefix=BOLT %s
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# BOLT-NOT: BOLT-ERROR
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ { call test_double_ext }
+ { jumpr r31 }
+ .size _start, .-_start
+
+##============================================================================
+## Two extended immediates in a single packet: each generates an immext.
+## The packet occupies 4 words: immext + tfrsi + immext + tfrsi.
+##============================================================================
+# CHECK-LABEL: <test_double_ext>:
+# CHECK: immext
+# CHECK: r0 = ##
+# CHECK: immext
+# CHECK: r1 = ##
+
+ .globl test_double_ext
+ .type test_double_ext, at function
+ .p2align 4
+test_double_ext:
+ {
+ r0 = ##1234567
+ r1 = ##7654321
+ }
+ {
+ jumpr r31
+ }
+ .size test_double_ext, .-test_double_ext
diff --git a/bolt/test/Hexagon/rewrite-duplex.s b/bolt/test/Hexagon/rewrite-duplex.s
new file mode 100644
index 0000000000000..ecb4a443250d5
--- /dev/null
+++ b/bolt/test/Hexagon/rewrite-duplex.s
@@ -0,0 +1,58 @@
+## Verify that BOLT can fully rewrite a binary containing duplex
+## instructions through the complete pipeline (disassembly -> CFG ->
+## emit -> encode). The existing cfg-duplex.s only verifies CFG
+## construction; this test verifies that the duplex instructions
+## survive the full round-trip and produce correct output.
+##
+## Hexagon duplex instructions pack two sub-instructions into a single
+## 32-bit word (parse bits = 0b00). When the assembler packs eligible
+## pairs, the encoder creates duplex words that the disassembler
+## splits back into sub-instruction MCInst operands.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck --check-prefix=BOLT %s
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# BOLT-NOT: BOLT-ERROR
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ {
+ call test_duplex
+ }
+ // Duplex-eligible pair: small immediate load + return.
+ {
+ r0 = #0
+ jumpr r31
+ }
+ .size _start, .-_start
+
+##============================================================================
+## Function with multiple duplex-eligible instruction pairs.
+## Small immediate assignments and register transfers can pack as duplexes.
+##============================================================================
+# CHECK-LABEL: <test_duplex>:
+# CHECK: r0 = #0x1
+# CHECK: r1 = #0x2
+# CHECK: r0 = add(r0,r1)
+# CHECK: jumpr r31
+
+ .globl test_duplex
+ .type test_duplex, at function
+ .p2align 4
+test_duplex:
+ {
+ r0 = #1
+ r1 = #2
+ }
+ {
+ r0 = add(r0, r1)
+ }
+ {
+ jumpr r31
+ }
+ .size test_duplex, .-test_duplex
diff --git a/bolt/test/Hexagon/rewrite-endloop-nested.s b/bolt/test/Hexagon/rewrite-endloop-nested.s
new file mode 100644
index 0000000000000..f6297334d00da
--- /dev/null
+++ b/bolt/test/Hexagon/rewrite-endloop-nested.s
@@ -0,0 +1,58 @@
+## Verify that BOLT can fully rewrite a binary containing nested hardware
+## loops (loop0 inside loop1 with endloop0 and endloop1). Nested loops
+## exercise the endloop1 code path in createBundle, which requires a
+## minimum of 3 instructions in the endloop1 packet (compared to 2 for
+## endloop0). BOLT must preserve both loop-end markers through the
+## round-trip.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck --check-prefix=BOLT %s
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# BOLT-NOT: BOLT-ERROR
+
+# CHECK-LABEL: <test_nested_loop>:
+# CHECK: loop1(
+# CHECK: loop0(
+# CHECK: :endloop0
+# CHECK: :endloop1
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ {
+ call test_nested_loop
+ }
+ {
+ jumpr r31
+ }
+ .size _start, .-_start
+
+ .globl test_nested_loop
+ .type test_nested_loop, at function
+ .p2align 4
+test_nested_loop:
+ {
+ loop1(.Louter, #3)
+ }
+.Louter:
+ {
+ loop0(.Linner, #4)
+ }
+.Linner:
+ {
+ r0 = add(r0, #1)
+ nop
+ }:endloop0
+ {
+ r1 = add(r1, #1)
+ nop
+ nop
+ }:endloop1
+ {
+ jumpr r31
+ }
+ .size test_nested_loop, .-test_nested_loop
diff --git a/bolt/test/Hexagon/rewrite-full-packet-mixed.s b/bolt/test/Hexagon/rewrite-full-packet-mixed.s
new file mode 100644
index 0000000000000..a61437ba90166
--- /dev/null
+++ b/bolt/test/Hexagon/rewrite-full-packet-mixed.s
@@ -0,0 +1,129 @@
+## Verify that BOLT can rewrite dense multi-instruction VLIW packets
+## containing a mix of instruction types. Real Hexagon code commonly fills
+## all 4 packet slots with ALU, load, store, and branch operations. This
+## tests that the packet accumulation and flush logic in BinaryEmitter
+## handles full packets correctly across different instruction combinations.
+##
+## Note: the assembler may reorder instructions within a packet for slot
+## assignment, and may create duplex encodings for eligible pairs. The
+## CHECK-DAG patterns allow any ordering within a packet.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck --check-prefix=BOLT %s
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# BOLT-NOT: BOLT-ERROR
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ { call test_alu_load_branch }
+ { call test_alu_store }
+ { call test_triple_alu }
+ { call test_load_alu_store }
+ { jumpr r31 }
+ .size _start, .-_start
+
+##============================================================================
+## Full 4-slot packet: 2 ALU + load + conditional branch.
+## Tests that all 4 slots survive the BOLT round-trip in a packet
+## containing ALU, memory, and control-flow operations.
+## The assembler may reorder instructions to satisfy slot constraints.
+##============================================================================
+# CHECK-LABEL: <test_alu_load_branch>:
+# CHECK-DAG: add(r1,r2)
+# CHECK-DAG: and(r4,r5)
+# CHECK-DAG: memw(r7+#0x0)
+# CHECK-DAG: if (p0) jump:nt
+
+ .globl test_alu_load_branch
+ .type test_alu_load_branch, at function
+ .p2align 4
+test_alu_load_branch:
+ {
+ r0 = add(r1, r2)
+ r3 = and(r4, r5)
+ r6 = memw(r7 + #0)
+ if (p0) jump:nt .Lhot_alb
+ }
+.Lcold_alb:
+ { r0 = #0 }
+ { jumpr r31 }
+.Lhot_alb:
+ { r0 = #1 }
+ { jumpr r31 }
+ .size test_alu_load_branch, .-test_alu_load_branch
+
+##============================================================================
+## 3-instruction packet: 2 ALU + store (no branch, falls through).
+##============================================================================
+# CHECK-LABEL: <test_alu_store>:
+# CHECK-DAG: add(r1,r2)
+# CHECK-DAG: sub(r4,r5)
+# CHECK-DAG: memw(r6+#0x0) = r7
+
+ .globl test_alu_store
+ .type test_alu_store, at function
+ .p2align 4
+test_alu_store:
+ {
+ r0 = add(r1, r2)
+ r3 = sub(r4, r5)
+ memw(r6 + #0) = r7
+ }
+ {
+ jumpr r31
+ }
+ .size test_alu_store, .-test_alu_store
+
+##============================================================================
+## Full 4-slot packet: 3 ALU + unconditional jump.
+##============================================================================
+# CHECK-LABEL: <test_triple_alu>:
+# CHECK-DAG: add(r1,r2)
+# CHECK-DAG: and(r4,r5)
+# CHECK-DAG: or(r7,r8)
+# CHECK: jumpr r31
+
+ .globl test_triple_alu
+ .type test_triple_alu, at function
+ .p2align 4
+test_triple_alu:
+ {
+ r0 = add(r1, r2)
+ r3 = and(r4, r5)
+ r6 = or(r7, r8)
+ jump .Ltriple_done
+ }
+.Ltriple_done:
+ {
+ jumpr r31
+ }
+ .size test_triple_alu, .-test_triple_alu
+
+##============================================================================
+## 3-instruction packet: load + ALU + store using different registers.
+## A typical loop body pattern. The assembler may pack the load and
+## store into a duplex instruction.
+##============================================================================
+# CHECK-LABEL: <test_load_alu_store>:
+# CHECK-DAG: add(r3,#0x1)
+# CHECK-DAG: memw(r2+#0x0)
+# CHECK-DAG: memw(r5+#0x0) = r6
+
+ .globl test_load_alu_store
+ .type test_load_alu_store, at function
+ .p2align 4
+test_load_alu_store:
+ {
+ r0 = memw(r2 + #0)
+ r4 = add(r3, #1)
+ memw(r5 + #0) = r6
+ }
+ {
+ jumpr r31
+ }
+ .size test_load_alu_store, .-test_load_alu_store
diff --git a/bolt/test/Hexagon/rewrite-hvx-packet.s b/bolt/test/Hexagon/rewrite-hvx-packet.s
new file mode 100644
index 0000000000000..fd19b8834348e
--- /dev/null
+++ b/bolt/test/Hexagon/rewrite-hvx-packet.s
@@ -0,0 +1,131 @@
+## Verify that BOLT can fully rewrite a binary containing HVX (Hexagon Vector
+## eXtension) instructions in VLIW packets. HVX vector loads, stores, ALU
+## operations, and .new value stores must survive the disassembly-CFG-emit
+## round-trip. Real Hexagon binaries make extensive use of HVX, so this is
+## a critical code path for BOLT.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl \
+# RUN: -mcpu=hexagonv68 -mhvx %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck --check-prefix=BOLT %s
+# RUN: llvm-objdump -d --mattr=+hvx %t.bolt | FileCheck %s
+
+# BOLT-NOT: BOLT-ERROR
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ { call test_hvx_basic }
+ { call test_hvx_newvalue }
+ { call test_hvx_predicated }
+ { call test_hvx_mixed }
+ { jumpr r31 }
+ .size _start, .-_start
+
+##============================================================================
+## Basic HVX: vector load, ALU, and store in separate packets.
+##============================================================================
+# CHECK-LABEL: <test_hvx_basic>:
+# CHECK: vmem(r0+#0x0)
+# CHECK: vmem(r1+#0x0)
+# CHECK: vadd(v0.w,v1.w)
+# CHECK: vmem(r2+#0x0) = v2
+
+ .globl test_hvx_basic
+ .type test_hvx_basic, at function
+ .p2align 4
+test_hvx_basic:
+ {
+ v0 = vmem(r0 + #0)
+ }
+ {
+ v1 = vmem(r1 + #0)
+ }
+ {
+ v2.w = vadd(v0.w, v1.w)
+ }
+ {
+ vmem(r2 + #0) = v2
+ }
+ {
+ jumpr r31
+ }
+ .size test_hvx_basic, .-test_hvx_basic
+
+##============================================================================
+## HVX .new value store: producer and consumer in the same packet.
+## The createBundle code must preserve the producer-consumer ordering
+## for the MC encoder's .new distance encoding.
+##============================================================================
+# CHECK-LABEL: <test_hvx_newvalue>:
+# CHECK: vadd(v0.w,v1.w)
+# CHECK: vmem(r2+#0x0) = v2.new
+
+ .globl test_hvx_newvalue
+ .type test_hvx_newvalue, at function
+ .p2align 4
+test_hvx_newvalue:
+ {
+ v2.w = vadd(v0.w, v1.w)
+ vmem(r2 + #0) = v2.new
+ }
+ {
+ jumpr r31
+ }
+ .size test_hvx_newvalue, .-test_hvx_newvalue
+
+##============================================================================
+## HVX with vector predicates: vector compare producing Q predicate,
+## then predicated vector store.
+##============================================================================
+# CHECK-LABEL: <test_hvx_predicated>:
+# CHECK: vcmp.eq(v0.w,v1.w)
+# CHECK: if (q0) vmem(r2+#0x0) = v0
+
+ .globl test_hvx_predicated
+ .type test_hvx_predicated, at function
+ .p2align 4
+test_hvx_predicated:
+ {
+ q0 = vcmp.eq(v0.w, v1.w)
+ }
+ {
+ if (q0) vmem(r2 + #0) = v0
+ }
+ {
+ jumpr r31
+ }
+ .size test_hvx_predicated, .-test_hvx_predicated
+
+##============================================================================
+## Mixed scalar and HVX operations in the same function.
+## Scalar ALU interleaved with vector load/ALU/store.
+##============================================================================
+# CHECK-LABEL: <test_hvx_mixed>:
+# CHECK: r0 = add(r0,#0x80)
+# CHECK: vmem(r0+#0x0)
+# CHECK: vadd(v0.w,v0.w)
+# CHECK: vmem(r0+#0x0) = v1
+
+ .globl test_hvx_mixed
+ .type test_hvx_mixed, at function
+ .p2align 4
+test_hvx_mixed:
+ {
+ r0 = add(r0, #128)
+ }
+ {
+ v0 = vmem(r0 + #0)
+ }
+ {
+ v1.w = vadd(v0.w, v0.w)
+ }
+ {
+ vmem(r0 + #0) = v1
+ }
+ {
+ jumpr r31
+ }
+ .size test_hvx_mixed, .-test_hvx_mixed
diff --git a/bolt/test/Hexagon/rewrite-newvalue-jump.s b/bolt/test/Hexagon/rewrite-newvalue-jump.s
new file mode 100644
index 0000000000000..4ae387eabc781
--- /dev/null
+++ b/bolt/test/Hexagon/rewrite-newvalue-jump.s
@@ -0,0 +1,40 @@
+## Verify that BOLT can fully rewrite new-value compare-and-jump (NCJ)
+## instructions where a register value is loaded and immediately used in
+## a compare-and-jump within the same packet. The MC encoder must find
+## the producer instruction's position within the bundle for the .new
+## operand distance encoding.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck --check-prefix=BOLT %s
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# BOLT-NOT: BOLT-ERROR
+
+# CHECK-LABEL: <test_nvjump>:
+# CHECK: r0 = memw(r1+#0x0)
+# CHECK: if (cmp.eq(r0.new,#0x0)) jump:nt
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ call test_nvjump
+ jumpr r31
+ .size _start, .-_start
+
+ .globl test_nvjump
+ .type test_nvjump, at function
+ .p2align 4
+test_nvjump:
+ {
+ r0 = memw(r1+#0)
+ if (cmp.eq(r0.new,#0)) jump:nt .Ltarget
+ }
+ r0 = #1
+ jumpr r31
+.Ltarget:
+ r0 = #0
+ jumpr r31
+ .size test_nvjump, .-test_nvjump
diff --git a/bolt/test/Hexagon/rewrite-newvalue-store.s b/bolt/test/Hexagon/rewrite-newvalue-store.s
new file mode 100644
index 0000000000000..fb468ceafa6ee
--- /dev/null
+++ b/bolt/test/Hexagon/rewrite-newvalue-store.s
@@ -0,0 +1,35 @@
+## Verify that BOLT can fully rewrite a binary containing new-value store
+## operations where the store operand is produced and consumed within the
+## same VLIW packet. BOLT must preserve producer-consumer ordering within
+## the BUNDLE MCInst for the MC code emitter's .new distance encoding.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck --check-prefix=BOLT %s
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# BOLT-NOT: BOLT-ERROR
+
+# CHECK-LABEL: <test_nvstore>:
+# CHECK: r0 = add(r1,r2)
+# CHECK-NEXT: memw(r3+#0x0) = r0.new
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ call test_nvstore
+ jumpr r31
+ .size _start, .-_start
+
+ .globl test_nvstore
+ .type test_nvstore, at function
+ .p2align 4
+test_nvstore:
+ {
+ r0 = add(r1, r2)
+ memw(r3+#0) = r0.new
+ }
+ jumpr r31
+ .size test_nvstore, .-test_nvstore
diff --git a/bolt/test/Hexagon/rewrite-pair-registers.s b/bolt/test/Hexagon/rewrite-pair-registers.s
new file mode 100644
index 0000000000000..ffce2df3c4ccd
--- /dev/null
+++ b/bolt/test/Hexagon/rewrite-pair-registers.s
@@ -0,0 +1,64 @@
+## Verify that BOLT can rewrite double-register (register pair) operations.
+## Hexagon uses register pairs (r1:0, r3:2, etc.) for 64-bit operations
+## including double-word loads, stores, and combine instructions.
+
+# RUN: llvm-mc -filetype=obj -triple=hexagon-unknown-linux-musl %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe -o %t.bolt 2>&1 | FileCheck --check-prefix=BOLT %s
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# BOLT-NOT: BOLT-ERROR
+
+ .text
+ .globl _start
+ .type _start, at function
+ .p2align 4
+_start:
+ { call test_combine }
+ { call test_memd }
+ { jumpr r31 }
+ .size _start, .-_start
+
+##============================================================================
+## Register pair combine operations.
+##============================================================================
+# CHECK-LABEL: <test_combine>:
+# CHECK: r1:0 = combine(r2,r3)
+# CHECK: r5:4 = combine(#0x1,#0x0)
+
+ .globl test_combine
+ .type test_combine, at function
+ .p2align 4
+test_combine:
+ {
+ r1:0 = combine(r2, r3)
+ }
+ {
+ r5:4 = combine(#1, #0)
+ }
+ {
+ jumpr r31
+ }
+ .size test_combine, .-test_combine
+
+##============================================================================
+## Double-word load and store (memd) with register pairs.
+##============================================================================
+# CHECK-LABEL: <test_memd>:
+# CHECK: r1:0 = memd(r2+#0x0)
+# CHECK: memd(r3+#0x0) = r1:0
+
+ .globl test_memd
+ .type test_memd, at function
+ .p2align 4
+test_memd:
+ {
+ r1:0 = memd(r2 + #0)
+ }
+ {
+ memd(r3 + #0) = r1:0
+ }
+ {
+ jumpr r31
+ }
+ .size test_memd, .-test_memd
More information about the llvm-commits
mailing list