[llvm] [RISCV][GlobalISel] Legalize readcyclecounter/readsteadycounter (PR #217535)

Kane Wang via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 23 19:36:47 PDT 2026


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

>From 7a017b0161d0c2876e1467eda006c19eeb5947ce Mon Sep 17 00:00:00 2001
From: Kane Wang <wangqiang1 at kylinos.cn>
Date: Thu, 20 Aug 2026 14:18:37 +0800
Subject: [PATCH 1/5] [RISCV][GlobalISel] Legalize
 readcyclecounter/readsteadycounter
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add a legalizer rule for G_READCYCLECOUNTER/G_READSTEADYCOUNTER. On RV64
they are legal and select via the existing tablegen Pats (rdcycle/rdtime).
On RV32 lower them to the ReadCounterWide target pseudo, which FinalizeISel
expands into the re-read-the-high-half loop (emitReadCounterWidePseudo) —
the same expansion SelectionDAG uses.
---
 .../Target/RISCV/GISel/RISCVLegalizerInfo.cpp | 41 +++++++++++++++++++
 .../Target/RISCV/GISel/RISCVLegalizerInfo.h   |  2 +
 .../GlobalISel/legalizer-info-validation.mir  |  9 ++--
 .../RISCV/GlobalISel/readcyclecounter.ll      | 24 +++++++++++
 .../RISCV/GlobalISel/readsteadycounter.ll     | 24 +++++++++++
 5 files changed, 96 insertions(+), 4 deletions(-)
 create mode 100644 llvm/test/CodeGen/RISCV/GlobalISel/readcyclecounter.ll
 create mode 100644 llvm/test/CodeGen/RISCV/GlobalISel/readsteadycounter.ll

diff --git a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
index 22f60dc0c1c24..57aead67c770e 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
+++ b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
@@ -542,6 +542,12 @@ RISCVLegalizerInfo::RISCVLegalizerInfo(const RISCVSubtarget &ST)
   getActionDefinitionsBuilder({G_DYN_STACKALLOC, G_STACKSAVE, G_STACKRESTORE})
       .lower();
 
+  // On RV64 the 64-bit counter CSRs (cycle/time) are read directly. On RV32
+  // they are custom-legally lowered to the ReadCounterWide target pseudo.
+  getActionDefinitionsBuilder({G_READCYCLECOUNTER, G_READSTEADYCOUNTER})
+      .legalFor(ST.is64Bit(), {s64})
+      .customFor(!ST.is64Bit(), {s64});
+
   // FP Operations
 
   // FIXME: Support s128 for rv32 when libcall handling is able to use sret.
@@ -889,6 +895,38 @@ bool RISCVLegalizerInfo::legalizeVAStart(MachineInstr &MI,
   return true;
 }
 
+bool RISCVLegalizerInfo::legalizeReadCounter(
+    MachineInstr &MI, MachineIRBuilder &MIRBuilder) const {
+  assert((MI.getOpcode() == TargetOpcode::G_READCYCLECOUNTER ||
+          MI.getOpcode() == TargetOpcode::G_READSTEADYCOUNTER) &&
+         "Unexpected opcode");
+  assert(!STI.is64Bit() && "READCYCLECOUNTER/READSTEADYCOUNTER only "
+                           "has custom type legalization on riscv32");
+
+  // On RV32 a 64-bit counter CSR must be read as two 32-bit halves. Lower to
+  // the ReadCounterWide target pseudo.
+  bool IsCycle = MI.getOpcode() == TargetOpcode::G_READCYCLECOUNTER;
+  int64_t LoCounter = IsCycle ? RISCVSysReg::cycle : RISCVSysReg::time;
+  int64_t HiCounter = IsCycle ? RISCVSysReg::cycleh : RISCVSysReg::timeh;
+
+  MachineRegisterInfo &MRI = *MIRBuilder.getMRI();
+  auto CreateGPR = [&]() {
+    Register R = MRI.createGenericVirtualRegister(LLT::scalar(32));
+    MRI.setRegClass(R, &RISCV::GPRRegClass);
+    return R;
+  };
+  Register LoReg = CreateGPR();
+  Register HiReg = CreateGPR();
+
+  Register DstReg = MI.getOperand(0).getReg();
+  MIRBuilder.setDebugLoc(MI.getDebugLoc());
+  MIRBuilder.buildInstr(RISCV::ReadCounterWide, {LoReg, HiReg},
+                        {LoCounter, HiCounter});
+  MIRBuilder.buildMergeValues(DstReg, {LoReg, HiReg});
+  MI.eraseFromParent();
+  return true;
+}
+
 bool RISCVLegalizerInfo::legalizeBRJT(MachineInstr &MI,
                                       MachineIRBuilder &MIRBuilder) const {
   MachineRegisterInfo &MRI = *MIRBuilder.getMRI();
@@ -1601,6 +1639,9 @@ bool RISCVLegalizerInfo::legalizeCustom(
     Helper.Observer.changedInstr(MI);
     return true;
   }
+  case TargetOpcode::G_READCYCLECOUNTER:
+  case TargetOpcode::G_READSTEADYCOUNTER:
+    return legalizeReadCounter(MI, MIRBuilder);
   case TargetOpcode::G_IS_FPCLASS: {
     Register GISFPCLASS = MI.getOperand(0).getReg();
     Register Src = MI.getOperand(1).getReg();
diff --git a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.h b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.h
index 4b2f794f3843a..13ff6f7b57a34 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.h
+++ b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.h
@@ -45,6 +45,8 @@ class RISCVLegalizerInfo : public LegalizerInfo {
   bool legalizeBitreverse(MachineInstr &MI, MachineIRBuilder &MIB) const;
   bool legalizeBRJT(MachineInstr &MI, MachineIRBuilder &MIRBuilder) const;
   bool legalizeVAStart(MachineInstr &MI, MachineIRBuilder &MIRBuilder) const;
+  bool legalizeReadCounter(MachineInstr &MI,
+                           MachineIRBuilder &MIRBuilder) const;
   bool legalizeVScale(MachineInstr &MI, MachineIRBuilder &MIB) const;
   bool legalizeExt(MachineInstr &MI, MachineIRBuilder &MIRBuilder) const;
   bool legalizeSplatVector(MachineInstr &MI, MachineIRBuilder &MIB) const;
diff --git a/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir b/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
index 1770bc6430eb9..653a6ee679cd2 100644
--- a/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/legalizer-info-validation.mir
@@ -197,11 +197,12 @@
 # DEBUG-NEXT: .. the first uncovered type index: 1, OK
 # DEBUG-NEXT: .. the first uncovered imm index: 0, OK
 # DEBUG-NEXT: G_READCYCLECOUNTER (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_READSTEADYCOUNTER (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: .. opcode {{[0-9]+}} is aliased to {{[0-9]+}}
+# DEBUG-NEXT: .. the first uncovered type index: 1, OK
+# DEBUG-NEXT: .. the first uncovered imm index: 0, OK
 
 # DEBUG-NEXT: G_LOAD (opcode {{[0-9]+}}): 2 type indices, 0 imm indices
 # DEBUG-NEXT: .. type index coverage check SKIPPED: user-defined predicate detected
diff --git a/llvm/test/CodeGen/RISCV/GlobalISel/readcyclecounter.ll b/llvm/test/CodeGen/RISCV/GlobalISel/readcyclecounter.ll
new file mode 100644
index 0000000000000..ff24b0da57b00
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/readcyclecounter.ll
@@ -0,0 +1,24 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv32 -global-isel < %s | FileCheck -check-prefix=RV32I %s
+; RUN: llc -mtriple=riscv64 -global-isel < %s | FileCheck -check-prefix=RV64I %s
+
+; Verify that we lower @llvm.readcyclecounter() correctly.
+
+define i64 @test_builtin_readcyclecounter() nounwind {
+; RV32I-LABEL: test_builtin_readcyclecounter:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:  .LBB0_1: # =>This Inner Loop Header: Depth=1
+; RV32I-NEXT:    rdcycleh a1
+; RV32I-NEXT:    rdcycle a0
+; RV32I-NEXT:    rdcycleh a2
+; RV32I-NEXT:    bne a1, a2, .LBB0_1
+; RV32I-NEXT:  # %bb.2:
+; RV32I-NEXT:    ret
+;
+; RV64I-LABEL: test_builtin_readcyclecounter:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    rdcycle a0
+; RV64I-NEXT:    ret
+  %1 = tail call i64 @llvm.readcyclecounter()
+  ret i64 %1
+}
diff --git a/llvm/test/CodeGen/RISCV/GlobalISel/readsteadycounter.ll b/llvm/test/CodeGen/RISCV/GlobalISel/readsteadycounter.ll
new file mode 100644
index 0000000000000..49b99d8f302f4
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/GlobalISel/readsteadycounter.ll
@@ -0,0 +1,24 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv32 -global-isel < %s | FileCheck -check-prefix=RV32I %s
+; RUN: llc -mtriple=riscv64 -global-isel < %s | FileCheck -check-prefix=RV64I %s
+
+; Verify that we lower @llvm.readsteadycounter() correctly.
+
+define i64 @test_builtin_readsteadycounter() nounwind {
+; RV32I-LABEL: test_builtin_readsteadycounter:
+; RV32I:       # %bb.0:
+; RV32I-NEXT:  .LBB0_1: # =>This Inner Loop Header: Depth=1
+; RV32I-NEXT:    rdtimeh a1
+; RV32I-NEXT:    rdtime a0
+; RV32I-NEXT:    rdtimeh a2
+; RV32I-NEXT:    bne a1, a2, .LBB0_1
+; RV32I-NEXT:  # %bb.2:
+; RV32I-NEXT:    ret
+;
+; RV64I-LABEL: test_builtin_readsteadycounter:
+; RV64I:       # %bb.0:
+; RV64I-NEXT:    rdtime a0
+; RV64I-NEXT:    ret
+  %1 = tail call i64 @llvm.readsteadycounter()
+  ret i64 %1
+}

>From 8cdd89f1b726e41c0dd2a7d32a58edb4f656ff9d Mon Sep 17 00:00:00 2001
From: Kane Wang <wangqiang1 at kylinos.cn>
Date: Thu, 20 Aug 2026 17:13:24 +0800
Subject: [PATCH 2/5] [RISCV][GlobalISel] Emit readcyclecounter loop directly
 instead of via pseudo

GlobalISel can introduce control flow directly in the legalizer, so emit
the re-read-the-high-half csrrs/bne loop there rather than lowering to the
ReadCounterWide target pseudo and deferring to FinalizeISel. That pseudo
only exists to work around SelectionDAG's inability to introduce control
flow during lowering; GISel does not share that limitation.

Assisted-by: Claude
---
 .../Target/RISCV/GISel/RISCVLegalizerInfo.cpp | 98 ++++++++++++++++---
 .../Target/RISCV/GISel/RISCVLegalizerInfo.h   |  4 +-
 2 files changed, 86 insertions(+), 16 deletions(-)

diff --git a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
index 57aead67c770e..05fb374a3bb15 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
+++ b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
@@ -543,7 +543,8 @@ RISCVLegalizerInfo::RISCVLegalizerInfo(const RISCVSubtarget &ST)
       .lower();
 
   // On RV64 the 64-bit counter CSRs (cycle/time) are read directly. On RV32
-  // they are custom-legally lowered to the ReadCounterWide target pseudo.
+  // they are custom-legally lowered to a re-read-the-high-half loop (see
+  // legalizeReadCounter).
   getActionDefinitionsBuilder({G_READCYCLECOUNTER, G_READSTEADYCOUNTER})
       .legalFor(ST.is64Bit(), {s64})
       .customFor(!ST.is64Bit(), {s64});
@@ -896,34 +897,103 @@ bool RISCVLegalizerInfo::legalizeVAStart(MachineInstr &MI,
 }
 
 bool RISCVLegalizerInfo::legalizeReadCounter(
-    MachineInstr &MI, MachineIRBuilder &MIRBuilder) const {
+    MachineInstr &MI, MachineIRBuilder &MIRBuilder,
+    GISelChangeObserver &Observer) const {
   assert((MI.getOpcode() == TargetOpcode::G_READCYCLECOUNTER ||
           MI.getOpcode() == TargetOpcode::G_READSTEADYCOUNTER) &&
          "Unexpected opcode");
   assert(!STI.is64Bit() && "READCYCLECOUNTER/READSTEADYCOUNTER only "
                            "has custom type legalization on riscv32");
 
-  // On RV32 a 64-bit counter CSR must be read as two 32-bit halves. Lower to
-  // the ReadCounterWide target pseudo.
-  bool IsCycle = MI.getOpcode() == TargetOpcode::G_READCYCLECOUNTER;
-  int64_t LoCounter = IsCycle ? RISCVSysReg::cycle : RISCVSysReg::time;
-  int64_t HiCounter = IsCycle ? RISCVSysReg::cycleh : RISCVSysReg::timeh;
+  // On RV32 a 64-bit counter CSR must be read as two 32-bit halves. Because
+  // the count may wrap between the two reads, re-read the high half and loop
+  // until the two high reads agree.
+  int64_t LoCounter, HiCounter;
+  if (MI.getOpcode() == TargetOpcode::G_READCYCLECOUNTER) {
+    LoCounter = RISCVSysReg::cycle;
+    HiCounter = RISCVSysReg::cycleh;
+  } else {
+    LoCounter = RISCVSysReg::time;
+    HiCounter = RISCVSysReg::timeh;
+  }
 
+  MachineBasicBlock *BB = MI.getParent();
+  MachineFunction &MF = *BB->getParent();
+  const BasicBlock *LLVMBB = BB->getBasicBlock();
+  DebugLoc DL = MI.getDebugLoc();
   MachineRegisterInfo &MRI = *MIRBuilder.getMRI();
+
+  // Split BB into an entry that falls through into a loop block, and a done
+  // block that receives the remainder of BB and its original successors.
+  MachineFunction::iterator It = std::next(BB->getIterator());
+  MachineBasicBlock *LoopMBB = MF.CreateMachineBasicBlock(LLVMBB);
+  MachineBasicBlock *DoneMBB = MF.CreateMachineBasicBlock(LLVMBB);
+  MF.insert(It, LoopMBB);
+  MF.insert(It, DoneMBB);
+
+  // Splice the instructions after the readcyclecounter into DoneMBB, notifying
+  // the observer about each moved instruction so CSEInfo stays consistent.
+  SmallVector<MachineInstr *, 4> MovedInstrs;
+  for (MachineBasicBlock::iterator I = std::next(MI.getIterator()),
+                                   E = BB->end();
+       I != E; ++I)
+    MovedInstrs.push_back(&*I);
+  for (MachineInstr *MovedMI : MovedInstrs)
+    Observer.changingInstr(*MovedMI);
+  DoneMBB->splice(DoneMBB->begin(), BB,
+                  std::next(MachineBasicBlock::iterator(MI)), BB->end());
+  for (MachineInstr *MovedMI : MovedInstrs)
+    Observer.changedInstr(*MovedMI);
+  DoneMBB->transferSuccessorsAndUpdatePHIs(BB);
+  BB->addSuccessor(LoopMBB);
+
+  LLT S32 = LLT::scalar(32);
+  // Generic vregs carry the s32 type for G_MERGE_VALUES below, but are also
+  // constrained to GPR so the target CSRRS/BNE instructions satisfy the
+  // verifier's register-class constraints.
   auto CreateGPR = [&]() {
-    Register R = MRI.createGenericVirtualRegister(LLT::scalar(32));
+    Register R = MRI.createGenericVirtualRegister(S32);
     MRI.setRegClass(R, &RISCV::GPRRegClass);
     return R;
   };
-  Register LoReg = CreateGPR();
   Register HiReg = CreateGPR();
+  Register LoReg = CreateGPR();
+  Register ReadAgainReg = CreateGPR();
+
+  // read:
+  //   csrrs HiReg, counterh    # high word
+  //   csrrs LoReg, counter     # low word
+  //   csrrs ReadAgainReg, counterh
+  //   bne   HiReg, ReadAgainReg, read
+  // Build the target instructions fully before inserting so the change
+  // observer (CSEInfo) and the legalizer worklist see their final form;
+  // raw BuildMI or chaining after insertInstr would bypass the observer.
+  MIRBuilder.setInsertPt(*LoopMBB, LoopMBB->begin());
+  MIRBuilder.setDebugLoc(DL);
+  auto BuildCSRRS = [&](Register Dst, int64_t Csr) {
+    MachineInstrBuilder MIB = MIRBuilder.buildInstrNoInsert(RISCV::CSRRS);
+    MIB.addReg(Dst, RegState::Define).addImm(Csr).addReg(RISCV::X0);
+    MIRBuilder.insertInstr(MIB);
+  };
+  BuildCSRRS(HiReg, HiCounter);
+  BuildCSRRS(LoReg, LoCounter);
+  BuildCSRRS(ReadAgainReg, HiCounter);
 
+  MachineInstrBuilder BNE = MIRBuilder.buildInstrNoInsert(RISCV::BNE);
+  BNE.addReg(HiReg).addReg(ReadAgainReg).addMBB(LoopMBB);
+  MIRBuilder.insertInstr(BNE);
+
+  LoopMBB->addSuccessor(LoopMBB);
+  LoopMBB->addSuccessor(DoneMBB);
+
+  // Re-pair the two halves into the 64-bit result.
   Register DstReg = MI.getOperand(0).getReg();
-  MIRBuilder.setDebugLoc(MI.getDebugLoc());
-  MIRBuilder.buildInstr(RISCV::ReadCounterWide, {LoReg, HiReg},
-                        {LoCounter, HiCounter});
-  MIRBuilder.buildMergeValues(DstReg, {LoReg, HiReg});
+  Observer.erasingInstr(MI);
   MI.eraseFromParent();
+
+  MIRBuilder.setInsertPt(*DoneMBB, DoneMBB->begin());
+  MIRBuilder.setDebugLoc(DL);
+  MIRBuilder.buildMergeValues(DstReg, {LoReg, HiReg});
   return true;
 }
 
@@ -1641,7 +1711,7 @@ bool RISCVLegalizerInfo::legalizeCustom(
   }
   case TargetOpcode::G_READCYCLECOUNTER:
   case TargetOpcode::G_READSTEADYCOUNTER:
-    return legalizeReadCounter(MI, MIRBuilder);
+    return legalizeReadCounter(MI, MIRBuilder, Helper.Observer);
   case TargetOpcode::G_IS_FPCLASS: {
     Register GISFPCLASS = MI.getOperand(0).getReg();
     Register Src = MI.getOperand(1).getReg();
diff --git a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.h b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.h
index 13ff6f7b57a34..639e68401e2da 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.h
+++ b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.h
@@ -45,8 +45,8 @@ class RISCVLegalizerInfo : public LegalizerInfo {
   bool legalizeBitreverse(MachineInstr &MI, MachineIRBuilder &MIB) const;
   bool legalizeBRJT(MachineInstr &MI, MachineIRBuilder &MIRBuilder) const;
   bool legalizeVAStart(MachineInstr &MI, MachineIRBuilder &MIRBuilder) const;
-  bool legalizeReadCounter(MachineInstr &MI,
-                           MachineIRBuilder &MIRBuilder) const;
+  bool legalizeReadCounter(MachineInstr &MI, MachineIRBuilder &MIRBuilder,
+                           GISelChangeObserver &Observer) const;
   bool legalizeVScale(MachineInstr &MI, MachineIRBuilder &MIB) const;
   bool legalizeExt(MachineInstr &MI, MachineIRBuilder &MIRBuilder) const;
   bool legalizeSplatVector(MachineInstr &MI, MachineIRBuilder &MIB) const;

>From c9585dbbbdb75301ea5546002acfb598cbb1c699 Mon Sep 17 00:00:00 2001
From: Kane Wang <wangqiang1 at kylinos.cn>
Date: Thu, 20 Aug 2026 18:31:06 +0800
Subject: [PATCH 3/5] [RISCV][GlobalISel] Use BuildMI directly for
 readcyclecounter loop

The legalizer emits real target instructions (CSRRS/BNE), so use BuildMI
directly rather than routing them through MIRBuilder.buildInstrNoInsert.
---
 .../Target/RISCV/GISel/RISCVLegalizerInfo.cpp | 35 +++++++++----------
 1 file changed, 17 insertions(+), 18 deletions(-)

diff --git a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
index 05fb374a3bb15..e61612e45b67f 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
+++ b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
@@ -956,8 +956,8 @@ bool RISCVLegalizerInfo::legalizeReadCounter(
     MRI.setRegClass(R, &RISCV::GPRRegClass);
     return R;
   };
-  Register HiReg = CreateGPR();
   Register LoReg = CreateGPR();
+  Register HiReg = CreateGPR();
   Register ReadAgainReg = CreateGPR();
 
   // read:
@@ -965,23 +965,22 @@ bool RISCVLegalizerInfo::legalizeReadCounter(
   //   csrrs LoReg, counter     # low word
   //   csrrs ReadAgainReg, counterh
   //   bne   HiReg, ReadAgainReg, read
-  // Build the target instructions fully before inserting so the change
-  // observer (CSEInfo) and the legalizer worklist see their final form;
-  // raw BuildMI or chaining after insertInstr would bypass the observer.
-  MIRBuilder.setInsertPt(*LoopMBB, LoopMBB->begin());
-  MIRBuilder.setDebugLoc(DL);
-  auto BuildCSRRS = [&](Register Dst, int64_t Csr) {
-    MachineInstrBuilder MIB = MIRBuilder.buildInstrNoInsert(RISCV::CSRRS);
-    MIB.addReg(Dst, RegState::Define).addImm(Csr).addReg(RISCV::X0);
-    MIRBuilder.insertInstr(MIB);
-  };
-  BuildCSRRS(HiReg, HiCounter);
-  BuildCSRRS(LoReg, LoCounter);
-  BuildCSRRS(ReadAgainReg, HiCounter);
-
-  MachineInstrBuilder BNE = MIRBuilder.buildInstrNoInsert(RISCV::BNE);
-  BNE.addReg(HiReg).addReg(ReadAgainReg).addMBB(LoopMBB);
-  MIRBuilder.insertInstr(BNE);
+  // Emit the target instructions directly with BuildMI.
+  const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
+  BuildMI(LoopMBB, DL, TII->get(RISCV::CSRRS), HiReg)
+      .addImm(HiCounter)
+      .addReg(RISCV::X0);
+  BuildMI(LoopMBB, DL, TII->get(RISCV::CSRRS), LoReg)
+      .addImm(LoCounter)
+      .addReg(RISCV::X0);
+  BuildMI(LoopMBB, DL, TII->get(RISCV::CSRRS), ReadAgainReg)
+      .addImm(HiCounter)
+      .addReg(RISCV::X0);
+
+  BuildMI(LoopMBB, DL, TII->get(RISCV::BNE))
+      .addReg(HiReg)
+      .addReg(ReadAgainReg)
+      .addMBB(LoopMBB);
 
   LoopMBB->addSuccessor(LoopMBB);
   LoopMBB->addSuccessor(DoneMBB);

>From 034220c766670025c6c1bbf0b2f489789c28feec Mon Sep 17 00:00:00 2001
From: Kane Wang <wangqiang1 at kylinos.cn>
Date: Fri, 21 Aug 2026 10:26:19 +0800
Subject: [PATCH 4/5] [RISCV][GlobalISel] Use RISCVInstrInfo for
 readcyclecounter loop

Use STI.getInstrInfo() (RISCVInstrInfo*) instead of the generic
TargetInstrInfo from MF.getSubtarget().
---
 llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
index e61612e45b67f..c3058023851c6 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
+++ b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
@@ -966,7 +966,7 @@ bool RISCVLegalizerInfo::legalizeReadCounter(
   //   csrrs ReadAgainReg, counterh
   //   bne   HiReg, ReadAgainReg, read
   // Emit the target instructions directly with BuildMI.
-  const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo();
+  const RISCVInstrInfo *TII = STI.getInstrInfo();
   BuildMI(LoopMBB, DL, TII->get(RISCV::CSRRS), HiReg)
       .addImm(HiCounter)
       .addReg(RISCV::X0);

>From 8928f3e7d1fa77d6ba98ec210baafe23c4e44d4c Mon Sep 17 00:00:00 2001
From: Kane Wang <wangqiang1 at kylinos.cn>
Date: Mon, 24 Aug 2026 10:36:03 +0800
Subject: [PATCH 5/5] [RISCV][GlobalISel][NFC] Drop MovedInstrs vector in
 readcyclecounter loop

Iterate BB before the splice and DoneMBB after it directly, instead of
collecting moved instructions into a SmallVector first.
---
 llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
index c3058023851c6..1175468e7f2ca 100644
--- a/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
+++ b/llvm/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp
@@ -933,17 +933,14 @@ bool RISCVLegalizerInfo::legalizeReadCounter(
 
   // Splice the instructions after the readcyclecounter into DoneMBB, notifying
   // the observer about each moved instruction so CSEInfo stays consistent.
-  SmallVector<MachineInstr *, 4> MovedInstrs;
   for (MachineBasicBlock::iterator I = std::next(MI.getIterator()),
                                    E = BB->end();
        I != E; ++I)
-    MovedInstrs.push_back(&*I);
-  for (MachineInstr *MovedMI : MovedInstrs)
-    Observer.changingInstr(*MovedMI);
+    Observer.changingInstr(*I);
   DoneMBB->splice(DoneMBB->begin(), BB,
                   std::next(MachineBasicBlock::iterator(MI)), BB->end());
-  for (MachineInstr *MovedMI : MovedInstrs)
-    Observer.changedInstr(*MovedMI);
+  for (MachineInstr &MovedMI : DoneMBB->instrs())
+    Observer.changedInstr(MovedMI);
   DoneMBB->transferSuccessorsAndUpdatePHIs(BB);
   BB->addSuccessor(LoopMBB);
 



More information about the llvm-commits mailing list