diff --git a/llvm/include/llvm/Transforms/IPO/Attributor.h b/llvm/include/llvm/Transforms/IPO/Attributor.h --- a/llvm/include/llvm/Transforms/IPO/Attributor.h +++ b/llvm/include/llvm/Transforms/IPO/Attributor.h @@ -3270,6 +3270,17 @@ LazyCallGraph &CG, CGSCCUpdateResult &UR); }; +struct LightweightAttributorPass + : public PassInfoMixin { + PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); +}; + +struct LightweightAttributorCGSCCPass + : public PassInfoMixin { + PreservedAnalyses run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, + LazyCallGraph &CG, CGSCCUpdateResult &UR); +}; + /// Helper function to clamp a state \p S of type \p StateType with the /// information in \p R and indicate/return if \p S did change (as-in update is /// required to be run again). diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp --- a/llvm/lib/Passes/PassBuilderPipelines.cpp +++ b/llvm/lib/Passes/PassBuilderPipelines.cpp @@ -257,6 +257,10 @@ cl::desc( "Enable pass to eliminate conditions based on linear constraints")); +static cl::opt UseLightAttributor("enable-light-attributor", + cl::init(true), cl::Hidden, + cl::desc("")); + static cl::opt AttributorRun( "attributor-enable", cl::Hidden, cl::init(AttributorRunOption::NONE), cl::desc("Enable the attributor inter-procedural deduction pass"), @@ -901,7 +905,7 @@ // Finally, deduce any function attributes based on the fully simplified // function. - MainCGPipeline.addPass(PostOrderFunctionAttrsPass()); + MainCGPipeline.addPass(PostOrderFunctionAttrsPass(/*SkipNonRecursive*/ true)); // Mark that the function is fully simplified and that it shouldn't be // simplified again if we somehow revisit it due to CGSCC mutations unless @@ -1287,7 +1291,10 @@ // Do RPO function attribute inference across the module to forward-propagate // attributes where applicable. // FIXME: Is this really an optimization rather than a canonicalization? - MPM.addPass(ReversePostOrderFunctionAttrsPass()); + if (UseLightAttributor) + MPM.addPass(LightweightAttributorPass()); + else + MPM.addPass(ReversePostOrderFunctionAttrsPass()); // Do a post inline PGO instrumentation and use pass. This is a context // sensitive PGO pass. We don't want to do this in LTOPreLink phrase as @@ -1656,13 +1663,17 @@ } // Now deduce any function attributes based in the current code. - MPM.addPass( - createModuleToPostOrderCGSCCPassAdaptor(PostOrderFunctionAttrsPass())); + if (UseLightAttributor) + MPM.addPass(LightweightAttributorPass()); + else { + MPM.addPass( + createModuleToPostOrderCGSCCPassAdaptor(PostOrderFunctionAttrsPass())); - // Do RPO function attribute inference across the module to forward-propagate - // attributes where applicable. - // FIXME: Is this really an optimization rather than a canonicalization? - MPM.addPass(ReversePostOrderFunctionAttrsPass()); + // Do RPO function attribute inference across the module to + // forward-propagate attributes where applicable. + // FIXME: Is this really an optimization rather than a canonicalization? + MPM.addPass(ReversePostOrderFunctionAttrsPass()); + } // Use in-range annotations on GEP indices to split globals where beneficial. MPM.addPass(GlobalSplitPass()); diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -42,6 +42,7 @@ #endif MODULE_PASS("always-inline", AlwaysInlinerPass()) MODULE_PASS("attributor", AttributorPass()) +MODULE_PASS("light-attributor", LightweightAttributorPass()) MODULE_PASS("annotation2metadata", Annotation2MetadataPass()) MODULE_PASS("openmp-opt", OpenMPOptPass()) MODULE_PASS("openmp-opt-postlink", OpenMPOptPass(ThinOrFullLTOPhase::FullLTOPostLink)) diff --git a/llvm/lib/Transforms/IPO/Attributor.cpp b/llvm/lib/Transforms/IPO/Attributor.cpp --- a/llvm/lib/Transforms/IPO/Attributor.cpp +++ b/llvm/lib/Transforms/IPO/Attributor.cpp @@ -3657,6 +3657,64 @@ return Changed == ChangeStatus::CHANGED; } +static bool runLightweightAttributorOnFunctions( + InformationCache &InfoCache, SetVector &Functions, + AnalysisGetter &AG, CallGraphUpdater &CGUpdater, bool DeleteFns, + bool IsModulePass) { + if (Functions.empty()) + return false; + + LLVM_DEBUG({ + dbgs() << "[LightweightAttributor] Run on module with " << Functions.size() + << " functions:\n"; + for (Function *Fn : Functions) + dbgs() << " - " << Fn->getName() << "\n"; + }); + + // Create an Attributor and initially empty information cache that is filled + // while we identify default attribute opportunities. + AttributorConfig AC(CGUpdater); + AC.IsModulePass = IsModulePass; + AC.DeleteFns = DeleteFns; + DenseSet Allowed( + {&AAWillReturn::ID, &AANoUnwind::ID, &AANoRecurse::ID, &AANoSync::ID, + &AANoFree::ID, &AANoReturn::ID, &AAMemoryLocation::ID, + &AAMemoryBehavior::ID, &AANoCapture::ID, &AAReturnedValues::ID, + &AAInterFnReachability::ID, &AACallEdges::ID}); + AC.Allowed = &Allowed; + + Attributor A(Functions, InfoCache, AC); + + for (Function *F : Functions) { + if (F->hasExactDefinition()) + NumFnWithExactDefinition++; + else + NumFnWithoutExactDefinition++; + + // We look at internal functions only on-demand but if any use is not a + // direct call or outside the current set of analyzed functions, we have + // to do it eagerly. + if (F->hasLocalLinkage()) { + if (llvm::all_of(F->uses(), [&Functions](const Use &U) { + const auto *CB = dyn_cast(U.getUser()); + return CB && CB->isCallee(&U) && + Functions.count(const_cast(CB->getCaller())); + })) + continue; + } + + // Populate the Attributor with abstract attribute opportunities in the + // function and the information cache with IR information. + A.identifyDefaultAbstractAttributes(*F); + } + + ChangeStatus Changed = A.run(); + + LLVM_DEBUG(dbgs() << "[Attributor] Done with " << Functions.size() + << " functions, result: " << Changed << ".\n"); + return Changed == ChangeStatus::CHANGED; +} + void AADepGraph::viewGraph() { llvm::ViewGraph(this, "Dependency Graph"); } void AADepGraph::dumpGraph() { @@ -3737,6 +3795,59 @@ return PreservedAnalyses::all(); } +PreservedAnalyses LightweightAttributorPass::run(Module &M, + ModuleAnalysisManager &AM) { + FunctionAnalysisManager &FAM = + AM.getResult(M).getManager(); + AnalysisGetter AG(FAM); + + SetVector Functions; + for (Function &F : M) + Functions.insert(&F); + + CallGraphUpdater CGUpdater; + BumpPtrAllocator Allocator; + InformationCache InfoCache(M, AG, Allocator, /* CGSCC */ nullptr); + if (runLightweightAttributorOnFunctions(InfoCache, Functions, AG, CGUpdater, + /* DeleteFns */ false, + /* IsModulePass */ true)) { + // FIXME: Think about passes we will preserve and add them here. + return PreservedAnalyses::none(); + } + return PreservedAnalyses::all(); +} + +PreservedAnalyses LightweightAttributorCGSCCPass::run(LazyCallGraph::SCC &C, + CGSCCAnalysisManager &AM, + LazyCallGraph &CG, + CGSCCUpdateResult &UR) { + FunctionAnalysisManager &FAM = + AM.getResult(C, CG).getManager(); + AnalysisGetter AG(FAM); + + SetVector Functions; + for (LazyCallGraph::Node &N : C) + Functions.insert(&N.getFunction()); + + if (Functions.empty()) + return PreservedAnalyses::all(); + + Module &M = *Functions.back()->getParent(); + CallGraphUpdater CGUpdater; + CGUpdater.initialize(CG, C, AM, UR); + BumpPtrAllocator Allocator; + InformationCache InfoCache(M, AG, Allocator, /* CGSCC */ &Functions); + if (runLightweightAttributorOnFunctions(InfoCache, Functions, AG, CGUpdater, + /* DeleteFns */ false, + /* IsModulePass */ false)) { + // FIXME: Think about passes we will preserve and add them here. + PreservedAnalyses PA; + PA.preserve(); + PA.preserveSet(); + return PA; + } + return PreservedAnalyses::all(); +} namespace llvm { template <> struct GraphTraits { diff --git a/llvm/test/Transforms/FunctionAttrs/nocapture.ll b/llvm/test/Transforms/FunctionAttrs/nocapture.ll --- a/llvm/test/Transforms/FunctionAttrs/nocapture.ll +++ b/llvm/test/Transforms/FunctionAttrs/nocapture.ll @@ -1,4 +1,5 @@ ; RUN: opt -passes=function-attrs -S < %s | FileCheck %s --check-prefixes=FNATTR +; RUN: opt -passes=light-attributor -S < %s | FileCheck %s --check-prefixes=FNATTR @g = global ptr null ; [#uses=1] diff --git a/llvm/test/Transforms/FunctionAttrs/readattrs.ll b/llvm/test/Transforms/FunctionAttrs/readattrs.ll --- a/llvm/test/Transforms/FunctionAttrs/readattrs.ll +++ b/llvm/test/Transforms/FunctionAttrs/readattrs.ll @@ -1,5 +1,6 @@ ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes -; RUN: opt < %s -passes=function-attrs -S | FileCheck %s +; RUN: opt < %s -passes=function-attrs -S | FileCheck --check-prefixes=COMMON,CHECK %s +; RUN: opt < %s -passes=light-attributor -S | FileCheck --check-prefixes=COMMON,ATTRIBUTOR %s @x = global i32 0 @@ -11,18 +12,31 @@ ; CHECK-NEXT: call void (ptr, ptr, ...) @test1_1(ptr [[X1_2]], ptr [[Y1_2]], ptr [[Z1_2]]) ; CHECK-NEXT: store i32 0, ptr @x, align 4 ; CHECK-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define {{[^@]+}}@test1_2 +; ATTRIBUTOR-SAME: (ptr [[X1_2:%.*]], ptr nocapture readonly [[Y1_2:%.*]], ptr [[Z1_2:%.*]]) { +; ATTRIBUTOR-NEXT: call void (ptr, ptr, ...) @test1_1(ptr [[X1_2]], ptr nocapture readonly [[Y1_2]], ptr [[Z1_2]]) +; ATTRIBUTOR-NEXT: store i32 0, ptr @x, align 4 +; ATTRIBUTOR-NEXT: ret void ; call void (ptr, ptr, ...) @test1_1(ptr %x1_2, ptr %y1_2, ptr %z1_2) store i32 0, ptr @x ret void } +; TODO: Missing with attributor: mustprogress, argmem: none, inaccessiblemem: none define ptr @test2(ptr %p) { ; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(write, argmem: none, inaccessiblemem: none) ; CHECK-LABEL: define {{[^@]+}}@test2 ; CHECK-SAME: (ptr readnone returned [[P:%.*]]) #[[ATTR0:[0-9]+]] { ; CHECK-NEXT: store i32 0, ptr @x, align 4 ; CHECK-NEXT: ret ptr [[P]] +; +; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind willreturn memory(write) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@test2 +; ATTRIBUTOR-SAME: (ptr nofree readnone returned [[P:%.*]]) #[[ATTR0:[0-9]+]] { +; ATTRIBUTOR-NEXT: store i32 0, ptr @x, align 4 +; ATTRIBUTOR-NEXT: ret ptr [[P]] ; store i32 0, ptr @x ret ptr %p @@ -34,6 +48,12 @@ ; CHECK-SAME: (ptr readnone [[P:%.*]], ptr readnone [[Q:%.*]]) #[[ATTR1:[0-9]+]] { ; CHECK-NEXT: [[A:%.*]] = icmp ult ptr [[P]], [[Q]] ; CHECK-NEXT: ret i1 [[A]] +; +; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@test3 +; ATTRIBUTOR-SAME: (ptr nofree readnone [[P:%.*]], ptr nofree readnone [[Q:%.*]]) #[[ATTR1:[0-9]+]] { +; ATTRIBUTOR-NEXT: [[A:%.*]] = icmp ult ptr [[P]], [[Q]] +; ATTRIBUTOR-NEXT: ret i1 [[A]] ; %A = icmp ult ptr %p, %q ret i1 %A @@ -47,6 +67,12 @@ ; CHECK-SAME: (ptr nocapture readonly [[P:%.*]]) #[[ATTR3:[0-9]+]] { ; CHECK-NEXT: call void @test4_1(ptr [[P]]) ; CHECK-NEXT: ret void +; +; ATTRIBUTOR: Function Attrs: memory(read) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@test4_2 +; ATTRIBUTOR-SAME: (ptr nocapture readonly [[P:%.*]]) #[[ATTR2:[0-9]+]] { +; ATTRIBUTOR-NEXT: call void @test4_1(ptr nocapture readonly [[P]]) +; ATTRIBUTOR-NEXT: ret void ; call void @test4_1(ptr %p) ret void @@ -59,6 +85,12 @@ ; CHECK-SAME: (ptr nocapture writeonly [[P:%.*]], ptr [[Q:%.*]]) #[[ATTR4:[0-9]+]] { ; CHECK-NEXT: store ptr [[Q]], ptr [[P]], align 8 ; CHECK-NEXT: ret void +; +; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind willreturn memory(argmem: write) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@test5 +; ATTRIBUTOR-SAME: (ptr nocapture nofree writeonly [[P:%.*]], ptr nofree writeonly [[Q:%.*]]) #[[ATTR3:[0-9]+]] { +; ATTRIBUTOR-NEXT: store ptr [[Q]], ptr [[P]], align 8 +; ATTRIBUTOR-NEXT: ret void ; store ptr %q, ptr %p ret void @@ -73,6 +105,12 @@ ; CHECK-NEXT: store ptr [[Q]], ptr [[P]], align 8 ; CHECK-NEXT: call void @test6_1() ; CHECK-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define {{[^@]+}}@test6_2 +; ATTRIBUTOR-SAME: (ptr nocapture nofree writeonly [[P:%.*]], ptr nofree [[Q:%.*]]) { +; ATTRIBUTOR-NEXT: store ptr [[Q]], ptr [[P]], align 8 +; ATTRIBUTOR-NEXT: call void @test6_1() +; ATTRIBUTOR-NEXT: ret void ; store ptr %q, ptr %p call void @test6_1() @@ -85,6 +123,11 @@ ; CHECK-LABEL: define {{[^@]+}}@test7_1 ; CHECK-SAME: (ptr nocapture inalloca(i32) [[A:%.*]]) #[[ATTR5:[0-9]+]] { ; CHECK-NEXT: ret void +; +; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@test7_1 +; ATTRIBUTOR-SAME: (ptr nocapture nofree writeonly inalloca(i32) [[A:%.*]]) #[[ATTR1]] { +; ATTRIBUTOR-NEXT: ret void ; ret void } @@ -95,6 +138,11 @@ ; CHECK-LABEL: define {{[^@]+}}@test7_2 ; CHECK-SAME: (ptr nocapture preallocated(i32) [[A:%.*]]) #[[ATTR5]] { ; CHECK-NEXT: ret void +; +; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@test7_2 +; ATTRIBUTOR-SAME: (ptr nocapture nofree writeonly preallocated(i32) [[A:%.*]]) #[[ATTR1]] { +; ATTRIBUTOR-NEXT: ret void ; ret void } @@ -106,6 +154,12 @@ ; CHECK-NEXT: entry: ; CHECK-NEXT: ret ptr [[P]] ; +; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@test8_1 +; ATTRIBUTOR-SAME: (ptr nofree readnone returned [[P:%.*]]) #[[ATTR1]] { +; ATTRIBUTOR-NEXT: entry: +; ATTRIBUTOR-NEXT: ret ptr [[P]] +; entry: ret ptr %p } @@ -119,6 +173,14 @@ ; CHECK-NEXT: store i32 10, ptr [[CALL]], align 4 ; CHECK-NEXT: ret void ; +; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind willreturn memory(write) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@test8_2 +; ATTRIBUTOR-SAME: (ptr nocapture nofree writeonly [[P:%.*]]) #[[ATTR0]] { +; ATTRIBUTOR-NEXT: entry: +; ATTRIBUTOR-NEXT: [[CALL:%.*]] = call ptr @test8_1(ptr nofree readnone [[P]]) #[[ATTR11:[0-9]+]] +; ATTRIBUTOR-NEXT: store i32 10, ptr [[CALL]], align 4 +; ATTRIBUTOR-NEXT: ret void +; entry: %call = call ptr @test8_1(ptr %p) store i32 10, ptr %call, align 4 @@ -133,6 +195,12 @@ ; CHECK-SAME: (<4 x ptr> [[PTRS:%.*]], <4 x i32> [[VAL:%.*]]) #[[ATTR7:[0-9]+]] { ; CHECK-NEXT: call void @llvm.masked.scatter.v4i32.v4p0(<4 x i32> [[VAL]], <4 x ptr> [[PTRS]], i32 4, <4 x i1> ) ; CHECK-NEXT: ret void +; +; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind willreturn memory(write) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@test9 +; ATTRIBUTOR-SAME: (<4 x ptr> [[PTRS:%.*]], <4 x i32> [[VAL:%.*]]) #[[ATTR0]] { +; ATTRIBUTOR-NEXT: call void @llvm.masked.scatter.v4i32.v4p0(<4 x i32> [[VAL]], <4 x ptr> [[PTRS]], i32 4, <4 x i1> ) #[[ATTR12:[0-9]+]] +; ATTRIBUTOR-NEXT: ret void ; call void @llvm.masked.scatter.v4i32.v4p0(<4 x i32>%val, <4 x ptr> %ptrs, i32 4, <4 x i1>) ret void @@ -145,6 +213,12 @@ ; CHECK-SAME: (<4 x ptr> [[PTRS:%.*]]) #[[ATTR9:[0-9]+]] { ; CHECK-NEXT: [[RES:%.*]] = call <4 x i32> @llvm.masked.gather.v4i32.v4p0(<4 x ptr> [[PTRS]], i32 4, <4 x i1> , <4 x i32> undef) ; CHECK-NEXT: ret <4 x i32> [[RES]] +; +; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind willreturn memory(read) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@test10 +; ATTRIBUTOR-SAME: (<4 x ptr> [[PTRS:%.*]]) #[[ATTR6:[0-9]+]] { +; ATTRIBUTOR-NEXT: [[RES:%.*]] = call <4 x i32> @llvm.masked.gather.v4i32.v4p0(<4 x ptr> [[PTRS]], i32 4, <4 x i1> , <4 x i32> undef) #[[ATTR12]] +; ATTRIBUTOR-NEXT: ret <4 x i32> [[RES]] ; %res = call <4 x i32> @llvm.masked.gather.v4i32.v4p0(<4 x ptr> %ptrs, i32 4, <4 x i1>, <4 x i32>undef) ret <4 x i32> %res @@ -157,6 +231,12 @@ ; CHECK-SAME: (<4 x ptr> [[PTRS:%.*]]) #[[ATTR11:[0-9]+]] { ; CHECK-NEXT: [[RES:%.*]] = call <4 x i32> @test11_1(<4 x ptr> [[PTRS]]) ; CHECK-NEXT: ret <4 x i32> [[RES]] +; +; ATTRIBUTOR: Function Attrs: nounwind memory(argmem: read) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@test11_2 +; ATTRIBUTOR-SAME: (<4 x ptr> [[PTRS:%.*]]) #[[ATTR7:[0-9]+]] { +; ATTRIBUTOR-NEXT: [[RES:%.*]] = call <4 x i32> @test11_1(<4 x ptr> [[PTRS]]) +; ATTRIBUTOR-NEXT: ret <4 x i32> [[RES]] ; %res = call <4 x i32> @test11_1(<4 x ptr> %ptrs) ret <4 x i32> %res @@ -169,6 +249,12 @@ ; CHECK-SAME: (<4 x ptr> [[PTRS:%.*]]) #[[ATTR12:[0-9]+]] { ; CHECK-NEXT: [[RES:%.*]] = call <4 x i32> @test12_1(<4 x ptr> [[PTRS]]) ; CHECK-NEXT: ret <4 x i32> [[RES]] +; +; ATTRIBUTOR: Function Attrs: nounwind memory(argmem: readwrite) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@test12_2 +; ATTRIBUTOR-SAME: (<4 x ptr> [[PTRS:%.*]]) #[[ATTR8:[0-9]+]] { +; ATTRIBUTOR-NEXT: [[RES:%.*]] = call <4 x i32> @test12_1(<4 x ptr> [[PTRS]]) +; ATTRIBUTOR-NEXT: ret <4 x i32> [[RES]] ; %res = call <4 x i32> @test12_1(<4 x ptr> %ptrs) ret <4 x i32> %res @@ -180,6 +266,12 @@ ; CHECK-SAME: (ptr [[P:%.*]]) #[[ATTR13:[0-9]+]] { ; CHECK-NEXT: [[LOAD:%.*]] = load volatile i32, ptr [[P]], align 4 ; CHECK-NEXT: ret i32 [[LOAD]] +; +; ATTRIBUTOR: Function Attrs: nofree norecurse nounwind willreturn memory(argmem: readwrite) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@volatile_load +; ATTRIBUTOR-SAME: (ptr nofree [[P:%.*]]) #[[ATTR9:[0-9]+]] { +; ATTRIBUTOR-NEXT: [[LOAD:%.*]] = load volatile i32, ptr [[P]], align 4 +; ATTRIBUTOR-NEXT: ret i32 [[LOAD]] ; %load = load volatile i32, ptr %p ret i32 %load @@ -200,6 +292,14 @@ ; CHECK-NEXT: [[ADDR_LD:%.*]] = load ptr, ptr [[ADDR]], align 8 ; CHECK-NEXT: store i8 0, ptr [[ADDR_LD]], align 1 ; CHECK-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define {{[^@]+}}@unsound_readnone +; ATTRIBUTOR-SAME: (ptr nocapture nofree readnone [[IGNORED:%.*]], ptr [[ESCAPED_THEN_WRITTEN:%.*]]) { +; ATTRIBUTOR-NEXT: [[ADDR:%.*]] = alloca ptr, align 8 +; ATTRIBUTOR-NEXT: call void @escape_readnone_ptr(ptr [[ADDR]], ptr [[ESCAPED_THEN_WRITTEN]]) +; ATTRIBUTOR-NEXT: [[ADDR_LD:%.*]] = load ptr, ptr [[ADDR]], align 8 +; ATTRIBUTOR-NEXT: store i8 0, ptr [[ADDR_LD]], align 1 +; ATTRIBUTOR-NEXT: ret void ; %addr = alloca ptr call void @escape_readnone_ptr(ptr %addr, ptr %escaped_then_written) @@ -216,6 +316,14 @@ ; CHECK-NEXT: [[ADDR_LD:%.*]] = load ptr, ptr [[ADDR]], align 8 ; CHECK-NEXT: store i8 0, ptr [[ADDR_LD]], align 1 ; CHECK-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define {{[^@]+}}@unsound_readonly +; ATTRIBUTOR-SAME: (ptr nocapture nofree readnone [[IGNORED:%.*]], ptr [[ESCAPED_THEN_WRITTEN:%.*]]) { +; ATTRIBUTOR-NEXT: [[ADDR:%.*]] = alloca ptr, align 8 +; ATTRIBUTOR-NEXT: call void @escape_readonly_ptr(ptr [[ADDR]], ptr [[ESCAPED_THEN_WRITTEN]]) +; ATTRIBUTOR-NEXT: [[ADDR_LD:%.*]] = load ptr, ptr [[ADDR]], align 8 +; ATTRIBUTOR-NEXT: store i8 0, ptr [[ADDR_LD]], align 1 +; ATTRIBUTOR-NEXT: ret void ; %addr = alloca ptr call void @escape_readonly_ptr(ptr %addr, ptr %escaped_then_written) @@ -229,6 +337,11 @@ ; CHECK-SAME: (ptr nocapture readnone [[P:%.*]], ptr nocapture readonly [[F:%.*]]) { ; CHECK-NEXT: call void [[F]](ptr nocapture readnone [[P]]) ; CHECK-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define {{[^@]+}}@fptr_test1a +; ATTRIBUTOR-SAME: (ptr nocapture [[P:%.*]], ptr nocapture nofree [[F:%.*]]) { +; ATTRIBUTOR-NEXT: call void [[F]](ptr nocapture readnone [[P]]) +; ATTRIBUTOR-NEXT: ret void ; call void %f(ptr nocapture readnone %p) ret void @@ -240,6 +353,11 @@ ; CHECK-SAME: (ptr [[P:%.*]], ptr nocapture readonly [[F:%.*]]) { ; CHECK-NEXT: call void [[F]](ptr readnone [[P]]) ; CHECK-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define {{[^@]+}}@fptr_test1b +; ATTRIBUTOR-SAME: (ptr [[P:%.*]], ptr nocapture nofree [[F:%.*]]) { +; ATTRIBUTOR-NEXT: call void [[F]](ptr readnone [[P]]) +; ATTRIBUTOR-NEXT: ret void ; call void %f(ptr readnone %p) ret void @@ -251,6 +369,12 @@ ; CHECK-SAME: (ptr readnone [[P:%.*]], ptr nocapture readonly [[F:%.*]]) #[[ATTR3]] { ; CHECK-NEXT: call void [[F]](ptr readnone [[P]]) #[[ATTR2:[0-9]+]] ; CHECK-NEXT: ret void +; +; ATTRIBUTOR: Function Attrs: memory(read) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@fptr_test1c +; ATTRIBUTOR-SAME: (ptr readonly [[P:%.*]], ptr nocapture nofree readonly [[F:%.*]]) #[[ATTR2]] { +; ATTRIBUTOR-NEXT: call void [[F]](ptr readnone [[P]]) #[[ATTR2]] +; ATTRIBUTOR-NEXT: ret void ; call void %f(ptr readnone %p) readonly ret void @@ -261,6 +385,11 @@ ; CHECK-SAME: (ptr nocapture readonly [[P:%.*]], ptr nocapture readonly [[F:%.*]]) { ; CHECK-NEXT: call void [[F]](ptr nocapture readonly [[P]]) ; CHECK-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define {{[^@]+}}@fptr_test2a +; ATTRIBUTOR-SAME: (ptr nocapture [[P:%.*]], ptr nocapture nofree [[F:%.*]]) { +; ATTRIBUTOR-NEXT: call void [[F]](ptr nocapture readonly [[P]]) +; ATTRIBUTOR-NEXT: ret void ; call void %f(ptr nocapture readonly %p) ret void @@ -272,6 +401,11 @@ ; CHECK-SAME: (ptr [[P:%.*]], ptr nocapture readonly [[F:%.*]]) { ; CHECK-NEXT: call void [[F]](ptr readonly [[P]]) ; CHECK-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define {{[^@]+}}@fptr_test2b +; ATTRIBUTOR-SAME: (ptr [[P:%.*]], ptr nocapture nofree [[F:%.*]]) { +; ATTRIBUTOR-NEXT: call void [[F]](ptr readonly [[P]]) +; ATTRIBUTOR-NEXT: ret void ; call void %f(ptr readonly %p) ret void @@ -283,6 +417,12 @@ ; CHECK-SAME: (ptr readonly [[P:%.*]], ptr nocapture readonly [[F:%.*]]) #[[ATTR3]] { ; CHECK-NEXT: call void [[F]](ptr readonly [[P]]) #[[ATTR2]] ; CHECK-NEXT: ret void +; +; ATTRIBUTOR: Function Attrs: memory(read) +; ATTRIBUTOR-LABEL: define {{[^@]+}}@fptr_test2c +; ATTRIBUTOR-SAME: (ptr readonly [[P:%.*]], ptr nocapture nofree readonly [[F:%.*]]) #[[ATTR2]] { +; ATTRIBUTOR-NEXT: call void [[F]](ptr readonly [[P]]) #[[ATTR2]] +; ATTRIBUTOR-NEXT: ret void ; call void %f(ptr readonly %p) readonly ret void @@ -306,6 +446,23 @@ ; CHECK: exit: ; CHECK-NEXT: ret void ; +; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind willreturn +; ATTRIBUTOR-LABEL: define {{[^@]+}}@alloca_recphi +; ATTRIBUTOR-SAME: () #[[ATTR10:[0-9]+]] { +; ATTRIBUTOR-NEXT: entry: +; ATTRIBUTOR-NEXT: [[A:%.*]] = alloca [8 x i32], align 4 +; ATTRIBUTOR-NEXT: [[A_END:%.*]] = getelementptr i32, ptr [[A]], i64 8 +; ATTRIBUTOR-NEXT: br label [[LOOP:%.*]] +; ATTRIBUTOR: loop: +; ATTRIBUTOR-NEXT: [[P:%.*]] = phi ptr [ [[A]], [[ENTRY:%.*]] ], [ [[P_NEXT:%.*]], [[LOOP]] ] +; ATTRIBUTOR-NEXT: store i32 0, ptr [[P]], align 4 +; ATTRIBUTOR-NEXT: [[TMP0:%.*]] = load i32, ptr [[P]], align 4 +; ATTRIBUTOR-NEXT: [[P_NEXT]] = getelementptr i32, ptr [[P]], i64 1 +; ATTRIBUTOR-NEXT: [[C:%.*]] = icmp ne ptr [[P_NEXT]], [[A_END]] +; ATTRIBUTOR-NEXT: br i1 [[C]], label [[LOOP]], label [[EXIT:%.*]] +; ATTRIBUTOR: exit: +; ATTRIBUTOR-NEXT: ret void +; entry: %a = alloca [8 x i32] %a.end = getelementptr i32, ptr %a, i64 8 @@ -332,6 +489,11 @@ ; CHECK-SAME: (ptr nocapture [[P:%.*]]) { ; CHECK-NEXT: call void @readnone_param(ptr [[P]]) [ "deopt"() ] ; CHECK-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define {{[^@]+}}@op_bundle_readnone_deopt +; ATTRIBUTOR-SAME: (ptr nocapture [[P:%.*]]) { +; ATTRIBUTOR-NEXT: call void @readnone_param(ptr nocapture [[P]]) [ "deopt"() ] +; ATTRIBUTOR-NEXT: ret void ; call void @readnone_param(ptr %p) ["deopt"()] ret void @@ -342,6 +504,11 @@ ; CHECK-SAME: (ptr nocapture [[P:%.*]]) { ; CHECK-NEXT: call void @readnone_param(ptr [[P]]) [ "unknown"() ] ; CHECK-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define {{[^@]+}}@op_bundle_readnone_unknown +; ATTRIBUTOR-SAME: (ptr nocapture [[P:%.*]]) { +; ATTRIBUTOR-NEXT: call void @readnone_param(ptr nocapture [[P]]) [ "unknown"() ] +; ATTRIBUTOR-NEXT: ret void ; call void @readnone_param(ptr %p) ["unknown"()] ret void @@ -352,6 +519,11 @@ ; CHECK-SAME: (ptr nocapture readonly [[P:%.*]]) { ; CHECK-NEXT: call void @readonly_param(ptr [[P]]) [ "deopt"() ] ; CHECK-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define {{[^@]+}}@op_bundle_readonly_deopt +; ATTRIBUTOR-SAME: (ptr nocapture [[P:%.*]]) { +; ATTRIBUTOR-NEXT: call void @readonly_param(ptr nocapture [[P]]) [ "deopt"() ] +; ATTRIBUTOR-NEXT: ret void ; call void @readonly_param(ptr %p) ["deopt"()] ret void @@ -362,7 +534,14 @@ ; CHECK-SAME: (ptr nocapture [[P:%.*]]) { ; CHECK-NEXT: call void @readonly_param(ptr [[P]]) [ "unknown"() ] ; CHECK-NEXT: ret void +; +; ATTRIBUTOR-LABEL: define {{[^@]+}}@op_bundle_readonly_unknown +; ATTRIBUTOR-SAME: (ptr nocapture [[P:%.*]]) { +; ATTRIBUTOR-NEXT: call void @readonly_param(ptr nocapture [[P]]) [ "unknown"() ] +; ATTRIBUTOR-NEXT: ret void ; call void @readonly_param(ptr %p) ["unknown"()] ret void } +;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line: +; COMMON: {{.*}} diff --git a/llvm/test/Transforms/FunctionAttrs/willreturn.ll b/llvm/test/Transforms/FunctionAttrs/willreturn.ll --- a/llvm/test/Transforms/FunctionAttrs/willreturn.ll +++ b/llvm/test/Transforms/FunctionAttrs/willreturn.ll @@ -1,5 +1,6 @@ ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --check-attributes -; RUN: opt -passes=function-attrs -S %s | FileCheck %s +; RUN: opt -passes=function-attrs -S %s | FileCheck --check-prefixes=CHECK,INFER %s +; RUN: opt -passes=light-attributor -S %s | FileCheck --check-prefixes=CHECK,ATTRIBUTOR %s define void @mustprogress_readnone() mustprogress { ; CHECK: Function Attrs: mustprogress nofree norecurse noreturn nosync nounwind willreturn memory(none) @@ -63,11 +64,17 @@ } define i32 @mustprogress_call_known_functions(ptr %ptr) mustprogress { -; CHECK: Function Attrs: mustprogress nofree norecurse noreturn nosync nounwind willreturn memory(argmem: read) -; CHECK-LABEL: @mustprogress_call_known_functions( -; CHECK-NEXT: call void @mustprogress_readnone() -; CHECK-NEXT: [[R:%.*]] = call i32 @mustprogress_load(ptr [[PTR:%.*]]) -; CHECK-NEXT: ret i32 [[R]] +; INFER: Function Attrs: mustprogress nofree norecurse noreturn nosync nounwind willreturn memory(argmem: read) +; INFER-LABEL: @mustprogress_call_known_functions( +; INFER-NEXT: call void @mustprogress_readnone() +; INFER-NEXT: [[R:%.*]] = call i32 @mustprogress_load(ptr [[PTR:%.*]]) +; INFER-NEXT: ret i32 [[R]] +; +; ATTRIBUTOR: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(argmem: read) +; ATTRIBUTOR-LABEL: @mustprogress_call_known_functions( +; ATTRIBUTOR-NEXT: call void @mustprogress_readnone() #[[ATTR11:[0-9]+]] +; ATTRIBUTOR-NEXT: [[R:%.*]] = call i32 @mustprogress_load(ptr nocapture nofree readonly [[PTR:%.*]]) #[[ATTR11]] +; ATTRIBUTOR-NEXT: ret i32 [[R]] ; call void @mustprogress_readnone() %r = call i32 @mustprogress_load(ptr %ptr) @@ -77,16 +84,27 @@ declare i32 @__gxx_personality_v0(...) define i64 @mustprogress_mayunwind() mustprogress personality ptr @__gxx_personality_v0 { -; CHECK: Function Attrs: mustprogress nofree nosync nounwind willreturn memory(none) -; CHECK-LABEL: @mustprogress_mayunwind( -; CHECK-NEXT: [[A:%.*]] = invoke i64 @fn_noread() -; CHECK-NEXT: to label [[A:%.*]] unwind label [[B:%.*]] -; CHECK: A: -; CHECK-NEXT: ret i64 10 -; CHECK: B: -; CHECK-NEXT: [[VAL:%.*]] = landingpad { ptr, i32 } -; CHECK-NEXT: catch ptr null -; CHECK-NEXT: ret i64 0 +; INFER: Function Attrs: mustprogress nofree nosync nounwind willreturn memory(none) +; INFER-LABEL: @mustprogress_mayunwind( +; INFER-NEXT: [[A:%.*]] = invoke i64 @fn_noread() +; INFER-NEXT: to label [[A:%.*]] unwind label [[B:%.*]] +; INFER: A: +; INFER-NEXT: ret i64 10 +; INFER: B: +; INFER-NEXT: [[VAL:%.*]] = landingpad { ptr, i32 } +; INFER-NEXT: catch ptr null +; INFER-NEXT: ret i64 0 +; +; ATTRIBUTOR: Function Attrs: mustprogress nosync nounwind willreturn memory(none) +; ATTRIBUTOR-LABEL: @mustprogress_mayunwind( +; ATTRIBUTOR-NEXT: [[A:%.*]] = invoke i64 @fn_noread() +; ATTRIBUTOR-NEXT: to label [[A:%.*]] unwind label [[B:%.*]] +; ATTRIBUTOR: A: +; ATTRIBUTOR-NEXT: ret i64 10 +; ATTRIBUTOR: B: +; ATTRIBUTOR-NEXT: [[VAL:%.*]] = landingpad { ptr, i32 } +; ATTRIBUTOR-NEXT: catch ptr null +; ATTRIBUTOR-NEXT: ret i64 0 ; %a = invoke i64 @fn_noread() to label %A unwind label %B @@ -101,18 +119,31 @@ ; Function without loops or non-willreturn calls will return. define void @willreturn_no_loop(i1 %c, ptr %p) { -; CHECK: Function Attrs: mustprogress willreturn -; CHECK-LABEL: @willreturn_no_loop( -; CHECK-NEXT: br i1 [[C:%.*]], label [[IF:%.*]], label [[ELSE:%.*]] -; CHECK: if: -; CHECK-NEXT: [[TMP1:%.*]] = load atomic i32, ptr [[P:%.*]] seq_cst, align 4 -; CHECK-NEXT: call void @fn_willreturn() -; CHECK-NEXT: br label [[END:%.*]] -; CHECK: else: -; CHECK-NEXT: store atomic i32 0, ptr [[P]] seq_cst, align 4 -; CHECK-NEXT: br label [[END]] -; CHECK: end: -; CHECK-NEXT: ret void +; INFER: Function Attrs: mustprogress willreturn +; INFER-LABEL: @willreturn_no_loop( +; INFER-NEXT: br i1 [[C:%.*]], label [[IF:%.*]], label [[ELSE:%.*]] +; INFER: if: +; INFER-NEXT: [[TMP1:%.*]] = load atomic i32, ptr [[P:%.*]] seq_cst, align 4 +; INFER-NEXT: call void @fn_willreturn() +; INFER-NEXT: br label [[END:%.*]] +; INFER: else: +; INFER-NEXT: store atomic i32 0, ptr [[P]] seq_cst, align 4 +; INFER-NEXT: br label [[END]] +; INFER: end: +; INFER-NEXT: ret void +; +; ATTRIBUTOR: Function Attrs: willreturn +; ATTRIBUTOR-LABEL: @willreturn_no_loop( +; ATTRIBUTOR-NEXT: br i1 [[C:%.*]], label [[IF:%.*]], label [[ELSE:%.*]] +; ATTRIBUTOR: if: +; ATTRIBUTOR-NEXT: [[TMP1:%.*]] = load atomic i32, ptr [[P:%.*]] seq_cst, align 4 +; ATTRIBUTOR-NEXT: call void @fn_willreturn() #[[ATTR6:[0-9]+]] +; ATTRIBUTOR-NEXT: br label [[END:%.*]] +; ATTRIBUTOR: else: +; ATTRIBUTOR-NEXT: store atomic i32 0, ptr [[P]] seq_cst, align 4 +; ATTRIBUTOR-NEXT: br label [[END]] +; ATTRIBUTOR: end: +; ATTRIBUTOR-NEXT: ret void ; br i1 %c, label %if, label %else @@ -156,17 +187,29 @@ ; Finite loop. Could be willreturn but not detected. ; FIXME define void @willreturn_finite_loop() { -; CHECK: Function Attrs: nofree norecurse nosync nounwind memory(none) -; CHECK-LABEL: @willreturn_finite_loop( -; CHECK-NEXT: entry: -; CHECK-NEXT: br label [[LOOP:%.*]] -; CHECK: loop: -; CHECK-NEXT: [[I:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[I_INC:%.*]], [[LOOP]] ] -; CHECK-NEXT: [[I_INC]] = add nuw i32 [[I]], 1 -; CHECK-NEXT: [[C:%.*]] = icmp ne i32 [[I_INC]], 100 -; CHECK-NEXT: br i1 [[C]], label [[LOOP]], label [[END:%.*]] -; CHECK: end: -; CHECK-NEXT: ret void +; INFER: Function Attrs: nofree norecurse nosync nounwind memory(none) +; INFER-LABEL: @willreturn_finite_loop( +; INFER-NEXT: entry: +; INFER-NEXT: br label [[LOOP:%.*]] +; INFER: loop: +; INFER-NEXT: [[I:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[I_INC:%.*]], [[LOOP]] ] +; INFER-NEXT: [[I_INC]] = add nuw i32 [[I]], 1 +; INFER-NEXT: [[C:%.*]] = icmp ne i32 [[I_INC]], 100 +; INFER-NEXT: br i1 [[C]], label [[LOOP]], label [[END:%.*]] +; INFER: end: +; INFER-NEXT: ret void +; +; ATTRIBUTOR: Function Attrs: nofree norecurse nosync nounwind willreturn memory(none) +; ATTRIBUTOR-LABEL: @willreturn_finite_loop( +; ATTRIBUTOR-NEXT: entry: +; ATTRIBUTOR-NEXT: br label [[LOOP:%.*]] +; ATTRIBUTOR: loop: +; ATTRIBUTOR-NEXT: [[I:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[I_INC:%.*]], [[LOOP]] ] +; ATTRIBUTOR-NEXT: [[I_INC]] = add nuw i32 [[I]], 1 +; ATTRIBUTOR-NEXT: [[C:%.*]] = icmp ne i32 [[I_INC]], 100 +; ATTRIBUTOR-NEXT: br i1 [[C]], label [[LOOP]], label [[END:%.*]] +; ATTRIBUTOR: end: +; ATTRIBUTOR-NEXT: ret void ; entry: br label %loop @@ -183,10 +226,15 @@ ; Infinite recursion without mustprogress, will not return. define void @willreturn_recursion() { -; CHECK: Function Attrs: nofree nosync nounwind memory(none) -; CHECK-LABEL: @willreturn_recursion( -; CHECK-NEXT: tail call void @willreturn_recursion() -; CHECK-NEXT: ret void +; INFER: Function Attrs: nofree nosync nounwind memory(none) +; INFER-LABEL: @willreturn_recursion( +; INFER-NEXT: tail call void @willreturn_recursion() +; INFER-NEXT: ret void +; +; ATTRIBUTOR: Function Attrs: nofree nosync nounwind memory(none) +; ATTRIBUTOR-LABEL: @willreturn_recursion( +; ATTRIBUTOR-NEXT: tail call void @willreturn_recursion() #[[ATTR11]] +; ATTRIBUTOR-NEXT: ret void ; tail call void @willreturn_recursion() ret void