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
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 {
#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>
uint64_t Bytes);
static Attribute getWithDereferenceableOrNullBytes(LLVMContext &Context,
uint64_t Bytes);
+ static Attribute getWithAllocSizeArgs(LLVMContext &Context,
+ unsigned ElemSizeArg,
+ const Optional<unsigned> &NumElemsArg);
//===--------------------------------------------------------------------===//
// Attribute Accessors
/// 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;
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
//===--------------------------------------------------------------------===//
/// 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;
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);
/// 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);
/// 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(); }
/// 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">;
};
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)"
};
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;
(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) {
/// 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
/// 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
}
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();
// 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();
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);
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
}
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();
KEYWORD(attributes);
KEYWORD(alwaysinline);
+ KEYWORD(allocsize);
KEYWORD(argmemonly);
KEYWORD(builtin);
KEYWORD(byval);
#include "LLParser.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringExtras.h"
#include "llvm/AsmParser/SlotMapping.h"
#include "llvm/IR/AutoUpgrade.h"
#include "llvm/IR/CallingConv.h"
B.addStackAlignmentAttr(Alignment);
continue;
}
+ case lltok::kw_allocsize: {
+ unsigned ElemSizeArg;
+ Optional<unsigned> NumElemsArg;
+ // inAttrGrp doesn't matter; we only support allocsize(a[, b])
+ if (parseAllocSizeArguments(ElemSizeArg, NumElemsArg))
+ return true;
+ B.addAllocSizeAttr(ElemSizeArg, NumElemsArg);
+ continue;
+ }
case lltok::kw_alwaysinline: B.addAttribute(Attribute::AlwaysInline); break;
case lltok::kw_argmemonly: B.addAttribute(Attribute::ArgMemOnly); break;
case lltok::kw_builtin: B.addAttribute(Attribute::Builtin); break;
return false;
}
+bool LLParser::parseAllocSizeArguments(unsigned &BaseSizeArg,
+ Optional<unsigned> &HowManyArg) {
+ Lex.Lex();
+
+ auto StartParen = Lex.getLoc();
+ if (!EatIfPresent(lltok::lparen))
+ return Error(StartParen, "expected '('");
+
+ if (ParseUInt32(BaseSizeArg))
+ return true;
+
+ if (EatIfPresent(lltok::comma)) {
+ auto HowManyAt = Lex.getLoc();
+ unsigned HowMany;
+ if (ParseUInt32(HowMany))
+ return true;
+ if (HowMany == BaseSizeArg)
+ return Error(HowManyAt,
+ "'allocsize' indices can't refer to the same parameter");
+ HowManyArg = HowMany;
+ } else
+ HowManyArg = None;
+
+ auto EndParen = Lex.getLoc();
+ if (!EatIfPresent(lltok::rparen))
+ return Error(EndParen, "expected ')'");
+ return false;
+}
+
/// ParseScopeAndOrdering
/// if isAtomic: ::= 'singlethread'? AtomicOrdering
/// else: ::=
#include "LLLexer.h"
#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/Optional.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/IR/Attributes.h"
#include "llvm/IR/Instructions.h"
bool ParseOptionalStackAlignment(unsigned &Alignment);
bool ParseOptionalCommaAlign(unsigned &Alignment, bool &AteExtraComma);
bool ParseOptionalCommaInAlloca(bool &IsInAlloca);
- bool ParseIndexList(SmallVectorImpl<unsigned> &Indices,bool &AteExtraComma);
+ bool parseAllocSizeArguments(unsigned &ElemSizeArg,
+ Optional<unsigned> &HowManyArg);
+ bool ParseIndexList(SmallVectorImpl<unsigned> &Indices,
+ bool &AteExtraComma);
bool ParseIndexList(SmallVectorImpl<unsigned> &Indices) {
bool AteExtraComma;
if (ParseIndexList(Indices, AteExtraComma)) return true;
// Attributes:
kw_attributes,
+ kw_allocsize,
kw_alwaysinline,
kw_argmemonly,
kw_sanitize_address,
return Attribute::Dereferenceable;
case bitc::ATTR_KIND_DEREFERENCEABLE_OR_NULL:
return Attribute::DereferenceableOrNull;
+ case bitc::ATTR_KIND_ALLOC_SIZE:
+ return Attribute::AllocSize;
case bitc::ATTR_KIND_NO_RED_ZONE:
return Attribute::NoRedZone;
case bitc::ATTR_KIND_NO_RETURN:
B.addDereferenceableAttr(Record[++i]);
else if (Kind == Attribute::DereferenceableOrNull)
B.addDereferenceableOrNullAttr(Record[++i]);
+ else if (Kind == Attribute::AllocSize)
+ B.addAllocSizeAttrFromRawRepr(Record[++i]);
} else { // String attribute
assert((Record[i] == 3 || Record[i] == 4) &&
"Invalid attribute group entry");
switch (Kind) {
case Attribute::Alignment:
return bitc::ATTR_KIND_ALIGNMENT;
+ case Attribute::AllocSize:
+ return bitc::ATTR_KIND_ALLOC_SIZE;
case Attribute::AlwaysInline:
return bitc::ATTR_KIND_ALWAYS_INLINE;
case Attribute::ArgMemOnly:
#define LLVM_LIB_IR_ATTRIBUTEIMPL_H
#include "llvm/ADT/FoldingSet.h"
+#include "llvm/ADT/Optional.h"
#include "llvm/IR/Attributes.h"
#include "llvm/Support/DataTypes.h"
#include "llvm/Support/TrailingObjects.h"
: EnumAttributeImpl(IntAttrEntry, Kind), Val(Val) {
assert((Kind == Attribute::Alignment || Kind == Attribute::StackAlignment ||
Kind == Attribute::Dereferenceable ||
- Kind == Attribute::DereferenceableOrNull) &&
+ Kind == Attribute::DereferenceableOrNull ||
+ Kind == Attribute::AllocSize) &&
"Wrong kind for int attribute!");
}
unsigned getStackAlignment() const;
uint64_t getDereferenceableBytes() const;
uint64_t getDereferenceableOrNullBytes() const;
+ std::pair<unsigned, Optional<unsigned>> getAllocSizeArgs() const;
std::string getAsString(bool InAttrGrp) const;
typedef const Attribute *iterator;
// Attribute Construction Methods
//===----------------------------------------------------------------------===//
+// allocsize has two integer arguments, but because they're both 32 bits, we can
+// pack them into one 64-bit value, at the cost of making said value
+// nonsensical.
+//
+// In order to do this, we need to reserve one value of the second (optional)
+// allocsize argument to signify "not present."
+LLVM_CONSTEXPR static unsigned AllocSizeNumElemsNotPresent =
+ std::numeric_limits<unsigned>::max();
+
+static uint64_t packAllocSizeArgs(unsigned ElemSizeArg,
+ const Optional<unsigned> &NumElemsArg) {
+ assert((!NumElemsArg.hasValue() ||
+ *NumElemsArg != AllocSizeNumElemsNotPresent) &&
+ "Attempting to pack a reserved value");
+
+ return uint64_t(ElemSizeArg) << 32 |
+ NumElemsArg.getValueOr(AllocSizeNumElemsNotPresent);
+}
+
+static std::pair<unsigned, Optional<unsigned>>
+unpackAllocSizeArgs(uint64_t Num) {
+ unsigned NumElems = Num & std::numeric_limits<unsigned>::max();
+ unsigned ElemSizeArg = Num >> 32;
+
+ Optional<unsigned> NumElemsArg;
+ if (NumElems != AllocSizeNumElemsNotPresent)
+ NumElemsArg = NumElems;
+ return std::make_pair(ElemSizeArg, NumElemsArg);
+}
+
Attribute Attribute::get(LLVMContext &Context, Attribute::AttrKind Kind,
uint64_t Val) {
LLVMContextImpl *pImpl = Context.pImpl;
return get(Context, DereferenceableOrNull, Bytes);
}
+Attribute
+Attribute::getWithAllocSizeArgs(LLVMContext &Context, unsigned ElemSizeArg,
+ const Optional<unsigned> &NumElemsArg) {
+ assert(!(ElemSizeArg == 0 && NumElemsArg && *NumElemsArg == 0) &&
+ "Invalid allocsize arguments -- given allocsize(0, 0)");
+ return get(Context, AllocSize, packAllocSizeArgs(ElemSizeArg, NumElemsArg));
+}
+
//===----------------------------------------------------------------------===//
// Attribute Accessor Methods
//===----------------------------------------------------------------------===//
return pImpl->getValueAsInt();
}
+std::pair<unsigned, Optional<unsigned>> Attribute::getAllocSizeArgs() const {
+ assert(hasAttribute(Attribute::AllocSize) &&
+ "Trying to get allocsize args from non-allocsize attribute");
+ return unpackAllocSizeArgs(pImpl->getValueAsInt());
+}
+
std::string Attribute::getAsString(bool InAttrGrp) const {
if (!pImpl) return "";
if (hasAttribute(Attribute::DereferenceableOrNull))
return AttrWithBytesToString("dereferenceable_or_null");
+ if (hasAttribute(Attribute::AllocSize)) {
+ unsigned ElemSize;
+ Optional<unsigned> NumElems;
+ std::tie(ElemSize, NumElems) = getAllocSizeArgs();
+
+ std::string Result = "allocsize(";
+ Result += utostr(ElemSize);
+ if (NumElems.hasValue()) {
+ Result += ',';
+ Result += utostr(*NumElems);
+ }
+ Result += ')';
+ return Result;
+ }
+
// Convert target-dependent attributes to strings of the form:
//
// "kind"
case Attribute::ArgMemOnly:
llvm_unreachable("argmemonly attribute not supported in raw format");
break;
+ case Attribute::AllocSize:
+ llvm_unreachable("allocsize not supported in raw format");
+ break;
}
llvm_unreachable("Unsupported attribute type");
}
return 0;
}
+std::pair<unsigned, Optional<unsigned>>
+AttributeSetNode::getAllocSizeArgs() const {
+ for (iterator I = begin(), E = end(); I != E; ++I)
+ if (I->hasAttribute(Attribute::AllocSize))
+ return I->getAllocSizeArgs();
+ return std::make_pair(0, 0);
+}
+
std::string AttributeSetNode::getAsString(bool InAttrGrp) const {
std::string Str;
for (iterator I = begin(), E = end(); I != E; ++I) {
Mask |= (Log2_32(ASN->getStackAlignment()) + 1) << 26;
else if (Kind == Attribute::Dereferenceable)
llvm_unreachable("dereferenceable not supported in bit mask");
+ else if (Kind == Attribute::AllocSize)
+ llvm_unreachable("allocsize not supported in bit mask");
else
Mask |= AttributeImpl::getAttrMask(Kind);
}
Attr = Attribute::getWithDereferenceableOrNullBytes(
C, B.getDereferenceableOrNullBytes());
break;
+ case Attribute::AllocSize: {
+ auto A = B.getAllocSizeArgs();
+ Attr = Attribute::getWithAllocSizeArgs(C, A.first, A.second);
+ break;
+ }
default:
Attr = Attribute::get(C, Kind);
}
return addAttributes(C, Index, AttributeSet::get(C, Index, B));
}
+AttributeSet
+AttributeSet::addAllocSizeAttr(LLVMContext &C, unsigned Index,
+ unsigned ElemSizeArg,
+ const Optional<unsigned> &NumElemsArg) {
+ llvm::AttrBuilder B;
+ B.addAllocSizeAttr(ElemSizeArg, NumElemsArg);
+ return addAttributes(C, Index, AttributeSet::get(C, Index, B));
+}
+
//===----------------------------------------------------------------------===//
// AttributeSet Accessor Methods
//===----------------------------------------------------------------------===//
return ASN ? ASN->getDereferenceableOrNullBytes() : 0;
}
-std::string AttributeSet::getAsString(unsigned Index,
- bool InAttrGrp) const {
+std::pair<unsigned, Optional<unsigned>>
+AttributeSet::getAllocSizeArgs(unsigned Index) const {
+ AttributeSetNode *ASN = getAttributes(Index);
+ return ASN ? ASN->getAllocSizeArgs() : std::make_pair(0, 0);
+}
+
+std::string AttributeSet::getAsString(unsigned Index, bool InAttrGrp) const {
AttributeSetNode *ASN = getAttributes(Index);
return ASN ? ASN->getAsString(InAttrGrp) : std::string("");
}
AttrBuilder::AttrBuilder(AttributeSet AS, unsigned Index)
: Attrs(0), Alignment(0), StackAlignment(0), DerefBytes(0),
- DerefOrNullBytes(0) {
+ DerefOrNullBytes(0), AllocSizeArgs(0) {
AttributeSetImpl *pImpl = AS.pImpl;
if (!pImpl) return;
Attrs.reset();
TargetDepAttrs.clear();
Alignment = StackAlignment = DerefBytes = DerefOrNullBytes = 0;
+ AllocSizeArgs = 0;
}
AttrBuilder &AttrBuilder::addAttribute(Attribute::AttrKind Val) {
assert((unsigned)Val < Attribute::EndAttrKinds && "Attribute out of range!");
assert(Val != Attribute::Alignment && Val != Attribute::StackAlignment &&
- Val != Attribute::Dereferenceable &&
+ Val != Attribute::Dereferenceable && Val != Attribute::AllocSize &&
"Adding integer attribute without adding a value!");
Attrs[Val] = true;
return *this;
DerefBytes = Attr.getDereferenceableBytes();
else if (Kind == Attribute::DereferenceableOrNull)
DerefOrNullBytes = Attr.getDereferenceableOrNullBytes();
+ else if (Kind == Attribute::AllocSize)
+ AllocSizeArgs = Attr.getValueAsInt();
return *this;
}
DerefBytes = 0;
else if (Val == Attribute::DereferenceableOrNull)
DerefOrNullBytes = 0;
+ else if (Val == Attribute::AllocSize)
+ AllocSizeArgs = 0;
return *this;
}
return *this;
}
+std::pair<unsigned, Optional<unsigned>> AttrBuilder::getAllocSizeArgs() const {
+ return unpackAllocSizeArgs(AllocSizeArgs);
+}
+
AttrBuilder &AttrBuilder::addAlignmentAttr(unsigned Align) {
if (Align == 0) return *this;
return *this;
}
+AttrBuilder &AttrBuilder::addAllocSizeAttr(unsigned ElemSize,
+ const Optional<unsigned> &NumElems) {
+ return addAllocSizeAttrFromRawRepr(packAllocSizeArgs(ElemSize, NumElems));
+}
+
+AttrBuilder &AttrBuilder::addAllocSizeAttrFromRawRepr(uint64_t RawArgs) {
+ // (0, 0) is our "not present" value, so we need to check for it here.
+ assert(RawArgs && "Invalid allocsize arguments -- given allocsize(0, 0)");
+
+ Attrs[Attribute::AllocSize] = true;
+ // Reuse existing machinery to store this as a single 64-bit integer so we can
+ // save a few bytes over using a pair<unsigned, Optional<unsigned>>.
+ AllocSizeArgs = RawArgs;
+ return *this;
+}
+
AttrBuilder &AttrBuilder::merge(const AttrBuilder &B) {
// FIXME: What if both have alignments, but they don't match?!
if (!Alignment)
if (!DerefOrNullBytes)
DerefOrNullBytes = B.DerefOrNullBytes;
+ if (!AllocSizeArgs)
+ AllocSizeArgs = B.AllocSizeArgs;
+
Attrs |= B.Attrs;
for (auto I : B.td_attrs())
if (B.DerefOrNullBytes)
DerefOrNullBytes = 0;
+ if (B.AllocSizeArgs)
+ AllocSizeArgs = 0;
+
Attrs &= ~B.Attrs;
for (auto I : B.td_attrs())
I = Attribute::AttrKind(I + 1)) {
if (I == Attribute::Dereferenceable ||
I == Attribute::DereferenceableOrNull ||
- I == Attribute::ArgMemOnly)
+ I == Attribute::ArgMemOnly ||
+ I == Attribute::AllocSize)
continue;
if (uint64_t A = (Val & AttributeImpl::getAttrMask(I))) {
Attrs[I] = true;
I->getKindAsEnum() == Attribute::ArgMemOnly ||
I->getKindAsEnum() == Attribute::NoRecurse ||
I->getKindAsEnum() == Attribute::InaccessibleMemOnly ||
- I->getKindAsEnum() == Attribute::InaccessibleMemOrArgMemOnly) {
+ I->getKindAsEnum() == Attribute::InaccessibleMemOrArgMemOnly ||
+ I->getKindAsEnum() == Attribute::AllocSize) {
if (!isFunction) {
CheckFailed("Attribute '" + I->getAsString() +
"' only applies to functions!", V);
Assert(GV->hasUnnamedAddr(),
"Attribute 'jumptable' requires 'unnamed_addr'", V);
}
+
+ if (Attrs.hasAttribute(AttributeSet::FunctionIndex, Attribute::AllocSize)) {
+ std::pair<unsigned, Optional<unsigned>> Args =
+ Attrs.getAllocSizeArgs(AttributeSet::FunctionIndex);
+
+ auto CheckParam = [&](StringRef Name, unsigned ParamNo) {
+ if (ParamNo >= FT->getNumParams()) {
+ CheckFailed("'allocsize' " + Name + " argument is out of bounds", V);
+ return false;
+ }
+
+ if (!FT->getParamType(ParamNo)->isIntegerTy()) {
+ CheckFailed("'allocsize' " + Name +
+ " argument must refer to an integer parameter",
+ V);
+ return false;
+ }
+
+ return true;
+ };
+
+ if (!CheckParam("element size", Args.first))
+ return;
+
+ if (Args.second && !CheckParam("number of elements", *Args.second))
+ return;
+ }
}
void Verifier::verifyFunctionMetadata(
default: break;
case Intrinsic::objectsize: {
uint64_t Size;
- if (getObjectSize(II->getArgOperand(0), Size, DL, TLI))
- return replaceInstUsesWith(CI, ConstantInt::get(CI.getType(), Size));
+ if (getObjectSize(II->getArgOperand(0), Size, DL, TLI)) {
+ APInt APSize(II->getType()->getIntegerBitWidth(), Size);
+ // Equality check to be sure that `Size` can fit in a value of type
+ // `II->getType()`
+ if (APSize == Size)
+ return replaceInstUsesWith(CI, ConstantInt::get(II->getType(), APSize));
+ }
return nullptr;
}
case Intrinsic::bswap: {
; CHECK: define void @f34()
{
call void @nobuiltin() nobuiltin
-; CHECK: call void @nobuiltin() #30
+; CHECK: call void @nobuiltin() #32
ret void;
}
ret float 1.0
}
+; CHECK: define i8* @f54(i32) #30
+define i8* @f54(i32) allocsize(0) {
+ ret i8* null
+}
+
+; CHECK: define i8* @f55(i32, i32) #31
+define i8* @f55(i32, i32) allocsize(0, 1) {
+ ret i8* null
+}
+
; CHECK: attributes #0 = { noreturn }
; CHECK: attributes #1 = { nounwind }
; CHECK: attributes #2 = { readnone }
; CHECK: attributes #27 = { norecurse }
; CHECK: attributes #28 = { inaccessiblememonly }
; CHECK: attributes #29 = { inaccessiblemem_or_argmemonly }
-; CHECK: attributes #30 = { nobuiltin }
+; CHECK: attributes #30 = { allocsize(0) }
+; CHECK: attributes #31 = { allocsize(0,1) }
+; CHECK: attributes #32 = { nobuiltin }
--- /dev/null
+; RUN: opt < %s -instcombine -S | FileCheck %s
+;
+; The idea is that we want to have sane semantics (e.g. not assertion failures)
+; when given an allocsize function that takes a 64-bit argument in the face of
+; 32-bit pointers.
+
+target datalayout="e-p:32:32:32"
+
+declare i8* @my_malloc(i8*, i64) allocsize(1)
+
+define void @test_malloc(i8** %p, i32* %r) {
+ %1 = call i8* @my_malloc(i8* null, i64 100)
+ store i8* %1, i8** %p, align 8 ; To ensure objectsize isn't killed
+
+ %2 = call i32 @llvm.objectsize.i32.p0i8(i8* %1, i1 false)
+ ; CHECK: store i32 100
+ store i32 %2, i32* %r, align 8
+
+ ; Big number is 5 billion.
+ %3 = call i8* @my_malloc(i8* null, i64 5000000000)
+ store i8* %3, i8** %p, align 8 ; To ensure objectsize isn't killed
+
+ ; CHECK: call i32 @llvm.objectsize
+ %4 = call i32 @llvm.objectsize.i32.p0i8(i8* %3, i1 false)
+ store i32 %4, i32* %r, align 8
+ ret void
+}
+
+declare i32 @llvm.objectsize.i32.p0i8(i8*, i1)
--- /dev/null
+; RUN: opt < %s -instcombine -S | FileCheck %s
+;
+; Test that instcombine folds allocsize function calls properly.
+; Dummy arguments are inserted to verify that allocsize is picking the right
+; args, and to prove that arbitrary unfoldable values don't interfere with
+; allocsize if they're not used by allocsize.
+
+declare i8* @my_malloc(i8*, i32) allocsize(1)
+declare i8* @my_calloc(i8*, i8*, i32, i32) allocsize(2, 3)
+
+; CHECK-LABEL: define void @test_malloc
+define void @test_malloc(i8** %p, i64* %r) {
+ %1 = call i8* @my_malloc(i8* null, i32 100)
+ store i8* %1, i8** %p, align 8 ; To ensure objectsize isn't killed
+
+ %2 = call i64 @llvm.objectsize.i64.p0i8(i8* %1, i1 false)
+ ; CHECK: store i64 100
+ store i64 %2, i64* %r, align 8
+ ret void
+}
+
+; CHECK-LABEL: define void @test_calloc
+define void @test_calloc(i8** %p, i64* %r) {
+ %1 = call i8* @my_calloc(i8* null, i8* null, i32 100, i32 5)
+ store i8* %1, i8** %p, align 8 ; To ensure objectsize isn't killed
+
+ %2 = call i64 @llvm.objectsize.i64.p0i8(i8* %1, i1 false)
+ ; CHECK: store i64 500
+ store i64 %2, i64* %r, align 8
+ ret void
+}
+
+; Failure cases with non-constant values...
+; CHECK-LABEL: define void @test_malloc_fails
+define void @test_malloc_fails(i8** %p, i64* %r, i32 %n) {
+ %1 = call i8* @my_malloc(i8* null, i32 %n)
+ store i8* %1, i8** %p, align 8 ; To ensure objectsize isn't killed
+
+ ; CHECK: @llvm.objectsize.i64.p0i8
+ %2 = call i64 @llvm.objectsize.i64.p0i8(i8* %1, i1 false)
+ store i64 %2, i64* %r, align 8
+ ret void
+}
+
+; CHECK-LABEL: define void @test_calloc_fails
+define void @test_calloc_fails(i8** %p, i64* %r, i32 %n) {
+ %1 = call i8* @my_calloc(i8* null, i8* null, i32 %n, i32 5)
+ store i8* %1, i8** %p, align 8 ; To ensure objectsize isn't killed
+
+ ; CHECK: @llvm.objectsize.i64.p0i8
+ %2 = call i64 @llvm.objectsize.i64.p0i8(i8* %1, i1 false)
+ store i64 %2, i64* %r, align 8
+
+
+ %3 = call i8* @my_calloc(i8* null, i8* null, i32 100, i32 %n)
+ store i8* %3, i8** %p, align 8 ; To ensure objectsize isn't killed
+
+ ; CHECK: @llvm.objectsize.i64.p0i8
+ %4 = call i64 @llvm.objectsize.i64.p0i8(i8* %3, i1 false)
+ store i64 %4, i64* %r, align 8
+ ret void
+}
+
+declare i8* @my_malloc_outofline(i8*, i32) #0
+declare i8* @my_calloc_outofline(i8*, i8*, i32, i32) #1
+
+; Verifying that out of line allocsize is parsed correctly
+; CHECK-LABEL: define void @test_outofline
+define void @test_outofline(i8** %p, i64* %r) {
+ %1 = call i8* @my_malloc_outofline(i8* null, i32 100)
+ store i8* %1, i8** %p, align 8 ; To ensure objectsize isn't killed
+
+ %2 = call i64 @llvm.objectsize.i64.p0i8(i8* %1, i1 false)
+ ; CHECK: store i64 100
+ store i64 %2, i64* %r, align 8
+
+
+ %3 = call i8* @my_calloc_outofline(i8* null, i8* null, i32 100, i32 5)
+ store i8* %3, i8** %p, align 8 ; To ensure objectsize isn't killed
+
+ %4 = call i64 @llvm.objectsize.i64.p0i8(i8* %3, i1 false)
+ ; CHECK: store i64 500
+ store i64 %4, i64* %r, align 8
+ ret void
+}
+
+declare i8* @my_malloc_i64(i8*, i64) #0
+declare i8* @my_tiny_calloc(i8*, i8*, i8, i8) #1
+declare i8* @my_varied_calloc(i8*, i8*, i32, i8) #1
+
+; CHECK-LABEL: define void @test_overflow
+define void @test_overflow(i8** %p, i32* %r) {
+ %r64 = bitcast i32* %r to i64*
+
+ ; (2**31 + 1) * 2 > 2**31. So overflow. Yay.
+ %big_malloc = call i8* @my_calloc(i8* null, i8* null, i32 2147483649, i32 2)
+ store i8* %big_malloc, i8** %p, align 8
+
+ ; CHECK: @llvm.objectsize
+ %1 = call i32 @llvm.objectsize.i32.p0i8(i8* %big_malloc, i1 false)
+ store i32 %1, i32* %r, align 4
+
+
+ %big_little_malloc = call i8* @my_tiny_calloc(i8* null, i8* null, i8 127, i8 4)
+ store i8* %big_little_malloc, i8** %p, align 8
+
+ ; CHECK: store i32 508
+ %2 = call i32 @llvm.objectsize.i32.p0i8(i8* %big_little_malloc, i1 false)
+ store i32 %2, i32* %r, align 4
+
+
+ ; malloc(2**33)
+ %big_malloc_i64 = call i8* @my_malloc_i64(i8* null, i64 8589934592)
+ store i8* %big_malloc_i64, i8** %p, align 8
+
+ ; CHECK: @llvm.objectsize
+ %3 = call i32 @llvm.objectsize.i32.p0i8(i8* %big_malloc_i64, i1 false)
+ store i32 %3, i32* %r, align 4
+
+
+ %4 = call i64 @llvm.objectsize.i64.p0i8(i8* %big_malloc_i64, i1 false)
+ ; CHECK: store i64 8589934592
+ store i64 %4, i64* %r64, align 8
+
+
+ ; Just intended to ensure that we properly handle args of different types...
+ %varied_calloc = call i8* @my_varied_calloc(i8* null, i8* null, i32 1000, i8 5)
+ store i8* %varied_calloc, i8** %p, align 8
+
+ ; CHECK: store i32 5000
+ %5 = call i32 @llvm.objectsize.i32.p0i8(i8* %varied_calloc, i1 false)
+ store i32 %5, i32* %r, align 4
+
+ ret void
+}
+
+attributes #0 = { allocsize(1) }
+attributes #1 = { allocsize(2, 3) }
+
+declare i32 @llvm.objectsize.i32.p0i8(i8*, i1)
+declare i64 @llvm.objectsize.i64.p0i8(i8*, i1)
--- /dev/null
+; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s
+;
+; We handle allocsize with identical args in the parser, rather than the
+; verifier. So, a seperate test is needed.
+
+; CHECK: 'allocsize' indices can't refer to the same parameter
+declare i8* @a(i32, i32) allocsize(0, 0)
--- /dev/null
+; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s
+
+; CHECK: 'allocsize' element size argument is out of bounds
+declare i8* @a(i32) allocsize(1)
+
+; CHECK: 'allocsize' element size argument must refer to an integer parameter
+declare i8* @b(i32*) allocsize(0)
+
+; CHECK: 'allocsize' number of elements argument is out of bounds
+declare i8* @c(i32) allocsize(0, 1)
+
+; CHECK: 'allocsize' number of elements argument must refer to an integer parameter
+declare i8* @d(i32, i32*) allocsize(0, 1)
+
+; CHECK: 'allocsize' number of elements argument is out of bounds
+declare i8* @e(i32, i32) allocsize(1, 2)