mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-26 04:32:44 +01:00
Add, and infer, a nofree function attribute
This patch adds a function attribute, nofree, to indicate that a function does not, directly or indirectly, call a memory-deallocation function (e.g., free, C++'s operator delete). Reviewers: jdoerfert Differential Revision: https://reviews.llvm.org/D49165 llvm-svn: 365336
This commit is contained in:
parent
98551a8d55
commit
ed0ab9dc76
@ -1453,6 +1453,14 @@ example:
|
||||
duplicated by inlining. That implies that the function has
|
||||
internal linkage and only has one call site, so the original
|
||||
call is dead after inlining.
|
||||
``nofree``
|
||||
This function attribute indicates that the function does not, directly or
|
||||
indirectly, call a memory-deallocation function (free, for example). As a
|
||||
result, uncaptured pointers that are known to be dereferenceable prior to a
|
||||
call to a function with the ``nofree`` attribute are still known to be
|
||||
dereferenceable after the call (the capturing condition is necessary in
|
||||
environments where the function might communicate the pointer to another thread
|
||||
which then deallocates the memory).
|
||||
``noimplicitfloat``
|
||||
This attributes disables implicit floating-point instructions.
|
||||
``noinline``
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/SmallPtrSet.h"
|
||||
#include "llvm/Analysis/TargetFolder.h"
|
||||
#include "llvm/Analysis/TargetLibraryInfo.h"
|
||||
#include "llvm/IR/CallSite.h"
|
||||
#include "llvm/IR/IRBuilder.h"
|
||||
#include "llvm/IR/InstVisitor.h"
|
||||
@ -83,6 +84,15 @@ bool isMallocOrCallocLikeFn(const Value *V, const TargetLibraryInfo *TLI,
|
||||
bool isAllocLikeFn(const Value *V, const TargetLibraryInfo *TLI,
|
||||
bool LookThroughBitCast = false);
|
||||
|
||||
/// Tests if a value is a call or invoke to a library function that
|
||||
/// reallocates memory (e.g., realloc).
|
||||
bool isReallocLikeFn(const Value *V, const TargetLibraryInfo *TLI,
|
||||
bool LookThroughBitCast = false);
|
||||
|
||||
/// Tests if a function is a call or invoke to a library function that
|
||||
/// reallocates memory (e.g., realloc).
|
||||
bool isReallocLikeFn(const Function *F, const TargetLibraryInfo *TLI);
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// malloc Call Utility Functions.
|
||||
//
|
||||
@ -134,6 +144,9 @@ inline CallInst *extractCallocCall(Value *I, const TargetLibraryInfo *TLI) {
|
||||
// free Call Utility Functions.
|
||||
//
|
||||
|
||||
/// isLibFreeFunction - Returns true if the function is a builtin free()
|
||||
bool isLibFreeFunction(const Function *F, const LibFunc TLIFn);
|
||||
|
||||
/// isFreeCall - Returns non-null if the value is a call to the builtin free()
|
||||
const CallInst *isFreeCall(const Value *I, const TargetLibraryInfo *TLI);
|
||||
|
||||
|
@ -11,6 +11,15 @@
|
||||
// Which is defined depends on whether TLI_DEFINE_ENUM is defined or
|
||||
// TLI_DEFINE_STRING is defined. Only one should be defined at a time.
|
||||
|
||||
// NOTE: The nofree attribute is added to Libfuncs which are not
|
||||
// listed as free or realloc functions in MemoryBuiltins.cpp
|
||||
//
|
||||
// When adding a function which frees memory include the LibFunc
|
||||
// in lib/Analysis/MemoryBuiltins.cpp "isLibFreeFunction".
|
||||
//
|
||||
// When adding a LibFunc which reallocates memory include the LibFunc
|
||||
// in lib/Analysis/MemoryBuiltins.cpp "AllocationFnData[]".
|
||||
|
||||
#if !(defined(TLI_DEFINE_ENUM) || defined(TLI_DEFINE_STRING))
|
||||
#error "Must define TLI_DEFINE_ENUM or TLI_DEFINE_STRING for TLI .def."
|
||||
#elif defined(TLI_DEFINE_ENUM) && defined(TLI_DEFINE_STRING)
|
||||
|
@ -629,6 +629,7 @@ enum AttributeKindCodes {
|
||||
ATTR_KIND_SPECULATIVE_LOAD_HARDENING = 59,
|
||||
ATTR_KIND_IMMARG = 60,
|
||||
ATTR_KIND_WILLRETURN = 61,
|
||||
ATTR_KIND_NOFREE = 62
|
||||
};
|
||||
|
||||
enum ComdatSelectionKindCodes {
|
||||
|
@ -85,6 +85,9 @@ def NoCapture : EnumAttr<"nocapture">;
|
||||
/// Call cannot be duplicated.
|
||||
def NoDuplicate : EnumAttr<"noduplicate">;
|
||||
|
||||
/// Function does not deallocate memory.
|
||||
def NoFree : EnumAttr<"nofree">;
|
||||
|
||||
/// Disable implicit floating point insts.
|
||||
def NoImplicitFloat : EnumAttr<"noimplicitfloat">;
|
||||
|
||||
|
@ -564,6 +564,14 @@ public:
|
||||
addFnAttr(Attribute::Speculatable);
|
||||
}
|
||||
|
||||
/// Determine if the call might deallocate memory.
|
||||
bool doesNotFreeMemory() const {
|
||||
return onlyReadsMemory() || hasFnAttribute(Attribute::NoFree);
|
||||
}
|
||||
void setDoesNotFreeMemory() {
|
||||
addFnAttr(Attribute::NoFree);
|
||||
}
|
||||
|
||||
/// Determine if the function is known not to recurse, directly or
|
||||
/// indirectly.
|
||||
bool doesNotRecurse() const {
|
||||
|
@ -263,6 +263,19 @@ bool llvm::isAllocLikeFn(const Value *V, const TargetLibraryInfo *TLI,
|
||||
return getAllocationData(V, AllocLike, TLI, LookThroughBitCast).hasValue();
|
||||
}
|
||||
|
||||
/// Tests if a value is a call or invoke to a library function that
|
||||
/// reallocates memory (e.g., realloc).
|
||||
bool llvm::isReallocLikeFn(const Value *V, const TargetLibraryInfo *TLI,
|
||||
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();
|
||||
}
|
||||
|
||||
/// extractMallocCall - Returns the corresponding CallInst if the instruction
|
||||
/// is a malloc call. Since CallInst::CreateMalloc() only creates calls, we
|
||||
/// ignore InvokeInst here.
|
||||
@ -358,19 +371,8 @@ const CallInst *llvm::extractCallocCall(const Value *I,
|
||||
return isCallocLikeFn(I, TLI) ? cast<CallInst>(I) : nullptr;
|
||||
}
|
||||
|
||||
/// isFreeCall - Returns non-null if the value is a call to the builtin free()
|
||||
const CallInst *llvm::isFreeCall(const Value *I, const TargetLibraryInfo *TLI) {
|
||||
bool IsNoBuiltinCall;
|
||||
const Function *Callee =
|
||||
getCalledFunction(I, /*LookThroughBitCast=*/false, IsNoBuiltinCall);
|
||||
if (Callee == nullptr || IsNoBuiltinCall)
|
||||
return nullptr;
|
||||
|
||||
StringRef FnName = Callee->getName();
|
||||
LibFunc TLIFn;
|
||||
if (!TLI || !TLI->getLibFunc(FnName, TLIFn) || !TLI->has(TLIFn))
|
||||
return nullptr;
|
||||
|
||||
/// isLibFreeFunction - Returns true if the function is a builtin free()
|
||||
bool llvm::isLibFreeFunction(const Function *F, const LibFunc TLIFn) {
|
||||
unsigned ExpectedNumParams;
|
||||
if (TLIFn == LibFunc_free ||
|
||||
TLIFn == LibFunc_ZdlPv || // operator delete(void*)
|
||||
@ -401,22 +403,39 @@ const CallInst *llvm::isFreeCall(const Value *I, const TargetLibraryInfo *TLI) {
|
||||
TLIFn == LibFunc_ZdlPvSt11align_val_tRKSt9nothrow_t) // delete[](void*, align_val_t, nothrow)
|
||||
ExpectedNumParams = 3;
|
||||
else
|
||||
return nullptr;
|
||||
return false;
|
||||
|
||||
// Check free prototype.
|
||||
// FIXME: workaround for PR5130, this will be obsolete when a nobuiltin
|
||||
// attribute will exist.
|
||||
FunctionType *FTy = Callee->getFunctionType();
|
||||
FunctionType *FTy = F->getFunctionType();
|
||||
if (!FTy->getReturnType()->isVoidTy())
|
||||
return nullptr;
|
||||
return false;
|
||||
if (FTy->getNumParams() != ExpectedNumParams)
|
||||
return nullptr;
|
||||
if (FTy->getParamType(0) != Type::getInt8PtrTy(Callee->getContext()))
|
||||
return false;
|
||||
if (FTy->getParamType(0) != Type::getInt8PtrTy(F->getContext()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// isFreeCall - Returns non-null if the value is a call to the builtin free()
|
||||
const CallInst *llvm::isFreeCall(const Value *I, const TargetLibraryInfo *TLI) {
|
||||
bool IsNoBuiltinCall;
|
||||
const Function *Callee =
|
||||
getCalledFunction(I, /*LookThroughBitCast=*/false, IsNoBuiltinCall);
|
||||
if (Callee == nullptr || IsNoBuiltinCall)
|
||||
return nullptr;
|
||||
|
||||
return dyn_cast<CallInst>(I);
|
||||
StringRef FnName = Callee->getName();
|
||||
LibFunc TLIFn;
|
||||
if (!TLI || !TLI->getLibFunc(FnName, TLIFn) || !TLI->has(TLIFn))
|
||||
return nullptr;
|
||||
|
||||
return isLibFreeFunction(Callee, TLIFn) ? dyn_cast<CallInst>(I) : nullptr;
|
||||
}
|
||||
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Utility functions to compute size of objects.
|
||||
//
|
||||
|
@ -650,6 +650,7 @@ lltok::Kind LLLexer::LexIdentifier() {
|
||||
KEYWORD(nobuiltin);
|
||||
KEYWORD(nocapture);
|
||||
KEYWORD(noduplicate);
|
||||
KEYWORD(nofree);
|
||||
KEYWORD(noimplicitfloat);
|
||||
KEYWORD(noinline);
|
||||
KEYWORD(norecurse);
|
||||
|
@ -1280,6 +1280,7 @@ bool LLParser::ParseFnAttributeValuePairs(AttrBuilder &B,
|
||||
case lltok::kw_naked: B.addAttribute(Attribute::Naked); break;
|
||||
case lltok::kw_nobuiltin: B.addAttribute(Attribute::NoBuiltin); break;
|
||||
case lltok::kw_noduplicate: B.addAttribute(Attribute::NoDuplicate); break;
|
||||
case lltok::kw_nofree: B.addAttribute(Attribute::NoFree); break;
|
||||
case lltok::kw_noimplicitfloat:
|
||||
B.addAttribute(Attribute::NoImplicitFloat); break;
|
||||
case lltok::kw_noinline: B.addAttribute(Attribute::NoInline); break;
|
||||
|
@ -195,6 +195,7 @@ enum Kind {
|
||||
kw_nobuiltin,
|
||||
kw_nocapture,
|
||||
kw_noduplicate,
|
||||
kw_nofree,
|
||||
kw_noimplicitfloat,
|
||||
kw_noinline,
|
||||
kw_norecurse,
|
||||
|
@ -1278,6 +1278,8 @@ static uint64_t getRawAttributeMask(Attribute::AttrKind Val) {
|
||||
return 1ULL << 61;
|
||||
case Attribute::WillReturn:
|
||||
return 1ULL << 62;
|
||||
case Attribute::NoFree:
|
||||
return 1ULL << 63;
|
||||
case Attribute::Dereferenceable:
|
||||
llvm_unreachable("dereferenceable attribute not supported in raw format");
|
||||
break;
|
||||
@ -1442,6 +1444,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
|
||||
return Attribute::NoCapture;
|
||||
case bitc::ATTR_KIND_NO_DUPLICATE:
|
||||
return Attribute::NoDuplicate;
|
||||
case bitc::ATTR_KIND_NOFREE:
|
||||
return Attribute::NoFree;
|
||||
case bitc::ATTR_KIND_NO_IMPLICIT_FLOAT:
|
||||
return Attribute::NoImplicitFloat;
|
||||
case bitc::ATTR_KIND_NO_INLINE:
|
||||
|
@ -639,6 +639,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
|
||||
return bitc::ATTR_KIND_NO_CAPTURE;
|
||||
case Attribute::NoDuplicate:
|
||||
return bitc::ATTR_KIND_NO_DUPLICATE;
|
||||
case Attribute::NoFree:
|
||||
return bitc::ATTR_KIND_NOFREE;
|
||||
case Attribute::NoImplicitFloat:
|
||||
return bitc::ATTR_KIND_NO_IMPLICIT_FLOAT;
|
||||
case Attribute::NoInline:
|
||||
|
@ -321,6 +321,8 @@ std::string Attribute::getAsString(bool InAttrGrp) const {
|
||||
return "nocapture";
|
||||
if (hasAttribute(Attribute::NoDuplicate))
|
||||
return "noduplicate";
|
||||
if (hasAttribute(Attribute::NoFree))
|
||||
return "nofree";
|
||||
if (hasAttribute(Attribute::NoImplicitFloat))
|
||||
return "noimplicitfloat";
|
||||
if (hasAttribute(Attribute::NoInline))
|
||||
|
@ -1497,6 +1497,7 @@ static bool isFuncOnlyAttr(Attribute::AttrKind Kind) {
|
||||
case Attribute::NoCfCheck:
|
||||
case Attribute::NoUnwind:
|
||||
case Attribute::NoInline:
|
||||
case Attribute::NoFree:
|
||||
case Attribute::AlwaysInline:
|
||||
case Attribute::OptimizeForSize:
|
||||
case Attribute::StackProtect:
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "llvm/Analysis/CallGraphSCCPass.h"
|
||||
#include "llvm/Analysis/CaptureTracking.h"
|
||||
#include "llvm/Analysis/LazyCallGraph.h"
|
||||
#include "llvm/Analysis/MemoryBuiltins.h"
|
||||
#include "llvm/Analysis/MemoryLocation.h"
|
||||
#include "llvm/Analysis/ValueTracking.h"
|
||||
#include "llvm/IR/Argument.h"
|
||||
@ -75,6 +76,7 @@ STATISTIC(NumNoAlias, "Number of function returns marked noalias");
|
||||
STATISTIC(NumNonNullReturn, "Number of function returns marked nonnull");
|
||||
STATISTIC(NumNoRecurse, "Number of functions marked as norecurse");
|
||||
STATISTIC(NumNoUnwind, "Number of functions marked as nounwind");
|
||||
STATISTIC(NumNoFree, "Number of functions marked as nofree");
|
||||
|
||||
// FIXME: This is disabled by default to avoid exposing security vulnerabilities
|
||||
// in C/C++ code compiled by clang:
|
||||
@ -88,6 +90,10 @@ static cl::opt<bool> DisableNoUnwindInference(
|
||||
"disable-nounwind-inference", cl::Hidden,
|
||||
cl::desc("Stop inferring nounwind attribute during function-attrs pass"));
|
||||
|
||||
static cl::opt<bool> DisableNoFreeInference(
|
||||
"disable-nofree-inference", cl::Hidden,
|
||||
cl::desc("Stop inferring nofree attribute during function-attrs pass"));
|
||||
|
||||
namespace {
|
||||
|
||||
using SCCNodeSet = SmallSetVector<Function *, 8>;
|
||||
@ -1227,6 +1233,25 @@ static bool InstrBreaksNonThrowing(Instruction &I, const SCCNodeSet &SCCNodes) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Helper for NoFree inference predicate InstrBreaksAttribute.
|
||||
static bool InstrBreaksNoFree(Instruction &I, const SCCNodeSet &SCCNodes) {
|
||||
CallSite CS(&I);
|
||||
if (!CS)
|
||||
return false;
|
||||
|
||||
Function *Callee = CS.getCalledFunction();
|
||||
if (!Callee)
|
||||
return true;
|
||||
|
||||
if (Callee->doesNotFreeMemory())
|
||||
return false;
|
||||
|
||||
if (SCCNodes.count(Callee) > 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Infer attributes from all functions in the SCC by scanning every
|
||||
/// instruction for compliance to the attribute assumptions. Currently it
|
||||
/// does:
|
||||
@ -1280,6 +1305,29 @@ static bool inferAttrsFromFunctionBodies(const SCCNodeSet &SCCNodes) {
|
||||
},
|
||||
/* RequiresExactDefinition= */ true});
|
||||
|
||||
if (!DisableNoFreeInference)
|
||||
// Request to infer nofree attribute for all the functions in the SCC if
|
||||
// every callsite within the SCC does not directly or indirectly free
|
||||
// memory (except for calls to functions within the SCC). Note that nofree
|
||||
// attribute suffers from derefinement - results may change depending on
|
||||
// how functions are optimized. Thus it can be inferred only from exact
|
||||
// definitions.
|
||||
AI.registerAttrInference(AttributeInferer::InferenceDescriptor{
|
||||
Attribute::NoFree,
|
||||
// Skip functions known not to free memory.
|
||||
[](const Function &F) { return F.doesNotFreeMemory(); },
|
||||
// Instructions that break non-deallocating assumption.
|
||||
[SCCNodes](Instruction &I) {
|
||||
return InstrBreaksNoFree(I, SCCNodes);
|
||||
},
|
||||
[](Function &F) {
|
||||
LLVM_DEBUG(dbgs()
|
||||
<< "Adding nofree attr to fn " << F.getName() << "\n");
|
||||
F.setDoesNotFreeMemory();
|
||||
++NumNoFree;
|
||||
},
|
||||
/* RequiresExactDefinition= */ true});
|
||||
|
||||
// Perform all the requested attribute inference actions.
|
||||
return AI.run(SCCNodes);
|
||||
}
|
||||
@ -1322,7 +1370,8 @@ static bool addNoRecurseAttrs(const SCCNodeSet &SCCNodes) {
|
||||
}
|
||||
|
||||
template <typename AARGetterT>
|
||||
static bool deriveAttrsInPostOrder(SCCNodeSet &SCCNodes, AARGetterT &&AARGetter,
|
||||
static bool deriveAttrsInPostOrder(SCCNodeSet &SCCNodes,
|
||||
AARGetterT &&AARGetter,
|
||||
bool HasUnknownCall) {
|
||||
bool Changed = false;
|
||||
|
||||
@ -1353,6 +1402,11 @@ PreservedAnalyses PostOrderFunctionAttrsPass::run(LazyCallGraph::SCC &C,
|
||||
FunctionAnalysisManager &FAM =
|
||||
AM.getResult<FunctionAnalysisManagerCGSCCProxy>(C, CG).getManager();
|
||||
|
||||
const ModuleAnalysisManager &MAM =
|
||||
AM.getResult<ModuleAnalysisManagerCGSCCProxy>(C, CG).getManager();
|
||||
assert(C.size() > 0 && "Cannot handle an empty SCC!");
|
||||
Module &M = *C.begin()->getFunction().getParent();
|
||||
|
||||
// We pass a lambda into functions to wire them up to the analysis manager
|
||||
// for getting function analyses.
|
||||
auto AARGetter = [&](Function &F) -> AAResults & {
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "llvm/IR/LLVMContext.h"
|
||||
#include "llvm/IR/Module.h"
|
||||
#include "llvm/IR/Type.h"
|
||||
#include "llvm/Analysis/MemoryBuiltins.h"
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
@ -120,6 +121,13 @@ static bool setNonLazyBind(Function &F) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setDoesNotFreeMemory(Function &F) {
|
||||
if (F.hasFnAttribute(Attribute::NoFree))
|
||||
return false;
|
||||
F.addFnAttr(Attribute::NoFree);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool llvm::inferLibFuncAttributes(Module *M, StringRef Name,
|
||||
const TargetLibraryInfo &TLI) {
|
||||
Function *F = M->getFunction(Name);
|
||||
@ -135,6 +143,9 @@ bool llvm::inferLibFuncAttributes(Function &F, const TargetLibraryInfo &TLI) {
|
||||
|
||||
bool Changed = false;
|
||||
|
||||
if(!isLibFreeFunction(&F, TheLibFunc) && !isReallocLikeFn(&F, &TLI))
|
||||
Changed |= setDoesNotFreeMemory(F);
|
||||
|
||||
if (F.getParent() != nullptr && F.getParent()->getRtLibUseGOT())
|
||||
Changed |= setNonLazyBind(F);
|
||||
|
||||
|
@ -814,6 +814,7 @@ Function *CodeExtractor::constructFunction(const ValueSet &inputs,
|
||||
case Attribute::InlineHint:
|
||||
case Attribute::MinSize:
|
||||
case Attribute::NoDuplicate:
|
||||
case Attribute::NoFree:
|
||||
case Attribute::NoImplicitFloat:
|
||||
case Attribute::NoInline:
|
||||
case Attribute::NonLazyBind:
|
||||
|
@ -73,11 +73,11 @@ declare void @callee(i32* %p) nounwind
|
||||
declare void @llvm.memcpy.p0i8.p0i8.i64(i8*, i8*, i64, i1) nounwind
|
||||
|
||||
; CHECK: attributes #0 = { norecurse nounwind readnone }
|
||||
; CHECK: attributes #1 = { norecurse nounwind writeonly }
|
||||
; CHECK: attributes #1 = { nofree norecurse nounwind writeonly }
|
||||
; CHECK: attributes #2 = { nounwind readonly }
|
||||
; CHECK: attributes #3 = { nounwind }
|
||||
; CHECK: attributes #4 = { nounwind readnone }
|
||||
; CHECK: attributes #5 = { norecurse nounwind }
|
||||
; CHECK: attributes #5 = { nofree norecurse nounwind }
|
||||
; CHECK: attributes #6 = { argmemonly nounwind }
|
||||
|
||||
; Root note.
|
||||
|
@ -204,7 +204,7 @@ define void @f34()
|
||||
; CHECK: define void @f34()
|
||||
{
|
||||
call void @nobuiltin() nobuiltin
|
||||
; CHECK: call void @nobuiltin() #37
|
||||
; CHECK: call void @nobuiltin() #38
|
||||
ret void;
|
||||
}
|
||||
|
||||
@ -357,6 +357,11 @@ define void @f60() willreturn
|
||||
ret void
|
||||
}
|
||||
|
||||
; CHECK: define void @f61() #37
|
||||
define void @f61() nofree {
|
||||
ret void
|
||||
}
|
||||
|
||||
; CHECK: attributes #0 = { noreturn }
|
||||
; CHECK: attributes #1 = { nounwind }
|
||||
; CHECK: attributes #2 = { readnone }
|
||||
@ -394,4 +399,5 @@ define void @f60() willreturn
|
||||
; CHECK: attributes #34 = { sanitize_hwaddress }
|
||||
; CHECK: attributes #35 = { shadowcallstack }
|
||||
; CHECK: attributes #36 = { willreturn }
|
||||
; CHECK: attributes #37 = { nobuiltin }
|
||||
; CHECK: attributes #37 = { nofree }
|
||||
; CHECK: attributes #38 = { nobuiltin }
|
||||
|
@ -7,13 +7,13 @@
|
||||
; GCN: %mul.i = fmul float %load, 1.500000e+01
|
||||
|
||||
; UNSAFE: attributes #0 = { norecurse nounwind readnone "less-precise-fpmad"="true" "no-infs-fp-math"="true" "no-nans-fp-math"="true" "unsafe-fp-math"="true" }
|
||||
; UNSAFE: attributes #1 = { norecurse nounwind "less-precise-fpmad"="true" "no-infs-fp-math"="true" "no-nans-fp-math"="true" "unsafe-fp-math"="true" }
|
||||
; UNSAFE: attributes #1 = { nofree norecurse nounwind "less-precise-fpmad"="true" "no-infs-fp-math"="true" "no-nans-fp-math"="true" "unsafe-fp-math"="true" }
|
||||
|
||||
; NOINFS: attributes #0 = { norecurse nounwind readnone "no-infs-fp-math"="true" }
|
||||
; NOINFS: attributes #1 = { norecurse nounwind "less-precise-fpmad"="false" "no-infs-fp-math"="true" "no-nans-fp-math"="false" "unsafe-fp-math"="false" }
|
||||
; NOINFS: attributes #1 = { nofree norecurse nounwind "less-precise-fpmad"="false" "no-infs-fp-math"="true" "no-nans-fp-math"="false" "unsafe-fp-math"="false" }
|
||||
|
||||
; NONANS: attributes #0 = { norecurse nounwind readnone "no-nans-fp-math"="true" }
|
||||
; NONANS: attributes #1 = { norecurse nounwind "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="true" "unsafe-fp-math"="false" }
|
||||
; NONANS: attributes #1 = { nofree norecurse nounwind "less-precise-fpmad"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="true" "unsafe-fp-math"="false" }
|
||||
|
||||
define float @foo(float %x) #0 {
|
||||
entry:
|
||||
|
@ -12,7 +12,7 @@ define void @printf_call() {
|
||||
ret void
|
||||
}
|
||||
|
||||
; CHECK: Function Attrs: nounwind nonlazybind
|
||||
; CHECK: Function Attrs: nofree nounwind nonlazybind
|
||||
; CHECK-NEXT: declare i32 @puts(i8* nocapture readonly)
|
||||
|
||||
!llvm.module.flags = !{!0}
|
||||
|
@ -6,7 +6,7 @@ declare void @f_readnone() readnone
|
||||
define void @test_0(i32* %x) {
|
||||
; FunctionAttrs must not infer readonly / readnone for %x
|
||||
|
||||
; CHECK-LABEL: define void @test_0(i32* %x) {
|
||||
; CHECK-LABEL: define void @test_0(i32* %x) #2 {
|
||||
entry:
|
||||
; CHECK: call void @f_readonly() [ "foo"(i32* %x) ]
|
||||
call void @f_readonly() [ "foo"(i32* %x) ]
|
||||
@ -16,7 +16,7 @@ define void @test_0(i32* %x) {
|
||||
define void @test_1(i32* %x) {
|
||||
; FunctionAttrs must not infer readonly / readnone for %x
|
||||
|
||||
; CHECK-LABEL: define void @test_1(i32* %x) {
|
||||
; CHECK-LABEL: define void @test_1(i32* %x) #2 {
|
||||
entry:
|
||||
; CHECK: call void @f_readnone() [ "foo"(i32* %x) ]
|
||||
call void @f_readnone() [ "foo"(i32* %x) ]
|
||||
@ -31,3 +31,6 @@ define void @test_2(i32* %x) {
|
||||
call void @f_readonly() [ "deopt"(i32* %x) ]
|
||||
ret void
|
||||
}
|
||||
|
||||
; CHECK: attributes #2 = { nofree }
|
||||
|
||||
|
@ -21,4 +21,4 @@ entry:
|
||||
}
|
||||
|
||||
; CHECK: attributes #0 = { norecurse nounwind readnone ssp uwtable }
|
||||
; CHECK: attributes #1 = { norecurse nounwind ssp uwtable }
|
||||
; CHECK: attributes #1 = { nofree norecurse nounwind ssp uwtable }
|
||||
|
113
test/Transforms/FunctionAttrs/nofree.ll
Normal file
113
test/Transforms/FunctionAttrs/nofree.ll
Normal file
@ -0,0 +1,113 @@
|
||||
; RUN: opt < %s -functionattrs -S | FileCheck %s
|
||||
; RUN: opt < %s -passes=function-attrs -S | FileCheck %s
|
||||
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64-unknown-linux-gnu"
|
||||
|
||||
; CHECK: define void @_Z4foo1Pi(i32* nocapture readnone %a) local_unnamed_addr #0 {
|
||||
define void @_Z4foo1Pi(i32* nocapture readnone %a) local_unnamed_addr #0 {
|
||||
entry:
|
||||
tail call void @_Z3extv()
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @_Z3extv() local_unnamed_addr
|
||||
|
||||
; CHECK: define void @_Z4foo2Pi(i32* nocapture %a) local_unnamed_addr #1 {
|
||||
define void @_Z4foo2Pi(i32* nocapture %a) local_unnamed_addr #1 {
|
||||
entry:
|
||||
%0 = bitcast i32* %a to i8*
|
||||
tail call void @free(i8* %0) #2
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @free(i8* nocapture) local_unnamed_addr #2
|
||||
|
||||
; CHECK: define i32 @_Z4foo3Pi(i32* nocapture readonly %a) local_unnamed_addr #3 {
|
||||
define i32 @_Z4foo3Pi(i32* nocapture readonly %a) local_unnamed_addr #3 {
|
||||
entry:
|
||||
%0 = load i32, i32* %a, align 4
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
; CHECK: define double @_Z4foo4Pd(double* nocapture readonly %a) local_unnamed_addr #1 {
|
||||
define double @_Z4foo4Pd(double* nocapture readonly %a) local_unnamed_addr #1 {
|
||||
entry:
|
||||
%0 = load double, double* %a, align 8
|
||||
%call = tail call double @cos(double %0) #2
|
||||
ret double %call
|
||||
}
|
||||
|
||||
declare double @cos(double) local_unnamed_addr #2
|
||||
|
||||
; CHECK: define noalias i32* @_Z4foo5Pm(i64* nocapture readonly %a) local_unnamed_addr #1 {
|
||||
define noalias i32* @_Z4foo5Pm(i64* nocapture readonly %a) local_unnamed_addr #1 {
|
||||
entry:
|
||||
%0 = load i64, i64* %a, align 8
|
||||
%call = tail call noalias i8* @malloc(i64 %0) #2
|
||||
%1 = bitcast i8* %call to i32*
|
||||
ret i32* %1
|
||||
}
|
||||
|
||||
declare noalias i8* @malloc(i64) local_unnamed_addr #2
|
||||
|
||||
; CHECK: define noalias i64* @_Z4foo6Pm(i64* nocapture %a) local_unnamed_addr #1 {
|
||||
define noalias i64* @_Z4foo6Pm(i64* nocapture %a) local_unnamed_addr #1 {
|
||||
entry:
|
||||
%0 = bitcast i64* %a to i8*
|
||||
%1 = load i64, i64* %a, align 8
|
||||
%call = tail call i8* @realloc(i8* %0, i64 %1) #2
|
||||
%2 = bitcast i8* %call to i64*
|
||||
ret i64* %2
|
||||
}
|
||||
|
||||
declare noalias i8* @realloc(i8* nocapture, i64) local_unnamed_addr #2
|
||||
|
||||
; CHECK: define void @_Z4foo7Pi(i32* %a) local_unnamed_addr #1 {
|
||||
define void @_Z4foo7Pi(i32* %a) local_unnamed_addr #1 {
|
||||
entry:
|
||||
%isnull = icmp eq i32* %a, null
|
||||
br i1 %isnull, label %delete.end, label %delete.notnull
|
||||
|
||||
delete.notnull: ; preds = %entry
|
||||
%0 = bitcast i32* %a to i8*
|
||||
tail call void @_ZdlPv(i8* %0) #5
|
||||
br label %delete.end
|
||||
|
||||
delete.end: ; preds = %delete.notnull, %entry
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @_ZdlPv(i8*) local_unnamed_addr #4
|
||||
|
||||
; CHECK: define void @_Z4foo8Pi(i32* %a) local_unnamed_addr #1 {
|
||||
define void @_Z4foo8Pi(i32* %a) local_unnamed_addr #1 {
|
||||
entry:
|
||||
%isnull = icmp eq i32* %a, null
|
||||
br i1 %isnull, label %delete.end, label %delete.notnull
|
||||
|
||||
delete.notnull: ; preds = %entry
|
||||
%0 = bitcast i32* %a to i8*
|
||||
tail call void @_ZdaPv(i8* %0) #5
|
||||
br label %delete.end
|
||||
|
||||
delete.end: ; preds = %delete.notnull, %entry
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @_ZdaPv(i8*) local_unnamed_addr #4
|
||||
|
||||
attributes #0 = { uwtable }
|
||||
attributes #1 = { nounwind uwtable }
|
||||
attributes #2 = { nounwind }
|
||||
attributes #3 = { norecurse nounwind readonly uwtable }
|
||||
attributes #4 = { nobuiltin nounwind }
|
||||
attributes #5 = { builtin nounwind }
|
||||
|
||||
; CHECK: attributes #0 = { uwtable }
|
||||
; CHECK: attributes #1 = { nounwind uwtable }
|
||||
; CHECK: attributes #2 = { nounwind }
|
||||
; CHECK: attributes #3 = { norecurse nounwind readonly uwtable }
|
||||
; CHECK: attributes #4 = { nobuiltin nounwind }
|
||||
; CHECK: attributes #5 = { builtin nounwind }
|
||||
|
@ -14,4 +14,4 @@ define void @g() {
|
||||
}
|
||||
|
||||
|
||||
; CHECK: attributes #0 = { nounwind }
|
||||
; CHECK: attributes #0 = { nofree nounwind }
|
||||
|
@ -30,7 +30,7 @@
|
||||
;
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
|
||||
; CHECK: Function Attrs: nounwind
|
||||
; CHECK: Function Attrs: nofree nounwind
|
||||
; CHECK-NEXT: define i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
|
||||
define i32* @external_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) {
|
||||
entry:
|
||||
@ -41,7 +41,7 @@ entry:
|
||||
ret i32* %call3
|
||||
}
|
||||
|
||||
; CHECK: Function Attrs: nounwind
|
||||
; CHECK: Function Attrs: nofree nounwind
|
||||
; CHECK-NEXT: define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0)
|
||||
define internal i32* @internal_ret0_nw(i32* %n0, i32* %w0) {
|
||||
entry:
|
||||
@ -70,7 +70,7 @@ return: ; preds = %if.end, %if.then
|
||||
ret i32* %retval.0
|
||||
}
|
||||
|
||||
; CHECK: Function Attrs: nounwind
|
||||
; CHECK: Function Attrs: nofree nounwind
|
||||
; CHECK-NEXT: define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0)
|
||||
define internal i32* @internal_ret1_rrw(i32* %r0, i32* %r1, i32* %w0) {
|
||||
entry:
|
||||
@ -102,7 +102,7 @@ return: ; preds = %if.end, %if.then
|
||||
ret i32* %retval.0
|
||||
}
|
||||
|
||||
; CHECK: Function Attrs: norecurse nounwind
|
||||
; CHECK: Function Attrs: nofree norecurse nounwind
|
||||
; CHECK-NEXT: define i32* @external_sink_ret2_nrw(i32* readnone %n0, i32* nocapture readonly %r0, i32* returned %w0)
|
||||
define i32* @external_sink_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) {
|
||||
entry:
|
||||
@ -121,7 +121,7 @@ return: ; preds = %if.end, %if.then
|
||||
ret i32* %w0
|
||||
}
|
||||
|
||||
; CHECK: Function Attrs: nounwind
|
||||
; CHECK: Function Attrs: nofree nounwind
|
||||
; CHECK-NEXT: define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0)
|
||||
define internal i32* @internal_ret1_rw(i32* %r0, i32* %w0) {
|
||||
entry:
|
||||
@ -147,7 +147,7 @@ return: ; preds = %if.end, %if.then
|
||||
ret i32* %retval.0
|
||||
}
|
||||
|
||||
; CHECK: Function Attrs: nounwind
|
||||
; CHECK: Function Attrs: nofree nounwind
|
||||
; CHECK-NEXT: define i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* %w0)
|
||||
define i32* @external_source_ret2_nrw(i32* %n0, i32* %r0, i32* %w0) {
|
||||
entry:
|
||||
@ -160,6 +160,6 @@ entry:
|
||||
; for a subset relation.
|
||||
;
|
||||
; CHECK-NOT: attributes #
|
||||
; CHECK: attributes #{{.*}} = { nounwind }
|
||||
; CHECK: attributes #{{.*}} = { norecurse nounwind }
|
||||
; CHECK: attributes #{{.*}} = { nofree nounwind }
|
||||
; CHECK: attributes #{{.*}} = { nofree norecurse nounwind }
|
||||
; CHECK-NOT: attributes #
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -52,5 +52,5 @@ attributes #1 = { nounwind readnone speculatable }
|
||||
!28 = !DILocation(line: 9, column: 18, scope: !2)
|
||||
!29 = !DILocation(line: 10, column: 1, scope: !2)
|
||||
|
||||
; CHECK: attributes #0 = { norecurse nounwind }
|
||||
; CHECK: attributes #0 = { nofree norecurse nounwind }
|
||||
; CHECK-NOT foo.coefficient1
|
||||
|
@ -13,7 +13,7 @@ loop:
|
||||
}
|
||||
|
||||
; CHECK: declare i64 @strlen(i8* nocapture) #0
|
||||
; CHECK: attributes #0 = { argmemonly nounwind readonly }
|
||||
; CHECK: attributes #0 = { argmemonly nofree nounwind readonly }
|
||||
declare i64 @strlen(i8*)
|
||||
|
||||
|
||||
|
@ -709,4 +709,4 @@ exit:
|
||||
|
||||
; Validate that "memset_pattern" has the proper attributes.
|
||||
; CHECK: declare void @memset_pattern16(i8* nocapture, i8* nocapture readonly, i64) [[ATTRS:#[0-9]+]]
|
||||
; CHECK: [[ATTRS]] = { argmemonly }
|
||||
; CHECK: [[ATTRS]] = { argmemonly nofree }
|
||||
|
Loading…
Reference in New Issue
Block a user