diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -16810,6 +16810,99 @@ would and handles error conditions in the same way. +Floating Point Environment Manipulation intrinsics +-------------------------------------------------- + +These functions provide access to floating point environment, such as rounding +mode or exception behavior. Targets that support the environment manipulation +should specify size of memory for storing floating point environment data in +:ref:`datalayout string`. + +'``llvm.get.fenv``' Intrinsic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Syntax: +""""""" + +:: + + declare void @llvm.get.fenv(i8* ) + +Overview: +""""""""" + +The '``llvm.get.fenv``' 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.fenv``' 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.fenv``' Intrinsic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Syntax: +""""""" + +:: + + declare void @llvm.set.fenv(i8* ) + +Overview: +""""""""" + +The '``llvm.set.fenv``' intrinsic sets floating point environment. + +Arguments: +"""""""""" + +The argument is a pointer to memory where the floating point environment is +stored. + +Semantics: +"""""""""" + +The '``llvm.set.fenv``' 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.fenv``' Intrinsic +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Syntax: +""""""" + +:: + + declare void @llvm.reset.fenv(i8* ) + +Overview: +""""""""" + +The '``llvm.reset.fenv``' intrinsic sets default floating point environment. + +Arguments: +"""""""""" + +None. + +Semantics: +"""""""""" + +The '``llvm.reset.fenv``' 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 @@ -659,6 +659,15 @@ /// 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_FENV, SET_FENV, + + /// Set floating point environment to default state. The first operand is + /// token chain. + RESET_FENV, + /// 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/IR/IRBuilder.h b/llvm/include/llvm/IR/IRBuilder.h --- a/llvm/include/llvm/IR/IRBuilder.h +++ b/llvm/include/llvm/IR/IRBuilder.h @@ -812,6 +812,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 @@ -595,6 +595,14 @@ [IntrNoMem, IntrSpeculatable, IntrWillReturn, ImmArg<1>, ImmArg<2>, ImmArg<3>]>, GCCBuiltin<"__builtin_object_size">; +// Operations on floating point environment. +def int_get_fenv : Intrinsic<[], [llvm_ptr_ty], + [IntrInaccessibleMemOnly, IntrWillReturn]>; +def int_set_fenv : Intrinsic<[], [llvm_ptr_ty], + [IntrInaccessibleMemOnly, IntrWillReturn]>; +def int_reset_fenv : 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 @@ -275,6 +275,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 ExpandFEnvOp(SDNode *Node, SmallVectorImpl &Results); + SDValue ExpandConstantFP(ConstantFPSDNode *CFP, bool UseCP); SDValue ExpandConstant(ConstantSDNode *CP); @@ -2709,6 +2711,63 @@ } } +/// Issue libcalls to perform operation on floating point environment. +void SelectionDAGLegalize::ExpandFEnvOp(SDNode *Node, + SmallVectorImpl &Results) { + unsigned Opcode = Node->getOpcode(); + + RTLIB::Libcall LC; + switch (Opcode) { + default: + llvm_unreachable("Unexpected request for libcall!"); + case ISD::GET_FENV: + LC = RTLIB::FEGETENV; + break; + case ISD::SET_FENV: + case ISD::RESET_FENV: + 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; @@ -4223,6 +4282,12 @@ break; } break; + case ISD::GET_FENV: + case ISD::SET_FENV: + case ISD::RESET_FENV: + // Expand into divrem libcall + ExpandFEnvOp(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 @@ -6223,6 +6223,18 @@ DAG.getNode(ISD::BITCAST, sdl, MVT::f16, getValue(I.getArgOperand(0))))); return; + case Intrinsic::get_fenv: + DAG.setRoot(DAG.getNode(ISD::GET_FENV, sdl, MVT::Other, getRoot(), + getValue(I.getArgOperand(0)))); + return; + case Intrinsic::set_fenv: + DAG.setRoot(DAG.getNode(ISD::SET_FENV, sdl, MVT::Other, getRoot(), + getValue(I.getArgOperand(0)))); + return; + case Intrinsic::reset_fenv: + DAG.setRoot(DAG.getNode(ISD::RESET_FENV, sdl, MVT::Other, getRoot())); + 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 @@ -385,6 +385,11 @@ case ISD::GC_TRANSITION_END: return "gc_transition.end"; case ISD::GET_DYNAMIC_AREA_OFFSET: return "get.dynamic.area.offset"; + // Environment manipulation + case ISD::GET_FENV: return "get_fenv"; + case ISD::SET_FENV: return "set_fenv"; + case ISD::RESET_FENV: return "clear_fenv"; + // 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 @@ -769,6 +769,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_FENV, MVT::Other, Expand); + setOperationAction(ISD::SET_FENV, MVT::Other, Expand); + setOperationAction(ISD::RESET_FENV, 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 @@ -764,3 +764,54 @@ Function *Fn = Intrinsic::getDeclaration(M, ID, Types); return createCallHelper(Fn, Args, this, Name, FMFSource); } + +CallInst *IRBuilderBase::CreateGetFPEnv(Value *&FPEnv) { + assert(BB && BB->getParent() && "No current function!"); + Module *M = BB->getParent()->getParent(); + const DataLayout &DL = M->getDataLayout(); + unsigned EnvSize = DL.getFPEnvironmentSize(); + // If target did not specify FP environment size, use one stack word. + if (EnvSize == 0) + EnvSize = DL.getStackAlignment().value(); + auto *VTy = ArrayType::get(getInt8Ty(), EnvSize); + auto *Var = new AllocaInst(VTy, DL.getAllocaAddrSpace()); + MaybeAlign A = DL.getFPEnvironmentAlign(); + if (A && DL.getStackAlignment() < A) + Var->setAlignment(A); + GetInsertBlock()->getInstList().insert(GetInsertPoint(), Var); + + auto *Ptr = + GetElementPtrInst::Create(nullptr, Var, {getInt32(0), getInt32(0)}); + GetInsertBlock()->getInstList().insert(GetInsertPoint(), Ptr); + Function *TheFn = Intrinsic::getDeclaration(M, Intrinsic::get_fenv); + FPEnv = Var; + return createCallHelper(TheFn, Ptr, this); +} + +CallInst *IRBuilderBase::CreateSetFPEnv(Value *FPEnv) { + assert(FPEnv); + assert(BB && BB->getParent() && "No current function!"); + Module *M = BB->getParent()->getParent(); + const DataLayout &DL = M->getDataLayout(); + unsigned EnvSize = DL.getFPEnvironmentSize(); + if (EnvSize == 0) + EnvSize = DL.getStackAlignment().value(); + (void)EnvSize; + assert(FPEnv->getType()->isPointerTy()); + auto *PTy = FPEnv->getType()->getPointerElementType(); + (void)PTy; + assert(DL.getTypeStoreSize(PTy) >= EnvSize); + + auto *Ptr = + GetElementPtrInst::Create(nullptr, FPEnv, {getInt32(0), getInt32(0)}); + GetInsertBlock()->getInstList().insert(GetInsertPoint(), Ptr); + Function *TheFn = Intrinsic::getDeclaration(M, Intrinsic::set_fenv); + return createCallHelper(TheFn, Ptr, this); +} + +CallInst *IRBuilderBase::CreateResetFPEnv() { + assert(BB && BB->getParent() && "No current function!"); + Module *M = BB->getParent()->getParent(); + Function *TheFn = Intrinsic::getDeclaration(M, Intrinsic::reset_fenv); + return createCallHelper(TheFn, {}, this); +} 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 @@ -4712,6 +4712,22 @@ "Intrinsic does not support vectors", &Call); break; } + case Intrinsic::get_fenv: + case Intrinsic::set_fenv: + case Intrinsic::reset_fenv: { + if (ID == Intrinsic::get_fenv || ID == Intrinsic::set_fenv) { + Type *ArgTy = Call.getArgOperand(0)->getType(); + Assert(ArgTy->isPointerTy(), "argument must have pointer type"); + Type *FEnvType = ArgTy->getPointerElementType(); + if (!FEnvType->isIntegerTy(8)) { + const DataLayout &DL = M.getDataLayout(); + unsigned FEnvSize = DL.getFPEnvironmentSize(); + unsigned ActualFEnvSize = DL.getTypeStoreSize(FEnvType); + Assert(ActualFEnvSize >= 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.fenv(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.fenv(i8* %fpenv) + ret void +} +; CHECK-LABEL: func_02: +; CHECK: call #fesetenv + +define void @func_03() { +entry: + call void @llvm.reset.fenv() + ret void +} +; CHECK-LABEL: func_03: +; CHECK: call #fesetenv + +declare void @llvm.get.fenv(i8* %fpenv) +declare void @llvm.set.fenv(i8* %fpenv) +declare void @llvm.reset.fenv() 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 @@ -123,6 +123,62 @@ EXPECT_FALSE(II->hasNoNaNs()); } +TEST_F(IRBuilderTest, FPEnvironment) { + IRBuilder<> Builder(BB); + CallInst *Call; + Value *V; + IntrinsicInst *II; + Type *PointeeTy; + + // If target did not specify FP environment size, use default value. + for (auto DLStr : {"S64", "S64-fe:0"}) { + M->setDataLayout(DLStr); + + Call = Builder.CreateGetFPEnv(V); + II = cast(Call); + EXPECT_EQ(Intrinsic::get_fenv, II->getIntrinsicID()); + EXPECT_TRUE(V->getType()->isPointerTy()); + PointeeTy = V->getType()->getPointerElementType(); + EXPECT_LE(8U, M->getDataLayout().getTypeStoreSize(PointeeTy)); + + Call = Builder.CreateSetFPEnv(V); + II = cast(Call); + EXPECT_EQ(Intrinsic::set_fenv, II->getIntrinsicID()); + + Call = Builder.CreateResetFPEnv(); + II = cast(Call); + EXPECT_EQ(Intrinsic::reset_fenv, II->getIntrinsicID()); + } + + M->setDataLayout("fe:32"); + + Call = Builder.CreateGetFPEnv(V); + II = cast(Call); + EXPECT_EQ(Intrinsic::get_fenv, II->getIntrinsicID()); + EXPECT_TRUE(V->getType()->isPointerTy()); + PointeeTy = V->getType()->getPointerElementType(); + EXPECT_EQ(4U, M->getDataLayout().getTypeStoreSize(PointeeTy)); + + Call = Builder.CreateSetFPEnv(V); + II = cast(Call); + EXPECT_EQ(Intrinsic::set_fenv, II->getIntrinsicID()); + + Call = Builder.CreateResetFPEnv(); + II = cast(Call); + EXPECT_EQ(Intrinsic::reset_fenv, II->getIntrinsicID()); + + M->setDataLayout("S32-fe:32:64"); + + Call = Builder.CreateGetFPEnv(V); + II = cast(Call); + EXPECT_EQ(Intrinsic::get_fenv, II->getIntrinsicID()); + EXPECT_TRUE(V->getType()->isPointerTy()); + PointeeTy = V->getType()->getPointerElementType(); + EXPECT_EQ(4U, M->getDataLayout().getTypeStoreSize(PointeeTy)); + auto AI = cast(V); + EXPECT_LE(8U, AI->getAlignment()); +} + TEST_F(IRBuilderTest, IntrinsicsWithScalableVectors) { IRBuilder<> Builder(BB); CallInst *Call;