[llvm] [RISCV][GlobalISel] Legalize readcyclecounter/readsteadycounter (PR #217535)
Kane Wang via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 19 23:28:04 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] [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
+}
More information about the llvm-commits
mailing list