Skip to content

[RISCV] Initial support for EarlyCSE #138812

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

HankChang736
Copy link
Contributor

This patch initially supports EarlyCSE for RISCV.

Note:

  • Add two TTI hook getTgtMemIntrinsic and getOrCreateResultFromMemIntrinsic
  • Add one test case intrinsics.ll in llvm/test/Transforms/EarlyCSE/RISCV/

@llvmbot
Copy link
Member

llvmbot commented May 7, 2025

@llvm/pr-subscribers-llvm-transforms

Author: Hank Chang (HankChang736)

Changes

This patch initially supports EarlyCSE for RISCV.

Note:

  • Add two TTI hook getTgtMemIntrinsic and getOrCreateResultFromMemIntrinsic
  • Add one test case intrinsics.ll in llvm/test/Transforms/EarlyCSE/RISCV/

Full diff: https://github.com/llvm/llvm-project/pull/138812.diff

3 Files Affected:

  • (modified) llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp (+45)
  • (modified) llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h (+8-1)
  • (added) llvm/test/Transforms/EarlyCSE/RISCV/intrinsics.ll (+42)
diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
index db2f1141ee4b7..0d7fa63f33999 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
@@ -15,6 +15,7 @@
 #include "llvm/CodeGen/TargetLowering.h"
 #include "llvm/CodeGen/ValueTypes.h"
 #include "llvm/IR/Instructions.h"
+#include "llvm/IR/IntrinsicsRISCV.h"
 #include "llvm/IR/PatternMatch.h"
 #include <cmath>
 #include <optional>
@@ -116,6 +117,50 @@ RISCVTTIImpl::getRISCVInstructionCost(ArrayRef<unsigned> OpCodes, MVT VT,
   return Cost;
 }
 
+Value *
+RISCVTTIImpl::getOrCreateResultFromMemIntrinsic(IntrinsicInst *Inst,
+                                                Type *ExpectedType) const {
+  Intrinsic::ID IID = Inst->getIntrinsicID();
+  switch (IID) {
+  default:
+    return nullptr;
+  // TODO: Add more memory intrinsic operations.
+  case Intrinsic::riscv_vle: {
+    if (Inst->getType() == ExpectedType)
+      return Inst;
+  }
+    return nullptr;
+  }
+}
+
+bool RISCVTTIImpl::getTgtMemIntrinsic(IntrinsicInst *Inst,
+                                      MemIntrinsicInfo &Info) const {
+  Intrinsic::ID IID = Inst->getIntrinsicID();
+  switch (IID) {
+  default:
+    return false;
+  case Intrinsic::riscv_vle: {
+    // Intrinsic interface:
+    // riscv_vle(merge, ptr, vl)
+    Info.ReadMem = true;
+    Info.WriteMem = false;
+    Info.PtrVal = Inst->getArgOperand(1);
+    Info.MatchingId = VECTOR_VLE_VSE;
+    break;
+  }
+  case Intrinsic::riscv_vse: {
+    // Intrinsic interface:
+    // riscv_vse(val, ptr, vl)
+    Info.ReadMem = false;
+    Info.WriteMem = true;
+    Info.PtrVal = Inst->getArgOperand(1);
+    Info.MatchingId = VECTOR_VLE_VSE;
+    break;
+  }
+  }
+  return true;
+}
+
 static InstructionCost getIntImmCostImpl(const DataLayout &DL,
                                          const RISCVSubtarget *ST,
                                          const APInt &Imm, Type *Ty,
diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
index 53529d077fd54..0eb2a033da9fb 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
@@ -37,6 +37,8 @@ class RISCVTTIImpl : public BasicTTIImplBase<RISCVTTIImpl> {
   const RISCVSubtarget *getST() const { return ST; }
   const RISCVTargetLowering *getTLI() const { return TLI; }
 
+  enum MemIntrinsicType { VECTOR_VLE_VSE };
+
   /// This function returns an estimate for VL to be used in VL based terms
   /// of the cost model.  For fixed length vectors, this is simply the
   /// vector length.  For scalable vectors, we return results consistent
@@ -156,7 +158,12 @@ class RISCVTTIImpl : public BasicTTIImplBase<RISCVTTIImpl> {
   void getPeelingPreferences(Loop *L, ScalarEvolution &SE,
                              TTI::PeelingPreferences &PP) const override;
 
-  unsigned getMinVectorRegisterBitWidth() const override {
+  Value *getOrCreateResultFromMemIntrinsic(IntrinsicInst *Inst,
+                                           Type *ExpectedType) const override;
+
+  bool getTgtMemIntrinsic(IntrinsicInst *Inst, MemIntrinsicInfo &Info) const;
+
+  unsigned getMinVectorRegisterBitWidth() const {
     return ST->useRVVForFixedLengthVectors() ? 16 : 0;
   }
 
diff --git a/llvm/test/Transforms/EarlyCSE/RISCV/intrinsics.ll b/llvm/test/Transforms/EarlyCSE/RISCV/intrinsics.ll
new file mode 100644
index 0000000000000..c00e822da3de4
--- /dev/null
+++ b/llvm/test/Transforms/EarlyCSE/RISCV/intrinsics.ll
@@ -0,0 +1,42 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -S -mtriple=riscv64 -mattr=+v -passes=early-cse -earlycse-debug-hash | FileCheck %s
+; RUN: opt < %s -S -mtriple=riscv64 -mattr=+v -aa-pipeline=basic-aa -passes='early-cse<memssa>' | FileCheck %s
+
+define <vscale x 2 x i32> @test_cse(ptr noundef %base) {
+; CHECK-LABEL: define <vscale x 2 x i32> @test_cse(
+; CHECK-SAME: ptr noundef [[BASE:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr [[BASE]], i64 8)
+; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vadd.nxv2i32.nxv2i32.i64(<vscale x 2 x i32> poison, <vscale x 2 x i32> [[TMP0]], <vscale x 2 x i32> [[TMP0]], i64 8)
+; CHECK-NEXT:    ret <vscale x 2 x i32> [[TMP1]]
+;
+entry:
+  %0 = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr %base, i64 8)
+  %1 = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr %base, i64 8)
+  %2 = call <vscale x 2 x i32> @llvm.riscv.vadd.nxv2i32.nxv2i32.i64(<vscale x 2 x i32> poison, <vscale x 2 x i32> %0, <vscale x 2 x i32> %1, i64 8)
+  ret <vscale x 2 x i32> %2
+}
+
+define <vscale x 2 x i32> @test_no_cse(ptr noundef %a, ptr noundef %b) {
+; CHECK-LABEL: define <vscale x 2 x i32> @test_no_cse(
+; CHECK-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr [[A]], i64 8)
+; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr [[B]], i64 8)
+; CHECK-NEXT:    [[TMP2:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vadd.nxv2i32.nxv2i32.i64(<vscale x 2 x i32> poison, <vscale x 2 x i32> [[TMP0]], <vscale x 2 x i32> [[TMP1]], i64 8)
+; CHECK-NEXT:    ret <vscale x 2 x i32> [[TMP2]]
+;
+entry:
+  %0 = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr %a, i64 8)
+  %1 = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr %b, i64 8)
+  %2 = call <vscale x 2 x i32> @llvm.riscv.vadd.nxv2i32.nxv2i32.i64(<vscale x 2 x i32> poison, <vscale x 2 x i32> %0, <vscale x 2 x i32> %1, i64 8)
+  ret <vscale x 2 x i32> %2
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read)
+declare <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32>, ptr captures(none), i64) #1
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn memory(none)
+declare <vscale x 2 x i32> @llvm.riscv.vadd.nxv2i32.nxv2i32.i64(<vscale x 2 x i32>, <vscale x 2 x i32>, <vscale x 2 x i32>, i64) #2
+
+

@llvmbot
Copy link
Member

llvmbot commented May 7, 2025

@llvm/pr-subscribers-backend-risc-v

Author: Hank Chang (HankChang736)

Changes

This patch initially supports EarlyCSE for RISCV.

Note:

  • Add two TTI hook getTgtMemIntrinsic and getOrCreateResultFromMemIntrinsic
  • Add one test case intrinsics.ll in llvm/test/Transforms/EarlyCSE/RISCV/

Full diff: https://github.com/llvm/llvm-project/pull/138812.diff

3 Files Affected:

  • (modified) llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp (+45)
  • (modified) llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h (+8-1)
  • (added) llvm/test/Transforms/EarlyCSE/RISCV/intrinsics.ll (+42)
diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
index db2f1141ee4b7..0d7fa63f33999 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.cpp
@@ -15,6 +15,7 @@
 #include "llvm/CodeGen/TargetLowering.h"
 #include "llvm/CodeGen/ValueTypes.h"
 #include "llvm/IR/Instructions.h"
+#include "llvm/IR/IntrinsicsRISCV.h"
 #include "llvm/IR/PatternMatch.h"
 #include <cmath>
 #include <optional>
@@ -116,6 +117,50 @@ RISCVTTIImpl::getRISCVInstructionCost(ArrayRef<unsigned> OpCodes, MVT VT,
   return Cost;
 }
 
+Value *
+RISCVTTIImpl::getOrCreateResultFromMemIntrinsic(IntrinsicInst *Inst,
+                                                Type *ExpectedType) const {
+  Intrinsic::ID IID = Inst->getIntrinsicID();
+  switch (IID) {
+  default:
+    return nullptr;
+  // TODO: Add more memory intrinsic operations.
+  case Intrinsic::riscv_vle: {
+    if (Inst->getType() == ExpectedType)
+      return Inst;
+  }
+    return nullptr;
+  }
+}
+
+bool RISCVTTIImpl::getTgtMemIntrinsic(IntrinsicInst *Inst,
+                                      MemIntrinsicInfo &Info) const {
+  Intrinsic::ID IID = Inst->getIntrinsicID();
+  switch (IID) {
+  default:
+    return false;
+  case Intrinsic::riscv_vle: {
+    // Intrinsic interface:
+    // riscv_vle(merge, ptr, vl)
+    Info.ReadMem = true;
+    Info.WriteMem = false;
+    Info.PtrVal = Inst->getArgOperand(1);
+    Info.MatchingId = VECTOR_VLE_VSE;
+    break;
+  }
+  case Intrinsic::riscv_vse: {
+    // Intrinsic interface:
+    // riscv_vse(val, ptr, vl)
+    Info.ReadMem = false;
+    Info.WriteMem = true;
+    Info.PtrVal = Inst->getArgOperand(1);
+    Info.MatchingId = VECTOR_VLE_VSE;
+    break;
+  }
+  }
+  return true;
+}
+
 static InstructionCost getIntImmCostImpl(const DataLayout &DL,
                                          const RISCVSubtarget *ST,
                                          const APInt &Imm, Type *Ty,
diff --git a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
index 53529d077fd54..0eb2a033da9fb 100644
--- a/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
+++ b/llvm/lib/Target/RISCV/RISCVTargetTransformInfo.h
@@ -37,6 +37,8 @@ class RISCVTTIImpl : public BasicTTIImplBase<RISCVTTIImpl> {
   const RISCVSubtarget *getST() const { return ST; }
   const RISCVTargetLowering *getTLI() const { return TLI; }
 
+  enum MemIntrinsicType { VECTOR_VLE_VSE };
+
   /// This function returns an estimate for VL to be used in VL based terms
   /// of the cost model.  For fixed length vectors, this is simply the
   /// vector length.  For scalable vectors, we return results consistent
@@ -156,7 +158,12 @@ class RISCVTTIImpl : public BasicTTIImplBase<RISCVTTIImpl> {
   void getPeelingPreferences(Loop *L, ScalarEvolution &SE,
                              TTI::PeelingPreferences &PP) const override;
 
-  unsigned getMinVectorRegisterBitWidth() const override {
+  Value *getOrCreateResultFromMemIntrinsic(IntrinsicInst *Inst,
+                                           Type *ExpectedType) const override;
+
+  bool getTgtMemIntrinsic(IntrinsicInst *Inst, MemIntrinsicInfo &Info) const;
+
+  unsigned getMinVectorRegisterBitWidth() const {
     return ST->useRVVForFixedLengthVectors() ? 16 : 0;
   }
 
diff --git a/llvm/test/Transforms/EarlyCSE/RISCV/intrinsics.ll b/llvm/test/Transforms/EarlyCSE/RISCV/intrinsics.ll
new file mode 100644
index 0000000000000..c00e822da3de4
--- /dev/null
+++ b/llvm/test/Transforms/EarlyCSE/RISCV/intrinsics.ll
@@ -0,0 +1,42 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -S -mtriple=riscv64 -mattr=+v -passes=early-cse -earlycse-debug-hash | FileCheck %s
+; RUN: opt < %s -S -mtriple=riscv64 -mattr=+v -aa-pipeline=basic-aa -passes='early-cse<memssa>' | FileCheck %s
+
+define <vscale x 2 x i32> @test_cse(ptr noundef %base) {
+; CHECK-LABEL: define <vscale x 2 x i32> @test_cse(
+; CHECK-SAME: ptr noundef [[BASE:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr [[BASE]], i64 8)
+; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vadd.nxv2i32.nxv2i32.i64(<vscale x 2 x i32> poison, <vscale x 2 x i32> [[TMP0]], <vscale x 2 x i32> [[TMP0]], i64 8)
+; CHECK-NEXT:    ret <vscale x 2 x i32> [[TMP1]]
+;
+entry:
+  %0 = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr %base, i64 8)
+  %1 = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr %base, i64 8)
+  %2 = call <vscale x 2 x i32> @llvm.riscv.vadd.nxv2i32.nxv2i32.i64(<vscale x 2 x i32> poison, <vscale x 2 x i32> %0, <vscale x 2 x i32> %1, i64 8)
+  ret <vscale x 2 x i32> %2
+}
+
+define <vscale x 2 x i32> @test_no_cse(ptr noundef %a, ptr noundef %b) {
+; CHECK-LABEL: define <vscale x 2 x i32> @test_no_cse(
+; CHECK-SAME: ptr noundef [[A:%.*]], ptr noundef [[B:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[TMP0:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr [[A]], i64 8)
+; CHECK-NEXT:    [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr [[B]], i64 8)
+; CHECK-NEXT:    [[TMP2:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vadd.nxv2i32.nxv2i32.i64(<vscale x 2 x i32> poison, <vscale x 2 x i32> [[TMP0]], <vscale x 2 x i32> [[TMP1]], i64 8)
+; CHECK-NEXT:    ret <vscale x 2 x i32> [[TMP2]]
+;
+entry:
+  %0 = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr %a, i64 8)
+  %1 = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr %b, i64 8)
+  %2 = call <vscale x 2 x i32> @llvm.riscv.vadd.nxv2i32.nxv2i32.i64(<vscale x 2 x i32> poison, <vscale x 2 x i32> %0, <vscale x 2 x i32> %1, i64 8)
+  ret <vscale x 2 x i32> %2
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read)
+declare <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32>, ptr captures(none), i64) #1
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn memory(none)
+declare <vscale x 2 x i32> @llvm.riscv.vadd.nxv2i32.nxv2i32.i64(<vscale x 2 x i32>, <vscale x 2 x i32>, <vscale x 2 x i32>, i64) #2
+
+

@HankChang736 HankChang736 requested a review from lukel97 May 7, 2025 07:38
@HankChang736 HankChang736 force-pushed the HankChang736/RISCV-Implement-getTgtMemIntrinsic branch from 13f307a to 427da9d Compare May 7, 2025 14:55
Copy link

github-actions bot commented May 7, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

This patch initially supports EarlyCSE for RISCV.

Note:
* Add two TTI hook getTgtMemIntrinsic and getOrCreateResultFromMemIntrinsic
* Add one test case intrinsics.ll in llvm/test/Transforms/EarlyCSE/RISCV/
@HankChang736 HankChang736 force-pushed the HankChang736/RISCV-Implement-getTgtMemIntrinsic branch from 427da9d to 94f87bf Compare May 7, 2025 15:02
@kito-cheng
Copy link
Member

Maybe the title should mention vector memory intrinsic? the EarlyCSE still work for RISC-V before, but just not work with those RISC-V specific memory intrinsic.

@HankChang736
Copy link
Contributor Author

Maybe the title should mention vector memory intrinsic? the EarlyCSE still work for RISC-V before, but just not work with those RISC-V specific memory intrinsic.

@kito-cheng Thank you for the feedback. Yeah, I think it's better to mention RISC-V specific memory intrinsics. Will update it later.

Comment on lines +151 to +158
case Intrinsic::riscv_vse: {
// Intrinsic interface:
// riscv_vse(val, ptr, vl)
Info.ReadMem = false;
Info.WriteMem = true;
Info.PtrVal = Inst->getArgOperand(1);
Info.MatchingId = VECTOR_VLE_VSE;
break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test case for vse?

Comment on lines +5 to +18
define <vscale x 2 x i32> @test_cse(ptr noundef %base) {
; CHECK-LABEL: define <vscale x 2 x i32> @test_cse(
; CHECK-SAME: ptr noundef [[BASE:%.*]]) #[[ATTR0:[0-9]+]] {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: [[TMP0:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr [[BASE]], i64 8)
; CHECK-NEXT: [[TMP1:%.*]] = call <vscale x 2 x i32> @llvm.riscv.vadd.nxv2i32.nxv2i32.i64(<vscale x 2 x i32> poison, <vscale x 2 x i32> [[TMP0]], <vscale x 2 x i32> [[TMP0]], i64 8)
; CHECK-NEXT: ret <vscale x 2 x i32> [[TMP1]]
;
entry:
%0 = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr %base, i64 8)
%1 = call <vscale x 2 x i32> @llvm.riscv.vle.nxv2i32.i64(<vscale x 2 x i32> poison, ptr %base, i64 8)
%2 = call <vscale x 2 x i32> @llvm.riscv.vadd.nxv2i32.nxv2i32.i64(<vscale x 2 x i32> poison, <vscale x 2 x i32> %0, <vscale x 2 x i32> %1, i64 8)
ret <vscale x 2 x i32> %2
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a testcase that two vle with different vl? e.g. 8 and 7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants