diff --git a/llvm/lib/Transforms/Utils/CloneFunction.cpp b/llvm/lib/Transforms/Utils/CloneFunction.cpp --- a/llvm/lib/Transforms/Utils/CloneFunction.cpp +++ b/llvm/lib/Transforms/Utils/CloneFunction.cpp @@ -279,6 +279,10 @@ bool ModuleLevelChanges; const char *NameSuffix; ClonedCodeInfo *CodeInfo; + bool HostFuncIsStrictFP; + bool InlinedFuncHasFPOps = false; + + Instruction *cloneInstruction(BasicBlock::const_iterator II); public: PruningFunctionCloner(Function *newFunc, const Function *oldFunc, @@ -286,7 +290,10 @@ const char *nameSuffix, ClonedCodeInfo *codeInfo) : NewFunc(newFunc), OldFunc(oldFunc), VMap(valueMap), ModuleLevelChanges(moduleLevelChanges), NameSuffix(nameSuffix), - CodeInfo(codeInfo) {} + CodeInfo(codeInfo) { + HostFuncIsStrictFP = newFunc->getAttributes().hasAttribute( + AttributeList::FunctionIndex, Attribute::StrictFP); + } /// The specified block is found to be reachable, clone it and /// anything that it can reach. @@ -296,6 +303,45 @@ }; } +Instruction * +PruningFunctionCloner::cloneInstruction(BasicBlock::const_iterator II) { + const Instruction &OldInst = *II; + Instruction *NewInst = nullptr; + bool NeedTransformToCall = false; + if (HostFuncIsStrictFP) { + Intrinsic::ID CIID = getConstrainedIntrinsicID(OldInst); + if (CIID != Intrinsic::not_intrinsic) { + // Instead of cloning the instruction, a call to constrained intrinsic + // should be created. + assert(CIID != Intrinsic::not_intrinsic); + Function *IFn = Intrinsic::getDeclaration(NewFunc->getParent(), CIID, + OldInst.getType()); + + // Assume that the first arguments of constrained intrinsics are the same as + // the operands of original instruction. + SmallVector Args; + for (auto &Op : OldInst.operands()) + Args.push_back(Op); + + // The last two arguments of a constrained intrinsic are metadata that + // represent rounding mode and exception behavior. The inlined function + // uses default settings. + LLVMContext &Ctx = NewFunc->getContext(); + Value *RoundingMode = MetadataAsValue::get(Ctx, MDString::get(Ctx, "round.tonearest")); + Value *ExBehavior = MetadataAsValue::get(Ctx, MDString::get(Ctx, "fpexcept.ignore")); + Args.push_back(RoundingMode); + Args.push_back(ExBehavior); + + CallInst *ICall = CallInst::Create(IFn, Args, OldInst.getName() + ".strict"); + ICall->addAttribute(AttributeList::FunctionIndex, Attribute::StrictFP); + NewInst = ICall; + } + } + if (!NewInst) + NewInst = II->clone(); + return NewInst; +} + /// The specified block is found to be reachable, clone it and /// anything that it can reach. void PruningFunctionCloner::CloneBlock(const BasicBlock *BB, @@ -333,7 +379,7 @@ for (BasicBlock::const_iterator II = StartingInst, IE = --BB->end(); II != IE; ++II) { - Instruction *NewInst = II->clone(); + Instruction *NewInst = cloneInstruction(II); // Eagerly remap operands to the newly cloned instruction, except for PHI // nodes for which we defer processing until we update the CFG. diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp --- a/llvm/lib/Transforms/Utils/InlineFunction.cpp +++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp @@ -1596,6 +1596,15 @@ BasicBlock *OrigBB = TheCall->getParent(); Function *Caller = OrigBB->getParent(); + // Do not inline strictfp function into non-strictfp for now. It would require + // conversion of all FP operations in host function to constrained intrinsics. + if (CalledFunc->getAttributes().hasAttribute( + AttributeList::FunctionIndex, Attribute::StrictFP) && + !Caller->getAttributes().hasAttribute( + AttributeList::FunctionIndex, Attribute::StrictFP)) { + return "incompatible strictfp attributes"; + } + // GC poses two hazards to inlining, which only occur when the callee has GC: // 1. If the caller has no GC, then the callee's GC must be propagated to the // caller. diff --git a/llvm/test/Transforms/CodeExtractor/PartialInlineAttributes.ll b/llvm/test/Transforms/CodeExtractor/PartialInlineAttributes.ll --- a/llvm/test/Transforms/CodeExtractor/PartialInlineAttributes.ll +++ b/llvm/test/Transforms/CodeExtractor/PartialInlineAttributes.ll @@ -73,10 +73,10 @@ attributes #0 = { inlinehint minsize noduplicate noimplicitfloat norecurse noredzone nounwind nonlazybind optsize safestack sanitize_address sanitize_hwaddress sanitize_memory - sanitize_thread ssp sspreq sspstrong strictfp uwtable "foo"="bar" + sanitize_thread ssp sspreq sspstrong uwtable "foo"="bar" "patchable-function"="prologue-short-redirect" "probe-stack"="_foo_guard" "stack-probe-size"="4096" } -; CHECK: attributes [[FN_ATTRS]] = { inlinehint minsize noduplicate noimplicitfloat norecurse noredzone nounwind nonlazybind optsize safestack sanitize_address sanitize_hwaddress sanitize_memory sanitize_thread ssp sspreq sspstrong strictfp uwtable "foo"="bar" "patchable-function"="prologue-short-redirect" "probe-stack"="_foo_guard" "stack-probe-size"="4096" } +; CHECK: attributes [[FN_ATTRS]] = { inlinehint minsize noduplicate noimplicitfloat norecurse noredzone nounwind nonlazybind optsize safestack sanitize_address sanitize_hwaddress sanitize_memory sanitize_thread ssp sspreq sspstrong uwtable "foo"="bar" "patchable-function"="prologue-short-redirect" "probe-stack"="_foo_guard" "stack-probe-size"="4096" } ; attributes to drop attributes #1 = { diff --git a/llvm/test/Transforms/Inline/inline_strictfp.ll b/llvm/test/Transforms/Inline/inline_strictfp.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Inline/inline_strictfp.ll @@ -0,0 +1,61 @@ +; RUN: opt -inline %s -S | FileCheck %s + + +; Ordinary function is inlined into strictfp function. + +define float @func_01(float %a) { +entry: + %add = fadd float %a, %a + ret float %add +} + +define float @func_02(float %a) strictfp { +entry: + %0 = tail call float @func_01(float %a) + %add = call float @llvm.experimental.constrained.fadd.f32(float %0, float 2.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.strict") strictfp + ret float %add +; CHECK_LABEL: @func_02 +; CHECK: call float @llvm.experimental.constrained.fadd.f32(float {{.*}}, float {{.*}}, metadata !"round.tonearest", metadata !"fpexcept.ignore") +; CHECK: call float @llvm.experimental.constrained.fadd.f32(float {{.*}}, float 2.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.strict") +} + + +; strictfp function is inlined into another strictfp function. + +define float @func_03(float %a) strictfp { +entry: + %add = call float @llvm.experimental.constrained.fadd.f32(float %a, float %a, metadata !"round.downward", metadata !"fpexcept.maytrap") strictfp + ret float %add +} + +define float @func_04(float %a) strictfp { +entry: + %0 = tail call float @func_03(float %a) + %add = call float @llvm.experimental.constrained.fadd.f32(float %0, float 2.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.strict") strictfp + ret float %add +; CHECK_LABEL: @func_04 +; CHECK: call float @llvm.experimental.constrained.fadd.f32(float {{.*}}, float {{.*}}, metadata !"round.downward", metadata !"fpexcept.maytrap") +; CHECK: call float @llvm.experimental.constrained.fadd.f32(float {{.*}}, float 2.000000e+00, metadata !"round.dynamic", metadata !"fpexcept.strict") +} + + +; strictfp function is NOT inlined into ordinary function. + +define float @func_05(float %a) strictfp { +entry: + %add = call float @llvm.experimental.constrained.fadd.f32(float %a, float %a, metadata !"round.downward", metadata !"fpexcept.maytrap") strictfp + ret float %add +} + +define float @func_06(float %a) { +entry: + %0 = tail call float @func_05(float %a) + %add = fadd float %0, 2.000000e+00 + ret float %add +; CHECK_LABEL: @func_06 +; CHECK: call float @func_05(float %a) +; CHECK: fadd float %0, 2.000000e+00 +} + + +declare float @llvm.experimental.constrained.fadd.f32(float, float, metadata, metadata) strictfp