Index: llvm/docs/LangRef.rst =================================================================== --- llvm/docs/LangRef.rst +++ llvm/docs/LangRef.rst @@ -1519,6 +1519,14 @@ This attribute indicates that the inliner should never inline this function in any situation. This attribute may not be used together with the ``alwaysinline`` attribute. +``nomerge`` + This attribute indicates that calls to this function should never be merged + during optimization. For example, it will prevent tail merging otherwise + identical code sequences that raise an exception or terminate the program. + Tail merging normally reduces the precision of source location information, + making stack traces less useful for debugging. This attribute gives the + user control over the tradeoff between code size and debug information + precision. ``nonlazybind`` This attribute suppresses lazy symbol binding for the function. This may make calls to the function faster, at the cost of extra program Index: llvm/include/llvm/Bitcode/LLVMBitCodes.h =================================================================== --- llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -634,6 +634,7 @@ ATTR_KIND_NOSYNC = 63, ATTR_KIND_SANITIZE_MEMTAG = 64, ATTR_KIND_PREALLOCATED = 65, + ATTR_KIND_NO_MERGE = 66, }; enum ComdatSelectionKindCodes { Index: llvm/include/llvm/IR/Attributes.td =================================================================== --- llvm/include/llvm/IR/Attributes.td +++ llvm/include/llvm/IR/Attributes.td @@ -103,6 +103,9 @@ /// Function is called early and/or often, so lazy binding isn't worthwhile. def NonLazyBind : EnumAttr<"nonlazybind">; +/// Disable merging for call sites +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 call cannot be tail merged. + bool cannotMerge() const { return isInlineAsm() || hasFnAttr(Attribute::NoMerge); } + /// Determine if the invoke is convergent bool isConvergent() const { return hasFnAttr(Attribute::Convergent); } void setConvergent() { Index: llvm/lib/AsmParser/LLLexer.cpp =================================================================== --- llvm/lib/AsmParser/LLLexer.cpp +++ llvm/lib/AsmParser/LLLexer.cpp @@ -658,6 +658,7 @@ KEYWORD(noinline); KEYWORD(norecurse); KEYWORD(nonlazybind); + KEYWORD(nomerge); KEYWORD(nonnull); KEYWORD(noredzone); KEYWORD(noreturn); Index: llvm/lib/AsmParser/LLParser.cpp =================================================================== --- llvm/lib/AsmParser/LLParser.cpp +++ llvm/lib/AsmParser/LLParser.cpp @@ -1306,6 +1306,7 @@ B.addAttribute(Attribute::NoImplicitFloat); break; case lltok::kw_noinline: B.addAttribute(Attribute::NoInline); break; case lltok::kw_nonlazybind: B.addAttribute(Attribute::NonLazyBind); break; + case lltok::kw_nomerge: B.addAttribute(Attribute::NoMerge); break; case lltok::kw_noredzone: B.addAttribute(Attribute::NoRedZone); break; case lltok::kw_noreturn: B.addAttribute(Attribute::NoReturn); break; case lltok::kw_nosync: B.addAttribute(Attribute::NoSync); break; @@ -1698,6 +1699,7 @@ case lltok::kw_noimplicitfloat: case lltok::kw_noinline: case lltok::kw_nonlazybind: + case lltok::kw_nomerge: case lltok::kw_noredzone: case lltok::kw_noreturn: case lltok::kw_nocf_check: @@ -1797,6 +1799,7 @@ case lltok::kw_noimplicitfloat: case lltok::kw_noinline: case lltok::kw_nonlazybind: + case lltok::kw_nomerge: case lltok::kw_noredzone: case lltok::kw_noreturn: case lltok::kw_nocf_check: Index: llvm/lib/AsmParser/LLToken.h =================================================================== --- llvm/lib/AsmParser/LLToken.h +++ llvm/lib/AsmParser/LLToken.h @@ -204,6 +204,7 @@ kw_noinline, kw_norecurse, kw_nonlazybind, + kw_nomerge, kw_nonnull, kw_noredzone, kw_noreturn, Index: llvm/lib/Bitcode/Reader/BitcodeReader.cpp =================================================================== --- llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -1306,6 +1306,9 @@ case Attribute::Preallocated: llvm_unreachable("preallocated attribute not supported in raw format"); break; + case Attribute::NoMerge: + llvm_unreachable("nomerge attribute not supported in raw format"); + break; } llvm_unreachable("Unsupported attribute type"); } @@ -1318,7 +1321,7 @@ if (I == Attribute::SanitizeMemTag || I == Attribute::Dereferenceable || I == Attribute::DereferenceableOrNull || I == Attribute::ArgMemOnly || I == Attribute::AllocSize || I == Attribute::NoSync || - I == Attribute::Preallocated) + I == Attribute::Preallocated || I == Attribute::NoMerge) continue; if (uint64_t A = (Val & getRawAttributeMask(I))) { if (I == Attribute::Alignment) @@ -1465,6 +1468,8 @@ return Attribute::NoInline; case bitc::ATTR_KIND_NO_RECURSE: return Attribute::NoRecurse; + case bitc::ATTR_KIND_NO_MERGE: + return Attribute::NoMerge; case bitc::ATTR_KIND_NON_LAZY_BIND: return Attribute::NonLazyBind; case bitc::ATTR_KIND_NON_NULL: Index: llvm/lib/Bitcode/Writer/BitcodeWriter.cpp =================================================================== --- llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -647,6 +647,8 @@ return bitc::ATTR_KIND_NO_INLINE; case Attribute::NoRecurse: return bitc::ATTR_KIND_NO_RECURSE; + case Attribute::NoMerge: + return bitc::ATTR_KIND_NO_MERGE; case Attribute::NonLazyBind: return bitc::ATTR_KIND_NON_LAZY_BIND; case Attribute::NonNull: Index: llvm/lib/IR/Attributes.cpp =================================================================== --- llvm/lib/IR/Attributes.cpp +++ llvm/lib/IR/Attributes.cpp @@ -372,6 +372,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 @@ -1300,6 +1300,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 @@ -1485,8 +1493,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->cannotMerge()) return false; // Each instruction must have zero or one use. Index: llvm/test/Transforms/SimplifyCFG/nomerge.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/SimplifyCFG/nomerge.ll @@ -0,0 +1,71 @@ +; RUN: opt < %s -O1 -S | FileCheck %s + +; The attribute nomerge prevents the 3 bar() calls from being sunk/hoisted into +; one inside a function. Check that there are still 3 tail calls. + +; Test case for preventing sinking +; CHECK-LABEL: define void @sink +; CHECK: if.then: +; CHECK-NEXT: tail call void @bar() +; CHECK: if.then2: +; CHECK-NEXT: tail call void @bar() +; CHECK: if.end3: +; CHECK-NEXT: tail call void @bar() +define void @sink(i32 %i) { +entry: + switch i32 %i, label %if.end3 [ + i32 5, label %if.then + i32 7, label %if.then2 + ] + +if.then: + tail call void @bar() #0 + br label %if.end3 + +if.then2: + tail call void @bar() #0 + br label %if.end3 + +if.end3: + tail call void @bar() #0 + ret void +} + +; Test case for preventing hoisting +; CHECK-LABEL: define void @hoist +; CHECK: if.then: +; CHECK-NEXT: tail call void @bar() +; CHECK: if.then2: +; CHECK-NEXT: tail call void @bar() +; CHECK: if.end: +; CHECK-NEXT: tail call void @bar() +define void @hoist(i32 %i) { +entry: + %i.addr = alloca i32, align 4 + store i32 %i, i32* %i.addr, align 4 + %0 = load i32, i32* %i.addr, align 4 + %cmp = icmp eq i32 %0, 5 + br i1 %cmp, label %if.then, label %if.else + +if.then: + tail call void @bar() #1 + unreachable + +if.else: + %1 = load i32, i32* %i.addr, align 4 + %cmp1 = icmp eq i32 %i, 7 + br i1 %cmp1, label %if.then2, label %if.end + +if.then2: + tail call void @bar() #1 + unreachable + +if.end: + tail call void @bar() #1 + unreachable +} + +declare void @bar() + +attributes #0 = { nomerge } +attributes #1 = { noreturn nomerge }