a valid attribute for return values and can only be applied to one
parameter.
+``swifterror``
+ This attribute is motivated to model and optimize Swift error handling. It
+ can be applied to a parameter with pointer to pointer type or a
+ pointer-sized alloca. At the call site, the actual argument that corresponds
+ to a ``swifterror`` parameter has to come from a ``swifterror`` alloca. A
+ ``swifterror`` value (either the parameter or the alloca) can only be loaded
+ and stored from, or used as a ``swifterror`` argument. This is not a valid
+ attribute for return values and can only be applied to one parameter.
+
+ These constraints allow the calling convention to optimize access to
+ ``swifterror`` variables by associating them with a specific register at
+ call boundaries rather than placing them in memory. Since this does change
+ the calling convention, a function which uses the ``swifterror`` attribute
+ on a parameter is not ABI-compatible with one which does not.
+
+ These constraints also allow LLVM to assume that a ``swifterror`` argument
+ does not alias any other memory visible within a function and that a
+ ``swifterror`` alloca passed as an argument does not escape.
+
.. _gc:
Garbage Collector Strategy Names
LLVMConvergentAttribute = 1ULL << 46,
LLVMSafeStackAttribute = 1ULL << 47,
LLVMSwiftSelfAttribute = 1ULL << 48,
+ LLVMSwiftErrorAttribute = 1ULL << 49,
*/
} LLVMAttribute;
bool IsInAlloca : 1;
bool IsReturned : 1;
bool IsSwiftSelf : 1;
+ bool IsSwiftError : 1;
uint16_t Alignment;
ArgListEntry()
: Val(nullptr), Ty(nullptr), IsSExt(false), IsZExt(false),
IsInReg(false), IsSRet(false), IsNest(false), IsByVal(false),
IsInAlloca(false), IsReturned(false), IsSwiftSelf(false),
- Alignment(0) {}
+ IsSwiftError(false), Alignment(0) {}
/// \brief Set CallLoweringInfo attribute flags based on a call instruction
/// and called function attributes.
/// \brief Return true if this argument has the swiftself attribute.
bool hasSwiftSelfAttr() const;
+ /// \brief Return true if this argument has the swifterror attribute.
+ bool hasSwiftErrorAttr() const;
+
/// \brief Return true if this argument has the byval attribute or inalloca
/// attribute on it in its containing function. These attributes both
/// represent arguments being passed by value.
/// MemorySanitizer is on.
def SanitizeMemory : EnumAttr<"sanitize_memory">;
+/// Argument is swift error.
+def SwiftError : EnumAttr<"swifterror">;
+
/// Argument is swift self/context.
def SwiftSelf : EnumAttr<"swiftself">;
(V ? 32 : 0));
}
+ /// \brief Return true if this alloca is used as a swifterror argument to a
+ /// call.
+ bool isSwiftError() const {
+ return getSubclassDataFromInstruction() & 64;
+ }
+
+ /// \brief Specify whether this alloca is used to represent a swifterror.
+ void setSwiftError(bool V) {
+ setInstructionSubclassData((getSubclassDataFromInstruction() & ~64) |
+ (V ? 64 : 0));
+ }
+
// Methods for support type inquiry through isa, cast, and dyn_cast:
static inline bool classof(const Instruction *I) {
return (I->getOpcode() == Instruction::Alloca);
static const uint64_t SplitEndOffs = 13;
static const uint64_t SwiftSelf = 1ULL<<14; ///< Swift self parameter
static const uint64_t SwiftSelfOffs = 14;
+ static const uint64_t SwiftError = 1ULL<<15; ///< Swift error parameter
+ static const uint64_t SwiftErrorOffs = 15;
static const uint64_t OrigAlign = 0x1FULL<<27;
static const uint64_t OrigAlignOffs = 27;
static const uint64_t ByValSize = 0x3fffffffULL<<32; ///< Struct size
bool isSwiftSelf() const { return Flags & SwiftSelf; }
void setSwiftSelf() { Flags |= One << SwiftSelfOffs; }
+ bool isSwiftError() const { return Flags & SwiftError; }
+ void setSwiftError() { Flags |= One << SwiftErrorOffs; }
+
bool isNest() const { return Flags & Nest; }
void setNest() { Flags |= One << NestOffs; }
class CCIfSwiftSelf<CCAction A> : CCIf<"ArgFlags.isSwiftSelf()", A> {
}
+/// CCIfSwiftError - If the current argument has swifterror parameter attribute,
+/// apply Action A.
+class CCIfSwiftError<CCAction A> : CCIf<"ArgFlags.isSwiftError()", A> {
+}
+
/// CCIfConsecutiveRegs - If the current argument has InConsecutiveRegs
/// parameter attribute, apply Action A.
class CCIfConsecutiveRegs<CCAction A> : CCIf<"ArgFlags.isInConsecutiveRegs()", A> {
bool isInAlloca : 1;
bool isReturned : 1;
bool isSwiftSelf : 1;
+ bool isSwiftError : 1;
uint16_t Alignment;
ArgListEntry() : isSExt(false), isZExt(false), isInReg(false),
isSRet(false), isNest(false), isByVal(false), isInAlloca(false),
- isReturned(false), isSwiftSelf(false), Alignment(0) { }
+ isReturned(false), isSwiftSelf(false), isSwiftError(false),
+ Alignment(0) { }
void setAttributes(ImmutableCallSite *CS, unsigned AttrIdx);
};
KEYWORD(sanitize_address);
KEYWORD(sanitize_thread);
KEYWORD(sanitize_memory);
+ KEYWORD(swifterror);
KEYWORD(swiftself);
KEYWORD(uwtable);
KEYWORD(zeroext);
case lltok::kw_nonnull:
case lltok::kw_returned:
case lltok::kw_sret:
+ case lltok::kw_swifterror:
case lltok::kw_swiftself:
HaveError |=
Error(Lex.getLoc(),
case lltok::kw_returned: B.addAttribute(Attribute::Returned); break;
case lltok::kw_signext: B.addAttribute(Attribute::SExt); break;
case lltok::kw_sret: B.addAttribute(Attribute::StructRet); break;
+ case lltok::kw_swifterror: B.addAttribute(Attribute::SwiftError); break;
case lltok::kw_swiftself: B.addAttribute(Attribute::SwiftSelf); break;
case lltok::kw_zeroext: B.addAttribute(Attribute::ZExt); break;
case lltok::kw_nocapture:
case lltok::kw_returned:
case lltok::kw_sret:
+ case lltok::kw_swifterror:
case lltok::kw_swiftself:
HaveError |= Error(Lex.getLoc(), "invalid use of parameter-only attribute");
break;
//===----------------------------------------------------------------------===//
/// ParseAlloc
-/// ::= 'alloca' 'inalloca'? Type (',' TypeAndValue)? (',' 'align' i32)?
+/// ::= 'alloca' 'inalloca'? 'swifterror'? Type (',' TypeAndValue)?
+/// (',' 'align' i32)?
int LLParser::ParseAlloc(Instruction *&Inst, PerFunctionState &PFS) {
Value *Size = nullptr;
LocTy SizeLoc, TyLoc;
Type *Ty = nullptr;
bool IsInAlloca = EatIfPresent(lltok::kw_inalloca);
+ bool IsSwiftError = EatIfPresent(lltok::kw_swifterror);
if (ParseType(Ty, TyLoc)) return true;
AllocaInst *AI = new AllocaInst(Ty, Size, Alignment);
AI->setUsedWithInAlloca(IsInAlloca);
+ AI->setSwiftError(IsSwiftError);
Inst = AI;
return AteExtraComma ? InstExtraComma : InstNormal;
}
kw_sret,
kw_sanitize_thread,
kw_sanitize_memory,
+ kw_swifterror,
kw_swiftself,
kw_uwtable,
kw_zeroext,
return Attribute::SanitizeThread;
case bitc::ATTR_KIND_SANITIZE_MEMORY:
return Attribute::SanitizeMemory;
+ case bitc::ATTR_KIND_SWIFT_ERROR:
+ return Attribute::SwiftError;
case bitc::ATTR_KIND_SWIFT_SELF:
return Attribute::SwiftSelf;
case bitc::ATTR_KIND_UW_TABLE:
uint64_t AlignRecord = Record[3];
const uint64_t InAllocaMask = uint64_t(1) << 5;
const uint64_t ExplicitTypeMask = uint64_t(1) << 6;
- // Reserve bit 7 for SwiftError flag.
- // const uint64_t SwiftErrorMask = uint64_t(1) << 7;
- const uint64_t FlagMask = InAllocaMask | ExplicitTypeMask;
+ const uint64_t SwiftErrorMask = uint64_t(1) << 7;
+ const uint64_t FlagMask = InAllocaMask | ExplicitTypeMask |
+ SwiftErrorMask;
bool InAlloca = AlignRecord & InAllocaMask;
+ bool SwiftError = AlignRecord & SwiftErrorMask;
Type *Ty = getTypeByID(Record[0]);
if ((AlignRecord & ExplicitTypeMask) == 0) {
auto *PTy = dyn_cast_or_null<PointerType>(Ty);
return error("Invalid record");
AllocaInst *AI = new AllocaInst(Ty, Size, Align);
AI->setUsedWithInAlloca(InAlloca);
+ AI->setSwiftError(SwiftError);
I = AI;
InstructionList.push_back(I);
break;
return bitc::ATTR_KIND_SANITIZE_THREAD;
case Attribute::SanitizeMemory:
return bitc::ATTR_KIND_SANITIZE_MEMORY;
+ case Attribute::SwiftError:
+ return bitc::ATTR_KIND_SWIFT_ERROR;
case Attribute::SwiftSelf:
return bitc::ATTR_KIND_SWIFT_SELF;
case Attribute::UWTable:
assert(AlignRecord < 1 << 5 && "alignment greater than 1 << 64");
AlignRecord |= AI.isUsedWithInAlloca() << 5;
AlignRecord |= 1 << 6;
- // Reserve bit 7 for SwiftError flag.
- // AlignRecord |= AI.isSwiftError() << 7;
+ AlignRecord |= AI.isSwiftError() << 7;
Vals.push_back(AlignRecord);
break;
}
IsInAlloca = CS->paramHasAttr(AttrIdx, Attribute::InAlloca);
IsReturned = CS->paramHasAttr(AttrIdx, Attribute::Returned);
IsSwiftSelf = CS->paramHasAttr(AttrIdx, Attribute::SwiftSelf);
+ IsSwiftError = CS->paramHasAttr(AttrIdx, Attribute::SwiftError);
Alignment = CS->getParamAlignment(AttrIdx);
}
Flags.setSRet();
if (Arg.IsSwiftSelf)
Flags.setSwiftSelf();
+ if (Arg.IsSwiftError)
+ Flags.setSwiftError();
if (Arg.IsByVal)
Flags.setByVal();
if (Arg.IsInAlloca) {
Entry.isByVal = false;
Entry.isReturned = false;
Entry.isSwiftSelf = false;
+ Entry.isSwiftError = false;
Entry.Alignment = Align;
CLI.getArgs().insert(CLI.getArgs().begin(), Entry);
CLI.RetTy = Type::getVoidTy(CLI.RetTy->getContext());
Flags.setSRet();
if (Args[i].isSwiftSelf)
Flags.setSwiftSelf();
+ if (Args[i].isSwiftError)
+ Flags.setSwiftError();
if (Args[i].isByVal)
Flags.setByVal();
if (Args[i].isInAlloca) {
Flags.setSRet();
if (F.getAttributes().hasAttribute(Idx, Attribute::SwiftSelf))
Flags.setSwiftSelf();
+ if (F.getAttributes().hasAttribute(Idx, Attribute::SwiftError))
+ Flags.setSwiftError();
if (F.getAttributes().hasAttribute(Idx, Attribute::ByVal))
Flags.setByVal();
if (F.getAttributes().hasAttribute(Idx, Attribute::InAlloca)) {
isInAlloca = CS->paramHasAttr(AttrIdx, Attribute::InAlloca);
isReturned = CS->paramHasAttr(AttrIdx, Attribute::Returned);
isSwiftSelf = CS->paramHasAttr(AttrIdx, Attribute::SwiftSelf);
+ isSwiftError = CS->paramHasAttr(AttrIdx, Attribute::SwiftError);
Alignment = CS->getParamAlignment(AttrIdx);
}
Out << ' ';
if (AI->isUsedWithInAlloca())
Out << "inalloca ";
+ if (AI->isSwiftError())
+ Out << "swifterror ";
TypePrinter.print(AI->getAllocatedType(), Out);
// Explicitly write the array size if the code is broken, if it's an array
return "byval";
if (hasAttribute(Attribute::Convergent))
return "convergent";
+ if (hasAttribute(Attribute::SwiftError))
+ return "swifterror";
if (hasAttribute(Attribute::SwiftSelf))
return "swiftself";
if (hasAttribute(Attribute::InaccessibleMemOnly))
case Attribute::InaccessibleMemOnly: return 1ULL << 49;
case Attribute::InaccessibleMemOrArgMemOnly: return 1ULL << 50;
case Attribute::SwiftSelf: return 1ULL << 51;
+ case Attribute::SwiftError: return 1ULL << 52;
case Attribute::Dereferenceable:
llvm_unreachable("dereferenceable attribute not supported in raw format");
break;
hasAttribute(getArgNo()+1, Attribute::SwiftSelf);
}
+bool Argument::hasSwiftErrorAttr() const {
+ return getParent()->getAttributes().
+ hasAttribute(getArgNo()+1, Attribute::SwiftError);
+}
+
/// \brief Return true if this argument has the inalloca attribute on it in
/// its containing function.
bool Argument::hasInAllocaAttr() const {
AllocaInst *Result = new AllocaInst(getAllocatedType(),
(Value *)getOperand(0), getAlignment());
Result->setUsedWithInAlloca(isUsedWithInAlloca());
+ Result->setSwiftError(isSwiftError());
return Result;
}
void visitCleanupReturnInst(CleanupReturnInst &CRI);
void verifyCallSite(CallSite CS);
+ void verifySwiftErrorCallSite(CallSite CS, const Value *SwiftErrorVal);
+ void verifySwiftErrorValue(const Value *SwiftErrorVal);
void verifyMustTailCall(CallInst &CI);
bool performTypeCheck(Intrinsic::ID ID, Function *F, Type *Ty, int VT,
unsigned ArgNo, std::string &Suffix);
!Attrs.hasAttribute(Idx, Attribute::NoCapture) &&
!Attrs.hasAttribute(Idx, Attribute::Returned) &&
!Attrs.hasAttribute(Idx, Attribute::InAlloca) &&
- !Attrs.hasAttribute(Idx, Attribute::SwiftSelf),
+ !Attrs.hasAttribute(Idx, Attribute::SwiftSelf) &&
+ !Attrs.hasAttribute(Idx, Attribute::SwiftError),
"Attributes 'byval', 'inalloca', 'nest', 'sret', 'nocapture', "
- "'returned', and 'swiftself' do not apply to return "
+ "'returned', 'swiftself', and 'swifterror' do not apply to return "
"values!",
V);
"Attributes 'byval' and 'inalloca' do not support unsized types!",
V);
}
+ if (!isa<PointerType>(PTy->getElementType()))
+ Assert(!Attrs.hasAttribute(Idx, Attribute::SwiftError),
+ "Attribute 'swifterror' only applies to parameters "
+ "with pointer to pointer type!",
+ V);
} else {
Assert(!Attrs.hasAttribute(Idx, Attribute::ByVal),
"Attribute 'byval' only applies to parameters with pointer type!",
V);
+ Assert(!Attrs.hasAttribute(Idx, Attribute::SwiftError),
+ "Attribute 'swifterror' only applies to parameters "
+ "with pointer type!",
+ V);
}
}
bool SawReturned = false;
bool SawSRet = false;
bool SawSwiftSelf = false;
+ bool SawSwiftError = false;
for (unsigned i = 0, e = Attrs.getNumSlots(); i != e; ++i) {
unsigned Idx = Attrs.getSlotIndex(i);
SawSwiftSelf = true;
}
+ if (Attrs.hasAttribute(Idx, Attribute::SwiftError)) {
+ Assert(!SawSwiftError, "Cannot have multiple 'swifterror' parameters!",
+ V);
+ SawSwiftError = true;
+ }
+
if (Attrs.hasAttribute(Idx, Attribute::InAlloca)) {
Assert(Idx == FT->getNumParams(), "inalloca isn't on the last parameter!",
V);
Assert(!Arg.getType()->isTokenTy(),
"Function takes token but isn't an intrinsic", &Arg, &F);
}
+
+ // Check that swifterror argument is only used by loads and stores.
+ if (Attrs.hasAttribute(i+1, Attribute::SwiftError)) {
+ verifySwiftErrorValue(&Arg);
+ }
++i;
}
"inalloca argument for call has mismatched alloca", AI, I);
}
+ // For each argument of the callsite, if it has the swifterror argument,
+ // make sure the underlying alloca has swifterror as well.
+ for (unsigned i = 0, e = FTy->getNumParams(); i != e; ++i)
+ if (CS.paramHasAttr(i+1, Attribute::SwiftError)) {
+ Value *SwiftErrorArg = CS.getArgument(i);
+ auto AI = dyn_cast<AllocaInst>(SwiftErrorArg->stripInBoundsOffsets());
+ Assert(AI, "swifterror argument should come from alloca", AI, I);
+ if (AI)
+ Assert(AI->isSwiftError(),
+ "swifterror argument for call has mismatched alloca", AI, I);
+ }
+
if (FTy->isVarArg()) {
// FIXME? is 'nest' even legal here?
bool SawNest = false;
static AttrBuilder getParameterABIAttributes(int I, AttributeSet Attrs) {
static const Attribute::AttrKind ABIAttrs[] = {
Attribute::StructRet, Attribute::ByVal, Attribute::InAlloca,
- Attribute::InReg, Attribute::Returned, Attribute::SwiftSelf};
+ Attribute::InReg, Attribute::Returned, Attribute::SwiftSelf,
+ Attribute::SwiftError};
AttrBuilder Copy;
for (auto AK : ABIAttrs) {
if (Attrs.hasAttribute(I + 1, AK))
visitInstruction(SI);
}
+/// Check that SwiftErrorVal is used as a swifterror argument in CS.
+void Verifier::verifySwiftErrorCallSite(CallSite CS,
+ const Value *SwiftErrorVal) {
+ unsigned Idx = 0;
+ for (CallSite::arg_iterator I = CS.arg_begin(), E = CS.arg_end();
+ I != E; ++I, ++Idx) {
+ if (*I == SwiftErrorVal) {
+ Assert(CS.paramHasAttr(Idx+1, Attribute::SwiftError),
+ "swifterror value when used in a callsite should be marked "
+ "with swifterror attribute",
+ SwiftErrorVal, CS);
+ }
+ }
+}
+
+void Verifier::verifySwiftErrorValue(const Value *SwiftErrorVal) {
+ // Check that swifterror value is only used by loads, stores, or as
+ // a swifterror argument.
+ for (const User *U : SwiftErrorVal->users()) {
+ Assert(isa<LoadInst>(U) || isa<StoreInst>(U) || isa<CallInst>(U) ||
+ isa<InvokeInst>(U),
+ "swifterror value can only be loaded and stored from, or "
+ "as a swifterror argument!",
+ SwiftErrorVal, U);
+ // If it is used by a store, check it is the second operand.
+ if (auto StoreI = dyn_cast<StoreInst>(U))
+ Assert(StoreI->getOperand(1) == SwiftErrorVal,
+ "swifterror value should be the second operand when used "
+ "by stores", SwiftErrorVal, U);
+ if (auto CallI = dyn_cast<CallInst>(U))
+ verifySwiftErrorCallSite(const_cast<CallInst*>(CallI), SwiftErrorVal);
+ if (auto II = dyn_cast<InvokeInst>(U))
+ verifySwiftErrorCallSite(const_cast<InvokeInst*>(II), SwiftErrorVal);
+ }
+}
+
void Verifier::visitAllocaInst(AllocaInst &AI) {
SmallPtrSet<Type*, 4> Visited;
PointerType *PTy = AI.getType();
Assert(AI.getAlignment() <= Value::MaximumAlignment,
"huge alignment values are unsupported", &AI);
+ if (AI.isSwiftError()) {
+ verifySwiftErrorValue(&AI);
+ }
+
visitInstruction(AI);
}
ret void;
}
+; CHECK: define i32 @f51(i8** swifterror)
+define i32 @f51(i8** swifterror)
+{
+ ret i32 0
+}
+
+; CHECK: define i32 @f52(i32, i8** swifterror)
+define i32 @f52(i32, i8** swifterror)
+{
+ ret i32 0
+}
+
+%swift_error = type {i64, i8}
+declare float @foo(%swift_error** swifterror %error_ptr_ref)
+
+; CHECK: define float @f53
+; CHECK: alloca swifterror
+define float @f53(i8* %error_ref) {
+entry:
+ %error_ptr_ref = alloca swifterror %swift_error*
+ store %swift_error* null, %swift_error** %error_ptr_ref
+ %call = call float @foo(%swift_error** swifterror %error_ptr_ref)
+ ret float 1.0
+}
+
; CHECK: attributes #0 = { noreturn }
; CHECK: attributes #1 = { nounwind }
; CHECK: attributes #2 = { readnone }
--- /dev/null
+; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s
+
+%swift_error = type {i64, i8}
+
+; CHECK: swifterror value can only be loaded and stored from, or as a swifterror argument!
+; CHECK: %swift_error** %error_ptr_ref
+; CHECK: %t = getelementptr inbounds %swift_error*, %swift_error** %error_ptr_ref, i64 1
+define float @foo(%swift_error** swifterror %error_ptr_ref) {
+ %t = getelementptr inbounds %swift_error*, %swift_error** %error_ptr_ref, i64 1
+ ret float 1.0
+}
+
+; CHECK: swifterror argument for call has mismatched alloca
+; CHECK: %error_ptr_ref = alloca %swift_error*
+; CHECK: %call = call float @foo(%swift_error** swifterror %error_ptr_ref)
+define float @caller(i8* %error_ref) {
+entry:
+ %error_ptr_ref = alloca %swift_error*
+ store %swift_error* null, %swift_error** %error_ptr_ref
+ %call = call float @foo(%swift_error** swifterror %error_ptr_ref)
+ ret float 1.0
+}
+
+; CHECK: Cannot have multiple 'swifterror' parameters!
+declare void @a(i32** swifterror %a, i32** swifterror %b)
+
+; CHECK: Attribute 'swifterror' only applies to parameters with pointer type!
+declare void @b(i32 swifterror %a)
+
+; CHECK: Attribute 'swifterror' only applies to parameters with pointer to pointer type!
+declare void @c(i32* swifterror %a)
--- /dev/null
+; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s
+
+; CHECK: invalid use of parameter-only attribute
+declare swifterror void @c(i32** swifterror %a)
--- /dev/null
+; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s
+
+; CHECK: expected type
+declare void @c(swifterror i32* %a)