Skip to content

Commit 44f71b1

Browse files
committed
Handle all the cases of scalar dynamic casts allowed by SIL in IRGen.
In particular, reliably look through a single level of optional types and handle metatype-to-class casts. Fixes rdar://24924966.
1 parent b384f5e commit 44f71b1

File tree

6 files changed

+186
-90
lines changed

6 files changed

+186
-90
lines changed

lib/IRGen/GenCast.cpp

Lines changed: 104 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "GenCast.h"
1818

1919
#include "Explosion.h"
20+
#include "GenEnum.h"
2021
#include "GenExistential.h"
2122
#include "GenMeta.h"
2223
#include "GenProto.h"
@@ -391,15 +392,15 @@ static llvm::Function *emitExistentialScalarCastFn(IRGenModule &IGM,
391392
return fn;
392393
}
393394

394-
void irgen::emitMetatypeToObjectDowncast(IRGenFunction &IGF,
395-
llvm::Value *metatypeValue,
396-
CanAnyMetatypeType type,
397-
CheckedCastMode mode,
398-
Explosion &ex) {
395+
llvm::Value *irgen::emitMetatypeToAnyObjectDowncast(IRGenFunction &IGF,
396+
llvm::Value *metatypeValue,
397+
CanAnyMetatypeType type,
398+
CheckedCastMode mode) {
399399
// If ObjC interop is enabled, casting a metatype to AnyObject succeeds
400400
// if the metatype is for a class.
401-
auto triviallyFail = [&] {
402-
ex.add(llvm::ConstantPointerNull::get(IGF.IGM.ObjCPtrTy));
401+
402+
auto triviallyFail = [&]() -> llvm::Value* {
403+
return llvm::ConstantPointerNull::get(IGF.IGM.ObjCPtrTy);
403404
};
404405

405406
if (!IGF.IGM.ObjCInterop)
@@ -408,8 +409,7 @@ void irgen::emitMetatypeToObjectDowncast(IRGenFunction &IGF,
408409
switch (type->getRepresentation()) {
409410
case MetatypeRepresentation::ObjC:
410411
// Metatypes that can be represented as ObjC trivially cast to AnyObject.
411-
ex.add(IGF.Builder.CreateBitCast(metatypeValue, IGF.IGM.ObjCPtrTy));
412-
return;
412+
return IGF.Builder.CreateBitCast(metatypeValue, IGF.IGM.ObjCPtrTy);
413413

414414
case MetatypeRepresentation::Thin:
415415
// Metatypes that can be thin would never be classes.
@@ -425,8 +425,7 @@ void irgen::emitMetatypeToObjectDowncast(IRGenFunction &IGF,
425425
// Get the ObjC metadata for the class.
426426
auto heapMetadata = emitClassHeapMetadataRefForMetatype(IGF,metatypeValue,
427427
instanceTy);
428-
ex.add(IGF.Builder.CreateBitCast(heapMetadata, IGF.IGM.ObjCPtrTy));
429-
return;
428+
return IGF.Builder.CreateBitCast(heapMetadata, IGF.IGM.ObjCPtrTy);
430429
}
431430

432431
// Is the type obviously not a class?
@@ -451,11 +450,10 @@ void irgen::emitMetatypeToObjectDowncast(IRGenFunction &IGF,
451450

452451
auto call = IGF.Builder.CreateCall(castFn, metatypeValue);
453452
call->setCallingConv(cc);
454-
ex.add(call);
455-
return;
453+
return call;
456454
}
457455
}
458-
456+
llvm_unreachable("invalid metatype representation");
459457
}
460458

461459

@@ -706,22 +704,58 @@ void irgen::emitScalarExistentialDowncast(IRGenFunction &IGF,
706704
}
707705
}
708706

709-
/// Emit a checked cast sequence.
710-
void irgen::emitValueCheckedCast(IRGenFunction &IGF,
711-
Explosion &value,
712-
SILType valueType,
713-
SILType loweredTargetType,
714-
CheckedCastMode mode,
715-
Explosion &out) {
716-
assert(valueType.isObject());
717-
assert(loweredTargetType.isObject());
718-
CanType sourceType = valueType.getSwiftRValueType();
719-
CanType targetType = loweredTargetType.getSwiftRValueType();
707+
/// Emit a checked cast of a scalar value.
708+
///
709+
/// This is not just an implementation of emitCheckedCast for scalar types;
710+
/// it imposes strict restrictions on the source and target types that ensure
711+
/// that the actual value isn't changed in any way, thus preserving its
712+
/// reference identity.
713+
///
714+
/// These restrictions are set by canUseScalarCheckedCastInstructions.
715+
/// Essentially, both the source and target types must be one of:
716+
/// - a (possibly generic) concrete class type,
717+
/// - a class-bounded archetype,
718+
/// - a class-bounded existential,
719+
/// - a concrete metatype, or
720+
/// - an existential metatype.
721+
///
722+
/// Furthermore, if the target type is a metatype, the source type must be
723+
/// a metatype. This restriction isn't obviously necessary; it's just that
724+
/// the runtime support for checking that an object instance is a metatype
725+
/// isn't exposed.
726+
void irgen::emitScalarCheckedCast(IRGenFunction &IGF,
727+
Explosion &value,
728+
SILType sourceType,
729+
SILType targetType,
730+
CheckedCastMode mode,
731+
Explosion &out) {
732+
assert(sourceType.isObject());
733+
assert(targetType.isObject());
734+
735+
OptionalTypeKind optKind;
736+
if (auto sourceOptObjectType =
737+
sourceType.getAnyOptionalObjectType(*IGF.IGM.SILMod, optKind)) {
738+
739+
// Translate the value from an enum represention to a possibly-null
740+
// representation. Note that we assume that this projection is safe
741+
// for the particular case of an optional class-reference or metatype
742+
// value.
743+
Explosion optValue;
744+
auto someDecl = IGF.IGM.Context.getOptionalSomeDecl(optKind);
745+
emitProjectLoadableEnum(IGF, sourceType, value, someDecl, optValue);
746+
747+
assert(value.empty());
748+
value = std::move(optValue);
749+
sourceType = sourceOptObjectType;
750+
}
720751

721-
if (auto sourceMetaType = dyn_cast<AnyMetatypeType>(sourceType)) {
752+
// If the source value is a metatype, either do a metatype-to-metatype
753+
// cast or cast it to an object instance and continue.
754+
if (auto sourceMetatype = sourceType.getAs<AnyMetatypeType>()) {
722755
llvm::Value *metatypeVal = nullptr;
723-
if (sourceMetaType->getRepresentation() != MetatypeRepresentation::Thin)
756+
if (sourceMetatype->getRepresentation() != MetatypeRepresentation::Thin)
724757
metatypeVal = value.claimNext();
758+
725759
// If the metatype is existential, there may be witness tables in the
726760
// value, which we don't need.
727761
// TODO: In existential-to-existential casts, we should carry over common
@@ -730,64 +764,60 @@ void irgen::emitValueCheckedCast(IRGenFunction &IGF,
730764

731765
SmallVector<ProtocolDecl*, 1> protocols;
732766

733-
if (auto existential = dyn_cast<ExistentialMetatypeType>(targetType))
734-
emitScalarExistentialDowncast(IGF, metatypeVal,
735-
valueType, loweredTargetType,
736-
mode,
737-
existential->getRepresentation(),
767+
// Casts to existential metatypes.
768+
if (auto existential = targetType.getAs<ExistentialMetatypeType>()) {
769+
emitScalarExistentialDowncast(IGF, metatypeVal, sourceType, targetType,
770+
mode, existential->getRepresentation(),
738771
out);
739-
else if (auto destMetaType = dyn_cast<MetatypeType>(targetType))
772+
return;
773+
774+
// Casts to concrete metatypes.
775+
} else if (auto destMetaType = targetType.getAs<MetatypeType>()) {
740776
emitMetatypeDowncast(IGF, metatypeVal, destMetaType, mode, out);
741-
else if (targetType->isExistentialType(protocols)) {
742-
assert(IGF.IGM.ObjCInterop
743-
&& protocols.size() == 1
744-
&& *protocols[0]->getKnownProtocolKind()
745-
== KnownProtocolKind::AnyObject
746-
&& "metatypes can only be cast to AnyObject, with ObjC interop");
747-
emitMetatypeToObjectDowncast(IGF, metatypeVal, sourceMetaType, mode, out);
777+
return;
748778
}
749-
return;
750-
}
751779

752-
if ((isa<ArchetypeType>(sourceType) && !targetType.isExistentialType()) ||
753-
(isa<ArchetypeType>(targetType) && !sourceType.isExistentialType())) {
754-
llvm::Value *fromValue = value.claimNext();
755-
llvm::Value *toValue =
756-
emitClassDowncast(IGF, fromValue, loweredTargetType, mode);
757-
out.add(toValue);
758-
return;
780+
// Otherwise, this is a metatype-to-object cast.
781+
assert(targetType.isAnyClassReferenceType());
782+
783+
// Convert the metatype value to AnyObject.
784+
llvm::Value *object =
785+
emitMetatypeToAnyObjectDowncast(IGF, metatypeVal, sourceMetatype, mode);
786+
787+
auto anyObjectProtocol =
788+
IGF.IGM.Context.getProtocol(KnownProtocolKind::AnyObject);
789+
SILType anyObjectType =
790+
SILType::getPrimitiveObjectType(
791+
CanType(anyObjectProtocol->getDeclaredType()));
792+
793+
// Continue, pretending that the source value was an (optional) value.
794+
Explosion newValue;
795+
newValue.add(object);
796+
value = std::move(newValue);
797+
sourceType = anyObjectType;
759798
}
760799

761-
if (sourceType.isExistentialType()) {
762-
llvm::Value *instance
763-
= emitClassExistentialProjection(IGF, value, valueType,
764-
CanArchetypeType());
765-
766-
llvm::Value *toValue;
767-
if (loweredTargetType.isExistentialType()) {
768-
emitScalarExistentialDowncast(IGF, instance, valueType,
769-
loweredTargetType, mode,
770-
/*not a metatype*/ None,
771-
out);
772-
} else {
773-
toValue = emitClassDowncast(IGF, instance, loweredTargetType, mode);
774-
out.add(toValue);
775-
}
800+
assert(!targetType.is<AnyMetatypeType>() &&
801+
"scalar cast of class reference to metatype is unimplemented");
776802

777-
return;
803+
// If the source type is existential, project out the class pointer.
804+
//
805+
// TODO: if we're casting to an existential type, don't throw away the
806+
// protocol conformance information we already have.
807+
llvm::Value *instance;
808+
if (sourceType.isExistentialType()) {
809+
instance = emitClassExistentialProjection(IGF, value, sourceType,
810+
CanArchetypeType());
811+
} else {
812+
instance = value.claimNext();
778813
}
779814

780815
if (targetType.isExistentialType()) {
781-
llvm::Value *fromValue = value.claimNext();
782-
emitScalarExistentialDowncast(IGF, fromValue, valueType,
783-
loweredTargetType, mode,
784-
/*not a metatype*/ None,
785-
out);
816+
emitScalarExistentialDowncast(IGF, instance, sourceType, targetType,
817+
mode, /*not a metatype*/ None, out);
786818
return;
787819
}
788820

789-
llvm::Value *fromValue = value.claimNext();
790-
llvm::Value *cast
791-
= emitClassDowncast(IGF, fromValue, loweredTargetType, mode);
792-
out.add(cast);
821+
llvm::Value *result = emitClassDowncast(IGF, instance, targetType, mode);
822+
out.add(result);
793823
}

lib/IRGen/GenCast.h

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ namespace irgen {
4747
CastConsumptionKind consumptionKind,
4848
CheckedCastMode mode);
4949

50-
void emitValueCheckedCast(IRGenFunction &IGF, Explosion &value,
51-
SILType valueType, SILType loweredTargetType,
52-
CheckedCastMode mode, Explosion &out);
50+
void emitScalarCheckedCast(IRGenFunction &IGF, Explosion &value,
51+
SILType valueType, SILType loweredTargetType,
52+
CheckedCastMode mode, Explosion &out);
5353

5454
/// \brief Convert a class object to the given destination type,
5555
/// using a runtime-checked cast.
@@ -95,11 +95,10 @@ namespace irgen {
9595
Explosion &ex);
9696

9797
/// Emit a checked cast from a metatype to AnyObject.
98-
void emitMetatypeToObjectDowncast(IRGenFunction &IGF,
99-
llvm::Value *metatypeValue,
100-
CanAnyMetatypeType type,
101-
CheckedCastMode mode,
102-
Explosion &ex);
98+
llvm::Value *emitMetatypeToAnyObjectDowncast(IRGenFunction &IGF,
99+
llvm::Value *metatypeValue,
100+
CanAnyMetatypeType type,
101+
CheckedCastMode mode);
103102

104103
/// Emit a Protocol* value referencing an ObjC protocol.
105104
llvm::Value *emitReferenceToObjCProtocol(IRGenFunction &IGF,

lib/IRGen/IRGenSIL.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3929,8 +3929,8 @@ void IRGenSILFunction::visitUnconditionalCheckedCastInst(
39293929
swift::UnconditionalCheckedCastInst *i) {
39303930
Explosion value = getLoweredExplosion(i->getOperand());
39313931
Explosion ex;
3932-
emitValueCheckedCast(*this, value, i->getOperand()->getType(), i->getType(),
3933-
CheckedCastMode::Unconditional, ex);
3932+
emitScalarCheckedCast(*this, value, i->getOperand()->getType(), i->getType(),
3933+
CheckedCastMode::Unconditional, ex);
39343934
setLoweredExplosion(i, ex);
39353935
}
39363936

@@ -4090,12 +4090,12 @@ void IRGenSILFunction::visitCheckedCastBranchInst(
40904090
operand->getType(), destTy);
40914091
} else {
40924092
Explosion value = getLoweredExplosion(i->getOperand());
4093-
emitValueCheckedCast(*this, value, i->getOperand()->getType(),
4094-
i->getCastType(), CheckedCastMode::Conditional, ex);
4093+
emitScalarCheckedCast(*this, value, i->getOperand()->getType(),
4094+
i->getCastType(), CheckedCastMode::Conditional, ex);
40954095
auto val = ex.claimNext();
40964096
castResult.casted = val;
40974097
llvm::Value *nil =
4098-
llvm::ConstantPointerNull::get(cast<llvm::PointerType>(val->getType()));
4098+
llvm::ConstantPointerNull::get(cast<llvm::PointerType>(val->getType()));
40994099
castResult.succeeded = Builder.CreateICmpNE(val, nil);
41004100
}
41014101

lib/SIL/DynamicCasts.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -884,6 +884,11 @@ bool swift::canUseScalarCheckedCastInstructions(SILModule &M,
884884
auto super = archetype->getSuperclass();
885885
if (super.isNull())
886886
return false;
887+
888+
// Only ever permit this if the source type is a reference type.
889+
if (!objectType.isAnyClassReferenceType())
890+
return false;
891+
887892
// A base class constraint that isn't NSError rules out the archetype being
888893
// bound to NSError.
889894
if (auto nserror = M.Types.getNSErrorType())
@@ -893,7 +898,7 @@ bool swift::canUseScalarCheckedCastInstructions(SILModule &M,
893898
}
894899

895900
if (targetType == M.Types.getNSErrorType()) {
896-
// If we statically know the target is an NSError subclass, then the cast
901+
// If we statically know the source is an NSError subclass, then the cast
897902
// can go through the scalar path (and it's trivially true so can be
898903
// killed).
899904
return targetType->isExactSuperclassOf(objectType, nullptr);
@@ -904,7 +909,7 @@ bool swift::canUseScalarCheckedCastInstructions(SILModule &M,
904909
// - metatype to object
905910
// - object to object
906911
if ((objectType.isAnyClassReferenceType() || isa<AnyMetatypeType>(objectType))
907-
&& targetType->isAnyClassReferenceType())
912+
&& targetType.isAnyClassReferenceType())
908913
return true;
909914

910915
if (isa<AnyMetatypeType>(objectType) && isa<AnyMetatypeType>(targetType))

test/IRGen/casts.sil

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,8 @@ nay:
221221
}
222222

223223
// CHECK-LABEL: define{{( protected)?}} %C5casts1A* @checked_downcast_optional({{(i32|i64)}}) {{.*}} {
224-
// CHECK: [[OBJ_PTR:%.*]] = inttoptr {{(i32|i64)}} %0 to i8*
224+
// CHECK: [[T0:%.*]] = inttoptr {{(i32|i64)}} %0 to %C5casts1A*
225+
// CHECK: [[OBJ_PTR:%.*]] = bitcast %C5casts1A* [[T0]] to i8*
225226
// CHECK: [[METADATA:%.*]] = call %swift.type* @_TMaC5casts1A()
226227
// CHECK: [[METADATA_PTR:%.*]] = bitcast %swift.type* [[METADATA]] to i8*
227228
// CHECK: [[RESULT_PTR:%.*]] = call i8* @rt_swift_dynamicCastClass(i8* [[OBJ_PTR]], i8* [[METADATA_PTR]])
@@ -237,6 +238,36 @@ nay:
237238
unreachable
238239
}
239240

241+
// CHECK-LABEL: define{{( protected)?}} %swift.type* @checked_downcast_optional_metatype({{(i32|i64)}}) {{.*}} {
242+
// CHECK: [[VALUE:%.*]] = inttoptr {{(i32|i64)}} %0 to %swift.type*
243+
// CHECK: [[METADATA:%.*]] = call %swift.type* @_TMaC5casts1B()
244+
// CHECK: [[RESULT:%.*]] = call %swift.type* @swift_dynamicCastMetatype(%swift.type* [[VALUE]], %swift.type* [[METADATA]])
245+
// CHECK: [[COND:%.*]] = icmp ne %swift.type* [[RESULT]], null
246+
// CHECK: br i1 [[COND]]
247+
sil @checked_downcast_optional_metatype : $@convention(thin) (Optional<A.Type>) -> @thick B.Type {
248+
entry(%a : $Optional<A.Type>):
249+
checked_cast_br %a : $Optional<A.Type> to $@thick B.Type, yea, nay
250+
yea(%b : $@thick B.Type):
251+
return %b : $@thick B.Type
252+
nay:
253+
unreachable
254+
}
255+
256+
// CHECK-LABEL: define{{( protected)?}} %swift.type* @checked_downcast_optional_exmetatype({{(i32, i32|i64, i64)}}) {{.*}} {
257+
// CHECK: [[VALUE:%.*]] = inttoptr {{(i32|i64)}} %0 to %swift.type*
258+
// CHECK: [[METADATA:%.*]] = call %swift.type* @_TMaC5casts1B()
259+
// CHECK: [[RESULT:%.*]] = call %swift.type* @swift_dynamicCastMetatype(%swift.type* [[VALUE]], %swift.type* [[METADATA]])
260+
// CHECK: [[COND:%.*]] = icmp ne %swift.type* [[RESULT]], null
261+
// CHECK: br i1 [[COND]]
262+
sil @checked_downcast_optional_exmetatype : $@convention(thin) (Optional<CP.Type>) -> @thick B.Type {
263+
entry(%a : $Optional<CP.Type>):
264+
checked_cast_br %a : $Optional<CP.Type> to $@thick B.Type, yea, nay
265+
yea(%b : $@thick B.Type):
266+
return %b : $@thick B.Type
267+
nay:
268+
unreachable
269+
}
270+
240271
// CHECK-LABEL: define{{( protected)?}} void @checked_metatype_to_object_casts
241272
sil @checked_metatype_to_object_casts : $@convention(thin) <T> (@thick Any.Type) -> () {
242273
entry(%e : $@thick Any.Type):

0 commit comments

Comments
 (0)