Skip to content

Commit 860078a

Browse files
committed
IRGen: fast casting to final classes
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.
1 parent cf2e90b commit 860078a

File tree

4 files changed

+244
-0
lines changed

4 files changed

+244
-0
lines changed

lib/IRGen/GenCast.cpp

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "llvm/IR/Module.h"
3131

3232
#include "swift/AST/ExistentialLayout.h"
33+
#include "swift/SIL/DynamicCasts.h"
3334
#include "swift/SIL/SILInstruction.h"
3435
#include "swift/SIL/SILModule.h"
3536
#include "swift/SIL/TypeLowering.h"
@@ -1038,8 +1039,69 @@ void irgen::emitScalarCheckedCast(IRGenFunction &IGF,
10381039
return;
10391040
}
10401041

1042+
if (llvm::Value *fastResult = emitFastClassCastIfPossible(IGF, instance,
1043+
sourceFormalType, targetFormalType)) {
1044+
out.add(fastResult);
1045+
return;
1046+
}
1047+
10411048
Explosion outRes;
10421049
llvm::Value *result = emitClassDowncast(IGF, instance, targetFormalType,
10431050
mode);
10441051
out.add(result);
10451052
}
1053+
1054+
/// When casting a class instance to a final class, we can directly compare
1055+
/// the isa-pointer with address of the metadata. This avoids a call to
1056+
/// `swift_dynamicCastClass`.
1057+
/// It also avoids a call to the metadata accessor of the class (which calls
1058+
/// `swift_getInitializedObjCClass`). For comparing the metadata pointers it's
1059+
/// not required that the metadata is fully initialized.
1060+
llvm::Value *irgen::emitFastClassCastIfPossible(IRGenFunction &IGF,
1061+
llvm::Value *instance,
1062+
CanType sourceFormalType,
1063+
CanType targetFormalType) {
1064+
if (!doesCastPreserveOwnershipForTypes(IGF.IGM.getSILModule(), sourceFormalType,
1065+
targetFormalType)) {
1066+
return nullptr;
1067+
}
1068+
1069+
// This does not include generic classes.
1070+
auto classTy = dyn_cast<ClassType>(targetFormalType);
1071+
if (!classTy)
1072+
return nullptr;
1073+
1074+
// TODO: we could use the ClassHierarchyAnalysis do also handle "effectively"
1075+
// final classes, e.g. not-subclassed internal classes in WMO.
1076+
// This would need some re-architecting of ClassHierarchyAnalysis to make it
1077+
// available in IRGen.
1078+
ClassDecl *toClass = classTy->getDecl();
1079+
if (!toClass->isFinal())
1080+
return nullptr;
1081+
1082+
AncestryOptions forbidden = AncestryOptions(AncestryFlags::ObjC) |
1083+
AncestryFlags::Resilient |
1084+
AncestryFlags::ResilientOther |
1085+
AncestryFlags::ClangImported |
1086+
AncestryFlags::ObjCObjectModel;
1087+
if (toClass->checkAncestry() & forbidden)
1088+
return nullptr;
1089+
1090+
// Get the metadata pointer of the destination class type.
1091+
llvm::Value *destMetadata = IGF.IGM.getAddrOfTypeMetadata(targetFormalType);
1092+
llvm::Value *lhs = IGF.Builder.CreateBitCast(destMetadata, IGF.IGM.Int8PtrTy);
1093+
1094+
// Load the isa pointer.
1095+
llvm::Value *objMetadata = emitHeapMetadataRefForHeapObject(IGF, instance,
1096+
targetFormalType, GenericSignature(), /*suppress cast*/ true);
1097+
llvm::Value *rhs = IGF.Builder.CreateBitCast(objMetadata, IGF.IGM.Int8PtrTy);
1098+
1099+
// return isa_ptr == metadat_ptr ? instance : nullptr
1100+
llvm::Value *isEqual = IGF.Builder.CreateCmp(llvm::CmpInst::Predicate::ICMP_EQ,
1101+
lhs, rhs);
1102+
auto *instanceTy = cast<llvm::PointerType>(instance->getType());
1103+
auto *nullPtr = llvm::ConstantPointerNull::get(instanceTy);
1104+
auto *select = IGF.Builder.CreateSelect(isEqual, instance, nullPtr);
1105+
llvm::Type *destTy = IGF.getTypeInfoForUnlowered(targetFormalType).getStorageType();
1106+
return IGF.Builder.CreateBitCast(select, destTy);
1107+
}

lib/IRGen/GenCast.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ namespace irgen {
5656
GenericSignature fnSig,
5757
Explosion &out);
5858

59+
llvm::Value *emitFastClassCastIfPossible(IRGenFunction &IGF,
60+
llvm::Value *instance,
61+
CanType sourceFormalType,
62+
CanType targetFormalType);
63+
5964
/// Convert a class object to the given destination type,
6065
/// using a runtime-checked cast.
6166
llvm::Value *emitClassDowncast(IRGenFunction &IGF,

test/Casting/Inputs/classes.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
public protocol P : AnyObject { }
3+
4+
public class Base {
5+
public init() {}
6+
}
7+
8+
public class Nonfinal : Base, P {
9+
public override init() {}
10+
}
11+
12+
final public class Final : Base, P {
13+
public override init() {}
14+
}
15+
16+
open class OpenBase {
17+
public init() {}
18+
}

test/Casting/fast_class_casts.swift

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// 1. functional test:
4+
5+
// RUN: %target-build-swift -parse-as-library -wmo -emit-module -emit-module-path=%t/Classes.swiftmodule -module-name=Classes %S/Inputs/classes.swift -c -o %t/classes.o
6+
// RUN: %target-build-swift -parse-as-library -wmo -enable-library-evolution -emit-module -emit-module-path=%t/ResilientClasses.swiftmodule -module-name=ResilientClasses %S/Inputs/classes.swift -c -o %t/resilientclasses.o
7+
// RUN: %target-build-swift -wmo -module-name=Main -I%t %s -c -o %t/main.o
8+
// RUN: %target-swiftc_driver %t/main.o %t/classes.o %t/resilientclasses.o -o %t/a.out
9+
// RUN: %target-codesign %t/a.out
10+
// RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT
11+
12+
// 2. check if the generated IR looks like expected:
13+
14+
// RUN: %target-swift-frontend -module-name=Main -I%t %s -emit-ir -g -o - | %FileCheck %s
15+
16+
// REQUIRES: executable_test
17+
18+
19+
import Classes
20+
import ResilientClasses
21+
22+
final class Internal : Classes.OpenBase, Hashable {
23+
func hash(into hasher: inout Hasher) {}
24+
static func == (lhs: Internal, rhs: Internal) -> Bool { return false }
25+
}
26+
27+
final class DerivedFromResilient : ResilientClasses.OpenBase {
28+
}
29+
30+
final class Generic<T> : Classes.OpenBase {
31+
}
32+
33+
// CHECK-LABEL: define {{.*}} @"$s4Main14castToNonfinaly7Classes0D0CSgAC4BaseCF"
34+
// CHECK: @swift_dynamicCastClass
35+
// CHECK: }
36+
@inline(never)
37+
func castToNonfinal(_ b: Classes.Base) -> Classes.Nonfinal? {
38+
return b as? Classes.Nonfinal
39+
}
40+
41+
// CHECK-LABEL: define {{.*}} @"$s4Main11castToFinaly7Classes0D0CSgAC4BaseCF"
42+
// CHECK-NOT: @swift_dynamicCastClass
43+
// CHECK: }
44+
@inline(never)
45+
func castToFinal(_ b: Classes.Base) -> Classes.Final? {
46+
return b as? Classes.Final
47+
}
48+
49+
// CHECK-LABEL: define {{.*}} @"$s4Main24unconditionalCastToFinaly7Classes0E0CAC4BaseCF"
50+
// CHECK-NOT: @swift_dynamicCastClass
51+
// CHECK: }
52+
@inline(never)
53+
func unconditionalCastToFinal(_ b: Classes.Base) -> Classes.Final {
54+
return b as! Classes.Final
55+
}
56+
57+
// CHECK-LABEL: define {{.*}} @"$s4Main20castToResilientFinaly0D7Classes0E0CSgAC4BaseCF"
58+
// CHECK: @swift_dynamicCastClass
59+
// CHECK: }
60+
@inline(never)
61+
func castToResilientFinal(_ b: ResilientClasses.Base) -> ResilientClasses.Final? {
62+
return b as? ResilientClasses.Final
63+
}
64+
65+
// CHECK-LABEL: define {{.*}} @"$s4Main19castProtocolToFinaly7Classes0E0CSgAC1P_pF"
66+
// CHECK-NOT: @swift_dynamicCastClass
67+
// CHECK: }
68+
@inline(never)
69+
func castProtocolToFinal(_ p: Classes.P) -> Classes.Final? {
70+
return p as? Classes.Final
71+
}
72+
73+
// CHECK-LABEL: define {{.*}} @"$s4Main14castToInternalyAA0D0CSg7Classes8OpenBaseCF"
74+
// CHECK-NOT: @swift_dynamicCastClass
75+
// CHECK: }
76+
@inline(never)
77+
func castToInternal(_ b: Classes.OpenBase) -> Internal? {
78+
return b as? Internal
79+
}
80+
81+
// CHECK-LABEL: define {{.*}} @"$s4Main23castAnyObjectToInternalyAA0F0CSgyXlF"
82+
// CHECK: @swift_dynamicCastClass
83+
// CHECK: }
84+
@inline(never)
85+
func castAnyObjectToInternal(_ a: AnyObject) -> Internal? {
86+
return a as? Internal
87+
}
88+
89+
// CHECK-LABEL: define {{.*}} @"$s4Main26castToDerivedFromResilientyAA0deF0CSg0F7Classes8OpenBaseCF"
90+
// CHECK: @swift_dynamicCastClass
91+
// CHECK: }
92+
@inline(never)
93+
func castToDerivedFromResilient(_ b: ResilientClasses.OpenBase) -> DerivedFromResilient? {
94+
return b as? DerivedFromResilient
95+
}
96+
97+
// CHECK-LABEL: define {{.*}} @"$s4Main13castToGenericyAA0D0CySiGSg7Classes8OpenBaseCF"
98+
// CHECK: @swift_dynamicCastClass
99+
// CHECK: }
100+
@inline(never)
101+
func castToGeneric(_ b: Classes.OpenBase) -> Generic<Int>? {
102+
return b as? Generic<Int>
103+
}
104+
105+
// CHECK-LABEL: define {{.*}} @"$s4Main14getAnyHashableys0cD0VAA8InternalCF"
106+
@inline(never)
107+
func getAnyHashable(_ i: Internal) -> AnyHashable {
108+
return i
109+
}
110+
111+
func test() {
112+
// CHECK-OUTPUT: nil
113+
print(castToNonfinal(Classes.Base()) as Any)
114+
// CHECK-OUTPUT: Optional(Classes.Nonfinal)
115+
print(castToNonfinal(Classes.Nonfinal()) as Any)
116+
117+
// CHECK-OUTPUT: nil
118+
print(castToFinal(Classes.Base()) as Any)
119+
// CHECK-OUTPUT: Optional(Classes.Final)
120+
print(castToFinal(Classes.Final()) as Any)
121+
// CHECK-OUTPUT: Classes.Final
122+
print(unconditionalCastToFinal(Classes.Final()) as Any)
123+
124+
// CHECK-OUTPUT: nil
125+
print(castToResilientFinal(ResilientClasses.Base()) as Any)
126+
// CHECK-OUTPUT: Optional(ResilientClasses.Final)
127+
print(castToResilientFinal(ResilientClasses.Final()) as Any)
128+
129+
// CHECK-OUTPUT: nil
130+
print(castProtocolToFinal(Classes.Nonfinal()) as Any)
131+
// CHECK-OUTPUT: Optional(Classes.Final)
132+
print(castProtocolToFinal(Classes.Final()) as Any)
133+
134+
// CHECK-OUTPUT: nil
135+
print(castToInternal(Classes.OpenBase()) as Any)
136+
// CHECK-OUTPUT: Optional(Main.Internal)
137+
print(castToInternal(Internal()) as Any)
138+
139+
// CHECK-OUTPUT: nil
140+
print(castAnyObjectToInternal(Classes.OpenBase()) as Any)
141+
// CHECK-OUTPUT: Optional(Main.Internal)
142+
let i = Internal()
143+
print(castAnyObjectToInternal(i) as Any)
144+
let i2 = castAnyObjectToInternal(getAnyHashable(i) as! AnyObject)
145+
precondition(i === i2)
146+
147+
// CHECK-OUTPUT: nil
148+
print(castToDerivedFromResilient(ResilientClasses.OpenBase()) as Any)
149+
// CHECK-OUTPUT: Optional(Main.DerivedFromResilient)
150+
print(castToDerivedFromResilient(DerivedFromResilient()) as Any)
151+
152+
// CHECK-OUTPUT: nil
153+
print(castToGeneric(Classes.OpenBase()) as Any)
154+
// CHECK-OUTPUT: Optional(Main.Generic<Swift.Int>)
155+
print(castToGeneric(Generic<Int>()) as Any)
156+
}
157+
158+
test()
159+

0 commit comments

Comments
 (0)