mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2025-01-31 12:41:49 +01:00
Add nomerge function attribute to supress tail merge optimization in simplifyCFG
We want to add a way to avoid merging identical calls so as to keep the separate debug-information for those calls. There is also an asan usecase where having this attribute would be beneficial to avoid alternative work-arounds. Here is the link to the feature request: https://bugs.llvm.org/show_bug.cgi?id=42783. `nomerge` is different from `noline`. `noinline` prevents function from inlining at callsites, but `nomerge` prevents multiple identical calls from being merged into one. This patch adds `nomerge` to disable the optimization in IR level. A followup patch will be needed to let backend understands `nomerge` and avoid tail merge at backend. Reviewed By: asbirlea, rnk Differential Revision: https://reviews.llvm.org/D78659
This commit is contained in:
parent
0437d2eefe
commit
570033ed62
@ -1528,6 +1528,14 @@ example:
|
||||
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
|
||||
|
@ -634,6 +634,7 @@ enum AttributeKindCodes {
|
||||
ATTR_KIND_NOSYNC = 63,
|
||||
ATTR_KIND_SANITIZE_MEMTAG = 64,
|
||||
ATTR_KIND_PREALLOCATED = 65,
|
||||
ATTR_KIND_NO_MERGE = 66,
|
||||
};
|
||||
|
||||
enum ComdatSelectionKindCodes {
|
||||
|
@ -103,6 +103,9 @@ def NoInline : EnumAttr<"noinline">;
|
||||
/// 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">;
|
||||
|
||||
|
@ -1715,6 +1715,9 @@ public:
|
||||
addAttribute(AttributeList::FunctionIndex, Attribute::NoDuplicate);
|
||||
}
|
||||
|
||||
/// Determine if the call 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() {
|
||||
|
@ -658,6 +658,7 @@ lltok::Kind LLLexer::LexIdentifier() {
|
||||
KEYWORD(noinline);
|
||||
KEYWORD(norecurse);
|
||||
KEYWORD(nonlazybind);
|
||||
KEYWORD(nomerge);
|
||||
KEYWORD(nonnull);
|
||||
KEYWORD(noredzone);
|
||||
KEYWORD(noreturn);
|
||||
|
@ -1306,6 +1306,7 @@ bool LLParser::ParseFnAttributeValuePairs(AttrBuilder &B,
|
||||
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 @@ bool LLParser::ParseOptionalParamAttrs(AttrBuilder &B) {
|
||||
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 @@ bool LLParser::ParseOptionalReturnAttrs(AttrBuilder &B) {
|
||||
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:
|
||||
|
@ -204,6 +204,7 @@ enum Kind {
|
||||
kw_noinline,
|
||||
kw_norecurse,
|
||||
kw_nonlazybind,
|
||||
kw_nomerge,
|
||||
kw_nonnull,
|
||||
kw_noredzone,
|
||||
kw_noreturn,
|
||||
|
@ -1442,6 +1442,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
|
||||
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:
|
||||
|
@ -647,6 +647,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
|
||||
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:
|
||||
|
@ -375,6 +375,8 @@ std::string Attribute::getAsString(bool InAttrGrp) const {
|
||||
return "noinline";
|
||||
if (hasAttribute(Attribute::NonLazyBind))
|
||||
return "nonlazybind";
|
||||
if (hasAttribute(Attribute::NoMerge))
|
||||
return "nomerge";
|
||||
if (hasAttribute(Attribute::NonNull))
|
||||
return "nonnull";
|
||||
if (hasAttribute(Attribute::NoRedZone))
|
||||
|
@ -1514,6 +1514,7 @@ void Verifier::visitModuleFlagCGProfileEntry(const MDOperand &MDO) {
|
||||
/// 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:
|
||||
|
@ -874,6 +874,7 @@ Function *CodeExtractor::constructFunction(const ValueSet &inputs,
|
||||
case Attribute::NoAlias:
|
||||
case Attribute::NoBuiltin:
|
||||
case Attribute::NoCapture:
|
||||
case Attribute::NoMerge:
|
||||
case Attribute::NoReturn:
|
||||
case Attribute::NoSync:
|
||||
case Attribute::None:
|
||||
|
@ -1300,6 +1300,14 @@ bool SimplifyCFGOpt::HoistThenElseCodeToIf(BranchInst *BI,
|
||||
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<CallBase>(I1))
|
||||
if (CB1->cannotMerge())
|
||||
return Changed;
|
||||
if (const auto *CB2 = dyn_cast<CallBase>(I2))
|
||||
if (CB2->cannotMerge())
|
||||
return Changed;
|
||||
|
||||
if (isa<DbgInfoIntrinsic>(I1) || isa<DbgInfoIntrinsic>(I2)) {
|
||||
assert (isa<DbgInfoIntrinsic>(I1) && isa<DbgInfoIntrinsic>(I2));
|
||||
// The debug location is an integral part of a debug info intrinsic
|
||||
@ -1485,8 +1493,9 @@ static bool canSinkInstructions(
|
||||
// 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<CallBase>(I))
|
||||
if (C->isInlineAsm())
|
||||
if (C->isInlineAsm() || C->cannotMerge())
|
||||
return false;
|
||||
|
||||
// Each instruction must have zero or one use.
|
||||
|
71
test/Transforms/SimplifyCFG/nomerge.ll
Normal file
71
test/Transforms/SimplifyCFG/nomerge.ll
Normal file
@ -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 }
|
Loading…
x
Reference in New Issue
Block a user