diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -18692,6 +18692,91 @@ Other values may be used to represent additional rounding modes, supported by a target. These values are target-specific. +'``llvm.get.fpenv``' Intrinsic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Syntax: +""""""" + +:: + + declare void @llvm.get.fpenv(i8* ) + +Overview: +""""""""" + +The '``llvm.get.fpenv``' intrinsic reads floating point environment. + +Arguments: +"""""""""" + +The argument is a pointer to memory where the floating point environment should +be written to. + +Semantics: +"""""""""" + +The '``llvm.get.fpenv``' intrinsic reads the current floating point environment +and stores it by the address specified by the argument. It is similar to C +library function 'fegetenv', however this function does not return any value. + +'``llvm.set.fpenv``' Intrinsic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Syntax: +""""""" + +:: + + declare void @llvm.set.fpenv(i8* ) + +Overview: +""""""""" + +The '``llvm.set.fpenv``' intrinsic sets floating point environment. + +Arguments: +"""""""""" + +The argument is a pointer to memory where the floating point environment is +stored. + +Semantics: +"""""""""" + +The '``llvm.set.fpenv``' intrinsic sets the current floating point environment +according to the state stored in the memory pointed by the argument. It is +similar to C library function 'fesetenv', however this function does not return +any value. + +'``llvm.reset.fpenv``' Intrinsic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Syntax: +""""""" + +:: + + declare void @llvm.reset.fpenv(i8* ) + +Overview: +""""""""" + +The '``llvm.reset.fpenv``' intrinsic sets default floating point environment. + +Arguments: +"""""""""" + +None. + +Semantics: +"""""""""" + +The '``llvm.reset.fpenv``' intrinsic sets the current floating point environment +to default state. It is similar to the call 'fesetenv(FE_DFL_ENV)', however this +function does not return any value. + + General Intrinsics ------------------ diff --git a/llvm/include/llvm/CodeGen/ISDOpcodes.h b/llvm/include/llvm/CodeGen/ISDOpcodes.h --- a/llvm/include/llvm/CodeGen/ISDOpcodes.h +++ b/llvm/include/llvm/CodeGen/ISDOpcodes.h @@ -809,6 +809,16 @@ /// FSINCOS - Compute both fsin and fcos as a single operation. FSINCOS, + /// Save and restore floating point environment to/from a memory location. + /// The first operand is token chain, the second is memory address of FP + /// environment. + GET_FPENV, + SET_FPENV, + + /// Set floating point environment to default state. The first operand is + /// token chain. + RESET_FPENV, + /// 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 diff --git a/llvm/include/llvm/CodeGen/SelectionDAGTargetInfo.h b/llvm/include/llvm/CodeGen/SelectionDAGTargetInfo.h --- a/llvm/include/llvm/CodeGen/SelectionDAGTargetInfo.h +++ b/llvm/include/llvm/CodeGen/SelectionDAGTargetInfo.h @@ -165,6 +165,30 @@ virtual bool disableGenericCombines(CodeGenOpt::Level OptLevel) const { return false; } + + /// Emit target-specific code that performs reading floating point + /// environment. + virtual SDValue emitTargetCodeForGetFPEnv(SelectionDAG &DAG, const SDLoc &DL, + SDValue Chain, SDValue Addr, + MachinePointerInfo PtrInfo) const { + return SDValue(); + } + + /// Emit target-specific code that performs setting floating point + /// environment. + virtual SDValue emitTargetCodeForSetFPEnv(SelectionDAG &DAG, const SDLoc &DL, + SDValue Chain, SDValue Addr, + MachinePointerInfo PtrInfo) const { + return SDValue(); + } + + /// Emit target-specific code that performs setting default floating point + /// environment. + virtual SDValue emitTargetCodeForResetFPEnv(SelectionDAG &DAG, + const SDLoc &DL, + SDValue Chain) const { + return SDValue(); + } }; } // end namespace llvm diff --git a/llvm/include/llvm/IR/IRBuilder.h b/llvm/include/llvm/IR/IRBuilder.h --- a/llvm/include/llvm/IR/IRBuilder.h +++ b/llvm/include/llvm/IR/IRBuilder.h @@ -894,6 +894,19 @@ return CreateBinaryIntrinsic(Intrinsic::maximum, LHS, RHS, nullptr, Name); } + /// Create call to the get_fpenv intrinsic. + /// \param[out] FPEnv This argument is assigned pointer to AllocaInst of the + /// variable created to store the FP environment. + CallInst *CreateGetFPEnv(Value *&FPEnv); + + /// Create call to the set_fpenv intrinsic. + /// \param[in] FPEnv Pointer to Value representing the memory to store the + /// FP environment. + CallInst *CreateSetFPEnv(Value *FPEnv); + + /// Create call to the reset_fpenv intrinsic. + CallInst *CreateResetFPEnv(); + private: /// Create a call to a masked intrinsic with given Id. CallInst *CreateMaskedIntrinsic(Intrinsic::ID Id, ArrayRef Ops, diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td --- a/llvm/include/llvm/IR/Intrinsics.td +++ b/llvm/include/llvm/IR/Intrinsics.td @@ -644,9 +644,18 @@ //===--------------- Access to Floating Point Environment -----------------===// // -let IntrProperties = [IntrInaccessibleMemOnly, IntrWillReturn] in { - def int_flt_rounds : Intrinsic<[llvm_i32_ty], []>; -} +def int_flt_rounds : Intrinsic<[llvm_i32_ty], [], + [IntrInaccessibleMemOnly, IntrWillReturn]>; +def int_get_fpenv : Intrinsic<[], [llvm_anyptr_ty], + [IntrWillReturn, IntrWriteMem, IntrNoSync, + IntrInaccessibleMemOrArgMemOnly, + NoCapture>]>; +def int_set_fpenv : Intrinsic<[], [llvm_anyptr_ty], + [IntrWillReturn, IntrReadMem, IntrNoSync, + IntrInaccessibleMemOrArgMemOnly, + NoCapture>]>; +def int_reset_fpenv : Intrinsic<[], [], + [IntrInaccessibleMemOnly, IntrWillReturn]>; //===--------------- Constrained Floating Point Intrinsics ----------------===// // diff --git a/llvm/include/llvm/IR/RuntimeLibcalls.def b/llvm/include/llvm/IR/RuntimeLibcalls.def --- a/llvm/include/llvm/IR/RuntimeLibcalls.def +++ b/llvm/include/llvm/IR/RuntimeLibcalls.def @@ -280,6 +280,10 @@ HANDLE_LIBCALL(LLRINT_F128, "llrintl") HANDLE_LIBCALL(LLRINT_PPCF128, "llrintl") +// Floating point environment +HANDLE_LIBCALL(FEGETENV, "fegetenv") +HANDLE_LIBCALL(FESETENV, "fesetenv") + // Conversion HANDLE_LIBCALL(FPEXT_F32_PPCF128, "__gcc_stoq") HANDLE_LIBCALL(FPEXT_F64_PPCF128, "__gcc_dtoq") diff --git a/llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp b/llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp --- a/llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/LegalizeDAG.cpp @@ -186,6 +186,8 @@ SDValue ExpandInsertToVectorThroughStack(SDValue Op); SDValue ExpandVectorBuildThroughStack(SDNode* Node); + void ExpandFPEnvOp(SDNode *Node, SmallVectorImpl &Results); + SDValue ExpandConstantFP(ConstantFPSDNode *CFP, bool UseCP); SDValue ExpandConstant(ConstantSDNode *CP); @@ -2768,6 +2770,63 @@ } } +/// Issue libcalls to perform operation on floating point environment. +void SelectionDAGLegalize::ExpandFPEnvOp(SDNode *Node, + SmallVectorImpl &Results) { + unsigned Opcode = Node->getOpcode(); + + RTLIB::Libcall LC; + switch (Opcode) { + default: + llvm_unreachable("Unexpected request for libcall!"); + case ISD::GET_FPENV: + LC = RTLIB::FEGETENV; + break; + case ISD::SET_FPENV: + case ISD::RESET_FPENV: + LC = RTLIB::FESETENV; + break; + } + + SDLoc DLoc(Node); + TargetLowering::ArgListTy Args; + if (Node->getNumOperands() > 1) { + SDValue Op = Node->getOperand(1); + TargetLowering::ArgListEntry Entry; + EVT ArgVT = Op.getValueType(); + Type *ArgTy = ArgVT.getTypeForEVT(*DAG.getContext()); + Entry.Node = Op; + Entry.Ty = ArgTy; + Args.push_back(Entry); + } else { + // This is RESET_FENV. It can be implemented as 'fesetenv(FE_DFL_ENV)'. + // Value of FE_DFL_ENV is implementation defined, however for most + // targets it is defined as '((const fenv_t *) -1)'. If a target defines + // FE_DFL_ENV differently, it must provide custom lowering. + const DataLayout &DL = DAG.getDataLayout(); + Type *ByteTy = EVT(MVT::i8).getTypeForEVT(*DAG.getContext()); + auto *PtrTy = PointerType::get(ByteTy, DL.getAllocaAddrSpace()); + Type *IntTy = DL.getIntPtrType(PtrTy); + TargetLowering::ArgListEntry Entry; + Entry.Node = DAG.getConstant(-1LL, DLoc, MVT::getVT(IntTy)); + Entry.Ty = PtrTy; + Args.push_back(Entry); + } + + SDValue Callee = DAG.getExternalSymbol(TLI.getLibcallName(LC), + TLI.getPointerTy(DAG.getDataLayout())); + TargetLowering::CallLoweringInfo CLI(DAG); + CLI.setDebugLoc(DLoc) + .setChain(Node->getOperand(0)) + .setLibCallee(TLI.getLibcallCallingConv(LC), + Type::getVoidTy(*DAG.getContext()), Callee, + std::move(Args)); + + std::pair CallInfo = TLI.LowerCallTo(CLI); + + Results.push_back(CallInfo.second); +} + bool SelectionDAGLegalize::ExpandNode(SDNode *Node) { LLVM_DEBUG(dbgs() << "Trying to expand node\n"); SmallVector Results; @@ -4300,6 +4359,12 @@ break; } break; + case ISD::GET_FPENV: + case ISD::SET_FPENV: + case ISD::RESET_FPENV: + // Expand into libcall + ExpandFPEnvOp(Node, Results); + break; } // Replace the original node with the legalized result. diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -6197,6 +6197,37 @@ DAG.getNode(ISD::BITCAST, sdl, MVT::f16, getValue(I.getArgOperand(0))))); return; + case Intrinsic::get_fpenv: { + const SelectionDAGTargetInfo &TSI = DAG.getSelectionDAGInfo(); + SDValue Result = TSI.emitTargetCodeForGetFPEnv( + DAG, getCurSDLoc(), getRoot(), getValue(I.getArgOperand(0)), + MachinePointerInfo(I.getArgOperand(0))); + if (!Result.getNode()) + Result = DAG.getNode(ISD::GET_FPENV, sdl, MVT::Other, getRoot(), + getValue(I.getArgOperand(0))); + DAG.setRoot(Result); + return; + } + case Intrinsic::set_fpenv: { + const SelectionDAGTargetInfo &TSI = DAG.getSelectionDAGInfo(); + SDValue Result = TSI.emitTargetCodeForSetFPEnv( + DAG, getCurSDLoc(), getRoot(), getValue(I.getArgOperand(0)), + MachinePointerInfo(I.getArgOperand(0))); + if (!Result.getNode()) + Result = DAG.getNode(ISD::SET_FPENV, sdl, MVT::Other, getRoot(), + getValue(I.getArgOperand(0))); + DAG.setRoot(Result); + return; + } + case Intrinsic::reset_fpenv: { + const SelectionDAGTargetInfo &TSI = DAG.getSelectionDAGInfo(); + SDValue Result = + TSI.emitTargetCodeForResetFPEnv(DAG, getCurSDLoc(), getRoot()); + if (!Result.getNode()) + Result = DAG.getNode(ISD::RESET_FPENV, sdl, MVT::Other, getRoot()); + DAG.setRoot(Result); + return; + } case Intrinsic::pcmarker: { SDValue Tmp = getValue(I.getArgOperand(0)); DAG.setRoot(DAG.getNode(ISD::PCMARKER, sdl, MVT::Other, getRoot(), Tmp)); diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGDumper.cpp @@ -400,6 +400,11 @@ case ISD::PREALLOCATED_ARG: return "call_alloc"; + // Environment manipulation + case ISD::GET_FPENV: return "get_fpenv"; + case ISD::SET_FPENV: return "set_fpenv"; + case ISD::RESET_FPENV: return "reset_fpenv"; + // Bit manipulation case ISD::ABS: return "abs"; case ISD::BITREVERSE: return "bitreverse"; diff --git a/llvm/lib/CodeGen/TargetLoweringBase.cpp b/llvm/lib/CodeGen/TargetLoweringBase.cpp --- a/llvm/lib/CodeGen/TargetLoweringBase.cpp +++ b/llvm/lib/CodeGen/TargetLoweringBase.cpp @@ -772,6 +772,11 @@ // On most systems, DEBUGTRAP and TRAP have no difference. The "Expand" // here is to inform DAG Legalizer to replace DEBUGTRAP with TRAP. setOperationAction(ISD::DEBUGTRAP, MVT::Other, Expand); + + // Environment operations default to expand to library calls. + setOperationAction(ISD::GET_FPENV, MVT::Other, Expand); + setOperationAction(ISD::SET_FPENV, MVT::Other, Expand); + setOperationAction(ISD::RESET_FPENV, MVT::Other, Expand); } MVT TargetLoweringBase::getScalarShiftAmountTy(const DataLayout &DL, diff --git a/llvm/lib/IR/IRBuilder.cpp b/llvm/lib/IR/IRBuilder.cpp --- a/llvm/lib/IR/IRBuilder.cpp +++ b/llvm/lib/IR/IRBuilder.cpp @@ -914,6 +914,54 @@ return C; } +CallInst *IRBuilderBase::CreateGetFPEnv(Value *&FPEnv) { + assert(BB && BB->getParent() && "No current function!"); + Module *M = BB->getParent()->getParent(); + // If space for saved FP environment is not provided, create it in stack. + if (FPEnv == nullptr) { + const DataLayout &DL = M->getDataLayout(); + unsigned EnvSize = DL.getFPEnvironmentSize(); + assert(EnvSize); + EnvSize = alignTo(EnvSize, DL.getStackAlignment().value()); + auto *VTy = ArrayType::get(getInt8Ty(), EnvSize); + FPEnv = CreateAlloca(VTy, DL.getAllocaAddrSpace()); + } + // If the space for FPE has array type, convert it to element pointer. + Value *Ptr; + auto *PTy = cast(FPEnv->getType()); + if (isa(PTy->getPointerElementType())) + Ptr = CreateGEP(FPEnv, {getInt32(0), getInt32(0)}); + else + Ptr = FPEnv; + // Bitcast FPE pointer to i8*. + Ptr = CreateBitCast(Ptr, getInt8PtrTy(PTy->getAddressSpace())); + + Function *TheFn = Intrinsic::getDeclaration(M, Intrinsic::get_fpenv, + Ptr->getType()); + return createCallHelper(TheFn, Ptr, this); +} + +CallInst *IRBuilderBase::CreateSetFPEnv(Value *FPEnv) { + assert(BB && BB->getParent() && "No current function!"); + assert(FPEnv); + Module *M = BB->getParent()->getParent(); + auto *PTy = cast(FPEnv->getType()); + if (isa(PTy->getPointerElementType())) + FPEnv = CreateGEP(FPEnv, {getInt32(0), getInt32(0)}); + FPEnv = CreateBitCast(FPEnv, getInt8PtrTy(PTy->getPointerAddressSpace())); + + Function *TheFn = Intrinsic::getDeclaration(M, Intrinsic::set_fpenv, + FPEnv->getType()); + return createCallHelper(TheFn, FPEnv, this); +} + +CallInst *IRBuilderBase::CreateResetFPEnv() { + assert(BB && BB->getParent() && "No current function!"); + Module *M = BB->getParent()->getParent(); + Function *TheFn = Intrinsic::getDeclaration(M, Intrinsic::reset_fpenv); + return createCallHelper(TheFn, {}, this); +} + Value *IRBuilderBase::CreateSelect(Value *C, Value *True, Value *False, const Twine &Name, Instruction *MDFrom) { if (auto *CC = dyn_cast(C)) diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -5027,6 +5027,19 @@ "result of a matrix operation does not fit in the returned vector"); break; } + case Intrinsic::get_fpenv: + case Intrinsic::set_fpenv: { + Type *ArgTy = Call.getArgOperand(0)->getType(); + Assert(ArgTy->isPointerTy(), "argument must have pointer type"); + Type *FPEnvType = ArgTy->getPointerElementType(); + if (!FPEnvType->isIntegerTy(8)) { + const DataLayout &DL = M.getDataLayout(); + unsigned FEnvSize = DL.getFPEnvironmentSize(); + unsigned ActualFPEnvSize = DL.getTypeStoreSize(FPEnvType); + Assert(ActualFPEnvSize >= FEnvSize, "too small size for FP environment"); + } + break; + } }; } diff --git a/llvm/test/CodeGen/Generic/fpenv.ll b/llvm/test/CodeGen/Generic/fpenv.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/Generic/fpenv.ll @@ -0,0 +1,42 @@ +; RUN: llc < %s | FileCheck %s + +; This test checks default lowering of the intrinsics operating floating point +; environment. MSP430 is used as a target in this test because it does not have +; native FP support, so it won't get custom lowering for these intrinsics. +; +; REQUIRES: msp430-registered-target + +target datalayout = "e-p:16:16:16-i8:8:8-i16:16:16-i32:16:32-n8:16" +target triple = "msp430---elf" + + +define i8 @func_01() { +entry: + %fpenv = alloca i8 + call void @llvm.get.fpenv(i8* %fpenv) + %lenv = load i8, i8* %fpenv + ret i8 %lenv +} +; CHECK-LABEL: func_01: +; CHECK: call #fegetenv + + +define void @func_02(i8* %fpenv) { +entry: + call void @llvm.set.fpenv(i8* %fpenv) + ret void +} +; CHECK-LABEL: func_02: +; CHECK: call #fesetenv + +define void @func_03() { +entry: + call void @llvm.reset.fpenv() + ret void +} +; CHECK-LABEL: func_03: +; CHECK: call #fesetenv + +declare void @llvm.get.fpenv(i8* %fpenv) +declare void @llvm.set.fpenv(i8* %fpenv) +declare void @llvm.reset.fpenv() diff --git a/llvm/unittests/IR/IRBuilderTest.cpp b/llvm/unittests/IR/IRBuilderTest.cpp --- a/llvm/unittests/IR/IRBuilderTest.cpp +++ b/llvm/unittests/IR/IRBuilderTest.cpp @@ -129,6 +129,63 @@ EXPECT_FALSE(II->hasNoNaNs()); } +TEST_F(IRBuilderTest, FPEnvironment) { + IRBuilder<> Builder(BB); + CallInst *Call; + Value *V; + IntrinsicInst *II; + Type *PointeeTy; + + Call = Builder.CreateResetFPEnv(); + II = cast(Call); + EXPECT_EQ(Intrinsic::reset_fpenv, II->getIntrinsicID()); + + // If set/get functions are provided with raw pointer, size of FP + // environment is not required. + AllocaInst *Var1 = Builder.CreateAlloca(Builder.getInt32Ty()); + V = Var1; + Call = Builder.CreateGetFPEnv(V); + II = cast(Call); + EXPECT_EQ(V, Var1); + EXPECT_EQ(Intrinsic::get_fpenv, II->getIntrinsicID()); + + V = Var1; + Call = Builder.CreateSetFPEnv(V); + II = cast(Call); + EXPECT_EQ(V, Var1); + EXPECT_EQ(Intrinsic::set_fpenv, II->getIntrinsicID()); + + AllocaInst *Var2 = + Builder.CreateAlloca(ArrayType::get(Builder.getInt32Ty(), 4)); + V = Var2; + Call = Builder.CreateGetFPEnv(V); + II = cast(Call); + EXPECT_EQ(V, Var2); + EXPECT_EQ(Intrinsic::get_fpenv, II->getIntrinsicID()); + + V = Var2; + Call = Builder.CreateSetFPEnv(V); + II = cast(Call); + EXPECT_EQ(V, Var2); + EXPECT_EQ(Intrinsic::set_fpenv, II->getIntrinsicID()); + + // The variant get_fpenv(nullptr) requires that DataLayout defines FP + // environment size. + M->setDataLayout("S32-fe:256"); + V = nullptr; + Call = Builder.CreateGetFPEnv(V); + II = cast(Call); + EXPECT_EQ(Intrinsic::get_fpenv, II->getIntrinsicID()); + ASSERT_TRUE(V != nullptr); + EXPECT_TRUE(V->getType()->isPointerTy()); + PointeeTy = V->getType()->getPointerElementType(); + EXPECT_EQ(32U, M->getDataLayout().getTypeStoreSize(PointeeTy)); + + Call = Builder.CreateSetFPEnv(V); + II = cast(Call); + EXPECT_EQ(Intrinsic::set_fpenv, II->getIntrinsicID()); +} + TEST_F(IRBuilderTest, IntrinsicsWithScalableVectors) { IRBuilder<> Builder(BB); CallInst *Call;