Begin adding docs and IR-level support for the inalloca attribute

The inalloca attribute is designed to support passing C++ objects by
value in the Microsoft C++ ABI.  It behaves the same as byval, except
that it always implies that the argument is in memory and that the bytes
are never copied.  This attribute allows the caller to take the address
of an outgoing argument's memory and execute arbitrary code to store
into it.

This patch adds basic IR support, docs, and verification.  It does not
attempt to implement any lowering or fix any possibly broken transforms.

When this patch lands, a complete description of this feature should
appear at http://llvm.org/docs/InAlloca.html .

Differential Revision: http://llvm-reviews.chandlerc.com/D2173

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@197645 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Reid Kleckner 2013-12-19 02:14:12 +00:00
parent 1d4866ccbf
commit 4b70bfc905
20 changed files with 328 additions and 27 deletions

140
docs/InAlloca.rst Normal file
View File

@ -0,0 +1,140 @@
==========================================
Design and Usage of the InAlloca Attribute
==========================================
Introduction
============
.. Warning:: This feature is unstable and not fully implemented.
The :ref:`attr_inalloca` attribute is designed to allow taking the
address of an aggregate argument that is being passed by value through
memory. Primarily, this feature is required for compatibility with the
Microsoft C++ ABI. Under that ABI, class instances that are passed by
value are constructed directly into argument stack memory. Prior to the
addition of inalloca, calls in LLVM were indivisible instructions.
There was no way to perform intermediate work, such as object
construction, between the first stack adjustment and the final control
transfer. With inalloca, each argument is modelled as an alloca, which
can be stored to independently of the call. Unfortunately, this
complicated feature comes with a large set of restrictions designed to
bound the lifetime of the argument memory around the call, which are
explained in this document.
For now, it is recommended that frontends and optimizers avoid producing
this construct, primarily because it forces the use of a base pointer.
This feature may grow in the future to allow general mid-level
optimization, but for now, it should be regarded as less efficient than
passing by value with a copy.
Intended Usage
==============
In the example below, ``f`` is attempting to pass a default-constructed
``Foo`` object to ``g`` by value.
.. code-block:: llvm
%Foo = type { i32, i32 }
declare void @Foo_ctor(%Foo* %this)
declare void @g(%Foo* inalloca %arg)
define void @f() {
...
bb1:
%base = call i8* @llvm.stacksave()
%arg = alloca %Foo
invoke void @Foo_ctor(%Foo* %arg)
to label %invoke.cont unwind %invoke.unwind
invoke.cont:
call void @g(%Foo* inalloca %arg)
call void @llvm.stackrestore(i8* %base)
...
invoke.unwind:
call void @llvm.stackrestore(i8* %base)
...
}
The alloca in this example is dynamic, meaning it is not in the entry
block, and it can be executed more than once. Due to the restrictions
against allocas between an alloca used with inalloca and its associated
call site, all allocas used with inalloca are considered dynamic.
To avoid any stack leakage, the frontend saves the current stack pointer
with a call to :ref:`llvm.stacksave <int_stacksave>`. Then, it
allocates the argument stack space with alloca and calls the default
constructor. One important consideration is that the default
constructor could throw an exception, so the frontend has to create a
landing pad. At this point, if there were any other inalloca arguments,
the frontend would have to destruct them before restoring the stack
pointer. If the constructor does not unwind, ``g`` is called, and then
the stack is restored.
Design Considerations
=====================
Lifetime
--------
The biggest design consideration for this feature is object lifetime.
We cannot model the arguments as static allocas in the entry block,
because all calls need to use the memory that is at the end of the call
frame to pass arguments. We cannot vend pointers to that memory at
function entry because after code generation they will alias. In the
current design, the rule against allocas between the inalloca alloca
values and the call site avoids this problem, but it creates a cleanup
problem. Cleanup and lifetime is handled explicitly with stack save and
restore calls. In the future, we may be able to avoid this by using
:ref:`llvm.lifetime.start <int_lifestart>` and :ref:`llvm.lifetime.end
<int_lifeend>` instead.
Nested Calls and Copy Elision
-----------------------------
The next consideration is the ability for the frontend to perform copy
elision in the face of nested calls. Consider the evaluation of
``foo(foo(Bar()))``, where ``foo`` takes and returns a ``Bar`` object by
value and ``Bar`` has non-trivial constructors. In this case, we want
to be able to elide copies into ``foo``'s argument slots. That means we
need to have more than one set of argument frames active at the same
time. First, we need to allocate the frame for the outer call so we can
pass it in as the hidden struct return pointer to the middle call. Then
we do the same for the middle call, allocating a frame and passing its
address to ``Bar``'s default constructor. By wrapping the evaluation of
the inner ``foo`` with stack save and restore, we can have multiple
overlapping active call frames.
Callee-cleanup Calling Conventions
----------------------------------
Another wrinkle is the existence of callee-cleanup conventions. On
Windows, all methods and many other functions adjust the stack to clear
the memory used to pass their arguments. In some sense, this means that
the allocas are automatically cleared by the call. However, LLVM
instead models this as a write of undef to all of the inalloca values
passed to the call instead of a stack adjustment. Frontends should
still restore the stack pointer to avoid a stack leak.
Exceptions
----------
There is also the possibility of an exception. If argument evaluation
or copy construction throws an exception, the landing pad must do
cleanup, which includes adjusting the stack pointer to avoid a stack
leak. This means the cleanup of the stack memory cannot be tied to the
call itself. There needs to be a separate IR-level instruction that can
perform independent cleanup of arguments.
Efficiency
----------
Eventually, it should be possible to generate efficient code for this
construct. In particular, using inalloca should not require a base
pointer. If the backend can prove that all points in the CFG only have
one possible stack level, then it can address the stack directly from
the stack pointer. While this is not yet implemented, the plan is that
the inalloca attribute should not change much, but the frontend IR
generation recommendations may change.

View File

@ -697,6 +697,39 @@ 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_inalloca:
``inalloca``
.. Warning:: This feature is unstable and not fully implemented.
The ``inalloca`` argument attribute allows the caller to get the
address of an outgoing argument to a ``call`` or ``invoke`` before
it executes. It is similar to ``byval`` in that it is used to pass
arguments by value, but it guarantees that the argument will not be
copied.
To be :ref:`well formed <wellformed>`, the caller must pass in an
alloca value into an ``inalloca`` parameter, and an alloca may be
used as an ``inalloca`` argument at most once. The attribute can
only be applied to parameters that would be passed in memory and not
registers. The ``inalloca`` attribute cannot be used in conjunction
with other attributes that affect argument storage, like ``inreg``,
``nest``, ``sret``, or ``byval``. The ``inalloca`` stack space is
considered to be clobbered by any call that uses it, so any
``inalloca`` parameters cannot be marked ``readonly``.
Allocas passed with ``inalloca`` to a call must be in the opposite
order of the parameter list, meaning that the rightmost argument
must be allocated first. If a call has inalloca arguments, no other
allocas can occur between the first alloca used by the call and the
call site, unless they are are cleared by calls to
:ref:`llvm.stackrestore <int_stackrestore>`. Violating these rules
results in undefined behavior at runtime.
See :doc:`InAlloca` for more information on how to use this
attribute.
``sret``
This indicates that the pointer parameter specifies the address of a
structure that is the return value of the function in the source
@ -8419,6 +8452,8 @@ Memory Use Markers
This class of intrinsics exists to information about the lifetime of
memory objects and ranges where variables are immutable.
.. _int_lifestart:
'``llvm.lifetime.start``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -8450,6 +8485,8 @@ of the memory pointed to by ``ptr`` is dead. This means that it is known
to never be used and has an undefined value. A load from the pointer
that precedes this intrinsic can be replaced with ``'undef'``.
.. _int_lifeend:
'``llvm.lifetime.end``' Intrinsic
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -167,7 +167,8 @@ typedef enum {
LLVMAddressSafety = 1ULL << 32,
LLVMStackProtectStrongAttribute = 1ULL<<33,
LLVMCold = 1ULL << 34,
LLVMOptimizeNone = 1ULL << 35
LLVMOptimizeNone = 1ULL << 35,
LLVMInAllocaAttribute = 1ULL << 36
*/
} LLVMAttribute;

View File

@ -370,7 +370,8 @@ namespace bitc {
ATTR_KIND_Z_EXT = 34,
ATTR_KIND_BUILTIN = 35,
ATTR_KIND_COLD = 36,
ATTR_KIND_OPTIMIZE_NONE = 37
ATTR_KIND_OPTIMIZE_NONE = 37,
ATTR_KIND_IN_ALLOCA = 38
};
} // End bitc namespace

View File

@ -59,7 +59,7 @@ public:
/// containing function.
bool hasByValAttr() const;
/// \brief If this is a byval argument, return its alignment.
/// \brief If this is a byval or inalloca argument, return its alignment.
unsigned getParamAlignment() const;
/// \brief Return true if this argument has the nest attribute on it in its
@ -86,6 +86,9 @@ public:
/// on it in its containing function.
bool onlyReadsMemory() const;
/// \brief Return true if this argument has the inalloca attribute on it in
/// its containing function.
bool hasInAllocaAttr() const;
/// \brief Add a Attribute to an argument.
void addAttr(AttributeSet AS);

View File

@ -71,6 +71,7 @@ public:
Builtin, ///< Callee is recognized as a builtin, despite
///< nobuiltin attribute on its declaration.
ByVal, ///< Pass structure by value
InAlloca, ///< Pass structure in an alloca
Cold, ///< Marks function as being in a cold path.
InlineHint, ///< Source said inlining was desirable
InReg, ///< Force argument to be passed in register

View File

@ -257,6 +257,22 @@ public:
return paramHasAttr(ArgNo + 1, Attribute::ByVal);
}
/// @brief Determine whether this argument is passed in an alloca.
bool isInAllocaArgument(unsigned ArgNo) const {
return paramHasAttr(ArgNo + 1, Attribute::InAlloca);
}
/// @brief Determine whether this argument is passed by value or in an alloca.
bool isByValOrInAllocaArgument(unsigned ArgNo) const {
return paramHasAttr(ArgNo + 1, Attribute::ByVal) ||
paramHasAttr(ArgNo + 1, Attribute::InAlloca);
}
/// @brief Determine if there are any inalloca arguments.
bool hasInAllocaArgument() const {
return getAttributes().hasAttrSomewhere(Attribute::InAlloca);
}
bool doesNotAccessMemory(unsigned ArgNo) const {
return paramHasAttr(ArgNo + 1, Attribute::ReadNone);
}

View File

@ -572,6 +572,7 @@ lltok::Kind LLLexer::LexIdentifier() {
KEYWORD(alwaysinline);
KEYWORD(builtin);
KEYWORD(byval);
KEYWORD(inalloca);
KEYWORD(cold);
KEYWORD(inlinehint);
KEYWORD(inreg);

View File

@ -944,6 +944,7 @@ bool LLParser::ParseFnAttributeValuePairs(AttrBuilder &B,
"invalid use of attribute on a function");
break;
case lltok::kw_byval:
case lltok::kw_inalloca:
case lltok::kw_nest:
case lltok::kw_noalias:
case lltok::kw_nocapture:
@ -1156,6 +1157,7 @@ bool LLParser::ParseOptionalParamAttrs(AttrBuilder &B) {
continue;
}
case lltok::kw_byval: B.addAttribute(Attribute::ByVal); break;
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;
case lltok::kw_noalias: B.addAttribute(Attribute::NoAlias); break;
@ -1218,6 +1220,7 @@ bool LLParser::ParseOptionalReturnAttrs(AttrBuilder &B) {
// Error handling.
case lltok::kw_align:
case lltok::kw_byval:
case lltok::kw_inalloca:
case lltok::kw_nest:
case lltok::kw_nocapture:
case lltok::kw_returned:

View File

@ -99,6 +99,7 @@ namespace lltok {
kw_sanitize_address,
kw_builtin,
kw_byval,
kw_inalloca,
kw_cold,
kw_inlinehint,
kw_inreg,

View File

@ -522,6 +522,8 @@ static Attribute::AttrKind GetAttrFromCode(uint64_t Code) {
return Attribute::Builtin;
case bitc::ATTR_KIND_BY_VAL:
return Attribute::ByVal;
case bitc::ATTR_KIND_IN_ALLOCA:
return Attribute::InAlloca;
case bitc::ATTR_KIND_COLD:
return Attribute::Cold;
case bitc::ATTR_KIND_INLINE_HINT:

View File

@ -169,6 +169,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
return bitc::ATTR_KIND_BUILTIN;
case Attribute::ByVal:
return bitc::ATTR_KIND_BY_VAL;
case Attribute::InAlloca:
return bitc::ATTR_KIND_IN_ALLOCA;
case Attribute::Cold:
return bitc::ATTR_KIND_COLD;
case Attribute::InlineHint:

View File

@ -166,6 +166,8 @@ std::string Attribute::getAsString(bool InAttrGrp) const {
return "builtin";
if (hasAttribute(Attribute::ByVal))
return "byval";
if (hasAttribute(Attribute::InAlloca))
return "inalloca";
if (hasAttribute(Attribute::InlineHint))
return "inlinehint";
if (hasAttribute(Attribute::InReg))
@ -388,6 +390,7 @@ uint64_t AttributeImpl::getAttrMask(Attribute::AttrKind Val) {
case Attribute::Cold: return 1ULL << 40;
case Attribute::Builtin: return 1ULL << 41;
case Attribute::OptimizeNone: return 1ULL << 42;
case Attribute::InAlloca: return 1ULL << 43;
}
llvm_unreachable("Unsupported attribute type");
}
@ -1174,7 +1177,8 @@ AttributeSet AttributeFuncs::typeIncompatible(Type *Ty, uint64_t Index) {
.addAttribute(Attribute::NoCapture)
.addAttribute(Attribute::ReadNone)
.addAttribute(Attribute::ReadOnly)
.addAttribute(Attribute::StructRet);
.addAttribute(Attribute::StructRet)
.addAttribute(Attribute::InAlloca);
return AttributeSet::get(Ty->getContext(), Index, Incompatible);
}

View File

@ -84,6 +84,14 @@ bool Argument::hasByValAttr() const {
hasAttribute(getArgNo()+1, Attribute::ByVal);
}
/// \brief Return true if this argument has the inalloca attribute on it in
/// its containing function.
bool Argument::hasInAllocaAttr() const {
if (!getType()->isPointerTy()) return false;
return getParent()->getAttributes().
hasAttribute(getArgNo()+1, Attribute::InAlloca);
}
unsigned Argument::getParamAlignment() const {
assert(getType()->isPointerTy() && "Only pointers have alignments");
return getParent()->getParamAlignment(getArgNo()+1);

View File

@ -813,26 +813,25 @@ void Verifier::VerifyParameterAttrs(AttributeSet Attrs, unsigned Idx, Type *Ty,
!Attrs.hasAttribute(Idx, Attribute::Nest) &&
!Attrs.hasAttribute(Idx, Attribute::StructRet) &&
!Attrs.hasAttribute(Idx, Attribute::NoCapture) &&
!Attrs.hasAttribute(Idx, Attribute::Returned),
"Attribute 'byval', 'nest', 'sret', 'nocapture', and 'returned' "
"do not apply to return values!", V);
!Attrs.hasAttribute(Idx, Attribute::Returned) &&
!Attrs.hasAttribute(Idx, Attribute::InAlloca),
"Attributes 'byval', 'inalloca', 'nest', 'sret', 'nocapture', and "
"'returned' do not apply to return values!", V);
// Check for mutually incompatible attributes.
Assert1(!((Attrs.hasAttribute(Idx, Attribute::ByVal) &&
Attrs.hasAttribute(Idx, Attribute::Nest)) ||
(Attrs.hasAttribute(Idx, Attribute::ByVal) &&
Attrs.hasAttribute(Idx, Attribute::StructRet)) ||
(Attrs.hasAttribute(Idx, Attribute::Nest) &&
Attrs.hasAttribute(Idx, Attribute::StructRet))), "Attributes "
"'byval, nest, and sret' are incompatible!", V);
// Check for mutually incompatible attributes. Only inreg is compatible with
// sret.
unsigned AttrCount = 0;
AttrCount += Attrs.hasAttribute(Idx, Attribute::ByVal);
AttrCount += Attrs.hasAttribute(Idx, Attribute::InAlloca);
AttrCount += Attrs.hasAttribute(Idx, Attribute::StructRet) ||
Attrs.hasAttribute(Idx, Attribute::InReg);
AttrCount += Attrs.hasAttribute(Idx, Attribute::Nest);
Assert1(AttrCount <= 1, "Attributes 'byval', 'inalloca', 'inreg', 'nest', "
"and 'sret' are incompatible!", V);
Assert1(!((Attrs.hasAttribute(Idx, Attribute::ByVal) &&
Attrs.hasAttribute(Idx, Attribute::Nest)) ||
(Attrs.hasAttribute(Idx, Attribute::ByVal) &&
Attrs.hasAttribute(Idx, Attribute::InReg)) ||
(Attrs.hasAttribute(Idx, Attribute::Nest) &&
Attrs.hasAttribute(Idx, Attribute::InReg))), "Attributes "
"'byval, nest, and inreg' are incompatible!", V);
Assert1(!(Attrs.hasAttribute(Idx, Attribute::InAlloca) &&
Attrs.hasAttribute(Idx, Attribute::ReadOnly)), "Attributes "
"'inalloca and readonly' are incompatible!", V);
Assert1(!(Attrs.hasAttribute(Idx, Attribute::StructRet) &&
Attrs.hasAttribute(Idx, Attribute::Returned)), "Attributes "
@ -855,14 +854,18 @@ void Verifier::VerifyParameterAttrs(AttributeSet Attrs, unsigned Idx, Type *Ty,
"Wrong types for attribute: " +
AttributeFuncs::typeIncompatible(Ty, Idx).getAsString(Idx), V);
if (PointerType *PTy = dyn_cast<PointerType>(Ty))
Assert1(!Attrs.hasAttribute(Idx, Attribute::ByVal) ||
PTy->getElementType()->isSized(),
"Attribute 'byval' does not support unsized types!", V);
else
if (PointerType *PTy = dyn_cast<PointerType>(Ty)) {
if (!PTy->getElementType()->isSized()) {
Assert1(!Attrs.hasAttribute(Idx, Attribute::ByVal) &&
!Attrs.hasAttribute(Idx, Attribute::InAlloca),
"Attributes 'byval' and 'inalloca' do not support unsized types!",
V);
}
} else {
Assert1(!Attrs.hasAttribute(Idx, Attribute::ByVal),
"Attribute 'byval' only applies to parameters with pointer type!",
V);
}
}
// VerifyFunctionAttrs - Check parameter attributes against a function type.
@ -1533,6 +1536,15 @@ void Verifier::VerifyCallSite(CallSite CS) {
// Verify call attributes.
VerifyFunctionAttrs(FTy, Attrs, I);
// Verify that values used for inalloca parameters are in fact allocas.
for (unsigned i = 0, e = CS.arg_size(); i != e; ++i) {
if (!Attrs.hasAttribute(1 + i, Attribute::InAlloca))
continue;
Value *Arg = CS.getArgument(i);
Assert2(isa<AllocaInst>(Arg), "Inalloca argument is not an alloca!", I,
Arg);
}
if (FTy->isVarArg()) {
// FIXME? is 'nest' even legal here?
bool SawNest = false;
@ -1870,6 +1882,22 @@ void Verifier::visitAllocaInst(AllocaInst &AI) {
&AI);
Assert1(AI.getArraySize()->getType()->isIntegerTy(),
"Alloca array size must have integer type", &AI);
// Verify that an alloca instruction is not used with inalloca more than once.
unsigned InAllocaUses = 0;
for (User::use_iterator UI = AI.use_begin(), UE = AI.use_end(); UI != UE;
++UI) {
CallSite CS(*UI);
if (!CS)
continue;
unsigned ArgNo = CS.getArgumentNo(UI);
if (CS.isInAllocaArgument(ArgNo)) {
InAllocaUses++;
Assert1(InAllocaUses <= 1,
"Allocas can be used at most once with inalloca!", &AI);
}
}
visitInstruction(AI);
}

View File

@ -491,6 +491,7 @@ void CppWriter::printAttributes(const AttributeSet &PAL,
HANDLE_ATTR(NoUnwind);
HANDLE_ATTR(NoAlias);
HANDLE_ATTR(ByVal);
HANDLE_ATTR(InAlloca);
HANDLE_ATTR(Nest);
HANDLE_ATTR(ReadNone);
HANDLE_ATTR(ReadOnly);

View File

@ -213,6 +213,11 @@ define void @f35() optnone noinline
ret void;
}
define void @f36(i8* inalloca) {
; CHECK: define void @f36(i8* inalloca) {
ret void
}
; CHECK: attributes #0 = { noreturn }
; CHECK: attributes #1 = { nounwind }
; CHECK: attributes #2 = { readnone }

View File

@ -0,0 +1,7 @@
; RUN: llc < %s -march=cpp | FileCheck %s
define void @f1(i8* byval, i8* inalloca) {
; CHECK: ByVal
; CHECK: InAlloca
ret void
}

View File

@ -0,0 +1,19 @@
; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s
declare void @a(i64* byval inalloca %p)
; CHECK: Attributes {{.*}} are incompatible
declare void @b(i64* inreg inalloca %p)
; CHECK: Attributes {{.*}} are incompatible
declare void @c(i64* sret inalloca %p)
; CHECK: Attributes {{.*}} are incompatible
declare void @d(i64* nest inalloca %p)
; CHECK: Attributes {{.*}} are incompatible
declare void @e(i64* readonly inalloca %p)
; CHECK: Attributes {{.*}} are incompatible
declare void @f(void ()* inalloca %p)
; CHECK: do not support unsized types

View File

@ -0,0 +1,21 @@
; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s
declare void @doit(i64* inalloca %a)
define void @a() {
entry:
%a = alloca [2 x i32]
%b = bitcast [2 x i32]* %a to i64*
call void @doit(i64* inalloca %b)
; CHECK: Inalloca argument is not an alloca!
ret void
}
define void @b() {
entry:
%a = alloca i64
call void @doit(i64* inalloca %a)
call void @doit(i64* inalloca %a)
; CHECK: Allocas can be used at most once with inalloca!
ret void
}