Index: docs/LangRef.rst =================================================================== --- docs/LangRef.rst +++ docs/LangRef.rst @@ -1202,6 +1202,14 @@ 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 base-0 parameter + numbers; if one argument is provided, then it's assumed that at least + ``CallSite.Args[n]`` bytes will be available at the returned pointer. If two + are provided, then it's assumed that ``CallSite.Args[n] * CallSite.Args[m]`` + 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: include/llvm/Bitcode/LLVMBitCodes.h =================================================================== --- include/llvm/Bitcode/LLVMBitCodes.h +++ include/llvm/Bitcode/LLVMBitCodes.h @@ -481,7 +481,8 @@ ATTR_KIND_ARGMEMONLY = 45, ATTR_KIND_SWIFT_SELF = 46, ATTR_KIND_SWIFT_ERROR = 47, - ATTR_KIND_NO_RECURSE = 48 + ATTR_KIND_NO_RECURSE = 48, + ATTR_KIND_ALLOC_SIZE = 49 }; enum ComdatSelectionKindCodes { Index: include/llvm/IR/Attributes.h =================================================================== --- include/llvm/IR/Attributes.h +++ 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 @@ -93,6 +94,9 @@ uint64_t Bytes); static Attribute getWithDereferenceableOrNullBytes(LLVMContext &Context, uint64_t Bytes); + static Attribute getWithAllocSizeArgs(LLVMContext &Context, + unsigned ElemSizeArg, + const Optional &NumElemsArg); //===--------------------------------------------------------------------===// // Attribute Accessors @@ -146,6 +150,10 @@ /// dereferenceable_or_null attribute (or zero if unknown). uint64_t getDereferenceableOrNullBytes() const; + /// \brief Returns the argument numbers form 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; @@ -261,6 +269,12 @@ AttributeSet addDereferenceableOrNullAttr(LLVMContext &C, unsigned Index, uint64_t Bytes) const; + /// \brief 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 //===--------------------------------------------------------------------===// @@ -309,6 +323,10 @@ /// unknown). uint64_t getDereferenceableOrNullBytes(unsigned Index) const; + /// \brief 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; @@ -390,19 +408,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); @@ -471,6 +490,10 @@ /// dereferenceable_or_null attribute exists (zero is returned otherwise). uint64_t getDereferenceableOrNullBytes() const { return DerefOrNullBytes; } + /// \brief 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); @@ -487,6 +510,11 @@ /// form used internally in Attribute. AttrBuilder &addDereferenceableOrNullAttr(uint64_t Bytes); + /// \brief This turns one (or two) ints into the form used internally in + /// Attribute. + AttrBuilder &addAllocSizeAttr(unsigned ElemSizeArg, + const Optional &NumElemsArg); + /// \brief Return true if the builder contains no target-independent /// attributes. bool empty() const { return Attrs.none(); } Index: include/llvm/IR/Attributes.td =================================================================== --- include/llvm/IR/Attributes.td +++ 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: lib/Analysis/MemoryBuiltins.cpp =================================================================== --- lib/Analysis/MemoryBuiltins.cpp +++ lib/Analysis/MemoryBuiltins.cpp @@ -42,35 +42,33 @@ }; 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::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::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)" }; - static Function *getCalledFunction(const Value *V, bool LookThroughBitCast) { if (LookThroughBitCast) V = V->stripPointerCasts(); @@ -88,34 +86,53 @@ 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) { +/// \brief 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 + LLVM_CONSTEXPR auto AllocSizeAllocTy = MallocLike; + if ((AllocTy & AllocSizeAllocTy) != 0 && + Callee->hasFnAttribute(Attribute::AllocSize)) { + auto 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; @@ -130,8 +147,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) { @@ -139,13 +156,12 @@ return CS && CS.hasFnAttr(Attribute::NoAlias); } - /// \brief Tests if a value is a call or invoke to a library function that /// allocates or reallocates memory (either malloc, calloc, realloc, or strdup /// 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 @@ -162,35 +178,35 @@ /// 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(); } /// \brief Tests if a value is a call or invoke to a library function that /// reallocates memory (such as realloc). bool llvm::isReallocLikeFn(const Value *V, const TargetLibraryInfo *TLI, bool LookThroughBitCast) { - return getAllocationData(V, ReallocLike, TLI, LookThroughBitCast); + return getAllocationData(V, ReallocLike, TLI, LookThroughBitCast).hasValue(); } /// \brief Tests if a value is a call or invoke to a library function that /// allocates memory and never returns null (such as operator new). bool llvm::isOperatorNewLikeFn(const Value *V, const TargetLibraryInfo *TLI, bool LookThroughBitCast) { - return getAllocationData(V, OpNewLike, TLI, LookThroughBitCast); + return getAllocationData(V, OpNewLike, TLI, LookThroughBitCast).hasValue(); } /// extractMallocCall - Returns the corresponding CallInst if the instruction @@ -448,8 +464,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(); @@ -461,7 +477,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(); @@ -664,8 +681,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: lib/AsmParser/LLLexer.cpp =================================================================== --- lib/AsmParser/LLLexer.cpp +++ lib/AsmParser/LLLexer.cpp @@ -598,6 +598,7 @@ KEYWORD(attributes); KEYWORD(alwaysinline); + KEYWORD(allocsize); KEYWORD(argmemonly); KEYWORD(builtin); KEYWORD(byval); Index: lib/AsmParser/LLParser.h =================================================================== --- lib/AsmParser/LLParser.h +++ 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" @@ -252,7 +253,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: lib/AsmParser/LLParser.cpp =================================================================== --- lib/AsmParser/LLParser.cpp +++ 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" @@ -981,6 +982,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; @@ -1690,6 +1700,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: lib/AsmParser/LLToken.h =================================================================== --- lib/AsmParser/LLToken.h +++ lib/AsmParser/LLToken.h @@ -102,6 +102,7 @@ // Attributes: kw_attributes, + kw_allocsize, kw_alwaysinline, kw_argmemonly, kw_sanitize_address, Index: lib/Bitcode/Writer/BitcodeWriter.cpp =================================================================== --- lib/Bitcode/Writer/BitcodeWriter.cpp +++ 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: lib/IR/AttributeImpl.h =================================================================== --- lib/IR/AttributeImpl.h +++ 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/TrailingObjects.h" #include @@ -118,7 +119,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!"); } @@ -171,6 +173,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: lib/IR/Attributes.cpp =================================================================== --- lib/IR/Attributes.cpp +++ lib/IR/Attributes.cpp @@ -31,6 +31,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 auto 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; @@ -100,6 +130,14 @@ return get(Context, DereferenceableOrNull, Bytes); } +Attribute +Attribute::getWithAllocSizeArgs(LLVMContext &Context, unsigned ElemSizeArg, + const Optional &NumElemsArg) { + assert((!NumElemsArg || *NumElemsArg != 0 || ElemSizeArg != 0) && + "Invalid allocsize arguments -- given allocsize(0, 0)"); + return get(Context, AllocSize, packAllocSizeArgs(ElemSizeArg, NumElemsArg)); +} + //===----------------------------------------------------------------------===// // Attribute Accessor Methods //===----------------------------------------------------------------------===// @@ -183,6 +221,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 ""; @@ -307,6 +351,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" @@ -455,6 +514,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"); } @@ -552,6 +614,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) { @@ -587,6 +657,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); } @@ -685,22 +757,33 @@ if (!B.contains(Kind)) continue; - if (Kind == Attribute::Alignment) - Attrs.push_back(std::make_pair(Index, Attribute:: - getWithAlignment(C, B.getAlignment()))); - else if (Kind == Attribute::StackAlignment) - Attrs.push_back(std::make_pair(Index, Attribute:: - getWithStackAlignment(C, B.getStackAlignment()))); - else if (Kind == Attribute::Dereferenceable) - Attrs.push_back(std::make_pair(Index, - Attribute::getWithDereferenceableBytes(C, - B.getDereferenceableBytes()))); - else if (Kind == Attribute::DereferenceableOrNull) - Attrs.push_back( - std::make_pair(Index, Attribute::getWithDereferenceableOrNullBytes( - C, B.getDereferenceableOrNullBytes()))); - else - Attrs.push_back(std::make_pair(Index, Attribute::get(C, Kind))); + Attribute Attr; + switch (Kind) { + case Attribute::Alignment: + Attr = Attribute::getWithAlignment(C, B.getAlignment()); + break; + case Attribute::StackAlignment: + Attr = Attribute::getWithStackAlignment(C, B.getStackAlignment()); + break; + case Attribute::Dereferenceable: + Attr = Attribute::getWithDereferenceableBytes( + C, B.getDereferenceableBytes()); + break; + case Attribute::DereferenceableOrNull: + 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); + break; + } + + Attrs.push_back(std::make_pair(Index, Attr)); } // Add target-dependent (string) attributes. @@ -919,6 +1002,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 //===----------------------------------------------------------------------===// @@ -1014,8 +1106,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(""); } @@ -1094,7 +1191,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; @@ -1113,12 +1210,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; @@ -1141,6 +1239,8 @@ DerefBytes = Attr.getDereferenceableBytes(); else if (Kind == Attribute::DereferenceableOrNull) DerefOrNullBytes = Attr.getDereferenceableOrNullBytes(); + else if (Kind == Attribute::AllocSize) + AllocSizeArgs = Attr.getValueAsInt(); return *this; } @@ -1161,6 +1261,8 @@ DerefBytes = 0; else if (Val == Attribute::DereferenceableOrNull) DerefOrNullBytes = 0; + else if (Val == Attribute::AllocSize) + AllocSizeArgs = 0; return *this; } @@ -1178,17 +1280,7 @@ for (AttributeSet::iterator I = A.begin(Slot), E = A.end(Slot); I != E; ++I) { Attribute Attr = *I; if (Attr.isEnumAttribute() || Attr.isIntAttribute()) { - Attribute::AttrKind Kind = I->getKindAsEnum(); - Attrs[Kind] = false; - - if (Kind == Attribute::Alignment) - Alignment = 0; - else if (Kind == Attribute::StackAlignment) - StackAlignment = 0; - else if (Kind == Attribute::Dereferenceable) - DerefBytes = 0; - else if (Kind == Attribute::DereferenceableOrNull) - DerefOrNullBytes = 0; + removeAttribute(I->getKindAsEnum()); } else { assert(Attr.isStringAttribute() && "Invalid attribute type!"); std::map::iterator @@ -1208,6 +1300,10 @@ return *this; } +std::pair> AttrBuilder::getAllocSizeArgs() const { + return unpackAllocSizeArgs(AllocSizeArgs); +} + AttrBuilder &AttrBuilder::addAlignmentAttr(unsigned Align) { if (Align == 0) return *this; @@ -1248,6 +1344,20 @@ return *this; } +AttrBuilder & +AttrBuilder::addAllocSizeAttr(unsigned ElemSizeArg, + const Optional &NumElemsArg) { + // (0, 0) is our "not present" value, so we need to check for it here. + assert((!NumElemsArg || *NumElemsArg != 0 || ElemSizeArg != 0) && + "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 = packAllocSizeArgs(ElemSizeArg, NumElemsArg); + return *this; +} + AttrBuilder &AttrBuilder::merge(const AttrBuilder &B) { // FIXME: What if both have alignments, but they don't match?! if (!Alignment) @@ -1262,6 +1372,9 @@ if (!DerefOrNullBytes) DerefOrNullBytes = B.DerefOrNullBytes; + if (!AllocSizeArgs) + AllocSizeArgs = B.AllocSizeArgs; + Attrs |= B.Attrs; for (auto I : B.td_attrs()) @@ -1284,6 +1397,9 @@ if (B.DerefOrNullBytes) DerefOrNullBytes = 0; + if (B.AllocSizeArgs) + AllocSizeArgs = 0; + Attrs &= ~B.Attrs; for (auto I : B.td_attrs()) @@ -1363,7 +1479,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: lib/IR/Verifier.cpp =================================================================== --- lib/IR/Verifier.cpp +++ lib/IR/Verifier.cpp @@ -1234,7 +1234,8 @@ I->getKindAsEnum() == Attribute::JumpTable || I->getKindAsEnum() == Attribute::Convergent || I->getKindAsEnum() == Attribute::ArgMemOnly || - I->getKindAsEnum() == Attribute::NoRecurse) { + I->getKindAsEnum() == Attribute::NoRecurse || + I->getKindAsEnum() == Attribute::AllocSize) { if (!isFunction) { CheckFailed("Attribute '" + I->getAsString() + "' only applies to functions!", V); @@ -1429,6 +1430,29 @@ Assert(GV->hasUnnamedAddr(), "Attribute 'jumptable' requires 'unnamed_addr'", V); } + + if (Attrs.hasAttribute(AttributeSet::FunctionIndex, Attribute::AllocSize)) { + auto CheckArg = [&](StringRef Name, unsigned ArgNo) { + Assert(ArgNo < FT->getNumParams(), + "'" + Name + "' parameter index out of bounds", V); + if (ArgNo >= FT->getNumParams()) + return; + + const Type *ArgTy = FT->getParamType(ArgNo); + Assert(ArgTy->isIntegerTy(), + "'" + Name + "' only applies to integer parameters", V); + }; + + std::pair> Args = + Attrs.getAllocSizeArgs(AttributeSet::FunctionIndex); + CheckArg("element size", Args.first); + if (Args.second.hasValue()) { + CheckArg("number of elements", *Args.second); + Assert(Args.first != *Args.second, + "'allocsize' parameter indices can't refer to the same parameter", + V); + } + } } void Verifier::VerifyFunctionMetadata( Index: test/Transforms/InstCombine/allocsize.ll =================================================================== --- /dev/null +++ test/Transforms/InstCombine/allocsize.ll @@ -0,0 +1,85 @@ +; Test that instcombine folds allocsize function calls properly +; +; RUN: opt < %s -instcombine -S | FileCheck %s + +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 +define void @test_outofline(i8** %p, i64* %r) { + ; FIXME + %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 +} + +attributes #0 = { allocsize(1) } +attributes #1 = { allocsize(2, 3) } + +declare i64 @llvm.objectsize.i64.p0i8(i8*, i1) Index: test/Verifier/alloc-size-failedparse.ll =================================================================== --- /dev/null +++ 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: test/Verifier/allocsize.ll =================================================================== --- /dev/null +++ test/Verifier/allocsize.ll @@ -0,0 +1,16 @@ +; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s + +; CHECK: 'element size' parameter index out of bounds +declare i8* @a(i32) allocsize(1) + +; CHECK: 'element size' only applies to integer parameters +declare i8* @b(i32*) allocsize(0) + +; CHECK: 'number of elements' parameter index out of bounds +declare i8* @c(i32) allocsize(0, 1) + +; CHECK: 'number of elements' only applies to integer parameters +declare i8* @d(i32, i32*) allocsize(0, 1) + +; CHECK: 'number of elements' parameter index out of bounds +declare i8* @e(i32) allocsize(0, 1)