Index: docs/HowToUseAttributes.rst =================================================================== --- docs/HowToUseAttributes.rst +++ docs/HowToUseAttributes.rst @@ -18,10 +18,10 @@ virtually no support for attribute-value pairs other than alignment. In the new scheme, an ``Attribute`` object represents a single attribute that's -uniqued. You use the ``Attribute::get`` methods to create a new ``Attribute`` -object. An attribute can be a single "enum" value (the enum being the -``Attribute::AttrKind`` enum), a string representing a target-dependent -attribute, or an attribute-value pair. Some examples: +uniqued on a best-effort basis. You use the ``Attribute::get`` methods to +create a new ``Attribute`` object. An attribute can be a single "enum" value +(the enum being the ``Attribute::AttrKind`` enum), a string representing a +target-dependent attribute, or an attribute-value pair. Some examples: * Target-independent: ``noinline``, ``zext`` * Target-dependent: ``"no-sse"``, ``"thumb2"`` @@ -30,6 +30,27 @@ Note: for an attribute value pair, we expect a target-dependent attribute to have a string for the value. +Kinds of Attributes +=================== +There are four kinds of ``Attribute`` objects that exist: + +* Enum attributes: ``noinline`` +* Integer attributes: ``dereferenceable(8)`` +* String attributes: ``"cpu" = "cortex-a8"`` +* Pseudo-call attributes: ``allocsize({i32, i32}(i32)* @malloc_getsize, [0])`` + +The first three are relatively straightforward; they are all entirely +immutable, and guaranteed to be uniqued. The fourth kind, however, is a bit +different. Pseudo-call attributes allow the user to RAUW the function behind +the attribute, under the assumption that ``F1.replaceAllUsesWith(F2)`` doesn't +change the semantics of the attribute. Notably, this makes such attributes +mutable, and can mess up ordering if you're e.g. using an ``Attribute`` as a key +in a ``std::map``. + +For this reason, it is *not* recommended that you count on the relative ordering +of multiple ``Attribute``s remaining identical across operations that may +RAUW Functions. + ``Attribute`` ============= An ``Attribute`` object is designed to be passed around by value. Index: docs/LangRef.rst =================================================================== --- docs/LangRef.rst +++ docs/LangRef.rst @@ -1223,6 +1223,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/Analysis/ConstantFolding.h =================================================================== --- include/llvm/Analysis/ConstantFolding.h +++ include/llvm/Analysis/ConstantFolding.h @@ -101,13 +101,23 @@ ArrayRef Indices); /// canConstantFoldCallTo - Return true if its even possible to fold a call to -/// the specified function. +/// the specified function using `ConstantFoldCall` bool canConstantFoldCallTo(const Function *F); /// ConstantFoldCall - Attempt to constant fold a call to the specified function /// with the specified arguments, returning null if unsuccessful. Constant *ConstantFoldCall(Function *F, ArrayRef Operands, const TargetLibraryInfo *TLI = nullptr); -} +/// \brief Given a user-supplied Function and arguments, try to fold the +/// Function to a constant. This is a *very* simple fold; it does not support +/// Functions with more than one basic block, nor does it support memory +/// operations (loads, stores, ...). +/// +/// Returns null if it's unsuccessful. +Constant *constantFoldUserFunction(const Function *Fn, + ArrayRef Args, + const DataLayout &DL, + const TargetLibraryInfo *TLI = nullptr); +} #endif Index: include/llvm/Bitcode/LLVMBitCodes.h =================================================================== --- include/llvm/Bitcode/LLVMBitCodes.h +++ include/llvm/Bitcode/LLVMBitCodes.h @@ -485,7 +485,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: 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/IR/ValueHandle.h" #include "llvm/Support/Compiler.h" #include "llvm/Support/PointerLikeTypeTraits.h" #include @@ -33,7 +34,6 @@ class AttributeSetNode; class Constant; template struct DenseMapInfo; -class Function; class LLVMContext; class Type; @@ -45,6 +45,22 @@ /// and should be passed around by-value. class Attribute { public: + /// A pseudo-call encoded in an attribute. + struct PseudoCall { + // FIXME: If this patch goes in, this should be made private, etc. + Constant *Fn; + ArrayRef Args; + + PseudoCall(): Fn(nullptr) {} + PseudoCall(Constant *Fn, ArrayRef Args) : Fn(Fn), Args(Args) { + assert(Fn && "Can't accept a null Function"); + } + + // FIXME: These should be refactored, too. + bool hasPlaceholderFunction() const; + Function *mustGetCalledFunction() const; + }; + /// This enumeration lists the attributes that can be associated with /// parameters, function results, or the function itself. /// @@ -74,6 +90,8 @@ AttributeImpl *pImpl; Attribute(AttributeImpl *A) : pImpl(A) {} + bool deepEqual(Attribute Other) const; + public: Attribute() : pImpl(nullptr) {} @@ -83,6 +101,8 @@ /// \brief Return a uniquified Attribute object. static Attribute get(LLVMContext &Context, AttrKind Kind, uint64_t Val = 0); + static Attribute get(LLVMContext &Context, AttrKind Kind, + const PseudoCall &Val); static Attribute get(LLVMContext &Context, StringRef Kind, StringRef Val = StringRef()); @@ -94,6 +114,8 @@ uint64_t Bytes); static Attribute getWithDereferenceableOrNullBytes(LLVMContext &Context, uint64_t Bytes); + static Attribute getWithAllocSize(LLVMContext &Context, + const PseudoCall &Val); //===--------------------------------------------------------------------===// // Attribute Accessors @@ -105,16 +127,28 @@ /// \brief Return true if the attribute is an integer attribute. bool isIntAttribute() const; + /// \brief Return true if the attribute is a GlobalValue attribute + bool isPseudoCallAttribute() const; + /// \brief Return true if the attribute is a string (target-dependent) /// attribute. bool isStringAttribute() const; + /// \brief Return true if this Attribute will never change. + bool isImmutable() const { return !isPseudoCallAttribute(); } + /// \brief Return true if the attribute is present. bool hasAttribute(AttrKind Val) const; /// \brief Return true if the target-dependent attribute is present. bool hasAttribute(StringRef Val) const; + /// \brief Return true if the attribute's kind is of type AttrKind + bool hasEnumKind() const { return !hasStringKind(); } + + /// \brief Return true if the attribute's kind is a string + bool hasStringKind() const { return isStringAttribute(); } + /// \brief Return the attribute's kind as an enum (Attribute::AttrKind). This /// requires the attribute to be an enum or alignment attribute. Attribute::AttrKind getKindAsEnum() const; @@ -123,6 +157,11 @@ /// attribute be an alignment attribute. uint64_t getValueAsInt() const; + /// \brief Gets the value as a pseudo-call instruction. The ArrayRef will live + /// as long as the attribute backing it lives (which is to say, for the life + /// of the Module). + PseudoCall getValueAsPseudoCall() const; + /// \brief Return the attribute's kind as a string. This requires the /// attribute to be a string attribute. StringRef getKindAsString() const; @@ -147,13 +186,19 @@ /// dereferenceable_or_null attribute. uint64_t getDereferenceableOrNullBytes() const; + /// \brief Returns the Function + arg numbers given to the allocsize + /// attribute. The array ref lives as long as `this` does. + PseudoCall 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; /// \brief Equality and non-equality operators. - bool operator==(Attribute A) const { return pImpl == A.pImpl; } - bool operator!=(Attribute A) const { return pImpl != A.pImpl; } + bool operator!=(Attribute A) const { return !operator==(A); } + bool operator==(Attribute A) const { + return pImpl == A.pImpl || deepEqual(A); + } /// \brief Less-than operator. Useful for sorting the attributes list. bool operator<(Attribute A) const; @@ -267,6 +312,11 @@ 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, + const Attribute::PseudoCall &Args); + //===--------------------------------------------------------------------===// // AttributeSet Accessors //===--------------------------------------------------------------------===// @@ -315,6 +365,10 @@ /// unknown). uint64_t getDereferenceableOrNullBytes(unsigned Index) const; + /// \brief Get the allocsize function (or a default-constructed + /// Attribute::PseudoCall if unknown). + Attribute::PseudoCall getAllocSizeArgs(unsigned Index) const; + /// \brief Return the attributes at the index as a string. std::string getAsString(unsigned Index, bool InAttrGrp = false) const; @@ -396,6 +450,8 @@ uint64_t StackAlignment; uint64_t DerefBytes; uint64_t DerefOrNullBytes; + TrackingVH AllocSizeFn; + SmallVector AllocSizeArgNos; public: AttrBuilder() @@ -411,6 +467,7 @@ DerefOrNullBytes(0) { addAttribute(A); } + AttrBuilder(AttributeSet AS, unsigned Idx); void clear(); @@ -477,6 +534,10 @@ /// dereferenceable_or_null attribute exists (zero is returned otherwise). uint64_t getDereferenceableOrNullBytes() const { return DerefOrNullBytes; } + /// \brief Retrieve the allocsize function, if the allocsize attribute exists + /// (A default-constructed Attribute::PseudoCall is returned otherwise). + Attribute::PseudoCall 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); @@ -493,6 +554,10 @@ /// form used internally in Attribute. AttrBuilder &addDereferenceableOrNullAttr(uint64_t Bytes); + /// \brief This turns the given Function into the form used internally in + /// Attribute. + AttrBuilder &addAllocSizeAttr(const Attribute::PseudoCall &Args); + /// \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/ConstantFolding.cpp =================================================================== --- lib/Analysis/ConstantFolding.cpp +++ lib/Analysis/ConstantFolding.cpp @@ -881,12 +881,103 @@ return C; } - +/// Constant fold an instruction using the given operands, rather than the +/// operands listed in the Instruction. Any non-Value operands (e.g. the indices +/// in an insertvalue instruction) will be taken from the Instruction. +static Constant *foldNonMemoryInstructionWithOperands( + const Instruction &Inst, ArrayRef Operands, + const DataLayout &DL, const TargetLibraryInfo *TLI) { + if (auto *Cmp = dyn_cast(&Inst)) + return ConstantFoldCompareInstOperands(Cmp->getPredicate(), Operands[0], + Operands[1], DL, TLI); + + if (auto *IVI = dyn_cast(&Inst)) + return ConstantExpr::getInsertValue(Operands[0], Operands[1], + IVI->getIndices(), IVI->getType()); + + if (auto *EVI = dyn_cast(&Inst)) + return ConstantExpr::getExtractValue(Operands[0], EVI->getIndices()); + + return ConstantFoldInstOperands(Inst.getOpcode(), Inst.getType(), Operands, + DL, TLI); +} //===----------------------------------------------------------------------===// // Constant Folding public APIs //===----------------------------------------------------------------------===// +/// Given a user-supplied Function and arguments, try to fold the Function to a +/// constant. This is a *very* trivial fold; it does not support Functions with +/// more than one basic block, nor does it support anything that the constant +/// folder doesn't support. +Constant *llvm::constantFoldUserFunction(const Function *Fn, + ArrayRef Args, + const DataLayout &DL, + const TargetLibraryInfo *TLI) { + assert(!Fn->getReturnType()->isVoidTy() && "Trying to fold a void function?"); + assert(std::none_of(Args.begin(), Args.end(), [](Value *P) { return !P; })); + + // Functions with zero blocks (naturally) can't be folded, and functions + // with more than one block are not supported due to their potential + // complexity. The goal is to try and fold very simple functions, not make + // LLVM's own flavor of constexpr. + if (Fn->empty() || std::next(Fn->begin()) != Fn->end()) + return nullptr; + + // Don't allocate for the body, because many of our attempts will presumably + // stop early on in the function (e.g. when we spot an alloca/load/store/...) + SmallDenseMap ValueMap(Args.size()); + + unsigned I = 0; + for (const Argument &A : Fn->getArgumentList()) { + assert(A.getType() == Args[I]->getType()); + ValueMap[&A] = Args[I++]; + } + + SmallVector Operands; + for (const Instruction &Inst : Fn->getEntryBlock()) { + // Fast path: Stores/loads aren't supported, and allocas are generally put + // really early in functions, so give up if we see one. + if (isa(Inst)) + return nullptr; + + Operands.resize(Inst.getNumOperands()); + unsigned I = 0; + for (Value *V : Inst.operands()) { + if (Constant *C = dyn_cast(V)) { + // Functions are fine if they're used as the operand of a call + // operation, because we may be able to fold them (e.g. math intrinsics, + // ...). Otherwise, global values aren't allowed. + if (isa(C) && + !(isa(Inst) && isa(C) && I == 0)) + return nullptr; + Operands[I++] = C; + continue; + } + + auto Result = ValueMap.find(V); + assert(Result != ValueMap.end()); + Operands[I++] = Result->second; + } + + if (isa(Inst)) + return Operands[0]; + + Constant *Result = + foldNonMemoryInstructionWithOperands(Inst, Operands, DL, TLI); + + // ConstantExprs are returned if we can't fold something to a single + // constant value. That's technically a failure, so we should bail out early + // instead of building an everlasting tree of ConstantExprs that ends up + // being thrown away. + if (!Result || isa(Result)) + return nullptr; + ValueMap[&Inst] = Result; + } + + llvm_unreachable("Encountered a function without a return instruction"); +} + /// Try to constant fold the specified instruction. /// If successful, the constant result is returned, if not, null is returned. /// Note that this fails if not all of the operands are constant. Otherwise, @@ -939,27 +1030,10 @@ Ops.push_back(Op); } - if (const CmpInst *CI = dyn_cast(I)) - return ConstantFoldCompareInstOperands(CI->getPredicate(), Ops[0], Ops[1], - DL, TLI); - if (const LoadInst *LI = dyn_cast(I)) return ConstantFoldLoadInst(LI, DL); - if (InsertValueInst *IVI = dyn_cast(I)) { - return ConstantExpr::getInsertValue( - cast(IVI->getAggregateOperand()), - cast(IVI->getInsertedValueOperand()), - IVI->getIndices()); - } - - if (ExtractValueInst *EVI = dyn_cast(I)) { - return ConstantExpr::getExtractValue( - cast(EVI->getAggregateOperand()), - EVI->getIndices()); - } - - return ConstantFoldInstOperands(I->getOpcode(), I->getType(), Ops, DL, TLI); + return foldNonMemoryInstructionWithOperands(*I, Ops, DL, TLI); } static Constant * Index: lib/Analysis/MemoryBuiltins.cpp =================================================================== --- lib/Analysis/MemoryBuiltins.cpp +++ lib/Analysis/MemoryBuiltins.cpp @@ -15,6 +15,7 @@ #include "llvm/Analysis/MemoryBuiltins.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/Statistic.h" +#include "llvm/Analysis/ConstantFolding.h" #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/DataLayout.h" @@ -49,6 +50,8 @@ signed char FstParam, SndParam; }; +constexpr static AllocType AllocSizeAllocTy = MallocLike; + // FIXME: certain users need more information. E.g., SimplifyLibCalls needs to // know which functions are nounwind, noalias, nocapture parameters, etc. static const AllocFnsTy AllocationFnData[] = { @@ -78,13 +81,8 @@ // TODO: Handle "int posix_memalign(void **, size_t, size_t)" }; - -static Function *getCalledFunction(const Value *V, bool LookThroughBitCast) { - if (LookThroughBitCast) - V = V->stripPointerCasts(); - - CallSite CS(const_cast(V)); - if (!CS.getInstruction()) +static Function *getCalledFunction(CallSite CS) { + if (!CS.getInstruction() || isa(CS.getInstruction())) return nullptr; if (CS.isNoBuiltin()) @@ -96,19 +94,82 @@ 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) { - // Skip intrinsics - if (isa(V)) +static Function *getCalledFunction(const Value *V, + bool LookThroughBitCast = true) { + if (LookThroughBitCast) + V = V->stripPointerCasts(); + return getCalledFunction(CallSite(const_cast(V))); +} + +static Optional +getAllocSizeArgs(const Function *Callee) { + if (!Callee->hasFnAttribute(Attribute::AllocSize)) + return None; + Attribute Attr = Callee->getFnAttribute(Attribute::AllocSize); + return Attr.getAllocSizeArgs(); +} + +static Constant * +foldBytesComputedByAllocSizeFunction(CallSite CS, const DataLayout &DL, + const TargetLibraryInfo *TLI) { + + Function *Fn = getCalledFunction(CS); + if (!Fn) return nullptr; - Function *Callee = getCalledFunction(V, LookThroughBitCast); - if (!Callee) + Optional MaybeAllocSizeArgs = getAllocSizeArgs(Fn); + if (!MaybeAllocSizeArgs) + return nullptr; + + const Attribute::PseudoCall &Call = *MaybeAllocSizeArgs; + SmallVector Args; + for (unsigned ArgNo : Call.Args) { + auto *C = dyn_cast(CS.getArgument(ArgNo)); + if (!C) + return nullptr; + Args.push_back(C); + } + + return constantFoldUserFunction(Call.mustGetCalledFunction(), Args, DL, TLI); +} + +static std::pair unpackAllocSizeResult(Constant *C) { + auto *Size = cast(C->getAggregateElement(0u)); + auto *Offset = cast(C->getAggregateElement(1)); + return std::make_pair(Size->getValue(), Offset->getValue()); +} + +static std::pair unpackAllocSizeResult(Value *V) { + return std::make_pair(ExtractValueInst::Create(V, {0}), + ExtractValueInst::Create(V, {1})); +} + +static Value * +getBytesReturnedByAllocSizeFunction(CallSite CS, const DataLayout &DL, + const TargetLibraryInfo *TLI, + bool RequireConstant = false) { + Function *Fn = getCalledFunction(CS); + if (!Fn) + return nullptr; + + Optional MaybeAllocSizeArgs = getAllocSizeArgs(Fn); + if (!MaybeAllocSizeArgs) return nullptr; + const Attribute::PseudoCall &Call = *MaybeAllocSizeArgs; + SmallVector Args(Call.mustGetCalledFunction()->arg_size()); + unsigned I = 0; + for (unsigned ArgNo : Call.Args) + Args[I++] = CS.getArgument(ArgNo); + + return CallInst::Create(Call.Fn, Args); +} + +/// \brief Returns the allocation data for the given value if it's either a call +/// to a known allocation function +static const AllocFnsTy * +getKnownAllocationData(const Function *Callee, AllocType AllocTy, + const TargetLibraryInfo *TLI) { // Make sure that the function is available. StringRef FnName = Callee->getName(); LibFunc::Func TLIFn; @@ -117,7 +178,7 @@ const AllocFnsTy *FnData = std::find_if(std::begin(AllocationFnData), std::end(AllocationFnData), - [TLIFn](const AllocFnsTy &Fn) { return Fn.Func == TLIFn; }); + [TLIFn](const AllocFnsTy &P) { return P.Func== TLIFn; }); if (FnData == std::end(AllocationFnData)) return nullptr; @@ -142,18 +203,37 @@ return nullptr; } +static const AllocFnsTy * +getKnownAllocationData(const Value *V, AllocType AllocTy, + const TargetLibraryInfo *TLI, + bool LookThroughBitCast = false) { + const Function *Callee = getCalledFunction(V, LookThroughBitCast); + return Callee ? getKnownAllocationData(Callee, AllocTy, TLI) : nullptr; +} + static bool hasNoAliasAttr(const Value *V, bool LookThroughBitCast) { ImmutableCallSite CS(LookThroughBitCast ? V->stripPointerCasts() : V); return CS && CS.hasFnAttr(Attribute::NoAlias); } +static bool isAllocFunctionOfType(const Value *V, AllocType Ty, + const TargetLibraryInfo *TLI, + bool LookThroughBitCast = true) { + const Function *Callee = getCalledFunction(V, LookThroughBitCast); + if (!Callee) + return false; + + if ((AllocSizeAllocTy & Ty) == AllocSizeAllocTy && getAllocSizeArgs(Callee)) + return true; + return getKnownAllocationData(Callee, Ty, TLI); +} /// \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 isAllocFunctionOfType(V, AnyAlloc, TLI, LookThroughBitCast); } /// \brief Tests if a value is a call or invoke to a function that returns a @@ -161,7 +241,8 @@ bool llvm::isNoAliasFn(const Value *V, const TargetLibraryInfo *TLI, bool LookThroughBitCast) { // it's safe to consider realloc as noalias since accessing the original - // pointer is undefined behavior + // pointer is undefined behavior. + // FIXME: Should we include allocsize functions in this? return isAllocationFn(V, TLI, LookThroughBitCast) || hasNoAliasAttr(V, LookThroughBitCast); } @@ -170,21 +251,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 isAllocFunctionOfType(V, MallocLike, TLI, LookThroughBitCast); } /// \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 isAllocFunctionOfType(V, CallocLike, TLI, LookThroughBitCast); } /// \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 isAllocFunctionOfType(V, AllocLike, TLI, LookThroughBitCast); } /// extractMallocCall - Returns the corresponding CallInst if the instruction @@ -454,10 +535,18 @@ } SizeOffsetType ObjectSizeOffsetVisitor::visitCallSite(CallSite CS) { - const AllocFnsTy *FnData = getAllocationData(CS.getInstruction(), AnyAlloc, - TLI); - if (!FnData) + const AllocFnsTy *FnData = getKnownAllocationData(CS.getInstruction(), AnyAlloc, TLI); + if (!FnData) { + if (Constant *Res = foldBytesComputedByAllocSizeFunction(CS, DL, TLI)) { + std::pair Unpacked = unpackAllocSizeResult(Res); + // If the user calculated a negative value, or the offset is greater than + // the size of the block of memory we're being handed back, bail out. + if (Unpacked.first.isNonNegative() && Unpacked.second.isNonNegative() && + Unpacked.first.uge(Unpacked.second)) + return Unpacked; + } return unknown(); + } // handle strdup-like functions separately if (FnData->AllocTy == StrDupLike) { @@ -467,7 +556,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(); @@ -670,10 +760,14 @@ } SizeOffsetEvalType ObjectSizeOffsetEvaluator::visitCallSite(CallSite CS) { - const AllocFnsTy *FnData = getAllocationData(CS.getInstruction(), AnyAlloc, - TLI); - if (!FnData) - return unknown(); + const AllocFnsTy *FnData = getKnownAllocationData(CS, AnyAlloc, TLI); + if (!FnData) { + // FIXME: Because we're running user code here, do we want to add a guard + // for if it returns a negative value? Or should we just let it crash and + // let the user figure it out? + Value *Res = getBytesReturnedByAllocSizeFunction(CS, DL, TLI); + return Res ? unpackAllocSizeResult(Res) : unknown(); + } // handle strdup-like functions separately if (FnData->AllocTy == StrDupLike) { Index: lib/AsmParser/LLLexer.cpp =================================================================== --- lib/AsmParser/LLLexer.cpp +++ lib/AsmParser/LLLexer.cpp @@ -602,6 +602,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" @@ -244,7 +245,10 @@ bool ParseOptionalStackAlignment(unsigned &Alignment); bool ParseOptionalCommaAlign(unsigned &Alignment, bool &AteExtraComma); bool ParseOptionalCommaInAlloca(bool &IsInAlloca); - bool ParseIndexList(SmallVectorImpl &Indices,bool &AteExtraComma); + bool parseAllocSizeArguments(Function *&Fn, + SmallVectorImpl &Params); + 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" @@ -990,6 +991,15 @@ B.addStackAlignmentAttr(Alignment); continue; } + case lltok::kw_allocsize: { + // inAttrGrp doesn't matter; we only support allocsize(a, [b, c, ...]) + Function *Fn; + SmallVector ArgNos; + if (parseAllocSizeArguments(Fn, ArgNos)) + return true; + B.addAllocSizeAttr({Fn, ArgNos}); + 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; @@ -1707,6 +1717,50 @@ return false; } +bool LLParser::parseAllocSizeArguments(Function *&Fn, + SmallVectorImpl &Params) { + Lex.Lex(); + + LocTy CurrentLoc = Lex.getLoc(); + if (!EatIfPresent(lltok::lparen)) + return Error(CurrentLoc, "expected '('"); + + CurrentLoc = Lex.getLoc(); + Constant *Val; + if (ParseGlobalTypeAndValue(Val)) + return true; + + Fn = dyn_cast(Val); + if (!Fn) + return Error(CurrentLoc, "expected function"); + + Params.clear(); + + CurrentLoc = Lex.getLoc(); + bool AteComma = EatIfPresent(lltok::comma); + if (AteComma) { + CurrentLoc = Lex.getLoc(); + if (!EatIfPresent(lltok::lsquare)) + return Error(CurrentLoc, "expected '['"); + + // Consume the arg list. + while (!EatIfPresent(lltok::rsquare)) { + CurrentLoc = Lex.getLoc(); + if (!Params.empty() && !EatIfPresent(lltok::comma)) + return Error(CurrentLoc, "expected ',' or ']'"); + + unsigned ArgNo; + if (ParseUInt32(ArgNo)) + return true; + Params.push_back(ArgNo); + } + } + + if (!EatIfPresent(lltok::rparen)) + return Error(CurrentLoc, "expected ')'"); + return false; +} + /// ParseScopeAndOrdering /// if isAtomic: ::= 'singlethread'? AtomicOrdering /// else: ::= Index: lib/AsmParser/LLToken.h =================================================================== --- lib/AsmParser/LLToken.h +++ lib/AsmParser/LLToken.h @@ -106,6 +106,7 @@ // Attributes: kw_attributes, + kw_allocsize, kw_alwaysinline, kw_argmemonly, kw_sanitize_address, Index: lib/Bitcode/Reader/BitcodeReader.cpp =================================================================== --- lib/Bitcode/Reader/BitcodeReader.cpp +++ lib/Bitcode/Reader/BitcodeReader.cpp @@ -62,6 +62,10 @@ } // vector compatibility methods + using ConstIterator = decltype(ValuePtrs.cbegin()); + ConstIterator begin() const { return ValuePtrs.cbegin(); } + ConstIterator end() const { return ValuePtrs.cend(); } + unsigned size() const { return ValuePtrs.size(); } void resize(unsigned N) { ValuePtrs.resize(N); } void push_back(Value *V) { ValuePtrs.emplace_back(V); } @@ -156,6 +160,13 @@ std::vector ComdatList; SmallVector InstructionList; + /// A sort of cursor into ValueList, used when we're parsing module constants. + /// It's needed because we may have forward references in Attributes, so we + /// can't just use the size of the value list as an indicator for where + /// the constants need to go. + /// FIXME: This could be done better. Fix it if we want this patch to go in. + unsigned ValueListIndex = 0; + std::vector > GlobalInits; std::vector > AliasInits; std::vector > FunctionPrefixes; @@ -388,7 +399,7 @@ ErrorOr recordValue(SmallVectorImpl &Record, unsigned NameIndex, Triple &TT); std::error_code parseValueSymbolTable(uint64_t Offset = 0); - std::error_code parseConstants(); + std::error_code parseConstants(size_t StartAt = size_t(-1)); std::error_code rememberAndSkipFunctionBodies(); std::error_code rememberAndSkipFunctionBody(); /// Save the positions of the Metadata blocks and skip parsing the blocks. @@ -1271,6 +1282,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: @@ -1391,9 +1404,7 @@ B.addDereferenceableAttr(Record[++i]); else if (Kind == Attribute::DereferenceableOrNull) B.addDereferenceableOrNullAttr(Record[++i]); - } else { // String attribute - assert((Record[i] == 3 || Record[i] == 4) && - "Invalid attribute group entry"); + } else if (Record[i] == 3 || Record[i] == 4) { // String attribute bool HasValue = (Record[i++] == 4); SmallString<64> KindStr; SmallString<64> ValStr; @@ -1411,6 +1422,22 @@ } B.addAttribute(KindStr.str(), ValStr.str()); + } else { + assert(Record[i] == 5 && "Invalid attribute group entry"); + Attribute::AttrKind Kind; + if (std::error_code EC = parseAttrKind(Record[++i], &Kind)) + return EC; + + assert(Kind == Attribute::AllocSize && "Unexpected attribute kind"); + + Type *Ty = getTypeByID(Record[++i]); + assert(Ty && "Types should exist by now."); + Constant *Fn = ValueList.getConstantFwdRef(Record[++i], Ty); + uint64_t NumArgNos = Record[++i]; + SmallVector ArgNos(NumArgNos); + for (unsigned &ArgNo : ArgNos) + ArgNo = Record[++i]; + B.addAllocSizeAttr({Fn, ArgNos}); } } @@ -2519,7 +2546,7 @@ return APInt(TypeBits, Words); } -std::error_code BitcodeReader::parseConstants() { +std::error_code BitcodeReader::parseConstants(size_t StartAt) { if (Stream.EnterSubBlock(bitc::CONSTANTS_BLOCK_ID)) return error("Invalid record"); @@ -2527,7 +2554,9 @@ // Read all the records for this value table. Type *CurTy = Type::getInt32Ty(Context); - unsigned NextCstNo = ValueList.size(); + if (StartAt == size_t(-1)) + StartAt = ValueList.size(); + unsigned NextCstNo = StartAt; while (1) { BitstreamEntry Entry = Stream.advanceSkippingSubblocks(); @@ -2972,8 +3001,11 @@ } } - ValueList.assignValue(V, NextCstNo); - ++NextCstNo; + assert((NextCstNo >= ValueList.size() || + !ValueList[NextCstNo] || + isa(ValueList[NextCstNo])) && + "We're about to overwrite an actual constant?"); + ValueList.assignValue(V, NextCstNo++); } } @@ -3124,7 +3156,7 @@ resolveGlobalAndAliasInits(); if (!GlobalInits.empty() || !AliasInits.empty()) return error("Malformed global initializer set"); - + // Look for intrinsic functions which need to be upgraded at some point for (Function &F : *TheModule) { Function *NewFn; @@ -3287,7 +3319,7 @@ } break; case bitc::CONSTANTS_BLOCK_ID: - if (std::error_code EC = parseConstants()) + if (std::error_code EC = parseConstants(ValueListIndex)) return EC; if (std::error_code EC = resolveGlobalAndAliasInits()) return EC; @@ -3518,7 +3550,7 @@ else upgradeDLLImportExportLinkage(NewGV, RawLinkage); - ValueList.push_back(NewGV); + ValueList.assignValue(NewGV, ValueListIndex++); // Remember which value to use for the global initializer. if (unsigned InitID = Record[2]) @@ -3608,7 +3640,7 @@ if (Record.size() > 14 && Record[14] != 0) FunctionPersonalityFns.push_back(std::make_pair(Func, Record[14] - 1)); - ValueList.push_back(Func); + ValueList.assignValue(Func, ValueListIndex++); // If this is a function with a body, remember the prototype we are // creating now, so that we can match up the body with them later. @@ -3662,7 +3694,7 @@ NewGA->setThreadLocalMode(getDecodedThreadLocalMode(Record[OpNum++])); if (OpNum != Record.size()) NewGA->setUnnamedAddr(Record[OpNum++]); - ValueList.push_back(NewGA); + ValueList.assignValue(NewGA, ValueListIndex++); AliasInits.push_back(std::make_pair(NewGA, Val)); break; } 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: @@ -293,7 +295,7 @@ Record.push_back(1); Record.push_back(getAttrKindEncoding(Attr.getKindAsEnum())); Record.push_back(Attr.getValueAsInt()); - } else { + } else if (Attr.isStringAttribute()) { StringRef Kind = Attr.getKindAsString(); StringRef Val = Attr.getValueAsString(); @@ -304,6 +306,18 @@ Record.append(Val.begin(), Val.end()); Record.push_back(0); } + } else { + assert(Attr.isPseudoCallAttribute() && "Unknown attribute type"); + Attribute::PseudoCall Call = Attr.getValueAsPseudoCall(); + assert(!Call.hasPlaceholderFunction() && + "Placeholder in the bitcode writer?!"); + + Record.push_back(5); + Record.push_back(getAttrKindEncoding(Attr.getKindAsEnum())); + Record.push_back(VE.getTypeID(Call.Fn->getType())); + Record.push_back(VE.getValueID(Call.Fn)); + Record.push_back(Call.Args.size()); + Record.append(Call.Args.begin(), Call.Args.end()); } } @@ -2906,15 +2920,15 @@ // Emit blockinfo, which defines the standard abbreviations etc. WriteBlockInfo(VE, Stream); + // Emit information describing all of the types in the module. + WriteTypeTable(VE, Stream); + // Emit information about attribute groups. WriteAttributeGroupTable(VE, Stream); // Emit information about parameter attributes. WriteAttributeTable(VE, Stream); - // Emit information describing all of the types in the module. - WriteTypeTable(VE, Stream); - writeComdats(VE, Stream); // Emit top-level description of module, including target triple, inline asm, Index: lib/IR/AttributeImpl.h =================================================================== --- lib/IR/AttributeImpl.h +++ lib/IR/AttributeImpl.h @@ -17,7 +17,10 @@ #define LLVM_LIB_IR_ATTRIBUTEIMPL_H #include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/Optional.h" #include "llvm/IR/Attributes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/ValueHandle.h" #include "llvm/Support/TrailingObjects.h" #include @@ -41,6 +44,7 @@ enum AttrEntryKind { EnumAttrEntry, IntAttrEntry, + PseudoCallEntry, StringAttrEntry }; @@ -51,13 +55,18 @@ bool isEnumAttribute() const { return KindID == EnumAttrEntry; } bool isIntAttribute() const { return KindID == IntAttrEntry; } + bool isPseudoCallAttribute() const { return KindID == PseudoCallEntry; } bool isStringAttribute() const { return KindID == StringAttrEntry; } + bool hasEnumKind() const { return !hasStringKind(); } + bool hasStringKind() const { return isStringAttribute(); } + bool hasAttribute(Attribute::AttrKind A) const; bool hasAttribute(StringRef Kind) const; Attribute::AttrKind getKindAsEnum() const; uint64_t getValueAsInt() const; + Attribute::PseudoCall getValueAsPseudoCall() const; StringRef getKindAsString() const; StringRef getValueAsString() const; @@ -67,20 +76,31 @@ void Profile(FoldingSetNodeID &ID) const { if (isEnumAttribute()) - Profile(ID, getKindAsEnum(), 0); + ID.AddInteger(getKindAsEnum()); else if (isIntAttribute()) Profile(ID, getKindAsEnum(), getValueAsInt()); - else + else if (isStringAttribute()) Profile(ID, getKindAsString(), getValueAsString()); + else + Profile(ID, getKindAsEnum(), getValueAsPseudoCall()); } static void Profile(FoldingSetNodeID &ID, Attribute::AttrKind Kind, uint64_t Val) { ID.AddInteger(Kind); - if (Val) ID.AddInteger(Val); + if (Val) + ID.AddInteger(Val); + } + static void Profile(FoldingSetNodeID &ID, Attribute::AttrKind Kind, + const Attribute::PseudoCall &Call) { + ID.AddInteger(Kind); + ID.AddPointer(Call.Fn); + for (unsigned I : Call.Args) + ID.AddInteger(I); } static void Profile(FoldingSetNodeID &ID, StringRef Kind, StringRef Values) { ID.AddString(Kind); - if (!Values.empty()) ID.AddString(Values); + if (!Values.empty()) + ID.AddString(Values); } // FIXME: Remove this! @@ -138,6 +158,44 @@ StringRef getStringValue() const { return Val; } }; +class PseudoCallAttributeImpl : public EnumAttributeImpl, CallbackVH { + std::vector Args; + /// Is no longer accessible through the AttrsSet field in LLVMContextImpl + bool IsDefunct; + + void anchor() override; + + bool repositionInAttrsSet(LLVMContextImpl &, Function *NewPtr); + void noteChanged(Value *V); + void deleted() override { noteChanged(nullptr); } + void allUsesReplacedWith(Value *V) override { + assert(isa(V) && + "Pseudo-call function replaced with non-constant?"); + noteChanged(V); + } + + bool isInvalid() const { return getValPtr() == nullptr; } + Constant *getFn() const { + assert(!isInvalid() && "Invalid access of PseudoCallAttribute"); + return cast(getValPtr()); + } + +public: + PseudoCallAttributeImpl(Attribute::AttrKind Kind, + const Attribute::PseudoCall &Call) + : EnumAttributeImpl(PseudoCallEntry, Kind), CallbackVH(Call.Fn), + Args(Call.Args.begin(), Call.Args.end()), IsDefunct(false) { + assert(Kind == Attribute::AllocSize && "Wrong kind for value attribute!"); + assert(Call.Fn && "Pseudo-call function may not be null."); + assert((isa(Call.Fn->getType()) || + (Call.Fn->getType()->isPointerTy() && + isa(Call.Fn->getType()->getPointerElementType()))) && + "Pseudo-call function isn't a Function or function pointer?"); + } + + Attribute::PseudoCall getValue() const { return {getFn(), Args}; } +}; + //===----------------------------------------------------------------------===// /// \class /// \brief This class represents a group of attributes that apply to one @@ -171,6 +229,7 @@ unsigned getStackAlignment() const; uint64_t getDereferenceableBytes() const; uint64_t getDereferenceableOrNullBytes() const; + Attribute::PseudoCall getAllocSizeArgs() const; std::string getAsString(bool InAttrGrp) const; typedef const Attribute *iterator; @@ -266,7 +325,7 @@ Profile(ID, makeArrayRef(getNode(0), getNumAttributes())); } static void Profile(FoldingSetNodeID &ID, - ArrayRef > Nodes) { + ArrayRef> Nodes) { for (unsigned i = 0, e = Nodes.size(); i != e; ++i) { ID.AddInteger(Nodes[i].first); ID.AddPointer(Nodes[i].second); Index: lib/IR/Attributes.cpp =================================================================== --- lib/IR/Attributes.cpp +++ lib/IR/Attributes.cpp @@ -19,6 +19,7 @@ #include "LLVMContextImpl.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/IR/IRPrintingPasses.h" #include "llvm/IR/Type.h" #include "llvm/Support/Atomic.h" #include "llvm/Support/Debug.h" @@ -32,48 +33,54 @@ // Attribute Construction Methods //===----------------------------------------------------------------------===// -Attribute Attribute::get(LLVMContext &Context, Attribute::AttrKind Kind, - uint64_t Val) { - LLVMContextImpl *pImpl = Context.pImpl; - FoldingSetNodeID ID; - ID.AddInteger(Kind); - if (Val) ID.AddInteger(Val); - +/// Gets the attribute with the ID `ID`, or adds a new one if it doesn't exist. +/// Uses `MakeNewAttr`, a zero-arg function, to make the new attribute. +template +static AttributeImpl *getOrAddAttribute(LLVMContextImpl &Ctx, + const FoldingSetNodeID &ID, + const Func &MakeNewAttr) { void *InsertPoint; - AttributeImpl *PA = pImpl->AttrsSet.FindNodeOrInsertPos(ID, InsertPoint); - - if (!PA) { - // If we didn't find any existing attributes of the same shape then create a - // new one and insert it. - if (!Val) - PA = new EnumAttributeImpl(Kind); - else - PA = new IntAttributeImpl(Kind, Val); - pImpl->AttrsSet.InsertNode(PA, InsertPoint); - } + if (AttributeImpl *PA = Ctx.AttrsSet.FindNodeOrInsertPos(ID, InsertPoint)) + return PA; - // Return the Attribute that we found or created. - return Attribute(PA); + // If we didn't find any existing attributes of the same shape then create a + // new one and insert it. + AttributeImpl *NewAttr = MakeNewAttr(); + Ctx.AttrsSet.InsertNode(NewAttr, InsertPoint); + return NewAttr; } -Attribute Attribute::get(LLVMContext &Context, StringRef Kind, StringRef Val) { - LLVMContextImpl *pImpl = Context.pImpl; - FoldingSetNodeID ID; - ID.AddString(Kind); - if (!Val.empty()) ID.AddString(Val); +Attribute Attribute::get(LLVMContext &Context, Attribute::AttrKind Kind, + const PseudoCall &Val) { + assert(Val.Fn && "Null values aren't allowed in PseudoCall"); - void *InsertPoint; - AttributeImpl *PA = pImpl->AttrsSet.FindNodeOrInsertPos(ID, InsertPoint); + FoldingSetNodeID ID; + AttributeImpl::Profile(ID, Kind, Val); + AttributeImpl *Result = getOrAddAttribute(*Context.pImpl, ID, [&] { + return new PseudoCallAttributeImpl(Kind, Val); + }); + return Attribute(Result); +} - if (!PA) { - // If we didn't find any existing attributes of the same shape then create a - // new one and insert it. - PA = new StringAttributeImpl(Kind, Val); - pImpl->AttrsSet.InsertNode(PA, InsertPoint); - } +Attribute Attribute::get(LLVMContext &Context, Attribute::AttrKind Kind, + uint64_t Val) { + FoldingSetNodeID ID; + AttributeImpl::Profile(ID, Kind, Val); + AttributeImpl *Res = + getOrAddAttribute(*Context.pImpl, ID, [&]() -> AttributeImpl * { + if (Val) + return new IntAttributeImpl(Kind, Val); + return new EnumAttributeImpl(Kind); + }); + return Attribute(Res); +} - // Return the Attribute that we found or created. - return Attribute(PA); +Attribute Attribute::get(LLVMContext &Context, StringRef Kind, StringRef Val) { + FoldingSetNodeID ID; + AttributeImpl::Profile(ID, Kind, Val); + AttributeImpl *Res = getOrAddAttribute( + *Context.pImpl, ID, [&] { return new StringAttributeImpl(Kind, Val); }); + return Attribute(Res); } Attribute Attribute::getWithAlignment(LLVMContext &Context, uint64_t Align) { @@ -101,6 +108,12 @@ return get(Context, DereferenceableOrNull, Bytes); } +Attribute Attribute::getWithAllocSize(LLVMContext &Context, + const Attribute::PseudoCall &Args) { + assert(Args.Fn && "allocsize can't take null values"); + return get(Context, AllocSize, Args); +} + //===----------------------------------------------------------------------===// // Attribute Accessor Methods //===----------------------------------------------------------------------===// @@ -113,14 +126,17 @@ return pImpl && pImpl->isIntAttribute(); } +bool Attribute::isPseudoCallAttribute() const { + return pImpl && pImpl->isPseudoCallAttribute(); +} + bool Attribute::isStringAttribute() const { return pImpl && pImpl->isStringAttribute(); } Attribute::AttrKind Attribute::getKindAsEnum() const { if (!pImpl) return None; - assert((isEnumAttribute() || isIntAttribute()) && - "Invalid attribute type to get the kind as an enum!"); + assert(hasEnumKind() && "Invalid attribute type to get the kind as an enum!"); return pImpl->getKindAsEnum(); } @@ -131,6 +147,13 @@ return pImpl->getValueAsInt(); } +Attribute::PseudoCall Attribute::getValueAsPseudoCall() const { + if (!pImpl) return PseudoCall(); + assert(isPseudoCallAttribute() && + "Expected the attribute to be a pseudo-call attribute!"); + return pImpl->getValueAsPseudoCall(); +} + StringRef Attribute::getKindAsString() const { if (!pImpl) return StringRef(); assert(isStringAttribute() && @@ -184,6 +207,12 @@ return pImpl->getValueAsInt(); } +Attribute::PseudoCall Attribute::getAllocSizeArgs() const { + assert(hasAttribute(Attribute::AllocSize) && + "Trying to get allocsize args from non-allocsize attribute"); + return pImpl->getValueAsPseudoCall(); +} + std::string Attribute::getAsString(bool InAttrGrp) const { if (!pImpl) return ""; @@ -312,6 +341,38 @@ if (hasAttribute(Attribute::DereferenceableOrNull)) return AttrWithBytesToString("dereferenceable_or_null"); + if (hasAttribute(Attribute::AllocSize)) { + // allocsize(FnTy @FnName, [ArgNo1, ArgNo2, ...]) + std::string Result; + llvm::raw_string_ostream OS(Result); + PseudoCall Call = getValueAsPseudoCall(); + + OS << "allocsize("; + + if (Call.hasPlaceholderFunction()) + OS << "[[Placeholder]]"; + else { + Function *Fn = Call.mustGetCalledFunction(); + + Fn->getType()->print(OS); + OS << " @"; + // FIXME: hasName required? + if (Fn->hasName()) + printLLVMNameWithoutPrefix(OS, Fn->getName()); + } + + OS << ", ["; + for (unsigned I = 0, E = Call.Args.size(); I != E; ++I) { + if (I) + OS << ", "; + OS << Call.Args[I]; + } + OS << "])"; + + OS.flush(); + return Result; + } + // Convert target-dependent attributes to strings of the form: // // "kind" @@ -331,6 +392,20 @@ llvm_unreachable("Unknown attribute"); } +bool Attribute::deepEqual(Attribute Other) const { + assert(pImpl != Other.pImpl && + "Use operator== to check for pointer equality"); + // Immutable attributes will always be uniqued, and pointer comparisons work + // 100% of the time. + if (isImmutable() || Other.isImmutable()) + return false; + + PseudoCall Ours = getValueAsPseudoCall(); + PseudoCall Theirs = Other.getValueAsPseudoCall(); + return Ours.Fn == Theirs.Fn && Ours.Args.size() == Theirs.Args.size() && + std::equal(Ours.Args.begin(), Ours.Args.end(), Theirs.Args.begin()); +} + bool Attribute::operator<(Attribute A) const { if (!pImpl && !A.pImpl) return false; if (!pImpl) return true; @@ -338,6 +413,14 @@ return *pImpl < *A.pImpl; } +bool Attribute::PseudoCall::hasPlaceholderFunction() const { + return !isa(Fn); +} + +Function *Attribute::PseudoCall::mustGetCalledFunction() const { + return cast(Fn); +} + //===----------------------------------------------------------------------===// // AttributeImpl Definition //===----------------------------------------------------------------------===// @@ -346,6 +429,7 @@ AttributeImpl::~AttributeImpl() {} void EnumAttributeImpl::anchor() {} void IntAttributeImpl::anchor() {} +void PseudoCallAttributeImpl::anchor() {} void StringAttributeImpl::anchor() {} bool AttributeImpl::hasAttribute(Attribute::AttrKind A) const { @@ -359,7 +443,7 @@ } Attribute::AttrKind AttributeImpl::getKindAsEnum() const { - assert(isEnumAttribute() || isIntAttribute()); + assert(hasEnumKind()); return static_cast(this)->getEnumKind(); } @@ -368,6 +452,11 @@ return static_cast(this)->getValue(); } +Attribute::PseudoCall AttributeImpl::getValueAsPseudoCall() const { + assert(isPseudoCallAttribute()); + return static_cast(this)->getValue(); +} + StringRef AttributeImpl::getKindAsString() const { assert(isStringAttribute()); return static_cast(this)->getStringKind(); @@ -384,17 +473,40 @@ if (isEnumAttribute()) { if (AI.isEnumAttribute()) return getKindAsEnum() < AI.getKindAsEnum(); if (AI.isIntAttribute()) return true; + if (AI.isPseudoCallAttribute()) return true; if (AI.isStringAttribute()) return true; } if (isIntAttribute()) { if (AI.isEnumAttribute()) return false; if (AI.isIntAttribute()) return getValueAsInt() < AI.getValueAsInt(); + if (AI.isPseudoCallAttribute()) return true; + if (AI.isStringAttribute()) return true; + } + + if (isPseudoCallAttribute()) { + if (AI.isEnumAttribute()) return false; + if (AI.isIntAttribute()) return false; + if (AI.isPseudoCallAttribute()) { + Attribute::PseudoCall Ours = getValueAsPseudoCall(); + Attribute::PseudoCall Theirs = getValueAsPseudoCall(); + if (Ours.Fn != Theirs.Fn) { + if (Ours.Fn->hasName() || Theirs.Fn->hasName()) + return Ours.Fn->getName() < Theirs.Fn->getName(); + // No names. Cheap out and order on address. + return Ours.Fn < Theirs.Fn; + } + + return std::lexicographical_compare( + Ours.Args.begin(), Ours.Args.end(), Theirs.Args.begin(), + Theirs.Args.end()); + } if (AI.isStringAttribute()) return true; } if (AI.isEnumAttribute()) return false; if (AI.isIntAttribute()) return false; + if (AI.isPseudoCallAttribute()) return false; if (getKindAsString() == AI.getKindAsString()) return getValueAsString() < AI.getValueAsString(); return getKindAsString() < AI.getKindAsString(); @@ -462,11 +574,74 @@ case Attribute::ArgMemOnly: llvm_unreachable("argmemonly attribute not supported in raw format"); break; + case Attribute::AllocSize: + llvm_unreachable("allocsize attribute not supported in raw format"); + break; } llvm_unreachable("Unsupported attribute type"); } //===----------------------------------------------------------------------===// +// PseudoCallAttributeImpl Definition +//===----------------------------------------------------------------------===// + +/// Removes the PseudoCallAttributeImpl from the AttrsSet in Ctx. Returns +/// whether it was reinserted, and updates the ValPtr. +bool PseudoCallAttributeImpl::repositionInAttrsSet(LLVMContextImpl &Ctx, + Function *NewPtr) { + assert(!IsDefunct && + "Defunct Pseudo-call attribute isn't in the set to begin with"); + + bool Removed = Ctx.AttrsSet.RemoveNode(this); + assert(Removed && "Non-defunct pseudo-call attribute not in the map?"); + (void)Removed; + + // AttrsSet may profile us on its own, so we need to set the value ptr here. + setValPtr(NewPtr); + + // We can't create a PseudoCallAttribtueImpl with a null Function, so + // there's no point in not making this defunct if the Function has been + // deleted. + if (!NewPtr) + return false; + + void *NewSlot; + FoldingSetNodeID ID; + Profile(ID); + if (Ctx.AttrsSet.FindNodeOrInsertPos(ID, NewSlot)) + return false; + + Ctx.AttrsSet.InsertNode(this, NewSlot); + return true; +} + +// FIXME: As the deletion order stands, LLVMContextImpls delete all of their +// Functions, then delete attributes. This moves all PseudoCallAttributes out of +// the Attribute FoldingSet, and into the DefunctAttrs vector. They later free +// everything in the DefunctAttrs vector. While this is suboptimal, there are +// only generally a few PseudoCallAttributes per module, so it's not a massive +// deal. +void PseudoCallAttributeImpl::noteChanged(Value *NewValPtr) { + assert(!isInvalid() && "RAUWd a null function?!"); + // It's possible to have a PseudoCallAttribute that is both defunct and valid. + // This occurs when a PseudoCallAttribute's Function gets RAUW'd such that it + // is identical to another PseudoCallAttribute. In that case, we have two + // identical attributes, one of which must become defunct (but which must also + // remain up-to-date with other RAUW's). + if (IsDefunct) { + setValPtr(cast_or_null(NewValPtr)); + return; + } + + LLVMContextImpl &Ctx = *getValPtr()->getContext().pImpl; + if (!repositionInAttrsSet(Ctx, cast_or_null(NewValPtr))) { + IsDefunct = true; + Ctx.DefunctAttrs.emplace_back(this); + } +} + + +//===----------------------------------------------------------------------===// // AttributeSetNode Definition //===----------------------------------------------------------------------===// @@ -558,6 +733,13 @@ return 0; } +Attribute::PseudoCall AttributeSetNode::getAllocSizeArgs() const { + for (iterator I = begin(), E = end(); I != E; ++I) + if (I->hasAttribute(Attribute::AllocSize)) + return I->getAllocSizeArgs(); + return {}; +} + std::string AttributeSetNode::getAsString(bool InAttrGrp) const { std::string Str; for (iterator I = begin(), E = end(); I != E; ++I) { @@ -593,6 +775,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); } @@ -708,6 +892,9 @@ Attr = Attribute::getWithDereferenceableOrNullBytes( C, B.getDereferenceableOrNullBytes()); break; + case Attribute::AllocSize: + Attr = Attribute::getWithAllocSize(C, B.getAllocSizeArgs()); + break; default: Attr = Attribute::get(C, Kind); } @@ -959,6 +1146,14 @@ return addAttributes(C, Index, AttributeSet::get(C, Index, B)); } +AttributeSet +AttributeSet::addAllocSizeAttr(LLVMContext &C, unsigned Index, + const Attribute::PseudoCall &Args) { + llvm::AttrBuilder B; + B.addAllocSizeAttr(Args); + return addAttributes(C, Index, AttributeSet::get(C, Index, B)); +} + //===----------------------------------------------------------------------===// // AttributeSet Accessor Methods //===----------------------------------------------------------------------===// @@ -1054,8 +1249,12 @@ return ASN ? ASN->getDereferenceableOrNullBytes() : 0; } -std::string AttributeSet::getAsString(unsigned Index, - bool InAttrGrp) const { +Attribute::PseudoCall AttributeSet::getAllocSizeArgs(unsigned Index) const { + AttributeSetNode *ASN = getAttributes(Index); + return ASN ? ASN->getAllocSizeArgs() : Attribute::PseudoCall{}; +} + +std::string AttributeSet::getAsString(unsigned Index, bool InAttrGrp) const { AttributeSetNode *ASN = getAttributes(Index); return ASN ? ASN->getAsString(InAttrGrp) : std::string(""); } @@ -1134,7 +1333,7 @@ AttrBuilder::AttrBuilder(AttributeSet AS, unsigned Index) : Attrs(0), Alignment(0), StackAlignment(0), DerefBytes(0), - DerefOrNullBytes(0) { + DerefOrNullBytes(0), AllocSizeFn(nullptr) { AttributeSetImpl *pImpl = AS.pImpl; if (!pImpl) return; @@ -1153,13 +1352,17 @@ Attrs.reset(); TargetDepAttrs.clear(); Alignment = StackAlignment = DerefBytes = DerefOrNullBytes = 0; + AllocSizeFn = nullptr; + AllocSizeArgNos.clear(); } 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!"); + assert(Val != Attribute::AllocSize && + "Adding pseudo-call attribute without a value"); Attrs[Val] = true; return *this; } @@ -1181,6 +1384,12 @@ DerefBytes = Attr.getDereferenceableBytes(); else if (Kind == Attribute::DereferenceableOrNull) DerefOrNullBytes = Attr.getDereferenceableOrNullBytes(); + else if (Kind == Attribute::AllocSize) { + Attribute::PseudoCall Call = Attr.getAllocSizeArgs(); + AllocSizeFn = Call.Fn; + AllocSizeArgNos.clear(); + AllocSizeArgNos.append(Call.Args.begin(), Call.Args.end()); + } return *this; } @@ -1201,6 +1410,10 @@ DerefBytes = 0; else if (Val == Attribute::DereferenceableOrNull) DerefOrNullBytes = 0; + else if (Val == Attribute::AllocSize) { + AllocSizeFn = nullptr; + AllocSizeArgNos.clear(); + } return *this; } @@ -1217,7 +1430,7 @@ for (AttributeSet::iterator I = A.begin(Slot), E = A.end(Slot); I != E; ++I) { Attribute Attr = *I; - if (Attr.isEnumAttribute() || Attr.isIntAttribute()) { + if (Attr.hasEnumKind()) { removeAttribute(Attr.getKindAsEnum()); } else { assert(Attr.isStringAttribute() && "Invalid attribute type!"); @@ -1235,6 +1448,10 @@ return *this; } +Attribute::PseudoCall AttrBuilder::getAllocSizeArgs() const { + return {AllocSizeFn, AllocSizeArgNos}; +} + AttrBuilder &AttrBuilder::addAlignmentAttr(unsigned Align) { if (Align == 0) return *this; @@ -1275,6 +1492,30 @@ return *this; } +AttrBuilder & +AttrBuilder::addAllocSizeAttr(const Attribute::PseudoCall &Call) { + // (0, 0) is our "not present" value, so we need to check for it here. + assert(Call.Fn && "allocsize can't take null values"); + + Attrs[Attribute::AllocSize] = true; + AllocSizeFn = Call.Fn; + AllocSizeArgNos.clear(); + AllocSizeArgNos.append(Call.Args.begin(), Call.Args.end()); + return *this; +} + +AttrBuilder & +AttrBuilder::addAllocSizeAttrWithPlaceholder(Constant *Fn, + ArrayRef Args) { + assert(Fn && "allocsize can't take null values"); + + Attrs[Attribute::AllocSize] = true; + AllocSizeFn = Fn; + AllocSizeArgNos.clear(); + AllocSizeArgNos.append(Args.begin(), Args.end()); + return *this; +} + AttrBuilder &AttrBuilder::merge(const AttrBuilder &B) { // FIXME: What if both have alignments, but they don't match?! if (!Alignment) @@ -1289,6 +1530,11 @@ if (!DerefOrNullBytes) DerefOrNullBytes = B.DerefOrNullBytes; + if (!AllocSizeFn) { + AllocSizeFn = B.AllocSizeFn; + AllocSizeArgNos = B.AllocSizeArgNos; + } + Attrs |= B.Attrs; for (auto I : B.td_attrs()) @@ -1311,6 +1557,11 @@ if (B.DerefOrNullBytes) DerefOrNullBytes = 0; + if (B.AllocSizeFn) { + AllocSizeFn = nullptr; + AllocSizeArgNos.clear(); + } + Attrs &= ~B.Attrs; for (auto I : B.td_attrs()) @@ -1352,7 +1603,7 @@ for (AttributeSet::iterator I = A.begin(Slot), E = A.end(Slot); I != E; ++I) { Attribute Attr = *I; - if (Attr.isEnumAttribute() || Attr.isIntAttribute()) { + if (Attr.hasEnumKind()) { if (Attrs[I->getKindAsEnum()]) return true; } else { @@ -1389,7 +1640,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/LLVMContextImpl.h =================================================================== --- lib/IR/LLVMContextImpl.h +++ lib/IR/LLVMContextImpl.h @@ -925,6 +925,11 @@ FoldingSet AttrsLists; FoldingSet AttrsSetNodes; + // Pseudo-call attributes that either had their functions deleted, or had + // their function changed such that they were identical to a preexisting + // pseudo-call attribute. + SmallVector, 8> DefunctAttrs; + StringMap MDStringCache; DenseMap ValuesAsMetadata; DenseMap MetadataAsValues; Index: lib/IR/Verifier.cpp =================================================================== --- lib/IR/Verifier.cpp +++ lib/IR/Verifier.cpp @@ -1310,7 +1310,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); @@ -1517,6 +1518,44 @@ Assert(GV->hasUnnamedAddr(), "Attribute 'jumptable' requires 'unnamed_addr'", V); } + + if (Attrs.hasAttribute(AttributeSet::FunctionIndex, Attribute::AllocSize)) { + Attribute::PseudoCall Call = + Attrs.getAllocSizeArgs(AttributeSet::FunctionIndex); + + Assert(!Call.hasPlaceholderFunction(), + "Pseudo-calls must have actual Functions."); + + Function *TargetFn = Call.mustGetCalledFunction(); + FunctionType *TargetFT = TargetFn->getFunctionType(); + auto *RetTy = dyn_cast(TargetFT->getReturnType()); + Assert(RetTy && RetTy->getNumElements() == 2 && + RetTy->getElementType(0)->isIntegerTy() && + RetTy->getElementType(1)->isIntegerTy(), + "Function used in allocsize should return a struct of two ints", V); + + ArrayRef TargetParams = TargetFT->params(); + Assert(Call.Args.size() == TargetParams.size(), + "Mismatch between allocsize argument array length and allocsize " + "target arity", + V); + + ArrayRef BaseFnParams = FT->params(); + for (unsigned I = 0, E = Call.Args.size(); I != E; ++I) { + unsigned ParamNo = Call.Args[I]; + Assert(ParamNo < BaseFnParams.size(), + "Out of bounds allocsize parameter index", V); + + Assert(BaseFnParams[ParamNo] == TargetParams[I], + Twine("Type of paramater #") + utostr(ParamNo) + + " doesn't match type of allocsize target parameter #" + + utostr(I), + V); + } + + Assert(!TargetFn->isDeclaration(), + "Function used in allocsize must have a visible definition", V); + } } void Verifier::VerifyFunctionMetadata( Index: test/Bitcode/allocsize.ll =================================================================== --- /dev/null +++ test/Bitcode/allocsize.ll @@ -0,0 +1,14 @@ +; RUN: llvm-as %s -o - | llvm-dis | FileCheck %s +; RUN: verify-uselistorder < %s + +; CHECK: Function Attrs: allocsize({ i32, i32 } (i32, i32)* @_check_allocsize, [1, 2]) +; CHECK: declare void @test_allocsize +declare void @test_allocsize(i8*, i32, i32) allocsize({i32, i32}(i32, i32)* @_check_allocsize, [1, 2]) + +define {i32, i32} @_check_allocsize(i32, i32) { + ret {i32, i32} {i32 0, i32 0} +} + +@llvm.compiler.used = appending global [1 x i8*] [ + i8* bitcast ({i32, i32}(i32, i32)* @_check_allocsize to i8*) +] Index: test/Transforms/InstCombine/allocsize.ll =================================================================== --- /dev/null +++ test/Transforms/InstCombine/allocsize.ll @@ -0,0 +1,206 @@ +; RUN: opt < %s -instcombine -S | FileCheck %s +; +; Test that instcombine folds allocsize function calls properly. +; All allocation functions are assumed to return a block of memory N bytes +; long. We expect that the returned pointer is 4 bytes into said block of +; memory, to simulate an object header (or similar). + +declare i8* @my_malloc(i8*, i32) allocsize({i32, i32}(i32)* @malloc_compute, [1]) + +declare i8* @my_calloc(i8*, i8*, i32, i32) allocsize( + {i32, i32}(i32, i32)* @calloc_compute, [2, 3]) + +; Checking core functionality... + +; 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 96 + store i64 %2, i64* %r, align 8 + + %3 = call i8* @my_malloc(i8* null, i32 -1) + store i8* %3, i8** %p, align 8 ; To ensure objectsize isn't killed + + ; CHECK: @llvm.objectsize + %4 = call i64 @llvm.objectsize.i64.p0i8(i8* %3, i1 false) + store i64 %4, 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 496 + store i64 %2, i64* %r, align 8 + + %3 = call i8* @my_calloc(i8* null, i8* null, i32 100, i32 -1) + store i8* %3, i8** %p, align 8 ; To ensure objectsize isn't killed + + ; CHECK: @llvm.objectsize + %4 = call i64 @llvm.objectsize.i64.p0i8(i8* %3, i1 false) + store i64 %4, i64* %r, align 8 + + %5 = call i8* @my_calloc(i8* null, i8* null, i32 -1, i32 100) + store i8* %5, i8** %p, align 8 ; To ensure objectsize isn't killed + + ; CHECK: @llvm.objectsize + %6 = call i64 @llvm.objectsize.i64.p0i8(i8* %5, i1 false) + store i64 %6, i64* %r, align 8 + ret void +} + +declare i64 @llvm.objectsize.i64.p0i8(i8*, i1) + +define {i32, i32} @malloc_compute(i32 %i) { + %1 = insertvalue {i32, i32} {i32 0, i32 4}, i32 %i, 0 + ret {i32, i32} %1 +} + +define {i32, i32} @calloc_compute(i32 %i, i32 %j) { + %1 = mul nsw i32 %i, %j + %2 = insertvalue {i32, i32} {i32 0, i32 4}, i32 %1, 0 + ret {i32, i32} %2 +} + +; 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 96 + 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 496 + store i64 %4, i64* %r, align 8 + ret void +} + +; Edge cases, yay. + +declare i8* @malloc_constant(i8* %p) allocsize({i32, i32}()* @compute_constant) +declare i8* @malloc_bad(i1 %badalign) allocsize( + {i32, i32}(i1)* @compute_bad_constant, [0]) + +; CHECK-LABEL: define void @test_constant +define void @test_constant(i8** %p, i64* %r) { + %1 = call i8* @malloc_constant(i8* null) + 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 8 + store i64 %2, i64* %r, align 8 + + %3 = call i8* @malloc_bad(i1 true) + store i8* %3, i8** %p, align 8 ; To ensure objectsize isn't killed + + ; CHECK: @llvm.objectsize + %4 = call i64 @llvm.objectsize.i64.p0i8(i8* %3, i1 false) + store i64 %4, i64* %r, align 8 + + %5 = call i8* @malloc_bad(i1 false) + store i8* %5, i8** %p, align 8 ; To ensure objectsize isn't killed + + ; CHECK: @llvm.objectsize + %6 = call i64 @llvm.objectsize.i64.p0i8(i8* %5, i1 false) + store i64 %6, i64* %r, align 8 + ret void +} + +define {i32, i32} @compute_constant() { ret {i32, i32} {i32 16, i32 8} } + +define {i32, i32} @compute_bad_constant(i1 %badalign) { + %1 = select i1 %badalign, {i32, i32} {i32 4, i32 8}, {i32, i32} {i32 -4, i32 0} + ret {i32, i32} %1 +} + +; Test with arbitrary (foldable) pointers, as well. + +declare i8* @malloc_fold(i8* %p, i32 %n) allocsize( + {i32, i32}(i8*, i32)* @malloc_compute2, [0, 1]) + +declare i8* @calloc_fold(i8* %p, i8* %u, i32 %n, i32 %m) allocsize( + {i32, i32}(i8*, i8*, i32, i32)* @calloc_compute2, [0, 1, 2, 3]) + +; CHECK-LABEL: define void @test_malloc_calloc_fold +define void @test_malloc_calloc_fold(i8** %p, i64* %r) { + %1 = call i8* @malloc_fold(i8* null, i32 32) + %2 = call i64 @llvm.objectsize.i64.p0i8(i8* %1, i1 false) + store i64 %2, i64* %r, align 8 + + %3 = call i8* @calloc_fold(i8* null, i8* null, i32 8, i32 8) + store i8* %3, i8** %p, align 8 + %4 = call i64 @llvm.objectsize.i64.p0i8(i8* %3, i1 false) + ; CHECK: store i64 60 + store i64 %4, i64* %r, align 8 + ret void +} + +attributes #0 = { allocsize({i32, i32}(i32)* @malloc_compute, [1]) } +attributes #1 = { allocsize({i32, i32}(i32, i32)* @calloc_compute, [2, 3]) } + +; Manually inlined @malloc_compute because we don't run the inliner pass. +define private {i32, i32} @malloc_compute2(i8* %_, i32 %i) unnamed_addr { + %1 = sub nsw i32 %i, 4 + %2 = insertvalue {i32, i32} {i32 0, i32 4}, i32 %1, 0 + ret {i32, i32} %2 +} + +; Manually inlined @calloc_compute because we don't run the inliner pass. +define {i32, i32} @calloc_compute2(i8* %_, i8* %_2, i32 %i, i32 %j) { + %1 = mul nsw i32 %i, %j + %2 = insertvalue {i32, i32} {i32 0, i32 4}, i32 %1, 0 + ret {i32, i32} %2 +} + +@llvm.compiler.used = appending global [4 x i8*] [ + i8* bitcast ({i32, i32}(i32)* @malloc_compute to i8*), + i8* bitcast ({i32, i32}(i32, i32)* @calloc_compute to i8*), + i8* bitcast ({i32, i32}(i8*, i32)* @malloc_compute2 to i8*), + i8* bitcast ({i32, i32}(i8*, i8*, i32, i32)* @calloc_compute2 to i8*) +] 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: expected function +declare i8* @a(i32, i32) allocsize(i32 0) Index: test/Verifier/allocsize.ll =================================================================== --- /dev/null +++ test/Verifier/allocsize.ll @@ -0,0 +1,44 @@ +; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s + +; CHECK: Function used in allocsize should return a struct of two ints +declare i8* @a(i32) allocsize(i32(i32)* @bad_return1, [0]) + +; CHECK: Function used in allocsize should return a struct of two ints +declare i8* @b(i32) allocsize({i32, double}(i32)* @bad_return2, [0]) + +; CHECK: Function used in allocsize should return a struct of two ints +declare i8* @c(i32) allocsize({i32}(i32)* @bad_return3, [0]) + +; CHECK: Function used in allocsize should return a struct of two ints +declare i8* @d(i32) allocsize({i32, i32, i32}(i32)* @bad_return4, [0]) + +; CHECK: Function used in allocsize must have a visible definition +declare i8* @e(i32) allocsize({i32, i32}(i32)* @just_decl, [0]) + +; CHECK: Mismatch between allocsize argument array length and allocsize target arity +declare i8* @f(i32, i32) allocsize({i32, i32}(i32)* @good_return_1, [0, 1]) + +; CHECK: Mismatch between allocsize argument array length and allocsize target arity +declare i8* @g(i32) allocsize({i32, i32}(i32, i32)* @good_return_2, [0]) + +; CHECK: Out of bounds allocsize parameter index +declare i8* @h(i32) allocsize({i32, i32}(i32, i32)* @good_return_2, [0, 1]) + +; CHECK: Out of bounds allocsize parameter index +declare i8* @i(i32) allocsize({i32, i32}(i32, i32)* @good_return_2, [1, 0]) + +; CHECK: Out of bounds allocsize parameter index +declare i8* @j(i32, i32) allocsize({i32, i32}(i32)* @good_return_1, [2]) + +; CHECK: Type of paramater #1 doesn't match type of allocsize target parameter #0 +declare i8* @k(i32, i8*) allocsize({i32, i32}(i32)* @good_return_1, [1]) + +define {i32, i32} @good_return_1(i32) { ret {i32, i32} zeroinitializer } +define {i32, i32} @good_return_2(i32, i32) { ret {i32, i32} zeroinitializer } + +define i32 @bad_return1(i32) { ret i32 zeroinitializer } +define {i32, double} @bad_return2(i32) { ret {i32, double} zeroinitializer } +define {i32} @bad_return3(i32) { ret {i32} zeroinitializer } +define {i32, i32, i32} @bad_return4(i32) { ret {i32, i32, i32} zeroinitializer } + +declare {i32, i32} @just_decl(i32)