Skip to content

Commit

Permalink
[FPEnv] Intrinsics for access to FP control modes
Browse files Browse the repository at this point in the history
The change introduces intrinsics 'get_fpmode', 'set_fpmode' and
'reset_fpmode'. They manage all target dynamic floating-point control
modes, which include, for instance, rounding direction, precision,
treatment of denormals and so on. The intrinsics do the same
operations as the C library functions 'fegetmode' and 'fesetmode'. By
default they are lowered to calls to these functions.

Two main use cases are supported by this implementation.

1. Local modification of the control modes. In this case the code
usually has a pattern (in pseudocode):

    saved_modes = get_fpmode()
    set_fpmode(<new_modes>)
    ...
    <do operations under the new modes>
    ...
    set_fpmode(saved_modes)

In the case when it is known that the current FP environment is default,
the code may be shorter:

    set_fpmode(<new_modes>)
    ...
    <do operations under the new modes>
    ...
    reset_fpmode()

Such patterns appear not only in user code but also in implementations
of various FP controlling pragmas. In particular, the implementation of
`#pragma STDC FENV_ROUND` requires similar code if the target does not
support static rounding mode.

2. Portable control of FP modes. Usually FP control modes are set by
writing to some control register. Different targets have different
layout of this register, the way the register is accessed also may be
different. Using set of target-specific definitions for the control
register bits together with these intrinsic functions provides enough
portable way to handle control modes across wide range of hardware.

This change defines only llvm intrinsic function, which implement the
access required for the aforementioned use cases.

Differential Revision: https://reviews.llvm.org/D82525
  • Loading branch information
spavloff committed Aug 24, 2023
1 parent 04c44c9 commit 6862f0f
Show file tree
Hide file tree
Showing 10 changed files with 297 additions and 0 deletions.
95 changes: 95 additions & 0 deletions llvm/docs/LangRef.rst
Expand Up @@ -25569,6 +25569,101 @@ to default state. It is similar to the call 'fesetenv(FE_DFL_ENV)', except it
does not return any value.


'``llvm.get.fpmode``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Syntax:
"""""""

The '``llvm.get.fpmode``' intrinsic returns bits of the current floating-point
control modes. The return value type is platform-specific.

::

declare <integer_type> @llvm.get.fpmode()

Overview:
"""""""""

The '``llvm.get.fpmode``' intrinsic reads the current dynamic floating-point
control modes and returns it as an integer value.

Arguments:
""""""""""

None.

Semantics:
""""""""""

The '``llvm.get.fpmode``' intrinsic reads the current dynamic floating-point
control modes, such as rounding direction, precision, treatment of denormals and
so on. It is similar to the C library function 'fegetmode', however this
function does not store the set of control modes into memory but returns it as
an integer value. Interpretation of the bits in this value is target-dependent.

'``llvm.set.fpmode``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Syntax:
"""""""

The '``llvm.set.fpmode``' intrinsic sets the current floating-point control modes.

::

declare void @llvm.set.fpmode(<integer_type> <val>)

Overview:
"""""""""

The '``llvm.set.fpmode``' intrinsic sets the current dynamic floating-point
control modes.

Arguments:
""""""""""

The argument is a set of floating-point control modes, represented as an integer
value in a target-dependent way.

Semantics:
""""""""""

The '``llvm.set.fpmode``' intrinsic sets the current dynamic floating-point
control modes to the state specified by the argument, which must be obtained by
a call to '``llvm.get.fpmode``' or constructed in a target-specific way. It is
similar to the C library function 'fesetmode', however this function does not
read the set of control modes from memory but gets it as integer value.

'``llvm.reset.fpmode``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Syntax:
"""""""

::

declare void @llvm.reset.fpmode()

Overview:
"""""""""

The '``llvm.reset.fpmode``' intrinsic sets the default dynamic floating-point
control modes.

Arguments:
""""""""""

None.

Semantics:
""""""""""

The '``llvm.reset.fpmode``' intrinsic sets the current dynamic floating-point
environment to default state. It is similar to the C library function call
'fesetmode(FE_DFL_MODE)', however this function does not return any value.


Floating-Point Test Intrinsics
------------------------------

Expand Down
13 changes: 13 additions & 0 deletions llvm/include/llvm/CodeGen/ISDOpcodes.h
Expand Up @@ -1004,6 +1004,19 @@ enum NodeType {
/// from. The result is a token chain.
SET_FPENV_MEM,

/// Reads the current dynamic floating-point control modes. The operand is
/// a token chain.
GET_FPMODE,

/// Sets the current dynamic floating-point control modes. The first operand
/// is a token chain, the second is control modes set represented as integer
/// value.
SET_FPMODE,

/// Sets default dynamic floating-point control modes. The operand is a
/// token chain.
RESET_FPMODE,

/// LOAD and STORE have token chains as their first operand, then the same
/// operands as an LLVM load/store instruction, then an offset node that
/// is added / subtracted from the base pointer to form the address (for
Expand Down
3 changes: 3 additions & 0 deletions llvm/include/llvm/IR/Intrinsics.td
Expand Up @@ -1073,6 +1073,9 @@ let IntrProperties = [IntrInaccessibleMemOnly, IntrWillReturn] in {
def int_get_fpenv : DefaultAttrsIntrinsic<[llvm_anyint_ty], []>;
def int_set_fpenv : DefaultAttrsIntrinsic<[], [llvm_anyint_ty]>;
def int_reset_fpenv : DefaultAttrsIntrinsic<[], []>;
def int_get_fpmode : DefaultAttrsIntrinsic<[llvm_anyint_ty], []>;
def int_set_fpmode : DefaultAttrsIntrinsic<[], [llvm_anyint_ty]>;
def int_reset_fpmode : DefaultAttrsIntrinsic<[], []>;
}

//===--------------- Floating Point Properties ----------------------------===//
Expand Down
4 changes: 4 additions & 0 deletions llvm/include/llvm/IR/RuntimeLibcalls.def
Expand Up @@ -294,6 +294,10 @@ HANDLE_LIBCALL(FREXP_PPCF128, "frexpl")
HANDLE_LIBCALL(FEGETENV, "fegetenv")
HANDLE_LIBCALL(FESETENV, "fesetenv")

// Floating point control modes
HANDLE_LIBCALL(FEGETMODE, "fegetmode")
HANDLE_LIBCALL(FESETMODE, "fesetmode")

// Conversion
HANDLE_LIBCALL(FPEXT_F32_PPCF128, "__gcc_stoq")
HANDLE_LIBCALL(FPEXT_F64_PPCF128, "__gcc_dtoq")
Expand Down
10 changes: 10 additions & 0 deletions llvm/include/llvm/Target/TargetSelectionDAG.td
Expand Up @@ -176,6 +176,12 @@ def SDTFPToIntSatOp : SDTypeProfile<1, 2, [ // fp_to_[su]int_sat
def SDTFPExpOp : SDTypeProfile<1, 2, [ // ldexp
SDTCisSameAs<0, 1>, SDTCisFP<0>, SDTCisInt<2>
]>;
def SDTGetFPStateOp : SDTypeProfile<1, 0, [ // get_fpenv, get_fpmode
SDTCisInt<0>
]>;
def SDTSetFPStateOp : SDTypeProfile<0, 1, [ // set_fpenv, set_fpmode
SDTCisInt<0>
]>;
def SDTExtInreg : SDTypeProfile<1, 2, [ // sext_inreg
SDTCisSameAs<0, 1>, SDTCisInt<0>, SDTCisVT<2, OtherVT>,
SDTCisVTSmallerThanOp<2, 1>
Expand Down Expand Up @@ -610,6 +616,10 @@ def strict_uint_to_fp : SDNode<"ISD::STRICT_UINT_TO_FP",
def strict_fsetcc : SDNode<"ISD::STRICT_FSETCC", SDTSetCC, [SDNPHasChain]>;
def strict_fsetccs : SDNode<"ISD::STRICT_FSETCCS", SDTSetCC, [SDNPHasChain]>;

def get_fpmode : SDNode<"ISD::GET_FPMODE", SDTGetFPStateOp, [SDNPHasChain]>;
def set_fpmode : SDNode<"ISD::SET_FPMODE", SDTSetFPStateOp, [SDNPHasChain]>;
def reset_fpmode : SDNode<"ISD::RESET_FPMODE", SDTNone, [SDNPHasChain]>;

def setcc : SDNode<"ISD::SETCC" , SDTSetCC>;
def select : SDNode<"ISD::SELECT" , SDTSelect>;
def vselect : SDNode<"ISD::VSELECT" , SDTVSelect>;
Expand Down
41 changes: 41 additions & 0 deletions llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp
Expand Up @@ -1007,6 +1007,7 @@ void SelectionDAGLegalize::LegalizeOp(SDNode *Node) {
Action = TLI.getOperationAction(Node->getOpcode(), MVT::Other);
break;
case ISD::SET_FPENV:
case ISD::SET_FPMODE:
Action = TLI.getOperationAction(Node->getOpcode(),
Node->getOperand(1).getValueType());
break;
Expand Down Expand Up @@ -4820,6 +4821,46 @@ void SelectionDAGLegalize::ConvertNodeToLibcall(SDNode *Node) {
DAG.makeStateFunctionCall(RTLIB::FESETENV, EnvPtr, Chain, dl));
break;
}
case ISD::GET_FPMODE: {
// Call fegetmode, which saves control modes into a stack slot. Then load
// the value to return from the stack.
EVT ModeVT = Node->getValueType(0);
SDValue StackPtr = DAG.CreateStackTemporary(ModeVT);
int SPFI = cast<FrameIndexSDNode>(StackPtr.getNode())->getIndex();
SDValue Chain = DAG.makeStateFunctionCall(RTLIB::FEGETMODE, StackPtr,
Node->getOperand(0), dl);
SDValue LdInst = DAG.getLoad(
ModeVT, dl, Chain, StackPtr,
MachinePointerInfo::getFixedStack(DAG.getMachineFunction(), SPFI));
Results.push_back(LdInst);
Results.push_back(LdInst.getValue(1));
break;
}
case ISD::SET_FPMODE: {
// Move control modes to stack slot and then call fesetmode with the pointer
// to the slot as argument.
SDValue Mode = Node->getOperand(1);
EVT ModeVT = Mode.getValueType();
SDValue StackPtr = DAG.CreateStackTemporary(ModeVT);
int SPFI = cast<FrameIndexSDNode>(StackPtr.getNode())->getIndex();
SDValue StInst = DAG.getStore(
Node->getOperand(0), dl, Mode, StackPtr,
MachinePointerInfo::getFixedStack(DAG.getMachineFunction(), SPFI));
Results.push_back(
DAG.makeStateFunctionCall(RTLIB::FESETMODE, StackPtr, StInst, dl));
break;
}
case ISD::RESET_FPMODE: {
// It is legalized to a call 'fesetmode(FE_DFL_MODE)'. On most targets
// FE_DFL_MODE is defined as '((const femode_t *) -1)' in glibc. If not, the
// target must provide custom lowering.
const DataLayout &DL = DAG.getDataLayout();
EVT PtrTy = TLI.getPointerTy(DL);
SDValue Mode = DAG.getConstant(-1LL, dl, PtrTy);
Results.push_back(DAG.makeStateFunctionCall(RTLIB::FESETMODE, Mode,
Node->getOperand(0), dl));
break;
}
}

// Replace the original node with the legalized result.
Expand Down
19 changes: 19 additions & 0 deletions llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
Expand Up @@ -6665,6 +6665,25 @@ void SelectionDAGBuilder::visitIntrinsicCall(const CallInst &I,
case Intrinsic::reset_fpenv:
DAG.setRoot(DAG.getNode(ISD::RESET_FPENV, sdl, MVT::Other, getRoot()));
return;
case Intrinsic::get_fpmode:
Res = DAG.getNode(
ISD::GET_FPMODE, sdl,
DAG.getVTList(TLI.getValueType(DAG.getDataLayout(), I.getType()),
MVT::Other),
DAG.getRoot());
setValue(&I, Res);
DAG.setRoot(Res.getValue(1));
return;
case Intrinsic::set_fpmode:
Res = DAG.getNode(ISD::SET_FPMODE, sdl, MVT::Other, {DAG.getRoot()},
getValue(I.getArgOperand(0)));
DAG.setRoot(Res);
return;
case Intrinsic::reset_fpmode: {
Res = DAG.getNode(ISD::RESET_FPMODE, sdl, MVT::Other, getRoot());
DAG.setRoot(Res);
return;
}
case Intrinsic::pcmarker: {
SDValue Tmp = getValue(I.getArgOperand(0));
DAG.setRoot(DAG.getNode(ISD::PCMARKER, sdl, MVT::Other, getRoot(), Tmp));
Expand Down
3 changes: 3 additions & 0 deletions llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp
Expand Up @@ -439,6 +439,9 @@ std::string SDNode::getOperationName(const SelectionDAG *G) const {
case ISD::RESET_FPENV: return "reset_fpenv";
case ISD::GET_FPENV_MEM: return "get_fpenv_mem";
case ISD::SET_FPENV_MEM: return "set_fpenv_mem";
case ISD::GET_FPMODE: return "get_fpmode";
case ISD::SET_FPMODE: return "set_fpmode";
case ISD::RESET_FPMODE: return "reset_fpmode";

// Bit manipulation
case ISD::ABS: return "abs";
Expand Down
6 changes: 6 additions & 0 deletions llvm/lib/CodeGen/TargetLoweringBase.cpp
Expand Up @@ -942,6 +942,12 @@ void TargetLoweringBase::initActions() {

setOperationAction(ISD::GET_FPENV_MEM, MVT::Other, Expand);
setOperationAction(ISD::SET_FPENV_MEM, MVT::Other, Expand);

for (MVT VT : {MVT::i8, MVT::i16, MVT::i32, MVT::i64}) {
setOperationAction(ISD::GET_FPMODE, VT, Expand);
setOperationAction(ISD::SET_FPMODE, VT, Expand);
}
setOperationAction(ISD::RESET_FPMODE, MVT::Other, Expand);
}

MVT TargetLoweringBase::getScalarShiftAmountTy(const DataLayout &DL,
Expand Down
103 changes: 103 additions & 0 deletions llvm/test/CodeGen/X86/fpenv.ll
Expand Up @@ -7,6 +7,9 @@ declare void @llvm.set.rounding(i32 %x)
declare i256 @llvm.get.fpenv.i256()
declare void @llvm.set.fpenv.i256(i256 %fpenv)
declare void @llvm.reset.fpenv()
declare i32 @llvm.get.fpmode.i32()
declare void @llvm.set.fpmode.i32(i32 %fpmode)
declare void @llvm.reset.fpmode()

define void @func_01() nounwind {
; X86-NOSSE-LABEL: func_01:
Expand Down Expand Up @@ -420,4 +423,104 @@ entry:
ret void
}

define i32 @func_get_fpmode() #0 {
; X86-NOSSE-LABEL: func_get_fpmode:
; X86-NOSSE: # %bb.0: # %entry
; X86-NOSSE-NEXT: subl $12, %esp
; X86-NOSSE-NEXT: leal {{[0-9]+}}(%esp), %eax
; X86-NOSSE-NEXT: movl %eax, (%esp)
; X86-NOSSE-NEXT: calll fegetmode
; X86-NOSSE-NEXT: movl {{[0-9]+}}(%esp), %eax
; X86-NOSSE-NEXT: addl $12, %esp
; X86-NOSSE-NEXT: retl
;
; X86-SSE-LABEL: func_get_fpmode:
; X86-SSE: # %bb.0: # %entry
; X86-SSE-NEXT: subl $12, %esp
; X86-SSE-NEXT: leal {{[0-9]+}}(%esp), %eax
; X86-SSE-NEXT: movl %eax, (%esp)
; X86-SSE-NEXT: calll fegetmode
; X86-SSE-NEXT: movl {{[0-9]+}}(%esp), %eax
; X86-SSE-NEXT: addl $12, %esp
; X86-SSE-NEXT: retl
;
; X64-LABEL: func_get_fpmode:
; X64: # %bb.0: # %entry
; X64-NEXT: pushq %rax
; X64-NEXT: leaq {{[0-9]+}}(%rsp), %rdi
; X64-NEXT: callq fegetmode@PLT
; X64-NEXT: movl {{[0-9]+}}(%rsp), %eax
; X64-NEXT: popq %rcx
; X64-NEXT: retq
entry:
%fpmode = call i32 @llvm.get.fpmode.i32()
ret i32 %fpmode
}

define void @func_set_fpmode(i32 %fpmode) #0 {
; X86-NOSSE-LABEL: func_set_fpmode:
; X86-NOSSE: # %bb.0: # %entry
; X86-NOSSE-NEXT: subl $12, %esp
; X86-NOSSE-NEXT: movl {{[0-9]+}}(%esp), %eax
; X86-NOSSE-NEXT: movl %eax, {{[0-9]+}}(%esp)
; X86-NOSSE-NEXT: leal {{[0-9]+}}(%esp), %eax
; X86-NOSSE-NEXT: movl %eax, (%esp)
; X86-NOSSE-NEXT: calll fesetmode
; X86-NOSSE-NEXT: addl $12, %esp
; X86-NOSSE-NEXT: retl
;
; X86-SSE-LABEL: func_set_fpmode:
; X86-SSE: # %bb.0: # %entry
; X86-SSE-NEXT: subl $12, %esp
; X86-SSE-NEXT: movl {{[0-9]+}}(%esp), %eax
; X86-SSE-NEXT: movl %eax, {{[0-9]+}}(%esp)
; X86-SSE-NEXT: leal {{[0-9]+}}(%esp), %eax
; X86-SSE-NEXT: movl %eax, (%esp)
; X86-SSE-NEXT: calll fesetmode
; X86-SSE-NEXT: addl $12, %esp
; X86-SSE-NEXT: retl
;
; X64-LABEL: func_set_fpmode:
; X64: # %bb.0: # %entry
; X64-NEXT: pushq %rax
; X64-NEXT: movl %edi, {{[0-9]+}}(%rsp)
; X64-NEXT: leaq {{[0-9]+}}(%rsp), %rdi
; X64-NEXT: callq fesetmode@PLT
; X64-NEXT: popq %rax
; X64-NEXT: retq
entry:
call void @llvm.set.fpmode.i32(i32 %fpmode)
ret void
}


define void @func_reset() #0 {
; X86-NOSSE-LABEL: func_reset:
; X86-NOSSE: # %bb.0: # %entry
; X86-NOSSE-NEXT: subl $12, %esp
; X86-NOSSE-NEXT: movl $-1, (%esp)
; X86-NOSSE-NEXT: calll fesetmode
; X86-NOSSE-NEXT: addl $12, %esp
; X86-NOSSE-NEXT: retl
;
; X86-SSE-LABEL: func_reset:
; X86-SSE: # %bb.0: # %entry
; X86-SSE-NEXT: subl $12, %esp
; X86-SSE-NEXT: movl $-1, (%esp)
; X86-SSE-NEXT: calll fesetmode
; X86-SSE-NEXT: addl $12, %esp
; X86-SSE-NEXT: retl
;
; X64-LABEL: func_reset:
; X64: # %bb.0: # %entry
; X64-NEXT: pushq %rax
; X64-NEXT: movq $-1, %rdi
; X64-NEXT: callq fesetmode@PLT
; X64-NEXT: popq %rax
; X64-NEXT: retq
entry:
call void @llvm.reset.fpmode()
ret void
}

attributes #0 = { nounwind "use-soft-float"="true" }

0 comments on commit 6862f0f

Please sign in to comment.