Files
swift-mirror/lib/IRGen/GenCast.cpp
Doug Gregor 43df05a89c [SE-0470] Prohibit isolated conformances in dynamic casts marked as such
Certain dynamic casts cannot work safely with isolated conformances,
regardless of what executor the code runs on. For such cases, reject
all attempts to conform to the type.
2025-03-26 22:31:52 -07:00

1132 lines
42 KiB
C++

//===--- GenCast.cpp - Swift IR Generation 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
//
//===----------------------------------------------------------------------===//
//
// This file implements IR generation for dynamic casts.
//
//===----------------------------------------------------------------------===//
#include "GenCast.h"
#include "Explosion.h"
#include "GenEnum.h"
#include "GenExistential.h"
#include "GenHeap.h"
#include "GenProto.h"
#include "IRGenDebugInfo.h"
#include "IRGenFunction.h"
#include "IRGenModule.h"
#include "MetadataRequest.h"
#include "TypeInfo.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Module.h"
#include "swift/AST/ExistentialLayout.h"
#include "swift/AST/IRGenOptions.h"
#include "swift/Basic/Assertions.h"
#include "swift/SIL/DynamicCasts.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SIL/SILModule.h"
#include "swift/SIL/TypeLowering.h"
#include "swift/ABI/MetadataValues.h"
using namespace swift;
using namespace irgen;
/// Compute the flags to pass to swift_dynamicCast.
static DynamicCastFlags getDynamicCastFlags(
CastConsumptionKind consumptionKind,
CheckedCastMode mode,
CastingIsolatedConformances isolatedConformances
) {
DynamicCastFlags flags = DynamicCastFlags::Default;
if (mode == CheckedCastMode::Unconditional)
flags |= DynamicCastFlags::Unconditional;
if (shouldDestroyOnFailure(consumptionKind))
flags |= DynamicCastFlags::DestroyOnFailure;
if (shouldTakeOnSuccess(consumptionKind))
flags |= DynamicCastFlags::TakeOnSuccess;
switch (isolatedConformances) {
case CastingIsolatedConformances::Allow:
break;
case CastingIsolatedConformances::Prohibit:
flags |= DynamicCastFlags::ProhibitIsolatedConformances;
break;
}
return flags;
}
/// Emit a checked cast, starting with a value in memory.
llvm::Value *irgen::emitCheckedCast(IRGenFunction &IGF,
Address src,
CanType srcType,
Address dest,
CanType targetType,
CastConsumptionKind consumptionKind,
CheckedCastMode mode,
CastingIsolatedConformances isolatedConformances) {
// TODO: attempt to specialize this based on the known types.
DynamicCastFlags flags = getDynamicCastFlags(consumptionKind, mode,
isolatedConformances);
// Cast both addresses to opaque pointer type.
dest = IGF.Builder.CreateElementBitCast(dest, IGF.IGM.OpaqueTy);
src = IGF.Builder.CreateElementBitCast(src, IGF.IGM.OpaqueTy);
// Load type metadata for the source's static type and the target type.
llvm::Value *srcMetadata = IGF.emitTypeMetadataRef(srcType);
llvm::Value *targetMetadata = IGF.emitTypeMetadataRef(targetType);
llvm::Value *args[] = {
dest.getAddress(), src.getAddress(),
srcMetadata, targetMetadata,
IGF.IGM.getSize(Size(unsigned(flags)))
};
auto call =
IGF.Builder.CreateCall(IGF.IGM.getDynamicCastFunctionPointer(), args);
call->setDoesNotThrow();
return call;
}
FailableCastResult irgen::emitClassIdenticalCast(IRGenFunction &IGF,
llvm::Value *from,
SILType fromType,
SILType toType) {
// Check metatype objects directly. Don't try to find their meta-metatype.
auto isMetatype = false;
if (auto metaType = toType.getAs<MetatypeType>()) {
isMetatype = true;
assert(metaType->getRepresentation() != MetatypeRepresentation::ObjC &&
"not implemented");
toType = IGF.IGM.getLoweredType(metaType.getInstanceType());
}
// Emit a reference to the heap metadata for the target type.
// If we're allowed to do a conservative check, try to just use the
// global class symbol. If the class has been re-allocated, this
// might not be the heap metadata actually in use, and hence the
// test might fail; but it's a much faster check.
// TODO: use ObjC class references
llvm::Value *targetMetadata;
if ((targetMetadata =
tryEmitConstantHeapMetadataRef(IGF.IGM, toType.getASTType(),
/*allowUninitialized*/ false))) {
// ok
} else {
targetMetadata
= emitClassHeapMetadataRef(IGF, toType.getASTType(),
MetadataValueType::ObjCClass,
MetadataState::Complete,
/*allowUninitialized*/ false);
}
// Handle checking a metatype object's type by directly comparing the address
// of the metatype value to the subclass's static metatype instance.
//
// %1 = value_metatype $Super.Type, %0 : $A
// checked_cast_br [exact] Super.Type in %1 : $Super.Type to $Sub.Type
// =>
// icmp eq %1, @metadata.Sub
llvm::Value *objectMetadata = isMetatype ? from :
emitHeapMetadataRefForHeapObject(IGF, from, fromType);
objectMetadata = IGF.Builder.CreateBitCast(objectMetadata,
targetMetadata->getType());
llvm::Value *cond = IGF.Builder.CreateICmpEQ(objectMetadata, targetMetadata);
return {cond, from};
}
/// Returns an ArrayRef with the set of arguments to pass to a dynamic cast call.
///
/// `argsBuf` should be passed in as a reference to an array with three nullptr
/// values at the end. These will be dropped from the return ArrayRef for a
/// conditional cast, or filled in with source location arguments for an
/// unconditional cast.
template<unsigned n>
static ArrayRef<llvm::Value*>
getDynamicCastArguments(IRGenFunction &IGF,
llvm::Value *(&argsBuf)[n], CheckedCastMode mode
/*TODO , SILLocation location*/)
{
switch (mode) {
case CheckedCastMode::Unconditional:
// TODO: Pass along location info if available for unconditional casts, so
// that the runtime error for a failed cast can report the source of the
// error from user code.
argsBuf[n-3] = llvm::ConstantPointerNull::get(IGF.IGM.Int8PtrTy);
argsBuf[n-2] = llvm::ConstantInt::get(IGF.IGM.Int32Ty, 0);
argsBuf[n-1] = llvm::ConstantInt::get(IGF.IGM.Int32Ty, 0);
return argsBuf;
case CheckedCastMode::Conditional:
return llvm::ArrayRef(argsBuf, n - 3);
break;
}
llvm_unreachable("covered switch");
}
/// Emit a checked unconditional downcast of a class value.
llvm::Value *irgen::emitClassDowncast(IRGenFunction &IGF, llvm::Value *from,
CanType toType, CheckedCastMode mode) {
// Emit the value we're casting from.
if (from->getType() != IGF.IGM.Int8PtrTy)
from = IGF.Builder.CreateBitOrPointerCast(from, IGF.IGM.Int8PtrTy);
// Emit a reference to the metadata and figure out what cast
// function to use.
llvm::Value *metadataRef;
FunctionPointer castFn;
// If true, the target class is not known at compile time because it is a
// class-bounded archetype or the dynamic Self type.
bool nonSpecificClass = false;
// Get the best known type information about the destination type.
ClassDecl *destClass = nullptr;
if (auto archetypeTy = dyn_cast<ArchetypeType>(toType)) {
nonSpecificClass = true;
if (auto superclassTy = archetypeTy->getSuperclass())
destClass = superclassTy->getClassOrBoundGenericClass();
} else if (auto selfTy = dyn_cast<DynamicSelfType>(toType)) {
nonSpecificClass = true;
destClass = selfTy->getSelfType()->getClassOrBoundGenericClass();
} else {
destClass = toType.getClassOrBoundGenericClass();
assert(destClass != nullptr);
}
// If the destination type is known to have a Swift-compatible
// implementation, use the most specific entrypoint.
if (destClass && destClass->hasKnownSwiftImplementation()) {
metadataRef = IGF.emitTypeMetadataRef(toType);
switch (mode) {
case CheckedCastMode::Unconditional:
castFn = IGF.IGM.getDynamicCastClassUnconditionalFunctionPointer();
break;
case CheckedCastMode::Conditional:
castFn = IGF.IGM.getDynamicCastClassFunctionPointer();
break;
}
// If the destination type is a CF type or a non-specific
// class-bounded archetype, use the most general cast entrypoint.
} else if (nonSpecificClass ||
destClass->getForeignClassKind()==ClassDecl::ForeignKind::CFType) {
metadataRef = IGF.emitTypeMetadataRef(toType);
switch (mode) {
case CheckedCastMode::Unconditional:
castFn = IGF.IGM.getDynamicCastUnknownClassUnconditionalFunctionPointer();
break;
case CheckedCastMode::Conditional:
castFn = IGF.IGM.getDynamicCastUnknownClassFunctionPointer();
break;
}
// Otherwise, use the ObjC-specific entrypoint.
} else {
metadataRef = emitObjCHeapMetadataRef(IGF, destClass);
switch (mode) {
case CheckedCastMode::Unconditional:
castFn = IGF.IGM.getDynamicCastObjCClassUnconditionalFunctionPointer();
break;
case CheckedCastMode::Conditional:
castFn = IGF.IGM.getDynamicCastObjCClassFunctionPointer();
break;
}
}
if (metadataRef->getType() != IGF.IGM.Int8PtrTy)
metadataRef = IGF.Builder.CreateBitCast(metadataRef, IGF.IGM.Int8PtrTy);
// Call the (unconditional) dynamic cast.
llvm::Value *argsBuf[] = {
from,
metadataRef,
nullptr,
nullptr,
nullptr,
};
auto call = IGF.Builder.CreateCall(
castFn, getDynamicCastArguments(IGF, argsBuf, mode));
call->setDoesNotThrow();
llvm::Type *subTy = IGF.getTypeInfoForUnlowered(toType).getStorageType();
return IGF.Builder.CreateBitCast(call, subTy);
}
/// Emit a checked cast of a metatype.
void irgen::emitMetatypeDowncast(IRGenFunction &IGF,
llvm::Value *metatype,
CanMetatypeType toMetatype,
CheckedCastMode mode,
Explosion &ex) {
// Pick a runtime entry point and target metadata based on what kind of
// representation we're casting.
FunctionPointer castFn;
llvm::Value *toMetadata;
switch (toMetatype->getRepresentation()) {
case MetatypeRepresentation::Thick: {
// Get the Swift metadata for the type we're checking.
toMetadata = IGF.emitTypeMetadataRef(toMetatype.getInstanceType());
switch (mode) {
case CheckedCastMode::Unconditional:
castFn = IGF.IGM.getDynamicCastMetatypeUnconditionalFunctionPointer();
break;
case CheckedCastMode::Conditional:
castFn = IGF.IGM.getDynamicCastMetatypeFunctionPointer();
break;
}
break;
}
case MetatypeRepresentation::ObjC: {
assert(IGF.IGM.ObjCInterop && "should have objc runtime");
// Get the ObjC metadata for the type we're checking.
toMetadata = emitClassHeapMetadataRef(IGF, toMetatype.getInstanceType(),
MetadataValueType::ObjCClass,
MetadataState::Complete);
switch (mode) {
case CheckedCastMode::Unconditional:
castFn =
IGF.IGM.getDynamicCastObjCClassMetatypeUnconditionalFunctionPointer();
break;
case CheckedCastMode::Conditional:
castFn = IGF.IGM.getDynamicCastObjCClassMetatypeFunctionPointer();
break;
}
break;
}
case MetatypeRepresentation::Thin:
llvm_unreachable("not implemented");
}
llvm::Value *argsBuf[] = {
metatype,
toMetadata,
nullptr,
nullptr,
nullptr,
};
auto call = IGF.Builder.CreateCall(
castFn, getDynamicCastArguments(IGF, argsBuf, mode));
call->setDoesNotThrow();
ex.add(call);
}
/// Emit a Protocol* value referencing an ObjC protocol.
llvm::Value *irgen::emitReferenceToObjCProtocol(IRGenFunction &IGF,
ProtocolDecl *proto) {
assert(proto->isObjC() && "not an objc protocol");
// Get the address of the global variable the protocol reference gets
// indirected through.
auto protocolRefAddr =
IGF.IGM.getAddrOfObjCProtocolRef(proto, NotForDefinition);
return IGF.Builder.CreateLoad(protocolRefAddr);
}
/// Emit a helper function to look up \c numProtocols witness tables given
/// a value and a type metadata reference.
///
/// If \p checkClassConstraint is true, we must emit an explicit check that the
/// instance is a class.
///
/// If \p checkSuperclassConstraint is true, we are given an additional parameter
/// with a superclass type in it, and must emit a check that the instance is a
/// subclass of the given class.
///
/// The function's input type is (value, metadataValue, superclass?, protocol...)
/// The function's output type is (value, witnessTable...)
///
/// The value is NULL if the cast failed.
static FunctionPointer
emitExistentialScalarCastFn(IRGenModule &IGM, unsigned numProtocols,
CheckedCastMode mode, bool checkClassConstraint,
bool checkSuperclassConstraint) {
assert(!checkSuperclassConstraint || checkClassConstraint);
// Build the function name.
llvm::SmallString<32> name;
{
llvm::raw_svector_ostream os(name);
os << "dynamic_cast_existential_";
os << numProtocols;
if (checkSuperclassConstraint)
os << "_superclass";
else if (checkClassConstraint)
os << "_class";
switch (mode) {
case CheckedCastMode::Unconditional:
os << "_unconditional";
break;
case CheckedCastMode::Conditional:
os << "_conditional";
break;
}
}
// Build the function type.
llvm::SmallVector<llvm::Type *, 4> argTys;
llvm::SmallVector<llvm::Type *, 4> returnTys;
argTys.push_back(IGM.Int8PtrTy);
argTys.push_back(IGM.TypeMetadataPtrTy);
returnTys.push_back(IGM.Int8PtrTy);
if (checkSuperclassConstraint)
argTys.push_back(IGM.TypeMetadataPtrTy);
for (unsigned i = 0; i < numProtocols; ++i) {
argTys.push_back(IGM.ProtocolDescriptorPtrTy);
returnTys.push_back(IGM.WitnessTablePtrTy);
}
llvm::Type *returnTy = llvm::StructType::get(IGM.getLLVMContext(), returnTys);
auto fn = IGM.getOrCreateHelperFunction(
name, returnTy, argTys, [&](IRGenFunction &IGF) {
Explosion args = IGF.collectParameters();
auto value = args.claimNext();
auto ref = args.claimNext();
auto failBB = IGF.createBasicBlock("fail");
auto conformsToProtocol = IGM.getConformsToProtocolFunctionPointer();
Explosion rets;
rets.add(value);
// Check the class constraint if necessary.
if (checkSuperclassConstraint) {
auto superclassMetadata = args.claimNext();
auto castFn = IGF.IGM.getDynamicCastMetatypeFunctionPointer();
auto castResult =
IGF.Builder.CreateCall(castFn, {ref, superclassMetadata});
// FIXME: Eventually, we may want to throw.
castResult->setDoesNotThrow();
auto isClass = IGF.Builder.CreateICmpNE(
castResult,
llvm::ConstantPointerNull::get(IGF.IGM.TypeMetadataPtrTy));
auto contBB = IGF.createBasicBlock("cont");
IGF.Builder.CreateCondBr(isClass, contBB, failBB);
IGF.Builder.emitBlock(contBB);
} else if (checkClassConstraint) {
auto isClass =
IGF.Builder.CreateCall(IGM.getIsClassTypeFunctionPointer(), ref);
auto contBB = IGF.createBasicBlock("cont");
IGF.Builder.CreateCondBr(isClass, contBB, failBB);
IGF.Builder.emitBlock(contBB);
}
// Look up each protocol conformance we want.
for (unsigned i = 0; i < numProtocols; ++i) {
auto proto = args.claimNext();
auto witness =
IGF.Builder.CreateCall(conformsToProtocol, {ref, proto});
auto isNull = IGF.Builder.CreateICmpEQ(
witness, llvm::ConstantPointerNull::get(IGM.WitnessTablePtrTy));
auto contBB = IGF.createBasicBlock("cont");
IGF.Builder.CreateCondBr(isNull, failBB, contBB);
IGF.Builder.emitBlock(contBB);
rets.add(witness);
}
// If we succeeded, return the witnesses.
IGF.emitScalarReturn(returnTy, rets);
// If we failed, return nil or trap.
IGF.Builder.emitBlock(failBB);
switch (mode) {
case CheckedCastMode::Conditional: {
auto null = llvm::ConstantStruct::getNullValue(returnTy);
IGF.Builder.CreateRet(null);
break;
}
case CheckedCastMode::Unconditional: {
IGF.emitTrap("type cast failed", /*EmitUnreachable=*/true);
break;
}
}
});
auto fnType = llvm::FunctionType::get(returnTy, argTys, false);
auto sig = Signature(fnType, {}, IGM.DefaultCC);
return FunctionPointer::forDirect(FunctionPointer::Kind::Function, fn,
nullptr, sig);
}
llvm::Value *irgen::emitMetatypeToAnyObjectDowncast(IRGenFunction &IGF,
llvm::Value *metatypeValue,
CanAnyMetatypeType type,
CheckedCastMode mode) {
// If ObjC interop is enabled, casting a metatype to AnyObject succeeds
// if the metatype is for a class.
if (!IGF.IGM.ObjCInterop)
return nullptr;
switch (type->getRepresentation()) {
case MetatypeRepresentation::ObjC:
// Metatypes that can be represented as ObjC trivially cast to AnyObject.
return IGF.Builder.CreateBitCast(metatypeValue, IGF.IGM.ObjCPtrTy);
case MetatypeRepresentation::Thin:
// Metatypes that can be thin would never be classes.
// TODO: Final class metatypes could in principle be thin.
assert(!type.getInstanceType()->mayHaveSuperclass()
&& "classes should not have thin metatypes (yet)");
return nullptr;
case MetatypeRepresentation::Thick: {
auto instanceTy = type.getInstanceType();
// Is the type obviously a class?
if (instanceTy->mayHaveSuperclass()) {
// Get the ObjC metadata for the class.
auto heapMetadata = emitClassHeapMetadataRefForMetatype(IGF,metatypeValue,
instanceTy);
return IGF.Builder.CreateBitCast(heapMetadata, IGF.IGM.ObjCPtrTy);
}
// If it's not a class, we can't handle it here
if (!isa<ArchetypeType>(instanceTy) && !isa<ExistentialMetatypeType>(type)) {
return nullptr;
}
// Ask the runtime whether this is class metadata.
FunctionPointer castFn;
switch (mode) {
case CheckedCastMode::Conditional:
castFn =
IGF.IGM.getDynamicCastMetatypeToObjectConditionalFunctionPointer();
break;
case CheckedCastMode::Unconditional:
castFn =
IGF.IGM.getDynamicCastMetatypeToObjectUnconditionalFunctionPointer();
break;
}
llvm::Value *argsBuf[] = {
metatypeValue,
nullptr,
nullptr,
nullptr,
};
auto call = IGF.Builder.CreateCall(
castFn, getDynamicCastArguments(IGF, argsBuf, mode));
return call;
}
}
llvm_unreachable("invalid metatype representation");
}
/// Emit a checked cast to a protocol or protocol composition.
void irgen::emitScalarExistentialDowncast(
IRGenFunction &IGF, llvm::Value *value, SILType srcType, SILType destType,
CheckedCastMode mode, std::optional<MetatypeRepresentation> metatypeKind,
Explosion &ex) {
auto srcInstanceType = srcType.getASTType();
auto destInstanceType = destType.getASTType();
while (auto metatypeType = dyn_cast<ExistentialMetatypeType>(
destInstanceType)) {
destInstanceType = metatypeType.getInstanceType();
srcInstanceType = cast<AnyMetatypeType>(srcInstanceType).getInstanceType();
}
auto layout = destInstanceType.getExistentialLayout();
// Look up witness tables for the protocols that need them and get
// references to the ObjC Protocol* values for the objc protocols.
SmallVector<llvm::Value*, 4> objcProtos;
SmallVector<llvm::Value*, 4> witnessTableProtos;
bool hasClassConstraint = layout.requiresClass();
bool hasClassConstraintByProtocol = false;
bool hasSuperclassConstraint = bool(layout.explicitSuperclass);
for (auto protoDecl : layout.getProtocols()) {
// If the protocol introduces a class constraint, track whether we need
// to check for it independent of protocol witnesses.
if (protoDecl->requiresClass()) {
assert(hasClassConstraint);
hasClassConstraintByProtocol = true;
}
if (Lowering::TypeConverter::protocolRequiresWitnessTable(protoDecl)) {
auto descriptor = IGF.IGM.getAddrOfProtocolDescriptor(protoDecl);
witnessTableProtos.push_back(descriptor);
}
if (protoDecl->isObjC())
objcProtos.push_back(emitReferenceToObjCProtocol(IGF, protoDecl));
}
llvm::Type *resultType;
if (metatypeKind) {
switch (*metatypeKind) {
case MetatypeRepresentation::Thin:
llvm_unreachable("can't cast to thin metatype");
case MetatypeRepresentation::Thick:
resultType = IGF.IGM.TypeMetadataPtrTy;
break;
case MetatypeRepresentation::ObjC:
resultType = IGF.IGM.ObjCClassPtrTy;
break;
}
} else {
auto schema = IGF.getTypeInfo(destType).getSchema();
resultType = schema[0].getScalarType();
}
// The source of a scalar cast is statically known to be a class or a
// metatype, so we only have to check the class constraint in two cases:
//
// 1) The destination type has a superclass constraint that is
// more derived than what the source type is known to be.
//
// 2) We are casting between metatypes, in which case the source might
// be a non-class metatype.
bool checkClassConstraint = false;
if ((bool)metatypeKind &&
hasClassConstraint &&
!hasClassConstraintByProtocol &&
!srcInstanceType->mayHaveSuperclass())
checkClassConstraint = true;
// If the source has an equal or more derived superclass constraint than
// the destination, we can elide the superclass check.
//
// Note that destInstanceType is always an existential type, so calling
// getSuperclass() returns the superclass constraint of the existential,
// not the superclass of some concrete class.
bool checkSuperclassConstraint = false;
if (hasSuperclassConstraint) {
Type srcSuperclassType = srcInstanceType;
if (srcSuperclassType->isExistentialType()) {
srcSuperclassType = srcSuperclassType->getSuperclass();
// Look for an AnyObject superclass (getSuperclass() returns nil).
if (!srcSuperclassType && srcInstanceType->isClassExistentialType())
checkSuperclassConstraint = true;
}
if (srcSuperclassType) {
checkSuperclassConstraint =
!destInstanceType->getSuperclass()->isExactSuperclassOf(
srcSuperclassType);
}
}
if (checkSuperclassConstraint)
checkClassConstraint = true;
llvm::Value *resultValue = value;
// If we don't have anything we really need to check, then trivially succeed.
if (objcProtos.empty() && witnessTableProtos.empty() &&
!checkClassConstraint) {
resultValue = IGF.Builder.CreateBitCast(value, resultType);
ex.add(resultValue);
return;
}
// Check the ObjC protocol conformances if there were any.
llvm::Value *objcCast = nullptr;
if (!objcProtos.empty()) {
// Get the ObjC instance or class object to check for these conformances.
llvm::Value *objcObject;
if (metatypeKind) {
switch (*metatypeKind) {
case MetatypeRepresentation::Thin:
llvm_unreachable("can't cast to thin metatype");
case MetatypeRepresentation::Thick: {
// The metadata might be for a non-class type, which wouldn't have
// an ObjC class object.
objcObject = nullptr;
break;
}
case MetatypeRepresentation::ObjC:
// Metatype is already an ObjC object.
objcObject = value;
break;
}
} else {
// Class instance is already an ObjC object.
objcObject = value;
}
if (objcObject)
objcObject = IGF.Builder.CreateBitCast(objcObject,
IGF.IGM.UnknownRefCountedPtrTy);
// Pick the cast function based on the cast mode and on whether we're
// casting a Swift metatype or ObjC object.
FunctionPointer castFn;
switch (mode) {
case CheckedCastMode::Unconditional:
castFn =
objcObject
? IGF.IGM.getDynamicCastObjCProtocolUnconditionalFunctionPointer()
: IGF.IGM
.getDynamicCastTypeToObjCProtocolUnconditionalFunctionPointer();
break;
case CheckedCastMode::Conditional:
castFn =
objcObject
? IGF.IGM.getDynamicCastObjCProtocolConditionalFunctionPointer()
: IGF.IGM
.getDynamicCastTypeToObjCProtocolConditionalFunctionPointer();
break;
}
llvm::Value *objcCastObject = objcObject ? objcObject : value;
Address protoRefsBuf = IGF.createAlloca(
llvm::ArrayType::get(IGF.IGM.Int8PtrTy,
objcProtos.size()),
IGF.IGM.getPointerAlignment(),
"objc_protocols");
protoRefsBuf =
IGF.Builder.CreateElementBitCast(protoRefsBuf, IGF.IGM.Int8PtrTy);
for (unsigned index : indices(objcProtos)) {
Address protoRefSlot = IGF.Builder.CreateConstArrayGEP(
protoRefsBuf, index,
IGF.IGM.getPointerSize());
IGF.Builder.CreateStore(objcProtos[index], protoRefSlot);
++index;
}
llvm::Value *argsBuf[] = {
objcCastObject,
IGF.IGM.getSize(Size(objcProtos.size())),
protoRefsBuf.getAddress(),
nullptr,
nullptr,
nullptr,
};
auto call = IGF.Builder.CreateCall(
castFn, getDynamicCastArguments(IGF, argsBuf, mode));
objcCast = call;
resultValue = IGF.Builder.CreateBitCast(objcCast, resultType);
}
// If we don't need to look up any witness tables, we're done.
if (witnessTableProtos.empty() && !checkClassConstraint) {
ex.add(resultValue);
return;
}
// If we're doing a conditional cast, and the ObjC protocol checks failed,
// then the cast is done.
std::optional<ConditionalDominanceScope> condition;
llvm::BasicBlock *origBB = nullptr, *successBB = nullptr, *contBB = nullptr;
if (!objcProtos.empty()) {
switch (mode) {
case CheckedCastMode::Unconditional:
break;
case CheckedCastMode::Conditional: {
origBB = IGF.Builder.GetInsertBlock();
successBB = IGF.createBasicBlock("success");
contBB = IGF.createBasicBlock("cont");
auto isNull = IGF.Builder.CreateICmpEQ(objcCast,
llvm::ConstantPointerNull::get(
cast<llvm::PointerType>(objcCast->getType())));
IGF.Builder.CreateCondBr(isNull, contBB, successBB);
IGF.Builder.emitBlock(successBB);
condition.emplace(IGF);
}
}
}
// Get the Swift type metadata for the type.
llvm::Value *metadataValue;
if (metatypeKind) {
switch (*metatypeKind) {
case MetatypeRepresentation::Thin:
llvm_unreachable("can't cast to thin metatype");
case MetatypeRepresentation::Thick:
// The value is already a native metatype.
metadataValue = value;
break;
case MetatypeRepresentation::ObjC:
// Get the type metadata from the ObjC class, which may be a wrapper.
metadataValue = emitObjCMetadataRefForMetadata(IGF, value);
}
} else {
// Get the type metadata for the instance.
metadataValue = emitDynamicTypeOfHeapObject(IGF, value,
MetatypeRepresentation::Thick,
srcType);
}
// Look up witness tables for the protocols that need them.
auto fn = emitExistentialScalarCastFn(IGF.IGM,
witnessTableProtos.size(),
mode,
checkClassConstraint,
checkSuperclassConstraint);
llvm::SmallVector<llvm::Value *, 4> args;
if (resultValue->getType() != IGF.IGM.Int8PtrTy)
resultValue = IGF.Builder.CreateBitCast(resultValue, IGF.IGM.Int8PtrTy);
args.push_back(resultValue);
args.push_back(metadataValue);
if (checkSuperclassConstraint)
args.push_back(IGF.emitTypeMetadataRef(CanType(layout.explicitSuperclass)));
for (auto proto : witnessTableProtos)
args.push_back(proto);
auto valueAndWitnessTables = IGF.Builder.CreateCall(fn, args);
resultValue = IGF.Builder.CreateExtractValue(valueAndWitnessTables, 0);
if (resultValue->getType() != resultType)
resultValue = IGF.Builder.CreateBitCast(resultValue, resultType);
ex.add(resultValue);
for (unsigned i = 0, e = witnessTableProtos.size(); i < e; ++i) {
auto wt = IGF.Builder.CreateExtractValue(valueAndWitnessTables, i + 1);
ex.add(wt);
}
// If we had conditional ObjC checks, join the failure paths.
if (contBB) {
condition.reset();
IGF.Builder.CreateBr(contBB);
IGF.Builder.emitBlock(contBB);
// Return null on the failure path.
Explosion successEx = std::move(ex);
ex.reset();
while (!successEx.empty()) {
auto successVal = successEx.claimNext();
auto failureVal = llvm::Constant::getNullValue(successVal->getType());
auto phi = IGF.Builder.CreatePHI(successVal->getType(), 2);
phi->addIncoming(successVal, successBB);
phi->addIncoming(failureVal, origBB);
ex.add(phi);
}
}
}
/// Emit a checked cast of a scalar value.
///
/// This is not just an implementation of emitCheckedCast for scalar types;
/// it imposes strict restrictions on the source and target types that ensure
/// that the actual value isn't changed in any way, thus preserving its
/// reference identity.
///
/// These restrictions are set by \c canSILUseScalarCheckedCastInstructions.
/// Essentially, both the source and target types must be one of:
/// - a (possibly generic) concrete class type,
/// - a class-bounded archetype,
/// - a class-bounded existential,
/// - a concrete metatype, or
/// - an existential metatype.
///
/// Furthermore, if the target type is a metatype, the source type must be
/// a metatype. This restriction isn't obviously necessary; it's just that
/// the runtime support for checking that an object instance is a metatype
/// isn't exposed.
void irgen::emitScalarCheckedCast(IRGenFunction &IGF,
Explosion &value,
SILType sourceLoweredType,
CanType sourceFormalType,
SILType targetLoweredType,
CanType targetFormalType,
CheckedCastMode mode,
CastingIsolatedConformances isolatedConformances,
Explosion &out) {
assert(sourceLoweredType.isObject());
assert(targetLoweredType.isObject());
llvm::BasicBlock *nilCheckBB = nullptr;
llvm::BasicBlock *nilMergeBB = nullptr;
// Merge the nil check and return the merged result: either nil or the value.
auto returnNilCheckedResult = [&](IRBuilder &Builder,
Explosion &nonNilResult) {
if (nilCheckBB) {
auto notNilBB = Builder.GetInsertBlock();
Builder.CreateBr(nilMergeBB);
Builder.emitBlock(nilMergeBB);
// Insert result phi.
Explosion result;
while (!nonNilResult.empty()) {
auto val = nonNilResult.claimNext();
auto valTy = cast<llvm::PointerType>(val->getType());
auto nil = llvm::ConstantPointerNull::get(valTy);
auto phi = Builder.CreatePHI(valTy, 2);
phi->addIncoming(nil, nilCheckBB);
phi->addIncoming(val, notNilBB);
result.add(phi);
}
out = std::move(result);
} else {
out = std::move(nonNilResult);
}
};
bool sourceWrappedInOptional = false;
std::optional<ConditionalDominanceScope> domScope;
if (auto sourceOptObjectType = sourceLoweredType.getOptionalObjectType()) {
// Translate the value from an enum representation to a possibly-null
// representation. Note that we assume that this projection is safe
// for the particular case of an optional class-reference or metatype
// value.
Explosion optValue;
auto someDecl = IGF.IGM.Context.getOptionalSomeDecl();
emitProjectLoadableEnum(IGF, sourceLoweredType, value, someDecl, optValue);
assert(value.empty());
value = std::move(optValue);
sourceLoweredType = sourceOptObjectType;
sourceFormalType = sourceFormalType.getOptionalObjectType();
sourceWrappedInOptional = true;
// We need a null-check because the runtime function can't handle null in
// some of the cases.
if (targetLoweredType.isExistentialType()) {
auto &Builder = IGF.Builder;
auto val = value.getAll()[0];
auto isNotNil = Builder.CreateICmpNE(
val, llvm::ConstantPointerNull::get(
cast<llvm::PointerType>(val->getType())));
auto *isNotNilContBB = llvm::BasicBlock::Create(IGF.IGM.getLLVMContext());
nilMergeBB = llvm::BasicBlock::Create(IGF.IGM.getLLVMContext());
nilCheckBB = Builder.GetInsertBlock();
Builder.CreateCondBr(isNotNil, isNotNilContBB, nilMergeBB);
Builder.emitBlock(isNotNilContBB);
domScope.emplace(IGF);
}
}
// If the source value is a metatype, either do a metatype-to-metatype
// cast or cast it to an object instance and continue.
if (auto sourceMetatype = sourceLoweredType.getAs<AnyMetatypeType>()) {
llvm::Value *metatypeVal = nullptr;
if (sourceMetatype->getRepresentation() != MetatypeRepresentation::Thin)
metatypeVal = value.claimNext();
// If the metatype is existential, there may be witness tables in the
// value, which we don't need.
// TODO: In existential-to-existential casts, we should carry over common
// witness tables from the source to the destination.
(void)value.claimAll();
SmallVector<ProtocolDecl*, 1> protocols;
// Casts to existential metatypes.
if (auto existential = targetLoweredType.getAs<ExistentialMetatypeType>()) {
emitScalarExistentialDowncast(IGF, metatypeVal, sourceLoweredType,
targetLoweredType, mode,
existential->getRepresentation(),
out);
return;
// Casts to concrete metatypes.
} else if (auto destMetaType = targetLoweredType.getAs<MetatypeType>()) {
emitMetatypeDowncast(IGF, metatypeVal, destMetaType, mode, out);
return;
}
// Otherwise, this is a metatype-to-object cast.
assert(targetLoweredType.isAnyClassReferenceType());
// Can we convert the metatype value to AnyObject using Obj-C machinery?
llvm::Value *object =
emitMetatypeToAnyObjectDowncast(IGF, metatypeVal, sourceMetatype, mode);
if (object == nullptr) {
// Obj-C cast routine failed, use swift_dynamicCast instead
if (sourceMetatype->getRepresentation() == MetatypeRepresentation::Thin
|| metatypeVal == nullptr) {
// Earlier stages *should* never generate a checked cast with a thin metatype argument.
// TODO: Move this assertion up to apply to all checked cast operations.
// In assert builds, enforce this by failing here:
assert(false && "Invalid SIL: General checked_cast_br cannot have thin argument");
// In non-assert builds, stay compatible with previous behavior by emitting a null load.
object = llvm::ConstantPointerNull::get(IGF.IGM.ObjCPtrTy);
} else {
Address src = IGF.createAlloca(metatypeVal->getType(),
IGF.IGM.getPointerAlignment(),
"castSrc");
IGF.Builder.CreateStore(metatypeVal, src);
llvm::PointerType *destPtrType = IGF.IGM.getStoragePointerType(targetLoweredType);
Address dest = IGF.createAlloca(destPtrType,
IGF.IGM.getPointerAlignment(),
"castDest");
IGF.Builder.CreateStore(llvm::ConstantPointerNull::get(destPtrType), dest);
llvm::Value *success = emitCheckedCast(IGF,
src, sourceFormalType,
dest, targetFormalType,
CastConsumptionKind::TakeAlways,
mode, isolatedConformances);
llvm::Value *successResult = IGF.Builder.CreateLoad(dest);
llvm::Value *failureResult = llvm::ConstantPointerNull::get(destPtrType);
llvm::Value *result = IGF.Builder.CreateSelect(success, successResult, failureResult);
object = std::move(result);
}
}
sourceFormalType = IGF.IGM.Context.getAnyObjectType();
sourceLoweredType = SILType::getPrimitiveObjectType(sourceFormalType);
// Continue, pretending that the source value was an (optional) value.
Explosion newValue;
newValue.add(object);
value = std::move(newValue);
}
assert(!targetLoweredType.is<AnyMetatypeType>() &&
"scalar cast of class reference to metatype is unimplemented");
// If the source type is existential, project out the class pointer.
//
// TODO: if we're casting to an existential type, don't throw away the
// protocol conformance information we already have.
llvm::Value *instance;
if (sourceLoweredType.isExistentialType()) {
instance = emitClassExistentialProjection(IGF, value, sourceLoweredType,
CanArchetypeType());
} else {
instance = value.claimNext();
}
if (targetFormalType.isExistentialType()) {
Explosion outRes;
emitScalarExistentialDowncast(
IGF, instance, sourceLoweredType, targetLoweredType, mode,
/*not a metatype*/ std::nullopt, outRes);
returnNilCheckedResult(IGF.Builder, outRes);
return;
}
if (llvm::Value *fastResult = emitFastClassCastIfPossible(
IGF, instance, sourceFormalType, targetFormalType,
sourceWrappedInOptional, nilCheckBB, nilMergeBB)) {
Explosion fastExplosion;
fastExplosion.add(fastResult);
returnNilCheckedResult(IGF.Builder, fastExplosion);
return;
}
Explosion outRes;
llvm::Value *result = emitClassDowncast(IGF, instance, targetFormalType,
mode);
out.add(result);
}
/// When casting a class instance to a final class, we can directly compare
/// the isa-pointer with address of the metadata. This avoids a call to
/// `swift_dynamicCastClass`.
/// It also avoids a call to the metadata accessor of the class (which calls
/// `swift_getInitializedObjCClass`). For comparing the metadata pointers it's
/// not required that the metadata is fully initialized.
llvm::Value *irgen::emitFastClassCastIfPossible(
IRGenFunction &IGF, llvm::Value *instance, CanType sourceFormalType,
CanType targetFormalType, bool sourceWrappedInOptional,
llvm::BasicBlock *&nilCheckBB, llvm::BasicBlock *&nilMergeBB) {
if (!doesCastPreserveOwnershipForTypes(IGF.IGM.getSILModule(),
sourceFormalType, targetFormalType)) {
return nullptr;
}
// This does not include generic classes.
auto classTy = dyn_cast<ClassType>(targetFormalType);
if (!classTy)
return nullptr;
ClassDecl *toClass = classTy->getDecl();
// Also exclude non-generic classes nested inside other generic types.
if (toClass->isGenericContext())
return nullptr;
// TODO: we could use the ClassHierarchyAnalysis do also handle "effectively"
// final classes, e.g. not-subclassed internal classes in WMO.
// This would need some rearchitecting of ClassHierarchyAnalysis to make it
// available in IRGen.
if (!toClass->isFinal())
return nullptr;
AncestryOptions forbidden = AncestryOptions(AncestryFlags::ObjC) |
AncestryFlags::Resilient |
AncestryFlags::ResilientOther |
AncestryFlags::ClangImported |
AncestryFlags::ObjCObjectModel;
if (toClass->checkAncestry() & forbidden)
return nullptr;
// If the source was originally wrapped in an Optional, check it for nil now.
if (sourceWrappedInOptional) {
auto isNotNil = IGF.Builder.CreateICmpNE(
instance, llvm::ConstantPointerNull::get(
cast<llvm::PointerType>(instance->getType())));
auto *isNotNilContBB = llvm::BasicBlock::Create(IGF.IGM.getLLVMContext());
nilMergeBB = llvm::BasicBlock::Create(IGF.IGM.getLLVMContext());
nilCheckBB = IGF.Builder.GetInsertBlock();
IGF.Builder.CreateCondBr(isNotNil, isNotNilContBB, nilMergeBB);
IGF.Builder.emitBlock(isNotNilContBB);
}
// Get the metadata pointer of the destination class type.
llvm::Value *destMetadata = IGF.IGM.getAddrOfTypeMetadata(targetFormalType);
if (IGF.IGM.IRGen.Opts.LazyInitializeClassMetadata) {
llvm::Function *accessor =
IGF.IGM.getAddrOfTypeMetadataAccessFunction(targetFormalType,
NotForDefinition);
auto request = DynamicMetadataRequest(MetadataState::Complete);
// We know that we are not in a generic class context, so we can safely
// determine that the call here does not need to take that into account.
auto response =
IGF.emitGenericTypeMetadataAccessFunctionCall(accessor, {}, request);
destMetadata = response.getMetadata();
}
llvm::Value *lhs = IGF.Builder.CreateBitCast(destMetadata, IGF.IGM.Int8PtrTy);
// Load the isa pointer.
llvm::Value *objMetadata = emitHeapMetadataRefForHeapObject(IGF, instance,
sourceFormalType, /*suppress cast*/ true);
llvm::Value *rhs = IGF.Builder.CreateBitCast(objMetadata, IGF.IGM.Int8PtrTy);
// return isa_ptr == metadata_ptr ? instance : nullptr
llvm::Value *isEqual = IGF.Builder.CreateCmp(llvm::CmpInst::Predicate::ICMP_EQ,
lhs, rhs);
auto *instanceTy = cast<llvm::PointerType>(instance->getType());
auto *nullPtr = llvm::ConstantPointerNull::get(instanceTy);
auto *select = IGF.Builder.CreateSelect(isEqual, instance, nullPtr);
llvm::Type *destTy = IGF.getTypeInfoForUnlowered(targetFormalType).getStorageType();
return IGF.Builder.CreateBitCast(select, destTy);
}