Files
swift-mirror/lib/SILGen/SILGenForeignError.cpp
Tim Kientzle 1d961ba22d Add #include "swift/Basic/Assertions.h" to a lot of source files
Although I don't plan to bring over new assertions wholesale
into the current qualification branch, it's entirely possible
that various minor changes in main will use the new assertions;
having this basic support in the release branch will simplify that.
(This is why I'm adding the includes as a separate pass from
rewriting the individual assertions)
2024-06-05 19:37:30 -07:00

470 lines
19 KiB
C++

//===--- SILGenForeignError.cpp - Error-handling code emission ------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "ASTVisitor.h"
#include "LValue.h"
#include "RValue.h"
#include "SILGen.h"
#include "SILGenFunction.h"
#include "Scope.h"
#include "swift/AST/DiagnosticsSIL.h"
#include "swift/AST/ForeignAsyncConvention.h"
#include "swift/AST/ForeignErrorConvention.h"
#include "swift/Basic/Assertions.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/SILUndef.h"
using namespace swift;
using namespace Lowering;
namespace {
/// An abstract interface for producing bridged errors.
struct BridgedErrorSource {
virtual ~BridgedErrorSource() = default;
virtual SILValue emitBridged(SILGenFunction &SGF, SILLocation loc,
CanType bridgedError) const = 0;
virtual void emitRelease(SILGenFunction &SGF, SILLocation loc) const = 0;
};
} // end anonymous namespace
/// Emit a store of a native error to the foreign-error slot.
static void emitStoreToForeignErrorSlot(SILGenFunction &SGF,
SILLocation loc,
SILValue foreignErrorSlot,
const BridgedErrorSource &errorSrc) {
ASTContext &ctx = SGF.getASTContext();
// The foreign error slot has type SomePointer<SomeError?>,
// or possibly an optional thereof.
// If the pointer itself is optional, we need to branch based on
// whether it's really there.
if (SILType errorPtrObjectTy =
foreignErrorSlot->getType().getOptionalObjectType()) {
SILBasicBlock *contBB = SGF.createBasicBlock();
SILBasicBlock *noSlotBB = SGF.createBasicBlock();
SILBasicBlock *hasSlotBB = SGF.createBasicBlock();
auto *switchEnum =
SGF.B.createSwitchEnum(loc, foreignErrorSlot, nullptr,
{{ctx.getOptionalSomeDecl(), hasSlotBB},
{ctx.getOptionalNoneDecl(), noSlotBB}});
SILValue slot = switchEnum->createOptionalSomeResult();
// If we have the slot, emit a store to it.
SGF.B.emitBlock(hasSlotBB);
emitStoreToForeignErrorSlot(SGF, loc, slot, errorSrc);
SGF.B.createBranch(loc, contBB);
// Otherwise, just release the error.
SGF.B.emitBlock(noSlotBB);
errorSrc.emitRelease(SGF, loc);
SGF.B.createBranch(loc, contBB);
// Continue.
SGF.B.emitBlock(contBB);
return;
}
// Okay, break down the components of SomePointer<SomeError?>.
// TODO: this should really be an unlowered AST type?
auto bridgedErrorPtrType = foreignErrorSlot->getType().getASTType();
PointerTypeKind ptrKind;
CanType bridgedErrorProto =
CanType(bridgedErrorPtrType->getAnyPointerElementType(ptrKind));
FullExpr scope(SGF.Cleanups, CleanupLocation(loc));
FormalEvaluationScope writebacks(SGF);
// Convert the error to a bridged form.
SILValue bridgedError = errorSrc.emitBridged(SGF, loc, bridgedErrorProto);
// Store to the "pointee" property.
// If we can't find it, diagnose and then just don't store anything.
VarDecl *pointeeProperty = ctx.getPointerPointeePropertyDecl(ptrKind);
if (!pointeeProperty) {
SGF.SGM.diagnose(loc, diag::could_not_find_pointer_pointee_property,
bridgedErrorPtrType);
return;
}
// Otherwise, do a normal assignment.
LValue lvalue = SGF.emitPropertyLValue(
loc, ManagedValue::forRValueWithoutOwnership(foreignErrorSlot),
bridgedErrorPtrType, pointeeProperty, LValueOptions(),
SGFAccessKind::Write, AccessSemantics::Ordinary);
RValue rvalue(SGF, loc, bridgedErrorProto,
SGF.emitManagedRValueWithCleanup(bridgedError));
SGF.emitAssignToLValue(loc, std::move(rvalue), std::move(lvalue));
}
/// Emit a value of a certain integer-like type.
static SILValue emitIntValue(SILGenFunction &SGF, SILLocation loc,
SILType type, unsigned value) {
if (auto structDecl = type.getStructOrBoundGenericStruct()) {
auto properties = structDecl->getStoredProperties();
assert(properties.size() == 1);
SILType fieldType = type.getFieldType(properties[0], SGF.SGM.M,
SGF.getTypeExpansionContext());
SILValue fieldValue = emitIntValue(SGF, loc, fieldType, value);
return SGF.B.createStruct(loc, type, fieldValue);
}
assert(type.is<BuiltinIntegerType>());
return SGF.B.createIntegerLiteral(loc, type, value);
}
namespace {
/// An error source that bridges a native error.
class EpilogErrorSource : public BridgedErrorSource {
SILValue NativeError;
public:
EpilogErrorSource(SILValue nativeError) : NativeError(nativeError) {}
SILValue emitBridged(SILGenFunction &SGF, SILLocation loc,
CanType bridgedErrorProto) const override {
auto nativeErrorType = NativeError->getType().getASTType();
assert(nativeErrorType == SGF.SGM.getASTContext().getErrorExistentialType());
SILValue bridgedError = SGF.emitNativeToBridgedError(loc,
SGF.emitManagedRValueWithCleanup(NativeError),
nativeErrorType,
bridgedErrorProto).forward(SGF);
return bridgedError;
}
void emitRelease(SILGenFunction &SGF, SILLocation loc) const override {
SGF.B.emitDestroyValueOperation(loc, NativeError);
}
};
/// An error source that produces nil errors.
class NilErrorSource : public BridgedErrorSource {
public:
SILValue emitBridged(SILGenFunction &SGF, SILLocation loc,
CanType bridgedError) const override {
SILType optTy = SGF.getLoweredType(bridgedError);
return SGF.B.createOptionalNone(loc, optTy);
}
void emitRelease(SILGenFunction &SGF, SILLocation loc) const override {
}
};
} // end anonymous namespace
/// Given that we are throwing a native error, turn it into a bridged
/// error, dispose of it in the correct way, and create the appropriate
/// normal return value for the given foreign-error convention.
SILValue SILGenFunction::
emitBridgeErrorForForeignError(SILLocation loc,
SILValue nativeError,
SILType bridgedResultType,
SILValue foreignErrorSlot,
const ForeignErrorConvention &foreignError) {
FullExpr scope(Cleanups, CleanupLocation(loc));
// Store the error to the foreign error slot.
emitStoreToForeignErrorSlot(*this, loc, foreignErrorSlot,
EpilogErrorSource(nativeError));
switch (foreignError.getKind()) {
case ForeignErrorConvention::ZeroResult:
return emitIntValue(*this, loc, bridgedResultType, 0);
case ForeignErrorConvention::ZeroPreservedResult:
return emitIntValue(*this, loc, bridgedResultType, 0);
case ForeignErrorConvention::NonZeroResult:
return emitIntValue(*this, loc, bridgedResultType, 1);
case ForeignErrorConvention::NilResult:
return B.createOptionalNone(loc, bridgedResultType);
case ForeignErrorConvention::NonNilError:
return SILUndef::get(F, bridgedResultType);
}
llvm_unreachable("bad foreign error convention kind");
}
/// Given that we are returning a normal value, convert it to a
/// bridged representation and set up a return value according to the
/// given foreign-error convention.
SILValue SILGenFunction::
emitBridgeReturnValueForForeignError(SILLocation loc,
SILValue result,
CanType formalNativeType,
CanType formalBridgedType,
SILType bridgedType,
SILValue foreignErrorSlot,
const ForeignErrorConvention &foreignError) {
FullExpr scope(Cleanups, CleanupLocation(loc));
switch (foreignError.getKind()) {
// If an error is signalled by a zero result, return non-zero.
case ForeignErrorConvention::ZeroResult:
return emitIntValue(*this, loc, bridgedType, 1);
// If an error is signalled by a non-zero result, return zero.
case ForeignErrorConvention::NonZeroResult:
return emitIntValue(*this, loc, bridgedType, 0);
// If an error is signalled by a zero result, but we've preserved
// the rest of the return value, then just return the normal
// result, assuming (hoping!) that it isn't zero.
case ForeignErrorConvention::ZeroPreservedResult:
return result;
// If an error is signalled by a nil result, inject a non-nil result.
case ForeignErrorConvention::NilResult: {
ManagedValue bridgedResult = emitNativeToBridgedValue(
loc, emitManagedRValueWithCleanup(result), formalNativeType,
formalBridgedType, bridgedType.getOptionalObjectType());
auto someResult =
B.createOptionalSome(loc, bridgedResult.forward(*this), bridgedType);
return someResult;
}
// If an error is signalled by a non-nil error, be sure to store a
// nil error there.
case ForeignErrorConvention::NonNilError: {
// Store nil to the foreign error slot.
emitStoreToForeignErrorSlot(*this, loc, foreignErrorSlot, NilErrorSource());
// The actual result value just needs to be bridged normally.
ManagedValue bridgedValue =
emitNativeToBridgedValue(loc, emitManagedRValueWithCleanup(result),
formalNativeType, formalBridgedType,
bridgedType);
return bridgedValue.forward(*this);
}
}
llvm_unreachable("bad foreign error convention kind");
}
static FunctionSection functionSectionForConvention(
std::optional<ForeignAsyncConvention> foreignAsync) {
// If there is a foreign async convention too, the error block branches to
// the block awaiting the continuation.
return foreignAsync ? FunctionSection::Ordinary : FunctionSection::Postmatter;
}
/// Step out of the current control flow to emit a foreign error block,
/// which loads from the error slot and jumps to the error slot.
SILValue SILGenFunction::emitForeignErrorBlock(
SILLocation loc, SILBasicBlock *errorBB,
std::optional<ManagedValue> errorSlot,
std::optional<ForeignAsyncConvention> foreignAsync) {
SILGenSavedInsertionPoint savedIP(*this, errorBB,
functionSectionForConvention(foreignAsync));
Scope scope(Cleanups, CleanupLocation(loc));
// Load the error (taking responsibility for it). In theory, this
// is happening within conditional code, so we need to be only
// conditionally claiming the value. In practice, claiming it
// unconditionally is fine because we want to assume it's nil in the
// other path.
SILValue errorV;
if (errorSlot.has_value()) {
errorV = B.emitLoadValueOperation(loc, errorSlot.value().forward(*this),
LoadOwnershipQualifier::Take);
} else {
// If we are not provided with an errorSlot value, then we are passed the
// unwrapped optional error as the argument of the errorBB. This value is
// passed at +1 meaning that we still need to create a cleanup for errorV.
errorV = errorBB->getArgument(0);
}
ManagedValue error = emitManagedRValueWithCleanup(errorV);
// Turn the error into an Error value.
error = emitBridgedToNativeError(loc, error);
// If there's no foreign async convention, then the error needs to be thrown.
//
// If there _is_ a foreign async convention, the error needs to be used to
// fulfill the unsafe continuation, and then destroyed. Afterwards, a branch
// to the block containing the await_async_continuation must be emitted. That
// work is done in ForeignAsyncInitializationPlan::finish using the error
// that is returned from here.
// [foreign_error_block_with_foreign_async_convention]
if (!foreignAsync) {
error = scope.popPreservingValue(error);
// Propagate.
FullExpr throwScope(Cleanups, CleanupLocation(loc));
emitThrow(loc, error, true);
}
return error.getValue();
}
/// Perform a foreign error check by testing whether the call result is zero.
/// The call result is otherwise ignored.
static SILValue
emitResultIsZeroErrorCheck(SILGenFunction &SGF, SILLocation loc,
ManagedValue result, ManagedValue errorSlot,
bool suppressErrorCheck, bool zeroIsError,
std::optional<ForeignAsyncConvention> foreignAsync) {
// Just ignore the call result if we're suppressing the error check.
if (suppressErrorCheck) {
return SILValue();
}
SILValue resultValue =
SGF.emitUnwrapIntegerResult(loc, result.getUnmanagedValue());
auto resultType = resultValue->getType().getASTType();
if (!resultType->isBuiltinIntegerType(1)) {
SILValue zero =
SGF.B.createIntegerLiteral(loc, resultValue->getType(), 0);
ASTContext &ctx = SGF.getASTContext();
resultValue =
SGF.B.createBuiltinBinaryFunction(loc,
"cmp_ne",
resultValue->getType(),
SILType::getBuiltinIntegerType(1, ctx),
{resultValue, zero});
}
SILBasicBlock *errorBB =
SGF.createBasicBlock(functionSectionForConvention(foreignAsync));
SILBasicBlock *contBB = SGF.createBasicBlock();
if (zeroIsError)
SGF.B.createCondBranch(loc, resultValue, contBB, errorBB);
else
SGF.B.createCondBranch(loc, resultValue, errorBB, contBB);
SILValue error =
SGF.emitForeignErrorBlock(loc, errorBB, errorSlot, foreignAsync);
SGF.B.emitBlock(contBB);
return error;
}
/// Perform a foreign error check by testing whether the call result is nil.
static std::pair<ManagedValue, SILValue>
emitResultIsNilErrorCheck(SILGenFunction &SGF, SILLocation loc,
ManagedValue origResult, ManagedValue errorSlot,
bool suppressErrorCheck,
std::optional<ForeignAsyncConvention> foreignAsync) {
assert(!foreignAsync);
// Take local ownership of the optional result value.
SILValue optionalResult = origResult.forward(SGF);
ASTContext &ctx = SGF.getASTContext();
// If we're suppressing the check, just do an unchecked take.
if (suppressErrorCheck) {
SILValue objectResult =
SGF.B.createUncheckedEnumData(loc, optionalResult,
ctx.getOptionalSomeDecl());
return {SGF.emitManagedRValueWithCleanup(objectResult), SILValue()};
}
// Switch on the optional result.
SILBasicBlock *errorBB =
SGF.createBasicBlock(functionSectionForConvention(foreignAsync));
SILBasicBlock *contBB = SGF.createBasicBlock();
auto *switchEnum =
SGF.B.createSwitchEnum(loc, optionalResult, /*default*/ nullptr,
{{ctx.getOptionalSomeDecl(), contBB},
{ctx.getOptionalNoneDecl(), errorBB}});
// Emit the error block.
SILValue error =
SGF.emitForeignErrorBlock(loc, errorBB, errorSlot, foreignAsync);
// In the continuation block, take ownership of the now non-optional
// result value.
SGF.B.emitBlock(contBB);
ManagedValue objectResult = SGF.B.createOptionalSomeResult(switchEnum);
return {objectResult, error};
}
/// Perform a foreign error check by testing whether the error was nil.
static SILValue emitErrorIsNonNilErrorCheck(
SILGenFunction &SGF, SILLocation loc, ManagedValue errorSlot,
bool suppressErrorCheck,
std::optional<ForeignAsyncConvention> foreignAsync) {
// If we're suppressing the check, just don't check.
if (suppressErrorCheck)
return SILValue();
SILValue optionalError = SGF.B.emitLoadValueOperation(
loc, errorSlot.forward(SGF), LoadOwnershipQualifier::Take);
ASTContext &ctx = SGF.getASTContext();
// Switch on the optional error.
SILBasicBlock *errorBB =
SGF.createBasicBlock(functionSectionForConvention(foreignAsync));
SILBasicBlock *contBB = SGF.createBasicBlock();
auto *switchEnum =
SGF.B.createSwitchEnum(loc, optionalError, /*default*/ nullptr,
{{ctx.getOptionalSomeDecl(), errorBB},
{ctx.getOptionalNoneDecl(), contBB}});
switchEnum->createOptionalSomeResult();
// Emit the error block. Pass in none for the errorSlot since we have passed
// in the errorSlot as our BB argument so we can pass ownership correctly. In
// emitForeignErrorBlock, we will create the appropriate cleanup for the
// argument.
SILValue error =
SGF.emitForeignErrorBlock(loc, errorBB, std::nullopt, foreignAsync);
// Return the result.
SGF.B.emitBlock(contBB);
return error;
}
/// Emit a check for whether a non-native function call produced an
/// error.
///
/// \c results should be left with only values that match the formal
/// direct results of the function.
SILValue SILGenFunction::emitForeignErrorCheck(
SILLocation loc, SmallVectorImpl<ManagedValue> &results,
ManagedValue errorSlot, bool suppressErrorCheck,
const ForeignErrorConvention &foreignError,
std::optional<ForeignAsyncConvention> foreignAsync) {
// All of this is autogenerated.
loc.markAutoGenerated();
switch (foreignError.getKind()) {
case ForeignErrorConvention::ZeroPreservedResult:
assert(results.size() == 1);
return emitResultIsZeroErrorCheck(*this, loc, results[0], errorSlot,
suppressErrorCheck,
/*zeroIsError*/ true, foreignAsync);
case ForeignErrorConvention::ZeroResult:
assert(results.size() == 1);
return emitResultIsZeroErrorCheck(*this, loc, results.pop_back_val(),
errorSlot, suppressErrorCheck,
/*zeroIsError*/ true, foreignAsync);
case ForeignErrorConvention::NonZeroResult:
assert(results.size() == 1);
return emitResultIsZeroErrorCheck(*this, loc, results.pop_back_val(),
errorSlot, suppressErrorCheck,
/*zeroIsError*/ false, foreignAsync);
case ForeignErrorConvention::NilResult: {
assert(results.size() == 1);
SILValue error;
std::tie(results[0], error) = emitResultIsNilErrorCheck(
*this, loc, results[0], errorSlot, suppressErrorCheck, foreignAsync);
return error;
}
case ForeignErrorConvention::NonNilError:
// Leave the direct results alone.
return emitErrorIsNonNilErrorCheck(*this, loc, errorSlot,
suppressErrorCheck, foreignAsync);
}
llvm_unreachable("bad foreign error convention kind");
}