Skip to content

[RISC-V] Add CSR read/write builtins #85091

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 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/BuiltinsRISCV.td
Original file line number Diff line number Diff line change
Expand Up @@ -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">;
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you want "size_t" or "uintptr_t", please use that, not "unsigned long".

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think these are Const since they operate on state that is not part of their arguments/return.

Copy link
Member Author

Choose a reason for hiding this comment

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

I can certainly change it to size_t. And I'll move them out of the section marked with Const.

def csrw :
RISCVBuiltin<"void(unsigned long int, unsigned long int)", "zicsr">;
//===----------------------------------------------------------------------===//
// Zbb extension.
//===----------------------------------------------------------------------===//
def orc_b_32 : RISCVBuiltin<"unsigned int(unsigned int)", "zbb">;
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
25 changes: 25 additions & 0 deletions clang/test/CodeGen/RISCV/csr-intrinsics/riscv-zicsr-invalid.c
Original file line number Diff line number Diff line change
@@ -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
63 changes: 63 additions & 0 deletions clang/test/CodeGen/RISCV/csr-intrinsics/riscv-zicsr.c
Original file line number Diff line number Diff line change
@@ -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);
}
15 changes: 15 additions & 0 deletions llvm/include/llvm/IR/IntrinsicsRISCV.td
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Collaborator

Choose a reason for hiding this comment

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

What about CSR swap?

Copy link
Member Author

Choose a reason for hiding this comment

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

TBH, I only implemented these two initially for 2 reasons

  1. The user that requested them only needs these for now
  2. To validate the approach with the community

I think ultimately, we could provide read, write, set, clear and swap (and handle producing the immediate forms if the operand allows it).

//===----------------------------------------------------------------------===//
// Bitmanip (Bit Manipulation) Extension

Expand Down
17 changes: 17 additions & 0 deletions llvm/lib/Target/RISCV/RISCVInstrInfo.td
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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">;
Expand Down Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions llvm/test/CodeGen/RISCV/rv32zicsr-intrinsic.ll
Original file line number Diff line number Diff line change
@@ -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
}
34 changes: 34 additions & 0 deletions llvm/test/CodeGen/RISCV/rv64zicsr-intrinsic.ll
Original file line number Diff line number Diff line change
@@ -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
}