Files
swift-mirror/lib/SILGen/SILGenDynamicCast.cpp
Doug Gregor c08bd5668e Prohibit isolated conformances for checked casts to potentially-SendableMetatype types
This removes the IsolatedConformances feature gate from SILGen for
dynamic casts, such that all dynamic casts that will not work with
isolated conformances are marked as such. Isolated conformances can
still only come into a program when part of it enables the feature
flag.
2025-03-28 16:55:36 -07:00

609 lines
24 KiB
C++

//===--- SILGenDynamicCast.cpp - SILGen for dynamic casts -----------------===//
//
// 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 "SILGenDynamicCast.h"
#include "Initialization.h"
#include "RValue.h"
#include "Scope.h"
#include "ExitableFullExpr.h"
#include "swift/Basic/Assertions.h"
#include "swift/AST/ConformanceLookup.h"
#include "swift/SIL/DynamicCasts.h"
#include "swift/SIL/SILArgument.h"
#include "swift/SIL/TypeLowering.h"
using namespace swift;
using namespace Lowering;
namespace {
class CheckedCastEmitter {
SILGenFunction &SGF;
SILLocation Loc;
CanType SourceType;
CanType TargetType;
enum class CastStrategy : uint8_t {
Address,
Scalar,
};
CastStrategy Strategy;
CastingIsolatedConformances IsolatedConformances;
public:
CheckedCastEmitter(SILGenFunction &SGF, SILLocation loc,
Type sourceType, Type targetType)
: SGF(SGF), Loc(loc), SourceType(sourceType->getCanonicalType()),
TargetType(targetType->getCanonicalType()),
Strategy(computeStrategy()),
IsolatedConformances(computedIsolatedConformances()) {
}
bool isOperandIndirect() const {
return Strategy == CastStrategy::Address;
}
ManagedValue emitOperand(Expr *operand) {
AbstractionPattern mostGeneral = SGF.SGM.Types.getMostGeneralAbstraction();
auto &origSourceTL = SGF.getTypeLowering(mostGeneral, SourceType);
SGFContext ctx;
std::unique_ptr<TemporaryInitialization> temporary;
if (isOperandIndirect()) {
temporary = SGF.emitTemporary(Loc, origSourceTL);
ctx = SGFContext(temporary.get());
}
auto result = SGF.emitRValueAsOrig(operand, mostGeneral,
origSourceTL, ctx);
if (isOperandIndirect()) {
// Force the result into the temporary if it's not already there.
if (!result.isInContext()) {
result.forwardInto(SGF, Loc, temporary->getAddress());
temporary->finishInitialization(SGF);
}
return temporary->getManagedAddress();
}
return result;
}
RValue emitUnconditionalCast(ManagedValue operand, SGFContext ctx) {
// The cast functions don't know how to work with anything but
// the most general possible abstraction level.
AbstractionPattern abstraction = SGF.SGM.Types.getMostGeneralAbstraction();
auto &origTargetTL = SGF.getTypeLowering(abstraction, TargetType);
auto &substTargetTL = SGF.getTypeLowering(TargetType);
bool hasAbstraction =
(origTargetTL.getLoweredType() != substTargetTL.getLoweredType());
// If we're using checked_cast_addr, take the operand (which
// should be an address) and build into the destination buffer.
if (Strategy == CastStrategy::Address) {
SILValue resultBuffer =
createAbstractResultBuffer(hasAbstraction, origTargetTL, ctx);
SGF.B.createUnconditionalCheckedCastAddr(Loc, IsolatedConformances,
operand.forward(SGF), SourceType,
resultBuffer, TargetType);
return RValue(SGF, Loc, TargetType,
finishFromResultBuffer(hasAbstraction, resultBuffer,
abstraction, origTargetTL, ctx));
}
ManagedValue result =
SGF.B.createUnconditionalCheckedCast(Loc, IsolatedConformances,
operand,
origTargetTL.getLoweredType(),
TargetType);
return RValue(SGF, Loc, TargetType,
finishFromResultScalar(hasAbstraction, result,
CastConsumptionKind::TakeAlways,
abstraction, origTargetTL, ctx));
}
/// Emit a conditional cast.
void emitConditional(
ManagedValue operand,
CastConsumptionKind consumption, SGFContext ctx,
llvm::function_ref<void(ManagedValue)> handleTrue,
llvm::function_ref<void(std::optional<ManagedValue>)> handleFalse,
ProfileCounter TrueCount = ProfileCounter(),
ProfileCounter FalseCount = ProfileCounter()) {
// The cast instructions don't know how to work with anything
// but the most general possible abstraction level.
AbstractionPattern abstraction =
SGF.SGM.Types.getMostGeneralAbstraction();
auto &origTargetTL = SGF.getTypeLowering(abstraction, TargetType);
auto &substTargetTL = SGF.getTypeLowering(TargetType);
bool hasAbstraction =
(origTargetTL.getLoweredType() != substTargetTL.getLoweredType());
SILBasicBlock *falseBB = SGF.B.splitBlockForFallthrough();
SILBasicBlock *trueBB = SGF.B.splitBlockForFallthrough();
// Emit the branch.
ManagedValue operandValue;
SILValue resultBuffer;
if (Strategy == CastStrategy::Address) {
assert(operand.getType().isAddress());
resultBuffer =
createAbstractResultBuffer(hasAbstraction, origTargetTL, ctx);
SGF.B.createCheckedCastAddrBranch(
Loc, IsolatedConformances, consumption, operand.forward(SGF),
SourceType, resultBuffer, TargetType, trueBB, falseBB,
TrueCount, FalseCount);
} else {
// Tolerate being passed an address here. It comes up during switch
// emission.
operandValue = std::move(operand);
if (operandValue.getType().isAddress()) {
operandValue = SGF.B.createLoadTake(Loc, operandValue);
}
// If we are not supposed to destroy this value on failure, then we need
// to borrow it.
if (!shouldDestroyOnFailure(consumption)) {
operandValue = operandValue.borrow(SGF, Loc);
}
SGF.B.createCheckedCastBranch(Loc, /*exact*/ false,
IsolatedConformances, operandValue,
SourceType, origTargetTL.getLoweredType(),
TargetType, trueBB, falseBB, TrueCount,
FalseCount);
}
// Emit the success block.
SGF.B.setInsertionPoint(trueBB);
{
FullExpr scope(SGF.Cleanups, CleanupLocation(Loc));
ManagedValue result;
if (Strategy == CastStrategy::Address) {
result = finishFromResultBuffer(hasAbstraction, resultBuffer,
abstraction, origTargetTL, ctx);
} else {
// If we had copy_on_success, then we need to use a guaranteed
// argument.
assert(!shouldTakeOnSuccess(consumption)
|| operandValue.getOwnershipKind().isCompatibleWith(
OwnershipKind::Owned)
&& "cast consumption does not match ownership");
ManagedValue termResult =
SGF.B.createForwardedTermResult(origTargetTL.getLoweredType());
result =
finishFromResultScalar(hasAbstraction, termResult, consumption,
abstraction, origTargetTL, ctx);
}
handleTrue(result);
assert(!SGF.B.hasValidInsertionPoint() && "handler did not end block");
}
// Emit the failure block.
SGF.B.setInsertionPoint(falseBB);
{
FullExpr scope(SGF.Cleanups, CleanupLocation(Loc));
// If we have an address only type, do not handle the consumption
// rules. These are handled for us by the user.
if (Strategy == CastStrategy::Address) {
handleFalse(std::nullopt);
assert(!SGF.B.hasValidInsertionPoint() &&
"handler did not end block");
return;
}
// Otherwise, we use the following strategy:
//
// 1. If we have a take_always, we create a phi node argument for the
// failure case and a scope for that so that it is immediately
// destroyed.
//
// 2. If we have a take_on_success or copy_on_success, then on failure,
// we propagate through the default argument, but do not clean it up. On
// the false case, our user must treat the taken value as a new value.
if (shouldDestroyOnFailure(consumption)) {
{
FullExpr argScope(SGF.Cleanups, CleanupLocation(Loc));
SGF.B.createForwardedTermResult(operandValue.getType());
}
handleFalse(std::nullopt);
assert(!SGF.B.hasValidInsertionPoint() &&
"handler did not end block");
return;
}
ManagedValue result =
SGF.B.createForwardedTermResult(operandValue.getType());
switch (consumption) {
case CastConsumptionKind::BorrowAlways:
case CastConsumptionKind::CopyOnSuccess:
handleFalse(std::nullopt);
break;
case CastConsumptionKind::TakeAlways:
case CastConsumptionKind::TakeOnSuccess:
handleFalse(result);
break;
}
assert(!SGF.B.hasValidInsertionPoint() && "handler did not end block");
}
}
SILValue createAbstractResultBuffer(bool hasAbstraction,
const TypeLowering &origTargetTL,
SGFContext ctx) {
if (!hasAbstraction) {
if (auto address = ctx.getAddressForInPlaceInitialization(SGF, Loc))
return address;
}
return SGF.emitTemporaryAllocation(Loc, origTargetTL.getLoweredType());
}
ManagedValue finishFromResultBuffer(bool hasAbstraction, SILValue buffer,
AbstractionPattern abstraction,
const TypeLowering &origTargetTL,
SGFContext ctx) {
if (!hasAbstraction) {
if (ctx.finishInPlaceInitialization(SGF))
return ManagedValue::forInContext();
}
ManagedValue result;
if (!origTargetTL.isAddressOnly() || !SGF.useLoweredAddresses()) {
result = SGF.emitLoad(Loc, buffer, origTargetTL, ctx, IsTake);
} else {
result = SGF.emitManagedBufferWithCleanup(buffer, origTargetTL);
}
if (hasAbstraction) {
result =
SGF.emitOrigToSubstValue(Loc, result, abstraction, TargetType, ctx);
}
return result;
}
/// Our cast succeeded and gave us this abstracted value.
ManagedValue finishFromResultScalar(bool hasAbstraction, ManagedValue value,
CastConsumptionKind consumption,
AbstractionPattern abstraction,
const TypeLowering &origTargetTL,
SGFContext ctx) {
ManagedValue result = value;
// Copy the result if this is copy-on-success.
if (!shouldTakeOnSuccess(consumption))
result = result.copy(SGF, Loc);
// Re-abstract if necessary.
if (hasAbstraction) {
result =
SGF.emitOrigToSubstValue(Loc, result, abstraction, TargetType, ctx);
}
return result;
}
private:
CastStrategy computeStrategy() const {
if (canSILUseScalarCheckedCastInstructions(SGF.SGM.M, SourceType,
TargetType))
return CastStrategy::Scalar;
return CastStrategy::Address;
}
CastingIsolatedConformances computedIsolatedConformances() const {
// Non-existential types don't carry conformances, so we always allow
// isolated conformances.
if (!TargetType->isAnyExistentialType())
return CastingIsolatedConformances::Allow;
// If there is a conformance to SendableMetatype, then this existential
// can leave the current isolation domain. Prohibit isolated conformances.
ASTContext &ctx = TargetType->getASTContext();
Type checkType;
if (auto existentialMetatype = TargetType->getAs<ExistentialMetatypeType>())
checkType = existentialMetatype->getInstanceType();
else
checkType = TargetType;
auto proto = ctx.getProtocol(KnownProtocolKind::SendableMetatype);
if (proto && lookupConformance(checkType, proto, /*allowMissing=*/false))
return CastingIsolatedConformances::Prohibit;
return CastingIsolatedConformances::Allow;
}
};
} // end anonymous namespace
void SILGenFunction::emitCheckedCastBranch(
SILLocation loc, Expr *source, Type targetType, SGFContext ctx,
llvm::function_ref<void(ManagedValue)> handleTrue,
llvm::function_ref<void(std::optional<ManagedValue>)> handleFalse,
ProfileCounter TrueCount, ProfileCounter FalseCount) {
CheckedCastEmitter emitter(*this, loc, source->getType(), targetType);
ManagedValue operand = emitter.emitOperand(source);
emitter.emitConditional(operand, CastConsumptionKind::TakeAlways, ctx,
handleTrue, handleFalse, TrueCount, FalseCount);
}
void SILGenFunction::emitCheckedCastBranch(
SILLocation loc, ConsumableManagedValue src, Type sourceType,
CanType targetType, SGFContext ctx,
llvm::function_ref<void(ManagedValue)> handleTrue,
llvm::function_ref<void(std::optional<ManagedValue>)> handleFalse,
ProfileCounter TrueCount, ProfileCounter FalseCount) {
CheckedCastEmitter emitter(*this, loc, sourceType, targetType);
emitter.emitConditional(src.getFinalManagedValue(), src.getFinalConsumption(),
ctx, handleTrue, handleFalse, TrueCount, FalseCount);
}
/// Emit a collection downcast expression.
///
/// \param conditional Whether to emit a conditional downcast; if
/// false, this will emit a forced downcast.
static RValue emitCollectionDowncastExpr(SILGenFunction &SGF,
ManagedValue source,
Type sourceType,
SILLocation loc,
Type destType,
SGFContext C,
bool conditional) {
// Compute substitutions for the intrinsic call.
auto fromCollection = sourceType->getCanonicalType();
auto toCollection = destType->getCanonicalType();
// Get the intrinsic function.
FuncDecl *fn = nullptr;
if (fromCollection->isArray()) {
fn = conditional ? SGF.SGM.getArrayConditionalCast(loc)
: SGF.SGM.getArrayForceCast(loc);
} else if (fromCollection->isDictionary()) {
fn = (conditional
? SGF.SGM.getDictionaryDownCastConditional(loc)
: SGF.SGM.getDictionaryDownCast(loc));
} else if (fromCollection->isSet()) {
fn = (conditional
? SGF.SGM.getSetDownCastConditional(loc)
: SGF.SGM.getSetDownCast(loc));
} else {
llvm_unreachable("unsupported collection upcast kind");
}
return SGF.emitCollectionConversion(loc, fn, fromCollection, toCollection,
source, C);
}
static ManagedValue
adjustForConditionalCheckedCastOperand(SILLocation loc, ManagedValue src,
CanType sourceType, CanType targetType,
SILGenFunction &SGF) {
// Reabstract to the most general abstraction, and put it into a
// temporary if necessary.
// Figure out if we need the value to be in a temporary.
bool requiresAddress =
!canSILUseScalarCheckedCastInstructions(SGF.SGM.M, sourceType, targetType);
AbstractionPattern abstraction = SGF.SGM.M.Types.getMostGeneralAbstraction();
auto &srcAbstractTL = SGF.getTypeLowering(abstraction, sourceType);
bool hasAbstraction = (src.getType() != srcAbstractTL.getLoweredType());
// Fast path: no re-abstraction required.
if (!hasAbstraction && (!requiresAddress || src.getType().isAddress()))
return src;
std::unique_ptr<TemporaryInitialization> init;
if (requiresAddress) {
init = SGF.emitTemporary(loc, srcAbstractTL);
if (hasAbstraction)
src = SGF.emitSubstToOrigValue(loc, src, abstraction, sourceType);
// Okay, if all we need to do is drop the value in an address,
// this is easy.
SGF.B.emitStoreValueOperation(loc, src.forward(SGF), init->getAddress(),
StoreOwnershipQualifier::Init);
init->finishInitialization(SGF);
return init->getManagedAddress();
}
assert(hasAbstraction);
assert(src.getType().isObject() &&
"address-only type with abstraction difference?");
// Produce the value at +1.
return SGF.emitSubstToOrigValue(loc, src, abstraction, sourceType);
}
RValue Lowering::emitUnconditionalCheckedCast(SILGenFunction &SGF,
SILLocation loc,
Expr *operand,
Type targetType,
CheckedCastKind castKind,
SGFContext C) {
// Handle collection downcasts directly; they have specific library
// entry points.
if (castKind == CheckedCastKind::ArrayDowncast ||
castKind == CheckedCastKind::DictionaryDowncast ||
castKind == CheckedCastKind::SetDowncast) {
ManagedValue operandMV = SGF.emitRValueAsSingleValue(operand);
return emitCollectionDowncastExpr(SGF, operandMV, operand->getType(), loc,
targetType, C,
/*conditional=*/false);
}
CheckedCastEmitter emitter(SGF, loc, operand->getType(),
targetType);
ManagedValue operandValue = emitter.emitOperand(operand);
return emitter.emitUnconditionalCast(operandValue, C);
}
RValue Lowering::emitConditionalCheckedCast(
SILGenFunction &SGF, SILLocation loc, ManagedValue operand,
Type operandType, Type optTargetType, CheckedCastKind castKind,
SGFContext C, ProfileCounter TrueCount, ProfileCounter FalseCount) {
// Drill into the result type.
CanType resultObjectType =
optTargetType->getCanonicalType().getOptionalObjectType();
assert(resultObjectType);
// Handle collection downcasts directly; they have specific library
// entry points.
if (castKind == CheckedCastKind::ArrayDowncast ||
castKind == CheckedCastKind::DictionaryDowncast ||
castKind == CheckedCastKind::SetDowncast) {
return emitCollectionDowncastExpr(SGF, operand, operandType, loc,
resultObjectType, C,
/*conditional=*/true);
}
operand = adjustForConditionalCheckedCastOperand(loc, operand,
operandType->getCanonicalType(),
resultObjectType, SGF);
auto someDecl = SGF.getASTContext().getOptionalSomeDecl();
auto &resultTL = SGF.getTypeLowering(optTargetType);
// Set up a result buffer if desirable/required.
SILValue resultBuffer;
SILValue resultObjectBuffer;
std::optional<TemporaryInitialization> resultObjectTemp;
SGFContext resultObjectCtx;
if ((resultTL.isAddressOnly() && SGF.useLoweredAddresses())
|| (C.getEmitInto()
&& C.getEmitInto()->canPerformInPlaceInitialization())) {
SILType resultTy = resultTL.getLoweredType();
resultBuffer = SGF.getBufferForExprResult(loc, resultTy, C);
resultObjectBuffer = SGF.B.createInitEnumDataAddr(
loc, resultBuffer, someDecl,
resultTy.getOptionalObjectType().getAddressType());
resultObjectTemp.emplace(resultObjectBuffer, CleanupHandle::invalid());
resultObjectCtx = SGFContext(&resultObjectTemp.value());
}
// Prepare a jump destination here.
ExitableFullExpr scope(SGF, CleanupLocation(loc));
auto operandCMV = ConsumableManagedValue::forOwned(operand);
assert(operandCMV.getFinalConsumption() == CastConsumptionKind::TakeAlways);
SGF.emitCheckedCastBranch(
loc, operandCMV, operandType, resultObjectType, resultObjectCtx,
// The success path.
[&](ManagedValue objectValue) {
// If we're not emitting into a temporary, just wrap up the result
// in Some and go to the continuation block.
if (!resultObjectTemp) {
auto some = SGF.B.createEnum(loc, objectValue.forward(SGF), someDecl,
resultTL.getLoweredType());
SGF.Cleanups.emitBranchAndCleanups(scope.getExitDest(), loc, {some});
return;
}
// Otherwise, make sure the value is in the context.
if (!objectValue.isInContext()) {
objectValue.forwardInto(SGF, loc, resultObjectBuffer);
}
SGF.B.createInjectEnumAddr(loc, resultBuffer, someDecl);
SGF.Cleanups.emitBranchAndCleanups(scope.getExitDest(), loc);
},
// The failure path.
[&](std::optional<ManagedValue> Value) {
// We always are performing a take here, so Value should be std::nullopt
// since the object should have been destroyed immediately in the fail
// block.
assert(!Value.has_value() && "Expected a take_always consumption kind");
auto noneDecl = SGF.getASTContext().getOptionalNoneDecl();
// If we're not emitting into a temporary, just wrap up the result
// in None and go to the continuation block.
if (!resultObjectTemp) {
auto none = SGF.B.createEnum(loc, nullptr, noneDecl,
resultTL.getLoweredType());
SGF.Cleanups.emitBranchAndCleanups(scope.getExitDest(), loc, {none});
// Just construct the enum directly in the context.
} else {
SGF.B.createInjectEnumAddr(loc, resultBuffer, noneDecl);
SGF.Cleanups.emitBranchAndCleanups(scope.getExitDest(), loc);
}
},
TrueCount, FalseCount);
// Enter the continuation block.
SILBasicBlock *contBlock = scope.exit();
ManagedValue result;
if (resultObjectTemp) {
result = SGF.manageBufferForExprResult(resultBuffer, resultTL, C);
} else {
auto argument = contBlock->createPhiArgument(resultTL.getLoweredType(),
OwnershipKind::Owned);
result = SGF.emitManagedRValueWithCleanup(argument, resultTL);
}
return RValue(SGF, loc, optTargetType->getCanonicalType(), result);
}
SILValue Lowering::emitIsa(SILGenFunction &SGF, SILLocation loc,
Expr *operand, Type targetType,
CheckedCastKind castKind) {
// Handle collection downcasts separately.
if (castKind == CheckedCastKind::ArrayDowncast ||
castKind == CheckedCastKind::DictionaryDowncast ||
castKind == CheckedCastKind::SetDowncast) {
ManagedValue operandMV = SGF.emitRValueAsSingleValue(operand);
ManagedValue optValue = emitCollectionDowncastExpr(
SGF, operandMV, operand->getType(), loc,
targetType,
SGFContext(), /*conditional=*/true)
.getAsSingleValue(SGF, loc);
// Materialize the input.
SILValue optValueTemp;
if (optValue.getType().isAddress()) {
optValueTemp = optValue.forward(SGF);
} else {
optValueTemp = SGF.emitTemporaryAllocation(loc, optValue.getType());
optValue.forwardInto(SGF, loc, optValueTemp);
}
return SGF.emitDoesOptionalHaveValue(loc, optValueTemp);
}
// Prepare a jump destination here.
ExitableFullExpr scope(SGF, CleanupLocation(loc));
auto i1Ty = SILType::getBuiltinIntegerType(1, SGF.getASTContext());
// When we pass in an expr, we perform a take_always cast.
SGF.emitCheckedCastBranch(
loc, operand, targetType, SGFContext(),
[&](ManagedValue value) {
SILValue yes = SGF.B.createIntegerLiteral(loc, i1Ty, 1);
SGF.Cleanups.emitBranchAndCleanups(scope.getExitDest(), loc, yes);
},
[&](std::optional<ManagedValue> Value) {
assert(!Value.has_value() && "Expected take_always semantics");
SILValue no = SGF.B.createIntegerLiteral(loc, i1Ty, 0);
SGF.Cleanups.emitBranchAndCleanups(scope.getExitDest(), loc, no);
});
auto contBB = scope.exit();
auto isa = contBB->createPhiArgument(i1Ty, OwnershipKind::None);
return isa;
}