Index: llvm/trunk/docs/LangRef.rst =================================================================== --- llvm/trunk/docs/LangRef.rst +++ llvm/trunk/docs/LangRef.rst @@ -1269,6 +1269,15 @@ epilogue, the backend should forcibly align the stack pointer. Specify the desired alignment, which must be a power of two, in parentheses. +``allocsize([, ])`` + 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 Index: llvm/trunk/include/llvm/Bitcode/LLVMBitCodes.h =================================================================== --- llvm/trunk/include/llvm/Bitcode/LLVMBitCodes.h +++ llvm/trunk/include/llvm/Bitcode/LLVMBitCodes.h @@ -513,7 +513,8 @@ 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 { Index: llvm/trunk/include/llvm/IR/Attributes.h =================================================================== --- llvm/trunk/include/llvm/IR/Attributes.h +++ llvm/trunk/include/llvm/IR/Attributes.h @@ -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 @@ -94,6 +95,9 @@ uint64_t Bytes); static Attribute getWithDereferenceableOrNullBytes(LLVMContext &Context, uint64_t Bytes); + static Attribute getWithAllocSizeArgs(LLVMContext &Context, + unsigned ElemSizeArg, + const Optional &NumElemsArg); //===--------------------------------------------------------------------===// // Attribute Accessors @@ -147,6 +151,10 @@ /// 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> 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; @@ -267,6 +275,12 @@ 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 &NumElemsArg); + //===--------------------------------------------------------------------===// // AttributeSet Accessors //===--------------------------------------------------------------------===// @@ -319,6 +333,10 @@ /// unknown). uint64_t getDereferenceableOrNullBytes(unsigned Index) const; + /// Get the allocsize argument numbers (or pair(0, 0) if unknown). + std::pair> + getAllocSizeArgs(unsigned Index) const; + /// \brief Return the attributes at the index as a string. std::string getAsString(unsigned Index, bool InAttrGrp = false) const; @@ -400,19 +418,20 @@ 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); @@ -481,6 +500,10 @@ /// 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> 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); @@ -497,6 +520,14 @@ /// 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 &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(); } Index: llvm/trunk/include/llvm/IR/Attributes.td =================================================================== --- llvm/trunk/include/llvm/IR/Attributes.td +++ llvm/trunk/include/llvm/IR/Attributes.td @@ -16,6 +16,10 @@ /// 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">; Index: llvm/trunk/lib/Analysis/MemoryBuiltins.cpp =================================================================== --- llvm/trunk/lib/Analysis/MemoryBuiltins.cpp +++ llvm/trunk/lib/Analysis/MemoryBuiltins.cpp @@ -42,39 +42,38 @@ }; 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 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)" }; @@ -96,34 +95,57 @@ 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 getAllocationData(const Value *V, AllocType AllocTy, + const TargetLibraryInfo *TLI, + bool LookThroughBitCast = false) { // Skip intrinsics if (isa(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> 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 &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; @@ -138,8 +160,8 @@ (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) { @@ -153,7 +175,7 @@ /// 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 @@ -170,21 +192,21 @@ /// 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 @@ -454,8 +476,8 @@ } SizeOffsetType ObjectSizeOffsetVisitor::visitCallSite(CallSite CS) { - const AllocFnsTy *FnData = getAllocationData(CS.getInstruction(), AnyAlloc, - TLI); + Optional FnData = + getAllocationData(CS.getInstruction(), AnyAlloc, TLI); if (!FnData) return unknown(); @@ -467,7 +489,8 @@ // strndup limits strlen if (FnData->FstParam > 0) { - ConstantInt *Arg= dyn_cast(CS.getArgument(FnData->FstParam)); + ConstantInt *Arg = + dyn_cast(CS.getArgument(FnData->FstParam)); if (!Arg) return unknown(); @@ -482,7 +505,25 @@ 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); @@ -491,8 +532,13 @@ 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 @@ -670,8 +716,8 @@ } SizeOffsetEvalType ObjectSizeOffsetEvaluator::visitCallSite(CallSite CS) { - const AllocFnsTy *FnData = getAllocationData(CS.getInstruction(), AnyAlloc, - TLI); + Optional FnData = + getAllocationData(CS.getInstruction(), AnyAlloc, TLI); if (!FnData) return unknown(); Index: llvm/trunk/lib/AsmParser/LLLexer.cpp =================================================================== --- llvm/trunk/lib/AsmParser/LLLexer.cpp +++ llvm/trunk/lib/AsmParser/LLLexer.cpp @@ -611,6 +611,7 @@ KEYWORD(attributes); KEYWORD(alwaysinline); + KEYWORD(allocsize); KEYWORD(argmemonly); KEYWORD(builtin); KEYWORD(byval); Index: llvm/trunk/lib/AsmParser/LLParser.h =================================================================== --- llvm/trunk/lib/AsmParser/LLParser.h +++ llvm/trunk/lib/AsmParser/LLParser.h @@ -16,6 +16,7 @@ #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" @@ -247,7 +248,10 @@ bool ParseOptionalStackAlignment(unsigned &Alignment); bool ParseOptionalCommaAlign(unsigned &Alignment, bool &AteExtraComma); bool ParseOptionalCommaInAlloca(bool &IsInAlloca); - bool ParseIndexList(SmallVectorImpl &Indices,bool &AteExtraComma); + bool parseAllocSizeArguments(unsigned &ElemSizeArg, + Optional &HowManyArg); + bool ParseIndexList(SmallVectorImpl &Indices, + bool &AteExtraComma); bool ParseIndexList(SmallVectorImpl &Indices) { bool AteExtraComma; if (ParseIndexList(Indices, AteExtraComma)) return true; Index: llvm/trunk/lib/AsmParser/LLParser.cpp =================================================================== --- llvm/trunk/lib/AsmParser/LLParser.cpp +++ llvm/trunk/lib/AsmParser/LLParser.cpp @@ -14,6 +14,7 @@ #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" @@ -1051,6 +1052,15 @@ B.addStackAlignmentAttr(Alignment); continue; } + case lltok::kw_allocsize: { + unsigned ElemSizeArg; + Optional 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; @@ -1790,6 +1800,35 @@ return false; } +bool LLParser::parseAllocSizeArguments(unsigned &BaseSizeArg, + Optional &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: ::= Index: llvm/trunk/lib/AsmParser/LLToken.h =================================================================== --- llvm/trunk/lib/AsmParser/LLToken.h +++ llvm/trunk/lib/AsmParser/LLToken.h @@ -114,6 +114,7 @@ // Attributes: kw_attributes, + kw_allocsize, kw_alwaysinline, kw_argmemonly, kw_sanitize_address, Index: llvm/trunk/lib/Bitcode/Reader/BitcodeReader.cpp =================================================================== --- llvm/trunk/lib/Bitcode/Reader/BitcodeReader.cpp +++ llvm/trunk/lib/Bitcode/Reader/BitcodeReader.cpp @@ -1288,6 +1288,8 @@ 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: @@ -1412,6 +1414,8 @@ 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"); Index: llvm/trunk/lib/Bitcode/Writer/BitcodeWriter.cpp =================================================================== --- llvm/trunk/lib/Bitcode/Writer/BitcodeWriter.cpp +++ llvm/trunk/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -164,6 +164,8 @@ 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: Index: llvm/trunk/lib/IR/AttributeImpl.h =================================================================== --- llvm/trunk/lib/IR/AttributeImpl.h +++ llvm/trunk/lib/IR/AttributeImpl.h @@ -17,6 +17,7 @@ #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" @@ -120,7 +121,8 @@ : 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!"); } @@ -188,6 +190,7 @@ unsigned getStackAlignment() const; uint64_t getDereferenceableBytes() const; uint64_t getDereferenceableOrNullBytes() const; + std::pair> getAllocSizeArgs() const; std::string getAsString(bool InAttrGrp) const; typedef const Attribute *iterator; Index: llvm/trunk/lib/IR/Attributes.cpp =================================================================== --- llvm/trunk/lib/IR/Attributes.cpp +++ llvm/trunk/lib/IR/Attributes.cpp @@ -32,6 +32,36 @@ // 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::max(); + +static uint64_t packAllocSizeArgs(unsigned ElemSizeArg, + const Optional &NumElemsArg) { + assert((!NumElemsArg.hasValue() || + *NumElemsArg != AllocSizeNumElemsNotPresent) && + "Attempting to pack a reserved value"); + + return uint64_t(ElemSizeArg) << 32 | + NumElemsArg.getValueOr(AllocSizeNumElemsNotPresent); +} + +static std::pair> +unpackAllocSizeArgs(uint64_t Num) { + unsigned NumElems = Num & std::numeric_limits::max(); + unsigned ElemSizeArg = Num >> 32; + + Optional 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; @@ -101,6 +131,14 @@ return get(Context, DereferenceableOrNull, Bytes); } +Attribute +Attribute::getWithAllocSizeArgs(LLVMContext &Context, unsigned ElemSizeArg, + const Optional &NumElemsArg) { + assert(!(ElemSizeArg == 0 && NumElemsArg && *NumElemsArg == 0) && + "Invalid allocsize arguments -- given allocsize(0, 0)"); + return get(Context, AllocSize, packAllocSizeArgs(ElemSizeArg, NumElemsArg)); +} + //===----------------------------------------------------------------------===// // Attribute Accessor Methods //===----------------------------------------------------------------------===// @@ -180,6 +218,12 @@ return pImpl->getValueAsInt(); } +std::pair> 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 ""; @@ -312,6 +356,21 @@ if (hasAttribute(Attribute::DereferenceableOrNull)) return AttrWithBytesToString("dereferenceable_or_null"); + if (hasAttribute(Attribute::AllocSize)) { + unsigned ElemSize; + Optional 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" @@ -468,6 +527,9 @@ 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"); } @@ -559,6 +621,14 @@ return 0; } +std::pair> +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) { @@ -594,6 +664,8 @@ 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); } @@ -709,6 +781,11 @@ 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); } @@ -960,6 +1037,15 @@ return addAttributes(C, Index, AttributeSet::get(C, Index, B)); } +AttributeSet +AttributeSet::addAllocSizeAttr(LLVMContext &C, unsigned Index, + unsigned ElemSizeArg, + const Optional &NumElemsArg) { + llvm::AttrBuilder B; + B.addAllocSizeAttr(ElemSizeArg, NumElemsArg); + return addAttributes(C, Index, AttributeSet::get(C, Index, B)); +} + //===----------------------------------------------------------------------===// // AttributeSet Accessor Methods //===----------------------------------------------------------------------===// @@ -1057,8 +1143,13 @@ return ASN ? ASN->getDereferenceableOrNullBytes() : 0; } -std::string AttributeSet::getAsString(unsigned Index, - bool InAttrGrp) const { +std::pair> +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(""); } @@ -1133,7 +1224,7 @@ 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; @@ -1152,12 +1243,13 @@ 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; @@ -1180,6 +1272,8 @@ DerefBytes = Attr.getDereferenceableBytes(); else if (Kind == Attribute::DereferenceableOrNull) DerefOrNullBytes = Attr.getDereferenceableOrNullBytes(); + else if (Kind == Attribute::AllocSize) + AllocSizeArgs = Attr.getValueAsInt(); return *this; } @@ -1200,6 +1294,8 @@ DerefBytes = 0; else if (Val == Attribute::DereferenceableOrNull) DerefOrNullBytes = 0; + else if (Val == Attribute::AllocSize) + AllocSizeArgs = 0; return *this; } @@ -1234,6 +1330,10 @@ return *this; } +std::pair> AttrBuilder::getAllocSizeArgs() const { + return unpackAllocSizeArgs(AllocSizeArgs); +} + AttrBuilder &AttrBuilder::addAlignmentAttr(unsigned Align) { if (Align == 0) return *this; @@ -1274,6 +1374,22 @@ return *this; } +AttrBuilder &AttrBuilder::addAllocSizeAttr(unsigned ElemSize, + const Optional &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>. + AllocSizeArgs = RawArgs; + return *this; +} + AttrBuilder &AttrBuilder::merge(const AttrBuilder &B) { // FIXME: What if both have alignments, but they don't match?! if (!Alignment) @@ -1288,6 +1404,9 @@ if (!DerefOrNullBytes) DerefOrNullBytes = B.DerefOrNullBytes; + if (!AllocSizeArgs) + AllocSizeArgs = B.AllocSizeArgs; + Attrs |= B.Attrs; for (auto I : B.td_attrs()) @@ -1310,6 +1429,9 @@ if (B.DerefOrNullBytes) DerefOrNullBytes = 0; + if (B.AllocSizeArgs) + AllocSizeArgs = 0; + Attrs &= ~B.Attrs; for (auto I : B.td_attrs()) @@ -1388,7 +1510,8 @@ 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; Index: llvm/trunk/lib/IR/Verifier.cpp =================================================================== --- llvm/trunk/lib/IR/Verifier.cpp +++ llvm/trunk/lib/IR/Verifier.cpp @@ -1313,7 +1313,8 @@ 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); @@ -1545,6 +1546,33 @@ Assert(GV->hasUnnamedAddr(), "Attribute 'jumptable' requires 'unnamed_addr'", V); } + + if (Attrs.hasAttribute(AttributeSet::FunctionIndex, Attribute::AllocSize)) { + std::pair> 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( Index: llvm/trunk/lib/Transforms/InstCombine/InstCombineCalls.cpp =================================================================== --- llvm/trunk/lib/Transforms/InstCombine/InstCombineCalls.cpp +++ llvm/trunk/lib/Transforms/InstCombine/InstCombineCalls.cpp @@ -996,8 +996,13 @@ 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: { Index: llvm/trunk/test/Bitcode/attributes.ll =================================================================== --- llvm/trunk/test/Bitcode/attributes.ll +++ llvm/trunk/test/Bitcode/attributes.ll @@ -204,7 +204,7 @@ ; CHECK: define void @f34() { call void @nobuiltin() nobuiltin -; CHECK: call void @nobuiltin() #30 +; CHECK: call void @nobuiltin() #32 ret void; } @@ -318,6 +318,16 @@ 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 } @@ -348,4 +358,6 @@ ; 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 } Index: llvm/trunk/test/Transforms/InstCombine/allocsize-32.ll =================================================================== --- llvm/trunk/test/Transforms/InstCombine/allocsize-32.ll +++ llvm/trunk/test/Transforms/InstCombine/allocsize-32.ll @@ -0,0 +1,29 @@ +; 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) Index: llvm/trunk/test/Transforms/InstCombine/allocsize.ll =================================================================== --- llvm/trunk/test/Transforms/InstCombine/allocsize.ll +++ llvm/trunk/test/Transforms/InstCombine/allocsize.ll @@ -0,0 +1,141 @@ +; 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) Index: llvm/trunk/test/Verifier/alloc-size-failedparse.ll =================================================================== --- llvm/trunk/test/Verifier/alloc-size-failedparse.ll +++ llvm/trunk/test/Verifier/alloc-size-failedparse.ll @@ -0,0 +1,7 @@ +; 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) Index: llvm/trunk/test/Verifier/allocsize.ll =================================================================== --- llvm/trunk/test/Verifier/allocsize.ll +++ llvm/trunk/test/Verifier/allocsize.ll @@ -0,0 +1,16 @@ +; 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)