diff --git a/lib/SILPasses/Utils/Devirtualize.cpp b/lib/SILPasses/Utils/Devirtualize.cpp index fb5cbbe2c50..80279786014 100644 --- a/lib/SILPasses/Utils/Devirtualize.cpp +++ b/lib/SILPasses/Utils/Devirtualize.cpp @@ -311,6 +311,50 @@ static SILValue conditionallyCastAddr(SILBuilderWithScope<16> &B, return B.createUncheckedRefCast(Loc, Arg, ParamTy); } +/// Insert instructions to cast the tuple return type into appropiate +/// tuple type expected by the original apply_inst. +static SILInstruction *castTupleReturnType(ApplyInst *AI, ApplyInst *NewAI, + CanTypeWrapper ResultTupleTy, SILFunction *F, SILBuilder& B) { + auto AITupleTy = cast(AI->getType().getSwiftRValueType()); + SmallVector TupleElements; + auto TupleElementTypes = ResultTupleTy.getElementTypes(); + unsigned NumElements = ResultTupleTy->getElements().size(); + for (unsigned i = 0; i < NumElements; ++i) { + auto EltTy = TupleElementTypes[i]; + auto ExtractedElt = B.createTupleExtract(AI->getLoc(), NewAI, i); + OptionalTypeKind OTK; + auto OptionalEltTy = + EltTy.getCanonicalTypeOrNull()->getAnyOptionalObjectType(OTK); + if (!OptionalEltTy + || !isa(OptionalEltTy.getCanonicalTypeOrNull())) { + // No need to convert this parameter + TupleElements.push_back(ExtractedElt); + continue; + } + + // Dereference the optional value + auto *SomeDecl = B.getASTContext().getOptionalSomeDecl(OTK); + auto FuncPtr = B.createUncheckedEnumData(AI->getLoc(), ExtractedElt, + SomeDecl); + + auto AIOptionalEltTy = + AITupleTy.getElementType(i).getCanonicalTypeOrNull()->getAnyOptionalObjectType(); + auto SILAIOptionalEltTy = AI->getModule().Types.getLoweredType( + AIOptionalEltTy); + auto ConvertedFuncPtr = B.createConvertFunction(AI->getLoc(), FuncPtr, + SILAIOptionalEltTy); + + TupleElements.push_back( + B.createOptionalSome(AI->getLoc(), ConvertedFuncPtr, OTK, + SILType::getPrimitiveObjectType(AITupleTy.getElementType(i)))); + } + + // Now create a new tuple + DEBUG(llvm::dbgs() << " SUCCESS: " << F->getName() << "\n"); + NumClassDevirt++; + return B.createTuple(AI->getLoc(), AI->getType(), TupleElements); +} + /// \brief Devirtualize an apply of a class method. /// /// \p AI is the apply to devirtualize. @@ -380,6 +424,20 @@ SILInstruction *swift::devirtualizeClassMethod(ApplyInst *AI, // If our return type differs from AI's return type, then we know that we have // a covariant return type. Cast it before we RAUW. This can not happen + // Accessors could return a tuple where one of the elements is of a function + // type, which may refer to a subclass instead of a superclass in its + // signature. + // These function types are ABI-compatible between the subclass and a + // superclass. To make the SIL type system happy, insert a conversion between + // the function types and reconstruct the tuple. + if (auto *FD = dyn_cast(CMI->getMember().getDecl())) { + if (FD->isAccessor()) { + if (auto ResultTupleTy = dyn_cast(ReturnType.getSwiftRValueType())) + return castTupleReturnType(AI, NewAI, ResultTupleTy, F, B); + } + } + + // Check if the return type is an optional of the apply_inst type // or the other way around bool UnwrapOptionalResult = false; diff --git a/test/SILPasses/devirt_materializeForSet.swift b/test/SILPasses/devirt_materializeForSet.swift new file mode 100644 index 00000000000..7282be52283 --- /dev/null +++ b/test/SILPasses/devirt_materializeForSet.swift @@ -0,0 +1,51 @@ +// RUN: %target-swift-frontend -O -emit-sil %s | FileCheck %s + +// Check that compiler does not crash on the devirtualization of materializeForSet methods +// and produces a correct code. + +// CHECK-LABEL: sil [transparent] [thunk] @_TTWC24devirt_materializeForSet7BaseFooS_3FooS_FS1_m3barSS +// CHECK: checked_cast_br [exact] %{{.*}} : $BaseFoo to $ChildFoo +// CHECK: convert_function %{{.*}} : $@convention(thin) (Builtin.RawPointer, @inout Builtin.UnsafeValueBuffer, @inout ChildFoo, @thick ChildFoo.Type) -> () to $@convention(thin) (Builtin.RawPointer, @inout Builtin.UnsafeValueBuffer, @inout BaseFoo, @thick BaseFoo.Type) -> () +// CHECK: enum $Optional<@convention(thin) (Builtin.RawPointer, inout Builtin.UnsafeValueBuffer, inout BaseFoo, @thick BaseFoo.Type) -> ()>, #Optional.Some!enumelt.1, %{{.*}} : $@convention(thin) (Builtin.RawPointer, @inout Builtin.UnsafeValueBuffer, @inout BaseFoo, @thick BaseFoo.Type) -> () +// CHECK: tuple (%{{.*}} : $Builtin.RawPointer, %{{.*}} : $Optional<@convention(thin) (Builtin.RawPointer, inout Builtin.UnsafeValueBuffer, inout BaseFoo, @thick BaseFoo.Type) -> ()>) + +public protocol Foo { + var bar: String { get set } +} + +public class BaseFoo: Foo { + public var bar: String = "hello" +} + +public class ChildFoo: BaseFoo { + private var _bar: String = "world" + + override public var bar: String { + get { + return _bar + } + set { + _bar = newValue + } + } +} + + +@inline(never) +public func test1(bf: BaseFoo) { + bf.bar = "test1" + print(bf.bar) +} + +@inline(never) +public func test2(var f: Foo) { + f.bar = "test2" + print(f.bar) +} + + +//test1(BaseFoo()) +//test1(ChildFoo()) + +//test2(BaseFoo()) +//test2(ChildFoo()) \ No newline at end of file