[llvm] [BOLT][Hexagon] Add Hexagon MCPlusBuilder, relocations (PR #192189)

Brian Cain via llvm-commits llvm-commits at lists.llvm.org
Tue Apr 14 22:56:28 PDT 2026


https://github.com/androm3da created https://github.com/llvm/llvm-project/pull/192189

 Add the Hexagon target backend for BOLT, enabling binary analysis and optimization of Hexagon ELF executables.


>From d4aac017e20f019053549d8a38c0bd243a361cbf 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 572002accfb564f67e450db585e4b9b965a05f22 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               |   5 +
 bolt/lib/Rewrite/RewriteInstance.cpp          |   7 +-
 bolt/lib/Target/Hexagon/CMakeLists.txt        |  34 +
 .../Target/Hexagon/HexagonMCPlusBuilder.cpp   | 635 ++++++++++++++++++
 7 files changed, 707 insertions(+), 2 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 f8a0be0418433..c4ae7b445dc8a 100644
--- a/bolt/include/bolt/Core/BinaryContext.h
+++ b/bolt/include/bolt/Core/BinaryContext.h
@@ -887,6 +887,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 0f6076688b65d..1bcc986d26df1 100644
--- a/bolt/include/bolt/Core/MCPlusBuilder.h
+++ b/bolt/include/bolt/Core/MCPlusBuilder.h
@@ -2523,6 +2523,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;
@@ -2546,6 +2563,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 554efe671fd5b..bc0cead1c17e0 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -206,6 +206,11 @@ Expected<std::unique_ptr<BinaryContext>> BinaryContext::createBinaryContext(
     FeaturesStr = Features->getString();
     break;
   }
+  case llvm::Triple::hexagon:
+    ArchName = "hexagon";
+    if (Features)
+      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 b629536f08c8f..cd0b4ee64adb8 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -368,6 +368,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");
 }
 
@@ -2613,7 +2618,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..1301a8ae1312e
--- /dev/null
+++ b/bolt/lib/Target/Hexagon/HexagonMCPlusBuilder.cpp
@@ -0,0 +1,635 @@
+//===- 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));
+    }
+
+    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 5acbd8a58aea04f9f59d9cf7031030b53e0a9c42 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 | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index c5aefe685de34..f001e9cd53d74 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,25 @@ 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 = packet end,
+    //   0b10 = hardware loop end (handled in hwloop support),
+    //   0b01 = not end (middle of packet),
+    //   0b00 = 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);
+      }
+    }
+
     addInstruction(Offset, std::move(Instruction));
   }
 
@@ -1828,6 +1848,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;

>From aee2b72c2f52ab7c1db1454107066d95e0b8a11a 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 | 126 +++++++++++++++++++++++++++++++-
 1 file changed, 124 insertions(+), 2 deletions(-)

diff --git a/bolt/lib/Core/BinaryEmitter.cpp b/bolt/lib/Core/BinaryEmitter.cpp
index 49604542cc7da..c102205afa167 100644
--- a/bolt/lib/Core/BinaryEmitter.cpp
+++ b/bolt/lib/Core/BinaryEmitter.cpp
@@ -454,18 +454,91 @@ 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";
+    });
+    MCInst Bundle = BC.MIB->createBundle(Streamer.getContext(), HexPacket);
+    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 +572,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 +601,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 c380a31275647e2fe69a5fa2f7b0bbdb34533e5c 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-newvalue-jump.s  |  40 ++++++++
 bolt/test/Hexagon/rewrite-newvalue-store.s |  35 +++++++
 12 files changed, 526 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-newvalue-jump.s
 create mode 100644 bolt/test/Hexagon/rewrite-newvalue-store.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-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



More information about the llvm-commits mailing list