Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhaul swift_dynamicCast #29658

Closed
wants to merge 10 commits into from
52 changes: 40 additions & 12 deletions lib/IRGen/GenCast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -478,13 +478,8 @@ llvm::Value *irgen::emitMetatypeToAnyObjectDowncast(IRGenFunction &IGF,
CheckedCastMode mode) {
// If ObjC interop is enabled, casting a metatype to AnyObject succeeds
// if the metatype is for a class.

auto triviallyFail = [&]() -> llvm::Value* {
return llvm::ConstantPointerNull::get(IGF.IGM.ObjCPtrTy);
};

if (!IGF.IGM.ObjCInterop)
return triviallyFail();
return nullptr;

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

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

// Is the type obviously not a class?
if (!isa<ArchetypeType>(instanceTy)
&& !isa<ExistentialMetatypeType>(type))
return triviallyFail();
// 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.
llvm::Constant *castFn;
Expand Down Expand Up @@ -966,10 +961,43 @@ void irgen::emitScalarCheckedCast(IRGenFunction &IGF,
// Otherwise, this is a metatype-to-object cast.
assert(targetLoweredType.isAnyClassReferenceType());

// Convert the metatype value to AnyObject.
// 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);
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);

Expand Down
28 changes: 16 additions & 12 deletions lib/SIL/Utils/DynamicCasts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -408,20 +408,24 @@ swift::classifyDynamicCast(ModuleDecl *M,
sourceMetatype.isAnyExistentialType())
return DynamicCastFeasibility::WillSucceed;

// If the source and target are the same existential type, but the source is
// P.Protocol and the dest is P.Type, then we need to consider whether the
// protocol is self-conforming.
// The only cases where a protocol self-conforms are objc protocols, but
// we're going to expect P.Type to hold a class object. And this case
// doesn't matter since for a self-conforming protocol type there can't be
// any type-level methods.
// Thus we consider this kind of cast to always fail. The only exception
// from this rule is when the target is Any.Type, because *.Protocol
// can always be casted to Any.Type.
// All metatypes are instances of Any.Type
if (isa<MetatypeType>(sourceMetatype) && target->isAny()) {
return DynamicCastFeasibility::WillSucceed;
}

// If the source is a protocol metatype (P.Protocol) and the target is a
// protocol existential metatype (Q.Type), then we can ask the Type Checker
// whether the cast will succeed or fail. In particular, if P == Q, then
// the Type Checker knows whether the protocol is self-conforming.
// (Note the call to classifyDynamicCastToProtocol below currently
// mis-handles such cases, so we need to check this first.)
if (source->isAnyExistentialType() && isa<MetatypeType>(sourceMetatype) &&
isa<ExistentialMetatypeType>(targetMetatype)) {
return target->isAny() ? DynamicCastFeasibility::WillSucceed
: DynamicCastFeasibility::WillFail;
auto targetProtocol = dyn_cast<ProtocolType>(target);
if (targetProtocol && M->conformsToProtocol(source, targetProtocol->getDecl())) {
return DynamicCastFeasibility::WillSucceed;
}
return DynamicCastFeasibility::WillFail;
}

if (targetMetatype.isAnyExistentialType() &&
Expand Down
13 changes: 9 additions & 4 deletions stdlib/public/runtime/DynamicCast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1625,12 +1625,17 @@ _dynamicCastMetatypeToExistentialMetatype(
const Metadata *&destFailureType, const Metadata *&srcFailureType,
bool takeOnSuccess, bool mayDeferChecks)
{
// The instance type of an existential metatype must be either an
// existential or an existential metatype.
// We have a metatype "Source.self" and we want to cast it to an existential
// metatype "Dest.Type"

// The instance type of an existential metatype must be either an existential
// (P.Type's instance type is the existential P) or an existential metatype
// (P.Type.Type's instance type is the existential metatype P.Type).
auto destMetatype
= reinterpret_cast<ExistentialMetatypeContainer *>(destLocation);

// If it's an existential, we need to check for conformances.
// Casting Source.self to P.Type for some protocol P just requires testing
// whether Source conforms to P.
auto targetInstanceType = destType->InstanceType;
if (auto targetInstanceTypeAsExistential =
dyn_cast<ExistentialTypeMetadata>(targetInstanceType)) {
Expand All @@ -1649,7 +1654,7 @@ _dynamicCastMetatypeToExistentialMetatype(
return DynamicCastResult::SuccessViaCopy;
}

// Otherwise, we're casting to SomeProtocol.Type.Type.
// Otherwise, we're casting to P.Type(.Type)+
auto targetInstanceTypeAsMetatype =
cast<ExistentialMetatypeMetadata>(targetInstanceType);

Expand Down
2 changes: 2 additions & 0 deletions test/AutoDiff/SILOptimizer/activity_analysis.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,12 @@ func TF_954(_ x: Float) -> Float {

@differentiable
func checked_cast_branch(_ x: Float) -> Float {
// expected-note @+2 {{condition always evaluates to true}}
// expected-warning @+1 {{'is' test is always true}}
if Int.self is Any.Type {
return x + x
}
// expected-warning @+1 {{will never be executed}}
return x * x
}

Expand Down
6 changes: 3 additions & 3 deletions test/IRGen/casts.sil
Original file line number Diff line number Diff line change
Expand Up @@ -298,9 +298,9 @@ bb3(%9 : $Optional<CP>):
// CHECK-LABEL: define{{( dllexport)?}}{{( protected)?}} swiftcc void @checked_metatype_to_object_casts
sil @checked_metatype_to_object_casts : $@convention(thin) <T> (@thick Any.Type) -> () {
entry(%e : $@thick Any.Type):
%a = metatype $@thin NotClass.Type
// CHECK: br i1 false
checked_cast_br %a : $@thin NotClass.Type to AnyObject, a_yea, a_nay
%a = metatype $@thick NotClass.Type
// CHECK: call i1 @swift_dynamicCast({{.*}})
checked_cast_br %a : $@thick NotClass.Type to AnyObject, a_yea, a_nay
a_yea(%1 : $AnyObject):
%b = metatype $@thick A.Type
// CHECK: bitcast %swift.type* {{%.*}} to %objc_object*
Expand Down
131 changes: 112 additions & 19 deletions test/Interpreter/subclass_existentials_objc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,30 @@
//===----------------------------------------------------------------------===//
//
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -o %t/a.out
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out
//
// RUN: %target-build-swift %s -g -Onone -swift-version 5 -o %t/a.swift5.Onone.out
// RUN: %target-codesign %t/a.swift5.Onone.out
// RUN: %target-run %t/a.swift5.Onone.out
//
// RUN: %target-build-swift %s -g -O -swift-version 5 -o %t/a.swift5.O.out
// RUN: %target-codesign %t/a.swift5.O.out
// RUN: %target-run %t/a.swift5.O.out
//
// RUN: %target-build-swift %s -g -O -swift-version 4 -o %t/a.swift4.O.out
// RUN: %target-codesign %t/a.swift4.O.out
// RUN: %target-run %t/a.swift4.O.out
//
// RUN: %target-build-swift %s -g -Onone -swift-version 4 -o %t/a.swift4.Onone.out
// RUN: %target-codesign %t/a.swift4.Onone.out
// RUN: %target-run %t/a.swift4.Onone.out
//
// REQUIRES: executable_test
// REQUIRES: objc_interop
//

import StdlibUnittest
import Foundation

// FIXME: Actually write proper tests here.

func cast<T, U>(_ t: T, to: U.Type) -> U? {
return t as? U
}
Expand All @@ -36,23 +48,104 @@ class D : C, OP {}

var SubclassExistentialsTestSuite = TestSuite("SubclassExistentials")

SubclassExistentialsTestSuite.test("Metatype self-conformance") {
expectFalse(CP.self is AnyObject.Type)
SubclassExistentialsTestSuite.test("test1(): Existential basics") {
// Making the test body a named function allows you to set a breakpoint more easily:
// (lldb) break set -n test1
test1()
}

func test1() {
// An OP instance can be cast to OP or AnyObject as expected
let op : OP = D()
expectTrue(op is OP)
expectNotNil(cast(op, to: OP.self))
expectTrue(op is D)
expectNotNil(cast(op, to: D.self))
expectTrue(op is AnyObject)
expectNotNil(cast(op, to: AnyObject.self))
expectTrue(D.self is AnyObject.Type)
expectNotNil(cast(D.self, to: AnyObject.Type.self))
}

SubclassExistentialsTestSuite.test("test2(): ObjC Metatype self-conformance") {
test2()
}

func test2() {
// Obj-C protocols self-conform if they have no static requirements
expectTrue(OP.self is OP.Type) // Self-conformance

// The following should be the same as above, but with a runtime cast instead of compile-time
// It fails because the runtime cannot find the necessary protocol conformance
// Bug: Compiler isn't emitting these conformances?
// In optimized build, this asserts because the casting oracle knows that it succeeds,
// so the optimizer converts it into an unconditional cast.
//expectFailure { expectNotNil(cast(OP.self, to: OP.Type.self)) }

// The following cast should succeed, because:
// 1. OP.self is OP.Type due to self-conformance above
// 2. OP is a sub-type of AnyObject (because OP is implicitly class-constrained)
// 3. OP.Type is a sub-type of AnyObject.Type
// 4. So OP.self is AnyObject.Type
expectFailure { expectTrue(OP.self is AnyObject.Type) }
expectFailure { expectNotNil(cast(OP.self, to: AnyObject.Type.self)) } // Ditto
// Casting to AnyObject doesn't change the representation, hence this equality
expectEqual(OP.self, OP.self as AnyObject.Type)
}

SubclassExistentialsTestSuite.test("test3(): Non-ObjC metatypes do not self-conform") {
test3()
}

func test3() {
// Non-ObjC protocols do not generally self-conform
expectFalse(CP.self is CP.Type) // No self-conformance
expectNil(cast(CP.self, to: CP.Type.self)) // Ditto
expectFalse(CP.self is AnyObject.Type)
expectNil(cast(CP.self, to: AnyObject.Type.self))
// FIXME
expectNil(cast(OP.self, to: AnyObject.Type.self))

// FIXME: Sema says 'always true', runtime says false.
//
// Runtime code had a FIXME in it already, so I think Sema is right.
expectFalse(OP.self is AnyObject.Type)
expectFalse((C & OP).self is AnyObject.Type)
expectFalse((C & OP).self is OP.Type)

expectTrue(D.self is (C & OP).Type)
expectFalse(OP.self is (C & OP).Type)
}

SubclassExistentialsTestSuite.test("test4(): (C & OP) acts like an Obj-C protocol") {
test4()
}

func test4() {
// (C & OP) acts like an Obj-C protocol (because OP is)
expectFailure { expectTrue((C & OP).self is (C & OP).Type) } // Self-conforms
expectFailure { expectNotNil(cast((C & OP).self, to: (C & OP).Type.self)) } // Ditto

// Casting oracle knows that the next cast must always succeed
// (This is why there's a "warning: 'is' test is always true")
expectTrue((C & OP).self is OP.Type) // (C & OP) is sub-protocol of OP

// The following cast is the same as the one above, except in a form that
// forces the compiler to use the runtime machinery. Because the oracle
// knows it will succeed, the optimizer legitimately converts it into an
// unconditional runtime cast. Which in turn leads to an assert when it fails at runtime.
// Bug: I suspect this is the same bug as above -- the compiler isn't emitting
// the protocol conformance information that the runtime requires to verify this cast.
//expectFailure { expectNotNil(cast((C & OP).self, to: OP.Type.self)) } // Ditto

expectFailure { expectTrue((C & OP).self is AnyObject.Type) } // (C & OP) is a sub-protocol of AnyObject
expectFailure { expectNotNil(cast((C & OP).self, to: AnyObject.Type.self)) } // Ditto
}

SubclassExistentialsTestSuite.test("test5(): (C & OP) is a sub-protocol of OP") {
test5()
}

func test5() {
expectTrue((C & OP).self is (C & OP).Protocol) // By definition
expectNotNil(cast((C & OP).self, to: (C & OP).Protocol.self)) // Ditto
expectFalse(D.self is (C & OP).Protocol) // (C & OP).self is only instance of (C&OP).Protocol
expectNil(cast(D.self, to: (C & OP).Protocol.self)) // Ditto
expectTrue(D.self is (C & OP).Type) // True, since D conforms to (C & OP)
expectFalse(OP.self is (C & OP).Protocol) // (C & OP).self is only instance of (C&OP).Protocol
expectNil(cast(OP.self, to: (C & OP).Protocol.self)) // Ditto
expectFalse(OP.self is (C & OP).Type) // True

expectFalse((C & OP).self is OP.Protocol) // OP.self is only instance of OP.Protocol
expectNil(cast((C & OP).self, to: OP.Protocol.self)) // ditto
}

runAllTests()
10 changes: 6 additions & 4 deletions test/SILOptimizer/cast_folding_objc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -207,14 +207,15 @@ public protocol P {

// Any casts from P.Protocol to P.Type should fail.
@inline(never)
public func testCastPProtocolToPType() -> ObjCP.Type? {
return cast(ObjCP.self)
public func testCastPProtocolToPType() -> P.Type? {
return cast(P.self)
}

@objc
public protocol ObjCP {
}

// ObjC protocols always self-conform
@inline(never)
public func testCastObjCPProtocolToObjCPType() -> ObjCP.Type? {
return cast(ObjCP.self)
Expand Down Expand Up @@ -280,8 +281,9 @@ print("test0=\(test0())")
// CHECK-NEXT: return %0

// CHECK-LABEL: sil [noinline] @{{.*}}testCastObjCPProtocolTo{{.*}}PType
// CHECK: %0 = enum $Optional{{.*}}, #Optional.none!enumelt
// CHECK-NEXT: return %0
// CHECK: %2 = unconditional_checked_cast %0 : $@thick ObjCP.Protocol to ObjCP.Type
// CHECK: %3 = enum $Optional<@thick ObjCP.Type>, #Optional.some!enumelt, %2 : $@thick ObjCP.Type
// CHECK-NEXT: return %3

// CHECK-LABEL: sil [noinline] @{{.*}}testCastProtocolComposition{{.*}}Type
// CHECK: %0 = enum $Optional{{.*}}, #Optional.none!enumelt
Expand Down
17 changes: 17 additions & 0 deletions test/stdlib/DebuggerSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,18 @@ class ClassWithMembers {
}

class ClassWithMirror: CustomReflectable {
var a = 1
var customMirror: Mirror {
return Mirror(self, children: ["a" : 1, "b" : "Hello World"])
}
}

class SubClassWithMirror: ClassWithMirror {
override var customMirror: Mirror {
return Mirror(self, children: ["z" : 99])
}
}

#if _runtime(_ObjC)
struct DontBridgeThisStruct {
var message = "Hello World"
Expand Down Expand Up @@ -72,6 +79,16 @@ StringForPrintObjectTests.test("ClassWithMirror") {
expectEqual(printed, "▿ ClassWithMirror\n - a : 1\n - b : \"Hello World\"\n")
}

StringForPrintObjectTests.test("SubClassWithMirror") {
let printed = _stringForPrintObject(SubClassWithMirror())
let expected =
"▿ SubClassWithMirror\n" +
" ▿ super : ClassWithMirror\n" +
" - a : 1\n" +
" - z : 99\n";
expectEqual(expected, printed);
}

StringForPrintObjectTests.test("Array") {
let printed = _stringForPrintObject([1,2,3,4])
expectEqual(printed, "▿ 4 elements\n - 0 : 1\n - 1 : 2\n - 2 : 3\n - 3 : 4\n")
Expand Down
Loading