Skip to content

Commit b0675c0

Browse files
authored
[DynamicCast] Rely on runtime when casts can't be optimized (#33761)
* [DynamicCast] Rely on runtime when casts can't be optimized The Swift compiler renders `as?` operations in Swift into variations of the `checked_cast_br` instructions in SIL. Subsequent optimization passes may alter or eliminate these instructions. Any remaining instructions after optimization are translated by IRGen into runtime calls. At least, that's the theory. Unfortunately, the current IRGen logic does not recognize all of the casting instruction variants and renders one particular unrecognized variant as a simple nil load. Specifically, this occurs for optimized casts of metatypes to AnyObject: ``` let a = Int.self let b = a as? AnyObject // b should not be nil here ``` This PR changes this case in IRGen to instead generate a call to `swift_dynamicCast`, deferring this case to the runtime. Future: Someday, the compiler should be taught to correctly optimize this cast away.
1 parent e8bc136 commit b0675c0

File tree

4 files changed

+61
-27
lines changed

4 files changed

+61
-27
lines changed

lib/IRGen/GenCast.cpp

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -478,13 +478,8 @@ llvm::Value *irgen::emitMetatypeToAnyObjectDowncast(IRGenFunction &IGF,
478478
CheckedCastMode mode) {
479479
// If ObjC interop is enabled, casting a metatype to AnyObject succeeds
480480
// if the metatype is for a class.
481-
482-
auto triviallyFail = [&]() -> llvm::Value* {
483-
return llvm::ConstantPointerNull::get(IGF.IGM.ObjCPtrTy);
484-
};
485-
486481
if (!IGF.IGM.ObjCInterop)
487-
return triviallyFail();
482+
return nullptr;
488483

489484
switch (type->getRepresentation()) {
490485
case MetatypeRepresentation::ObjC:
@@ -496,7 +491,7 @@ llvm::Value *irgen::emitMetatypeToAnyObjectDowncast(IRGenFunction &IGF,
496491
// TODO: Final class metatypes could in principle be thin.
497492
assert(!type.getInstanceType()->mayHaveSuperclass()
498493
&& "classes should not have thin metatypes (yet)");
499-
return triviallyFail();
494+
return nullptr;
500495

501496
case MetatypeRepresentation::Thick: {
502497
auto instanceTy = type.getInstanceType();
@@ -508,10 +503,10 @@ llvm::Value *irgen::emitMetatypeToAnyObjectDowncast(IRGenFunction &IGF,
508503
return IGF.Builder.CreateBitCast(heapMetadata, IGF.IGM.ObjCPtrTy);
509504
}
510505

511-
// Is the type obviously not a class?
512-
if (!isa<ArchetypeType>(instanceTy)
513-
&& !isa<ExistentialMetatypeType>(type))
514-
return triviallyFail();
506+
// If it's not a class, we can't handle it here
507+
if (!isa<ArchetypeType>(instanceTy) && !isa<ExistentialMetatypeType>(type)) {
508+
return nullptr;
509+
}
515510

516511
// Ask the runtime whether this is class metadata.
517512
llvm::Constant *castFn;
@@ -966,10 +961,43 @@ void irgen::emitScalarCheckedCast(IRGenFunction &IGF,
966961
// Otherwise, this is a metatype-to-object cast.
967962
assert(targetLoweredType.isAnyClassReferenceType());
968963

969-
// Convert the metatype value to AnyObject.
964+
// Can we convert the metatype value to AnyObject using Obj-C machinery?
970965
llvm::Value *object =
971966
emitMetatypeToAnyObjectDowncast(IGF, metatypeVal, sourceMetatype, mode);
972967

968+
if (object == nullptr) {
969+
// Obj-C cast routine failed, use swift_dynamicCast instead
970+
971+
if (sourceMetatype->getRepresentation() == MetatypeRepresentation::Thin
972+
|| metatypeVal == nullptr) {
973+
// Earlier stages *should* never generate a checked cast with a thin metatype argument.
974+
// TODO: Move this assertion up to apply to all checked cast operations.
975+
// In assert builds, enforce this by failing here:
976+
assert(false && "Invalid SIL: General checked_cast_br cannot have thin argument");
977+
// In non-assert builds, stay compatible with previous behavior by emitting a null load.
978+
object = llvm::ConstantPointerNull::get(IGF.IGM.ObjCPtrTy);
979+
} else {
980+
Address src = IGF.createAlloca(metatypeVal->getType(),
981+
IGF.IGM.getPointerAlignment(),
982+
"castSrc");
983+
IGF.Builder.CreateStore(metatypeVal, src);
984+
llvm::PointerType *destPtrType = IGF.IGM.getStoragePointerType(targetLoweredType);
985+
Address dest = IGF.createAlloca(destPtrType,
986+
IGF.IGM.getPointerAlignment(),
987+
"castDest");
988+
IGF.Builder.CreateStore(llvm::ConstantPointerNull::get(destPtrType), dest);
989+
llvm::Value *success = emitCheckedCast(IGF,
990+
src, sourceFormalType,
991+
dest, targetFormalType,
992+
CastConsumptionKind::TakeAlways,
993+
mode);
994+
llvm::Value *successResult = IGF.Builder.CreateLoad(dest);
995+
llvm::Value *failureResult = llvm::ConstantPointerNull::get(destPtrType);
996+
llvm::Value *result = IGF.Builder.CreateSelect(success, successResult, failureResult);
997+
object = std::move(result);
998+
}
999+
}
1000+
9731001
sourceFormalType = IGF.IGM.Context.getAnyObjectType();
9741002
sourceLoweredType = SILType::getPrimitiveObjectType(sourceFormalType);
9751003

test/Casting/Casts.swift

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -522,12 +522,7 @@ class ClassInt: Equatable, Hashable {
522522
static func == (lhs: ClassInt, rhs: ClassInt) -> Bool {return true}
523523
func hash(into hasher: inout Hasher) {}
524524
}
525-
CastsTests.test("AnyHashable(Class) -> Obj-C -> Class")
526-
.skip(.custom({
527-
!_isDebugAssertConfiguration()
528-
},
529-
reason: "Cast optimizer breaks this test"))
530-
.code {
525+
CastsTests.test("AnyHashable(Class) -> Obj-C -> Class") {
531526
let a = ClassInt()
532527
let b = runtimeCast(a, to: AnyHashable.self)!
533528
let c = _bridgeAnythingToObjectiveC(b)

test/IRGen/casts.sil

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,9 @@ bb3(%9 : $Optional<CP>):
298298
// CHECK-LABEL: define{{( dllexport)?}}{{( protected)?}} swiftcc void @checked_metatype_to_object_casts
299299
sil @checked_metatype_to_object_casts : $@convention(thin) <T> (@thick Any.Type) -> () {
300300
entry(%e : $@thick Any.Type):
301-
%a = metatype $@thin NotClass.Type
302-
// CHECK: br i1 false
303-
checked_cast_br %a : $@thin NotClass.Type to AnyObject, a_yea, a_nay
301+
%a = metatype $@thick NotClass.Type
302+
// CHECK: call i1 @swift_dynamicCast({{.*}})
303+
checked_cast_br %a : $@thick NotClass.Type to AnyObject, a_yea, a_nay
304304
a_yea(%1 : $AnyObject):
305305
%b = metatype $@thick A.Type
306306
// CHECK: bitcast %swift.type* {{%.*}} to %objc_object*

validation-test/Casting/BoxingCasts.swift.gyb

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,27 @@
1616
// RUN: %empty-directory(%t)
1717
//
1818
// RUN: %gyb %s -o %t/BoxingCasts.swift
19-
// RUN: %line-directive %t/BoxingCasts.swift -- %target-build-swift -g -module-name a -swift-version 5 -Onone %t/BoxingCasts.swift -o %t/a.swift5.Onone.out
19+
//
20+
// RUN: %line-directive %t/BoxingCasts.swift -- %target-build-swift -g -module-name a -Onone -swift-version 5 %t/BoxingCasts.swift -o %t/a.swift5.Onone.out
2021
// RUN: %target-codesign %t/a.swift5.Onone.out
2122
// RUN: %line-directive %t/BoxingCasts.swift -- %target-run %t/a.swift5.Onone.out
2223
//
23-
// Note: The RUN directives above override the default test optimizations.
24-
// This test is deliberately run non-optimized in order to verify the
25-
// behavior of runtime methods that may not be called for optimized casts.
24+
// RUN: %line-directive %t/BoxingCasts.swift -- %target-build-swift -g -O -module-name a -O -swift-version 5 %t/BoxingCasts.swift -o %t/a.swift5.O.out
25+
// RUN: %target-codesign %t/a.swift5.O.out
26+
// RUN: %line-directive %t/BoxingCasts.swift -- %target-run %t/a.swift5.O.out
27+
//
28+
// RUN: %line-directive %t/BoxingCasts.swift -- %target-build-swift -g -module-name a -Onone -swift-version 4 %t/BoxingCasts.swift -o %t/a.swift4.Onone.out
29+
// RUN: %target-codesign %t/a.swift4.Onone.out
30+
// RUN: %line-directive %t/BoxingCasts.swift -- %target-run %t/a.swift4.Onone.out
2631
//
27-
// XXX FIXME XXX TODO XXX _Also_ build this with optimizations in order to
28-
// verify compiler behaviors.
32+
// RUN: %line-directive %t/BoxingCasts.swift -- %target-build-swift -g -O -module-name a -O -swift-version 4 %t/BoxingCasts.swift -o %t/a.swift4.O.out
33+
// RUN: %target-codesign %t/a.swift4.O.out
34+
// RUN: %line-directive %t/BoxingCasts.swift -- %target-run %t/a.swift4.O.out
35+
//
36+
// Note: The RUN directives above override the default test optimizations.
37+
// This test is deliberately run both ways:
38+
// * optimized to verify compiler cast optimizations, and
39+
// * non-optimized to verify the runtime methods used for non-optimized casts.
2940
//
3041
// REQUIRES: executable_test
3142

0 commit comments

Comments
 (0)