[llvm] [RISCV][GlobalISel] Legalize and select G_PREFETCH (PR #215466)

Kane Wang via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 12 02:44:30 PDT 2026


https://github.com/ReVe1uv updated https://github.com/llvm/llvm-project/pull/215466

>From d39c71f759491dc330edffc8d4953d363e1e7206 Mon Sep 17 00:00:00 2001
From: Kane Wang <wangqiang1 at kylinos.cn>
Date: Tue, 11 Aug 2026 14:16:35 +0800
Subject: [PATCH] [RISCV][GlobalISel] Legalize and select G_PREFETCH

GlobalISel aborted on llvm.prefetch ("unable to legalize G_PREFETCH")
while SDAG lowers it to prefetch.r/w/i. Add a legalizer rule marking
G_PREFETCH legal for p0 addresses, and a custom selector since the
SDAG patterns use the AddrRegImmLsb00000 complex pattern that the GISel
pattern importer does not auto-translate. The selector picks
prefetch.r/w/i from the rw/cache-type immediates and folds a
simm12_lsb00000 offset when present, matching SDAG.
---
 .../RISCV/GISel/RISCVInstructionSelector.cpp  |  77 ++++++++++++
 .../Target/RISCV/GISel/RISCVLegalizerInfo.cpp |   2 +
 .../GlobalISel/legalizer-info-validation.mir  |   4 +-
 .../test/CodeGen/RISCV/GlobalISel/prefetch.ll | 116 ++++++++++++++++++
 4 files changed, 197 insertions(+), 2 deletions(-)
 create mode 100644 llvm/test/CodeGen/RISCV/GlobalISel/prefetch.ll

diff --git a/llvm/lib/Target/RISCV/GISel/RISCVInstructionSelector.cpp b/llvm/lib/Target/RISCV/GISel/RISCVInstructionSelector.cpp
index 16af89c715861..5e8e7fd4c18f5 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVInstructionSelector.cpp
+++ b/llvm/lib/Target/RISCV/GISel/RISCVInstructionSelector.cpp
@@ -98,6 +98,7 @@ class RISCVInstructionSelector : public InstructionSelector {
   bool selectIntrinsic(MachineInstr &I) const;
   bool selectExtractSubvector(MachineInstr &MI) const;
   bool selectInsertSubVector(MachineInstr &I) const;
+  bool selectPrefetch(MachineInstr &MI) const;
   ComplexRendererFns selectShiftMask(MachineOperand &Root,
                                      unsigned ShiftWidth) const;
   ComplexRendererFns selectShiftMaskXLen(MachineOperand &Root) const {
@@ -107,6 +108,7 @@ class RISCVInstructionSelector : public InstructionSelector {
     return selectShiftMask(Root, 32);
   }
   ComplexRendererFns selectAddrRegImm(MachineOperand &Root) const;
+  ComplexRendererFns selectAddrRegImmLsb00000(MachineOperand &Root) const;
 
   ComplexRendererFns selectSExtBits(MachineOperand &Root, unsigned Bits) const;
   template <unsigned Bits>
@@ -593,6 +595,42 @@ RISCVInstructionSelector::selectAddrRegImm(MachineOperand &Root) const {
            [=](MachineInstrBuilder &MIB) { MIB.addImm(0); }}};
 }
 
+InstructionSelector::ComplexRendererFns
+RISCVInstructionSelector::selectAddrRegImmLsb00000(MachineOperand &Root) const {
+  if (!Root.isReg())
+    return std::nullopt;
+
+  MachineInstr *RootDef = MRI->getVRegDef(Root.getReg());
+  if (RootDef->getOpcode() == TargetOpcode::G_FRAME_INDEX) {
+    return {{
+        [=](MachineInstrBuilder &MIB) { MIB.add(RootDef->getOperand(1)); },
+        [=](MachineInstrBuilder &MIB) { MIB.addImm(0); },
+    }};
+  }
+
+  if (isBaseWithConstantOffset(Root, *MRI)) {
+    MachineOperand &LHS = RootDef->getOperand(1);
+    MachineOperand &RHS = RootDef->getOperand(2);
+    MachineInstr *LHSDef = MRI->getVRegDef(LHS.getReg());
+    MachineInstr *RHSDef = MRI->getVRegDef(RHS.getReg());
+    int64_t RHSC = RHSDef->getOperand(1).getCImm()->getSExtValue();
+    if (isInt<12>(RHSC) && (RHSC % 32 == 0)) {
+      if (LHSDef->getOpcode() == TargetOpcode::G_FRAME_INDEX)
+        return {{
+            [=](MachineInstrBuilder &MIB) { MIB.add(LHSDef->getOperand(1)); },
+            [=](MachineInstrBuilder &MIB) { MIB.addImm(RHSC); },
+        }};
+      return {{[=](MachineInstrBuilder &MIB) { MIB.add(LHS); },
+               [=](MachineInstrBuilder &MIB) { MIB.addImm(RHSC); }}};
+    }
+  }
+
+  return {{[=](MachineInstrBuilder &MIB) { MIB.addReg(Root.getReg()); },
+           [=](MachineInstrBuilder &MIB) { MIB.addImm(0); }}};
+// TODO: fold large-constant offsets (ADDI adjustment / Hi-Lo12 split) like
+// SDAG's SelectAddrRegImmLsb00000; load/store share this gap.
+}
+
 /// Returns the RISCVCC::CondCode that corresponds to the CmpInst::Predicate CC.
 /// CC Must be an ICMP Predicate.
 static RISCVCC::CondCode getRISCVCCFromICmp(CmpInst::Predicate CC) {
@@ -1444,11 +1482,50 @@ bool RISCVInstructionSelector::select(MachineInstr &MI) {
     return selectExtractSubvector(MI);
   case TargetOpcode::G_INSERT_SUBVECTOR:
     return selectInsertSubVector(MI);
+  case TargetOpcode::G_PREFETCH:
+    return selectPrefetch(MI);
   default:
     return false;
   }
 }
 
+bool RISCVInstructionSelector::selectPrefetch(MachineInstr &MI) const {
+  assert(MI.getOpcode() == TargetOpcode::G_PREFETCH);
+  // G_PREFETCH operands: 0=address, 1=rw, 2=locality, 3=cache type.
+  int64_t IsWrite = MI.getOperand(1).getImm();
+  int64_t IsData = MI.getOperand(3).getImm();
+  unsigned Opc;
+  if (!IsData)
+    Opc = RISCV::PREFETCH_I;
+  else if (!IsWrite)
+    Opc = RISCV::PREFETCH_R;
+  else
+    Opc = RISCV::PREFETCH_W;
+
+  // Can only handle AddressSpace 0.
+  LLT PtrTy = MRI->getType(MI.getOperand(0).getReg());
+  if (PtrTy.getAddressSpace() != 0)
+    return false;
+
+  // Fold a simm12_lsb00000 offset into the prefetch immediate when possible;
+  // otherwise use the address as-is with a zero offset. Mirrors how load/store
+  // use selectAddrRegImm.
+  auto AddrModeFns = selectAddrRegImmLsb00000(MI.getOperand(0));
+  if (!AddrModeFns)
+    return false;
+
+  MachineInstrBuilder NewInst =
+      BuildMI(*MI.getParent(), MI, MI.getDebugLoc(), TII.get(Opc));
+  NewInst.setMIFlags(MI.getFlags());
+  NewInst.cloneMemRefs(MI);
+  for (auto &Fn : *AddrModeFns)
+    Fn(NewInst);
+
+  MI.eraseFromParent();
+  constrainSelectedInstRegOperands(*NewInst, TII, TRI, RBI);
+  return true;
+}
+
 bool RISCVInstructionSelector::selectUnmergeValues(MachineInstr &MI) const {
   assert(MI.getOpcode() == TargetOpcode::G_UNMERGE_VALUES);
 
diff --git a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
index c5459d862b03f..78d96d69344e6 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
+++ b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
@@ -777,6 +777,8 @@ RISCVLegalizerInfo::RISCVLegalizerInfo(const RISCVSubtarget &ST)
       .clampScalar(0, sXLen, sXLen)
       .unsupported();
 
+  getActionDefinitionsBuilder(G_PREFETCH).legalFor({p0});
+
   LegalityPredicate InsertVectorEltPred = [=](const LegalityQuery &Query) {
     LLT VecTy = Query.Types[0];
     LLT EltTy = Query.Types[1];
diff --git a/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir b/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
index 1770bc6430eb9..280ec7cd1e0f5 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
@@ -321,8 +321,8 @@
 # DEBUG-NEXT: .. type index coverage check SKIPPED: user-defined predicate detected
 # DEBUG-NEXT: .. imm index coverage check SKIPPED: user-defined predicate detected
 # DEBUG-NEXT: G_PREFETCH (opcode {{[0-9]+}}): 1 type index, 0 imm indices
-# DEBUG-NEXT: .. type index coverage check SKIPPED: no rules defined
-# DEBUG-NEXT: .. imm index coverage check SKIPPED: no rules defined
+# DEBUG-NEXT: .. the first uncovered type index: 1, OK
+# DEBUG-NEXT: .. the first uncovered imm index: 0, OK
 # DEBUG-NEXT: G_BRCOND (opcode {{[0-9]+}}): 1 type index, 0 imm indices
 # DEBUG-NEXT: .. the first uncovered type index: 1, OK
 # DEBUG-NEXT: .. the first uncovered imm index: 0, OK
diff --git a/llvm/test/CodeGen/RISCV/GlobalISel/prefetch.ll b/llvm/test/CodeGen/RISCV/GlobalISel/prefetch.ll
new file mode 100644
index 0000000000000..c725b37c0812f
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/prefetch.ll
@@ -0,0 +1,116 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv32 -mattr=+zicbop -global-isel -global-isel-abort=1 \
+; RUN:  < %s | FileCheck %s --check-prefix=RV32ZICBOP
+; RUN: llc -mtriple=riscv64 -mattr=+zicbop -global-isel -global-isel-abort=1 \
+; RUN:  < %s | FileCheck %s --check-prefix=RV64ZICBOP
+; RUN: llc -mtriple=riscv64 -global-isel -global-isel-abort=1 \
+; RUN:  < %s | FileCheck %s --check-prefix=RV64
+
+; prefetch hints are available even without Zicbop per the psABI, so the
+; RV64 run (no +zicbop) still emits prefetch.r/w/i.
+
+define void @prefetch_r(ptr %a) nounwind {
+; RV32ZICBOP-LABEL: prefetch_r:
+; RV32ZICBOP:       # %bb.0:
+; RV32ZICBOP-NEXT:    prefetch.r 0(a0)
+; RV32ZICBOP-NEXT:    ret
+;
+; RV64ZICBOP-LABEL: prefetch_r:
+; RV64ZICBOP:       # %bb.0:
+; RV64ZICBOP-NEXT:    prefetch.r 0(a0)
+; RV64ZICBOP-NEXT:    ret
+;
+; RV64-LABEL: prefetch_r:
+; RV64:       # %bb.0:
+; RV64-NEXT:    prefetch.r 0(a0)
+; RV64-NEXT:    ret
+  call void @llvm.prefetch(ptr %a, i32 0, i32 3, i32 1)
+  ret void
+}
+
+define void @prefetch_w(ptr %a) nounwind {
+; RV32ZICBOP-LABEL: prefetch_w:
+; RV32ZICBOP:       # %bb.0:
+; RV32ZICBOP-NEXT:    prefetch.w 0(a0)
+; RV32ZICBOP-NEXT:    ret
+;
+; RV64ZICBOP-LABEL: prefetch_w:
+; RV64ZICBOP:       # %bb.0:
+; RV64ZICBOP-NEXT:    prefetch.w 0(a0)
+; RV64ZICBOP-NEXT:    ret
+;
+; RV64-LABEL: prefetch_w:
+; RV64:       # %bb.0:
+; RV64-NEXT:    prefetch.w 0(a0)
+; RV64-NEXT:    ret
+  call void @llvm.prefetch(ptr %a, i32 1, i32 3, i32 1)
+  ret void
+}
+
+define void @prefetch_i(ptr %a) nounwind {
+; RV32ZICBOP-LABEL: prefetch_i:
+; RV32ZICBOP:       # %bb.0:
+; RV32ZICBOP-NEXT:    prefetch.i 0(a0)
+; RV32ZICBOP-NEXT:    ret
+;
+; RV64ZICBOP-LABEL: prefetch_i:
+; RV64ZICBOP:       # %bb.0:
+; RV64ZICBOP-NEXT:    prefetch.i 0(a0)
+; RV64ZICBOP-NEXT:    ret
+;
+; RV64-LABEL: prefetch_i:
+; RV64:       # %bb.0:
+; RV64-NEXT:    prefetch.i 0(a0)
+; RV64-NEXT:    ret
+  call void @llvm.prefetch(ptr %a, i32 0, i32 3, i32 0)
+  ret void
+}
+
+; A constant offset that is a multiple of 32 (simm12_lsb00000) folds into the
+; prefetch immediate.
+define void @prefetch_r_aligned_offset(ptr %a) nounwind {
+; RV32ZICBOP-LABEL: prefetch_r_aligned_offset:
+; RV32ZICBOP:       # %bb.0:
+; RV32ZICBOP-NEXT:    prefetch.r 64(a0)
+; RV32ZICBOP-NEXT:    ret
+;
+; RV64ZICBOP-LABEL: prefetch_r_aligned_offset:
+; RV64ZICBOP:       # %bb.0:
+; RV64ZICBOP-NEXT:    prefetch.r 64(a0)
+; RV64ZICBOP-NEXT:    ret
+;
+; RV64-LABEL: prefetch_r_aligned_offset:
+; RV64:       # %bb.0:
+; RV64-NEXT:    prefetch.r 64(a0)
+; RV64-NEXT:    ret
+  %q = getelementptr i8, ptr %a, i32 64
+  call void @llvm.prefetch(ptr %q, i32 0, i32 3, i32 1)
+  ret void
+}
+
+; An offset that is not a multiple of 32 cannot be encoded in simm12_lsb00000;
+; the address is materialized with an ADDI and a zero offset is used.
+define void @prefetch_r_nonaligned_offset(ptr %a) nounwind {
+; RV32ZICBOP-LABEL: prefetch_r_nonaligned_offset:
+; RV32ZICBOP:       # %bb.0:
+; RV32ZICBOP-NEXT:    addi a0, a0, 4
+; RV32ZICBOP-NEXT:    prefetch.r 0(a0)
+; RV32ZICBOP-NEXT:    ret
+;
+; RV64ZICBOP-LABEL: prefetch_r_nonaligned_offset:
+; RV64ZICBOP:       # %bb.0:
+; RV64ZICBOP-NEXT:    addi a0, a0, 4
+; RV64ZICBOP-NEXT:    prefetch.r 0(a0)
+; RV64ZICBOP-NEXT:    ret
+;
+; RV64-LABEL: prefetch_r_nonaligned_offset:
+; RV64:       # %bb.0:
+; RV64-NEXT:    addi a0, a0, 4
+; RV64-NEXT:    prefetch.r 0(a0)
+; RV64-NEXT:    ret
+  %q = getelementptr i8, ptr %a, i32 4
+  call void @llvm.prefetch(ptr %q, i32 0, i32 3, i32 1)
+  ret void
+}
+
+declare void @llvm.prefetch(ptr, i32, i32, i32)



More information about the llvm-commits mailing list