Index: llvm/include/llvm/Analysis/CallGraph.h =================================================================== --- llvm/include/llvm/Analysis/CallGraph.h +++ llvm/include/llvm/Analysis/CallGraph.h @@ -240,9 +240,6 @@ /// Adds a function to the list of functions called by this one. void addCalledFunction(CallBase *Call, CallGraphNode *M) { - assert(!Call || !Call->getCalledFunction() || - !Call->getCalledFunction()->isIntrinsic() || - !Intrinsic::isLeaf(Call->getCalledFunction()->getIntrinsicID())); CalledFunctions.emplace_back(Call ? std::optional(Call) : std::optional(), M); Index: llvm/include/llvm/IR/IntrinsicInst.h =================================================================== --- llvm/include/llvm/IR/IntrinsicInst.h +++ llvm/include/llvm/IR/IntrinsicInst.h @@ -41,6 +41,32 @@ class Metadata; +/// Check if \p ID corresponds to a known class of intrinsics that we often want +/// to ignore. +/// TODO: Split this up into meaningful categories, e.g., APIs for properties. +static inline bool isAssumeLikeIntrinsic(Intrinsic::ID ID) { + switch (ID) { + default: + return false; + case Intrinsic::assume: + case Intrinsic::sideeffect: + case Intrinsic::pseudoprobe: + case Intrinsic::dbg_assign: + case Intrinsic::dbg_declare: + case Intrinsic::dbg_value: + case Intrinsic::dbg_label: + case Intrinsic::invariant_start: + case Intrinsic::invariant_end: + case Intrinsic::lifetime_start: + case Intrinsic::lifetime_end: + case Intrinsic::experimental_noalias_scope_decl: + case Intrinsic::objectsize: + case Intrinsic::ptr_annotation: + case Intrinsic::var_annotation: + return true; + } +} + /// A wrapper class for inspecting calls to intrinsic functions. /// This allows the standard isa/dyncast/cast functionality to work with calls /// to intrinsic functions. @@ -85,28 +111,10 @@ } } - /// Checks if the intrinsic is an annotation. + /// Check if the intrinsic belongs to a known class of intrinsics that we + /// often want to ignore. bool isAssumeLikeIntrinsic() const { - switch (getIntrinsicID()) { - default: break; - case Intrinsic::assume: - case Intrinsic::sideeffect: - case Intrinsic::pseudoprobe: - case Intrinsic::dbg_assign: - case Intrinsic::dbg_declare: - case Intrinsic::dbg_value: - case Intrinsic::dbg_label: - case Intrinsic::invariant_start: - case Intrinsic::invariant_end: - case Intrinsic::lifetime_start: - case Intrinsic::lifetime_end: - case Intrinsic::experimental_noalias_scope_decl: - case Intrinsic::objectsize: - case Intrinsic::ptr_annotation: - case Intrinsic::var_annotation: - return true; - } - return false; + return llvm::isAssumeLikeIntrinsic(getIntrinsicID()); } /// Check if the intrinsic might lower into a regular function call in the Index: llvm/include/llvm/IR/Intrinsics.h =================================================================== --- llvm/include/llvm/IR/Intrinsics.h +++ llvm/include/llvm/IR/Intrinsics.h @@ -79,11 +79,6 @@ /// Returns true if the intrinsic can be overloaded. bool isOverloaded(ID id); - /// Returns true if the intrinsic is a leaf, i.e. it does not make any calls - /// itself. Most intrinsics are leafs, the exceptions being the patchpoint - /// and statepoint intrinsics. These call (or invoke) their "target" argument. - bool isLeaf(ID id); - /// Return the attributes for an intrinsic. AttributeList getAttributes(LLVMContext &C, ID id); Index: llvm/include/llvm/IR/Intrinsics.td =================================================================== --- llvm/include/llvm/IR/Intrinsics.td +++ llvm/include/llvm/IR/Intrinsics.td @@ -1236,12 +1236,12 @@ def int_experimental_stackmap : DefaultAttrsIntrinsic<[], [llvm_i64_ty, llvm_i32_ty, llvm_vararg_ty], [Throws]>; -def int_experimental_patchpoint_void : DefaultAttrsIntrinsic<[], +def int_experimental_patchpoint_void : Intrinsic<[], [llvm_i64_ty, llvm_i32_ty, llvm_ptr_ty, llvm_i32_ty, llvm_vararg_ty], [Throws]>; -def int_experimental_patchpoint_i64 : DefaultAttrsIntrinsic<[llvm_i64_ty], +def int_experimental_patchpoint_i64 : Intrinsic<[llvm_i64_ty], [llvm_i64_ty, llvm_i32_ty, llvm_ptr_ty, llvm_i32_ty, llvm_vararg_ty], Index: llvm/lib/Analysis/CallGraph.cpp =================================================================== --- llvm/lib/Analysis/CallGraph.cpp +++ llvm/lib/Analysis/CallGraph.cpp @@ -14,7 +14,6 @@ #include "llvm/IR/AbstractCallSite.h" #include "llvm/IR/Function.h" #include "llvm/IR/IntrinsicInst.h" -#include "llvm/IR/Intrinsics.h" #include "llvm/IR/Module.h" #include "llvm/IR/PassManager.h" #include "llvm/InitializePasses.h" @@ -35,7 +34,7 @@ CallsExternalNode(std::make_unique(this, nullptr)) { // Add every interesting function to the call graph. for (Function &F : M) - if (!isDbgInfoIntrinsic(F.getIntrinsicID())) + if (!isAssumeLikeIntrinsic(F.getIntrinsicID())) addToCallGraph(&F); } @@ -92,7 +91,7 @@ // If this function is not defined in this translation unit, it could call // anything. - if (F->isDeclaration() && !F->isIntrinsic()) + if (F->isDeclaration() && !F->hasFnAttribute(Attribute::NoCallback)) Node->addCalledFunction(nullptr, CallsExternalNode.get()); // Look for calls by this function. @@ -100,12 +99,9 @@ for (Instruction &I : BB) { if (auto *Call = dyn_cast(&I)) { const Function *Callee = Call->getCalledFunction(); - if (!Callee || !Intrinsic::isLeaf(Callee->getIntrinsicID())) - // Indirect calls of intrinsics are not allowed so no need to check. - // We can be more precise here by using TargetArg returned by - // Intrinsic::isLeaf. + if (!Callee) Node->addCalledFunction(Call, CallsExternalNode.get()); - else if (!Callee->isIntrinsic()) + else if (!isAssumeLikeIntrinsic(Callee->getIntrinsicID())) Node->addCalledFunction(Call, getOrInsertFunction(Callee)); // Add reference to callback functions. Index: llvm/lib/Analysis/CallGraphSCCPass.cpp =================================================================== --- llvm/lib/Analysis/CallGraphSCCPass.cpp +++ llvm/lib/Analysis/CallGraphSCCPass.cpp @@ -267,15 +267,7 @@ // If we've already seen this call site, then the FunctionPass RAUW'd // one call with another, which resulted in two "uses" in the edge // list of the same call. - Calls.count(Call) || - - // If the call edge is not from a call or invoke, or it is a - // intrinsic call, then the function pass RAUW'd a call with - // another value. This can happen when constant folding happens - // of well known functions etc. - (Call->getCalledFunction() && - Call->getCalledFunction()->isIntrinsic() && - Intrinsic::isLeaf(Call->getCalledFunction()->getIntrinsicID()))) { + Calls.count(Call)) { assert(!CheckingMode && "CallGraphSCCPass did not update the CallGraph correctly!"); Index: llvm/lib/Analysis/GlobalsModRef.cpp =================================================================== --- llvm/lib/Analysis/GlobalsModRef.cpp +++ llvm/lib/Analysis/GlobalsModRef.cpp @@ -23,7 +23,6 @@ #include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/Instructions.h" -#include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Module.h" #include "llvm/IR/PassManager.h" #include "llvm/InitializePasses.h" @@ -591,23 +590,6 @@ if (isModAndRefSet(FI.getModRefInfo())) break; // The mod/ref lattice saturates here. - // We handle calls specially because the graph-relevant aspects are - // handled above. - if (auto *Call = dyn_cast(&I)) { - if (Function *Callee = Call->getCalledFunction()) { - // The callgraph doesn't include intrinsic calls. - if (Callee->isIntrinsic()) { - if (isa(Call)) - // Don't let dbg intrinsics affect alias info. - continue; - - MemoryEffects Behaviour = AAResultBase::getMemoryEffects(Callee); - FI.addModRefInfo(Behaviour.getModRef()); - } - } - continue; - } - // All non-call instructions we use the primary predicates for whether // they read or write memory. if (I.mayReadFromMemory()) Index: llvm/lib/IR/Function.cpp =================================================================== --- llvm/lib/IR/Function.cpp +++ llvm/lib/IR/Function.cpp @@ -1499,18 +1499,6 @@ #undef GET_INTRINSIC_OVERLOAD_TABLE } -bool Intrinsic::isLeaf(ID id) { - switch (id) { - default: - return true; - - case Intrinsic::experimental_gc_statepoint: - case Intrinsic::experimental_patchpoint_void: - case Intrinsic::experimental_patchpoint_i64: - return false; - } -} - /// This defines the "Intrinsic::getAttributes(ID id)" method. #define GET_INTRINSIC_ATTRIBUTES #include "llvm/IR/IntrinsicImpl.inc" Index: llvm/test/Analysis/CallGraph/ignore-assumelike-calls.ll =================================================================== --- llvm/test/Analysis/CallGraph/ignore-assumelike-calls.ll +++ llvm/test/Analysis/CallGraph/ignore-assumelike-calls.ll @@ -2,7 +2,6 @@ ; CHECK: Call graph node <><<{{.*}}>> #uses=0 ; CHECK-NEXT: CS calls function 'other_intrinsic_use' ; CHECK-NEXT: CS calls function 'other_cast_intrinsic_use' -; CHECK-NEXT: CS calls function 'llvm.lifetime.start.p0' ; CHECK-NEXT: CS calls function 'llvm.memset.p0.i64' ; CHECK-NEXT: CS calls function 'llvm.memset.p1.i64' ; CHECK-EMPTY: @@ -10,15 +9,15 @@ ; CHECK-EMPTY: ; CHECK-NEXT: Call graph node for function: 'bitcast_only'<<{{.*}}>> #uses=0 ; CHECK-EMPTY: -; CHECK-NEXT: Call graph node for function: 'llvm.lifetime.start.p0'<<{{.*}}>> #uses=1 +; CHECK-NEXT: Call graph node for function: 'llvm.memset.p0.i64'<<{{.*}}>> #uses=2 ; CHECK-EMPTY: -; CHECK-NEXT: Call graph node for function: 'llvm.memset.p0.i64'<<{{.*}}>> #uses=1 -; CHECK-EMPTY: -; CHECK-NEXT: Call graph node for function: 'llvm.memset.p1.i64'<<{{.*}}>> #uses=1 +; CHECK-NEXT: Call graph node for function: 'llvm.memset.p1.i64'<<{{.*}}>> #uses=2 ; CHECK-EMPTY: ; CHECK-NEXT: Call graph node for function: 'other_cast_intrinsic_use'<<{{.*}}>> #uses=1 +; CHECK-NEXT: CS<{{.*}}> calls function 'llvm.memset.p1.i64' ; CHECK-EMPTY: ; CHECK-NEXT: Call graph node for function: 'other_intrinsic_use'<<{{.*}}>> #uses=1 +; CHECK-NEXT: CS<{{.*}}> calls function 'llvm.memset.p0.i64' ; CHECK-EMPTY: ; CHECK-NEXT: Call graph node for function: 'used_by_lifetime'<<{{.*}}>> #uses=0 ; CHECK-EMPTY: Index: llvm/test/Analysis/CallGraph/non-leaf-intrinsics.ll =================================================================== --- llvm/test/Analysis/CallGraph/non-leaf-intrinsics.ll +++ llvm/test/Analysis/CallGraph/non-leaf-intrinsics.ll @@ -25,7 +25,13 @@ ; CHECK: CS calls function 'f' ; CHECK: Call graph node for function: 'calls_patchpoint' -; CHECK-NEXT: CS<[[addr_1:[^>]+]]> calls external node +; CS<{{.*}}> calls function 'llvm.experimental.patchpoint.void' ; CHECK: Call graph node for function: 'calls_statepoint' -; CHECK-NEXT: CS<[[addr_0:[^>]+]]> calls external node +; CS<{{.*}}> calls function 'llvm.experimental.gc.statepoint.p0' + +; CHECK: Call graph node for function: 'llvm.experimental.gc.statepoint.p0'<<{{.*}}>> #uses=2 +; CHECK-NEXT: CS<[[addr_1:[^>]+]]> calls external node + +; CHECK: Call graph node for function: 'llvm.experimental.patchpoint.void'<<{{.*}}>> #uses=2 +; CHECK-NEXT: CS<[[addr_1:[^>]+]]> calls external node Index: llvm/test/Transforms/GVN/intrinsics_in_cg.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/GVN/intrinsics_in_cg.ll @@ -0,0 +1,63 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature +; RUN: opt < %s -passes='require,gvn' -S | FileCheck %s + +; Ensure we do not hoist the load over the call. + +@G1 = internal global i32 1 +@G2 = internal global i32 1 + +define i32 @direct_inrinsic(i1 %c) { +; CHECK-LABEL: define {{[^@]+}}@direct_inrinsic +; CHECK-SAME: (i1 [[C:%.*]]) { +; CHECK-NEXT: br i1 [[C]], label [[INIT:%.*]], label [[CHECK:%.*]] +; CHECK: init: +; CHECK-NEXT: store i32 0, ptr @G1, align 4 +; CHECK-NEXT: br label [[CHECK]] +; CHECK: check: +; CHECK-NEXT: call void @llvm.unknown() +; CHECK-NEXT: [[V:%.*]] = load i32, ptr @G1, align 4 +; CHECK-NEXT: ret i32 [[V]] +; + br i1 %c, label %init, label %check +init: + store i32 0, ptr @G1 + br label %check +check: + call void @llvm.unknown() + %v = load i32, ptr @G1 + ret i32 %v +} + +define i32 @indirect_intrinsic(i1 %c) { +; CHECK-LABEL: define {{[^@]+}}@indirect_intrinsic +; CHECK-SAME: (i1 [[C:%.*]]) { +; CHECK-NEXT: br i1 [[C]], label [[INIT:%.*]], label [[CHECK:%.*]] +; CHECK: init: +; CHECK-NEXT: store i32 0, ptr @G2, align 4 +; CHECK-NEXT: br label [[CHECK]] +; CHECK: check: +; CHECK-NEXT: call void @intrinsic_caller() +; CHECK-NEXT: [[V:%.*]] = load i32, ptr @G2, align 4 +; CHECK-NEXT: ret i32 [[V]] +; + br i1 %c, label %init, label %check +init: + store i32 0, ptr @G2 + br label %check +check: + call void @intrinsic_caller() + %v = load i32, ptr @G2 + ret i32 %v +} + +define fastcc void @intrinsic_caller() { +; CHECK-LABEL: define {{[^@]+}}@intrinsic_caller() { +; CHECK-NEXT: call void @llvm.unknown() +; CHECK-NEXT: ret void +; + call void @llvm.unknown() + ret void +} + +declare void @llvm.unknown() +