[llvm] [RISCV] Add a Pass for adding %qc.access specifiers (PR #201938)

Sam Elliott via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 9 21:15:19 PDT 2026


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

>From f467e5f6e1636d916831971bf1d8f87bc7b1e01e Mon Sep 17 00:00:00 2001
From: Sam Elliott <aelliott at qti.qualcomm.com>
Date: Tue, 2 Jun 2026 11:28:02 -0700
Subject: [PATCH 1/2] [RISCV] Add a Pass for adding %qc.access specifiers

Qualcomm has some Access Relocation Markers, which are used to enable
more linker relaxations for the medlow code model. This change
implements a pass which will annotate loads and stores (accesses) which
are the single user of a `qc.e.li`-materialized address.

This is a follow-up to #188671.
---
 llvm/lib/Target/RISCV/CMakeLists.txt          |   1 +
 .../Target/RISCV/MCTargetDesc/RISCVBaseInfo.h |   1 +
 llvm/lib/Target/RISCV/RISCV.h                 |   3 +
 llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp     |   3 +
 llvm/lib/Target/RISCV/RISCVInstrInfo.cpp      |   3 +-
 llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp | 229 ++++++++++++
 llvm/lib/Target/RISCV/RISCVTargetMachine.cpp  |   5 +
 llvm/test/CodeGen/RISCV/O0-pipeline.ll        |   3 +-
 llvm/test/CodeGen/RISCV/O3-pipeline.ll        |   3 +-
 llvm/test/CodeGen/RISCV/qc-relax-marking.ll   | 326 ++++++++++++++++++
 10 files changed, 574 insertions(+), 3 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/lib/Target/RISCV/CMakeLists.txt b/llvm/lib/Target/RISCV/CMakeLists.txt
index 4a1a21cc9b5cd..afab81b2cf7b0 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/RISCVBaseInfo.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
index 0eba38f2c8962..677edb56020b4 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
@@ -474,6 +474,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/RISCV.h b/llvm/lib/Target/RISCV/RISCV.h
index 929a8d8f17b4f..8db90ee51c441 100644
--- a/llvm/lib/Target/RISCV/RISCV.h
+++ b/llvm/lib/Target/RISCV/RISCV.h
@@ -133,6 +133,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 13b54caed4d21..a5e35977f8407 100644
--- a/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
+++ b/llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp
@@ -1083,6 +1083,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 fc9aea14d83a2..9f6a530cd2f76 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
@@ -3608,7 +3608,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/RISCVQCRelaxMarking.cpp b/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp
new file mode 100644
index 0000000000000..e437feb118bb3
--- /dev/null
+++ b/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp
@@ -0,0 +1,229 @@
+//===-- 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(const MachineInstr &MI) {
+  return llvm::is_contained(
+      {RISCV::LW, RISCV::LH, RISCV::LHU, RISCV::LB, RISCV::LBU},
+      MI.getOpcode());
+}
+
+static bool isStore(const 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) {
+  if (skipFunction(MF.getFunction()))
+    return false;
+
+  // 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(dbgs() << "Found QC_E_LI " << *MI);
+      LLVM_DEBUG(dbgs() << "Followed by Load/Store " << *NextMI);
+
+      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(NextMI->getOperand(2))
+              .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 c0de8fe473ae1..0f3482e5fc08a 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetMachine.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetMachine.cpp
@@ -128,6 +128,7 @@ extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void LLVMInitializeRISCVTarget() {
   initializeRISCVDeadRegisterDefinitionsPass(*PR);
   initializeRISCVLateBranchOptPass(*PR);
   initializeRISCVMakeCompressibleOptPass(*PR);
+  initializeRISCVQCRelaxMarkingPass(*PR);
   initializeRISCVGatherScatterLoweringPass(*PR);
   initializeRISCVCodeGenPrepareLegacyPassPass(*PR);
   initializeRISCVPostRAExpandPseudoPass(*PR);
@@ -600,6 +601,10 @@ void RISCVPassConfig::addPreEmitPass2() {
   }
   addPass(createRISCVExpandPseudoPass());
 
+  // 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
   // 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..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,6 +76,7 @@
 ; CHECK-NEXT:       Machine Optimization Remark Emitter
 ; CHECK-NEXT:       Stack Frame Layout Analysis
 ; CHECK-NEXT:       RISC-V pseudo instruction expansion pass
+; 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 149764ffedf9e..12b0895e0f158 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
@@ -222,6 +222,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
+; 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/qc-relax-marking.ll b/llvm/test/CodeGen/RISCV/qc-relax-marking.ll
new file mode 100644
index 0000000000000..9e6ee282260b9
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/qc-relax-marking.ll
@@ -0,0 +1,326 @@
+; 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
+}
+
+define zeroext i8 @load_unsigned_byte_optnone() nounwind optnone noinline {
+; NO-C-LABEL: load_unsigned_byte_optnone:
+; NO-C:       # %bb.0:
+; NO-C-NEXT:    qc.e.li a0, global
+; NO-C-NEXT:    lbu a0, 0(a0)
+; NO-C-NEXT:    ret
+;
+; WITH-C-LABEL: load_unsigned_byte_optnone:
+; WITH-C:       # %bb.0:
+; WITH-C-NEXT:    qc.e.li a0, global
+; WITH-C-NEXT:    lbu a0, 0(a0)
+; WITH-C-NEXT:    ret
+  %1 = load i8, ptr @global, align 1
+  ret i8 %1
+}

>From 8830128841eee11fba5c3bf948a952d9d5564f87 Mon Sep 17 00:00:00 2001
From: Sam Elliott <aelliott at qti.qualcomm.com>
Date: Tue, 9 Jun 2026 15:00:54 -0700
Subject: [PATCH 2/2] Address review feedback

---
 llvm/lib/Target/RISCV/RISCVInstrInfo.cpp      |  3 ++-
 llvm/lib/Target/RISCV/RISCVInstrPredicates.td | 25 +++++++++++++++++++
 llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp | 14 ++---------
 3 files changed, 29 insertions(+), 13 deletions(-)

diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
index 9f6a530cd2f76..99d90661fcdad 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
@@ -3609,7 +3609,8 @@ RISCVInstrInfo::getSerializableDirectMachineOperandTargetFlags() const {
       {MO_TLSDESC_LOAD_LO, "riscv-tlsdesc-load-lo"},
       {MO_TLSDESC_ADD_LO, "riscv-tlsdesc-add-lo"},
       {MO_TLSDESC_CALL, "riscv-tlsdesc-call"},
-      {MO_QC_ACCESS, "riscv-qc-access"}};
+      {MO_QC_ACCESS, "riscv-qc-access"},
+  };
   return ArrayRef(TargetFlags);
 }
 bool RISCVInstrInfo::isFunctionSafeToOutlineFrom(
diff --git a/llvm/lib/Target/RISCV/RISCVInstrPredicates.td b/llvm/lib/Target/RISCV/RISCVInstrPredicates.td
index 60d8657d7000c..367f91bb9902f 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrPredicates.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrPredicates.td
@@ -217,3 +217,28 @@ def ignoresVXRM
                         ]),
                         MCReturnStatement<CheckImmOperand<3, 0>>>],
                       MCReturnStatement<FalsePred>>>;
+
+// RVI Base Loads, only those using simm12(reg) for addressing
+def isBaseLoad
+    : TIIPredicate<"isBaseLoad",
+                   MCReturnStatement<
+                     CheckOpcode<[
+                       LD,
+                       LW,
+                       LWU,
+                       LH,
+                       LHU,
+                       LB,
+                       LBU
+                     ]>>>;
+
+// RVI Base Stores, only those using simm12(reg) for addressing
+def isBaseStore
+    : TIIPredicate<"isBaseStore",
+                   MCReturnStatement<
+                     CheckOpcode<[
+                       SD,
+                       SW,
+                       SH,
+                       SB,
+                     ]>>>;
diff --git a/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp b/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp
index e437feb118bb3..d683b36ac851f 100644
--- a/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp
+++ b/llvm/lib/Target/RISCV/RISCVQCRelaxMarking.cpp
@@ -71,16 +71,6 @@ FunctionPass *llvm::createRISCVQCRelaxMarkingPass() {
   return new RISCVQCRelaxMarking();
 }
 
-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(const 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());
 }
@@ -171,7 +161,7 @@ bool RISCVQCRelaxMarking::runOnMachineFunction(MachineFunction &MF) {
 
       // Looking for QC.E.LI followed by a load or store
       if (MI->getOpcode() != RISCV::QC_E_LI ||
-          !(isLoad(*NextMI) || isStore(*NextMI)))
+          !(RISCVInstrInfo::isBaseLoad(*NextMI) || RISCVInstrInfo::isBaseStore(*NextMI)))
         continue;
 
       LLVM_DEBUG(dbgs() << "Found QC_E_LI " << *MI);
@@ -183,7 +173,7 @@ bool RISCVQCRelaxMarking::runOnMachineFunction(MachineFunction &MF) {
         continue;
 
       // This is unsafe for stores where the access address is being stored.
-      if (isStore(*NextMI) &&
+      if (RISCVInstrInfo::isBaseStore(*NextMI) &&
           MI->getOperand(0).getReg() == NextMI->getOperand(0).getReg())
         continue;
 



More information about the llvm-commits mailing list