Index: llvm/docs/LangRef.rst =================================================================== --- llvm/docs/LangRef.rst +++ llvm/docs/LangRef.rst @@ -1066,6 +1066,30 @@ site. If the alignment is not specified, then the code generator makes a target-specific assumption. +.. _attr_byref: + +``byref()`` + + The ``byref`` argument attribute allows specifying the pointee in + memory size in bytes of an argument. This is similar to ``byval``, + but does not imply a copy is made anywhere, or that the argument + is passed on the stack. This implies the pointer is + dereferenceable up to the storage size. + + It is not generally permissible to introduce a write to an + ``byref`` pointer. The pointer may have any address space and may + be read only. + + This is not a valid attribute for return values. + + The alignment for a ``byref`` parameter can be explicitly + specified by combining it with the ``align`` attribute, similar to + ``byval``. If the alignment is not specified, this is assumed to + have an alignment of 1. + + This is intended for representing ABI constraints, and is not + intended to be inferred for optimization use. + .. _attr_preallocated: ``preallocated()`` Index: llvm/docs/ReleaseNotes.rst =================================================================== --- llvm/docs/ReleaseNotes.rst +++ llvm/docs/ReleaseNotes.rst @@ -74,6 +74,9 @@ information. This information is used to represent Fortran modules debug info at IR level. +* Added the ``byref`` attribute to better represent argument passing + for the `amdgpu_kernel` calling convention. + Changes to building LLVM ------------------------ @@ -134,6 +137,9 @@ retain the old behavior should explicitly request f32 denormal flushing. +* The new ``byref`` attribute is now the preferred method for + representing aggregate kernel arguments. + Changes to the AVR Target ----------------------------- Index: llvm/include/llvm/Bitcode/LLVMBitCodes.h =================================================================== --- llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -644,6 +644,7 @@ ATTR_KIND_NO_MERGE = 66, ATTR_KIND_NULL_POINTER_IS_VALID = 67, ATTR_KIND_NOUNDEF = 68, + ATTR_KIND_BYREF = 69, }; enum ComdatSelectionKindCodes { Index: llvm/include/llvm/IR/Argument.h =================================================================== --- llvm/include/llvm/IR/Argument.h +++ llvm/include/llvm/IR/Argument.h @@ -62,9 +62,16 @@ /// number of bytes known to be dereferenceable. Otherwise, zero is returned. uint64_t getDereferenceableOrNullBytes() const; + /// If this argument has the byref attribute, return the size. Otherwise, zero + /// is returned. + uint64_t getByRefBytes() const; + /// Return true if this argument has the byval attribute. bool hasByValAttr() const; + /// Return true if this argument has the byref attribute. + bool hasByRefAttr() const; + /// Return true if this argument has the swiftself attribute. bool hasSwiftSelfAttr() const; @@ -80,6 +87,15 @@ /// in-memory ABI size copied to the stack for the call. Otherwise, return 0. uint64_t getPassPointeeByValueCopySize(const DataLayout &DL) const; + /// Return true if this argument has the byval, inalloca, preallocated, or + /// byref attribute. These attributes represent arguments being passed by + /// value (which may or may not involve a stack copy) + bool hasPointeeInMemoryValueAttr() const; + + /// If this argument satisfies has hasPointeeInMemoryValueAttr, return the + /// in-memory ABI size copied to the stack for the call. Otherwise, return 0. + uint64_t getPointeeInMemoryValueSize(const DataLayout &DL) const; + /// If this is a byval or inalloca argument, return its alignment. /// FIXME: Remove this function once transition to Align is over. /// Use getParamAlign() instead. Index: llvm/include/llvm/IR/Attributes.h =================================================================== --- llvm/include/llvm/IR/Attributes.h +++ llvm/include/llvm/IR/Attributes.h @@ -104,6 +104,8 @@ uint64_t Bytes); static Attribute getWithDereferenceableOrNullBytes(LLVMContext &Context, uint64_t Bytes); + static Attribute getWithByRefBytes(LLVMContext &Context, uint64_t Bytes); + static Attribute getWithAllocSizeArgs(LLVMContext &Context, unsigned ElemSizeArg, const Optional &NumElemsArg); @@ -180,6 +182,9 @@ /// dereferenceable_or_null attribute. uint64_t getDereferenceableOrNullBytes() const; + /// Returns the number of bytes from the byref attribute. + uint64_t getByRefBytes() const; + /// Returns the argument numbers for the allocsize attribute (or pair(0, 0) /// if not known). std::pair> getAllocSizeArgs() const; @@ -302,6 +307,7 @@ MaybeAlign getStackAlignment() const; uint64_t getDereferenceableBytes() const; uint64_t getDereferenceableOrNullBytes() const; + uint64_t getByRefBytes() const; Type *getByValType() const; Type *getPreallocatedType() const; std::pair> getAllocSizeArgs() const; @@ -521,6 +527,9 @@ LLVM_NODISCARD AttributeList addDereferenceableOrNullAttr( LLVMContext &C, unsigned Index, uint64_t Bytes) const; + LLVM_NODISCARD AttributeList addByRefAttr(LLVMContext &C, unsigned Index, + uint64_t Bytes) const; + /// Add the dereferenceable_or_null attribute to the attribute set at /// the given arg index. Returns a new list because attribute lists are /// immutable. @@ -529,6 +538,11 @@ return addDereferenceableOrNullAttr(C, ArgNo + FirstArgIndex, Bytes); } + LLVM_NODISCARD AttributeList addByRefParamAttr(LLVMContext &C, unsigned ArgNo, + uint64_t Bytes) const { + return addByRefAttr(C, ArgNo + FirstArgIndex, Bytes); + } + /// Add the allocsize attribute to the attribute set at the given index. /// Returns a new list because attribute lists are immutable. LLVM_NODISCARD AttributeList @@ -651,6 +665,14 @@ return getDereferenceableOrNullBytes(ArgNo + FirstArgIndex); } + /// Get the number of byref bytes (or zero if unknown). + uint64_t getByRefBytes(unsigned Index) const; + + /// Get the number of byref bytes for a pointer argument. + uint64_t getParamByRefBytes(unsigned ArgNo) const { + return getByRefBytes(ArgNo + FirstArgIndex); + } + /// Get the allocsize argument numbers (or pair(0, 0) if unknown). std::pair> getAllocSizeArgs(unsigned Index) const; @@ -728,6 +750,7 @@ uint64_t DerefBytes = 0; uint64_t DerefOrNullBytes = 0; uint64_t AllocSizeArgs = 0; + uint64_t ByRefBytes = 0; Type *ByValType = nullptr; Type *PreallocatedType = nullptr; @@ -805,6 +828,9 @@ /// dereferenceable_or_null attribute exists (zero is returned otherwise). uint64_t getDereferenceableOrNullBytes() const { return DerefOrNullBytes; } + /// Retrieve the number of in-memory bytes. + uint64_t getByRefBytes() const { return ByRefBytes; } + /// Retrieve the byval type. Type *getByValType() const { return ByValType; } @@ -847,6 +873,8 @@ /// form used internally in Attribute. AttrBuilder &addDereferenceableOrNullAttr(uint64_t Bytes); + AttrBuilder &addByRefAttr(uint64_t Bytes); + /// This turns one (or two) ints into the form used internally in Attribute. AttrBuilder &addAllocSizeAttr(unsigned ElemSizeArg, const Optional &NumElemsArg); Index: llvm/include/llvm/IR/Attributes.td =================================================================== --- llvm/include/llvm/IR/Attributes.td +++ llvm/include/llvm/IR/Attributes.td @@ -42,6 +42,9 @@ /// Parameter or return value may not contain uninitialized or poison bits. def NoUndef : EnumAttr<"noundef">; +/// Mark in-memory ABI type. +def ByRef : IntAttr<"byref">; + /// Marks function as being in a cold path. def Cold : EnumAttr<"cold">; Index: llvm/include/llvm/IR/Function.h =================================================================== --- llvm/include/llvm/IR/Function.h +++ llvm/include/llvm/IR/Function.h @@ -494,6 +494,12 @@ return AttributeSets.getParamDereferenceableOrNullBytes(ArgNo); } + /// Extract the number of byref bytes for a parameter. + /// @param ArgNo AttributeList ArgNo, referring to an argument. + uint64_t getParamByRefBytes(unsigned ArgNo) const { + return AttributeSets.getParamByRefBytes(ArgNo); + } + /// Determine if the function does not access memory. bool doesNotAccessMemory() const { return hasFnAttribute(Attribute::ReadNone); Index: llvm/lib/Analysis/MemoryBuiltins.cpp =================================================================== --- llvm/lib/Analysis/MemoryBuiltins.cpp +++ llvm/lib/Analysis/MemoryBuiltins.cpp @@ -676,13 +676,14 @@ } SizeOffsetType ObjectSizeOffsetVisitor::visitArgument(Argument &A) { + uint64_t MemoryBytes = A.getPointeeInMemoryValueSize(DL); // No interprocedural analysis is done at the moment. - if (!A.hasPassPointeeByValueCopyAttr()) { + if (MemoryBytes == 0) { ++ObjectVisitorArgument; return unknown(); } - PointerType *PT = cast(A.getType()); - APInt Size(IntTyBits, DL.getTypeAllocSize(PT->getElementType())); + + APInt Size(IntTyBits, MemoryBytes); return std::make_pair(align(Size, A.getParamAlignment()), Zero); } Index: llvm/lib/AsmParser/LLLexer.cpp =================================================================== --- llvm/lib/AsmParser/LLLexer.cpp +++ llvm/lib/AsmParser/LLLexer.cpp @@ -697,6 +697,7 @@ KEYWORD(writeonly); KEYWORD(zeroext); KEYWORD(immarg); + KEYWORD(byref); KEYWORD(type); KEYWORD(opaque); Index: llvm/lib/AsmParser/LLParser.h =================================================================== --- llvm/lib/AsmParser/LLParser.h +++ llvm/lib/AsmParser/LLParser.h @@ -334,6 +334,7 @@ bool inAttrGrp, LocTy &BuiltinLoc); bool ParseByValWithOptionalType(Type *&Result); bool ParsePreallocated(Type *&Result); + bool ParseByRef(uint64_t &Bytes); // Module Summary Index Parsing. bool SkipModuleSummaryEntry(); Index: llvm/lib/AsmParser/LLParser.cpp =================================================================== --- llvm/lib/AsmParser/LLParser.cpp +++ llvm/lib/AsmParser/LLParser.cpp @@ -1382,6 +1382,7 @@ case lltok::kw_swifterror: case lltok::kw_swiftself: case lltok::kw_immarg: + case lltok::kw_byref: HaveError |= Error(Lex.getLoc(), "invalid use of parameter-only attribute on a function"); @@ -1675,6 +1676,13 @@ B.addDereferenceableOrNullAttr(Bytes); continue; } + case lltok::kw_byref: { + uint64_t Bytes; + if (ParseByRef(Bytes)) + return true; + B.addByRefAttr(Bytes); + continue; + } case lltok::kw_inalloca: B.addAttribute(Attribute::InAlloca); break; case lltok::kw_inreg: B.addAttribute(Attribute::InReg); break; case lltok::kw_nest: B.addAttribute(Attribute::Nest); break; @@ -1795,6 +1803,7 @@ case lltok::kw_swifterror: case lltok::kw_swiftself: case lltok::kw_immarg: + case lltok::kw_byref: HaveError |= Error(Lex.getLoc(), "invalid use of parameter-only attribute"); break; @@ -2583,6 +2592,25 @@ return false; } +/// ParseByRef +/// ::= byref() +bool LLParser::ParseByRef(uint64_t &Bytes) { + Bytes = 0; + if (!EatIfPresent(lltok::kw_byref)) + return true; + if (!EatIfPresent(lltok::lparen)) + return Error(Lex.getLoc(), "expected '('"); + + LocTy ByRefLoc = Lex.getLoc(); + if (ParseUInt64(Bytes)) + return true; + if (!EatIfPresent(lltok::rparen)) + return Error(Lex.getLoc(), "expected ')'"); + if (!Bytes) + return Error(ByRefLoc, "byref bytes must be non-zero"); + return false; +} + /// ParseOptionalOperandBundles /// ::= /*empty*/ /// ::= '[' OperandBundle [, OperandBundle ]* ']' Index: llvm/lib/AsmParser/LLToken.h =================================================================== --- llvm/lib/AsmParser/LLToken.h +++ llvm/lib/AsmParser/LLToken.h @@ -240,6 +240,7 @@ kw_writeonly, kw_zeroext, kw_immarg, + kw_byref, kw_type, kw_opaque, Index: llvm/lib/Bitcode/Reader/BitcodeReader.cpp =================================================================== --- llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -1532,6 +1532,8 @@ return Attribute::Preallocated; case bitc::ATTR_KIND_NOUNDEF: return Attribute::NoUndef; + case bitc::ATTR_KIND_BYREF: + return Attribute::ByRef; } } @@ -1622,6 +1624,8 @@ B.addDereferenceableOrNullAttr(Record[++i]); else if (Kind == Attribute::AllocSize) B.addAllocSizeAttrFromRawRepr(Record[++i]); + else if (Kind == Attribute::ByRef) + B.addByRefAttr(Record[++i]); } else if (Record[i] == 3 || Record[i] == 4) { // String attribute bool HasValue = (Record[i++] == 4); SmallString<64> KindStr; Index: llvm/lib/Bitcode/Writer/BitcodeWriter.cpp =================================================================== --- llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -733,6 +733,8 @@ return bitc::ATTR_KIND_PREALLOCATED; case Attribute::NoUndef: return bitc::ATTR_KIND_NOUNDEF; + case Attribute::ByRef: + return bitc::ATTR_KIND_BYREF; case Attribute::EndAttrKinds: llvm_unreachable("Can not encode end-attribute kinds marker."); case Attribute::None: Index: llvm/lib/IR/AttributeImpl.h =================================================================== --- llvm/lib/IR/AttributeImpl.h +++ llvm/lib/IR/AttributeImpl.h @@ -248,6 +248,7 @@ MaybeAlign getStackAlignment() const; uint64_t getDereferenceableBytes() const; uint64_t getDereferenceableOrNullBytes() const; + uint64_t getByRefBytes() const; std::pair> getAllocSizeArgs() const; std::string getAsString(bool InAttrGrp) const; Type *getByValType() const; Index: llvm/lib/IR/Attributes.cpp =================================================================== --- llvm/lib/IR/Attributes.cpp +++ llvm/lib/IR/Attributes.cpp @@ -168,6 +168,11 @@ return get(Context, DereferenceableOrNull, Bytes); } +Attribute Attribute::getWithByRefBytes(LLVMContext &Context, uint64_t Bytes) { + assert(Bytes && "Bytes must be non-zero."); + return get(Context, ByRef, Bytes); +} + Attribute Attribute::getWithByValType(LLVMContext &Context, Type *Ty) { return get(Context, ByVal, Ty); } @@ -212,7 +217,8 @@ AttrKind == Attribute::StackAlignment || AttrKind == Attribute::Dereferenceable || AttrKind == Attribute::AllocSize || - AttrKind == Attribute::DereferenceableOrNull; + AttrKind == Attribute::DereferenceableOrNull || + AttrKind == Attribute::ByRef; } bool Attribute::isExistingAttribute(StringRef Name) { @@ -314,6 +320,12 @@ return pImpl->getValueAsInt(); } +uint64_t Attribute::getByRefBytes() const { + assert(hasAttribute(Attribute::ByRef) && + "Trying to get byref bytes from non-byref attribute!"); + return pImpl->getValueAsInt(); +} + std::pair> Attribute::getAllocSizeArgs() const { assert(hasAttribute(Attribute::AllocSize) && "Trying to get allocsize args from non-allocsize attribute"); @@ -506,6 +518,9 @@ if (hasAttribute(Attribute::DereferenceableOrNull)) return AttrWithBytesToString("dereferenceable_or_null"); + if (hasAttribute(Attribute::ByRef)) + return AttrWithBytesToString("byref"); + if (hasAttribute(Attribute::AllocSize)) { unsigned ElemSize; Optional NumElems; @@ -742,6 +757,10 @@ return SetNode ? SetNode->getDereferenceableOrNullBytes() : 0; } +uint64_t AttributeSet::getByRefBytes() const { + return SetNode ? SetNode->getByRefBytes() : 0; +} + Type *AttributeSet::getByValType() const { return SetNode ? SetNode->getByValType() : nullptr; } @@ -861,6 +880,9 @@ Attr = Attribute::getWithDereferenceableOrNullBytes( C, B.getDereferenceableOrNullBytes()); break; + case Attribute::ByRef: + Attr = Attribute::getWithByRefBytes(C, B.getByRefBytes()); + break; case Attribute::AllocSize: { auto A = B.getAllocSizeArgs(); Attr = Attribute::getWithAllocSizeArgs(C, A.first, A.second); @@ -947,6 +969,12 @@ return 0; } +uint64_t AttributeSetNode::getByRefBytes() const { + if (auto A = findEnumAttribute(Attribute::ByRef)) + return A->getByRefBytes(); + return 0; +} + std::pair> AttributeSetNode::getAllocSizeArgs() const { if (auto A = findEnumAttribute(Attribute::AllocSize)) @@ -1375,6 +1403,13 @@ return addAttributes(C, Index, B); } +AttributeList AttributeList::addByRefAttr(LLVMContext &C, unsigned Index, + uint64_t Bytes) const { + AttrBuilder B; + B.addByRefAttr(Bytes); + return addAttributes(C, Index, B); +} + AttributeList AttributeList::addAllocSizeAttr(LLVMContext &C, unsigned Index, unsigned ElemSizeArg, @@ -1468,6 +1503,10 @@ return getAttributes(Index).getDereferenceableOrNullBytes(); } +uint64_t AttributeList::getByRefBytes(unsigned Index) const { + return getAttributes(Index).getByRefBytes(); +} + std::pair> AttributeList::getAllocSizeArgs(unsigned Index) const { return getAttributes(Index).getAllocSizeArgs(); @@ -1535,6 +1574,7 @@ Alignment.reset(); StackAlignment.reset(); DerefBytes = DerefOrNullBytes = 0; + ByRefBytes = 0; AllocSizeArgs = 0; ByValType = nullptr; PreallocatedType = nullptr; @@ -1569,6 +1609,8 @@ DerefBytes = Attr.getDereferenceableBytes(); else if (Kind == Attribute::DereferenceableOrNull) DerefOrNullBytes = Attr.getDereferenceableOrNullBytes(); + else if (Kind == Attribute::ByRef) + ByRefBytes = Attr.getByRefBytes(); else if (Kind == Attribute::AllocSize) AllocSizeArgs = Attr.getValueAsInt(); return *this; @@ -1595,6 +1637,8 @@ DerefBytes = 0; else if (Val == Attribute::DereferenceableOrNull) DerefOrNullBytes = 0; + else if (Val == Attribute::ByRef) + ByRefBytes = 0; else if (Val == Attribute::AllocSize) AllocSizeArgs = 0; @@ -1657,6 +1701,15 @@ return *this; } +AttrBuilder &AttrBuilder::addByRefAttr(uint64_t Bytes) { + if (Bytes == 0) + return *this; + + Attrs[Attribute::ByRef] = true; + ByRefBytes = Bytes; + return *this; +} + AttrBuilder &AttrBuilder::addAllocSizeAttr(unsigned ElemSize, const Optional &NumElems) { return addAllocSizeAttrFromRawRepr(packAllocSizeArgs(ElemSize, NumElems)); @@ -1708,6 +1761,9 @@ if (!PreallocatedType) PreallocatedType = B.PreallocatedType; + if (!ByRefBytes) + ByRefBytes = B.ByRefBytes; + Attrs |= B.Attrs; for (const auto &I : B.td_attrs()) @@ -1739,6 +1795,9 @@ if (B.PreallocatedType) PreallocatedType = nullptr; + if (B.ByRefBytes) + ByRefBytes = 0; + Attrs &= ~B.Attrs; for (const auto &I : B.td_attrs()) @@ -1823,6 +1882,7 @@ .addAttribute(Attribute::NonNull) .addDereferenceableAttr(1) // the int here is ignored .addDereferenceableOrNullAttr(1) // the int here is ignored + .addByRefAttr(1) // the int here is ignored .addAttribute(Attribute::ReadNone) .addAttribute(Attribute::ReadOnly) .addAttribute(Attribute::StructRet) Index: llvm/lib/IR/Function.cpp =================================================================== --- llvm/lib/IR/Function.cpp +++ llvm/lib/IR/Function.cpp @@ -101,6 +101,12 @@ return hasAttribute(Attribute::ByVal); } +bool Argument::hasByRefAttr() const { + if (!getType()->isPointerTy()) + return false; + return hasAttribute(Attribute::ByRef); +} + bool Argument::hasSwiftSelfAttr() const { return getParent()->hasParamAttribute(getArgNo(), Attribute::SwiftSelf); } @@ -128,10 +134,21 @@ Attrs.hasParamAttribute(getArgNo(), Attribute::Preallocated); } -uint64_t Argument::getPassPointeeByValueCopySize(const DataLayout &DL) const { - AttributeSet ParamAttrs - = getParent()->getAttributes().getParamAttributes(getArgNo()); +bool Argument::hasPointeeInMemoryValueAttr() const { + if (!getType()->isPointerTy()) + return false; + AttributeList Attrs = getParent()->getAttributes(); + return Attrs.hasParamAttribute(getArgNo(), Attribute::ByVal) || + Attrs.hasParamAttribute(getArgNo(), Attribute::InAlloca) || + Attrs.hasParamAttribute(getArgNo(), Attribute::Preallocated) || + Attrs.hasParamAttribute(getArgNo(), Attribute::ByRef); +} +/// For a byval, inalloca, or preallocated parameter, get the in-memory +/// parameter size. +static uint64_t getMemoryParamAllocSize(AttributeSet ParamAttrs, + const Type *ArgTy, + const DataLayout &DL) { // FIXME: All the type carrying attributes are mutually exclusive, so there // should be a single query to get the stored type that handles any of them. if (Type *ByValTy = ParamAttrs.getByValType()) @@ -144,11 +161,25 @@ if (ParamAttrs.hasAttribute(Attribute::InAlloca) || ParamAttrs.hasAttribute(Attribute::ByVal) || ParamAttrs.hasAttribute(Attribute::Preallocated)) - return DL.getTypeAllocSize(cast(getType())->getElementType()); + return DL.getTypeAllocSize(cast(ArgTy)->getElementType()); return 0; } +uint64_t Argument::getPassPointeeByValueCopySize(const DataLayout &DL) const { + AttributeSet ParamAttrs = + getParent()->getAttributes().getParamAttributes(getArgNo()); + return getMemoryParamAllocSize(ParamAttrs, getType(), DL); +} + +uint64_t Argument::getPointeeInMemoryValueSize(const DataLayout &DL) const { + AttributeSet ParamAttrs = + getParent()->getAttributes().getParamAttributes(getArgNo()); + if (ParamAttrs.hasAttribute(Attribute::ByRef)) + return ParamAttrs.getByRefBytes(); + return getMemoryParamAllocSize(ParamAttrs, getType(), DL); +} + unsigned Argument::getParamAlignment() const { assert(getType()->isPointerTy() && "Only pointers have alignments"); return getParent()->getParamAlignment(getArgNo()); @@ -176,6 +207,11 @@ return getParent()->getParamDereferenceableOrNullBytes(getArgNo()); } +uint64_t Argument::getByRefBytes() const { + assert(getType()->isPointerTy() && "Only pointers have byref bytes"); + return getParent()->getParamByRefBytes(getArgNo()); +} + bool Argument::hasNestAttr() const { if (!getType()->isPointerTy()) return false; return hasAttribute(Attribute::Nest); Index: llvm/lib/IR/Verifier.cpp =================================================================== --- llvm/lib/IR/Verifier.cpp +++ llvm/lib/IR/Verifier.cpp @@ -1652,9 +1652,10 @@ AttrCount += Attrs.hasAttribute(Attribute::StructRet) || Attrs.hasAttribute(Attribute::InReg); AttrCount += Attrs.hasAttribute(Attribute::Nest); + AttrCount += Attrs.hasAttribute(Attribute::ByRef); Assert(AttrCount <= 1, "Attributes 'byval', 'inalloca', 'preallocated', 'inreg', 'nest', " - "and 'sret' are incompatible!", + "'byref', and 'sret' are incompatible!", V); Assert(!(Attrs.hasAttribute(Attribute::InAlloca) && @@ -1765,10 +1766,11 @@ !RetAttrs.hasAttribute(Attribute::Returned) && !RetAttrs.hasAttribute(Attribute::InAlloca) && !RetAttrs.hasAttribute(Attribute::Preallocated) && + !RetAttrs.hasAttribute(Attribute::ByRef) && !RetAttrs.hasAttribute(Attribute::SwiftSelf) && !RetAttrs.hasAttribute(Attribute::SwiftError)), - "Attributes 'byval', 'inalloca', 'preallocated', 'nest', 'sret', " - "'nocapture', 'nofree', " + "Attributes 'byval', 'inalloca', 'preallocated', 'byref', " + "'nest', 'sret', 'nocapture', 'nofree', " "'returned', 'swiftself', and 'swifterror' do not apply to return " "values!", V); @@ -3182,9 +3184,15 @@ if (Attrs.hasParamAttribute(I, AK)) Copy.addAttribute(AK); } - // `align` is ABI-affecting only in combination with `byval`. + + // FIXME: byval is dropping type? + if (Attrs.hasParamAttribute(I, Attribute::ByRef)) + Copy.addByRefAttr(Attrs.getParamByRefBytes(I)); + + // `align` is ABI-affecting only in combination with `byval` or `byref`. if (Attrs.hasParamAttribute(I, Attribute::Alignment) && - Attrs.hasParamAttribute(I, Attribute::ByVal)) + (Attrs.hasParamAttribute(I, Attribute::ByVal) || + Attrs.hasParamAttribute(I, Attribute::ByRef))) Copy.addAlignmentAttr(Attrs.getParamAlignment(I)); return Copy; } Index: llvm/lib/Transforms/Utils/CodeExtractor.cpp =================================================================== --- llvm/lib/Transforms/Utils/CodeExtractor.cpp +++ llvm/lib/Transforms/Utils/CodeExtractor.cpp @@ -895,6 +895,7 @@ case Attribute::WriteOnly: case Attribute::ZExt: case Attribute::ImmArg: + case Attribute::ByRef: case Attribute::EndAttrKinds: case Attribute::EmptyKey: case Attribute::TombstoneKey: Index: llvm/test/Assembler/byref-parse-error-0.ll =================================================================== --- /dev/null +++ llvm/test/Assembler/byref-parse-error-0.ll @@ -0,0 +1,6 @@ +; RUN: not llvm-as < %s 2>&1 | FileCheck %s + +; CHECK: :[[@LINE+1]]:34: error: expected '('{{$}} +define void @test_byref(i8* byref) { + ret void +} Index: llvm/test/Assembler/byref-parse-error-1.ll =================================================================== --- /dev/null +++ llvm/test/Assembler/byref-parse-error-1.ll @@ -0,0 +1,6 @@ +; RUN: not llvm-as < %s 2>&1 | FileCheck %s + +; CHECK: :[[@LINE+1]]:35: error: expected integer{{$}} +define void @test_byref(i8* byref() { + ret void +} Index: llvm/test/Assembler/byref-parse-error-10.ll =================================================================== --- /dev/null +++ llvm/test/Assembler/byref-parse-error-10.ll @@ -0,0 +1,6 @@ +; RUN: not llvm-as < %s 2>&1 | FileCheck %s + +; CHECK: :[[@LINE+1]]:27: error: invalid use of parameter-only attribute on a function{{$}} +define void @test_byref() byref(4) { + ret void +} Index: llvm/test/Assembler/byref-parse-error-2.ll =================================================================== --- /dev/null +++ llvm/test/Assembler/byref-parse-error-2.ll @@ -0,0 +1,7 @@ +; RUN: not llvm-as < %s 2>&1 | FileCheck %s + +; CHECK: :[[@LINE+1]]:35: error: expected integer{{$}} +define void @test_byref(i8* byref()) { + ret void +} + Index: llvm/test/Assembler/byref-parse-error-3.ll =================================================================== --- /dev/null +++ llvm/test/Assembler/byref-parse-error-3.ll @@ -0,0 +1,6 @@ +; RUN: not llvm-as < %s 2>&1 | FileCheck %s + +; CHECK: :[[@LINE+1]]:35: error: expected integer{{$}} +define void @test_byref(i8* byref(-1)) { + ret void +} Index: llvm/test/Assembler/byref-parse-error-4.ll =================================================================== --- /dev/null +++ llvm/test/Assembler/byref-parse-error-4.ll @@ -0,0 +1,6 @@ +; RUN: not llvm-as < %s 2>&1 | FileCheck %s + +; CHECK: :[[@LINE+1]]:35: error: byref bytes must be non-zero{{$}} +define void @test_byref(i8* byref(0)) { + ret void +} Index: llvm/test/Assembler/byref-parse-error-5.ll =================================================================== --- /dev/null +++ llvm/test/Assembler/byref-parse-error-5.ll @@ -0,0 +1,6 @@ +; RUN: not llvm-as < %s 2>&1 | FileCheck %s + +; CHECK: :[[@LINE+1]]:8: error: invalid use of parameter-only attribute{{$}} +define byref i8* @test_byref() { + ret void +} Index: llvm/test/Assembler/byref-parse-error-6.ll =================================================================== --- /dev/null +++ llvm/test/Assembler/byref-parse-error-6.ll @@ -0,0 +1,6 @@ +; RUN: not llvm-as < %s 2>&1 | FileCheck %s + +; CHECK: :[[@LINE+1]]:8: error: invalid use of parameter-only attribute{{$}} +define byref 8 i8* @test_byref() { + ret void +} Index: llvm/test/Assembler/byref-parse-error-7.ll =================================================================== --- /dev/null +++ llvm/test/Assembler/byref-parse-error-7.ll @@ -0,0 +1,6 @@ +; RUN: not llvm-as < %s 2>&1 | FileCheck %s + +; CHECK: :[[@LINE+1]]:8: error: invalid use of parameter-only attribute{{$}} +define byref(8) i8* @test_byref() { + ret void +} Index: llvm/test/Assembler/byref-parse-error-8.ll =================================================================== --- /dev/null +++ llvm/test/Assembler/byref-parse-error-8.ll @@ -0,0 +1,6 @@ +; RUN: not llvm-as < %s 2>&1 | FileCheck %s + +; CHECK: :[[@LINE+1]]:27: error: invalid use of parameter-only attribute on a function{{$}} +define void @test_byref() byref { + ret void +} Index: llvm/test/Assembler/byref-parse-error-9.ll =================================================================== --- /dev/null +++ llvm/test/Assembler/byref-parse-error-9.ll @@ -0,0 +1,6 @@ +; RUN: not llvm-as < %s 2>&1 | FileCheck %s + +; CHECK: :[[@LINE+1]]:27: error: invalid use of parameter-only attribute on a function{{$}} +define void @test_byref() byref=4 { + ret void +} Index: llvm/test/Bitcode/attributes.ll =================================================================== --- llvm/test/Bitcode/attributes.ll +++ llvm/test/Bitcode/attributes.ll @@ -392,6 +392,12 @@ ret i32 %a } +; CHECK: define void @f67(i32* byref(4) %a) +define void @f67(i32* byref(4) %a) +{ + ret void +} + ; CHECK: attributes #0 = { noreturn } ; CHECK: attributes #1 = { nounwind } ; CHECK: attributes #2 = { readnone } Index: llvm/test/CodeGen/X86/byref.ll =================================================================== --- /dev/null +++ llvm/test/CodeGen/X86/byref.ll @@ -0,0 +1,20 @@ +; RUN: llc < %s -mtriple=i686-pc-win32 | FileCheck %s + +%Foo = type { i32, i32 } + +declare x86_stdcallcc void @foo_byref_stdcall_p(%Foo* byref(8)) +declare x86_stdcallcc void @i(i32) + +; byref does not imply a stack copy, so this should append 4 bytes, +; not 8. +define void @stdcall(%Foo* %value) { +; CHECK-LABEL: _stdcall: +; CHECK: pushl 4(%esp) +; CHECK: calll _foo_byref_stdcall_p@4 + call x86_stdcallcc void @foo_byref_stdcall_p(%Foo* byref(8) %value) +; CHECK-NOT: %esp +; CHECK: pushl +; CHECK: calll _i@4 + call x86_stdcallcc void @i(i32 0) + ret void +} Index: llvm/test/Instrumentation/AddressSanitizer/byref-args.ll =================================================================== --- /dev/null +++ llvm/test/Instrumentation/AddressSanitizer/byref-args.ll @@ -0,0 +1,20 @@ +; RUN: opt < %s -asan -S | FileCheck %s + +; Test that for call instructions, the byref arguments are not +; instrumented, as no copy is implied. + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +%struct.bar = type { %struct.foo } +%struct.foo = type { i8*, i8*, i8* } + +; CHECK-LABEL: @func2 +; CHECK-NEXT: tail call void @func1( +; CHECK-NEXT: ret void +define dso_local void @func2(%struct.foo* %foo) sanitize_address { + tail call void @func1(%struct.foo* byref(32) align 8 %foo) #2 + ret void +} + +declare dso_local void @func1(%struct.foo* byref(32) align 8) Index: llvm/test/Transforms/DeadArgElim/byref.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/DeadArgElim/byref.ll @@ -0,0 +1,22 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt < %s -deadargelim -S | FileCheck %s + +declare void @sideeffect() + +define void @unused_byref_arg(i32* byref(4) %dead_arg) { +; CHECK-LABEL: @unused_byref_arg( +; CHECK-NEXT: tail call void @sideeffect() +; CHECK-NEXT: ret void +; + tail call void @sideeffect() + ret void +} + +define void @dont_replace_by_undef(i32* %ptr) { +; CHECK-LABEL: @dont_replace_by_undef( +; CHECK-NEXT: call void @unused_byref_arg(i32* undef) +; CHECK-NEXT: ret void +; + call void @unused_byref_arg(i32* %ptr) + ret void +} Index: llvm/test/Transforms/Inline/byref-align.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/Inline/byref-align.ll @@ -0,0 +1,52 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature +; RUN: opt -inline -preserve-alignment-assumptions-during-inlining -S < %s | FileCheck %s +target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; Test behavior of inserted alignment assumptions with byref. There is +; no implied copy to a higher alignment, so an alignment assume call +; should be inserted. +define void @byref_callee(float* align(128) byref(4) nocapture %a, float* %b) #0 { +; CHECK-LABEL: define {{[^@]+}}@byref_callee +; CHECK-SAME: (float* nocapture align 128 byref(4) [[A:%.*]], float* [[B:%.*]]) #0 +; CHECK-NEXT: entry: +; CHECK-NEXT: [[LOAD:%.*]] = load float, float* [[A]], align 4 +; CHECK-NEXT: [[B_IDX:%.*]] = getelementptr inbounds float, float* [[B]], i64 8 +; CHECK-NEXT: [[ADD:%.*]] = fadd float [[LOAD]], 2.000000e+00 +; CHECK-NEXT: store float [[ADD]], float* [[B_IDX]], align 4 +; CHECK-NEXT: ret void +; +entry: + %load = load float, float* %a, align 4 + %b.idx = getelementptr inbounds float, float* %b, i64 8 + %add = fadd float %load, 2.0 + store float %add, float* %b.idx, align 4 + ret void +} + +define void @byref_caller(float* nocapture align 64 %a, float* %b) #0 { +; CHECK-LABEL: define {{[^@]+}}@byref_caller +; CHECK-SAME: (float* nocapture align 64 [[A:%.*]], float* [[B:%.*]]) #0 +; CHECK-NEXT: entry: +; CHECK-NEXT: [[PTRINT:%.*]] = ptrtoint float* [[A]] to i64 +; CHECK-NEXT: [[MASKEDPTR:%.*]] = and i64 [[PTRINT]], 127 +; CHECK-NEXT: [[MASKCOND:%.*]] = icmp eq i64 [[MASKEDPTR]], 0 +; CHECK-NEXT: call void @llvm.assume(i1 [[MASKCOND]]) +; CHECK-NEXT: [[LOAD_I:%.*]] = load float, float* [[A]], align 4 +; CHECK-NEXT: [[B_IDX_I:%.*]] = getelementptr inbounds float, float* [[B]], i64 8 +; CHECK-NEXT: [[ADD_I:%.*]] = fadd float [[LOAD_I]], 2.000000e+00 +; CHECK-NEXT: store float [[ADD_I]], float* [[B_IDX_I]], align 4 +; CHECK-NEXT: [[CALLER_LOAD:%.*]] = load float, float* [[B]], align 4 +; CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds float, float* [[A]], i64 7 +; CHECK-NEXT: store float [[CALLER_LOAD]], float* [[ARRAYIDX]], align 4 +; CHECK-NEXT: ret void +; +entry: + call void @byref_callee(float* align(128) byref(4) %a, float* %b) + %caller.load = load float, float* %b, align 4 + %arrayidx = getelementptr inbounds float, float* %a, i64 7 + store float %caller.load, float* %arrayidx, align 4 + ret void +} + +attributes #0 = { nounwind uwtable } Index: llvm/test/Transforms/LowerConstantIntrinsics/objectsize_basic.ll =================================================================== --- llvm/test/Transforms/LowerConstantIntrinsics/objectsize_basic.ll +++ llvm/test/Transforms/LowerConstantIntrinsics/objectsize_basic.ll @@ -89,3 +89,10 @@ %size = tail call i64 @llvm.objectsize.i64(i8* %cast, i1 true, i1 false, i1 false) ret i64 %size } + +; CHECK-LABEL: @test_objectsize_byref_arg( +; CHECK: ret i64 42 +define i64 @test_objectsize_byref_arg(i8* byref(42) %ptr) { + %size = tail call i64 @llvm.objectsize.i64(i8* %ptr, i1 true, i1 false, i1 false) + ret i64 %size +} Index: llvm/test/Verifier/byref.ll =================================================================== --- /dev/null +++ llvm/test/Verifier/byref.ll @@ -0,0 +1,83 @@ +; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s + +;declare void @invalid_byref_funcattr0() byref +;declare void @invalid_byref_funcattr1() byref(0) +;declare void @invalid_byref_funcattr2() byref(4) + +; CHECK: Attributes 'byval', 'inalloca', 'preallocated', 'inreg', 'nest', 'byref', and 'sret' are incompatible! +; CHECK-NEXT: void (i32*)* @byref_byval +define void @byref_byval(i32* byref(4) byval(i32)) { + ret void +} + +; CHECK: Attributes 'byval', 'inalloca', 'preallocated', 'inreg', 'nest', 'byref', and 'sret' are incompatible! +; CHECK-NEXT: void (i32*)* @byref_inalloca +define void @byref_inalloca(i32* byref(4) inalloca) { + ret void +} + +; CHECK: Attributes 'byval', 'inalloca', 'preallocated', 'inreg', 'nest', 'byref', and 'sret' are incompatible! +; CHECK-NEXT: void (i32*)* @byref_preallocated +define void @byref_preallocated(i32* byref(4) preallocated(i32)) { + ret void +} + +; CHECK: Attributes 'byval', 'inalloca', 'preallocated', 'inreg', 'nest', 'byref', and 'sret' are incompatible! +; CHECK-NEXT: void (i32*)* @byref_sret +define void @byref_sret(i32* byref(4) sret) { + ret void +} + +; CHECK: Attributes 'byval', 'inalloca', 'preallocated', 'inreg', 'nest', 'byref', and 'sret' are incompatible! +; CHECK-NEXT: void (i32*)* @byref_inreg +define void @byref_inreg(i32* byref(4) inreg) { + ret void +} + +; CHECK: Attributes 'byval', 'inalloca', 'preallocated', 'inreg', 'nest', 'byref', and 'sret' are incompatible! +; CHECK-NEXT: void (i32*)* @byref_nest +define void @byref_nest(i32* byref(4) nest) { + ret void +} + +; CHECK: Wrong types for attribute: inalloca nest noalias nocapture nonnull readnone readonly sret byval(i32) preallocated(i32) byref(1) dereferenceable(1) dereferenceable_or_null(1) +; CHECK-NEXT: void (i32)* @byref_non_pointer +define void @byref_non_pointer(i32 byref(4)) { + ret void +} + +define void @byref_callee(i8* byref(64)) { + ret void +} + +define void @no_byref_callee(i8*) { + ret void +} + +; CHECK: cannot guarantee tail call due to mismatched ABI impacting function attributes +; CHECK-NEXT: musttail call void @byref_callee(i8* byref(64) %ptr) +; CHECK-NEXT: i8* %ptr +define void @musttail_byref_caller(i8* %ptr) { + musttail call void @byref_callee(i8* byref(64) %ptr) + ret void +} + +; CHECK: cannot guarantee tail call due to mismatched ABI impacting function attributes +; CHECK-NEXT: musttail call void @byref_callee(i8* %ptr) +; CHECK-NEXT: i8* %ptr +define void @musttail_byref_callee(i8* byref(64) %ptr) { + musttail call void @byref_callee(i8* %ptr) + ret void +} + +define void @byref_callee_align32(i8* byref(64) align 32) { + ret void +} + +; CHECK: cannot guarantee tail call due to mismatched ABI impacting function attributes +; CHECK-NEXT: musttail call void @byref_callee_align32(i8* align 32 byref(64) %ptr) +; CHECK-NEXT: i8* %ptr +define void @musttail_byref_caller_mismatched_align(i8* byref(64) align 16 %ptr) { + musttail call void @byref_callee_align32(i8* byref(64) align 32 %ptr) + ret void +} Index: llvm/unittests/IR/VerifierTest.cpp =================================================================== --- llvm/unittests/IR/VerifierTest.cpp +++ llvm/unittests/IR/VerifierTest.cpp @@ -238,5 +238,47 @@ } } +// Make sure byref is rejected in contexts where the parser doesn't handle it. +TEST(VerifierTest, InvalidByRefAttribute) { + LLVMContext C; + FunctionType *FTy = FunctionType::get( + Type::getInt32Ty(C), {Type::getInt8PtrTy(C, 0)}, /*isVarArg=*/false); + std::string Error; + raw_string_ostream ErrorOS(Error); + + // Reject byref on return values. + { + Module M0("M0", C); + Function *F0 = Function::Create(FTy, Function::ExternalLinkage, "foo", M0); + AttributeList AS0 = F0->getAttributes(); + F0->setAttributes(AS0.addByRefAttr(C, AttributeList::ReturnIndex, 42)); + + EXPECT_TRUE(verifyModule(M0, &ErrorOS)); + EXPECT_TRUE( + StringRef(ErrorOS.str()) + .startswith( + "Attributes 'byval', 'inalloca', 'preallocated', 'byref', " + "'nest', " + "'sret', 'nocapture', 'nofree', 'returned', 'swiftself', and " + "'swifterror' do not apply to return values!")) + << ErrorOS.str(); + Error.clear(); + } + + // Reject byref on functions + { + Module M1("M1", C); + Function *F1 = Function::Create(FTy, Function::ExternalLinkage, "bar", M1); + AttributeList AS1 = F1->getAttributes(); + F1->setAttributes(AS1.addByRefAttr(C, AttributeList::FunctionIndex, 42)); + EXPECT_TRUE(verifyModule(M1, &ErrorOS)); + EXPECT_TRUE( + StringRef(ErrorOS.str()) + .startswith("Attribute 'byref(42)' does not apply to functions!")) + << ErrorOS.str(); + Error.clear(); + } +} + } // end anonymous namespace } // end namespace llvm