[llvm] [RISCV] Mark QC Instructions for Relaxation (PR #188671)

Sam Elliott via llvm-commits llvm-commits at lists.llvm.org
Thu May 7 14:04:17 PDT 2026


https://github.com/lenary updated https://github.com/llvm/llvm-project/pull/188671

>From 190da79f81b3ec64e7d5bdafd286530712b5b659 Mon Sep 17 00:00:00 2001
From: Sam Elliott <aelliott at qti.qualcomm.com>
Date: Fri, 6 Mar 2026 23:28:35 -0800
Subject: [PATCH 1/4] [RISCV] Mark QC Instructions for Relaxation

This adds a pass and the infrastructure to mark specific Loads/Stores as
eligible for a new relaxation we are specifying in the Qualcomm psABI
extensions.
---
 .../ELFRelocs/RISCV_nonstandard.def           |   2 +
 .../Target/RISCV/AsmParser/RISCVAsmParser.cpp |  11 +
 llvm/lib/Target/RISCV/CMakeLists.txt          |   1 +
 .../RISCV/MCTargetDesc/RISCVAsmBackend.cpp    |  12 +
 .../RISCV/MCTargetDesc/RISCVBaseInfo.cpp      |   1 +
 .../Target/RISCV/MCTargetDesc/RISCVBaseInfo.h |   2 +
 .../MCTargetDesc/RISCVELFObjectWriter.cpp     |   4 +
 .../RISCV/MCTargetDesc/RISCVFixupKinds.h      |   3 +
 .../RISCV/MCTargetDesc/RISCVInstPrinter.cpp   |   1 +
 .../RISCV/MCTargetDesc/RISCVMCAsmInfo.h       |   1 +
 .../RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp | 100 ++++++
 .../Target/RISCV/MCTargetDesc/RISCVMCExpr.cpp |   3 +
 llvm/lib/Target/RISCV/RISCV.h                 |   3 +
 llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp     |   3 +
 llvm/lib/Target/RISCV/RISCVInstrInfo.cpp      |   4 +-
 llvm/lib/Target/RISCV/RISCVInstrInfoC.td      |   9 +-
 llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td   | 113 +++++++
 llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp | 228 +++++++++++++
 llvm/lib/Target/RISCV/RISCVTargetMachine.cpp  |   3 +
 llvm/test/CodeGen/RISCV/O0-pipeline.ll        |   1 +
 llvm/test/CodeGen/RISCV/O3-pipeline.ll        |   1 +
 llvm/test/CodeGen/RISCV/qc-relax-marking.ll   | 310 ++++++++++++++++++
 22 files changed, 814 insertions(+), 2 deletions(-)
 create mode 100644 llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp
 create mode 100644 llvm/test/CodeGen/RISCV/qc-relax-marking.ll

diff --git a/llvm/include/llvm/BinaryFormat/ELFRelocs/RISCV_nonstandard.def b/llvm/include/llvm/BinaryFormat/ELFRelocs/RISCV_nonstandard.def
index fb0e29b2aaae5..ba81072fc5cb4 100644
--- a/llvm/include/llvm/BinaryFormat/ELFRelocs/RISCV_nonstandard.def
+++ b/llvm/include/llvm/BinaryFormat/ELFRelocs/RISCV_nonstandard.def
@@ -26,6 +26,8 @@ ELF_RISCV_NONSTANDARD_RELOC(QUALCOMM, R_RISCV_QC_ABS20_U,    192)
 ELF_RISCV_NONSTANDARD_RELOC(QUALCOMM, R_RISCV_QC_E_BRANCH,   193)
 ELF_RISCV_NONSTANDARD_RELOC(QUALCOMM, R_RISCV_QC_E_32,       194)
 ELF_RISCV_NONSTANDARD_RELOC(QUALCOMM, R_RISCV_QC_E_CALL_PLT, 195)
+ELF_RISCV_NONSTANDARD_RELOC(QUALCOMM, R_RISCV_QC_ACCESS_16,  196)
+ELF_RISCV_NONSTANDARD_RELOC(QUALCOMM, R_RISCV_QC_ACCESS_32,  197)
 
 // Andes Nonstandard Relocations
 // Calculation: S + A - P (10-bit PC-relative branch offset)
diff --git a/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp b/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
index 0d9b9b06f9973..0413ddea7423a 100644
--- a/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
+++ b/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
@@ -619,6 +619,17 @@ struct RISCVOperand final : public MCParsedAsmOperand {
            VK == ELF::R_RISCV_TLSDESC_CALL;
   }
 
+  bool isQCAccessSymbol() const {
+    int64_t Imm;
+    // Must be of 'immediate' type but not a constant.
+    if (!isExpr() || evaluateConstantExpr(getExpr(), Imm))
+      return false;
+
+    RISCV::Specifier VK = RISCV::S_None;
+    return RISCVAsmParser::classifySymbolRef(getExpr(), VK) &&
+           VK == RISCV::S_QC_ACCESS;
+  }
+
   bool isCSRSystemRegister() const { return isSystemRegister(); }
 
   // If the last operand of the vsetvli/vsetvli instruction is a constant
diff --git a/llvm/lib/Target/RISCV/CMakeLists.txt b/llvm/lib/Target/RISCV/CMakeLists.txt
index 3b529c1471d54..bfe4a72f7b37b 100644
--- a/llvm/lib/Target/RISCV/CMakeLists.txt
+++ b/llvm/lib/Target/RISCV/CMakeLists.txt
@@ -61,6 +61,7 @@ add_llvm_target(RISCVCodeGen
   RISCVPostRAExpandPseudoInsts.cpp
   RISCVPromoteConstant.cpp
   RISCVPushPopOptimizer.cpp
+  RISCVQCRelaxMarking.cpp
   RISCVRedundantCopyElimination.cpp
   RISCVRegisterInfo.cpp
   RISCVSelectionDAGInfo.cpp
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
index c2d1b7cbddcee..2b3161a8aa33b 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
@@ -88,10 +88,13 @@ MCFixupKindInfo RISCVAsmBackend::getFixupKindInfo(MCFixupKind Kind) const {
       {"fixup_riscv_call", 0, 64, 0},
       {"fixup_riscv_call_plt", 0, 64, 0},
 
+      // Qualcomm fixups
       {"fixup_riscv_qc_e_branch", 0, 48, 0},
       {"fixup_riscv_qc_e_32", 16, 32, 0},
       {"fixup_riscv_qc_abs20_u", 0, 32, 0},
       {"fixup_riscv_qc_e_call_plt", 0, 48, 0},
+      {"fixup_qc_access_16", 0, 0, 0},
+      {"fixup_qc_access_32", 0, 0, 0},
 
       // Andes fixups
       {"fixup_riscv_nds_branch_10", 0, 32, 0},
@@ -638,6 +641,9 @@ static uint64_t adjustFixupValue(const MCFixup &Fixup, uint64_t Value,
             (Bit15_13 << 17) | (Bit4_1 << 8) | (Bit11 << 7);
     return Value;
   }
+  case RISCV::fixup_qc_access_16:
+  case RISCV::fixup_qc_access_32:
+    return 0;
   case RISCV::fixup_riscv_nds_branch_10: {
     if (!isInt<11>(Value))
       Ctx.reportError(Fixup.getLoc(), "fixup value out of range");
@@ -737,6 +743,10 @@ std::optional<bool> RISCVAsmBackend::evaluateFixup(const MCFragment &,
   default:
     // Use default handling for `Value` and `IsResolved`.
     return {};
+  case RISCV::fixup_qc_access_16:
+  case RISCV::fixup_qc_access_32:
+    // Never resolved in the assembler
+    return false;
   case RISCV::fixup_riscv_pcrel_lo12_i:
   case RISCV::fixup_riscv_pcrel_lo12_s: {
     AUIPCFixup =
@@ -787,6 +797,8 @@ void RISCVAsmBackend::maybeAddVendorReloc(const MCFragment &F,
   case RISCV::fixup_riscv_qc_abs20_u:
   case RISCV::fixup_riscv_qc_e_32:
   case RISCV::fixup_riscv_qc_e_call_plt:
+  case RISCV::fixup_qc_access_16:
+  case RISCV::fixup_qc_access_32:
     VendorIdentifier = "QUALCOMM";
     break;
   case RISCV::fixup_riscv_nds_branch_10:
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.cpp
index 62cd6cca49c0e..7f43e47a51b3f 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.cpp
@@ -13,6 +13,7 @@
 
 #include "RISCVBaseInfo.h"
 #include "RISCVInstrInfo.h"
+#include "RISCVMCAsmInfo.h"
 #include "llvm/MC/MCInst.h"
 #include "llvm/MC/MCRegisterInfo.h"
 #include "llvm/MC/MCSubtargetInfo.h"
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
index b5aade96d4093..78ea685afa788 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
@@ -78,6 +78,7 @@ enum OperandType : unsigned {
   OPERAND_SIMM10_LSB0000_NONZERO,
   OPERAND_SIMM10_UNSIGNED,
   OPERAND_SIMM11,
+  OPERAND_SIMM12,
   OPERAND_SIMM12_LSB00000,
   OPERAND_SIMM16,
   OPERAND_SIMM16_NONZERO,
@@ -471,6 +472,7 @@ enum {
   MO_TLSDESC_LOAD_LO = 14,
   MO_TLSDESC_ADD_LO = 15,
   MO_TLSDESC_CALL = 16,
+  MO_QC_ACCESS = 17,
 
   // Used to differentiate between target-specific "direct" flags and "bitmask"
   // flags. A machine operand can only have one "direct" flag, but can have
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFObjectWriter.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFObjectWriter.cpp
index 2885e3cca8722..e47102b508cda 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFObjectWriter.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFObjectWriter.cpp
@@ -142,6 +142,10 @@ unsigned RISCVELFObjectWriter::getRelocType(const MCFixup &Fixup,
     return ELF::R_RISCV_QC_E_32;
   case RISCV::fixup_riscv_qc_abs20_u:
     return ELF::R_RISCV_QC_ABS20_U;
+  case RISCV::fixup_qc_access_16:
+    return ELF::R_RISCV_QC_ACCESS_16;
+  case RISCV::fixup_qc_access_32:
+    return ELF::R_RISCV_QC_ACCESS_32;
   }
 }
 
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVFixupKinds.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVFixupKinds.h
index a2b75e4a42e76..8104ee0291b34 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVFixupKinds.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVFixupKinds.h
@@ -59,6 +59,9 @@ enum Fixups {
   // 32-bit fixup for symbol references in the 48-bit qc.j/qc.jal instructions
   fixup_riscv_qc_e_call_plt,
 
+  fixup_qc_access_16,
+  fixup_qc_access_32,
+
   // Andes specific fixups
   // 10-bit fixup for symbol references in the xandesperf branch instruction
   fixup_riscv_nds_branch_10,
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVInstPrinter.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVInstPrinter.cpp
index b937d81bb98cb..a1c148888c9b3 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVInstPrinter.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVInstPrinter.cpp
@@ -12,6 +12,7 @@
 
 #include "RISCVInstPrinter.h"
 #include "RISCVBaseInfo.h"
+#include "RISCVMCAsmInfo.h"
 #include "llvm/MC/MCAsmInfo.h"
 #include "llvm/MC/MCExpr.h"
 #include "llvm/MC/MCInst.h"
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCAsmInfo.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCAsmInfo.h
index d030c3b5cf867..b49b4383b8e1b 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCAsmInfo.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCAsmInfo.h
@@ -48,6 +48,7 @@ enum {
   // Vendor-specific relocation types might conflict across vendors.
   // Refer to them using Specifier constants.
   S_QC_ABS20,
+  S_QC_ACCESS,
 };
 
 Specifier parseSpecifierName(StringRef name);
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
index 971b881e1ab28..0c531498da278 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
@@ -72,6 +72,10 @@ class RISCVMCCodeEmitter : public MCCodeEmitter {
                              SmallVectorImpl<MCFixup> &Fixups,
                              const MCSubtargetInfo &STI, unsigned Size) const;
 
+  void expandPseudoQCAccess(const MCInst &MI, SmallVectorImpl<char> &CB,
+                            SmallVectorImpl<MCFixup> &Fixups,
+                            const MCSubtargetInfo &STI) const;
+
   /// TableGen'erated function for getting the binary encoding for an
   /// instruction.
   uint64_t getBinaryCodeForInstr(const MCInst &MI,
@@ -420,6 +424,77 @@ void RISCVMCCodeEmitter::expandQCLongCondBrImm(const MCInst &MI,
   }
 }
 
+void RISCVMCCodeEmitter::expandPseudoQCAccess(
+    const MCInst &MI, SmallVectorImpl<char> &CB,
+    SmallVectorImpl<MCFixup> &Fixups, const MCSubtargetInfo &STI) const {
+  unsigned AccessOpc;
+
+  switch (MI.getOpcode()) {
+#define QC_ACCESS_CASE(_Suffix)                                                \
+  case RISCV::PseudoQCAccess##_Suffix:                                         \
+    AccessOpc = RISCV::_Suffix;                                                \
+    break;
+    // clang-format off
+  QC_ACCESS_CASE(LB)
+  QC_ACCESS_CASE(LBU)
+  QC_ACCESS_CASE(LH)
+  QC_ACCESS_CASE(LHU)
+  QC_ACCESS_CASE(LW)
+  QC_ACCESS_CASE(SB)
+  QC_ACCESS_CASE(SH)
+  QC_ACCESS_CASE(SW)
+  QC_ACCESS_CASE(C_LBU)
+  QC_ACCESS_CASE(C_LH)
+  QC_ACCESS_CASE(C_LHU)
+  QC_ACCESS_CASE(C_LW)
+  QC_ACCESS_CASE(C_SB)
+  QC_ACCESS_CASE(C_SH)
+  QC_ACCESS_CASE(C_SW)
+  // clang-format on
+  default:
+    llvm_unreachable("Unhandled QC Access Opcode");
+  };
+
+  MCInst TmpAccess = MCInstBuilder(AccessOpc)
+                         .addOperand(MI.getOperand(0))
+                         .addOperand(MI.getOperand(1))
+                         .addOperand(MI.getOperand(2));
+  unsigned Size = MCII.get(AccessOpc).getSize();
+  uint16_t FixupKind;
+  switch (Size) {
+  default:
+    llvm_unreachable("Unhandled QC Access Instruction Size");
+  case 2: {
+    uint16_t AccessBinary = getBinaryCodeForInstr(TmpAccess, Fixups, STI);
+    support::endian::write(CB, AccessBinary, llvm::endianness::little);
+    FixupKind = RISCV::fixup_qc_access_16;
+    break;
+  }
+  case 4: {
+    uint32_t AccessBinary = getBinaryCodeForInstr(TmpAccess, Fixups, STI);
+    support::endian::write(CB, AccessBinary, llvm::endianness::little);
+    FixupKind = RISCV::fixup_qc_access_32;
+    break;
+  }
+  }
+  // Only emit the qc.access fixup if linker relaxation is enabled. The pass has
+  // already checked for this before using the Pseudos, but the user may have
+  // written the instructions directly in assembly.
+  if (!STI.hasFeature(RISCV::FeatureRelax))
+    return;
+
+  MCOperand AccessSymbol = MI.getOperand(3);
+  assert(AccessSymbol.isExpr() && "Expected expression in PseudoQCAccess");
+
+  const auto *AccessExpr = dyn_cast<MCSpecifierExpr>(AccessSymbol.getExpr());
+  assert(AccessExpr && AccessExpr->getSpecifier() == RISCV::S_QC_ACCESS &&
+         "Expected qc.access specifier on symbol");
+
+  addFixup(Fixups, /*Offset=*/0, AccessExpr, FixupKind);
+  // The added fixup is always linker relaxable.
+  Fixups.back().setLinkerRelaxable();
+}
+
 void RISCVMCCodeEmitter::encodeInstruction(const MCInst &MI,
                                            SmallVectorImpl<char> &CB,
                                            SmallVectorImpl<MCFixup> &Fixups,
@@ -478,6 +553,24 @@ void RISCVMCCodeEmitter::encodeInstruction(const MCInst &MI,
     expandTLSDESCCall(MI, CB, Fixups, STI);
     MCNumEmitted += 1;
     return;
+  case RISCV::PseudoQCAccessLB:
+  case RISCV::PseudoQCAccessLBU:
+  case RISCV::PseudoQCAccessLH:
+  case RISCV::PseudoQCAccessLHU:
+  case RISCV::PseudoQCAccessLW:
+  case RISCV::PseudoQCAccessSB:
+  case RISCV::PseudoQCAccessSH:
+  case RISCV::PseudoQCAccessSW:
+  case RISCV::PseudoQCAccessC_LBU:
+  case RISCV::PseudoQCAccessC_LH:
+  case RISCV::PseudoQCAccessC_LHU:
+  case RISCV::PseudoQCAccessC_LW:
+  case RISCV::PseudoQCAccessC_SB:
+  case RISCV::PseudoQCAccessC_SH:
+  case RISCV::PseudoQCAccessC_SW:
+    expandPseudoQCAccess(MI, CB, Fixups, STI);
+    MCNumEmitted += 1;
+    return;
   }
 
   switch (Size) {
@@ -649,6 +742,12 @@ uint64_t RISCVMCCodeEmitter::getImmOpValue(const MCInst &MI, unsigned OpNo,
       // encounter it here is an error.
       llvm_unreachable(
           "ELF::R_RISCV_TPREL_ADD should not represent an instruction operand");
+    case RISCV::S_QC_ACCESS:
+      // The same logic for tprel_add applies to S_QC_ACCESS, for similar
+      // reasons, but we use a specifier becuase %qc.access() gets expanded
+      // differently depending on the underlying instruction.
+      llvm_unreachable(
+          "S_QC_ACCESS should not represent an instruction operand");
     case RISCV::S_LO:
       if (MIFrm == RISCVII::InstFormatI)
         FixupKind = RISCV::fixup_riscv_lo12_i;
@@ -700,6 +799,7 @@ uint64_t RISCVMCCodeEmitter::getImmOpValue(const MCInst &MI, unsigned OpNo,
       FixupKind = RISCV::fixup_riscv_qc_abs20_u;
       RelaxCandidate = true;
       break;
+
     case ELF::R_RISCV_GOT_HI20:
     case ELF::R_RISCV_TPREL_HI20:
     case ELF::R_RISCV_TLSDESC_HI20:
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCExpr.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCExpr.cpp
index e6366af9163e6..cf9db21a47dcb 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCExpr.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCExpr.cpp
@@ -37,6 +37,7 @@ RISCV::Specifier RISCV::parseSpecifierName(StringRef name) {
       .Case("tlsdesc_add_lo", ELF::R_RISCV_TLSDESC_ADD_LO12)
       .Case("tlsdesc_call", ELF::R_RISCV_TLSDESC_CALL)
       .Case("qc.abs20", RISCV::S_QC_ABS20)
+      .Case("qc.access", RISCV::S_QC_ACCESS)
       // Used in data directives
       .Case("pltpcrel", ELF::R_RISCV_PLT32)
       .Case("gotpcrel", ELF::R_RISCV_GOT32_PCREL)
@@ -85,6 +86,8 @@ StringRef RISCV::getSpecifierName(Specifier S) {
     return "pltpcrel";
   case RISCV::S_QC_ABS20:
     return "qc.abs20";
+  case RISCV::S_QC_ACCESS:
+    return "qc.access";
   }
   llvm_unreachable("Invalid ELF symbol kind");
 }
diff --git a/llvm/lib/Target/RISCV/RISCV.h b/llvm/lib/Target/RISCV/RISCV.h
index 048db205e2289..76c373710a207 100644
--- a/llvm/lib/Target/RISCV/RISCV.h
+++ b/llvm/lib/Target/RISCV/RISCV.h
@@ -132,6 +132,9 @@ void initializeRISCVVLOptimizerPass(PassRegistry &);
 FunctionPass *createRISCVVMV0EliminationPass();
 void initializeRISCVVMV0EliminationPass(PassRegistry &);
 
+FunctionPass *createRISCVQCRelaxMarkingPass();
+void initializeRISCVQCRelaxMarkingPass(PassRegistry &);
+
 void initializeRISCVAsmPrinterPass(PassRegistry &);
 } // namespace llvm
 
diff --git a/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp b/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
index eb15227a72a83..f9e067923cb76 100644
--- a/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
+++ b/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
@@ -1020,6 +1020,9 @@ static MCOperand lowerSymbolOperand(const MachineOperand &MO, MCSymbol *Sym,
   case RISCVII::MO_TLSDESC_CALL:
     Kind = ELF::R_RISCV_TLSDESC_CALL;
     break;
+  case RISCVII::MO_QC_ACCESS:
+    Kind = RISCV::S_QC_ACCESS;
+    break;
   }
 
   const MCExpr *ME = MCSymbolRefExpr::create(Sym, Ctx);
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
index 089683a43f800..26a5ca67732d8 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
@@ -3070,6 +3070,7 @@ bool RISCVInstrInfo::verifyInstruction(const MachineInstr &MI,
         CASE_OPERAND_SIMM(8)
         CASE_OPERAND_SIMM(10)
         CASE_OPERAND_SIMM(11)
+        CASE_OPERAND_SIMM(12)
         CASE_OPERAND_SIMM(26)
         // clang-format on
         case RISCVOp::OPERAND_SIMM5_PLUS1:
@@ -3602,7 +3603,8 @@ RISCVInstrInfo::getSerializableDirectMachineOperandTargetFlags() const {
       {MO_TLSDESC_HI, "riscv-tlsdesc-hi"},
       {MO_TLSDESC_LOAD_LO, "riscv-tlsdesc-load-lo"},
       {MO_TLSDESC_ADD_LO, "riscv-tlsdesc-add-lo"},
-      {MO_TLSDESC_CALL, "riscv-tlsdesc-call"}};
+      {MO_TLSDESC_CALL, "riscv-tlsdesc-call"},
+      {MO_QC_ACCESS, "riscv-qc-access"}};
   return ArrayRef(TargetFlags);
 }
 bool RISCVInstrInfo::isFunctionSafeToOutlineFrom(
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoC.td b/llvm/lib/Target/RISCV/RISCVInstrInfoC.td
index 8f76fa3b5bfd3..7ff9e83f4d0bc 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoC.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoC.td
@@ -108,7 +108,14 @@ class RISCVUImmLsbZeroOp<int width, int zeros>
 }
 
 // A 7-bit unsigned immediate where the least significant two bits are zero.
-def uimm7_lsb00 : RISCVUImmLsbZeroOp<7, 2>;
+def uimm7_lsb00 : RISCVUImmLsbZeroOp<7, 2> {
+  let MCOperandPredicate = [{
+    int64_t Imm;
+    if (!MCOp.evaluateAsConstantImm(Imm))
+      return false;
+    return isShiftedUInt<5, 2>(Imm);
+  }];
+}
 
 // A 8-bit unsigned immediate where the least significant two bits are zero.
 def uimm8_lsb00 : RISCVUImmLsbZeroOp<8, 2>;
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td b/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
index d868a7aa80d38..10a5e84b2d9df 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
@@ -1359,6 +1359,119 @@ def PseudoCCQC_E_LHU : SFBQCLoad;
 def PseudoCCQC_E_LBU : SFBQCLoad;
 }
 
+def simm12 : RISCVSImmOp<12>;
+
+def QCAccessSymbol : AsmOperandClass {
+  let Name = "QCAccessSymbol";
+  let RenderMethod = "addImmOperands";
+  let DiagnosticType = "InvalidQCAccessSymbol";
+  let DiagnosticString = "operand must be a symbol with a %qc.access specifier";
+  let ParserMethod = "parseOperandWithSpecifier";
+}
+
+def qc_access_symbol : Operand<XLenVT> {
+  let ParserMatchClass = QCAccessSymbol;
+
+  let MCOperandPredicate = [{
+    return MCOp.isExpr() && MCOp.getExpr()->getKind() == MCExpr::Specifier &&
+      cast<MCSpecifierExpr>(MCOp.getExpr())->getSpecifier() == RISCV::S_QC_ACCESS;
+  }];
+}
+
+// This is modelled after `PseudoAddTPRel` but with `mayLoad = true`
+let hasSideEffects = false, mayLoad = true, mayStore = false, Size = 32,
+  isCodeGenOnly = false in
+class PseudoQCAccessLoad_ri<string opcodestr>
+  : Pseudo<(outs GPR:$rd),
+           (ins GPRMem:$rs1, simm12:$imm12, qc_access_symbol:$expr), [],
+           opcodestr, "$rd, ${imm12}(${rs1}), $expr">;
+
+let hasSideEffects = false, mayLoad = false, mayStore = true, Size = 32,
+  isCodeGenOnly = false in
+class PseudoQCAccessStore_rri<string opcodestr>
+  : Pseudo<(outs), (ins GPR:$rs2, GPRMem:$rs1, simm12:$imm12, qc_access_symbol:$expr),
+           [], opcodestr, "$rs2, ${imm12}(${rs1}), $expr">;
+
+
+let Predicates = [HasVendorXqcili] in {
+def PseudoQCAccessLB  : PseudoQCAccessLoad_ri<"lb">;
+def PseudoQCAccessLBU : PseudoQCAccessLoad_ri<"lbu">;
+def PseudoQCAccessLH  : PseudoQCAccessLoad_ri<"lh">;
+def PseudoQCAccessLHU : PseudoQCAccessLoad_ri<"lhu">;
+def PseudoQCAccessLW : PseudoQCAccessLoad_ri<"lw">;
+
+let EmitPriority = 0 in {
+def : InstAlias<"lb $rd, (${rs1}), $expr", (PseudoQCAccessLB GPR:$rd, GPRMem:$rs1, 0, qc_access_symbol:$expr)>;
+def : InstAlias<"lbu $rd, (${rs1}), $expr", (PseudoQCAccessLB GPR:$rd, GPRMem:$rs1, 0, qc_access_symbol:$expr)>;
+def : InstAlias<"lh $rd, (${rs1}), $expr", (PseudoQCAccessLH GPR:$rd, GPRMem:$rs1, 0, qc_access_symbol:$expr)>;
+def : InstAlias<"lhu $rd, (${rs1}), $expr", (PseudoQCAccessLHU GPR:$rd, GPRMem:$rs1, 0, qc_access_symbol:$expr)>;
+def : InstAlias<"lw $rd, (${rs1}), $expr", (PseudoQCAccessLW GPR:$rd, GPRMem:$rs1, 0, qc_access_symbol:$expr)>;
+}
+
+def PseudoQCAccessSW : PseudoQCAccessStore_rri<"sw">;
+def PseudoQCAccessSH : PseudoQCAccessStore_rri<"sh">;
+def PseudoQCAccessSB : PseudoQCAccessStore_rri<"sb">;
+
+let EmitPriority = 0 in {
+def : InstAlias<"sb $rs2, (${rs1}), $expr", (PseudoQCAccessSB GPR:$rs2, GPRMem:$rs1, 0, qc_access_symbol:$expr)>;
+def : InstAlias<"sh $rs2, (${rs1}), $expr", (PseudoQCAccessSH GPR:$rs2, GPRMem:$rs1, 0, qc_access_symbol:$expr)>;
+def : InstAlias<"sw $rs2, (${rs1}), $expr", (PseudoQCAccessSW GPR:$rs2, GPRMem:$rs1, 0, qc_access_symbol:$expr)>;
+}
+}
+
+let hasSideEffects = false, mayLoad = true, mayStore = false, Size = 16,
+  isCodeGenOnly = false in
+class PseudoQCAccessCLoad_ri<string opcodestr, DAGOperand immop>
+  : Pseudo<(outs GPRC:$rd),
+           (ins GPRCMem:$rs1, immop:$imm, qc_access_symbol:$expr),
+           [], opcodestr, "$rd, ${imm}(${rs1}), $expr">;
+
+let hasSideEffects = false, mayLoad = false, mayStore = true, Size = 16,
+  isCodeGenOnly = false in
+class PseudoQCAccessCStore_rri<string opcodestr, DAGOperand immop>
+  : Pseudo<(outs),
+           (ins GPRC:$rs2, GPRCMem:$rs1, immop:$imm, qc_access_symbol:$expr),
+           [], opcodestr, "$rs2, ${imm}(${rs1}), $expr">;
+
+let Predicates = [HasVendorXqcili, HasStdExtZca] in {
+def PseudoQCAccessC_LW : PseudoQCAccessCLoad_ri<"c.lw", uimm7_lsb00>;
+let EmitPriority = 0 in
+def : InstAlias<"c.lw $rd, (${rs1}), $expr", (PseudoQCAccessC_LW GPRC:$rd, GPRCMem:$rs1, 0, qc_access_symbol:$expr)>;
+
+def PseudoQCAccessC_SW : PseudoQCAccessCStore_rri<"c.sw", uimm7_lsb00>;
+let EmitPriority = 0 in
+def : InstAlias<"c.sw $rs2, (${rs1}), $expr", (PseudoQCAccessC_SW GPRC:$rs2, GPRCMem:$rs1, 0, qc_access_symbol:$expr)>;
+}
+
+let Predicates = [HasVendorXqcili, HasStdExtZcb] in {
+// no C_LB so no PseudoQCAccessC_LB
+def PseudoQCAccessC_LBU : PseudoQCAccessCLoad_ri<"c.lbu", uimm2>;
+def PseudoQCAccessC_LH : PseudoQCAccessCLoad_ri<"c.lh", uimm2_lsb0>;
+def PseudoQCAccessC_LHU : PseudoQCAccessCLoad_ri<"c.lhu", uimm2_lsb0>;
+
+let EmitPriority = 0 in {
+// No c.lb so no alias
+def : InstAlias<"c.lbu $rd, (${rs1}), $expr", (PseudoQCAccessC_LBU GPRC:$rd, GPRCMem:$rs1, 0, qc_access_symbol:$expr)>;
+def : InstAlias<"c.lh $rd, (${rs1}), $expr", (PseudoQCAccessC_LH GPRC:$rd, GPRCMem:$rs1, 0, qc_access_symbol:$expr)>;
+def : InstAlias<"c.lhu $rd, (${rs1}), $expr", (PseudoQCAccessC_LHU GPRC:$rd, GPRCMem:$rs1, 0, qc_access_symbol:$expr)>;
+}
+
+def PseudoQCAccessC_SB : PseudoQCAccessCStore_rri<"c.sb", uimm2>;
+def PseudoQCAccessC_SH : PseudoQCAccessCStore_rri<"c.sh", uimm2_lsb0>;
+
+let EmitPriority = 0 in {
+def : InstAlias<"c.sb $rs2, (${rs1}), $expr", (PseudoQCAccessC_SB GPRC:$rs2, GPRCMem:$rs1, 0, qc_access_symbol:$expr)>;
+def : InstAlias<"c.sh $rs2, (${rs1}), $expr", (PseudoQCAccessC_SH GPRC:$rs2, GPRCMem:$rs1, 0, qc_access_symbol:$expr)>;
+}
+}
+
+// FIXME: We would like to add these but they are not supported by CompressPat.
+// let Predicates = [HasVendorXqcili, HasStdExtZca] in {
+// def : CompressPat<(PseudoQCAccessLW GPRC:$rd, GPRCMem:$rs1, uimm7_lsb00:$imm, qc_access_symbol:$expr),
+//                   (PseudoQCAccessC_LW GPRC:$rd, GPRCMem:$rs1, uimm7_lsb00:$imm, qc_access_symbol:$expr)>;
+// }
+
+
 //===----------------------------------------------------------------------===//
 // Code Gen Patterns
 //===----------------------------------------------------------------------===//
diff --git a/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp b/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp
new file mode 100644
index 0000000000000..9956bc82efe00
--- /dev/null
+++ b/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp
@@ -0,0 +1,228 @@
+//===-- RISCVQCRelaxMarking.cpp - Mark Instructions for QC Relaxations ----===//
+//
+// 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 pass adds access tags to some instructions which are used by the
+// assembler to emit marker relocations, which enable some code-size relaxations
+// for Xqcilo/Xqcili.
+//
+// The pass is looking for the following sequences:
+//
+//   $dst1 = QC_E_LI sym
+//   $dst2 = Load killed $dst1, 0
+//
+//   $dst1 = QC_E_LI sym
+//   Store $dst2, killed $dst1, 0
+//
+// In either case, the Load/Store is modified to become a
+// PseudoQCAccess<Load/Store>, with an additional operand that represents the
+// accessed symbolic address, which will become the contents of a
+// `R_RISCV_QC_ACCESS_*` relocation on the emitted instruction.
+//
+// FIXME: The intention is this pass does not change the size of any
+// instructions, but right now it has to do instruction compression as the
+// CompressPat infrastructure cannot handle compressing the `%qc.access(...)`
+// operand. Symbolic operands are not usually compressible, but this one is as
+// we have relocations for both 32-bit and 16-bit instructions (and the
+// relocation does not care about the fields of the instruction).
+
+#include "RISCV.h"
+#include "RISCVSubtarget.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/Statistic.h"
+#include "llvm/CodeGen/Passes.h"
+#include "llvm/CodeGen/RegisterScavenging.h"
+#include "llvm/MC/TargetRegistry.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/ErrorHandling.h"
+
+using namespace llvm;
+
+#define DEBUG_TYPE "riscv-qc-relax-marking"
+#define RISCV_QC_RELAX_MARKING_NAME "RISC-V QC Relaxation Marking"
+
+STATISTIC(NumMarked, "Number of Loads/Stores Marked");
+
+namespace {
+
+struct RISCVQCRelaxMarking : public MachineFunctionPass {
+  static char ID;
+
+  bool runOnMachineFunction(MachineFunction &) override;
+
+  RISCVQCRelaxMarking() : MachineFunctionPass(ID) {}
+
+  StringRef getPassName() const override { return RISCV_QC_RELAX_MARKING_NAME; }
+};
+
+} // end namespace
+
+char RISCVQCRelaxMarking::ID = 0;
+
+INITIALIZE_PASS(RISCVQCRelaxMarking, DEBUG_TYPE, RISCV_QC_RELAX_MARKING_NAME,
+                false, false)
+
+/// Returns an instance of the Make Compressible Optimization pass.
+FunctionPass *llvm::createRISCVQCRelaxMarkingPass() {
+  return new RISCVQCRelaxMarking();
+}
+
+static bool isLoad(MachineInstr &MI) {
+  return llvm::is_contained(
+      {RISCV::LW, RISCV::LH, RISCV::LHU, RISCV::LB, RISCV::LBU},
+      MI.getOpcode());
+}
+
+static bool isStore(MachineInstr &MI) {
+  return llvm::is_contained({RISCV::SW, RISCV::SH, RISCV::SB}, MI.getOpcode());
+}
+
+static bool isUImm7LSB000(const MachineOperand &MO) {
+  return MO.isImm() && isShiftedUInt<4, 3>(MO.getImm());
+}
+
+static bool isUImm2LSB0(const MachineOperand &MO) {
+  return MO.isImm() && isShiftedUInt<1, 1>(MO.getImm());
+}
+
+static bool isUImm2(const MachineOperand &MO) {
+  return MO.isImm() && isUInt<2>(MO.getImm());
+}
+
+static bool isGPRC(const MachineOperand &MO) {
+  return MO.isReg() && RISCV::GPRCRegClass.contains(MO.getReg());
+}
+
+static unsigned getQCMarkedOpcode(const MachineInstr &MI,
+                                  const RISCVSubtarget &STI) {
+  switch (MI.getOpcode()) {
+  case RISCV::LB:
+    // No c.lb
+    return RISCV::PseudoQCAccessLB;
+  case RISCV::LBU:
+    if (STI.hasStdExtZcb() && isGPRC(MI.getOperand(0)) &&
+        isGPRC(MI.getOperand(1)) && isUImm2(MI.getOperand(2)))
+      return RISCV::PseudoQCAccessC_LBU;
+    return RISCV::PseudoQCAccessLBU;
+  case RISCV::LH:
+    if (STI.hasStdExtZcb() && isGPRC(MI.getOperand(0)) &&
+        isGPRC(MI.getOperand(1)) && isUImm2LSB0(MI.getOperand(2)))
+      return RISCV::PseudoQCAccessC_LH;
+    return RISCV::PseudoQCAccessLH;
+  case RISCV::LHU:
+    if (STI.hasStdExtZcb() && isGPRC(MI.getOperand(0)) &&
+        isGPRC(MI.getOperand(1)) && isUImm2LSB0(MI.getOperand(2)))
+      return RISCV::PseudoQCAccessC_LHU;
+    return RISCV::PseudoQCAccessLHU;
+  case RISCV::LW:
+    if (STI.hasStdExtZca() && isGPRC(MI.getOperand(0)) &&
+        isGPRC(MI.getOperand(1)) && isUImm7LSB000(MI.getOperand(2)))
+      return RISCV::PseudoQCAccessC_LW;
+    return RISCV::PseudoQCAccessLW;
+  case RISCV::SB:
+    if (STI.hasStdExtZcb() && isGPRC(MI.getOperand(0)) &&
+        isGPRC(MI.getOperand(1)) && isUImm2(MI.getOperand(2)))
+      return RISCV::PseudoQCAccessC_SB;
+    return RISCV::PseudoQCAccessSB;
+  case RISCV::SH:
+    if (STI.hasStdExtZcb() && isGPRC(MI.getOperand(0)) &&
+        isGPRC(MI.getOperand(1)) && isUImm2LSB0(MI.getOperand(2)))
+      return RISCV::PseudoQCAccessC_SH;
+    return RISCV::PseudoQCAccessSH;
+  case RISCV::SW:
+    if (STI.hasStdExtZca() && isGPRC(MI.getOperand(0)) &&
+        isGPRC(MI.getOperand(1)) && isUImm7LSB000(MI.getOperand(2)))
+      return RISCV::PseudoQCAccessC_SW;
+    return RISCV::PseudoQCAccessSW;
+  default:
+    reportFatalInternalError(
+        "Unhandled Opcode: No Corresponding Marked Opcode");
+  }
+}
+
+bool RISCVQCRelaxMarking::runOnMachineFunction(MachineFunction &MF) {
+  // This is only relevant for QC.E.LI with a symbol, which we only use in the
+  // small code model.
+  if (MF.getTarget().getCodeModel() != CodeModel::Small)
+    return false;
+
+  auto &STI = MF.getSubtarget<RISCVSubtarget>();
+  // We need QC.E.LI instructions to perform this optimisation, which needs
+  // 32-bit and Xqcili. The markers are only needed when linker relaxations are
+  // enabled.
+  if (STI.is64Bit() || !STI.hasVendorXqcili() || !STI.enableLinkerRelax())
+    return false;
+
+  const RISCVInstrInfo *TII = STI.getInstrInfo();
+
+  bool Changed = false;
+  for (MachineBasicBlock &MBB : MF) {
+    for (auto MI = MBB.begin(), E = MBB.end(); MI != E; MI++) {
+      auto NextMI = std::next(MI);
+      if (NextMI == E)
+        break;
+
+      // Looking for QC.E.LI followed by a load or store
+      if (MI->getOpcode() != RISCV::QC_E_LI ||
+          !(isLoad(*NextMI) || isStore(*NextMI)))
+        continue;
+
+      LLVM_DEBUG(llvm::dbgs() << "Found QC_E_LI " << *MI);
+      LLVM_DEBUG(llvm::dbgs() << "Followed by Load/Store " << *NextMI);
+
+      MachineOperand &OffsetOp = NextMI->getOperand(2);
+      if (OffsetOp.getImm() != 0)
+        continue;
+      if (MI->getOperand(0).getReg() != NextMI->getOperand(1).getReg())
+        continue;
+      if (!NextMI->getOperand(1).isKill())
+        continue;
+
+      // This is unsafe for stores where the access address is being stored.
+      if (isStore(*NextMI) &&
+          MI->getOperand(0).getReg() == NextMI->getOperand(0).getReg())
+        continue;
+
+      MachineOperand &SymOp = MI->getOperand(1);
+      if (!SymOp.isSymbol() && !SymOp.isGlobal() && !SymOp.isMCSymbol() && !SymOp.isCPI())
+        continue;
+
+      unsigned NewOpc = getQCMarkedOpcode(*NextMI, STI);
+      LLVM_DEBUG(dbgs() << "Load/Store " << TII->getName(NextMI->getOpcode())
+                        << " will become " << TII->getName(NewOpc) << "\n");
+      MachineInstrBuilder MIB =
+          BuildMI(MBB, NextMI, NextMI->getDebugLoc(), TII->get(NewOpc))
+              .add(NextMI->getOperand(0))
+              .add(NextMI->getOperand(1))
+              .add(OffsetOp)
+              .cloneMemRefs(*NextMI);
+
+      if (SymOp.isSymbol()) {
+        MIB.addExternalSymbol(SymOp.getSymbolName(), RISCVII::MO_QC_ACCESS);
+      } else if (SymOp.isGlobal()) {
+        MIB.addGlobalAddress(SymOp.getGlobal(), SymOp.getOffset(),
+                             RISCVII::MO_QC_ACCESS);
+      } else if (SymOp.isMCSymbol()) {
+        MachineOperand MO = MachineOperand::CreateMCSymbol(
+            SymOp.getMCSymbol(), RISCVII::MO_QC_ACCESS);
+        MO.setOffset(SymOp.getOffset());
+        MIB.add(MO);
+      } else if (SymOp.isCPI()) {
+        MIB.addConstantPoolIndex(SymOp.getIndex(), SymOp.getOffset(),
+                                 RISCVII::MO_QC_ACCESS);
+      } else {
+        reportFatalInternalError("Unhandled SymOp Kind");
+      }
+
+      NextMI->removeFromParent();
+      NumMarked++;
+      Changed |= true;
+    }
+  }
+
+  return Changed;
+}
diff --git a/llvm/lib/Target/RISCV/RISCVTargetMachine.cpp b/llvm/lib/Target/RISCV/RISCVTargetMachine.cpp
index 54b286d128dfa..f85046b6c1f83 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetMachine.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetMachine.cpp
@@ -594,6 +594,9 @@ void RISCVPassConfig::addPreEmitPass2() {
   }
   addPass(createRISCVExpandPseudoPass());
 
+  // Add QC Relaxation Markers as late as possible.
+  addPass(createRISCVQCRelaxMarkingPass());
+
   // Schedule the expansion of AMOs at the last possible moment, avoiding the
   // possibility for other passes to break the requirements for forward
   // progress in the LR/SC block.
diff --git a/llvm/test/CodeGen/RISCV/O0-pipeline.ll b/llvm/test/CodeGen/RISCV/O0-pipeline.ll
index 847a8bd96c6d6..84c005fb539dd 100644
--- a/llvm/test/CodeGen/RISCV/O0-pipeline.ll
+++ b/llvm/test/CodeGen/RISCV/O0-pipeline.ll
@@ -76,6 +76,7 @@
 ; CHECK-NEXT:       Machine Optimization Remark Emitter
 ; CHECK-NEXT:       Stack Frame Layout Analysis
 ; CHECK-NEXT:       RISC-V pseudo instruction expansion pass
+; CHECK-NEXT:       RISC-V QC Relaxation Marking
 ; CHECK-NEXT:       RISC-V atomic pseudo instruction expansion pass
 ; CHECK-NEXT:       Unpack machine instruction bundles
 ; CHECK-NEXT:       Lazy Machine Block Frequency Analysis
diff --git a/llvm/test/CodeGen/RISCV/O3-pipeline.ll b/llvm/test/CodeGen/RISCV/O3-pipeline.ll
index 2bbf67b04dff5..91b0005d4fd6e 100644
--- a/llvm/test/CodeGen/RISCV/O3-pipeline.ll
+++ b/llvm/test/CodeGen/RISCV/O3-pipeline.ll
@@ -227,6 +227,7 @@
 ; CHECK-NEXT:       RISC-V Zcmp move merging pass
 ; CHECK-NEXT:       RISC-V Zcmp Push/Pop optimization pass
 ; CHECK-NEXT:       RISC-V pseudo instruction expansion pass
+; CHECK-NEXT:       RISC-V QC Relaxation Marking
 ; CHECK-NEXT:       RISC-V atomic pseudo instruction expansion pass
 ; CHECK-NEXT:       Unpack machine instruction bundles
 ; CHECK-NEXT:       Lazy Machine Block Frequency Analysis
diff --git a/llvm/test/CodeGen/RISCV/qc-relax-marking.ll b/llvm/test/CodeGen/RISCV/qc-relax-marking.ll
new file mode 100644
index 0000000000000..a0669d3118738
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/qc-relax-marking.ll
@@ -0,0 +1,310 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 6
+; RUN: llc -mtriple=riscv32 -mattr=+xqcili,+relax -code-model=small \
+; RUN:    -verify-machineinstrs < %s | FileCheck %s --check-prefix=NO-C
+; RUN: llc -mtriple=riscv32 -mattr=+xqcili,+relax,+zca,+zcb -code-model=small \
+; RUN:    -verify-machineinstrs < %s | FileCheck %s --check-prefix=WITH-C
+
+ at global = external global [8 x i8]
+
+define zeroext i8 @load_unsigned_byte() nounwind optsize {
+; NO-C-LABEL: load_unsigned_byte:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global
+; NO-C-NEXT:    lbu a0, 0(a0), %qc.access(global)
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_unsigned_byte:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global
+; WITH-C-NEXT:    c.lbu a0, 0(a0), %qc.access(global)
+; WITH-C-NEXT:    ret
+  %1 = load i8, ptr @global, align 1
+  ret i8 %1
+}
+
+define zeroext i8 @load_unsigned_byte_offset() nounwind optsize {
+; NO-C-LABEL: load_unsigned_byte_offset:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global+1
+; NO-C-NEXT:    lbu a0, 0(a0), %qc.access(global+1)
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_unsigned_byte_offset:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global+1
+; WITH-C-NEXT:    c.lbu a0, 0(a0), %qc.access(global+1)
+; WITH-C-NEXT:    ret
+  %1 = getelementptr i8, ptr @global, i32 1
+  %2 = load i8, ptr %1, align 1
+  ret i8 %2
+}
+
+define zeroext i16 @load_two_unsigned_byte() nounwind optsize {
+; NO-C-LABEL: load_two_unsigned_byte:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global
+; NO-C-NEXT:    lbu a1, 1(a0)
+; NO-C-NEXT:    lbu a0, 0(a0)
+; NO-C-NEXT:    slli a1, a1, 8
+; NO-C-NEXT:    or a0, a0, a1
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_two_unsigned_byte:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global
+; WITH-C-NEXT:    lbu a1, 1(a0)
+; WITH-C-NEXT:    lbu a0, 0(a0)
+; WITH-C-NEXT:    slli a1, a1, 8
+; WITH-C-NEXT:    or a0, a0, a1
+; WITH-C-NEXT:    ret
+  %1 = load i16, ptr @global, align 1
+  ret i16 %1
+}
+
+define zeroext i8 @load_unsigned_byte_twice() nounwind optsize {
+; NO-C-LABEL: load_unsigned_byte_twice:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global
+; NO-C-NEXT:    lbu a1, 0(a0)
+; NO-C-NEXT:    lbu a0, 0(a0)
+; NO-C-NEXT:    or a0, a0, a1
+; NO-C-NEXT:    zext.b a0, a0
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_unsigned_byte_twice:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global
+; WITH-C-NEXT:    lbu a1, 0(a0)
+; WITH-C-NEXT:    lbu a0, 0(a0)
+; WITH-C-NEXT:    or a0, a0, a1
+; WITH-C-NEXT:    zext.b a0, a0
+; WITH-C-NEXT:    ret
+  %1 = load volatile i8, ptr @global, align 1
+  %2 = load volatile i8, ptr @global, align 1
+  %3 = or i8 %1, %2
+  ret i8 %3
+}
+
+define zeroext i8 @load_unsigned_byte_twice_offset() nounwind optsize {
+; NO-C-LABEL: load_unsigned_byte_twice_offset:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global+1
+; NO-C-NEXT:    lbu a1, 0(a0)
+; NO-C-NEXT:    lbu a0, 0(a0)
+; NO-C-NEXT:    or a0, a0, a1
+; NO-C-NEXT:    zext.b a0, a0
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_unsigned_byte_twice_offset:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global+1
+; WITH-C-NEXT:    lbu a1, 0(a0)
+; WITH-C-NEXT:    lbu a0, 0(a0)
+; WITH-C-NEXT:    or a0, a0, a1
+; WITH-C-NEXT:    zext.b a0, a0
+; WITH-C-NEXT:    ret
+  %1 = getelementptr i8, ptr @global, i32 1
+  %2 = load volatile i8, ptr %1, align 1
+  %3 = load volatile i8, ptr %1, align 1
+  %4 = or i8 %2, %3
+  ret i8 %4
+}
+
+
+define i16 @load_halfword() nounwind optsize {
+; NO-C-LABEL: load_halfword:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global
+; NO-C-NEXT:    lh a0, 0(a0), %qc.access(global)
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_halfword:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global
+; WITH-C-NEXT:    c.lh a0, 0(a0), %qc.access(global)
+; WITH-C-NEXT:    ret
+  %1 = load i16, ptr @global, align 2
+  ret i16 %1
+}
+
+define i16 @load_halfword_offset() nounwind optsize {
+; NO-C-LABEL: load_halfword_offset:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global+2
+; NO-C-NEXT:    lh a0, 0(a0), %qc.access(global+2)
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_halfword_offset:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global+2
+; WITH-C-NEXT:    c.lh a0, 0(a0), %qc.access(global+2)
+; WITH-C-NEXT:    ret
+  %1 = getelementptr i8, ptr @global, i32 2
+  %2 = load i16, ptr %1, align 2
+  ret i16 %2
+}
+
+define i32 @load_two_halfword() nounwind optsize {
+; NO-C-LABEL: load_two_halfword:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global
+; NO-C-NEXT:    lhu a1, 2(a0)
+; NO-C-NEXT:    lhu a0, 0(a0)
+; NO-C-NEXT:    slli a1, a1, 16
+; NO-C-NEXT:    or a0, a0, a1
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_two_halfword:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global
+; WITH-C-NEXT:    lhu a1, 2(a0)
+; WITH-C-NEXT:    lhu a0, 0(a0)
+; WITH-C-NEXT:    slli a1, a1, 16
+; WITH-C-NEXT:    or a0, a0, a1
+; WITH-C-NEXT:    ret
+  %1 = load i32, ptr @global, align 2
+  ret i32 %1
+}
+
+define i16 @load_halfword_twice() nounwind optsize {
+; NO-C-LABEL: load_halfword_twice:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global
+; NO-C-NEXT:    lh a1, 0(a0)
+; NO-C-NEXT:    lh a0, 0(a0)
+; NO-C-NEXT:    or a0, a0, a1
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_halfword_twice:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global
+; WITH-C-NEXT:    lh a1, 0(a0)
+; WITH-C-NEXT:    lh a0, 0(a0)
+; WITH-C-NEXT:    or a0, a0, a1
+; WITH-C-NEXT:    ret
+  %1 = load volatile i16, ptr @global, align 2
+  %2 = load volatile i16, ptr @global, align 2
+  %3 = or i16 %1, %2
+  ret i16 %3
+}
+
+define i16 @load_halfword_twice_offset() nounwind optsize {
+; NO-C-LABEL: load_halfword_twice_offset:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global+4
+; NO-C-NEXT:    lh a1, 0(a0)
+; NO-C-NEXT:    lh a0, 0(a0)
+; NO-C-NEXT:    or a0, a0, a1
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_halfword_twice_offset:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global+4
+; WITH-C-NEXT:    lh a1, 0(a0)
+; WITH-C-NEXT:    lh a0, 0(a0)
+; WITH-C-NEXT:    or a0, a0, a1
+; WITH-C-NEXT:    ret
+  %1 = getelementptr i16, ptr @global, i32 2
+  %2 = load volatile i16, ptr %1, align 2
+  %3 = load volatile i16, ptr %1, align 2
+  %4 = or i16 %2, %3
+  ret i16 %4
+}
+
+
+
+define i32 @load_word() nounwind optsize {
+; NO-C-LABEL: load_word:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global
+; NO-C-NEXT:    c.lw a0, 0(a0), %qc.access(global)
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_word:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global
+; WITH-C-NEXT:    c.lw a0, 0(a0), %qc.access(global)
+; WITH-C-NEXT:    ret
+  %1 = load i32, ptr @global, align 4
+  ret i32 %1
+}
+
+define i32 @load_word_offset() nounwind optsize {
+; NO-C-LABEL: load_word_offset:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global+4
+; NO-C-NEXT:    c.lw a0, 0(a0), %qc.access(global+4)
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_word_offset:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global+4
+; WITH-C-NEXT:    c.lw a0, 0(a0), %qc.access(global+4)
+; WITH-C-NEXT:    ret
+  %1 = getelementptr i8, ptr @global, i32 4
+  %2 = load i32, ptr %1, align 4
+  ret i32 %2
+}
+
+define i64 @load_two_word() nounwind optsize {
+; NO-C-LABEL: load_two_word:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a1, global
+; NO-C-NEXT:    lw a0, 0(a1)
+; NO-C-NEXT:    lw a1, 4(a1)
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_two_word:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a1, global
+; WITH-C-NEXT:    lw a0, 0(a1)
+; WITH-C-NEXT:    lw a1, 4(a1)
+; WITH-C-NEXT:    ret
+  %1 = load i64, ptr @global, align 4
+  ret i64 %1
+}
+
+define i32 @load_word_twice() nounwind optsize {
+; NO-C-LABEL: load_word_twice:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global
+; NO-C-NEXT:    lw a1, 0(a0)
+; NO-C-NEXT:    lw a0, 0(a0)
+; NO-C-NEXT:    or a0, a0, a1
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_word_twice:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global
+; WITH-C-NEXT:    lw a1, 0(a0)
+; WITH-C-NEXT:    lw a0, 0(a0)
+; WITH-C-NEXT:    or a0, a0, a1
+; WITH-C-NEXT:    ret
+  %1 = load volatile i32, ptr @global, align 4
+  %2 = load volatile i32, ptr @global, align 4
+  %3 = or i32 %1, %2
+  ret i32 %3
+}
+
+define i32 @load_word_twice_offset() nounwind optsize {
+; NO-C-LABEL: load_word_twice_offset:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global+8
+; NO-C-NEXT:    lw a1, 0(a0)
+; NO-C-NEXT:    lw a0, 0(a0)
+; NO-C-NEXT:    or a0, a0, a1
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_word_twice_offset:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global+8
+; WITH-C-NEXT:    lw a1, 0(a0)
+; WITH-C-NEXT:    lw a0, 0(a0)
+; WITH-C-NEXT:    or a0, a0, a1
+; WITH-C-NEXT:    ret
+  %1 = getelementptr i32, ptr @global, i32 2
+  %2 = load volatile i32, ptr %1, align 4
+  %3 = load volatile i32, ptr %1, align 4
+  %4 = or i32 %2, %3
+  ret i32 %4
+}

>From 5c49a9dc978776588cad7f656438cbd7bd4577ef Mon Sep 17 00:00:00 2001
From: Sam Elliott <aelliott at qti.qualcomm.com>
Date: Fri, 27 Mar 2026 16:29:50 -0700
Subject: [PATCH 2/4] clang-format, add asm tests

---
 llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td   |   6 +-
 llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp |   3 +-
 llvm/test/MC/RISCV/rvi-pseudos-invalid.s      |  11 +-
 llvm/test/MC/RISCV/xqci-access-pseudos.s      | 151 ++++++++++++++++++
 4 files changed, 163 insertions(+), 8 deletions(-)
 create mode 100644 llvm/test/MC/RISCV/xqci-access-pseudos.s

diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td b/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
index 10a5e84b2d9df..724b1a5d8a4c7 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
@@ -1393,7 +1393,7 @@ class PseudoQCAccessStore_rri<string opcodestr>
            [], opcodestr, "$rs2, ${imm12}(${rs1}), $expr">;
 
 
-let Predicates = [HasVendorXqcili] in {
+let Predicates = [IsRV32] in {
 def PseudoQCAccessLB  : PseudoQCAccessLoad_ri<"lb">;
 def PseudoQCAccessLBU : PseudoQCAccessLoad_ri<"lbu">;
 def PseudoQCAccessLH  : PseudoQCAccessLoad_ri<"lh">;
@@ -1433,7 +1433,7 @@ class PseudoQCAccessCStore_rri<string opcodestr, DAGOperand immop>
            (ins GPRC:$rs2, GPRCMem:$rs1, immop:$imm, qc_access_symbol:$expr),
            [], opcodestr, "$rs2, ${imm}(${rs1}), $expr">;
 
-let Predicates = [HasVendorXqcili, HasStdExtZca] in {
+let Predicates = [IsRV32, HasStdExtZca] in {
 def PseudoQCAccessC_LW : PseudoQCAccessCLoad_ri<"c.lw", uimm7_lsb00>;
 let EmitPriority = 0 in
 def : InstAlias<"c.lw $rd, (${rs1}), $expr", (PseudoQCAccessC_LW GPRC:$rd, GPRCMem:$rs1, 0, qc_access_symbol:$expr)>;
@@ -1443,7 +1443,7 @@ let EmitPriority = 0 in
 def : InstAlias<"c.sw $rs2, (${rs1}), $expr", (PseudoQCAccessC_SW GPRC:$rs2, GPRCMem:$rs1, 0, qc_access_symbol:$expr)>;
 }
 
-let Predicates = [HasVendorXqcili, HasStdExtZcb] in {
+let Predicates = [IsRV32, HasStdExtZcb] in {
 // no C_LB so no PseudoQCAccessC_LB
 def PseudoQCAccessC_LBU : PseudoQCAccessCLoad_ri<"c.lbu", uimm2>;
 def PseudoQCAccessC_LH : PseudoQCAccessCLoad_ri<"c.lh", uimm2_lsb0>;
diff --git a/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp b/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp
index 9956bc82efe00..b4f1ed9ec87da 100644
--- a/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp
+++ b/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp
@@ -188,7 +188,8 @@ bool RISCVQCRelaxMarking::runOnMachineFunction(MachineFunction &MF) {
         continue;
 
       MachineOperand &SymOp = MI->getOperand(1);
-      if (!SymOp.isSymbol() && !SymOp.isGlobal() && !SymOp.isMCSymbol() && !SymOp.isCPI())
+      if (!SymOp.isSymbol() && !SymOp.isGlobal() && !SymOp.isMCSymbol() &&
+          !SymOp.isCPI())
         continue;
 
       unsigned NewOpc = getQCMarkedOpcode(*NextMI, STI);
diff --git a/llvm/test/MC/RISCV/rvi-pseudos-invalid.s b/llvm/test/MC/RISCV/rvi-pseudos-invalid.s
index 2dcab572fb7b6..d5b6db3b80b21 100644
--- a/llvm/test/MC/RISCV/rvi-pseudos-invalid.s
+++ b/llvm/test/MC/RISCV/rvi-pseudos-invalid.s
@@ -1,5 +1,5 @@
-# RUN: not llvm-mc %s -triple=riscv32 2>&1 | FileCheck %s
-# RUN: not llvm-mc %s -triple=riscv64 2>&1 | FileCheck %s
+# RUN: not llvm-mc %s -triple=riscv32 2>&1 | FileCheck --check-prefixes=CHECK,CHECK-RV32 %s
+# RUN: not llvm-mc %s -triple=riscv64 2>&1 | FileCheck --check-prefixes=CHECK,CHECK-RV64 %s
 
 lga x1, 1234 # CHECK: :[[@LINE]]:9: error: operand must be a bare symbol name
 lga x1, %pcrel_hi(1234) # CHECK: :[[@LINE]]:9: error: operand must be a bare symbol name
@@ -11,9 +11,12 @@ lga x1, %lo(1234) # CHECK: :[[@LINE]]:9: error: operand must be a bare symbol na
 lga x1, %hi(foo) # CHECK: :[[@LINE]]:9: error: operand must be a bare symbol name
 lga x1, %lo(foo) # CHECK: :[[@LINE]]:9: error: operand must be a bare symbol name
 
-sw a2, %hi(a_symbol), a3 # CHECK: :[[@LINE]]:8: error: operand must be a symbol with %lo/%pcrel_lo/%tprel_lo specifier or an integer in the range [-2048, 2047]
+sw a2, %hi(a_symbol), a3
+# CHECK-RV32: :[[@LINE-1]]:8: error: immediate must be an integer in the range [-2048, 2047]
+# CHECK-RV64: :[[@LINE-2]]:8: error: operand must be a symbol with %lo/%pcrel_lo/%tprel_lo specifier or an integer in the range [-2048, 2047]
+
 sw a2, %lo(a_symbol), a3 # CHECK: :[[@LINE]]:23: error: invalid operand for instruction
-sw a2, %lo(a_symbol)(a4), a3 # CHECK: :[[@LINE]]:27: error: invalid operand for instruction
+sw a2, %lo(a_symbol)(a4), a3 # CHECK: :[[@LINE]]:27: error: expected '%' relocation specifier
 
 # Too few operands must be rejected
 sw a2, a_symbol # CHECK: :[[@LINE]]:1: error: too few operands for instruction
diff --git a/llvm/test/MC/RISCV/xqci-access-pseudos.s b/llvm/test/MC/RISCV/xqci-access-pseudos.s
new file mode 100644
index 0000000000000..aaabc15b0712a
--- /dev/null
+++ b/llvm/test/MC/RISCV/xqci-access-pseudos.s
@@ -0,0 +1,151 @@
+# RUN: llvm-mc %s -triple=riscv32 -mattr=+zca,+zcb -M no-aliases -show-encoding \
+# RUN:     | FileCheck -check-prefixes=CHECK-ENC,CHECK-INST,CHECK-ASM %s
+# RUN: llvm-mc -filetype=obj -triple riscv32 -mattr=+zca,+zcb < %s \
+# RUN:     | llvm-objdump --mattr=+zca,+zcb -M no-aliases --no-print-imm-hex -d - \
+# RUN:     | FileCheck -check-prefix=CHECK-INST %s
+# RUN: llvm-mc -filetype=obj -triple riscv32 -mattr=+zca,+zcb,+relax < %s \
+# RUN:     | llvm-objdump --mattr=+zca,+zcb -M no-aliases --no-print-imm-hex -dr - \
+# RUN:     | FileCheck -check-prefix=CHECK-INST,CHECK-RELOC %s
+
+# RUN: not llvm-mc %s -triple=riscv64 -mattr=+zca,+zcb 2>&1 \
+# RUN:     | FileCheck -check-prefix=CHECK-RV64 %s
+
+## This tests the instructions which accept %qc.access(...), which are the
+## RVI, Zca, and Zcb loads and stores, on RV32 only.
+
+lb a0, 0(a1), %qc.access(extern1)
+# CHECK-INST: lb a0, 0(a1)
+# CHECK-ASM-SAME: , %qc.access(extern1)
+# CHECK-ENC-SAME: [0x03,0x85,0x05,0x00]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_32 extern1
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set
+
+lbu a0, 0(a1), %qc.access(extern1)
+# CHECK-INST: lbu a0, 0(a1)
+# CHECK-ASM-SAME: , %qc.access(extern1)
+# CHECK-ENC-SAME: [0x03,0xc5,0x05,0x00]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_32 extern1
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set
+
+lh a2, 0(a3), %qc.access(extern2)
+# CHECK-INST: lh a2, 0(a3)
+# CHECK-ASM-SAME: , %qc.access(extern2)
+# CHECK-ENC-SAME: [0x03,0x96,0x06,0x00]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_32 extern2
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set
+
+lhu a2, 0(a3), %qc.access(extern2)
+# CHECK-INST: lhu a2, 0(a3)
+# CHECK-ASM-SAME: , %qc.access(extern2)
+# CHECK-ENC-SAME: [0x03,0xd6,0x06,0x00]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_32 extern2
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set
+
+lw a4, 0(a5), %qc.access(extern4)
+# CHECK-INST: lw a4, 0(a5)
+# CHECK-ASM-SAME: , %qc.access(extern4)
+# CHECK-ENC-SAME: [0x03,0xa7,0x07,0x00]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_32 extern4
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set
+
+sb a0, 0(a1), %qc.access(extern1)
+# CHECK-INST: sb a0, 0(a1)
+# CHECK-ASM-SAME: , %qc.access(extern1)
+# CHECK-ENC-SAME: [0x23,0x80,0xa5,0x00]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_32 extern1
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set
+
+sh a2, 0(a3), %qc.access(extern2)
+# CHECK-INST: sh a2, 0(a3)
+# CHECK-ASM-SAME: , %qc.access(extern2)
+# CHECK-ENC-SAME: [0x23,0x90,0xc6,0x00]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_32 extern2
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set
+
+sw a4, 0(a5), %qc.access(extern4)
+# CHECK-INST: sw a4, 0(a5)
+# CHECK-ASM-SAME: , %qc.access(extern4)
+# CHECK-ENC-SAME: [0x23,0xa0,0xe7,0x00]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_32 extern4
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set
+
+## No c.lb
+
+c.lbu a0, 0(a1), %qc.access(extern1)
+# CHECK-INST: c.lbu a0, 0(a1)
+# CHECK-ASM-SAME: , %qc.access(extern1)
+# CHECK-ENC-SAME: [0x88,0x81]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_16 extern1
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set
+
+c.lh a2, 0(a3), %qc.access(extern2)
+# CHECK-INST: c.lh a2, 0(a3)
+# CHECK-ASM-SAME: , %qc.access(extern2)
+# CHECK-ENC-SAME: [0xd0,0x86]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_16 extern2
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set
+
+c.lhu a2, 0(a3), %qc.access(extern2)
+# CHECK-INST: c.lhu a2, 0(a3)
+# CHECK-ASM-SAME: , %qc.access(extern2)
+# CHECK-ENC-SAME: [0x90,0x86]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_16 extern2
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set
+
+c.lw a4, 0(a5), %qc.access(extern4)
+# CHECK-INST: c.lw a4, 0(a5)
+# CHECK-ASM-SAME: , %qc.access(extern4)
+# CHECK-ENC-SAME: [0x98,0x43]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_16 extern4
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set
+
+c.sb a0, 0(a1), %qc.access(extern1)
+# CHECK-INST: c.sb a0, 0(a1)
+# CHECK-ASM-SAME: , %qc.access(extern1)
+# CHECK-ENC-SAME: [0x88,0x89]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_16 extern1
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set
+
+c.sh a2, 0(a3), %qc.access(extern2)
+# CHECK-INST: c.sh a2, 0(a3)
+# CHECK-ASM-SAME: , %qc.access(extern2)
+# CHECK-ENC-SAME: [0x90,0x8e]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_16 extern2
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set
+
+c.sw a4, 0(a5), %qc.access(extern4)
+# CHECK-INST: c.sw a4, 0(a5)
+# CHECK-ASM-SAME: , %qc.access(extern4)
+# CHECK-ENC-SAME: [0x98,0xc3]
+# CHECK-RELOC: R_RISCV_VENDOR QUALCOMM
+# CHECK-RELOC-NEXT: R_RISCV_QC_ACCESS_16 extern4
+# CHECK-RELOC-NEXT: R_RISCV_RELAX
+# CHECK-RV64: [[@LINE-7]]:1: error: instruction requires the following: RV32I Base Instruction Set

>From e6044c130b190def647cca57d329e4f55a6f5dc1 Mon Sep 17 00:00:00 2001
From: Sam Elliott <aelliott at qti.qualcomm.com>
Date: Fri, 27 Mar 2026 19:13:40 -0700
Subject: [PATCH 3/4] review feedback

---
 .../Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp |  3 +--
 llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td          | 12 ++++++------
 llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp        |  8 ++++----
 3 files changed, 11 insertions(+), 12 deletions(-)

diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
index 0c531498da278..b103eee1d81f5 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
@@ -483,7 +483,7 @@ void RISCVMCCodeEmitter::expandPseudoQCAccess(
   if (!STI.hasFeature(RISCV::FeatureRelax))
     return;
 
-  MCOperand AccessSymbol = MI.getOperand(3);
+  const MCOperand &AccessSymbol = MI.getOperand(3);
   assert(AccessSymbol.isExpr() && "Expected expression in PseudoQCAccess");
 
   const auto *AccessExpr = dyn_cast<MCSpecifierExpr>(AccessSymbol.getExpr());
@@ -799,7 +799,6 @@ uint64_t RISCVMCCodeEmitter::getImmOpValue(const MCInst &MI, unsigned OpNo,
       FixupKind = RISCV::fixup_riscv_qc_abs20_u;
       RelaxCandidate = true;
       break;
-
     case ELF::R_RISCV_GOT_HI20:
     case ELF::R_RISCV_TPREL_HI20:
     case ELF::R_RISCV_TLSDESC_HI20:
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td b/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
index 724b1a5d8a4c7..b53f41b4cf6f2 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfoXqci.td
@@ -1379,14 +1379,14 @@ def qc_access_symbol : Operand<XLenVT> {
 }
 
 // This is modelled after `PseudoAddTPRel` but with `mayLoad = true`
-let hasSideEffects = false, mayLoad = true, mayStore = false, Size = 32,
+let hasSideEffects = false, mayLoad = true, mayStore = false, Size = 4,
   isCodeGenOnly = false in
 class PseudoQCAccessLoad_ri<string opcodestr>
   : Pseudo<(outs GPR:$rd),
            (ins GPRMem:$rs1, simm12:$imm12, qc_access_symbol:$expr), [],
            opcodestr, "$rd, ${imm12}(${rs1}), $expr">;
 
-let hasSideEffects = false, mayLoad = false, mayStore = true, Size = 32,
+let hasSideEffects = false, mayLoad = false, mayStore = true, Size = 4,
   isCodeGenOnly = false in
 class PseudoQCAccessStore_rri<string opcodestr>
   : Pseudo<(outs), (ins GPR:$rs2, GPRMem:$rs1, simm12:$imm12, qc_access_symbol:$expr),
@@ -1398,7 +1398,7 @@ def PseudoQCAccessLB  : PseudoQCAccessLoad_ri<"lb">;
 def PseudoQCAccessLBU : PseudoQCAccessLoad_ri<"lbu">;
 def PseudoQCAccessLH  : PseudoQCAccessLoad_ri<"lh">;
 def PseudoQCAccessLHU : PseudoQCAccessLoad_ri<"lhu">;
-def PseudoQCAccessLW : PseudoQCAccessLoad_ri<"lw">;
+def PseudoQCAccessLW  : PseudoQCAccessLoad_ri<"lw">;
 
 let EmitPriority = 0 in {
 def : InstAlias<"lb $rd, (${rs1}), $expr", (PseudoQCAccessLB GPR:$rd, GPRMem:$rs1, 0, qc_access_symbol:$expr)>;
@@ -1419,14 +1419,14 @@ def : InstAlias<"sw $rs2, (${rs1}), $expr", (PseudoQCAccessSW GPR:$rs2, GPRMem:$
 }
 }
 
-let hasSideEffects = false, mayLoad = true, mayStore = false, Size = 16,
+let hasSideEffects = false, mayLoad = true, mayStore = false, Size = 2,
   isCodeGenOnly = false in
 class PseudoQCAccessCLoad_ri<string opcodestr, DAGOperand immop>
   : Pseudo<(outs GPRC:$rd),
            (ins GPRCMem:$rs1, immop:$imm, qc_access_symbol:$expr),
            [], opcodestr, "$rd, ${imm}(${rs1}), $expr">;
 
-let hasSideEffects = false, mayLoad = false, mayStore = true, Size = 16,
+let hasSideEffects = false, mayLoad = false, mayStore = true, Size = 2,
   isCodeGenOnly = false in
 class PseudoQCAccessCStore_rri<string opcodestr, DAGOperand immop>
   : Pseudo<(outs),
@@ -1446,7 +1446,7 @@ def : InstAlias<"c.sw $rs2, (${rs1}), $expr", (PseudoQCAccessC_SW GPRC:$rs2, GPR
 let Predicates = [IsRV32, HasStdExtZcb] in {
 // no C_LB so no PseudoQCAccessC_LB
 def PseudoQCAccessC_LBU : PseudoQCAccessCLoad_ri<"c.lbu", uimm2>;
-def PseudoQCAccessC_LH : PseudoQCAccessCLoad_ri<"c.lh", uimm2_lsb0>;
+def PseudoQCAccessC_LH  : PseudoQCAccessCLoad_ri<"c.lh", uimm2_lsb0>;
 def PseudoQCAccessC_LHU : PseudoQCAccessCLoad_ri<"c.lhu", uimm2_lsb0>;
 
 let EmitPriority = 0 in {
diff --git a/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp b/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp
index b4f1ed9ec87da..c0f8dae87ea7b 100644
--- a/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp
+++ b/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp
@@ -71,13 +71,13 @@ FunctionPass *llvm::createRISCVQCRelaxMarkingPass() {
   return new RISCVQCRelaxMarking();
 }
 
-static bool isLoad(MachineInstr &MI) {
+static bool isLoad(const MachineInstr &MI) {
   return llvm::is_contained(
       {RISCV::LW, RISCV::LH, RISCV::LHU, RISCV::LB, RISCV::LBU},
       MI.getOpcode());
 }
 
-static bool isStore(MachineInstr &MI) {
+static bool isStore(const MachineInstr &MI) {
   return llvm::is_contained({RISCV::SW, RISCV::SH, RISCV::SB}, MI.getOpcode());
 }
 
@@ -171,8 +171,8 @@ bool RISCVQCRelaxMarking::runOnMachineFunction(MachineFunction &MF) {
           !(isLoad(*NextMI) || isStore(*NextMI)))
         continue;
 
-      LLVM_DEBUG(llvm::dbgs() << "Found QC_E_LI " << *MI);
-      LLVM_DEBUG(llvm::dbgs() << "Followed by Load/Store " << *NextMI);
+      LLVM_DEBUG(dbgs() << "Found QC_E_LI " << *MI);
+      LLVM_DEBUG(dbgs() << "Followed by Load/Store " << *NextMI);
 
       MachineOperand &OffsetOp = NextMI->getOperand(2);
       if (OffsetOp.getImm() != 0)

>From a2c8d20760e3b693d7365a0f5f00bd267384bbdc Mon Sep 17 00:00:00 2001
From: Sam Elliott <aelliott at qti.qualcomm.com>
Date: Sat, 28 Mar 2026 02:27:35 -0700
Subject: [PATCH 4/4] rv32 only pass

---
 llvm/lib/Target/RISCV/RISCVTargetMachine.cpp | 5 +++--
 llvm/test/CodeGen/RISCV/O0-pipeline.ll       | 4 ++--
 llvm/test/CodeGen/RISCV/O3-pipeline.ll       | 4 ++--
 3 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVTargetMachine.cpp b/llvm/lib/Target/RISCV/RISCVTargetMachine.cpp
index f85046b6c1f83..e9e7e5f958e94 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetMachine.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetMachine.cpp
@@ -594,8 +594,9 @@ void RISCVPassConfig::addPreEmitPass2() {
   }
   addPass(createRISCVExpandPseudoPass());
 
-  // Add QC Relaxation Markers as late as possible.
-  addPass(createRISCVQCRelaxMarkingPass());
+  // Add QC Relaxation Markers as late as possible, and only for RV32
+  if (TM->getTargetTriple().isRISCV32())
+    addPass(createRISCVQCRelaxMarkingPass());
 
   // Schedule the expansion of AMOs at the last possible moment, avoiding the
   // possibility for other passes to break the requirements for forward
diff --git a/llvm/test/CodeGen/RISCV/O0-pipeline.ll b/llvm/test/CodeGen/RISCV/O0-pipeline.ll
index 84c005fb539dd..049d39bef2493 100644
--- a/llvm/test/CodeGen/RISCV/O0-pipeline.ll
+++ b/llvm/test/CodeGen/RISCV/O0-pipeline.ll
@@ -1,6 +1,6 @@
 ; RUN: llc -mtriple=riscv32 -O0 -debug-pass=Structure < %s -o /dev/null 2>&1 | \
 ; RUN:   grep -v "Verify generated machine code" | \
-; RUN:   FileCheck %s --check-prefixes=CHECK
+; RUN:   FileCheck %s --check-prefixes=CHECK,RV32
 ; RUN: llc -mtriple=riscv64 -O0 -debug-pass=Structure < %s -o /dev/null 2>&1 | \
 ; RUN:   grep -v "Verify generated machine code" | \
 ; RUN:   FileCheck %s --check-prefixes=CHECK
@@ -76,7 +76,7 @@
 ; CHECK-NEXT:       Machine Optimization Remark Emitter
 ; CHECK-NEXT:       Stack Frame Layout Analysis
 ; CHECK-NEXT:       RISC-V pseudo instruction expansion pass
-; CHECK-NEXT:       RISC-V QC Relaxation Marking
+; RV32-NEXT:        RISC-V QC Relaxation Marking
 ; CHECK-NEXT:       RISC-V atomic pseudo instruction expansion pass
 ; CHECK-NEXT:       Unpack machine instruction bundles
 ; CHECK-NEXT:       Lazy Machine Block Frequency Analysis
diff --git a/llvm/test/CodeGen/RISCV/O3-pipeline.ll b/llvm/test/CodeGen/RISCV/O3-pipeline.ll
index 91b0005d4fd6e..6244a00fbc15a 100644
--- a/llvm/test/CodeGen/RISCV/O3-pipeline.ll
+++ b/llvm/test/CodeGen/RISCV/O3-pipeline.ll
@@ -1,6 +1,6 @@
 ; RUN: llc -mtriple=riscv32 -O3 -debug-pass=Structure < %s -o /dev/null 2>&1 | \
 ; RUN:   grep -v "Verify generated machine code" | \
-; RUN:   FileCheck %s --check-prefixes=CHECK
+; RUN:   FileCheck %s --check-prefixes=CHECK,RV32
 ; RUN: llc -mtriple=riscv64 -O3 -debug-pass=Structure < %s -o /dev/null 2>&1 | \
 ; RUN:   grep -v "Verify generated machine code" | \
 ; RUN:   FileCheck %s --check-prefixes=CHECK,RV64
@@ -227,7 +227,7 @@
 ; CHECK-NEXT:       RISC-V Zcmp move merging pass
 ; CHECK-NEXT:       RISC-V Zcmp Push/Pop optimization pass
 ; CHECK-NEXT:       RISC-V pseudo instruction expansion pass
-; CHECK-NEXT:       RISC-V QC Relaxation Marking
+; RV32-NEXT:        RISC-V QC Relaxation Marking
 ; CHECK-NEXT:       RISC-V atomic pseudo instruction expansion pass
 ; CHECK-NEXT:       Unpack machine instruction bundles
 ; CHECK-NEXT:       Lazy Machine Block Frequency Analysis



More information about the llvm-commits mailing list