IR: Define byref parameter attribute

This allows tracking the in-memory type of a pointer argument to a
function for ABI purposes. This is essentially a stripped down version
of byval to remove some of the stack-copy implications in its
definition.

This includes the base IR changes, and some tests for places where it
should be treated similarly to byval. Codegen support will be in a
future patch.

My original attempt at solving some of these problems was to repurpose
byval with a different address space from the stack. However, it is
technically permitted for the callee to introduce a write to the
argument, although nothing does this in reality. There is also talk of
removing and replacing the byval attribute, so a new attribute would
need to take its place anyway.

This is intended avoid some optimization issues with the current
handling of aggregate arguments, as well as fixes inflexibilty in how
frontends can specify the kernel ABI. The most honest representation
of the amdgpu_kernel convention is to expose all kernel arguments as
loads from constant memory. Today, these are raw, SSA Argument values
and codegen is responsible for turning these into loads.

Background:

There currently isn't a satisfactory way to represent how arguments
for the amdgpu_kernel calling convention are passed. In reality,
arguments are passed in a single, flat, constant memory buffer
implicitly passed to the function. It is also illegal to call this
function in the IR, and this is only ever invoked by a driver of some
kind.

It does not make sense to have a stack passed parameter in this
context as is implied by byval. It is never valid to write to the
kernel arguments, as this would corrupt the inputs seen by other
dispatches of the kernel. These argumets are also not in the same
address space as the stack, so a copy is needed to an alloca. From a
source C-like language, the kernel parameters are invisible.
Semantically, a copy is always required from the constant argument
memory to a mutable variable.

The current clang calling convention lowering emits raw values,
including aggregates into the function argument list, since using
byval would not make sense. This has some unfortunate consequences for
the optimizer. In the aggregate case, we end up with an aggregate
store to alloca, which both SROA and instcombine turn into a store of
each aggregate field. The optimizer never pieces this back together to
see that this is really just a copy from constant memory, so we end up
stuck with expensive stack usage.

This also means the backend dictates the alignment of arguments, and
arbitrarily picks the LLVM IR ABI type alignment. By allowing an
explicit alignment, frontends can make better decisions. For example,
there's real no advantage to an aligment higher than 4, so a frontend
could choose to compact the argument layout. Similarly, there is a
high penalty to using an alignment lower than 4, so a frontend could
opt into more padding for small arguments.

Another design consideration is when it is appropriate to expose the
fact that these arguments are all really passed in adjacent
memory. Currently we have a late IR optimization pass in codegen to
rewrite the kernel argument values into explicit loads to enable
vectorization. In most programs, unrelated argument loads can be
merged together. However, exposing this property directly from the
frontend has some disadvantages. We still need a way to track the
original argument sizes and alignments to report to the driver. I find
using some side-channel, metadata mechanism to track this
unappealing. If the kernel arguments were exposed as a single buffer
to begin with, alias analysis would be unaware that the padding bits
betewen arguments are meaningless. Another family of problems is there
are still some gaps in replacing all of the available parameter
attributes with metadata equivalents once lowered to loads.

The immediate plan is to start using this new attribute to handle all
aggregate argumets for kernels. Long term, it makes sense to migrate
all kernel arguments, including scalars, to be passed indirectly in
the same manner.

Additional context is in D79744.
This commit is contained in:
Matt Arsenault 2020-06-05 16:58:47 -04:00
parent a3033adc1a
commit ea505ad2f6
38 changed files with 516 additions and 29 deletions

View File

@ -1066,6 +1066,30 @@ Currently, only the following parameter attributes are defined:
site. If the alignment is not specified, then the code generator
makes a target-specific assumption.
.. _attr_byref:
``byref(<ty>)``
The ``byref`` argument attribute allows specifying the pointee
memory type 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 of the type.
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 an ``byref`` parameter can be explicitly
specified by combining it with the ``align`` attribute, similar to
``byval``. If the alignment is not specified, then the code generator
makes a target-specific assumption.
This is intended for representing ABI constraints, and is not
intended to be inferred for optimization use.
.. _attr_preallocated:
``preallocated(<ty>)``

View File

@ -59,6 +59,9 @@ Changes to the LLVM IR
* ...
* Added the ``byref`` attribute to better represent argument passing
for the `amdgpu_kernel` calling convention.
Changes to building LLVM
------------------------
@ -88,6 +91,9 @@ Changes to the AMDGPU Target
During this release ...
* The new ``byref`` attribute is now the preferred method for
representing aggregate kernel arguments.
Changes to the AVR Target
-----------------------------

View File

@ -644,6 +644,7 @@ enum AttributeKindCodes {
ATTR_KIND_NO_MERGE = 66,
ATTR_KIND_NULL_POINTER_IS_VALID = 67,
ATTR_KIND_NOUNDEF = 68,
ATTR_KIND_BYREF = 69,
};
enum ComdatSelectionKindCodes {

View File

@ -65,6 +65,9 @@ public:
/// 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 +83,15 @@ public:
/// 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 hasPointeeInMemoryValueAttr returns true, the in-memory ABI type is
/// returned. Otherwise, nullptr.
Type *getPointeeInMemoryValueType() 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.
@ -91,6 +103,9 @@ public:
/// If this is a byval argument, return its type.
Type *getParamByValType() const;
/// If this is a byref argument, return its type.
Type *getParamByRefType() const;
/// Return true if this argument has the nest attribute.
bool hasNestAttr() const;

View File

@ -108,6 +108,7 @@ public:
unsigned ElemSizeArg,
const Optional<unsigned> &NumElemsArg);
static Attribute getWithByValType(LLVMContext &Context, Type *Ty);
static Attribute getWithByRefType(LLVMContext &Context, Type *Ty);
static Attribute getWithPreallocatedType(LLVMContext &Context, Type *Ty);
static Attribute::AttrKind getAttrKindFromName(StringRef AttrName);
@ -303,6 +304,7 @@ public:
uint64_t getDereferenceableBytes() const;
uint64_t getDereferenceableOrNullBytes() const;
Type *getByValType() const;
Type *getByRefType() const;
Type *getPreallocatedType() const;
std::pair<unsigned, Optional<unsigned>> getAllocSizeArgs() const;
std::string getAsString(bool InAttrGrp = false) const;
@ -626,6 +628,9 @@ public:
/// Return the byval type for the specified function parameter.
Type *getParamByValType(unsigned ArgNo) const;
/// Return the byref type for the specified function parameter.
Type *getParamByRefType(unsigned ArgNo) const;
/// Return the preallocated type for the specified function parameter.
Type *getParamPreallocatedType(unsigned ArgNo) const;
@ -729,6 +734,7 @@ class AttrBuilder {
uint64_t DerefOrNullBytes = 0;
uint64_t AllocSizeArgs = 0;
Type *ByValType = nullptr;
Type *ByRefType = nullptr;
Type *PreallocatedType = nullptr;
public:
@ -808,6 +814,9 @@ public:
/// Retrieve the byval type.
Type *getByValType() const { return ByValType; }
/// Retrieve the byref type.
Type *getByRefType() const { return ByRefType; }
/// Retrieve the preallocated type.
Type *getPreallocatedType() const { return PreallocatedType; }
@ -854,6 +863,9 @@ public:
/// This turns a byval type into the form used internally in Attribute.
AttrBuilder &addByValAttr(Type *Ty);
/// This turns a byref type into the form used internally in Attribute.
AttrBuilder &addByRefAttr(Type *Ty);
/// This turns a preallocated type into the form used internally in Attribute.
AttrBuilder &addPreallocatedAttr(Type *Ty);

View File

@ -39,6 +39,9 @@ def Builtin : EnumAttr<"builtin">;
/// Pass structure by value.
def ByVal : TypeAttr<"byval">;
/// Mark in-memory ABI type.
def ByRef : TypeAttr<"byref">;
/// Parameter or return value may not contain uninitialized or poison bits.
def NoUndef : EnumAttr<"noundef">;

View File

@ -467,6 +467,11 @@ public:
return Ty ? Ty : (arg_begin() + ArgNo)->getType()->getPointerElementType();
}
/// Extract the byref type for a parameter.
Type *getParamByRefType(unsigned ArgNo) const {
return AttributeSets.getParamByRefType(ArgNo);
}
/// Extract the number of dereferenceable bytes for a call or
/// parameter (0=unknown).
/// @param i AttributeList index, referring to a return value or argument.

View File

@ -676,13 +676,14 @@ SizeOffsetType ObjectSizeOffsetVisitor::visitAllocaInst(AllocaInst &I) {
}
SizeOffsetType ObjectSizeOffsetVisitor::visitArgument(Argument &A) {
Type *MemoryTy = A.getPointeeInMemoryValueType();
// No interprocedural analysis is done at the moment.
if (!A.hasPassPointeeByValueCopyAttr()) {
if (!MemoryTy) {
++ObjectVisitorArgument;
return unknown();
}
PointerType *PT = cast<PointerType>(A.getType());
APInt Size(IntTyBits, DL.getTypeAllocSize(PT->getElementType()));
APInt Size(IntTyBits, DL.getTypeAllocSize(MemoryTy));
return std::make_pair(align(Size, A.getParamAlignment()), Zero);
}

View File

@ -697,6 +697,7 @@ lltok::Kind LLLexer::LexIdentifier() {
KEYWORD(writeonly);
KEYWORD(zeroext);
KEYWORD(immarg);
KEYWORD(byref);
KEYWORD(type);
KEYWORD(opaque);

View File

@ -1382,6 +1382,7 @@ bool LLParser::ParseFnAttributeValuePairs(AttrBuilder &B,
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 @@ bool LLParser::ParseOptionalParamAttrs(AttrBuilder &B) {
B.addDereferenceableOrNullAttr(Bytes);
continue;
}
case lltok::kw_byref: {
Type *Ty;
if (ParseByRef(Ty))
return true;
B.addByRefAttr(Ty);
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 @@ bool LLParser::ParseOptionalReturnAttrs(AttrBuilder &B) {
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;
@ -2568,11 +2577,11 @@ bool LLParser::ParseByValWithOptionalType(Type *&Result) {
return false;
}
/// ParsePreallocated
/// ::= preallocated(<ty>)
bool LLParser::ParsePreallocated(Type *&Result) {
/// ParseRequiredTypeAttr
/// ::= attrname(<ty>)
bool LLParser::ParseRequiredTypeAttr(Type *&Result, lltok::Kind AttrName) {
Result = nullptr;
if (!EatIfPresent(lltok::kw_preallocated))
if (!EatIfPresent(AttrName))
return true;
if (!EatIfPresent(lltok::lparen))
return Error(Lex.getLoc(), "expected '('");
@ -2583,6 +2592,18 @@ bool LLParser::ParsePreallocated(Type *&Result) {
return false;
}
/// ParsePreallocated
/// ::= preallocated(<ty>)
bool LLParser::ParsePreallocated(Type *&Result) {
return ParseRequiredTypeAttr(Result, lltok::kw_preallocated);
}
/// ParseByRef
/// ::= byref(<type>)
bool LLParser::ParseByRef(Type *&Result) {
return ParseRequiredTypeAttr(Result, lltok::kw_byref);
}
/// ParseOptionalOperandBundles
/// ::= /*empty*/
/// ::= '[' OperandBundle [, OperandBundle ]* ']'

View File

@ -333,7 +333,10 @@ namespace llvm {
std::vector<unsigned> &FwdRefAttrGrps,
bool inAttrGrp, LocTy &BuiltinLoc);
bool ParseByValWithOptionalType(Type *&Result);
bool ParseRequiredTypeAttr(Type *&Result, lltok::Kind AttrName);
bool ParsePreallocated(Type *&Result);
bool ParseByRef(Type *&Result);
// Module Summary Index Parsing.
bool SkipModuleSummaryEntry();

View File

@ -240,6 +240,7 @@ enum Kind {
kw_writeonly,
kw_zeroext,
kw_immarg,
kw_byref,
kw_type,
kw_opaque,

View File

@ -1532,6 +1532,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) {
return Attribute::Preallocated;
case bitc::ATTR_KIND_NOUNDEF:
return Attribute::NoUndef;
case bitc::ATTR_KIND_BYREF:
return Attribute::ByRef;
}
}
@ -1649,6 +1651,8 @@ Error BitcodeReader::parseAttributeGroupBlock() {
return Err;
if (Kind == Attribute::ByVal) {
B.addByValAttr(HasType ? getTypeByID(Record[++i]) : nullptr);
} else if (Kind == Attribute::ByRef) {
B.addByRefAttr(getTypeByID(Record[++i]));
} else if (Kind == Attribute::Preallocated) {
B.addPreallocatedAttr(getTypeByID(Record[++i]));
}

View File

@ -733,6 +733,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
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:

View File

@ -4269,11 +4269,14 @@ void AssemblyWriter::writeAttribute(const Attribute &Attr, bool InAttrGroup) {
}
assert((Attr.hasAttribute(Attribute::ByVal) ||
Attr.hasAttribute(Attribute::ByRef) ||
Attr.hasAttribute(Attribute::Preallocated)) &&
"unexpected type attr");
if (Attr.hasAttribute(Attribute::ByVal)) {
Out << "byval";
} else if (Attr.hasAttribute(Attribute::ByRef)) {
Out << "byref";
} else {
Out << "preallocated";
}

View File

@ -251,6 +251,7 @@ public:
std::pair<unsigned, Optional<unsigned>> getAllocSizeArgs() const;
std::string getAsString(bool InAttrGrp) const;
Type *getByValType() const;
Type *getByRefType() const;
Type *getPreallocatedType() const;
using iterator = const Attribute *;

View File

@ -172,6 +172,10 @@ Attribute Attribute::getWithByValType(LLVMContext &Context, Type *Ty) {
return get(Context, ByVal, Ty);
}
Attribute Attribute::getWithByRefType(LLVMContext &Context, Type *Ty) {
return get(Context, ByRef, Ty);
}
Attribute Attribute::getWithPreallocatedType(LLVMContext &Context, Type *Ty) {
return get(Context, Preallocated, Ty);
}
@ -459,9 +463,9 @@ std::string Attribute::getAsString(bool InAttrGrp) const {
return Result;
}
if (hasAttribute(Attribute::Preallocated)) {
std::string Result;
Result += "preallocated";
const bool IsByRef = hasAttribute(Attribute::ByRef);
if (IsByRef || hasAttribute(Attribute::Preallocated)) {
std::string Result = IsByRef ? "byref" : "preallocated";
raw_string_ostream OS(Result);
Result += '(';
getValueAsType()->print(OS, false, true);
@ -742,6 +746,10 @@ uint64_t AttributeSet::getDereferenceableOrNullBytes() const {
return SetNode ? SetNode->getDereferenceableOrNullBytes() : 0;
}
Type *AttributeSet::getByRefType() const {
return SetNode ? SetNode->getByRefType() : nullptr;
}
Type *AttributeSet::getByValType() const {
return SetNode ? SetNode->getByValType() : nullptr;
}
@ -842,6 +850,9 @@ AttributeSetNode *AttributeSetNode::get(LLVMContext &C, const AttrBuilder &B) {
case Attribute::ByVal:
Attr = Attribute::getWithByValType(C, B.getByValType());
break;
case Attribute::ByRef:
Attr = Attribute::getWithByRefType(C, B.getByRefType());
break;
case Attribute::Preallocated:
Attr = Attribute::getWithPreallocatedType(C, B.getPreallocatedType());
break;
@ -928,6 +939,12 @@ Type *AttributeSetNode::getByValType() const {
return nullptr;
}
Type *AttributeSetNode::getByRefType() const {
if (auto A = findEnumAttribute(Attribute::ByRef))
return A->getValueAsType();
return nullptr;
}
Type *AttributeSetNode::getPreallocatedType() const {
if (auto A = findEnumAttribute(Attribute::Preallocated))
return A->getValueAsType();
@ -1449,6 +1466,10 @@ Type *AttributeList::getParamByValType(unsigned Index) const {
return getAttributes(Index+FirstArgIndex).getByValType();
}
Type *AttributeList::getParamByRefType(unsigned Index) const {
return getAttributes(Index + FirstArgIndex).getByRefType();
}
Type *AttributeList::getParamPreallocatedType(unsigned Index) const {
return getAttributes(Index + FirstArgIndex).getPreallocatedType();
}
@ -1534,6 +1555,7 @@ void AttrBuilder::clear() {
DerefBytes = DerefOrNullBytes = 0;
AllocSizeArgs = 0;
ByValType = nullptr;
ByRefType = nullptr;
PreallocatedType = nullptr;
}
@ -1560,6 +1582,8 @@ AttrBuilder &AttrBuilder::addAttribute(Attribute Attr) {
StackAlignment = Attr.getStackAlignment();
else if (Kind == Attribute::ByVal)
ByValType = Attr.getValueAsType();
else if (Kind == Attribute::ByRef)
ByRefType = Attr.getValueAsType();
else if (Kind == Attribute::Preallocated)
PreallocatedType = Attr.getValueAsType();
else if (Kind == Attribute::Dereferenceable)
@ -1586,6 +1610,8 @@ AttrBuilder &AttrBuilder::removeAttribute(Attribute::AttrKind Val) {
StackAlignment.reset();
else if (Val == Attribute::ByVal)
ByValType = nullptr;
else if (Val == Attribute::ByRef)
ByRefType = nullptr;
else if (Val == Attribute::Preallocated)
PreallocatedType = nullptr;
else if (Val == Attribute::Dereferenceable)
@ -1676,6 +1702,12 @@ AttrBuilder &AttrBuilder::addByValAttr(Type *Ty) {
return *this;
}
AttrBuilder &AttrBuilder::addByRefAttr(Type *Ty) {
Attrs[Attribute::ByRef] = true;
ByRefType = Ty;
return *this;
}
AttrBuilder &AttrBuilder::addPreallocatedAttr(Type *Ty) {
Attrs[Attribute::Preallocated] = true;
PreallocatedType = Ty;
@ -1702,6 +1734,9 @@ AttrBuilder &AttrBuilder::merge(const AttrBuilder &B) {
if (!ByValType)
ByValType = B.ByValType;
if (!ByRefType)
ByRefType = B.ByRefType;
if (!PreallocatedType)
PreallocatedType = B.PreallocatedType;
@ -1733,6 +1768,9 @@ AttrBuilder &AttrBuilder::remove(const AttrBuilder &B) {
if (B.ByValType)
ByValType = nullptr;
if (B.ByRefType)
ByRefType = nullptr;
if (B.PreallocatedType)
PreallocatedType = nullptr;
@ -1796,7 +1834,7 @@ bool AttrBuilder::operator==(const AttrBuilder &B) {
return Alignment == B.Alignment && StackAlignment == B.StackAlignment &&
DerefBytes == B.DerefBytes && ByValType == B.ByValType &&
PreallocatedType == B.PreallocatedType;
ByRefType == B.ByRefType && PreallocatedType == B.PreallocatedType;
}
//===----------------------------------------------------------------------===//
@ -1825,7 +1863,8 @@ AttrBuilder AttributeFuncs::typeIncompatible(Type *Ty) {
.addAttribute(Attribute::StructRet)
.addAttribute(Attribute::InAlloca)
.addPreallocatedAttr(Ty)
.addByValAttr(Ty);
.addByValAttr(Ty)
.addByRefAttr(Ty);
return Incompatible;
}

View File

@ -102,6 +102,12 @@ bool Argument::hasByValAttr() const {
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);
}
@ -129,27 +135,52 @@ bool Argument::hasPassPointeeByValueCopyAttr() const {
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 type.
static Type *getMemoryParamAllocType(AttributeSet ParamAttrs, Type *ArgTy) {
// 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())
return DL.getTypeAllocSize(ByValTy);
return ByValTy;
if (Type *ByRefTy = ParamAttrs.getByRefType())
return ByRefTy;
if (Type *PreAllocTy = ParamAttrs.getPreallocatedType())
return DL.getTypeAllocSize(PreAllocTy);
return PreAllocTy;
// FIXME: inalloca always depends on pointee element type. It's also possible
// for byval to miss it.
if (ParamAttrs.hasAttribute(Attribute::InAlloca) ||
ParamAttrs.hasAttribute(Attribute::ByVal) ||
ParamAttrs.hasAttribute(Attribute::Preallocated))
return DL.getTypeAllocSize(cast<PointerType>(getType())->getElementType());
return cast<PointerType>(ArgTy)->getElementType();
return nullptr;
}
uint64_t Argument::getPassPointeeByValueCopySize(const DataLayout &DL) const {
AttributeSet ParamAttrs =
getParent()->getAttributes().getParamAttributes(getArgNo());
if (Type *MemTy = getMemoryParamAllocType(ParamAttrs, getType()))
return DL.getTypeAllocSize(MemTy);
return 0;
}
Type *Argument::getPointeeInMemoryValueType() const {
AttributeSet ParamAttrs =
getParent()->getAttributes().getParamAttributes(getArgNo());
return getMemoryParamAllocType(ParamAttrs, getType());
}
unsigned Argument::getParamAlignment() const {
assert(getType()->isPointerTy() && "Only pointers have alignments");
return getParent()->getParamAlignment(getArgNo());
@ -165,6 +196,11 @@ Type *Argument::getParamByValType() const {
return getParent()->getParamByValType(getArgNo());
}
Type *Argument::getParamByRefType() const {
assert(getType()->isPointerTy() && "Only pointers have byval types");
return getParent()->getParamByRefType(getArgNo());
}
uint64_t Argument::getDereferenceableBytes() const {
assert(getType()->isPointerTy() &&
"Only pointers have dereferenceable bytes");

View File

@ -1652,9 +1652,10 @@ void Verifier::verifyParameterAttrs(AttributeSet Attrs, Type *Ty,
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) &&
@ -1720,9 +1721,10 @@ void Verifier::verifyParameterAttrs(AttributeSet Attrs, Type *Ty,
SmallPtrSet<Type*, 4> Visited;
if (!PTy->getElementType()->isSized(&Visited)) {
Assert(!Attrs.hasAttribute(Attribute::ByVal) &&
!Attrs.hasAttribute(Attribute::InAlloca) &&
!Attrs.hasAttribute(Attribute::Preallocated),
"Attributes 'byval', 'inalloca', and 'preallocated' do not "
!Attrs.hasAttribute(Attribute::ByRef) &&
!Attrs.hasAttribute(Attribute::InAlloca) &&
!Attrs.hasAttribute(Attribute::Preallocated),
"Attributes 'byval', 'byref', 'inalloca', and 'preallocated' do not "
"support unsized types!",
V);
}
@ -1731,10 +1733,18 @@ void Verifier::verifyParameterAttrs(AttributeSet Attrs, Type *Ty,
"Attribute 'swifterror' only applies to parameters "
"with pointer to pointer type!",
V);
if (Attrs.hasAttribute(Attribute::ByRef)) {
Assert(Attrs.getByRefType() == PTy->getElementType(),
"Attribute 'byref' type does not match parameter!", V);
}
} else {
Assert(!Attrs.hasAttribute(Attribute::ByVal),
"Attribute 'byval' only applies to parameters with pointer type!",
V);
Assert(!Attrs.hasAttribute(Attribute::ByRef),
"Attribute 'byref' only applies to parameters with pointer type!",
V);
Assert(!Attrs.hasAttribute(Attribute::SwiftError),
"Attribute 'swifterror' only applies to parameters "
"with pointer type!",
@ -1765,10 +1775,11 @@ void Verifier::verifyFunctionAttrs(FunctionType *FT, AttributeList Attrs,
!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);
@ -3174,17 +3185,19 @@ static bool isTypeCongruent(Type *L, Type *R) {
static AttrBuilder getParameterABIAttributes(int I, AttributeList Attrs) {
static const Attribute::AttrKind ABIAttrs[] = {
Attribute::StructRet, Attribute::ByVal, Attribute::InAlloca,
Attribute::InReg, Attribute::SwiftSelf, Attribute::SwiftError,
Attribute::Preallocated};
Attribute::StructRet, Attribute::ByVal, Attribute::InAlloca,
Attribute::InReg, Attribute::SwiftSelf, Attribute::SwiftError,
Attribute::Preallocated, Attribute::ByRef};
AttrBuilder Copy;
for (auto AK : ABIAttrs) {
if (Attrs.hasParamAttribute(I, AK))
Copy.addAttribute(AK);
}
// `align` is ABI-affecting only in combination with `byval`.
// `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;
}

View File

@ -895,6 +895,7 @@ Function *CodeExtractor::constructFunction(const ValueSet &inputs,
case Attribute::WriteOnly:
case Attribute::ZExt:
case Attribute::ImmArg:
case Attribute::ByRef:
case Attribute::EndAttrKinds:
case Attribute::EmptyKey:
case Attribute::TombstoneKey:

View File

@ -0,0 +1,6 @@
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
; CHECK: <stdin>:[[@LINE+1]]:34: error: expected '('{{$}}
define void @test_byref(i8* byref) {
ret void
}

View File

@ -0,0 +1,6 @@
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
; CHECK: <stdin>:[[@LINE+1]]:35: error: expected type{{$}}
define void @test_byref(i8* byref() {
ret void
}

View File

@ -0,0 +1,6 @@
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
; CHECK: <stdin>:[[@LINE+1]]:27: error: invalid use of parameter-only attribute on a function{{$}}
define void @test_byref() byref(4) {
ret void
}

View File

@ -0,0 +1,7 @@
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
; CHECK: <stdin>:[[@LINE+1]]:35: error: expected type{{$}}
define void @test_byref(i8* byref()) {
ret void
}

View File

@ -0,0 +1,6 @@
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
; CHECK: <stdin>:[[@LINE+1]]:35: error: expected type{{$}}
define void @test_byref(i8* byref(-1)) {
ret void
}

View File

@ -0,0 +1,6 @@
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
; CHECK: <stdin>:[[@LINE+1]]:35: error: expected type{{$}}
define void @test_byref(i8* byref(0)) {
ret void
}

View File

@ -0,0 +1,6 @@
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
; CHECK: <stdin>:[[@LINE+1]]:8: error: invalid use of parameter-only attribute{{$}}
define byref i8* @test_byref() {
ret void
}

View File

@ -0,0 +1,6 @@
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
; CHECK: <stdin>:[[@LINE+1]]:8: error: invalid use of parameter-only attribute{{$}}
define byref 8 i8* @test_byref() {
ret void
}

View File

@ -0,0 +1,6 @@
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
; CHECK: <stdin>:[[@LINE+1]]:8: error: invalid use of parameter-only attribute{{$}}
define byref(8) i8* @test_byref() {
ret void
}

View File

@ -0,0 +1,6 @@
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
; CHECK: <stdin>:[[@LINE+1]]:27: error: invalid use of parameter-only attribute on a function{{$}}
define void @test_byref() byref {
ret void
}

View File

@ -0,0 +1,6 @@
; RUN: not llvm-as < %s 2>&1 | FileCheck %s
; CHECK: <stdin>:[[@LINE+1]]:27: error: invalid use of parameter-only attribute on a function{{$}}
define void @test_byref() byref=4 {
ret void
}

View File

@ -392,6 +392,12 @@ define noundef i32 @f66(i32 noundef %a)
ret i32 %a
}
; CHECK: define void @f67(i32* byref(i32) %a)
define void @f67(i32* byref(i32) %a)
{
ret void
}
; CHECK: attributes #0 = { noreturn }
; CHECK: attributes #1 = { nounwind }
; CHECK: attributes #2 = { readnone }

20
test/CodeGen/X86/byref.ll Normal file
View File

@ -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(%Foo))
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(%Foo) %value)
; CHECK-NOT: %esp
; CHECK: pushl
; CHECK: calll _i@4
call x86_stdcallcc void @i(i32 0)
ret void
}

View File

@ -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(%struct.foo) align 8 %foo) #2
ret void
}
declare dso_local void @func1(%struct.foo* byref(%struct.foo) align 8)

View File

@ -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(i32) %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
}

View File

@ -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(float) nocapture %a, float* %b) #0 {
; CHECK-LABEL: define {{[^@]+}}@byref_callee
; CHECK-SAME: (float* nocapture byref(float) align 128 [[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(float) %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 }

View File

@ -89,3 +89,11 @@ define i64 @test_objectsize_byval_arg([42 x i8]* byval([42 x i8]) %ptr) {
%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([42 x i8]* byref([42 x i8]) %ptr) {
%cast = bitcast [42 x i8]* %ptr to i8*
%size = tail call i64 @llvm.objectsize.i64(i8* %cast, i1 true, i1 false, i1 false)
ret i64 %size
}

100
test/Verifier/byref.ll Normal file
View File

@ -0,0 +1,100 @@
; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s
; CHECK: Attribute 'byref' type does not match parameter!
; CHECK-NEXT: void (i32*)* @byref_mismatched_pointee_type0
define void @byref_mismatched_pointee_type0(i32* byref(i8)) {
ret void
}
; CHECK: Attribute 'byref' type does not match parameter!
; CHECK-NEXT: void (i8*)* @byref_mismatched_pointee_type1
define void @byref_mismatched_pointee_type1(i8* byref(i32)) {
ret void
}
%opaque.ty = type opaque
; CHECK: Attributes 'byval', 'byref', 'inalloca', and 'preallocated' do not support unsized types!
; CHECK-NEXT: void (%opaque.ty*)* @byref_unsized
define void @byref_unsized(%opaque.ty* byref(%opaque.ty)) {
ret void
}
; CHECK: Attributes 'byval', 'inalloca', 'preallocated', 'inreg', 'nest', 'byref', and 'sret' are incompatible!
; CHECK-NEXT: void (i32*)* @byref_byval
define void @byref_byval(i32* byref(i32) 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(i32) 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(i32) 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(i32) 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(i32) 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(i32) nest) {
ret void
}
; CHECK: Wrong types for attribute: inalloca nest noalias nocapture nonnull readnone readonly sret byref(i32) byval(i32) preallocated(i32) dereferenceable(1) dereferenceable_or_null(1)
; CHECK-NEXT: void (i32)* @byref_non_pointer
define void @byref_non_pointer(i32 byref(i32)) {
ret void
}
define void @byref_callee([64 x i8]* byref([64 x i8])) {
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([64 x i8]* byref([64 x i8]) %cast)
; CHECK-NEXT: i8* %ptr
define void @musttail_byref_caller(i8* %ptr) {
%cast = bitcast i8* %ptr to [64 x i8]*
musttail call void @byref_callee([64 x i8]* byref([64 x i8]) %cast)
ret void
}
; CHECK: cannot guarantee tail call due to mismatched ABI impacting function attributes
; CHECK-NEXT: musttail call void @byref_callee([64 x i8]* %ptr)
; CHECK-NEXT: [64 x i8]* %ptr
define void @musttail_byref_callee([64 x i8]* byref([64 x i8]) %ptr) {
musttail call void @byref_callee([64 x i8]* %ptr)
ret void
}
define void @byref_callee_align32(i8* byref([64 x i8]) 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* byref([64 x i8]) align 32 %ptr)
; CHECK-NEXT: i8* %ptr
define void @musttail_byref_caller_mismatched_align(i8* byref([64 x i8]) align 16 %ptr) {
musttail call void @byref_callee_align32(i8* byref([64 x i8]) align 32 %ptr)
ret void
}