Index: clang/lib/CodeGen/CGCall.cpp =================================================================== --- clang/lib/CodeGen/CGCall.cpp +++ clang/lib/CodeGen/CGCall.cpp @@ -4526,13 +4526,13 @@ // Apply some call-site-specific attributes. // TODO: work this into building the attribute set. - // Apply always_inline to all calls within flatten functions. + // Apply alwaysinline_recursively to all calls within flatten functions. // FIXME: should this really take priority over __try, below? if (CurCodeDecl && CurCodeDecl->hasAttr() && !(TargetDecl && TargetDecl->hasAttr())) { Attrs = Attrs.addAttribute(getLLVMContext(), llvm::AttributeList::FunctionIndex, - llvm::Attribute::AlwaysInline); + llvm::Attribute::AlwaysInlineRecursively); } // Disable inlining inside SEH __try blocks. Index: llvm/docs/BitCodeFormat.rst =================================================================== --- llvm/docs/BitCodeFormat.rst +++ llvm/docs/BitCodeFormat.rst @@ -1059,6 +1059,7 @@ * code 57: ``optforfuzzing`` * code 58: ``shadowcallstack`` * code 64: ``sanitize_memtag`` +* code 65: ``alwaysinline_recursively`` .. note:: The ``allocsize`` attribute has a special encoding for its arguments. Its two Index: llvm/docs/LangRef.rst =================================================================== --- llvm/docs/LangRef.rst +++ llvm/docs/LangRef.rst @@ -1393,6 +1393,9 @@ This attribute indicates that the inliner should attempt to inline this function into callers whenever possible, ignoring any active inlining size threshold for this caller. +``alwaysinline_recursively`` + This attribute is similar to ``alwaysinline``, but also applies recursively to + all inlined function calls. ``builtin`` This indicates that the callee function at a call site should be recognized as a built-in function, even though the function's declaration Index: llvm/include/llvm/Bitcode/LLVMBitCodes.h =================================================================== --- llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -633,6 +633,7 @@ ATTR_KIND_NOFREE = 62, ATTR_KIND_NOSYNC = 63, ATTR_KIND_SANITIZE_MEMTAG = 64, + ATTR_KIND_ALWAYS_INLINE_RECURSIVELY = 65, }; enum ComdatSelectionKindCodes { Index: llvm/include/llvm/IR/Attributes.td =================================================================== --- llvm/include/llvm/IR/Attributes.td +++ llvm/include/llvm/IR/Attributes.td @@ -23,6 +23,9 @@ /// inline=always. def AlwaysInline : EnumAttr<"alwaysinline">; +/// Like AlwaysInline, but applies recursively +def AlwaysInlineRecursively : EnumAttr<"alwaysinline_recursively">; + /// Function can access memory only using pointers based on its arguments. def ArgMemOnly : EnumAttr<"argmemonly">; Index: llvm/lib/Analysis/InlineCost.cpp =================================================================== --- llvm/lib/Analysis/InlineCost.cpp +++ llvm/lib/Analysis/InlineCost.cpp @@ -2245,6 +2245,14 @@ return llvm::InlineCost::getNever(IsViable.getFailureReason()); } + // Inline call sites marked alwaysinline_recursively + if (Call.hasFnAttr(Attribute::AlwaysInlineRecursively)) { + auto IsViable = isInlineViable(*Callee); + if (IsViable.isSuccess()) + return llvm::InlineCost::getAlways("alwaysinline_recursively attribute"); + return llvm::InlineCost::getNever(IsViable.getFailureReason()); + } + // Never inline functions with conflicting attributes (unless callee has // always-inline attribute). Function *Caller = Call.getCaller(); Index: llvm/lib/AsmParser/LLLexer.cpp =================================================================== --- llvm/lib/AsmParser/LLLexer.cpp +++ llvm/lib/AsmParser/LLLexer.cpp @@ -632,6 +632,7 @@ KEYWORD(attributes); KEYWORD(alwaysinline); + KEYWORD(alwaysinline_recursively); KEYWORD(allocsize); KEYWORD(argmemonly); KEYWORD(builtin); Index: llvm/lib/AsmParser/LLParser.cpp =================================================================== --- llvm/lib/AsmParser/LLParser.cpp +++ llvm/lib/AsmParser/LLParser.cpp @@ -1270,6 +1270,9 @@ continue; } case lltok::kw_alwaysinline: B.addAttribute(Attribute::AlwaysInline); break; + case lltok::kw_alwaysinline_recursively: + B.addAttribute(Attribute::AlwaysInlineRecursively); + break; case lltok::kw_argmemonly: B.addAttribute(Attribute::ArgMemOnly); break; case lltok::kw_builtin: B.addAttribute(Attribute::Builtin); break; case lltok::kw_cold: B.addAttribute(Attribute::Cold); break; @@ -1654,6 +1657,7 @@ case lltok::kw_alignstack: case lltok::kw_alwaysinline: + case lltok::kw_alwaysinline_recursively: case lltok::kw_argmemonly: case lltok::kw_builtin: case lltok::kw_inlinehint: @@ -1752,6 +1756,7 @@ case lltok::kw_alignstack: case lltok::kw_alwaysinline: + case lltok::kw_alwaysinline_recursively: case lltok::kw_argmemonly: case lltok::kw_builtin: case lltok::kw_cold: Index: llvm/lib/AsmParser/LLToken.h =================================================================== --- llvm/lib/AsmParser/LLToken.h +++ llvm/lib/AsmParser/LLToken.h @@ -176,6 +176,7 @@ kw_attributes, kw_allocsize, kw_alwaysinline, + kw_alwaysinline_recursively, kw_argmemonly, kw_sanitize_address, kw_sanitize_hwaddress, Index: llvm/lib/Bitcode/Reader/BitcodeReader.cpp =================================================================== --- llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -1304,6 +1304,10 @@ case Attribute::SanitizeMemTag: llvm_unreachable("sanitize_memtag attribute not supported in raw format"); break; + case Attribute::AlwaysInlineRecursively: + llvm_unreachable( + "alwaysinline_recursively attribute not supported in raw format"); + break; } llvm_unreachable("Unsupported attribute type"); } @@ -1313,12 +1317,10 @@ for (Attribute::AttrKind I = Attribute::None; I != Attribute::EndAttrKinds; I = Attribute::AttrKind(I + 1)) { - if (I == Attribute::SanitizeMemTag || - I == Attribute::Dereferenceable || - I == Attribute::DereferenceableOrNull || - I == Attribute::ArgMemOnly || - I == Attribute::AllocSize || - I == Attribute::NoSync) + if (I == Attribute::SanitizeMemTag || I == Attribute::Dereferenceable || + I == Attribute::DereferenceableOrNull || I == Attribute::ArgMemOnly || + I == Attribute::AllocSize || I == Attribute::NoSync || + I == Attribute::AlwaysInlineRecursively) continue; if (uint64_t A = (Val & getRawAttributeMask(I))) { if (I == Attribute::Alignment) Index: llvm/lib/Bitcode/Writer/BitcodeWriter.cpp =================================================================== --- llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -604,6 +604,8 @@ return bitc::ATTR_KIND_ALLOC_SIZE; case Attribute::AlwaysInline: return bitc::ATTR_KIND_ALWAYS_INLINE; + case Attribute::AlwaysInlineRecursively: + return bitc::ATTR_KIND_ALWAYS_INLINE_RECURSIVELY; case Attribute::ArgMemOnly: return bitc::ATTR_KIND_ARGMEMONLY; case Attribute::Builtin: Index: llvm/lib/CodeGen/SafeStack.cpp =================================================================== --- llvm/lib/CodeGen/SafeStack.cpp +++ llvm/lib/CodeGen/SafeStack.cpp @@ -707,7 +707,8 @@ bool SafeStack::ShouldInlinePointerAddress(CallSite &CS) { Function *Callee = CS.getCalledFunction(); - if (CS.hasFnAttr(Attribute::AlwaysInline) && + if ((CS.hasFnAttr(Attribute::AlwaysInline) || + CS.hasFnAttr(Attribute::AlwaysInlineRecursively)) && isInlineViable(*Callee).isSuccess()) return true; if (Callee->isInterposable() || Callee->hasFnAttribute(Attribute::NoInline) || Index: llvm/lib/IR/Attributes.cpp =================================================================== --- llvm/lib/IR/Attributes.cpp +++ llvm/lib/IR/Attributes.cpp @@ -324,6 +324,8 @@ return "sanitize_memtag"; if (hasAttribute(Attribute::AlwaysInline)) return "alwaysinline"; + if (hasAttribute(Attribute::AlwaysInlineRecursively)) + return "alwaysinline_recursively"; if (hasAttribute(Attribute::ArgMemOnly)) return "argmemonly"; if (hasAttribute(Attribute::Builtin)) Index: llvm/lib/IR/Verifier.cpp =================================================================== --- llvm/lib/IR/Verifier.cpp +++ llvm/lib/IR/Verifier.cpp @@ -1552,6 +1552,7 @@ case Attribute::SpeculativeLoadHardening: case Attribute::Speculatable: case Attribute::StrictFP: + case Attribute::AlwaysInlineRecursively: return true; default: break; @@ -1663,6 +1664,12 @@ "'noinline and alwaysinline' are incompatible!", V); + Assert(!(Attrs.hasAttribute(Attribute::NoInline) && + Attrs.hasAttribute(Attribute::AlwaysInlineRecursively)), + "Attributes " + "'noinline and alwaysinline_recursively' are incompatible!", + V); + if (Attrs.hasAttribute(Attribute::ByVal) && Attrs.getByValType()) { Assert(Attrs.getByValType() == cast(Ty)->getElementType(), "Attribute 'byval' type does not match parameter!", V); @@ -1815,6 +1822,11 @@ Attrs.hasFnAttribute(Attribute::AlwaysInline)), "Attributes 'noinline and alwaysinline' are incompatible!", V); + Assert(!(Attrs.hasFnAttribute(Attribute::NoInline) && + Attrs.hasFnAttribute(Attribute::AlwaysInlineRecursively)), + "Attributes 'noinline and alwaysinline_recursively' are incompatible!", + V); + if (Attrs.hasFnAttribute(Attribute::OptimizeNone)) { Assert(Attrs.hasFnAttribute(Attribute::NoInline), "Attribute 'optnone' requires 'noinline'!", V); Index: llvm/lib/Target/AMDGPU/AMDGPUInline.cpp =================================================================== --- llvm/lib/Target/AMDGPU/AMDGPUInline.cpp +++ llvm/lib/Target/AMDGPU/AMDGPUInline.cpp @@ -188,7 +188,8 @@ if (!TTI.areInlineCompatible(Caller, Callee)) return llvm::InlineCost::getNever("incompatible"); - if (CS.hasFnAttr(Attribute::AlwaysInline)) { + if (CS.hasFnAttr(Attribute::AlwaysInline) || + CS.hasFnAttr(Attribute::AlwaysInlineRecursively)) { auto IsViable = isInlineViable(*Callee); if (IsViable.isSuccess()) return llvm::InlineCost::getAlways("alwaysinline viable"); Index: llvm/lib/Target/Hexagon/HexagonLoopIdiomRecognition.cpp =================================================================== --- llvm/lib/Target/Hexagon/HexagonLoopIdiomRecognition.cpp +++ llvm/lib/Target/Hexagon/HexagonLoopIdiomRecognition.cpp @@ -2090,7 +2090,8 @@ // Don't generate memmove if this function will be inlined. This is // because the caller will undergo this transformation after inlining. Function *Func = CurLoop->getHeader()->getParent(); - if (Func->hasFnAttribute(Attribute::AlwaysInline)) + if ((Func->hasFnAttribute(Attribute::AlwaysInline) || + Func->hasFnAttribute(Attribute::AlwaysInlineRecursively))) goto CleanupAndExit; // In case of a memmove, the call to memmove will be executed instead Index: llvm/lib/Transforms/IPO/AlwaysInliner.cpp =================================================================== --- llvm/lib/Transforms/IPO/AlwaysInliner.cpp +++ llvm/lib/Transforms/IPO/AlwaysInliner.cpp @@ -47,7 +47,9 @@ bool Changed = false; SmallVector InlinedFunctions; for (Function &F : M) - if (!F.isDeclaration() && F.hasFnAttribute(Attribute::AlwaysInline) && + if (!F.isDeclaration() && + (F.hasFnAttribute(Attribute::AlwaysInline) || + F.hasFnAttribute(Attribute::AlwaysInlineRecursively)) && isInlineViable(F).isSuccess()) { Calls.clear(); @@ -164,7 +166,8 @@ if (Callee->isDeclaration()) return InlineCost::getNever("no definition"); - if (!CS.hasFnAttr(Attribute::AlwaysInline)) + if (!CS.hasFnAttr(Attribute::AlwaysInline) && + !CS.hasFnAttr(Attribute::AlwaysInlineRecursively)) return InlineCost::getNever("no alwaysinline attribute"); auto IsViable = isInlineViable(*Callee); Index: llvm/lib/Transforms/IPO/Attributor.cpp =================================================================== --- llvm/lib/Transforms/IPO/Attributor.cpp +++ llvm/lib/Transforms/IPO/Attributor.cpp @@ -8380,7 +8380,8 @@ ReadOrWriteInsts.push_back(&I); } - if (F.hasFnAttribute(Attribute::AlwaysInline) && + if ((F.hasFnAttribute(Attribute::AlwaysInline) || + F.hasFnAttribute(Attribute::AlwaysInlineRecursively)) && isInlineViable(F).isSuccess()) InfoCache.InlineableFunctions.insert(&F); } Index: llvm/lib/Transforms/IPO/ForceFunctionAttrs.cpp =================================================================== --- llvm/lib/Transforms/IPO/ForceFunctionAttrs.cpp +++ llvm/lib/Transforms/IPO/ForceFunctionAttrs.cpp @@ -29,6 +29,7 @@ static Attribute::AttrKind parseAttrKind(StringRef Kind) { return StringSwitch(Kind) .Case("alwaysinline", Attribute::AlwaysInline) + .Case("alwaysinline_recursively", Attribute::AlwaysInlineRecursively) .Case("builtin", Attribute::Builtin) .Case("cold", Attribute::Cold) .Case("convergent", Attribute::Convergent) Index: llvm/lib/Transforms/IPO/HotColdSplitting.cpp =================================================================== --- llvm/lib/Transforms/IPO/HotColdSplitting.cpp +++ llvm/lib/Transforms/IPO/HotColdSplitting.cpp @@ -203,6 +203,8 @@ bool HotColdSplitting::shouldOutlineFrom(const Function &F) const { if (F.hasFnAttribute(Attribute::AlwaysInline)) return false; + if (F.hasFnAttribute(Attribute::AlwaysInlineRecursively)) + return false; if (F.hasFnAttribute(Attribute::NoInline)) return false; Index: llvm/lib/Transforms/IPO/Inliner.cpp =================================================================== --- llvm/lib/Transforms/IPO/Inliner.cpp +++ llvm/lib/Transforms/IPO/Inliner.cpp @@ -682,6 +682,9 @@ DebugLoc DLoc = CS->getDebugLoc(); BasicBlock *Block = CS.getParent(); + bool AlwaysInlineRecursively = + CS.hasFnAttr(Attribute::AlwaysInlineRecursively); + // Attempt to inline the function. using namespace ore; @@ -712,8 +715,16 @@ int NewHistoryID = InlineHistory.size(); InlineHistory.push_back(std::make_pair(Callee, InlineHistoryID)); - for (Value *Ptr : InlineInfo.InlinedCalls) - CallSites.push_back(std::make_pair(CallSite(Ptr), NewHistoryID)); + for (Value *Ptr : InlineInfo.InlinedCalls) { + CallSite NewCS(Ptr); + // Propagate alwaysinline_recursively attribute to all inlined call + // sites which are not marked noinline + if (AlwaysInlineRecursively && + !NewCS.hasFnAttr(Attribute::NoInline)) + NewCS.addAttribute(AttributeList::FunctionIndex, + Attribute::AlwaysInlineRecursively); + CallSites.push_back(std::make_pair(NewCS, NewHistoryID)); + } } } @@ -813,7 +824,8 @@ // Handle the case when this function is called and we only want to care // about always-inline functions. This is a bit of a hack to share code // between here and the InlineAlways pass. - if (AlwaysInlineOnly && !F->hasFnAttribute(Attribute::AlwaysInline)) + if (AlwaysInlineOnly && !F->hasFnAttribute(Attribute::AlwaysInline) && + !F->hasFnAttribute(Attribute::AlwaysInlineRecursively)) continue; // If the only remaining users of the function are dead constants, remove @@ -1079,6 +1091,9 @@ DebugLoc DLoc = CS->getDebugLoc(); BasicBlock *Block = CS.getParent(); + bool AlwaysInlineRecursively = + CS.hasFnAttr(Attribute::AlwaysInlineRecursively); + using namespace ore; InlineResult IR = InlineFunction(CS, IFI); @@ -1114,9 +1129,16 @@ if (tryPromoteCall(CS)) NewCallee = CS.getCalledFunction(); } - if (NewCallee) - if (!NewCallee->isDeclaration()) + if (NewCallee) { + if (!NewCallee->isDeclaration()) { + // Propagate alwaysinline_recursively attribute to all inlined + // call sites, which are not marked noinline + if (AlwaysInlineRecursively && !CS.hasFnAttr(Attribute::NoInline)) + CS.addAttribute(AttributeList::FunctionIndex, + Attribute::AlwaysInlineRecursively); Calls.push_back({CS, NewHistoryID}); + } + } } } Index: llvm/lib/Transforms/IPO/PartialInlining.cpp =================================================================== --- llvm/lib/Transforms/IPO/PartialInlining.cpp +++ llvm/lib/Transforms/IPO/PartialInlining.cpp @@ -1271,6 +1271,9 @@ if (F->hasFnAttribute(Attribute::AlwaysInline)) return {false, nullptr}; + if (F->hasFnAttribute(Attribute::AlwaysInlineRecursively)) + return {false, nullptr}; + if (F->hasFnAttribute(Attribute::NoInline)) return {false, nullptr}; Index: llvm/lib/Transforms/IPO/SyntheticCountsPropagation.cpp =================================================================== --- llvm/lib/Transforms/IPO/SyntheticCountsPropagation.cpp +++ llvm/lib/Transforms/IPO/SyntheticCountsPropagation.cpp @@ -77,6 +77,7 @@ if (F.isDeclaration()) continue; if (F.hasFnAttribute(Attribute::AlwaysInline) || + F.hasFnAttribute(Attribute::AlwaysInlineRecursively) || F.hasFnAttribute(Attribute::InlineHint)) { // Use a higher value for inline functions to account for the fact that // these are usually beneficial to inline. Index: llvm/lib/Transforms/Instrumentation/InstrProfiling.cpp =================================================================== --- llvm/lib/Transforms/Instrumentation/InstrProfiling.cpp +++ llvm/lib/Transforms/Instrumentation/InstrProfiling.cpp @@ -751,7 +751,8 @@ // have its address taken. Doing so would create an undefined external ref to // the function, which would fail to link. if (HasAvailableExternallyLinkage && - F->hasFnAttribute(Attribute::AlwaysInline)) + (F->hasFnAttribute(Attribute::AlwaysInline) || + F->hasFnAttribute(Attribute::AlwaysInlineRecursively))) return false; // Prohibit function address recording if the function is both internal and Index: llvm/lib/Transforms/Utils/CodeExtractor.cpp =================================================================== --- llvm/lib/Transforms/Utils/CodeExtractor.cpp +++ llvm/lib/Transforms/Utils/CodeExtractor.cpp @@ -892,6 +892,7 @@ continue; // Those attributes should be safe to propagate to the extracted function. case Attribute::AlwaysInline: + case Attribute::AlwaysInlineRecursively: case Attribute::Cold: case Attribute::NoRecurse: case Attribute::InlineHint: Index: llvm/test/Transforms/Inline/always-inline-recursively.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/Inline/always-inline-recursively.ll @@ -0,0 +1,267 @@ +; RUN: opt < %s -inline-threshold=0 -inline -S | FileCheck %s +; RUN: opt < %s -inline-threshold=0 -always-inline -S | FileCheck %s +; +; Ensure the threshold has no impact on these decisions. +; RUN: opt < %s -inline-threshold=20000000 -inline -S | FileCheck %s +; RUN: opt < %s -inline-threshold=20000000 -always-inline -S | FileCheck %s +; RUN: opt < %s -inline-threshold=-20000000 -inline -S | FileCheck %s +; RUN: opt < %s -inline-threshold=-20000000 -always-inline -S | FileCheck %s + +; In the tests involving recursive functions we call the external function and +; continue recursion depending on the external variable, so that any recursion +; conditions are opaque to the optimizer + +; Following tests are conducted, for annotating call-sites and function declarations: +; Test that a simple tree call-graph is inlined +; Test that functions marked noinline are not inlined +; Test that a recursive call is not inlined +; Test that an indirectly recursive call is inlined until a directly recursive call remains + +; External funcion not visible to the optimizer +declare void @ext_func() +; External variable not visible to the optimizer +@ext_var = external global i32 + +; Test that a simple tree call-graph is inlined +; when annotating call-sites + +define void @test_calls_tree() { +; CHECK-LABEL: @test_calls_tree() { + call void @test_calls_tree_1() alwaysinline_recursively +; CHECK-NEXT: call void @ext_func() #1 + call void @test_calls_tree_2() alwaysinline_recursively +; CHECK-NEXT: call void @ext_func() #1 +; CHECK-NEXT: call void @ext_func() #1 +; CHECK-NEXT: call void @ext_func() #1 + ret void +; CHECK-NEXT: ret void +} + +define void @test_calls_tree_1() { + call void @ext_func() + ret void +} + +define void @test_calls_tree_2() { + call void @test_calls_tree_2_1() + call void @test_calls_tree_2_2() + call void @test_calls_tree_2_3() + ret void +} + +define void @test_calls_tree_2_1() { + call void @ext_func() + ret void +} + +define void @test_calls_tree_2_2() { + call void @ext_func() + ret void +} + +define void @test_calls_tree_2_3() { + call void @ext_func() + ret void +} + +; Test that functions marked noinline are not inlined +; when annotating call-sites + +define void @test_calls_noinline() { +; CHECK-LABEL: @test_calls_noinline() { + call void @test_calls_noinline_inlined() alwaysinline_recursively +; CHECK-NEXT: call void @ext_func() #1 + call void @test_calls_noinline_inlined2() alwaysinline_recursively +; CHECK-NEXT: call void @test_calls_noinline_notinlined() + ret void +; CHECK-NEXT: ret void +} + +define void @test_calls_noinline_inlined() { + call void @ext_func() + ret void +} + +define void @test_calls_noinline_inlined2() { + call void @test_calls_noinline_notinlined() + ret void +} + +define void @test_calls_noinline_notinlined() noinline { + call void @ext_func() + ret void +} + +; Test that a recursive call is not inlined +; when annotating call-sites + +define void @test_calls_rec() { +; CHECK-LABEL: @test_calls_rec() { + call void @test_calls_rec_func() alwaysinline_recursively +; CHECK-NEXT: call void @test_calls_rec_func() #1 + ret void +; CHECK-NEXT: ret void +} + +define void @test_calls_rec_func() { + call void @ext_func() + %1 = load i32, i32* @ext_var + %2 = icmp ne i32 %1, 0 + br i1 %2, label %3, label %4 + +3: + call void @test_calls_rec_func() + br label %4 + +4: + ret void +} + +; Test that an indirectly recursive call is inlined +; until a directly recursive call remains +; when annotating call-sites + +define void @test_calls_irec() { +; CHECK-LABEL: @test_calls_irec() { + call void @test_calls_irec_func() alwaysinline_recursively +; CHECK: call void @test_calls_irec() #1 + ret void +; CHECK: ret void +} + +define void @test_calls_irec_func() { + call void @ext_func() + %1 = load i32, i32* @ext_var + %2 = icmp ne i32 %1, 0 + br i1 %2, label %3, label %4 + +3: + call void @test_calls_irec() + br label %4 + +4: + ret void +} + +; Test that a simple tree call-graph is inlined +; when annotating function definitions + +define void @test_defs_tree() { +; CHECK-LABEL: @test_defs_tree() { + call void @test_defs_tree_1() +; CHECK-NEXT: call void @ext_func() #1 + call void @test_defs_tree_2() +; CHECK-NEXT: call void @ext_func() #1 +; CHECK-NEXT: call void @ext_func() #1 +; CHECK-NEXT: call void @ext_func() #1 + ret void +; CHECK-NEXT: ret void +} + +define void @test_defs_tree_1() alwaysinline_recursively { + call void @ext_func() + ret void +} + +define void @test_defs_tree_2() alwaysinline_recursively { + call void @test_defs_tree_2_1() + call void @test_defs_tree_2_2() + call void @test_defs_tree_2_3() + ret void +} + +define void @test_defs_tree_2_1() { + call void @ext_func() + ret void +} + +define void @test_defs_tree_2_2() { + call void @ext_func() + ret void +} + +define void @test_defs_tree_2_3() { + call void @ext_func() + ret void +} + +; Test that functions marked noinline are not inlined +; when annotating function definitions + +define void @test_defs_noinline() { +; CHECK-LABEL: @test_defs_noinline() { + call void @test_defs_noinline_inlined() +; CHECK-NEXT: call void @ext_func() #1 + call void @test_defs_noinline_inlined2() +; CHECK-NEXT: call void @test_defs_noinline_notinlined() + ret void +; CHECK-NEXT: ret void +} + +define void @test_defs_noinline_inlined() alwaysinline_recursively { + call void @ext_func() + ret void +} + +define void @test_defs_noinline_inlined2() alwaysinline_recursively { + call void @test_defs_noinline_notinlined() + ret void +} + +define void @test_defs_noinline_notinlined() noinline { + call void @ext_func() + ret void +} + +; Test that a recursive call is not inlined +; when annotating function definitions + +define void @test_defs_rec() { +; CHECK-LABEL: @test_defs_rec() { + call void @test_defs_rec_func() +; CHECK-NEXT: call void @test_defs_rec_func() + ret void +; CHECK-NEXT: ret void +} + +define void @test_defs_rec_func() alwaysinline_recursively { + call void @ext_func() + %1 = load i32, i32* @ext_var + %2 = icmp ne i32 %1, 0 + br i1 %2, label %3, label %4 + +3: + call void @test_defs_rec_func() + br label %4 + +4: + ret void +} + +; Test that an indirectly recursive call is inlined +; until a directly recursive call remains +; when annotating function definitions + +define void @test_defs_irec() { +; CHECK-LABEL: @test_defs_irec() { + call void @test_defs_irec_func() +; CHECK: call void @test_defs_irec() #1 + ret void +; CHECK: ret void +} + +define void @test_defs_irec_func() alwaysinline_recursively { + call void @ext_func() + %1 = load i32, i32* @ext_var + %2 = icmp ne i32 %1, 0 + br i1 %2, label %3, label %4 + +3: + call void @test_defs_irec() + br label %4 + +4: + ret void +} + +; CHECK: attributes #1 = { alwaysinline_recursively }