diff --git a/llvm/include/llvm/Analysis/MemoryBuiltins.h b/llvm/include/llvm/Analysis/MemoryBuiltins.h --- a/llvm/include/llvm/Analysis/MemoryBuiltins.h +++ b/llvm/include/llvm/Analysis/MemoryBuiltins.h @@ -64,6 +64,22 @@ bool isNoAliasFn(const Value *V, const TargetLibraryInfo *TLI, bool LookThroughBitCast = false); +/// Tests if a value is a call or invoke to a library function that +/// allocates uninitialized memory (such as malloc). +bool isUndefAllocFn(const Value *V, const TargetLibraryInfo *TLI, + bool LookThroughBitCast = false); +bool isUndefAllocFn(const Value *V, + function_ref GetTLI, + bool LookThroughBitCast = false); + +/// Tests if a value is a call or invoke to a library function that +/// allocates zero-initialized memory (such as calloc). +bool isZeroAllocFn(const Value *V, const TargetLibraryInfo *TLI, + bool LookThroughBitCast = false); +bool isZeroAllocFn(const Value *V, + function_ref GetTLI, + bool LookThroughBitCast = false); + /// Tests if a value is a call or invoke to a library function that /// allocates uninitialized memory (such as malloc). bool isMallocLikeFn(const Value *V, const TargetLibraryInfo *TLI, diff --git a/llvm/lib/Analysis/MemoryBuiltins.cpp b/llvm/lib/Analysis/MemoryBuiltins.cpp --- a/llvm/lib/Analysis/MemoryBuiltins.cpp +++ b/llvm/lib/Analysis/MemoryBuiltins.cpp @@ -50,15 +50,35 @@ #define DEBUG_TYPE "memory-builtins" enum AllocType : uint8_t { - OpNewLike = 1<<0, // allocates; never returns null - MallocLike = 1<<1 | OpNewLike, // allocates; may return null - AlignedAllocLike = 1<<2, // allocates with alignment; may return null - CallocLike = 1<<3, // allocates + bzero - ReallocLike = 1<<4, // reallocates - StrDupLike = 1<<5, + // Because there is a bit for nullable but not never-null, it is possible to + // ask if a function is never null and if it is either never null or nullable. + // I did this because I ran out of bits and this is how the previous version + // of this enum worked (MallocLike acted as a subtype of OpNewLike) + Nullable = 1 << 0, // can return null on failed allocation (like malloc) + + ArgAlign = 1 << 1, // aligns to argument specified value (like memalign) + OtherAlign = 1 << 2, // alignment not based on argument + + UndefAlloc = 1 << 3, // allocated memory is undefined (like malloc) + ZeroAlloc = 1 << 4, // allocated memory is set to 0 (like calloc) + OtherAlloc = 1 << 5, // allocated memory is defined but not 0 (like strdup) + + Realloc = 1 << 6, // changes an existing allocation size (like realloc) + NotRealloc = 1 << 7, // doesn't reallocate memory + + OpNewLike = OtherAlign | UndefAlloc | NotRealloc, + MallocLike = Nullable | OtherAlign | UndefAlloc | NotRealloc, + AlignedAllocLike = Nullable | ArgAlign | UndefAlloc | NotRealloc, + CallocLike = Nullable | OtherAlign | ZeroAlloc | NotRealloc, + ReallocLike = Nullable | OtherAlign | UndefAlloc | Realloc, + StrDupLike = Nullable | OtherAlign | OtherAlloc | NotRealloc, + MallocOrCallocLike = MallocLike | CallocLike | AlignedAllocLike, - AllocLike = MallocOrCallocLike | StrDupLike, - AnyAlloc = AllocLike | ReallocLike + AllocLike = MallocOrCallocLike | StrDupLike, + AnyAlloc = AllocLike | ReallocLike, + + UndefAllocFn = Nullable | ArgAlign | OtherAlign | UndefAlloc | NotRealloc, + ZeroAllocFn = Nullable | ArgAlign | OtherAlign | ZeroAlloc | NotRealloc, }; struct AllocFnsTy { @@ -258,6 +278,32 @@ hasNoAliasAttr(V, LookThroughBitCast); } +/// Tests if a value is a call or invoke to a library function that +/// allocates uninitialized memory (such as malloc). +bool llvm::isUndefAllocFn(const Value *V, const TargetLibraryInfo *TLI, + bool LookThroughBitCast) { + return getAllocationData(V, UndefAllocFn, TLI, LookThroughBitCast).hasValue(); +} +bool llvm::isUndefAllocFn( + const Value *V, function_ref GetTLI, + bool LookThroughBitCast) { + return getAllocationData(V, UndefAllocFn, GetTLI, LookThroughBitCast) + .hasValue(); +} + +/// Tests if a value is a call or invoke to a library function that +/// allocates zero-initialized memory (such as calloc). +bool llvm::isZeroAllocFn(const Value *V, const TargetLibraryInfo *TLI, + bool LookThroughBitCast) { + return getAllocationData(V, ZeroAllocFn, TLI, LookThroughBitCast).hasValue(); +} +bool llvm::isZeroAllocFn( + const Value *V, function_ref GetTLI, + bool LookThroughBitCast) { + return getAllocationData(V, ZeroAllocFn, GetTLI, LookThroughBitCast) + .hasValue(); +} + /// Tests if a value is a call or invoke to a library function that /// allocates uninitialized memory (such as malloc). bool llvm::isMallocLikeFn(const Value *V, const TargetLibraryInfo *TLI, @@ -313,9 +359,6 @@ bool LookThroughBitCast) { return getAllocationData(V, ReallocLike, TLI, LookThroughBitCast).hasValue(); } - -/// Tests if a functions is a call or invoke to a library function that -/// reallocates memory (e.g., realloc). bool llvm::isReallocLikeFn(const Function *F, const TargetLibraryInfo *TLI) { return getAllocationDataForFunction(F, ReallocLike, TLI).hasValue(); } 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 @@ -208,9 +208,9 @@ if (isa(Obj)) return UndefValue::get(&Ty); if (isNoAliasFn(&Obj, TLI)) { - if (isMallocLikeFn(&Obj, TLI) || isAlignedAllocLikeFn(&Obj, TLI)) + if (isUndefAllocFn(&Obj, TLI)) return UndefValue::get(&Ty); - if (isCallocLikeFn(&Obj, TLI)) + if (isZeroAllocFn(&Obj, TLI)) return Constant::getNullValue(&Ty); return nullptr; } diff --git a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp --- a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp +++ b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp @@ -1716,7 +1716,7 @@ if (StoredConstant && StoredConstant->isNullValue()) { auto *DefUOInst = dyn_cast(DefUO); if (DefUOInst) { - if (isCallocLikeFn(DefUOInst, &TLI)) { + if (isZeroAllocFn(DefUOInst, &TLI)) { auto *UnderlyingDef = cast(MSSA.getMemoryAccess(DefUOInst)); // If UnderlyingDef is the clobbering access of Def, no instructions diff --git a/llvm/lib/Transforms/Scalar/GVN.cpp b/llvm/lib/Transforms/Scalar/GVN.cpp --- a/llvm/lib/Transforms/Scalar/GVN.cpp +++ b/llvm/lib/Transforms/Scalar/GVN.cpp @@ -1105,8 +1105,7 @@ assert(DepInfo.isDef() && "follows from above"); // Loading the allocation -> undef. - if (isa(DepInst) || isMallocLikeFn(DepInst, TLI) || - isAlignedAllocLikeFn(DepInst, TLI) || + if (isa(DepInst) || isUndefAllocFn(DepInst, TLI) || // Loading immediately after lifetime begin -> undef. isLifetimeStart(DepInst)) { Res = AvailableValue::get(UndefValue::get(Load->getType())); @@ -1114,7 +1113,7 @@ } // Loading from calloc (which zero initializes memory) -> zero - if (isCallocLikeFn(DepInst, TLI)) { + if (isZeroAllocFn(DepInst, TLI)) { Res = AvailableValue::get(Constant::getNullValue(Load->getType())); return true; } diff --git a/llvm/lib/Transforms/Scalar/NewGVN.cpp b/llvm/lib/Transforms/Scalar/NewGVN.cpp --- a/llvm/lib/Transforms/Scalar/NewGVN.cpp +++ b/llvm/lib/Transforms/Scalar/NewGVN.cpp @@ -1493,8 +1493,7 @@ // undef value. This can happen when loading for a fresh allocation with no // intervening stores, for example. Note that this is only true in the case // that the result of the allocation is pointer equal to the load ptr. - if (isa(DepInst) || isMallocLikeFn(DepInst, TLI) || - isAlignedAllocLikeFn(DepInst, TLI)) { + if (isa(DepInst) || isUndefAllocFn(DepInst, TLI)) { return createConstantExpression(UndefValue::get(LoadType)); } // If this load occurs either right after a lifetime begin, @@ -1503,9 +1502,8 @@ if (II->getIntrinsicID() == Intrinsic::lifetime_start) return createConstantExpression(UndefValue::get(LoadType)); } - // If this load follows a calloc (which zero initializes memory), - // then the loaded value is zero - else if (isCallocLikeFn(DepInst, TLI)) { + // If this load follows an allocation that returns zeroed memory + else if (isZeroAllocFn(DepInst, TLI)) { return createConstantExpression(Constant::getNullValue(LoadType)); }