Skip to content

Commit

Permalink
Add the allocsize attribute to LLVM.
Browse files Browse the repository at this point in the history
`allocsize` is a function attribute that allows users to request that
LLVM treat arbitrary functions as allocation functions.

This patch makes LLVM accept the `allocsize` attribute, and makes
`@llvm.objectsize` recognize said attribute.

The review for this was split into two patches for ease of reviewing:
D18974 and D14933. As promised on the revisions, I'm landing both
patches as a single commit.

Differential Revision: http://reviews.llvm.org/D14933

llvm-svn: 266032
  • Loading branch information
gburgessiv committed Apr 12, 2016
1 parent b40d14f commit 278199f
Show file tree
Hide file tree
Showing 20 changed files with 577 additions and 71 deletions.
9 changes: 9 additions & 0 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1269,6 +1269,15 @@ example:
epilogue, the backend should forcibly align the stack pointer.
Specify the desired alignment, which must be a power of two, in
parentheses.
``allocsize(<EltSizeParam>[, <NumEltsParam>])``
This attribute indicates that the annotated function will always return at
least a given number of bytes (or null). Its arguments are zero-indexed
parameter numbers; if one argument is provided, then it's assumed that at
least ``CallSite.Args[EltSizeParam]`` bytes will be available at the
returned pointer. If two are provided, then it's assumed that
``CallSite.Args[EltSizeParam] * CallSite.Args[NumEltsParam]`` bytes are
available. The referenced parameters must be integer types. No assumptions
are made about the contents of the returned block of memory.
``alwaysinline``
This attribute indicates that the inliner should attempt to inline
this function into callers whenever possible, ignoring any active
Expand Down
3 changes: 2 additions & 1 deletion llvm/include/llvm/Bitcode/LLVMBitCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,8 @@ enum AttributeKindCodes {
ATTR_KIND_SWIFT_ERROR = 47,
ATTR_KIND_NO_RECURSE = 48,
ATTR_KIND_INACCESSIBLEMEM_ONLY = 49,
ATTR_KIND_INACCESSIBLEMEM_OR_ARGMEMONLY = 50
ATTR_KIND_INACCESSIBLEMEM_OR_ARGMEMONLY = 50,
ATTR_KIND_ALLOC_SIZE = 51
};

enum ComdatSelectionKindCodes {
Expand Down
37 changes: 34 additions & 3 deletions llvm/include/llvm/IR/Attributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/FoldingSet.h"
#include "llvm/ADT/Optional.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/PointerLikeTypeTraits.h"
#include <bitset>
Expand Down Expand Up @@ -94,6 +95,9 @@ class Attribute {
uint64_t Bytes);
static Attribute getWithDereferenceableOrNullBytes(LLVMContext &Context,
uint64_t Bytes);
static Attribute getWithAllocSizeArgs(LLVMContext &Context,
unsigned ElemSizeArg,
const Optional<unsigned> &NumElemsArg);

//===--------------------------------------------------------------------===//
// Attribute Accessors
Expand Down Expand Up @@ -147,6 +151,10 @@ class Attribute {
/// dereferenceable_or_null attribute.
uint64_t getDereferenceableOrNullBytes() const;

/// Returns the argument numbers for the allocsize attribute (or pair(0, 0)
/// if not known).
std::pair<unsigned, Optional<unsigned>> getAllocSizeArgs() const;

/// \brief The Attribute is converted to a string of equivalent mnemonic. This
/// is, presumably, for writing out the mnemonics for the assembly writer.
std::string getAsString(bool InAttrGrp = false) const;
Expand Down Expand Up @@ -267,6 +275,12 @@ class AttributeSet {
AttributeSet addDereferenceableOrNullAttr(LLVMContext &C, unsigned Index,
uint64_t Bytes) const;

/// Add the allocsize attribute to the attribute set at the given index.
/// Because attribute sets are immutable, this returns a new set.
AttributeSet addAllocSizeAttr(LLVMContext &C, unsigned Index,
unsigned ElemSizeArg,
const Optional<unsigned> &NumElemsArg);

//===--------------------------------------------------------------------===//
// AttributeSet Accessors
//===--------------------------------------------------------------------===//
Expand Down Expand Up @@ -319,6 +333,10 @@ class AttributeSet {
/// unknown).
uint64_t getDereferenceableOrNullBytes(unsigned Index) const;

/// Get the allocsize argument numbers (or pair(0, 0) if unknown).
std::pair<unsigned, Optional<unsigned>>
getAllocSizeArgs(unsigned Index) const;

/// \brief Return the attributes at the index as a string.
std::string getAsString(unsigned Index, bool InAttrGrp = false) const;

Expand Down Expand Up @@ -400,19 +418,20 @@ class AttrBuilder {
uint64_t StackAlignment;
uint64_t DerefBytes;
uint64_t DerefOrNullBytes;
uint64_t AllocSizeArgs;

public:
AttrBuilder()
: Attrs(0), Alignment(0), StackAlignment(0), DerefBytes(0),
DerefOrNullBytes(0) {}
DerefOrNullBytes(0), AllocSizeArgs(0) {}
explicit AttrBuilder(uint64_t Val)
: Attrs(0), Alignment(0), StackAlignment(0), DerefBytes(0),
DerefOrNullBytes(0) {
DerefOrNullBytes(0), AllocSizeArgs(0) {
addRawValue(Val);
}
AttrBuilder(const Attribute &A)
: Attrs(0), Alignment(0), StackAlignment(0), DerefBytes(0),
DerefOrNullBytes(0) {
DerefOrNullBytes(0), AllocSizeArgs(0) {
addAttribute(A);
}
AttrBuilder(AttributeSet AS, unsigned Idx);
Expand Down Expand Up @@ -481,6 +500,10 @@ class AttrBuilder {
/// dereferenceable_or_null attribute exists (zero is returned otherwise).
uint64_t getDereferenceableOrNullBytes() const { return DerefOrNullBytes; }

/// Retrieve the allocsize args, if the allocsize attribute exists. If it
/// doesn't exist, pair(0, 0) is returned.
std::pair<unsigned, Optional<unsigned>> getAllocSizeArgs() const;

/// \brief This turns an int alignment (which must be a power of 2) into the
/// form used internally in Attribute.
AttrBuilder &addAlignmentAttr(unsigned Align);
Expand All @@ -497,6 +520,14 @@ class AttrBuilder {
/// form used internally in Attribute.
AttrBuilder &addDereferenceableOrNullAttr(uint64_t Bytes);

/// This turns one (or two) ints into the form used internally in Attribute.
AttrBuilder &addAllocSizeAttr(unsigned ElemSizeArg,
const Optional<unsigned> &NumElemsArg);

/// Add an allocsize attribute, using the representation returned by
/// Attribute.getIntValue().
AttrBuilder &addAllocSizeAttrFromRawRepr(uint64_t RawAllocSizeRepr);

/// \brief Return true if the builder contains no target-independent
/// attributes.
bool empty() const { return Attrs.none(); }
Expand Down
4 changes: 4 additions & 0 deletions llvm/include/llvm/IR/Attributes.td
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ class StrBoolAttr<string S> : Attr<S>;
/// 0 means unaligned (different from align(1)).
def Alignment : EnumAttr<"align">;

/// The result of the function is guaranteed to point to a number of bytes that
/// we can determine if we know the value of the function's arguments.
def AllocSize : EnumAttr<"allocsize">;

/// inline=always.
def AlwaysInline : EnumAttr<"alwaysinline">;

Expand Down
156 changes: 101 additions & 55 deletions llvm/lib/Analysis/MemoryBuiltins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,39 +42,38 @@ enum AllocType : uint8_t {
};

struct AllocFnsTy {
LibFunc::Func Func;
AllocType AllocTy;
unsigned char NumParams;
unsigned NumParams;
// First and Second size parameters (or -1 if unused)
signed char FstParam, SndParam;
int FstParam, SndParam;
};

// FIXME: certain users need more information. E.g., SimplifyLibCalls needs to
// know which functions are nounwind, noalias, nocapture parameters, etc.
static const AllocFnsTy AllocationFnData[] = {
{LibFunc::malloc, MallocLike, 1, 0, -1},
{LibFunc::valloc, MallocLike, 1, 0, -1},
{LibFunc::Znwj, OpNewLike, 1, 0, -1}, // new(unsigned int)
{LibFunc::ZnwjRKSt9nothrow_t, MallocLike, 2, 0, -1}, // new(unsigned int, nothrow)
{LibFunc::Znwm, OpNewLike, 1, 0, -1}, // new(unsigned long)
{LibFunc::ZnwmRKSt9nothrow_t, MallocLike, 2, 0, -1}, // new(unsigned long, nothrow)
{LibFunc::Znaj, OpNewLike, 1, 0, -1}, // new[](unsigned int)
{LibFunc::ZnajRKSt9nothrow_t, MallocLike, 2, 0, -1}, // new[](unsigned int, nothrow)
{LibFunc::Znam, OpNewLike, 1, 0, -1}, // new[](unsigned long)
{LibFunc::ZnamRKSt9nothrow_t, MallocLike, 2, 0, -1}, // new[](unsigned long, nothrow)
{LibFunc::msvc_new_int, OpNewLike, 1, 0, -1}, // new(unsigned int)
{LibFunc::msvc_new_int_nothrow, MallocLike, 2, 0, -1}, // new(unsigned int, nothrow)
{LibFunc::msvc_new_longlong, OpNewLike, 1, 0, -1}, // new(unsigned long long)
{LibFunc::msvc_new_longlong_nothrow, MallocLike, 2, 0, -1}, // new(unsigned long long, nothrow)
{LibFunc::msvc_new_array_int, OpNewLike, 1, 0, -1}, // new[](unsigned int)
{LibFunc::msvc_new_array_int_nothrow, MallocLike, 2, 0, -1}, // new[](unsigned int, nothrow)
{LibFunc::msvc_new_array_longlong, OpNewLike, 1, 0, -1}, // new[](unsigned long long)
{LibFunc::msvc_new_array_longlong_nothrow, MallocLike, 2, 0, -1}, // new[](unsigned long long, nothrow)
{LibFunc::calloc, CallocLike, 2, 0, 1},
{LibFunc::realloc, ReallocLike, 2, 1, -1},
{LibFunc::reallocf, ReallocLike, 2, 1, -1},
{LibFunc::strdup, StrDupLike, 1, -1, -1},
{LibFunc::strndup, StrDupLike, 2, 1, -1}
static const std::pair<LibFunc::Func, AllocFnsTy> AllocationFnData[] = {
{LibFunc::malloc, {MallocLike, 1, 0, -1}},
{LibFunc::valloc, {MallocLike, 1, 0, -1}},
{LibFunc::Znwj, {OpNewLike, 1, 0, -1}}, // new(unsigned int)
{LibFunc::ZnwjRKSt9nothrow_t, {MallocLike, 2, 0, -1}}, // new(unsigned int, nothrow)
{LibFunc::Znwm, {OpNewLike, 1, 0, -1}}, // new(unsigned long)
{LibFunc::ZnwmRKSt9nothrow_t, {MallocLike, 2, 0, -1}}, // new(unsigned long, nothrow)
{LibFunc::Znaj, {OpNewLike, 1, 0, -1}}, // new[](unsigned int)
{LibFunc::ZnajRKSt9nothrow_t, {MallocLike, 2, 0, -1}}, // new[](unsigned int, nothrow)
{LibFunc::Znam, {OpNewLike, 1, 0, -1}}, // new[](unsigned long)
{LibFunc::ZnamRKSt9nothrow_t, {MallocLike, 2, 0, -1}}, // new[](unsigned long, nothrow)
{LibFunc::msvc_new_int, {OpNewLike, 1, 0, -1}}, // new(unsigned int)
{LibFunc::msvc_new_int_nothrow, {MallocLike, 2, 0, -1}}, // new(unsigned int, nothrow)
{LibFunc::msvc_new_longlong, {OpNewLike, 1, 0, -1}}, // new(unsigned long long)
{LibFunc::msvc_new_longlong_nothrow, {MallocLike, 2, 0, -1}}, // new(unsigned long long, nothrow)
{LibFunc::msvc_new_array_int, {OpNewLike, 1, 0, -1}}, // new[](unsigned int)
{LibFunc::msvc_new_array_int_nothrow, {MallocLike, 2, 0, -1}}, // new[](unsigned int, nothrow)
{LibFunc::msvc_new_array_longlong, {OpNewLike, 1, 0, -1}}, // new[](unsigned long long)
{LibFunc::msvc_new_array_longlong_nothrow, {MallocLike, 2, 0, -1}}, // new[](unsigned long long, nothrow)
{LibFunc::calloc, {CallocLike, 2, 0, 1}},
{LibFunc::realloc, {ReallocLike, 2, 1, -1}},
{LibFunc::reallocf, {ReallocLike, 2, 1, -1}},
{LibFunc::strdup, {StrDupLike, 1, -1, -1}},
{LibFunc::strndup, {StrDupLike, 2, 1, -1}}
// TODO: Handle "int posix_memalign(void **, size_t, size_t)"
};

Expand All @@ -96,34 +95,57 @@ static Function *getCalledFunction(const Value *V, bool LookThroughBitCast) {
return Callee;
}

/// \brief Returns the allocation data for the given value if it is a call to a
/// known allocation function, and NULL otherwise.
static const AllocFnsTy *getAllocationData(const Value *V, AllocType AllocTy,
const TargetLibraryInfo *TLI,
bool LookThroughBitCast = false) {
/// Returns the allocation data for the given value if it's either a call to a
/// known allocation function, or a call to a function with the allocsize
/// attribute.
static Optional<AllocFnsTy> getAllocationData(const Value *V, AllocType AllocTy,
const TargetLibraryInfo *TLI,
bool LookThroughBitCast = false) {
// Skip intrinsics
if (isa<IntrinsicInst>(V))
return nullptr;
return None;

Function *Callee = getCalledFunction(V, LookThroughBitCast);
const Function *Callee = getCalledFunction(V, LookThroughBitCast);
if (!Callee)
return nullptr;
return None;

// If it has allocsize, we can skip checking if it's a known function.
//
// MallocLike is chosen here because allocsize makes no guarantees about the
// nullness of the result of the function, nor does it deal with strings, nor
// does it require that the memory returned is zeroed out.
LLVM_CONSTEXPR auto AllocSizeAllocTy = MallocLike;
if ((AllocTy & AllocSizeAllocTy) == AllocSizeAllocTy &&
Callee->hasFnAttribute(Attribute::AllocSize)) {
Attribute Attr = Callee->getFnAttribute(Attribute::AllocSize);
std::pair<unsigned, Optional<unsigned>> Args = Attr.getAllocSizeArgs();

AllocFnsTy Result;
Result.AllocTy = AllocSizeAllocTy;
Result.NumParams = Callee->getNumOperands();
Result.FstParam = Args.first;
Result.SndParam = Args.second.getValueOr(-1);
return Result;
}

// Make sure that the function is available.
StringRef FnName = Callee->getName();
LibFunc::Func TLIFn;
if (!TLI || !TLI->getLibFunc(FnName, TLIFn) || !TLI->has(TLIFn))
return nullptr;
return None;

const AllocFnsTy *FnData =
const auto *Iter =
std::find_if(std::begin(AllocationFnData), std::end(AllocationFnData),
[TLIFn](const AllocFnsTy &Fn) { return Fn.Func == TLIFn; });
[TLIFn](const std::pair<LibFunc::Func, AllocFnsTy> &P) {
return P.first == TLIFn;
});

if (FnData == std::end(AllocationFnData))
return nullptr;
if (Iter == std::end(AllocationFnData))
return None;

const AllocFnsTy *FnData = &Iter->second;
if ((FnData->AllocTy & AllocTy) != FnData->AllocTy)
return nullptr;
return None;

// Check function prototype.
int FstParam = FnData->FstParam;
Expand All @@ -138,8 +160,8 @@ static const AllocFnsTy *getAllocationData(const Value *V, AllocType AllocTy,
(SndParam < 0 ||
FTy->getParamType(SndParam)->isIntegerTy(32) ||
FTy->getParamType(SndParam)->isIntegerTy(64)))
return FnData;
return nullptr;
return *FnData;
return None;
}

static bool hasNoAliasAttr(const Value *V, bool LookThroughBitCast) {
Expand All @@ -153,7 +175,7 @@ static bool hasNoAliasAttr(const Value *V, bool LookThroughBitCast) {
/// like).
bool llvm::isAllocationFn(const Value *V, const TargetLibraryInfo *TLI,
bool LookThroughBitCast) {
return getAllocationData(V, AnyAlloc, TLI, LookThroughBitCast);
return getAllocationData(V, AnyAlloc, TLI, LookThroughBitCast).hasValue();
}

/// \brief Tests if a value is a call or invoke to a function that returns a
Expand All @@ -170,21 +192,21 @@ bool llvm::isNoAliasFn(const Value *V, const TargetLibraryInfo *TLI,
/// allocates uninitialized memory (such as malloc).
bool llvm::isMallocLikeFn(const Value *V, const TargetLibraryInfo *TLI,
bool LookThroughBitCast) {
return getAllocationData(V, MallocLike, TLI, LookThroughBitCast);
return getAllocationData(V, MallocLike, TLI, LookThroughBitCast).hasValue();
}

/// \brief Tests if a value is a call or invoke to a library function that
/// allocates zero-filled memory (such as calloc).
bool llvm::isCallocLikeFn(const Value *V, const TargetLibraryInfo *TLI,
bool LookThroughBitCast) {
return getAllocationData(V, CallocLike, TLI, LookThroughBitCast);
return getAllocationData(V, CallocLike, TLI, LookThroughBitCast).hasValue();
}

/// \brief Tests if a value is a call or invoke to a library function that
/// allocates memory (either malloc, calloc, or strdup like).
bool llvm::isAllocLikeFn(const Value *V, const TargetLibraryInfo *TLI,
bool LookThroughBitCast) {
return getAllocationData(V, AllocLike, TLI, LookThroughBitCast);
return getAllocationData(V, AllocLike, TLI, LookThroughBitCast).hasValue();
}

/// extractMallocCall - Returns the corresponding CallInst if the instruction
Expand Down Expand Up @@ -454,8 +476,8 @@ SizeOffsetType ObjectSizeOffsetVisitor::visitArgument(Argument &A) {
}

SizeOffsetType ObjectSizeOffsetVisitor::visitCallSite(CallSite CS) {
const AllocFnsTy *FnData = getAllocationData(CS.getInstruction(), AnyAlloc,
TLI);
Optional<AllocFnsTy> FnData =
getAllocationData(CS.getInstruction(), AnyAlloc, TLI);
if (!FnData)
return unknown();

Expand All @@ -467,7 +489,8 @@ SizeOffsetType ObjectSizeOffsetVisitor::visitCallSite(CallSite CS) {

// strndup limits strlen
if (FnData->FstParam > 0) {
ConstantInt *Arg= dyn_cast<ConstantInt>(CS.getArgument(FnData->FstParam));
ConstantInt *Arg =
dyn_cast<ConstantInt>(CS.getArgument(FnData->FstParam));
if (!Arg)
return unknown();

Expand All @@ -482,7 +505,25 @@ SizeOffsetType ObjectSizeOffsetVisitor::visitCallSite(CallSite CS) {
if (!Arg)
return unknown();

APInt Size = Arg->getValue().zextOrSelf(IntTyBits);
// When we're compiling N-bit code, and the user uses parameters that are
// greater than N bits (e.g. uint64_t on a 32-bit build), we can run into
// trouble with APInt size issues. This function handles resizing + overflow
// checks for us.
auto CheckedZextOrTrunc = [&](APInt &I) {
// More bits than we can handle. Checking the bit width isn't necessary, but
// it's faster than checking active bits, and should give `false` in the
// vast majority of cases.
if (I.getBitWidth() > IntTyBits && I.getActiveBits() > IntTyBits)
return false;
if (I.getBitWidth() != IntTyBits)
I = I.zextOrTrunc(IntTyBits);
return true;
};

APInt Size = Arg->getValue();
if (!CheckedZextOrTrunc(Size))
return unknown();

// size determined by just 1 parameter
if (FnData->SndParam < 0)
return std::make_pair(Size, Zero);
Expand All @@ -491,8 +532,13 @@ SizeOffsetType ObjectSizeOffsetVisitor::visitCallSite(CallSite CS) {
if (!Arg)
return unknown();

Size *= Arg->getValue().zextOrSelf(IntTyBits);
return std::make_pair(Size, Zero);
APInt NumElems = Arg->getValue();
if (!CheckedZextOrTrunc(NumElems))
return unknown();

bool Overflow;
Size = Size.umul_ov(NumElems, Overflow);
return Overflow ? unknown() : std::make_pair(Size, Zero);

// TODO: handle more standard functions (+ wchar cousins):
// - strdup / strndup
Expand Down Expand Up @@ -670,8 +716,8 @@ SizeOffsetEvalType ObjectSizeOffsetEvaluator::visitAllocaInst(AllocaInst &I) {
}

SizeOffsetEvalType ObjectSizeOffsetEvaluator::visitCallSite(CallSite CS) {
const AllocFnsTy *FnData = getAllocationData(CS.getInstruction(), AnyAlloc,
TLI);
Optional<AllocFnsTy> FnData =
getAllocationData(CS.getInstruction(), AnyAlloc, TLI);
if (!FnData)
return unknown();

Expand Down

0 comments on commit 278199f

Please sign in to comment.