Skip to content

[RISCV][FPEnv] Lowering of fpenv intrinsics #141498

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 5 commits 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
44 changes: 44 additions & 0 deletions llvm/lib/Target/RISCV/RISCVISelLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,9 @@ RISCVTargetLowering::RISCVTargetLowering(const TargetMachine &TM,

setOperationAction(ISD::GET_ROUNDING, XLenVT, Custom);
setOperationAction(ISD::SET_ROUNDING, MVT::Other, Custom);
setOperationAction(ISD::GET_FPENV, XLenVT, Custom);
setOperationAction(ISD::SET_FPENV, XLenVT, Custom);
setOperationAction(ISD::RESET_FPENV, MVT::Other, Custom);
}

setOperationAction({ISD::GlobalAddress, ISD::BlockAddress, ISD::ConstantPool,
Expand Down Expand Up @@ -8091,6 +8094,12 @@ SDValue RISCVTargetLowering::LowerOperation(SDValue Op,
return lowerGET_ROUNDING(Op, DAG);
case ISD::SET_ROUNDING:
return lowerSET_ROUNDING(Op, DAG);
case ISD::GET_FPENV:
return lowerGET_FPENV(Op, DAG);
case ISD::SET_FPENV:
return lowerSET_FPENV(Op, DAG);
case ISD::RESET_FPENV:
return lowerRESET_FPENV(Op, DAG);
case ISD::EH_DWARF_CFA:
return lowerEH_DWARF_CFA(Op, DAG);
case ISD::VP_MERGE:
Expand Down Expand Up @@ -13665,6 +13674,41 @@ SDValue RISCVTargetLowering::lowerSET_ROUNDING(SDValue Op,
RMValue);
}

SDValue RISCVTargetLowering::lowerGET_FPENV(SDValue Op,
SelectionDAG &DAG) const {
const MVT XLenVT = Subtarget.getXLenVT();
SDLoc DL(Op);
SDValue Chain = Op->getOperand(0);
SDValue SysRegNo = DAG.getTargetConstant(RISCVSysReg::fcsr, DL, XLenVT);
SDVTList VTs = DAG.getVTList(XLenVT, MVT::Other);
return DAG.getNode(RISCVISD::READ_CSR, DL, VTs, Chain, SysRegNo);
}

SDValue RISCVTargetLowering::lowerSET_FPENV(SDValue Op,
SelectionDAG &DAG) const {
const MVT XLenVT = Subtarget.getXLenVT();
SDLoc DL(Op);
SDValue Chain = Op->getOperand(0);
SDValue EnvValue = Op->getOperand(1);
SDValue SysRegNo = DAG.getTargetConstant(RISCVSysReg::fcsr, DL, XLenVT);

EnvValue = DAG.getNode(ISD::ZERO_EXTEND, DL, XLenVT, EnvValue);
return DAG.getNode(RISCVISD::WRITE_CSR, DL, MVT::Other, Chain, SysRegNo,
EnvValue);
}

SDValue RISCVTargetLowering::lowerRESET_FPENV(SDValue Op,
SelectionDAG &DAG) const {
const MVT XLenVT = Subtarget.getXLenVT();
SDLoc DL(Op);
SDValue Chain = Op->getOperand(0);
SDValue EnvValue = DAG.getRegister(RISCV::X0, XLenVT);
SDValue SysRegNo = DAG.getTargetConstant(RISCVSysReg::fcsr, DL, XLenVT);

return DAG.getNode(RISCVISD::WRITE_CSR, DL, MVT::Other, Chain, SysRegNo,
EnvValue);
}

SDValue RISCVTargetLowering::lowerEH_DWARF_CFA(SDValue Op,
SelectionDAG &DAG) const {
MachineFunction &MF = DAG.getMachineFunction();
Expand Down
3 changes: 3 additions & 0 deletions llvm/lib/Target/RISCV/RISCVISelLowering.h
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,9 @@ class RISCVTargetLowering : public TargetLowering {
unsigned ExtendOpc) const;
SDValue lowerGET_ROUNDING(SDValue Op, SelectionDAG &DAG) const;
SDValue lowerSET_ROUNDING(SDValue Op, SelectionDAG &DAG) const;
SDValue lowerGET_FPENV(SDValue Op, SelectionDAG &DAG) const;
SDValue lowerSET_FPENV(SDValue Op, SelectionDAG &DAG) const;
SDValue lowerRESET_FPENV(SDValue Op, SelectionDAG &DAG) const;

SDValue lowerEH_DWARF_CFA(SDValue Op, SelectionDAG &DAG) const;
SDValue lowerCTLZ_CTTZ_ZERO_UNDEF(SDValue Op, SelectionDAG &DAG) const;
Expand Down
7 changes: 7 additions & 0 deletions llvm/lib/Target/RISCV/RISCVInstrInfo.td
Original file line number Diff line number Diff line change
Expand Up @@ -2028,6 +2028,13 @@ let hasSideEffects = true in {
def ReadFFLAGS : ReadSysReg<SysRegFFLAGS, [FFLAGS]>;
def WriteFFLAGS : WriteSysReg<SysRegFFLAGS, [FFLAGS]>;
}

let hasPostISelHook = 1 in {
def ReadFCSR : ReadSysReg<SysRegFCSR, [FRM, FFLAGS]>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need hasSideEffects like the ReadFFLAGS and WriteFFLAGS above?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It looks like too rigid restriction. ReadFFLAGS and WriteFFLAGS already have FRM and FFLAGS as inplicit use/def, shouldn't that be sufficient to provide the necessary ordering?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This patch suffered from the same problem as described in #135172. It has been fixed similar to #135176. The added test checks proper dependencies.

def WriteFCSR : WriteSysReg<SysRegFCSR, [FRM, FFLAGS]>;
def WriteFCSRImm : WriteSysRegImm<SysRegFCSR, [FRM, FFLAGS]>;
}

/// Other pseudo-instructions

// Pessimistically assume the stack pointer will be clobbered
Expand Down
1 change: 1 addition & 0 deletions llvm/lib/Target/RISCV/RISCVRegisterInfo.td
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,7 @@ foreach m = LMULList in {

def FFLAGS : RISCVReg<0, "fflags">;
def FRM : RISCVReg<0, "frm">;
def FCSR : RISCVReg<0, "fcsr">;

// Shadow Stack register
def SSP : RISCVReg<0, "ssp">;
Expand Down
37 changes: 37 additions & 0 deletions llvm/test/CodeGen/RISCV/fpenv-xlen.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
; RUN: sed 's/iXLen/i32/g' %s | llc -mtriple=riscv32 -mattr=+f -verify-machineinstrs | FileCheck %s
; RUN: sed 's/iXLen/i64/g' %s | llc -mtriple=riscv64 -mattr=+f -verify-machineinstrs | FileCheck %s
; RUN: sed 's/iXLen/i32/g' %s | llc -mtriple=riscv32 -mattr=+zfinx -verify-machineinstrs | FileCheck %s
; RUN: sed 's/iXLen/i64/g' %s | llc -mtriple=riscv64 -mattr=+zfinx -verify-machineinstrs | FileCheck %s
; RUN: sed 's/iXLen/i32/g' %s | llc -mtriple=riscv32 -mattr=+f -verify-machineinstrs -O0 | FileCheck %s
; RUN: sed 's/iXLen/i64/g' %s | llc -mtriple=riscv64 -mattr=+f -verify-machineinstrs -O0 | FileCheck %s

define iXLen @func_get_fpenv() {
; CHECK-LABEL: func_get_fpenv:
; CHECK: # %bb.0: # %entry
; CHECK-NEXT: frcsr a0
; CHECK-NEXT: ret
entry:
%fpenv = call iXLen @llvm.get.fpenv.iXLen()
ret iXLen %fpenv
}

define void @func_set_fpenv(iXLen %fpenv) {
; CHECK-LABEL: func_set_fpenv:
; CHECK: # %bb.0: # %entry
; CHECK-NEXT: fscsr a0
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm trying to decide how concerned we should be that this generates fscsr a0, a0 with -O0. The replacement of the destination with x0 to discard it is treated as an optimization.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The choice here is determined by compatibility with GLIBC, which uses this form:

https://github.com/bminor/glibc/blob/08d7243a6179d5a1f3f65a53aba1ec0803895aeb/sysdeps/riscv/fpu_control.h#L44

Copy link
Collaborator

@topperc topperc May 28, 2025

Choose a reason for hiding this comment

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

gcc always uses fscsr a0. With the current patch, -O0 will use fscsr a0, a0 and -O1 and above will use fscsr a0. The one operand form implicitly uses X0 as the destination which is a special encoding to hardware that indicates that no read needs to be performed. The X0 destination is currently being set by RISCVDeadRegisterDefinitions.cpp.

To fix -O0 to always use X0 destination, we need a pseudo instruction that doesn't have a destination that we will expand to to use X0 during RISCVAsmPrinter.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thank you for explanation. The new inplementation uses custom lowering, which is also necessary to maintain correct implicit reg dependencies.

; CHECK-NEXT: ret
entry:
call void @llvm.set.fpenv.iXLen(iXLen %fpenv)
ret void
}

define void @func_reset_fpenv() {
; CHECK-LABEL: func_reset_fpenv:
; CHECK: # %bb.0: # %entry
; CHECK-NEXT: fscsr zero
; CHECK-NEXT: ret
entry:
call void @llvm.reset.fpenv()
ret void
}
31 changes: 31 additions & 0 deletions llvm/test/CodeGen/RISCV/frm-write-in-loop.ll
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,34 @@ loop:
exit:
ret double %f2
}

define double @foo2(double %0, double %1, i64 %n, i64 %fcsr) strictfp {
; CHECK-LABEL: foo2:
; CHECK: # %bb.0: # %entry
; CHECK-NEXT: fmv.d.x fa5, zero
; CHECK-NEXT: .LBB2_1: # %loop
; CHECK-NEXT: # =>This Inner Loop Header: Depth=1
; CHECK-NEXT: csrwi fcsr, 0
; CHECK-NEXT: fadd.d fa5, fa5, fa0
; CHECK-NEXT: addi a0, a0, -1
; CHECK-NEXT: fscsr a1
; CHECK-NEXT: fadd.d fa5, fa5, fa1
; CHECK-NEXT: beqz a0, .LBB2_1
; CHECK-NEXT: # %bb.2: # %exit
; CHECK-NEXT: fmv.d fa0, fa5
; CHECK-NEXT: ret
entry:
br label %loop
loop:
%cnt = phi i64 [0, %entry], [%cnt_inc, %loop]
%acc = phi double [0.0, %entry], [%f2, %loop]
call void @llvm.set.fpenv(i64 0) strictfp
%f1 = call double @llvm.experimental.constrained.fadd.f64(double %acc, double %0, metadata !"round.dynamic", metadata !"fpexcept.ignore") strictfp
call void @llvm.set.fpenv(i64 %fcsr) strictfp
%f2 = call double @llvm.experimental.constrained.fadd.f64(double %f1, double %1, metadata !"round.dynamic", metadata !"fpexcept.ignore") strictfp
%cnt_inc = add i64 %cnt, 1
%cond = icmp eq i64 %cnt_inc, %n
br i1 %cond, label %loop, label %exit
exit:
ret double %f2
}
Loading