[clang] [llvm] [RISC-V] Add CSR read/write builtins (PR #85091)

Nemanja Ivanovic via cfe-commits cfe-commits at lists.llvm.org
Wed Mar 13 07:47:00 PDT 2024


https://github.com/nemanjai created https://github.com/llvm/llvm-project/pull/85091

To facilitate proper range checking and better error messages if an attempt is made to call these with non-litaral arguments, we provide builtins to emit the read/write CSR instructions.

>From 543086dd6a20852721bd54667196c68011d0e46e Mon Sep 17 00:00:00 2001
From: Nemanja Ivanovic <nemanja at synopsys.com>
Date: Wed, 13 Mar 2024 15:44:15 +0100
Subject: [PATCH] [RISC-V] Add CSR read/write builtins

To facilitate proper range checking and better error messages
if an attempt is made to call these with non-litaral arguments,
we provide builtins to emit the read/write CSR instructions.
---
 clang/include/clang/Basic/BuiltinsRISCV.td    |  6 ++
 clang/lib/CodeGen/CGBuiltin.cpp               | 10 +++
 clang/lib/Sema/SemaChecking.cpp               |  3 +
 .../csr-intrinsics/riscv-zicsr-invalid.c      | 25 ++++++++
 .../RISCV/csr-intrinsics/riscv-zicsr.c        | 63 +++++++++++++++++++
 llvm/include/llvm/IR/IntrinsicsRISCV.td       | 15 +++++
 llvm/lib/Target/RISCV/RISCVInstrInfo.td       | 17 +++++
 .../test/CodeGen/RISCV/rv32zicsr-intrinsic.ll | 34 ++++++++++
 .../test/CodeGen/RISCV/rv64zicsr-intrinsic.ll | 34 ++++++++++
 9 files changed, 207 insertions(+)
 create mode 100644 clang/test/CodeGen/RISCV/csr-intrinsics/riscv-zicsr-invalid.c
 create mode 100644 clang/test/CodeGen/RISCV/csr-intrinsics/riscv-zicsr.c
 create mode 100644 llvm/test/CodeGen/RISCV/rv32zicsr-intrinsic.ll
 create mode 100644 llvm/test/CodeGen/RISCV/rv64zicsr-intrinsic.ll

diff --git a/clang/include/clang/Basic/BuiltinsRISCV.td b/clang/include/clang/Basic/BuiltinsRISCV.td
index 4cc89a8a9d8af2..14ef6f9d313a41 100644
--- a/clang/include/clang/Basic/BuiltinsRISCV.td
+++ b/clang/include/clang/Basic/BuiltinsRISCV.td
@@ -20,6 +20,12 @@ class RISCVBuiltin<string prototype, string features = ""> : TargetBuiltin {
 
 let Attributes = [NoThrow, Const] in {
 //===----------------------------------------------------------------------===//
+// Zicsr extension.
+//===----------------------------------------------------------------------===//
+def csrr : RISCVBuiltin<"unsigned long int(unsigned long int)", "zicsr">;
+def csrw :
+  RISCVBuiltin<"void(unsigned long int, unsigned long int)", "zicsr">;
+//===----------------------------------------------------------------------===//
 // Zbb extension.
 //===----------------------------------------------------------------------===//
 def orc_b_32 : RISCVBuiltin<"unsigned int(unsigned int)", "zbb">;
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 93ab465079777b..99486d8aee6f9e 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -21379,6 +21379,16 @@ Value *CodeGenFunction::EmitRISCVBuiltinExpr(unsigned BuiltinID,
   llvm::SmallVector<llvm::Type *, 2> IntrinsicTypes;
   switch (BuiltinID) {
   default: llvm_unreachable("unexpected builtin ID");
+  // Zicsr
+  case RISCV::BI__builtin_riscv_csrr:
+  case RISCV::BI__builtin_riscv_csrw:
+    if (IntPtrTy->getScalarSizeInBits() == 32)
+      ID = BuiltinID == RISCV::BI__builtin_riscv_csrr ? Intrinsic::riscv_csrr
+                                                      : Intrinsic::riscv_csrw;
+    else
+      ID = BuiltinID == RISCV::BI__builtin_riscv_csrr ? Intrinsic::riscv_csrr64
+                                                      : Intrinsic::riscv_csrw64;
+    break;
   case RISCV::BI__builtin_riscv_orc_b_32:
   case RISCV::BI__builtin_riscv_orc_b_64:
   case RISCV::BI__builtin_riscv_clz_32:
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index a5f42b630c3fa2..637da165c73589 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -6223,6 +6223,9 @@ bool Sema::CheckRISCVBuiltinFunctionCall(const TargetInfo &TI,
   case RISCVVector::BI__builtin_rvv_vfwnmsac_vv_rm_mu:
   case RISCVVector::BI__builtin_rvv_vfwnmsac_vf_rm_mu:
     return SemaBuiltinConstantArgRange(TheCall, 4, 0, 4);
+  case RISCV::BI__builtin_riscv_csrr:
+  case RISCV::BI__builtin_riscv_csrw:
+    return SemaBuiltinConstantArgRange(TheCall, 0, 0, 4095);
   case RISCV::BI__builtin_riscv_ntl_load:
   case RISCV::BI__builtin_riscv_ntl_store:
     DeclRefExpr *DRE =
diff --git a/clang/test/CodeGen/RISCV/csr-intrinsics/riscv-zicsr-invalid.c b/clang/test/CodeGen/RISCV/csr-intrinsics/riscv-zicsr-invalid.c
new file mode 100644
index 00000000000000..d0420731977d05
--- /dev/null
+++ b/clang/test/CodeGen/RISCV/csr-intrinsics/riscv-zicsr-invalid.c
@@ -0,0 +1,25 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py
+// RUN: %clang_cc1 -triple riscv32 -target-feature +zicsr %s -fsyntax-only -verify
+// RUN: %clang_cc1 -triple riscv64 -target-feature +zicsr %s -fsyntax-only -verify
+// RUN: %clang_cc1 -triple riscv32 %s -fsyntax-only -verify
+
+#ifdef __riscv_zicsr
+unsigned long non_const(unsigned long a) {
+  return __builtin_riscv_csrr(a); // expected-error {{argument to '__builtin_riscv_csrr' must be a constant integer}}
+}
+
+unsigned long too_large() {
+  return __builtin_riscv_csrr(33312); // expected-error {{argument value 33312 is outside the valid range [0, 4095]}}
+}
+
+void non_const_write(unsigned long d) {
+  return __builtin_riscv_csrw(d, d); // expected-error {{argument to '__builtin_riscv_csrw' must be a constant integer}}
+}
+#else
+unsigned long read(unsigned long a) {
+  return __builtin_riscv_csrr(3); // expected-error {{builtin requires at least one of the following extensions: 'Zicsr'}}
+}
+void write(unsigned long d) {
+  return __builtin_riscv_csrw(3, d); // expected-error {{builtin requires at least one of the following extensions: 'Zicsr'}}
+}
+#endif
diff --git a/clang/test/CodeGen/RISCV/csr-intrinsics/riscv-zicsr.c b/clang/test/CodeGen/RISCV/csr-intrinsics/riscv-zicsr.c
new file mode 100644
index 00000000000000..c86c796303eba6
--- /dev/null
+++ b/clang/test/CodeGen/RISCV/csr-intrinsics/riscv-zicsr.c
@@ -0,0 +1,63 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py
+// RUN: %clang_cc1 -triple riscv32 -target-feature +zicsr -emit-llvm %s -o - \
+// RUN:     -disable-O0-optnone | opt -S -passes=mem2reg \
+// RUN:     | FileCheck %s  -check-prefix=RV32ZICSR
+// RUN: %clang_cc1 -triple riscv64 -target-feature +zicsr -emit-llvm %s -o - \
+// RUN:     -disable-O0-optnone | opt -S -passes=mem2reg \
+// RUN:     | FileCheck %s  -check-prefix=RV64ZICSR
+
+// RV32ZICSR-LABEL: @readcsr(
+// RV32ZICSR-NEXT:  entry:
+// RV32ZICSR-NEXT:    [[TMP0:%.*]] = call i32 @llvm.riscv.csrr(i32 3)
+// RV32ZICSR-NEXT:    ret i32 [[TMP0]]
+//
+// RV64ZICSR-LABEL: @readcsr(
+// RV64ZICSR-NEXT:  entry:
+// RV64ZICSR-NEXT:    [[TMP0:%.*]] = call i64 @llvm.riscv.csrr64(i64 3)
+// RV64ZICSR-NEXT:    ret i64 [[TMP0]]
+//
+unsigned long readcsr() {
+  return __builtin_riscv_csrr(3);
+}
+
+// RV32ZICSR-LABEL: @readcsr_arbitrary(
+// RV32ZICSR-NEXT:  entry:
+// RV32ZICSR-NEXT:    [[TMP0:%.*]] = call i32 @llvm.riscv.csrr(i32 333)
+// RV32ZICSR-NEXT:    ret i32 [[TMP0]]
+//
+// RV64ZICSR-LABEL: @readcsr_arbitrary(
+// RV64ZICSR-NEXT:  entry:
+// RV64ZICSR-NEXT:    [[TMP0:%.*]] = call i64 @llvm.riscv.csrr64(i64 333)
+// RV64ZICSR-NEXT:    ret i64 [[TMP0]]
+//
+unsigned long readcsr_arbitrary() {
+  return __builtin_riscv_csrr(333);
+}
+
+// RV32ZICSR-LABEL: @writecsr(
+// RV32ZICSR-NEXT:  entry:
+// RV32ZICSR-NEXT:    call void @llvm.riscv.csrw(i32 3, i32 [[D:%.*]])
+// RV32ZICSR-NEXT:    ret void
+//
+// RV64ZICSR-LABEL: @writecsr(
+// RV64ZICSR-NEXT:  entry:
+// RV64ZICSR-NEXT:    call void @llvm.riscv.csrw64(i64 3, i64 [[D:%.*]])
+// RV64ZICSR-NEXT:    ret void
+//
+void writecsr(unsigned long d) {
+  return __builtin_riscv_csrw(3, d);
+}
+
+// RV32ZICSR-LABEL: @writecsr_arbitrary(
+// RV32ZICSR-NEXT:  entry:
+// RV32ZICSR-NEXT:    call void @llvm.riscv.csrw(i32 333, i32 [[D:%.*]])
+// RV32ZICSR-NEXT:    ret void
+//
+// RV64ZICSR-LABEL: @writecsr_arbitrary(
+// RV64ZICSR-NEXT:  entry:
+// RV64ZICSR-NEXT:    call void @llvm.riscv.csrw64(i64 333, i64 [[D:%.*]])
+// RV64ZICSR-NEXT:    ret void
+//
+void writecsr_arbitrary(unsigned long d) {
+  return __builtin_riscv_csrw(333, d);
+}
diff --git a/llvm/include/llvm/IR/IntrinsicsRISCV.td b/llvm/include/llvm/IR/IntrinsicsRISCV.td
index 1d58860a0afc8a..71d20f5be0bfa2 100644
--- a/llvm/include/llvm/IR/IntrinsicsRISCV.td
+++ b/llvm/include/llvm/IR/IntrinsicsRISCV.td
@@ -74,6 +74,21 @@ let TargetPrefix = "riscv" in {
 
 } // TargetPrefix = "riscv"
 
+let TargetPrefix = "riscv" in {
+  // Zicsr
+  def int_riscv_csrr :
+    DefaultAttrsIntrinsic<[llvm_i32_ty], [llvm_i32_ty],
+                          [IntrNoMem, IntrHasSideEffects, ImmArg<ArgIndex<0>>]>;
+  def int_riscv_csrr64 :
+    DefaultAttrsIntrinsic<[llvm_i64_ty], [llvm_i64_ty],
+                          [IntrNoMem, IntrHasSideEffects, ImmArg<ArgIndex<0>>]>;
+  def int_riscv_csrw :
+    DefaultAttrsIntrinsic<[], [llvm_i32_ty, llvm_i32_ty],
+                          [IntrNoMem, IntrHasSideEffects, ImmArg<ArgIndex<0>>]>;
+  def int_riscv_csrw64 :
+    DefaultAttrsIntrinsic<[], [llvm_i64_ty, llvm_i64_ty],
+                          [IntrNoMem, IntrHasSideEffects, ImmArg<ArgIndex<0>>]>;
+} // TargetPrefix = "riscv"
 //===----------------------------------------------------------------------===//
 // Bitmanip (Bit Manipulation) Extension
 
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.td b/llvm/lib/Target/RISCV/RISCVInstrInfo.td
index e753c1f1add0c6..fa01ae9fdeb915 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.td
@@ -565,6 +565,13 @@ class CSR_ir<bits<3> funct3, string opcodestr>
     : RVInstI<funct3, OPC_SYSTEM, (outs GPR:$rd), (ins csr_sysreg:$imm12, GPR:$rs1),
               opcodestr, "$rd, $imm12, $rs1">, Sched<[WriteCSR, ReadCSR]>;
 
+let hasNoSchedulingInfo = 1,
+    hasSideEffects = 1, mayLoad = 0, mayStore = 0 in
+class CSR_ir_x0<bits<3> funct3, string opcodestr>
+    : RVInstI<funct3, OPC_SYSTEM, (outs), (ins csr_sysreg:$imm12, GPR:$rs1),
+              opcodestr, "$imm12, $rs1">, Sched<[WriteCSR]> {
+  let rd = 0;
+}
 let hasNoSchedulingInfo = 1,
     hasSideEffects = 1, mayLoad = 0, mayStore = 0 in
 class CSR_ii<bits<3> funct3, string opcodestr>
@@ -733,6 +740,8 @@ def UNIMP : RVInstI<0b001, OPC_SYSTEM, (outs), (ins), "unimp", "">,
 
 } // hasSideEffects = 1, mayLoad = 0, mayStore = 0
 
+let isCodeGenOnly = 1 in
+def CSRW : CSR_ir_x0<0b001, "csrw">;
 def CSRRW : CSR_ir<0b001, "csrrw">;
 def CSRRS : CSR_ir<0b010, "csrrs">;
 def CSRRC : CSR_ir<0b011, "csrrc">;
@@ -1845,6 +1854,14 @@ def ReadCounterWide : Pseudo<(outs GPR:$lo, GPR:$hi), (ins i32imm:$csr_lo, i32im
                               (riscv_read_counter_wide csr_sysreg:$csr_lo, csr_sysreg:$csr_hi))],
                              "", "">;
 
+// Zicsr
+let Predicates = [IsRV64] in {
+  def : Pat<(i64 (int_riscv_csrr64 timm:$I)), (CSRRS csr_sysreg:$I, (XLenVT X0))>;
+  def : Pat<(int_riscv_csrw64 timm:$I, i64:$D), (CSRW csr_sysreg:$I, i64:$D)>;
+}
+def : Pat<(i32 (int_riscv_csrr timm:$I)), (CSRRS csr_sysreg:$I, (XLenVT X0))>;
+def : Pat<(int_riscv_csrw timm:$I, i32:$D), (CSRW csr_sysreg:$I, i32:$D)>;
+
 /// traps
 
 // We lower `trap` to `unimp`, as this causes a hard exception on nearly all
diff --git a/llvm/test/CodeGen/RISCV/rv32zicsr-intrinsic.ll b/llvm/test/CodeGen/RISCV/rv32zicsr-intrinsic.ll
new file mode 100644
index 00000000000000..b866bdb415cd30
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/rv32zicsr-intrinsic.ll
@@ -0,0 +1,34 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv32 -mattr=+zicsr -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s -check-prefix=RV32ZICSR
+
+declare i32 @llvm.riscv.csrr(i32 immarg)
+declare void @llvm.riscv.csrw(i32 immarg, i32)
+
+define i32 @read() nounwind {
+; RV32ZICSR-LABEL: read:
+; RV32ZICSR:       # %bb.0:
+; RV32ZICSR-NEXT:    csrr a0, fcsr
+; RV32ZICSR-NEXT:    csrr a1, fcsr
+; RV32ZICSR-NEXT:    csrr a2, stval
+; RV32ZICSR-NEXT:    add a1, a1, a2
+; RV32ZICSR-NEXT:    add a0, a0, a1
+; RV32ZICSR-NEXT:    ret
+  %val = call i32 @llvm.riscv.csrr(i32 3)
+  %val2 = call i32 @llvm.riscv.csrr(i32 3)
+  %val3 = call i32 @llvm.riscv.csrr(i32 323)
+  %add = add i32 %val2, %val3
+  %ret = add i32 %val, %add
+  ret i32 %ret
+}
+
+define void @testwrite(i32 %d) nounwind {
+; RV32ZICSR-LABEL: testwrite:
+; RV32ZICSR:       # %bb.0:
+; RV32ZICSR-NEXT:    csrw fcsr, a0
+; RV32ZICSR-NEXT:    csrw 3435, a0
+; RV32ZICSR-NEXT:    ret
+  call void @llvm.riscv.csrw(i32 3, i32 %d)
+  call void @llvm.riscv.csrw(i32 3435, i32 %d)
+  ret void
+}
diff --git a/llvm/test/CodeGen/RISCV/rv64zicsr-intrinsic.ll b/llvm/test/CodeGen/RISCV/rv64zicsr-intrinsic.ll
new file mode 100644
index 00000000000000..39163100673b0f
--- /dev/null
+++ b/llvm/test/CodeGen/RISCV/rv64zicsr-intrinsic.ll
@@ -0,0 +1,34 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc -mtriple=riscv64 -mattr=+zicsr -verify-machineinstrs < %s \
+; RUN:   | FileCheck %s
+
+declare i64 @llvm.riscv.csrr64(i64 immarg)
+declare void @llvm.riscv.csrw64(i64 immarg, i64)
+
+define i64 @read() nounwind {
+; CHECK-LABEL: read:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    csrr a0, fcsr
+; CHECK-NEXT:    csrr a1, fcsr
+; CHECK-NEXT:    csrr a2, 111
+; CHECK-NEXT:    add a1, a1, a2
+; CHECK-NEXT:    add a0, a0, a1
+; CHECK-NEXT:    ret
+  %val = call i64 @llvm.riscv.csrr64(i64 3)
+  %val2 = call i64 @llvm.riscv.csrr64(i64 3)
+  %val3 = call i64 @llvm.riscv.csrr64(i64 111)
+  %add = add i64 %val2, %val3
+  %ret = add i64 %val, %add
+  ret i64 %ret
+}
+
+define void @testwrite(i64 %d) nounwind {
+; CHECK-LABEL: testwrite:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    csrw fcsr, a0
+; CHECK-NEXT:    csrw 3231, a0
+; CHECK-NEXT:    ret
+  call void @llvm.riscv.csrw64(i64 3, i64 %d)
+  call void @llvm.riscv.csrw64(i64 3231, i64 %d)
+  ret void
+}



More information about the cfe-commits mailing list