Skip to content

Commit 027d8a7

Browse files
authored
Merge pull request #5306 from swiftix/fixes-for-witness-method-devirt
Fixes for witness method devirtualization
2 parents 781ee82 + 1e6edcb commit 027d8a7

File tree

4 files changed

+156
-4
lines changed

4 files changed

+156
-4
lines changed

include/swift/SILOptimizer/Utils/Devirtualize.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ DevirtualizationResult devirtualizeClassMethod(FullApplySite AI,
5555
DevirtualizationResult tryDevirtualizeClassMethod(FullApplySite AI,
5656
SILValue ClassInstance);
5757
DevirtualizationResult tryDevirtualizeWitnessMethod(ApplySite AI);
58+
/// Check if an upcast is legal.
59+
bool isLegalUpcast(SILType FromTy, SILType ToTy);
5860
}
5961

6062
#endif

lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -871,12 +871,17 @@ SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite AI,
871871
return nullptr;
872872
}
873873

874-
// Obtain the protocol whose which should be used by the conformance.
874+
// The lookup type is not a opened existential type,
875+
// thus it cannot be made more concrete.
876+
if (!WMI->getLookupType()->isOpenedExistential())
877+
return nullptr;
878+
879+
// Obtain the protocol which should be used by the conformance.
875880
auto *PD = WMI->getLookupProtocol();
876881

877882
// Propagate the concrete type into a callee-operand, which is a
878883
// witness_method instruction.
879-
auto PropagateIntoOperand = [this, &WMI](CanType ConcreteType,
884+
auto PropagateIntoOperand = [this, &WMI, &AI](CanType ConcreteType,
880885
ProtocolConformanceRef Conformance) {
881886
// Keep around the dependence on the open instruction unless we've
882887
// actually eliminated the use.
@@ -885,8 +890,15 @@ SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite AI,
885890
Conformance, WMI->getMember(),
886891
WMI->getType(),
887892
WMI->isVolatile());
888-
replaceInstUsesWith(*WMI, NewWMI);
889-
eraseInstFromFunction(*WMI);
893+
// Replace only uses of the witness_method in the apply that is going to
894+
// be changed.
895+
MutableArrayRef<Operand> Operands = AI.getInstruction()->getAllOperands();
896+
for (auto &Op : Operands) {
897+
if (Op.get() == WMI)
898+
Op.set(NewWMI);
899+
}
900+
if (WMI->use_empty())
901+
eraseInstFromFunction(*WMI);
890902
};
891903

892904
// Try to perform the propagation.

lib/SILOptimizer/Utils/Devirtualize.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,73 @@ static void getWitnessMethodSubstitutions(ApplySite AI, SILFunction *F,
866866
origSubs, NewSubs);
867867
}
868868

869+
/// Check if an upcast is legal.
870+
/// THe logic in this function is heavily based on the checks in
871+
/// the SILVerifier.
872+
bool swift::isLegalUpcast(SILType FromTy, SILType ToTy) {
873+
if (ToTy.is<MetatypeType>()) {
874+
CanType InstTy(ToTy.castTo<MetatypeType>()->getInstanceType());
875+
if (!FromTy.is<MetatypeType>())
876+
return false;
877+
CanType OpInstTy(FromTy.castTo<MetatypeType>()->getInstanceType());
878+
auto InstClass = InstTy->getClassOrBoundGenericClass();
879+
if (!InstClass)
880+
return false;
881+
882+
bool CanBeUpcasted =
883+
InstClass->usesObjCGenericsModel()
884+
? InstClass->getDeclaredTypeInContext()->isBindableToSuperclassOf(
885+
OpInstTy, nullptr)
886+
: InstTy->isExactSuperclassOf(OpInstTy, nullptr);
887+
888+
return CanBeUpcasted;
889+
}
890+
891+
// Upcast from Optional<B> to Optional<A> is legal as long as B is a
892+
// subclass of A.
893+
if (ToTy.getSwiftRValueType().getAnyOptionalObjectType() &&
894+
FromTy.getSwiftRValueType().getAnyOptionalObjectType()) {
895+
ToTy = SILType::getPrimitiveObjectType(
896+
ToTy.getSwiftRValueType().getAnyOptionalObjectType());
897+
FromTy = SILType::getPrimitiveObjectType(
898+
FromTy.getSwiftRValueType().getAnyOptionalObjectType());
899+
}
900+
901+
auto ToClass = ToTy.getClassOrBoundGenericClass();
902+
if (!ToClass)
903+
return false;
904+
bool CanBeUpcasted =
905+
ToClass->usesObjCGenericsModel()
906+
? ToClass->getDeclaredTypeInContext()->isBindableToSuperclassOf(
907+
FromTy.getSwiftRValueType(), nullptr)
908+
: ToTy.isExactSuperclassOf(FromTy);
909+
910+
return CanBeUpcasted;
911+
}
912+
913+
/// Check if we can pass/convert all arguments of the original apply
914+
/// as required by the found devirtualized method.
915+
static bool
916+
canPassOrConvertAllArguments(ApplySite AI,
917+
CanSILFunctionType SubstCalleeCanType) {
918+
for (unsigned ArgN = 0, ArgE = AI.getNumArguments(); ArgN != ArgE; ++ArgN) {
919+
SILValue A = AI.getArgument(ArgN);
920+
auto ParamType = SubstCalleeCanType->getSILArgumentType(
921+
SubstCalleeCanType->getNumSILArguments() - AI.getNumArguments() + ArgN);
922+
// Check if we can cast the provided argument into the required
923+
// parameter type.
924+
auto FromTy = A->getType();
925+
auto ToTy = ParamType;
926+
// If types are the same, no conversion will be required.
927+
if (FromTy == ToTy)
928+
continue;
929+
// Otherwise, it should be possible to upcast the arguments.
930+
if (!isLegalUpcast(FromTy, ToTy))
931+
return false;
932+
}
933+
return true;
934+
}
935+
869936
/// Generate a new apply of a function_ref to replace an apply of a
870937
/// witness_method when we've determined the actual function we'll end
871938
/// up calling.
@@ -890,6 +957,11 @@ static ApplySite devirtualizeWitnessMethod(ApplySite AI, SILFunction *F,
890957
auto SubstCalleeCanType = CalleeCanType->substGenericArgs(
891958
Module, Module.getSwiftModule(), NewSubs);
892959

960+
// Bail if some of the arguments cannot be converted into
961+
// types required by the found devirtualized method.
962+
if (!canPassOrConvertAllArguments(AI, SubstCalleeCanType))
963+
return ApplySite();
964+
893965
// Collect arguments from the apply instruction.
894966
auto Arguments = SmallVector<SILValue, 4>();
895967

test/SILOptimizer/sil_combine.sil

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3002,3 +3002,69 @@ bb0(%0 : $VV):
30023002
return %26 : $()
30033003
}
30043004

3005+
protocol PPP : class {
3006+
func foo()
3007+
}
3008+
3009+
class BBB: PPP {
3010+
@inline(never)
3011+
func foo()
3012+
}
3013+
3014+
final class XXX : BBB {
3015+
@inline(never)
3016+
override func foo()
3017+
}
3018+
3019+
// Check that sil-combine does not crash on this example and does not generate a wrong
3020+
// upcast.
3021+
// CHECK-LABEL: sil @silcombine_dont_generate_wrong_upcasts_during_devirt
3022+
// CHECK-NOT: upcast
3023+
// CHECK: witness_method $XXX, #PPP.foo!1 : $@convention(witness_method) <τ_0_0 where τ_0_0 : PPP> (@guaranteed τ_0_0) -> ()
3024+
// CHECK-NOT: upcast
3025+
// CHECK: return
3026+
sil @silcombine_dont_generate_wrong_upcasts_during_devirt: $@convention(thin) (@owned BBB) -> () {
3027+
bb0(%0 : $BBB):
3028+
strong_retain %0 : $BBB
3029+
%3 = init_existential_ref %0 : $BBB : $BBB, $PPP
3030+
%5 = open_existential_ref %3 : $PPP to $@opened("0AC9A62E-926E-11E6-8BF5-685B35C48C83") PPP
3031+
%6 = witness_method $XXX, #PPP.foo!1, %5 : $@opened("0AC9A62E-926E-11E6-8BF5-685B35C48C83") PPP : $@convention(witness_method) <τ_0_0 where τ_0_0 : PPP> (@guaranteed τ_0_0) -> ()
3032+
%7 = apply %6<@opened("0AC9A62E-926E-11E6-8BF5-685B35C48C83") PPP>(%5) : $@convention(witness_method) <τ_0_0 where τ_0_0 : PPP> (@guaranteed τ_0_0) -> ()
3033+
%8 = apply %6<@opened("0AC9A62E-926E-11E6-8BF5-685B35C48C83") PPP>(%5) : $@convention(witness_method) <τ_0_0 where τ_0_0 : PPP> (@guaranteed τ_0_0) -> ()
3034+
strong_release %3 : $PPP
3035+
%9 = tuple ()
3036+
strong_release %0 : $BBB
3037+
%11 = tuple ()
3038+
return %11 : $()
3039+
}
3040+
3041+
// Check that both applies can be devirtualized by means of propagating the concrete
3042+
// type of the existential into witness_method and apply instructions.
3043+
// CHECK-LABEL: sil @silcombine_devirt_both_applies_of_witness_method
3044+
// CHECK-NOT: open_existential_ref
3045+
// CHECK-NOT: witness_method
3046+
// CHECK: [[FR1:%.*]] = function_ref @_TTWC4nix23XXXS_3PPPS_FS1_3foofT_T_
3047+
// CHECK: apply [[FR1]](%0)
3048+
// CHECK: [[FR2:%.*]] = function_ref @_TTWC4nix23XXXS_3PPPS_FS1_3foofT_T_
3049+
// CHECK: apply [[FR2]](%0)
3050+
// CHECK: return
3051+
sil @silcombine_devirt_both_applies_of_witness_method : $@convention(thin) (@owned XXX) -> () {
3052+
bb0(%0 : $XXX):
3053+
strong_retain %0 : $XXX
3054+
%3 = init_existential_ref %0 : $XXX : $XXX, $PPP
3055+
%5 = open_existential_ref %3 : $PPP to $@opened("0AC9A62E-926E-11E6-8BF5-685B35C48C83") PPP
3056+
%6 = witness_method $@opened("0AC9A62E-926E-11E6-8BF5-685B35C48C83") PPP, #PPP.foo!1, %5 : $@opened("0AC9A62E-926E-11E6-8BF5-685B35C48C83") PPP : $@convention(witness_method) <τ_0_0 where τ_0_0 : PPP> (@guaranteed τ_0_0) -> ()
3057+
%7 = apply %6<@opened("0AC9A62E-926E-11E6-8BF5-685B35C48C83") PPP>(%5) : $@convention(witness_method) <τ_0_0 where τ_0_0 : PPP> (@guaranteed τ_0_0) -> ()
3058+
%8 = apply %6<@opened("0AC9A62E-926E-11E6-8BF5-685B35C48C83") PPP>(%5) : $@convention(witness_method) <τ_0_0 where τ_0_0 : PPP> (@guaranteed τ_0_0) -> ()
3059+
strong_release %3 : $PPP
3060+
%9 = tuple ()
3061+
strong_release %0 : $XXX
3062+
%11 = tuple ()
3063+
return %11 : $()
3064+
}
3065+
3066+
sil @_TTWC4nix23XXXS_3PPPS_FS1_3foofT_T_ : $@convention(witness_method) (@guaranteed XXX) -> ()
3067+
3068+
sil_witness_table XXX: PPP module nix2 {
3069+
method #PPP.foo!1: @_TTWC4nix23XXXS_3PPPS_FS1_3foofT_T_ // protocol witness for PPP.foo() -> () in conformance XXX
3070+
}

0 commit comments

Comments
 (0)