[Attr] Add noipa function attribute (#203304)
This adds a `noipa` function attribute to LLVM IR. This new attribute disables any interprocedural analysis that inspects the definition of the function. Setting this attribute is equivalent to moving the function definition to a separate, optimizer-opaque, module. The `noipa` attribute does *not* control inlining or outlining. Add the `noinline` and `nooutline` attributes as well in cases where inlining and outlining should additionally be disabled. Revival of https://reviews.llvm.org/D101011 Discussed in https://discourse.llvm.org/t/noipa-continues/74411 LLVM portion of https://github.com/llvm/llvm-project/issues/40819
This commit is contained in:
1 parent
94ae3dd241
commit
c07bc2962c
32 files changed
+453
-7
No files matched your search
@@ -2390,6 +2390,14 @@ For 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.
|
||||
``noipa``
|
||||
Disables any interprocedural analysis that inspects the definition of this
|
||||
function. This attribute is equivalent to moving this function definition to
|
||||
a separate, optimizer-opaque, module. Any attributes on the function are
|
||||
still respected (as they would be if they remained on a function declaration
|
||||
in this module). This attribute does *not* control inlining or outlining.
|
||||
Add the ``noinline`` and ``nooutline`` attributes as well in cases where
|
||||
inlining and outlining should additionally be disabled.
|
||||
``nomerge``
|
||||
This attribute indicates that calls to this function should never be merged
|
||||
during optimization. For example, it will prevent tail merging otherwise
|
||||
|
||||
@@ -97,6 +97,11 @@ Makes programs 10x faster by doing Special New Thing.
|
||||
* The ``modular-format`` attribute now supports the ``fixed`` aspect for C
|
||||
ISO 18037 fixed-point ``printf`` specifiers.
|
||||
|
||||
* Added `noipa` attribute which disables interprocedural analyses that inspect
|
||||
the definition of the function. This attribute does *not* control inlining or
|
||||
outlining. Add the `noinline` and `nooutline` attributes as well in cases
|
||||
where inlining and outlining should additionally be disabled.
|
||||
|
||||
### Changes to LLVM infrastructure
|
||||
|
||||
* Removed ``Constant::isZeroValue``. It was functionally identical to
|
||||
|
||||
@@ -820,6 +820,7 @@ enum AttributeKindCodes {
|
||||
ATTR_KIND_DENORMAL_FPENV = 106,
|
||||
ATTR_KIND_NOOUTLINE = 107,
|
||||
ATTR_KIND_FLATTEN = 108,
|
||||
ATTR_KIND_NOIPA = 109,
|
||||
};
|
||||
|
||||
enum ComdatSelectionKindCodes {
|
||||
|
||||
@@ -218,6 +218,9 @@ def NoInline : EnumAttr<"noinline", IntersectPreserve, [FnAttr]>;
|
||||
/// nooutline
|
||||
def NoOutline : EnumAttr<"nooutline", IntersectPreserve, [FnAttr]>;
|
||||
|
||||
/// Do not do interprocedural analysis or optimization including this function
|
||||
def NoIPA : EnumAttr<"noipa", IntersectPreserve, [FnAttr]>;
|
||||
|
||||
/// Function is called early and/or often, so lazy binding isn't worthwhile.
|
||||
def NonLazyBind : EnumAttr<"nonlazybind", IntersectPreserve, [FnAttr]>;
|
||||
|
||||
|
||||
@@ -165,6 +165,10 @@ private:
|
||||
/// attribute.
|
||||
LLVM_ABI bool isNobuiltinFnDef() const;
|
||||
|
||||
/// Returns true if the global is a function definition with the noipa
|
||||
/// attribute.
|
||||
LLVM_ABI bool isNoipaFnDef() const;
|
||||
|
||||
protected:
|
||||
/// The intrinsic ID for this subclass (which must be a Function).
|
||||
///
|
||||
@@ -506,8 +510,12 @@ public:
|
||||
/// Return true if this global's definition can be substituted with an
|
||||
/// *arbitrary* definition at link time or load time. We cannot do any IPO or
|
||||
/// inlining across interposable call edges, since the callee can be
|
||||
/// replaced with something arbitrary.
|
||||
LLVM_ABI bool isInterposable() const;
|
||||
/// replaced with something arbitrary. For most IPO passes, the `noipa`
|
||||
/// attribute on a function definition is also treated as if it were
|
||||
/// interposable (and thus blocking interprocedural analysis). Passes
|
||||
/// which already have their own distinct control attributes (e.g. inlining)
|
||||
/// may set `CheckNoIPA = false` when calling this.
|
||||
LLVM_ABI bool isInterposable(bool CheckNoIPA = true) const;
|
||||
LLVM_ABI bool canBenefitFromLocalAlias() const;
|
||||
|
||||
bool hasExternalLinkage() const { return isExternalLinkage(getLinkage()); }
|
||||
|
||||
@@ -3245,7 +3245,7 @@ std::optional<InlineResult> llvm::getAttributeBasedInliningDecision(
|
||||
return InlineResult::failure("optnone attribute");
|
||||
|
||||
// Don't inline functions which can be interposed at link-time.
|
||||
if (Callee->isInterposable())
|
||||
if (Callee->isInterposable(/*CheckNoIPA=*/false))
|
||||
return InlineResult::failure("interposable");
|
||||
|
||||
// Don't inline functions marked noinline.
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
using namespace llvm;
|
||||
|
||||
bool llvm::canTrackArgumentsInterprocedurally(Function *F) {
|
||||
return F->hasLocalLinkage() && !F->hasAddressTaken();
|
||||
return F->hasExactDefinition() && F->hasLocalLinkage() &&
|
||||
!F->hasAddressTaken();
|
||||
}
|
||||
|
||||
bool llvm::canTrackReturnsInterprocedurally(Function *F) {
|
||||
|
||||
@@ -2308,6 +2308,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
|
||||
return Attribute::DenormalFPEnv;
|
||||
case bitc::ATTR_KIND_NOOUTLINE:
|
||||
return Attribute::NoOutline;
|
||||
case bitc::ATTR_KIND_NOIPA:
|
||||
return Attribute::NoIPA;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1008,6 +1008,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
|
||||
return bitc::ATTR_KIND_DENORMAL_FPENV;
|
||||
case Attribute::NoOutline:
|
||||
return bitc::ATTR_KIND_NOOUTLINE;
|
||||
case Attribute::NoIPA:
|
||||
return bitc::ATTR_KIND_NOIPA;
|
||||
case Attribute::EndAttrKinds:
|
||||
llvm_unreachable("Can not encode end-attribute kinds marker.");
|
||||
case Attribute::None:
|
||||
|
||||
+10
-1
@@ -113,7 +113,9 @@ GlobalObject::~GlobalObject() {
|
||||
setComdat(nullptr);
|
||||
}
|
||||
|
||||
bool GlobalValue::isInterposable() const {
|
||||
bool GlobalValue::isInterposable(bool CheckNoIPA) const {
|
||||
if (CheckNoIPA && isNoipaFnDef())
|
||||
return true;
|
||||
if (isInterposableLinkage(getLinkage()))
|
||||
return true;
|
||||
return !isDSOLocal() && getParent() &&
|
||||
@@ -334,6 +336,13 @@ bool GlobalValue::isNobuiltinFnDef() const {
|
||||
return F->hasFnAttribute(Attribute::NoBuiltin);
|
||||
}
|
||||
|
||||
bool GlobalValue::isNoipaFnDef() const {
|
||||
const Function *F = dyn_cast<Function>(this);
|
||||
if (!F || F->isDeclaration())
|
||||
return false;
|
||||
return F->hasFnAttribute(Attribute::NoIPA);
|
||||
}
|
||||
|
||||
bool GlobalValue::isDeclaration() const {
|
||||
// Globals are definitions if they have an initializer.
|
||||
if (const GlobalVariable *GV = dyn_cast<GlobalVariable>(this))
|
||||
|
||||
@@ -817,6 +817,10 @@ static Function *promoteArguments(Function *F, FunctionAnalysisManager &FAM,
|
||||
if (!F->hasLocalLinkage())
|
||||
return nullptr;
|
||||
|
||||
// Ensure function definition is available for interprocedural analysis.
|
||||
if (!F->isDefinitionExact())
|
||||
return nullptr;
|
||||
|
||||
// Don't promote arguments for variadic functions. Adding, removing, or
|
||||
// changing non-pack parameters can change the classification of pack
|
||||
// parameters. Frontends encode that classification at the call site in the
|
||||
|
||||
@@ -482,6 +482,12 @@ void DeadArgumentEliminationPass::surveyFunction(const Function &F) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure function definition is available for interprocedural analysis.
|
||||
if (!F.isDefinitionExact()) {
|
||||
markFrozen(F);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned RetCount = numRetVals(&F);
|
||||
|
||||
// Assume all return values are dead
|
||||
|
||||
@@ -1983,6 +1983,10 @@ OptimizeFunctions(Module &M,
|
||||
if (!F.hasLocalLinkage())
|
||||
continue;
|
||||
|
||||
// Ensure function definition is available for interprocedural analysis.
|
||||
if (!F.isDefinitionExact())
|
||||
continue;
|
||||
|
||||
// If we have an inalloca parameter that we can safely remove the
|
||||
// inalloca attribute from, do so. This unlocks optimizations that
|
||||
// wouldn't be safe in the presence of inalloca.
|
||||
|
||||
@@ -428,6 +428,7 @@ static bool hasDistinctMetadataIntrinsic(const Function &F) {
|
||||
/// Check whether \p F is eligible for function merging.
|
||||
static bool isEligibleForMerging(Function &F) {
|
||||
return !F.isDeclaration() && !F.hasAvailableExternallyLinkage() &&
|
||||
!F.hasFnAttribute(Attribute::NoIPA) &&
|
||||
!hasDistinctMetadataIntrinsic(F);
|
||||
}
|
||||
|
||||
@@ -473,7 +474,8 @@ template <typename FuncContainer> bool MergeFunctions::run(FuncContainer &M) {
|
||||
if (!I)
|
||||
continue;
|
||||
Function *F = cast<Function>(I);
|
||||
if (!F->isDeclaration() && !F->hasAvailableExternallyLinkage()) {
|
||||
if (!F->isDeclaration() && !F->hasAvailableExternallyLinkage() &&
|
||||
!F->hasFnAttribute(Attribute::NoIPA)) {
|
||||
Changed |= insert(F);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -982,6 +982,7 @@ Function *CodeExtractor::constructFunctionDeclaration(
|
||||
case Attribute::NoFree:
|
||||
case Attribute::NoImplicitFloat:
|
||||
case Attribute::NoInline:
|
||||
case Attribute::NoIPA:
|
||||
case Attribute::NoOutline:
|
||||
case Attribute::NonLazyBind:
|
||||
case Attribute::NoRedZone:
|
||||
|
||||
@@ -592,6 +592,11 @@ define void @dead_on_return_sized(ptr dead_on_return(4) %p) {
|
||||
ret void
|
||||
}
|
||||
|
||||
; CHECK: define void @noipa() [[NOIPA:#[0-9]+]]
|
||||
define void @noipa() noipa {
|
||||
ret void
|
||||
}
|
||||
|
||||
; CHECK: attributes #0 = { noreturn }
|
||||
; CHECK: attributes #1 = { nounwind }
|
||||
; CHECK: attributes #2 = { memory(none) }
|
||||
@@ -654,4 +659,5 @@ define void @dead_on_return_sized(ptr dead_on_return(4) %p) {
|
||||
; CHECK: attributes [[SKIPPROFILE]] = { skipprofile }
|
||||
; CHECK: attributes [[OPTDEBUG]] = { optdebug }
|
||||
; CHECK: attributes [[NODIVERGENCESOURCE]] = { nodivergencesource }
|
||||
; CHECK: attributes [[NOIPA]] = { noipa }
|
||||
; CHECK: attributes #[[NOBUILTIN]] = { nobuiltin }
|
||||
@@ -0,0 +1,27 @@
|
||||
; RUN: opt -passes=argpromotion -S < %s | FileCheck %s
|
||||
|
||||
; ArgumentPromotion promoting a by-ref argument of a `noipa` function.
|
||||
; If `noipa` is working, argpromotion should NOT promote the pointer argument,
|
||||
; because it requires inspecting the function body to determine load patterns.
|
||||
|
||||
; CHECK: define internal i32 @foo(i32 %p.0.val)
|
||||
define internal i32 @foo(ptr %p) noinline {
|
||||
%v = load i32, ptr %p
|
||||
ret i32 %v
|
||||
}
|
||||
|
||||
; CHECK: define internal i32 @foo_noipa(ptr %p)
|
||||
define internal i32 @foo_noipa(ptr %p) noipa noinline {
|
||||
%v = load i32, ptr %p
|
||||
ret i32 %v
|
||||
}
|
||||
|
||||
define i32 @bar() {
|
||||
%a = alloca i32
|
||||
store i32 7, ptr %a
|
||||
%r = call i32 @foo(ptr %a)
|
||||
%b = alloca i32
|
||||
store i32 %r, ptr %b
|
||||
%s = call i32 @foo_noipa(ptr %b)
|
||||
ret i32 %s
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
; RUN: opt -passes=deadargelim -S < %s | FileCheck %s
|
||||
|
||||
; DeadArgumentElimination removing unused args of a `noipa` function.
|
||||
; If `noipa` is working, DAE should NOT remove the unused argument %y from @foo,
|
||||
; because determining which arguments are dead requires inspecting the
|
||||
; definition.
|
||||
|
||||
; CHECK: define internal i32 @foo(i32 %x)
|
||||
define internal i32 @foo(i32 %x, i32 %y) noinline {
|
||||
ret i32 %x
|
||||
}
|
||||
|
||||
; CHECK: define internal i32 @foo_noipa(i32 %x, i32 %y)
|
||||
define internal i32 @foo_noipa(i32 %x, i32 %y) noipa noinline {
|
||||
ret i32 %x
|
||||
}
|
||||
|
||||
define i32 @bar() {
|
||||
%r = call i32 @foo(i32 1, i32 2)
|
||||
%s = call i32 @foo_noipa(i32 1, i32 2)
|
||||
%res = add i32 %r, %s
|
||||
ret i32 %res
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
; RUN: opt -passes='ipsccp<func-spec>' -force-specialization -S < %s | FileCheck %s
|
||||
|
||||
; Function specialisation should not activate for `noipa` functions.
|
||||
|
||||
define i32 @foo(i32 %x, i32 %y) {
|
||||
%r2 = add i32 %x, %y
|
||||
ret i32 %r2
|
||||
}
|
||||
|
||||
define i32 @foo_noipa(i32 %x, i32 %y) noipa {
|
||||
%r2 = add i32 %x, %y
|
||||
ret i32 %r2
|
||||
}
|
||||
|
||||
; CHECK: call i32 @foo.specialized.1(
|
||||
; CHECK: call i32 @foo_noipa(
|
||||
; CHECK: define internal i32 @foo.specialized.1(
|
||||
; CHECK-NOT: define internal i32 @foo_noipa.specialized.1(
|
||||
define i32 @test() {
|
||||
%r = call i32 @foo(i32 0, i32 5)
|
||||
%s = call i32 @foo_noipa(i32 0, i32 5)
|
||||
%res = add i32 %r, %s
|
||||
ret i32 %res
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
; RUN: opt -passes=globalopt -S -o - < %s | FileCheck %s
|
||||
|
||||
; Calls to `noipa` functions in constructors prevent evaluation.
|
||||
|
||||
; CHECK: @object = local_unnamed_addr global i32 4
|
||||
@object = local_unnamed_addr global i32 0
|
||||
|
||||
define void @ctor() {
|
||||
store i32 4, ptr @object
|
||||
%a = bitcast ptr @object to ptr
|
||||
call void @test(ptr %a)
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @test(ptr %ptr) {
|
||||
ret void
|
||||
}
|
||||
|
||||
; CHECK: @object_noipa = local_unnamed_addr global i32 0
|
||||
@object_noipa = local_unnamed_addr global i32 0
|
||||
|
||||
define void @ctor_noipa() {
|
||||
store i32 4, ptr @object_noipa
|
||||
%a = bitcast ptr @object_noipa to ptr
|
||||
call void @test_noipa(ptr %a)
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @test_noipa(ptr %ptr) noipa {
|
||||
ret void
|
||||
}
|
||||
|
||||
@llvm.global_ctors = appending constant
|
||||
[2 x { i32, ptr, ptr }]
|
||||
[ { i32, ptr, ptr } { i32 65535, ptr @ctor, ptr null },
|
||||
{ i32, ptr, ptr } { i32 65535, ptr @ctor_noipa, ptr null } ]
|
||||
@@ -0,0 +1,22 @@
|
||||
; RUN: opt -passes=globalopt -S < %s | FileCheck %s
|
||||
|
||||
; GlobalOpt changing calling convention of a `noipa` function.
|
||||
; If `noipa` is working, GlobalOpt should NOT change the CC of @foo,
|
||||
; because it requires analyzing callers/callees interprocedurally.
|
||||
|
||||
; CHECK: define internal fastcc i32 @foo(
|
||||
define internal i32 @foo(i32 %x) noinline {
|
||||
ret i32 %x
|
||||
}
|
||||
|
||||
; CHECK: define internal i32 @foo_noipa(
|
||||
define internal i32 @foo_noipa(i32 %x) noipa noinline {
|
||||
ret i32 %x
|
||||
}
|
||||
|
||||
define i32 @bar() {
|
||||
%r = call i32 @foo(i32 5)
|
||||
%s = call i32 @foo_noipa(i32 5)
|
||||
%res = add i32 %r, %s
|
||||
ret i32 %res
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
; REQUIRES: aarch64-registered-target
|
||||
|
||||
; RUN: opt --passes=globalopt -o - -S < %s | FileCheck %s
|
||||
|
||||
; `noipa` resolvers are not eligible for inspection
|
||||
|
||||
target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"
|
||||
target triple = "aarch64-unknown-linux-gnu"
|
||||
|
||||
$callee.resolver = comdat any
|
||||
$caller.resolver = comdat any
|
||||
|
||||
@__aarch64_cpu_features = external local_unnamed_addr global { i64 }
|
||||
|
||||
@callee = weak_odr ifunc i32 (), ptr @callee.resolver
|
||||
@caller = weak_odr ifunc i32 (), ptr @caller.resolver
|
||||
|
||||
declare void @__init_cpu_features_resolver() local_unnamed_addr
|
||||
|
||||
declare i32 @callee.default() #0
|
||||
declare i32 @callee._Msve() #1
|
||||
declare i32 @callee._Msve2() #2
|
||||
|
||||
define weak_odr ptr @callee.resolver() noipa comdat {
|
||||
resolver_entry:
|
||||
tail call void @__init_cpu_features_resolver()
|
||||
%0 = load i64, ptr @__aarch64_cpu_features, align 8
|
||||
%1 = and i64 %0, 69793284352
|
||||
%2 = icmp eq i64 %1, 69793284352
|
||||
%3 = and i64 %0, 1073807616
|
||||
%4 = icmp eq i64 %3, 1073807616
|
||||
%callee._Msve.callee.default = select i1 %4, ptr @callee._Msve, ptr @callee.default
|
||||
%common.ret.op = select i1 %2, ptr @callee._Msve2, ptr %callee._Msve.callee.default
|
||||
ret ptr %common.ret.op
|
||||
}
|
||||
|
||||
; CHECK: define i32 @caller._Msve()
|
||||
; CHECK: tail call i32 @callee()
|
||||
define i32 @caller._Msve() #1 {
|
||||
entry:
|
||||
%call = tail call i32 @callee()
|
||||
ret i32 %call
|
||||
}
|
||||
|
||||
; CHECK: define i32 @caller._Msve2()
|
||||
; CHECK: tail call i32 @callee()
|
||||
define i32 @caller._Msve2() #2 {
|
||||
entry:
|
||||
%call = tail call i32 @callee()
|
||||
ret i32 %call
|
||||
}
|
||||
|
||||
; CHECK: define i32 @caller.default()
|
||||
; CHECK: tail call i32 @callee()
|
||||
define i32 @caller.default() #0 {
|
||||
entry:
|
||||
%call = tail call i32 @callee()
|
||||
ret i32 %call
|
||||
}
|
||||
|
||||
define weak_odr ptr @caller.resolver() comdat {
|
||||
resolver_entry:
|
||||
tail call void @__init_cpu_features_resolver()
|
||||
%0 = load i64, ptr @__aarch64_cpu_features, align 8
|
||||
%1 = and i64 %0, 69793284352
|
||||
%2 = icmp eq i64 %1, 69793284352
|
||||
%3 = and i64 %0, 1073807616
|
||||
%4 = icmp eq i64 %3, 1073807616
|
||||
%caller._Msve.caller.default = select i1 %4, ptr @caller._Msve, ptr @caller.default
|
||||
%common.ret.op = select i1 %2, ptr @caller._Msve2, ptr %caller._Msve.caller.default
|
||||
ret ptr %common.ret.op
|
||||
}
|
||||
|
||||
attributes #0 = { "fmv-features" }
|
||||
attributes #1 = { "fmv-features"="sve" }
|
||||
attributes #2 = { "fmv-features"="sve2" }
|
||||
@@ -0,0 +1,29 @@
|
||||
; RUN: opt --passes=globalopt -o - -S < %s | FileCheck %s
|
||||
|
||||
; `noipa` resolvers are not eligible for inspection
|
||||
|
||||
@static.ifunc = internal ifunc void (), ptr @static.resolver
|
||||
|
||||
define ptr @static.resolver() {
|
||||
ret ptr @static._Msimd
|
||||
}
|
||||
define void @static._Msimd() {
|
||||
ret void
|
||||
}
|
||||
define void @static.default() {
|
||||
ret void
|
||||
}
|
||||
|
||||
@static_noipa.ifunc = internal ifunc void (), ptr @static_noipa.resolver
|
||||
|
||||
define ptr @static_noipa.resolver() noipa {
|
||||
ret ptr @static._Msimd
|
||||
}
|
||||
|
||||
define void @caller() {
|
||||
; CHECK: call void @static._Msimd()
|
||||
call void @static.ifunc()
|
||||
; CHECK: call void @static_noipa.ifunc()
|
||||
call void @static_noipa.ifunc()
|
||||
ret void
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
; RUN: opt -passes=inline -S < %s | FileCheck %s
|
||||
|
||||
; Ensure `noipa` does _not_ control inlining. The `noipa` attribute controls
|
||||
; _other_ interprocedural optimisations by affecting the result of
|
||||
; `isInterposable` and `isDefinitionExact` checks, but inlining is
|
||||
; controlled separately by the `noinline` attribute.
|
||||
|
||||
define internal i32 @noipa(i32 %x) noipa {
|
||||
ret i32 %x
|
||||
}
|
||||
|
||||
define internal i32 @noipa_noinline(i32 %x) noipa noinline {
|
||||
ret i32 %x
|
||||
}
|
||||
|
||||
define i32 @bob() {
|
||||
%a = call i32 @noipa(i32 1)
|
||||
%b = call i32 @noipa_noinline(i32 %a)
|
||||
ret i32 %b
|
||||
}
|
||||
|
||||
; CHECK-NOT: call i32 @noipa(
|
||||
; CHECK: call i32 @noipa_noinline(
|
||||
@@ -0,0 +1,53 @@
|
||||
; RUN: opt -passes=mergefunc -S < %s | FileCheck %s
|
||||
|
||||
; MergeFunctions merging a `noipa` function with an identical one.
|
||||
; If `noipa` is working, MergeFunctions should NOT merge
|
||||
; @foo_noipa into @baz_noipa (or vice versa), because it inspects
|
||||
; the function definitions.
|
||||
|
||||
; CHECK-NOT: define internal i32 @foo(
|
||||
define internal i32 @foo(i32 %x, i32 %y) noinline {
|
||||
%sum = add i32 %x, %y
|
||||
%mul = mul i32 %sum, %x
|
||||
%sub = sub i32 %mul, %y
|
||||
ret i32 %sub
|
||||
}
|
||||
|
||||
; CHECK: define internal i32 @baz(
|
||||
define internal i32 @baz(i32 %x, i32 %y) noinline {
|
||||
%sum = add i32 %x, %y
|
||||
%mul = mul i32 %sum, %x
|
||||
%sub = sub i32 %mul, %y
|
||||
ret i32 %sub
|
||||
}
|
||||
|
||||
; CHECK: define internal i32 @foo_noipa(
|
||||
define internal i32 @foo_noipa(i32 %x, i32 %y) noipa noinline {
|
||||
%sum = add i32 %x, %y
|
||||
%mul = mul i32 %sum, %x
|
||||
%sub = sub i32 %mul, %y
|
||||
ret i32 %sub
|
||||
}
|
||||
|
||||
; CHECK: define internal i32 @baz_noipa(
|
||||
define internal i32 @baz_noipa(i32 %x, i32 %y) noipa noinline {
|
||||
%sum = add i32 %x, %y
|
||||
%mul = mul i32 %sum, %x
|
||||
%sub = sub i32 %mul, %y
|
||||
ret i32 %sub
|
||||
}
|
||||
|
||||
define i32 @bar() {
|
||||
; CHECK: call i32 @baz(
|
||||
%r1 = call i32 @foo(i32 1, i32 2)
|
||||
; CHECK: call i32 @baz(
|
||||
%r2 = call i32 @baz(i32 3, i32 4)
|
||||
; CHECK: call i32 @foo_noipa(
|
||||
%r3 = call i32 @foo_noipa(i32 1, i32 2)
|
||||
; CHECK: call i32 @baz_noipa(
|
||||
%r4 = call i32 @baz_noipa(i32 3, i32 4)
|
||||
%res1 = add i32 %r1, %r2
|
||||
%res2 = add i32 %r3, %r4
|
||||
%res3 = add i32 %res1, %res2
|
||||
ret i32 %res3
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
; RUN: opt -passes=ipsccp -S < %s | FileCheck %s
|
||||
|
||||
; IPSCCP constant-propagating through arguments of a `noipa` function.
|
||||
; If `noipa` is working, IPSCCP should NOT propagate the constant 42 into @foo,
|
||||
; because analyzing calls to @foo to determine argument values is IPA.
|
||||
|
||||
; CHECK: define internal i32 @foo(i32 %x)
|
||||
; CHECK-NEXT: ret i32 poison
|
||||
define internal i32 @foo(i32 %x) noinline {
|
||||
ret i32 %x
|
||||
}
|
||||
|
||||
; CHECK: define internal i32 @foo_noipa(i32 %x)
|
||||
; CHECK-NEXT: ret i32 %x
|
||||
define internal i32 @foo_noipa(i32 %x) noipa noinline {
|
||||
ret i32 %x
|
||||
}
|
||||
|
||||
define i32 @bar() {
|
||||
%r = call i32 @foo(i32 42)
|
||||
%s = call i32 @foo_noipa(i32 42)
|
||||
; CHECK: %res = add i32 42, %s
|
||||
%res = add i32 %r, %s
|
||||
ret i32 %res
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
; RUN: opt -S -passes=wholeprogramdevirt -whole-program-visibility %s | FileCheck %s
|
||||
|
||||
; The `noipa` attribute prevents interprocedural function body inspection,
|
||||
; including this virtual constant propagation transformation.
|
||||
|
||||
@vt1 = constant [1 x ptr] [ptr @vf1], !type !0
|
||||
@vt2 = constant [1 x ptr] [ptr @vf2], !type !0
|
||||
|
||||
define i32 @vf1(ptr %this, i32 %arg) readnone noipa {
|
||||
ret i32 %arg
|
||||
}
|
||||
|
||||
define i32 @vf2(ptr %this, i32 %arg) readnone {
|
||||
ret i32 %arg
|
||||
}
|
||||
|
||||
; CHECK: define i32 @fn
|
||||
define i32 @fn(ptr %obj) {
|
||||
%vtable = load ptr, ptr %obj
|
||||
%p = call i1 @llvm.type.test(ptr %vtable, metadata !"typeid")
|
||||
call void @llvm.assume(i1 %p)
|
||||
%fptr = load ptr, ptr %vtable
|
||||
; CHECK: call i32 %
|
||||
%result = call i32 %fptr(ptr %obj, i32 1)
|
||||
ret i32 %result
|
||||
}
|
||||
declare i1 @llvm.type.test(ptr, metadata)
|
||||
declare void @llvm.assume(i1)
|
||||
|
||||
!0 = !{i32 0, !"typeid"}
|
||||
@@ -644,4 +644,17 @@ TEST(FunctionTest, LLVMGetOrInsertFunction) {
|
||||
EXPECT_EQ(FuncRef, FuncRef2);
|
||||
}
|
||||
|
||||
TEST(FunctionTest, NoIPAInterposable) {
|
||||
LLVMContext Ctx;
|
||||
std::unique_ptr<Module> M = parseIR(Ctx, R"(
|
||||
define void @foo() { bb1: ret void }
|
||||
define void @bar() #0 { bb1: ret void }
|
||||
attributes #0 = { noipa }
|
||||
)");
|
||||
EXPECT_FALSE(M->getFunction("foo")->isInterposable());
|
||||
EXPECT_TRUE(M->getFunction("foo")->isDefinitionExact());
|
||||
EXPECT_TRUE(M->getFunction("bar")->isInterposable());
|
||||
EXPECT_FALSE(M->getFunction("bar")->isDefinitionExact());
|
||||
}
|
||||
|
||||
} // end namespace
|
||||
@@ -35,7 +35,7 @@
|
||||
`(,(regexp-opt
|
||||
'("alwaysinline" "argmemonly" "allocsize" "builtin" "cold" "convergent" "dereferenceable" "dereferenceable_or_null" "hot" "immarg" "inaccessiblememonly"
|
||||
"inaccessiblemem_or_argmemonly" "inalloca" "inlinehint" "jumptable" "minsize" "mustprogress" "naked" "nobuiltin" "nonnull" "nocapture"
|
||||
"nocallback" "nocf_check" "noduplicate" "noext" "nofree" "noimplicitfloat" "noinline" "nomerge" "nonlazybind" "noprofile" "noredzone" "noreturn"
|
||||
"nocallback" "nocf_check" "noduplicate" "noext" "nofree" "noimplicitfloat" "noinline" "noipa" "nomerge" "nonlazybind" "noprofile" "noredzone" "noreturn"
|
||||
"norecurse" "nosync" "noundef" "nounwind" "nosanitize_bounds" "nosanitize_coverage" "null_pointer_is_valid" "optdebug" "optforfuzzing" "optnone" "optsize" "preallocated" "readnone" "readonly" "returned" "returns_twice"
|
||||
"shadowcallstack" "signext" "speculatable" "speculative_load_hardening" "ssp" "sspreq" "sspstrong" "safestack" "sanitize_address" "sanitize_alloc_token" "sanitize_hwaddress" "sanitize_memtag"
|
||||
"sanitize_thread" "sanitize_memory" "strictfp" "swifterror" "uwtable" "vscale_range" "willreturn" "writeonly" "zeroext") 'symbols) . font-lock-constant-face)
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
<item> nofree </item>
|
||||
<item> noimplicitfloat </item>
|
||||
<item> noinline </item>
|
||||
<item> noipa </item>
|
||||
<item> nomerge </item>
|
||||
<item> nooutline </item>
|
||||
<item> noprofile </item>
|
||||
|
||||
@@ -132,6 +132,7 @@ syn keyword llvmKeyword
|
||||
\ nofree
|
||||
\ noimplicitfloat
|
||||
\ noinline
|
||||
\ noipa
|
||||
\ nomerge
|
||||
\ nooutline
|
||||
\ nonlazybind
|
||||
|
||||
@@ -229,6 +229,7 @@ patterns:
|
||||
\\bnofree\\b|\
|
||||
\\bnoimplicitfloat\\b|\
|
||||
\\bnoinline\\b|\
|
||||
\\bnoipa\\b|\
|
||||
\\bnomerge\\b|\
|
||||
\\bnooutline\\b|\
|
||||
\\bnonlazybind\\b|\
|
||||
|
||||
Reference in new issue
Block a user