Skip to content

Preliminary SILGen support for address-only typed throws #69430

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Oct 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions include/swift/SIL/AbstractionPattern.h
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,22 @@ class AbstractionPattern {
/// abstraction pattern for its result type.
AbstractionPattern getFunctionResultType() const;

/// Given that the value being abstracted is a function, return the
/// abstraction pattern for its thrown error type.
llvm::Optional<AbstractionPattern> getFunctionThrownErrorType() const;

/// Utility method to adjust a thrown error pattern and thrown error type
/// to account for some quirks in type lowering.
///
/// When lowered with an opaque pattern,
///
/// - () -> () becomes () -> (),
/// - () throws(any Error) -> () becomes () -> (@error any Error),
///
/// *not* () -> (@error_indirect Never) or () -> (@error_indirect any Error).
llvm::Optional<std::pair<AbstractionPattern, CanType>>
getFunctionThrownErrorType(CanAnyFunctionType substFnInterfaceType) const;

/// Given that the value being abstracted is a function type, return
/// the abstraction pattern for one of its parameter types.
AbstractionPattern getFunctionParamType(unsigned index) const;
Expand Down Expand Up @@ -1611,15 +1627,19 @@ class AbstractionPattern {
/// Given that this is a pack expansion, do the pack elements need to be
/// passed indirectly?
bool arePackElementsPassedIndirectly(TypeConverter &TC) const;

/// If this abstraction pattern appears in function return position, how is
/// the corresponding value returned?
CallingConventionKind getResultConvention(TypeConverter &TC) const;

/// If this abstraction pattern appears in function parameter position, how
/// is the corresponding value passed?
CallingConventionKind getParameterConvention(TypeConverter &TC) const;


/// If this abstraction pattern appears in function thrown error position, how
/// is the corresponding value passed?
CallingConventionKind getErrorConvention(TypeConverter &TC) const;

/// Generate the abstraction pattern for lowering the substituted SIL
/// function type for a function type matching this abstraction pattern.
///
Expand Down
2 changes: 2 additions & 0 deletions include/swift/SIL/SILArgument.h
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,8 @@ class SILFunctionArgument : public SILArgument {

bool isIndirectResult() const;

bool isIndirectErrorResult() const;

SILArgumentConvention getArgumentConvention() const;

/// Given that this is an entry block argument, and given that it does
Expand Down
2 changes: 2 additions & 0 deletions include/swift/SIL/SILFunctionConventions.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ class SILModuleConventions {
public:
static bool isPassedIndirectlyInSIL(SILType type, SILModule &M);

static bool isThrownIndirectlyInSIL(SILType type, SILModule &M);

static bool isReturnedIndirectlyInSIL(SILType type, SILModule &M);

static SILModuleConventions getLoweredAddressConventions(SILModule &M) {
Expand Down
7 changes: 7 additions & 0 deletions include/swift/SIL/SILType.h
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,13 @@ class SILType {
return isAddressOnly(type, tc, sig, TypeExpansionContext::minimal());
}

/// Return true if this type must be thrown indirectly.
static bool isFormallyThrownIndirectly(CanType type,
Lowering::TypeConverter &tc,
CanGenericSignature sig) {
return isAddressOnly(type, tc, sig, TypeExpansionContext::minimal());
}

/// True if the type, or the referenced type of an address type, is loadable.
/// This is the opposite of isAddressOnly.
bool isLoadable(const SILFunction &F) const {
Expand Down
6 changes: 3 additions & 3 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4224,16 +4224,16 @@ namespace {
printFlag(T->isAsync(), "async");
printFlag(T->isThrowing(), "throws");
}
if (Type thrownError = T->getThrownError()) {
printFieldQuoted(thrownError.getString(), "thrown_error");
}
if (Type globalActor = T->getGlobalActor()) {
printFieldQuoted(globalActor.getString(), "global_actor");
}

printClangTypeRec(T->getClangTypeInfo(), T->getASTContext());
printAnyFunctionParamsRec(T->getParams(), "input");
printRec(T->getResult(), "output");
if (Type thrownError = T->getThrownError()) {
printRec(thrownError, "thrown_error");
}
}

void visitFunctionType(FunctionType *T, StringRef label) {
Expand Down
143 changes: 135 additions & 8 deletions lib/SIL/IR/AbstractionPattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1214,6 +1214,78 @@ AbstractionPattern AbstractionPattern::getFunctionResultType() const {
llvm_unreachable("bad kind");
}

llvm::Optional<AbstractionPattern>
AbstractionPattern::getFunctionThrownErrorType() const {
switch (getKind()) {
case Kind::Invalid:
llvm_unreachable("querying invalid abstraction pattern!");
case Kind::ObjCCompletionHandlerArgumentsType:
case Kind::Tuple:
llvm_unreachable("abstraction pattern for tuple cannot be function");
case Kind::Opaque:
return *this;
case Kind::Type: {
if (isTypeParameterOrOpaqueArchetype())
return getOpaque();

if (auto errorType = cast<AnyFunctionType>(getType())->getEffectiveThrownErrorType()) {
return AbstractionPattern(getGenericSubstitutions(),
getGenericSignatureForFunctionComponent(),
(*errorType)->getCanonicalType());
}

return llvm::None;
}
case Kind::Discard:
llvm_unreachable("don't need to discard function abstractions yet");
case Kind::ClangType:
case Kind::CFunctionAsMethodType:
case Kind::PartialCurriedCFunctionAsMethodType:
case Kind::CXXMethodType:
case Kind::PartialCurriedCXXMethodType:
case Kind::CurriedObjCMethodType:
case Kind::CurriedCFunctionAsMethodType:
case Kind::CurriedCXXMethodType:
case Kind::PartialCurriedObjCMethodType:
case Kind::ObjCMethodType:
llvm_unreachable("implement me");
case Kind::OpaqueFunction:
case Kind::OpaqueDerivativeFunction:
return llvm::None;
}
llvm_unreachable("bad kind");
}

llvm::Optional<std::pair<AbstractionPattern, CanType>>
AbstractionPattern::getFunctionThrownErrorType(
CanAnyFunctionType substFnInterfaceType) const {
auto optOrigErrorType = getFunctionThrownErrorType();
if (!optOrigErrorType)
return llvm::None;

auto &ctx = substFnInterfaceType->getASTContext();
auto optErrorType = substFnInterfaceType->getEffectiveThrownErrorType();

if (isTypeParameterOrOpaqueArchetype()) {
if (!optErrorType)
return llvm::None;

if (!(*optErrorType)->isEqual(ctx.getErrorExistentialType())) {
llvm::errs() << "unsupported reabstraction\n";
abort();
}

return std::make_pair(AbstractionPattern(*optErrorType),
(*optErrorType)->getCanonicalType());
}

if (!optErrorType)
optErrorType = ctx.getErrorExistentialType();

return std::make_pair(*optOrigErrorType,
(*optErrorType)->getCanonicalType());
}

AbstractionPattern
AbstractionPattern::getObjCMethodAsyncCompletionHandlerType(
CanType swiftCompletionHandlerType) const {
Expand Down Expand Up @@ -1939,7 +2011,7 @@ AbstractionPattern::getParameterConvention(TypeConverter &TC) const {
case Kind::Opaque:
// Maximally abstracted values are always passed indirectly.
return Indirect;

case Kind::OpaqueFunction:
case Kind::OpaqueDerivativeFunction:
case Kind::PartialCurriedObjCMethodType:
Expand All @@ -1953,16 +2025,57 @@ AbstractionPattern::getParameterConvention(TypeConverter &TC) const {
case Kind::PartialCurriedCXXMethodType:
// Function types are always passed directly
return Direct;

case Kind::ClangType:
case Kind::Type:
case Kind::Discard:
// Pass according to the formal type.
return SILType::isFormallyPassedIndirectly(getType(),
TC,
getGenericSignatureOrNull())
TC,
getGenericSignatureOrNull())
? Indirect : Direct;


case Kind::Invalid:
case Kind::Tuple:
case Kind::ObjCCompletionHandlerArgumentsType:
llvm_unreachable("should not get here");
}
}

AbstractionPattern::CallingConventionKind
AbstractionPattern::getErrorConvention(TypeConverter &TC) const {
// Tuples should be destructured.
if (isTuple()) {
return Destructured;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We ban tuple types from being thrown up in the type checker now, and even if we allowed them there... I think we'd still pass them indirectly here, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I’ll remove this. It was just a copy and paste

}
switch (getKind()) {
case Kind::Opaque:
// Maximally abstracted values are always thrown indirectly.
return Indirect;

case Kind::OpaqueFunction:
case Kind::OpaqueDerivativeFunction:
case Kind::PartialCurriedObjCMethodType:
case Kind::CurriedObjCMethodType:
case Kind::PartialCurriedCFunctionAsMethodType:
case Kind::CurriedCFunctionAsMethodType:
case Kind::CFunctionAsMethodType:
case Kind::ObjCMethodType:
case Kind::CXXMethodType:
case Kind::CurriedCXXMethodType:
case Kind::PartialCurriedCXXMethodType:
// Function types are always thrown directly
return Direct;

case Kind::ClangType:
case Kind::Type:
case Kind::Discard:
// Pass according to the formal type.
return SILType::isFormallyThrownIndirectly(getType(),
TC,
getGenericSignatureOrNull())
? Indirect : Direct;

case Kind::Invalid:
case Kind::Tuple:
case Kind::ObjCCompletionHandlerArgumentsType:
Expand Down Expand Up @@ -2514,14 +2627,28 @@ class SubstFunctionTypePatternVisitor
if (yieldType) {
substYieldType = visit(yieldType, yieldPattern);
}


CanType newErrorType;

if (auto optPair = pattern.getFunctionThrownErrorType(func)) {
auto errorPattern = optPair->first;
auto errorType = optPair->second;
newErrorType = visit(errorType, errorPattern);
}

auto newResultTy = visit(func.getResult(),
pattern.getFunctionResultType());

llvm::Optional<FunctionType::ExtInfo> extInfo;
if (func->hasExtInfo())
extInfo = func->getExtInfo();


if (newErrorType) {
if (!extInfo)
extInfo = FunctionType::ExtInfo();
extInfo = extInfo->withThrows(true, newErrorType);
}

return CanFunctionType::get(FunctionType::CanParamArrayRef(newParams),
newResultTy, extInfo);
}
Expand Down Expand Up @@ -2562,7 +2689,7 @@ const {
auto substTy = visitor.handleUnabstractedFunctionType(substType, *this,
substYieldType,
origYieldType);

auto substSig = buildGenericSignature(TC.Context, GenericSignature(),
std::move(visitor.substGenericParams),
std::move(visitor.substRequirements))
Expand Down
9 changes: 9 additions & 0 deletions lib/SIL/IR/SILArgument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ bool SILFunctionArgument::isIndirectResult() const {
return getIndex() < numIndirectResults;
}

bool SILFunctionArgument::isIndirectErrorResult() const {
auto numIndirectResults =
getFunction()->getConventions().getNumIndirectSILResults();
auto numIndirectErrorResults =
getFunction()->getConventions().getNumIndirectSILErrorResults();
return ((getIndex() >= numIndirectResults) &&
(getIndex() < numIndirectResults + numIndirectErrorResults));
}

SILArgumentConvention SILFunctionArgument::getArgumentConvention() const {
return getFunction()->getConventions().getSILArgumentConvention(getIndex());
}
Expand Down
56 changes: 30 additions & 26 deletions lib/SIL/IR/SILFunctionType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2190,32 +2190,6 @@ static CanSILFunctionType getSILFunctionType(
isAsync = true;
}

// Map 'throws' to the appropriate error convention.
// Give the type an error argument whether the substituted type semantically
// `throws` or if the abstraction pattern specifies a Swift function type
// that also throws. This prevents the need for a second possibly-thunking
// conversion when using a non-throwing function in more abstract throwing
// context.
bool isThrowing = substFnInterfaceType->getExtInfo().isThrowing();
if (auto origFnType = origType.getAs<AnyFunctionType>()) {
isThrowing |= origFnType->getExtInfo().isThrowing();
}
if (isThrowing && !foreignInfo.error &&
!foreignInfo.async) {
assert(!origType.isForeign()
&& "using native Swift error convention for foreign type!");
SILType exnType;
if (CanType thrownError = substFnInterfaceType.getThrownError()) {
exnType = TC.getLoweredType(thrownError, expansionContext);
} else {
// Untyped error throws the exception type.
exnType = SILType::getExceptionType(TC.Context);
}
assert(exnType.isObject());
errorResult = SILResultInfo(exnType.getASTType(),
ResultConvention::Owned);
}

// Get the yield type for an accessor coroutine.
SILCoroutineKind coroutineKind = SILCoroutineKind::None;
AbstractionPattern coroutineOrigYieldType = AbstractionPattern::getInvalid();
Expand Down Expand Up @@ -2308,6 +2282,36 @@ static CanSILFunctionType getSILFunctionType(
}
}

// Map 'throws' to the appropriate error convention.
// Give the type an error argument whether the substituted type semantically
// `throws` or if the abstraction pattern specifies a Swift function type
// that also throws. This prevents the need for a second possibly-thunking
// conversion when using a non-throwing function in more abstract throwing
// context.
bool isThrowing = substFnInterfaceType->getExtInfo().isThrowing();
if (auto origFnType = origType.getAs<AnyFunctionType>()) {
isThrowing |= origFnType->getExtInfo().isThrowing();
}

if (isThrowing && !foreignInfo.error &&
!foreignInfo.async) {
assert(!origType.isForeign()
&& "using native Swift error convention for foreign type!");
auto optPair = origType.getFunctionThrownErrorType(substFnInterfaceType);
assert(optPair &&
"Lowering a throwing function type against non-throwing pattern");

auto origErrorType = optPair->first;
auto errorType = optPair->second;
auto &errorTLConv = TC.getTypeLowering(origErrorType, errorType,
TypeExpansionContext::minimal());

errorResult = SILResultInfo(errorTLConv.getLoweredType().getASTType(),
errorTLConv.isAddressOnly()
? ResultConvention::Indirect
: ResultConvention::Owned);
}

// Lower the result type.
AbstractionPattern origResultType = origType.getFunctionResultType();
CanType substFormalResultType = substFnInterfaceType.getResult();
Expand Down
9 changes: 9 additions & 0 deletions lib/SIL/IR/SILType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,15 @@ bool SILModuleConventions::isPassedIndirectlyInSIL(SILType type, SILModule &M) {
return false;
}

bool SILModuleConventions::isThrownIndirectlyInSIL(SILType type, SILModule &M) {
if (SILModuleConventions(M).loweredAddresses) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (SILModuleConventions(M).loweredAddresses) {
if (SILModuleConventions(M).useLoweredAddresses()) {

return M.Types.getTypeLowering(type, TypeExpansionContext::minimal())
.isAddressOnly();
}

return false;
}

bool SILFunctionType::isNoReturnFunction(SILModule &M,
TypeExpansionContext context) const {
for (unsigned i = 0, e = getNumResults(); i < e; ++i) {
Expand Down
Loading