Index: clang/include/clang/Basic/Attr.td =================================================================== --- clang/include/clang/Basic/Attr.td +++ clang/include/clang/Basic/Attr.td @@ -1793,6 +1793,13 @@ let Documentation = [Undocumented]; } +def NoMerge : InheritableAttr { + let Spellings = [Clang<"nomerge">]; + let Subjects = SubjectList<[Function]>; + let Documentation = [NoMergeDocs]; + let SimpleHandler = 1; +} + def NoInstrumentFunction : InheritableAttr { let Spellings = [GCC<"no_instrument_function">]; let Subjects = SubjectList<[Function]>; Index: clang/include/clang/Basic/AttrDocs.td =================================================================== --- clang/include/clang/Basic/AttrDocs.td +++ clang/include/clang/Basic/AttrDocs.td @@ -350,6 +350,14 @@ }]; } +def NoMergeDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +A function declared as ``nomerge`` shall not be tail merged at call sites during +optimization phase. + }]; +} + def AssertCapabilityDocs : Documentation { let Category = DocCatFunction; let Heading = "assert_capability, assert_shared_capability"; Index: clang/lib/CodeGen/CGCall.cpp =================================================================== --- clang/lib/CodeGen/CGCall.cpp +++ clang/lib/CodeGen/CGCall.cpp @@ -1905,6 +1905,8 @@ FuncAttrs.addAttribute(llvm::Attribute::NoDuplicate); if (TargetDecl->hasAttr()) FuncAttrs.addAttribute(llvm::Attribute::Convergent); + if (TargetDecl->hasAttr()) + FuncAttrs.addAttribute(llvm::Attribute::NoMerge); if (const FunctionDecl *Fn = dyn_cast(TargetDecl)) { AddAttributesFromFunctionProtoType( Index: clang/test/CodeGen/attr-nomerge.c =================================================================== --- /dev/null +++ clang/test/CodeGen/attr-nomerge.c @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -O2 -S -emit-llvm %s -o - | FileCheck %s + +void bar() __attribute__((nomerge)); + +void foo(int i) { + if (i == 5) { + bar(); + } + else if (i == 7) { + bar(); + } + bar(); +} +// CHECK: tail call void +// CHECK: tail call void +// CHECK: tail call void Index: clang/test/Misc/pragma-attribute-supported-attributes-list.test =================================================================== --- clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -88,6 +88,7 @@ // CHECK-NEXT: NoEscape (SubjectMatchRule_variable_is_parameter) // CHECK-NEXT: NoInline (SubjectMatchRule_function) // CHECK-NEXT: NoInstrumentFunction (SubjectMatchRule_function) +// CHECK-NEXT: NoMerge (SubjectMatchRule_function) // CHECK-NEXT: NoMicroMips (SubjectMatchRule_function) // CHECK-NEXT: NoMips16 (SubjectMatchRule_function) // CHECK-NEXT: NoSanitize (SubjectMatchRule_function, SubjectMatchRule_objc_method, SubjectMatchRule_variable_is_global) Index: llvm/include/llvm/IR/Attributes.td =================================================================== --- llvm/include/llvm/IR/Attributes.td +++ llvm/include/llvm/IR/Attributes.td @@ -97,6 +97,9 @@ /// Function is called early and/or often, so lazy binding isn't worthwhile. def NonLazyBind : EnumAttr<"nonlazybind">; +/// Disable tail merge for this function +def NoMerge : EnumAttr<"nomerge">; + /// Pointer is known to be not null. def NonNull : EnumAttr<"nonnull">; Index: llvm/include/llvm/IR/InstrTypes.h =================================================================== --- llvm/include/llvm/IR/InstrTypes.h +++ llvm/include/llvm/IR/InstrTypes.h @@ -1717,6 +1717,9 @@ addAttribute(AttributeList::FunctionIndex, Attribute::NoDuplicate); } + /// Determine if the invoke cannot be tail merged. + bool cannotMerge() const { return hasFnAttr(Attribute::NoMerge); } + /// Determine if the invoke is convergent bool isConvergent() const { return hasFnAttr(Attribute::Convergent); } void setConvergent() { Index: llvm/lib/IR/Attributes.cpp =================================================================== --- llvm/lib/IR/Attributes.cpp +++ llvm/lib/IR/Attributes.cpp @@ -368,6 +368,8 @@ return "noinline"; if (hasAttribute(Attribute::NonLazyBind)) return "nonlazybind"; + if (hasAttribute(Attribute::NoMerge)) + return "nomerge"; if (hasAttribute(Attribute::NonNull)) return "nonnull"; if (hasAttribute(Attribute::NoRedZone)) Index: llvm/lib/IR/Verifier.cpp =================================================================== --- llvm/lib/IR/Verifier.cpp +++ llvm/lib/IR/Verifier.cpp @@ -1509,6 +1509,7 @@ /// Return true if this attribute kind only applies to functions. static bool isFuncOnlyAttr(Attribute::AttrKind Kind) { switch (Kind) { + case Attribute::NoMerge: case Attribute::NoReturn: case Attribute::NoSync: case Attribute::WillReturn: Index: llvm/lib/Transforms/Utils/SimplifyCFG.cpp =================================================================== --- llvm/lib/Transforms/Utils/SimplifyCFG.cpp +++ llvm/lib/Transforms/Utils/SimplifyCFG.cpp @@ -1287,6 +1287,14 @@ if (!TTI.isProfitableToHoist(I1) || !TTI.isProfitableToHoist(I2)) return Changed; + // If any of the two call sites has nomerge attribute, stop hoisting. + if (const auto *CB1 = dyn_cast(I1)) + if (CB1->cannotMerge()) + return Changed; + if (const auto *CB2 = dyn_cast(I2)) + if (CB2->cannotMerge()) + return Changed; + if (isa(I1) || isa(I2)) { assert (isa(I1) && isa(I2)); // The debug location is an integral part of a debug info intrinsic @@ -1472,8 +1480,9 @@ // Conservatively return false if I is an inline-asm instruction. Sinking // and merging inline-asm instructions can potentially create arguments // that cannot satisfy the inline-asm constraints. + // If the instruction has nomerge attribute, return false. if (const auto *C = dyn_cast(I)) - if (C->isInlineAsm()) + if (C->isInlineAsm() || C->cannotMerge()) return false; // Each instruction must have zero or one use.