[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