diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp --- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -69,6 +69,7 @@ #define DEBUG_TYPE "function-attrs" +STATISTIC(NumArgMemOnly, "Number of functions marked argmemonly"); STATISTIC(NumReadNone, "Number of functions marked readnone"); STATISTIC(NumReadOnly, "Number of functions marked readonly"); STATISTIC(NumWriteOnly, "Number of functions marked writeonly"); @@ -1691,6 +1692,44 @@ } } +static bool functionArgMemOnly(const Function &F) { + // We can infer and propagate function attributes only when we know that the + // definition we'll get at link time is *exactly* the definition we see now. + // For more details, see GlobalValue::mayBeDerefined. + if (!F.hasExactDefinition()) + return false; + + // Can only analyze functions with a definition. + if (F.isDeclaration()) + return false; + + // If all memory accesses are to arguments, the function is argmemonly. + // TODO: The current implementation doesn't yet consider calls to argmemonly + // functions. + return all_of(instructions(F), [](const Instruction &I) { + if (!I.mayReadOrWriteMemory()) + return true; + auto Loc = MemoryLocation::getOrNone(&I); + if (!Loc) + return false; + auto *UO = getUnderlyingObject(Loc->Ptr); + return UO && isa(UO); + }); +} + +static void addArgMemOnly(const SCCNodeSet &SCCNodes, + SmallSet &Changed) { + for (Function *F : SCCNodes) { + if (!F || F->doesNotAccessMemory() || F->onlyAccessesArgMemory() || + !functionArgMemOnly(*F)) + continue; + + F->setOnlyAccessesArgMemory(); + NumArgMemOnly++; + Changed.insert(F); + } +} + // Return true if this is an atomic which has an ordering stronger than // unordered. Note that this is different than the predicate we use in // Attributor. Here we chose to be conservative and consider monotonic @@ -1815,6 +1854,7 @@ inferConvergent(Nodes.SCCNodes, Changed); addNoReturnAttrs(Nodes.SCCNodes, Changed); addWillReturn(Nodes.SCCNodes, Changed); + addArgMemOnly(Nodes.SCCNodes, Changed); // If we have no external nodes participating in the SCC, we can deduce some // more precise attributes as well. diff --git a/llvm/test/Transforms/FunctionAttrs/argmemonly.ll b/llvm/test/Transforms/FunctionAttrs/argmemonly.ll --- a/llvm/test/Transforms/FunctionAttrs/argmemonly.ll +++ b/llvm/test/Transforms/FunctionAttrs/argmemonly.ll @@ -14,7 +14,7 @@ } define i32 @test_only_read_arg(i32* %ptr) { -; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind readonly willreturn +; CHECK: Function Attrs: argmemonly mustprogress nofree norecurse nosync nounwind readonly willreturn ; CHECK-LABEL: @test_only_read_arg( ; CHECK-NEXT: entry: ; CHECK-NEXT: [[L:%.*]] = load i32, i32* [[PTR:%.*]], align 4 @@ -52,7 +52,7 @@ } define void @test_only_write_arg(i32* %ptr) { -; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn writeonly +; CHECK: Function Attrs: argmemonly mustprogress nofree norecurse nosync nounwind willreturn writeonly ; CHECK-LABEL: @test_only_write_arg( ; CHECK-NEXT: entry: ; CHECK-NEXT: store i32 0, i32* [[PTR:%.*]], align 4 @@ -91,7 +91,7 @@ declare i32 @fn_readnone() readnone define void @test_call_readnone(i32* %ptr) { -; CHECK: Function Attrs: writeonly +; CHECK: Function Attrs: argmemonly writeonly ; CHECK-LABEL: @test_call_readnone( ; CHECK-NEXT: entry: ; CHECK-NEXT: [[C:%.*]] = call i32 @fn_readnone() diff --git a/llvm/test/Transforms/FunctionAttrs/atomic.ll b/llvm/test/Transforms/FunctionAttrs/atomic.ll --- a/llvm/test/Transforms/FunctionAttrs/atomic.ll +++ b/llvm/test/Transforms/FunctionAttrs/atomic.ll @@ -21,4 +21,4 @@ } ; CHECK: attributes #0 = { mustprogress nofree norecurse nosync nounwind readnone ssp willreturn uwtable } -; CHECK: attributes #1 = { mustprogress nofree norecurse nounwind ssp willreturn uwtable } +; CHECK: attributes #1 = { argmemonly mustprogress nofree norecurse nounwind ssp willreturn uwtable } diff --git a/llvm/test/Transforms/FunctionAttrs/nofree.ll b/llvm/test/Transforms/FunctionAttrs/nofree.ll --- a/llvm/test/Transforms/FunctionAttrs/nofree.ll +++ b/llvm/test/Transforms/FunctionAttrs/nofree.ll @@ -36,7 +36,7 @@ declare void @free(i8* nocapture) local_unnamed_addr #2 define i32 @_Z4foo3Pi(i32* nocapture readonly %a) local_unnamed_addr #3 { -; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind readonly willreturn uwtable +; CHECK: Function Attrs: argmemonly mustprogress nofree norecurse nosync nounwind readonly willreturn uwtable ; CHECK-LABEL: @_Z4foo3Pi( ; CHECK-NEXT: entry: ; CHECK-NEXT: [[TMP0:%.*]] = load i32, i32* [[A:%.*]], align 4 diff --git a/llvm/test/Transforms/FunctionAttrs/nosync.ll b/llvm/test/Transforms/FunctionAttrs/nosync.ll --- a/llvm/test/Transforms/FunctionAttrs/nosync.ll +++ b/llvm/test/Transforms/FunctionAttrs/nosync.ll @@ -49,7 +49,7 @@ ; negative case - explicit sync define void @test5(i8* %p) { -; CHECK: Function Attrs: mustprogress nofree norecurse nounwind willreturn +; CHECK: Function Attrs: argmemonly mustprogress nofree norecurse nounwind willreturn ; CHECK-LABEL: @test5( ; CHECK-NEXT: store atomic i8 0, i8* [[P:%.*]] seq_cst, align 1 ; CHECK-NEXT: ret void @@ -60,7 +60,7 @@ ; negative case - explicit sync define i8 @test6(i8* %p) { -; CHECK: Function Attrs: mustprogress nofree norecurse nounwind willreturn +; CHECK: Function Attrs: argmemonly mustprogress nofree norecurse nounwind willreturn ; CHECK-LABEL: @test6( ; CHECK-NEXT: [[V:%.*]] = load atomic i8, i8* [[P:%.*]] seq_cst, align 1 ; CHECK-NEXT: ret i8 [[V]] @@ -71,7 +71,7 @@ ; negative case - explicit sync define void @test7(i8* %p) { -; CHECK: Function Attrs: mustprogress nofree norecurse nounwind willreturn +; CHECK: Function Attrs: argmemonly mustprogress nofree norecurse nounwind willreturn ; CHECK-LABEL: @test7( ; CHECK-NEXT: [[TMP1:%.*]] = atomicrmw add i8* [[P:%.*]], i8 0 seq_cst, align 1 ; CHECK-NEXT: ret void @@ -104,7 +104,7 @@ ; atomic load with monotonic ordering define i32 @load_monotonic(i32* nocapture readonly %0) norecurse nounwind uwtable { -; CHECK: Function Attrs: mustprogress nofree norecurse nounwind willreturn uwtable +; CHECK: Function Attrs: argmemonly mustprogress nofree norecurse nounwind willreturn uwtable ; CHECK-LABEL: @load_monotonic( ; CHECK-NEXT: [[TMP2:%.*]] = load atomic i32, i32* [[TMP0:%.*]] monotonic, align 4 ; CHECK-NEXT: ret i32 [[TMP2]] @@ -115,7 +115,7 @@ ; atomic store with monotonic ordering. define void @store_monotonic(i32* nocapture %0) norecurse nounwind uwtable { -; CHECK: Function Attrs: mustprogress nofree norecurse nounwind willreturn uwtable +; CHECK: Function Attrs: argmemonly mustprogress nofree norecurse nounwind willreturn uwtable ; CHECK-LABEL: @store_monotonic( ; CHECK-NEXT: store atomic i32 10, i32* [[TMP0:%.*]] monotonic, align 4 ; CHECK-NEXT: ret void @@ -127,7 +127,7 @@ ; negative, should not deduce nosync ; atomic load with acquire ordering. define i32 @load_acquire(i32* nocapture readonly %0) norecurse nounwind uwtable { -; CHECK: Function Attrs: mustprogress nofree norecurse nounwind willreturn uwtable +; CHECK: Function Attrs: argmemonly mustprogress nofree norecurse nounwind willreturn uwtable ; CHECK-LABEL: @load_acquire( ; CHECK-NEXT: [[TMP2:%.*]] = load atomic i32, i32* [[TMP0:%.*]] acquire, align 4 ; CHECK-NEXT: ret i32 [[TMP2]] @@ -137,7 +137,7 @@ } define i32 @load_unordered(i32* nocapture readonly %0) norecurse nounwind uwtable { -; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind readonly willreturn uwtable +; CHECK: Function Attrs: argmemonly mustprogress nofree norecurse nosync nounwind readonly willreturn uwtable ; CHECK-LABEL: @load_unordered( ; CHECK-NEXT: [[TMP2:%.*]] = load atomic i32, i32* [[TMP0:%.*]] unordered, align 4 ; CHECK-NEXT: ret i32 [[TMP2]] @@ -148,7 +148,7 @@ ; atomic store with unordered ordering. define void @store_unordered(i32* nocapture %0) norecurse nounwind uwtable { -; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn writeonly uwtable +; CHECK: Function Attrs: argmemonly mustprogress nofree norecurse nosync nounwind willreturn writeonly uwtable ; CHECK-LABEL: @store_unordered( ; CHECK-NEXT: store atomic i32 10, i32* [[TMP0:%.*]] unordered, align 4 ; CHECK-NEXT: ret void @@ -161,7 +161,7 @@ ; negative, should not deduce nosync ; atomic load with release ordering define void @load_release(i32* nocapture %0) norecurse nounwind uwtable { -; CHECK: Function Attrs: nofree norecurse nounwind uwtable +; CHECK: Function Attrs: argmemonly nofree norecurse nounwind uwtable ; CHECK-LABEL: @load_release( ; CHECK-NEXT: store atomic volatile i32 10, i32* [[TMP0:%.*]] release, align 4 ; CHECK-NEXT: ret void @@ -172,7 +172,7 @@ ; negative volatile, relaxed atomic define void @load_volatile_release(i32* nocapture %0) norecurse nounwind uwtable { -; CHECK: Function Attrs: nofree norecurse nounwind uwtable +; CHECK: Function Attrs: argmemonly nofree norecurse nounwind uwtable ; CHECK-LABEL: @load_volatile_release( ; CHECK-NEXT: store atomic volatile i32 10, i32* [[TMP0:%.*]] release, align 4 ; CHECK-NEXT: ret void @@ -183,7 +183,7 @@ ; volatile store. define void @volatile_store(i32* %0) norecurse nounwind uwtable { -; CHECK: Function Attrs: nofree norecurse nounwind uwtable +; CHECK: Function Attrs: argmemonly nofree norecurse nounwind uwtable ; CHECK-LABEL: @volatile_store( ; CHECK-NEXT: store volatile i32 14, i32* [[TMP0:%.*]], align 4 ; CHECK-NEXT: ret void @@ -195,7 +195,7 @@ ; negative, should not deduce nosync ; volatile load. define i32 @volatile_load(i32* %0) norecurse nounwind uwtable { -; CHECK: Function Attrs: mustprogress nofree norecurse nounwind willreturn uwtable +; CHECK: Function Attrs: argmemonly mustprogress nofree norecurse nounwind willreturn uwtable ; CHECK-LABEL: @volatile_load( ; CHECK-NEXT: [[TMP2:%.*]] = load volatile i32, i32* [[TMP0:%.*]], align 4 ; CHECK-NEXT: ret i32 [[TMP2]] diff --git a/llvm/test/Transforms/FunctionAttrs/stats.ll b/llvm/test/Transforms/FunctionAttrs/stats.ll --- a/llvm/test/Transforms/FunctionAttrs/stats.ll +++ b/llvm/test/Transforms/FunctionAttrs/stats.ll @@ -16,7 +16,8 @@ ret void } -; CHECK: 1 function-attrs - Number of arguments marked nocapture +; CHECK: 1 function-attrs - Number of functions marked argmemonly +; CHECK-NEXT: 1 function-attrs - Number of arguments marked nocapture ; CHECK-NEXT: 1 function-attrs - Number of functions marked as nofree ; CHECK-NEXT: 2 function-attrs - Number of functions marked as norecurse ; CHECK-NEXT: 2 function-attrs - Number of functions marked as nosync