[CSApply] Don't attempt operator devirtualization

This appears to only support synthesized conformances because
operators in such cases use different names - `__derived_*`.

These days devirtualization like this is performed as part of
mandatory inlining. And this approach doesn't stack well with
features like `MemberImportVisibility` because there is change
to check whether witness is available or not.
This commit is contained in:
Pavel Yaskevich
2025-10-09 16:15:16 -07:00
parent 537353074d
commit 9d00c7bd18
6 changed files with 46 additions and 147 deletions

View File

@@ -588,66 +588,6 @@ namespace {
}
}
// Returns None if the AST does not contain enough information to recover
// substitutions; this is different from an Optional(SubstitutionMap()),
// indicating a valid call to a non-generic operator.
std::optional<SubstitutionMap> getOperatorSubstitutions(ValueDecl *witness,
Type refType) {
// We have to recover substitutions in this hacky way because
// the AST does not retain enough information to devirtualize
// calls like this.
auto witnessType = witness->getInterfaceType();
// Compute the substitutions.
auto *gft = witnessType->getAs<GenericFunctionType>();
if (gft == nullptr) {
if (refType->isEqual(witnessType))
return SubstitutionMap();
return std::nullopt;
}
auto sig = gft->getGenericSignature();
auto *env = sig.getGenericEnvironment();
witnessType = FunctionType::get(gft->getParams(),
gft->getResult(),
gft->getExtInfo());
witnessType = env->mapTypeIntoContext(witnessType);
TypeSubstitutionMap subs;
auto substType = witnessType->substituteBindingsTo(
refType,
[&](ArchetypeType *origType, CanType substType) -> CanType {
if (auto gpType = dyn_cast<GenericTypeParamType>(
origType->getInterfaceType()->getCanonicalType()))
subs[gpType] = substType;
return substType;
});
// If substitution failed, it means that the protocol requirement type
// and the witness type did not match up. The only time that this
// should happen is when the witness is defined in a base class and
// the actual call uses a derived class. For example,
//
// protocol P { func +(lhs: Self, rhs: Self) }
// class Base : P { func +(lhs: Base, rhs: Base) {} }
// class Derived : Base {}
//
// If we enter this code path with two operands of type Derived,
// we know we're calling the protocol requirement P.+, with a
// substituted type of (Derived, Derived) -> (). But the type of
// the witness is (Base, Base) -> (). Just bail out and make a
// witness method call in this rare case; SIL mandatory optimizations
// will likely devirtualize it anyway.
if (!substType)
return std::nullopt;
return SubstitutionMap::get(sig,
QueryTypeSubstitutionMap{subs},
LookUpConformanceInModule());
}
/// Determine whether the given reference is to a method on
/// a remote distributed actor in the given context.
bool isDistributedThunk(ConcreteDeclRef ref, Expr *context);
@@ -674,65 +614,6 @@ namespace {
auto baseTy = getBaseType(adjustedFullType->castTo<FunctionType>());
// Handle operator requirements found in protocols.
if (auto proto = dyn_cast<ProtocolDecl>(decl->getDeclContext())) {
bool isCurried = shouldBuildCurryThunk(choice, /*baseIsInstance=*/false);
// If we have a concrete conformance, build a call to the witness.
//
// FIXME: This is awful. We should be able to handle this as a call to
// the protocol requirement with Self == the concrete type, and SILGen
// (or later) can devirtualize as appropriate.
auto conformance = checkConformance(baseTy, proto);
if (conformance.isConcrete()) {
if (auto witness = conformance.getConcrete()->getWitnessDecl(decl)) {
bool isMemberOperator = witness->getDeclContext()->isTypeContext();
if (!isMemberOperator || !isCurried) {
// The fullType was computed by substituting the protocol
// requirement so it always has a (Self) -> ... curried
// application. Strip it off if the witness was a top-level
// function.
Type refType;
if (isMemberOperator)
refType = adjustedFullType;
else
refType = adjustedFullType->castTo<AnyFunctionType>()->getResult();
// Build the AST for the call to the witness.
auto subMap = getOperatorSubstitutions(witness, refType);
if (subMap) {
ConcreteDeclRef witnessRef(witness, *subMap);
auto declRefExpr = new (ctx) DeclRefExpr(witnessRef, loc,
/*Implicit=*/false);
declRefExpr->setFunctionRefInfo(choice.getFunctionRefInfo());
cs.setType(declRefExpr, refType);
Expr *refExpr;
if (isMemberOperator) {
// If the operator is a type member, add the implicit
// (Self) -> ... call.
Expr *base =
TypeExpr::createImplicitHack(loc.getBaseNameLoc(), baseTy,
ctx);
cs.setType(base, MetatypeType::get(baseTy));
refExpr =
DotSyntaxCallExpr::create(ctx, declRefExpr, SourceLoc(),
Argument::unlabeled(base));
auto refType = adjustedFullType->castTo<FunctionType>()->getResult();
cs.setType(refExpr, refType);
} else {
refExpr = declRefExpr;
}
return forceUnwrapIfExpected(refExpr, locator);
}
}
}
}
}
// Build a reference to the member.
Expr *base =
TypeExpr::createImplicitHack(loc.getBaseNameLoc(), baseTy, ctx);

View File

@@ -21,9 +21,10 @@ func test(arr: [any P]) {
}
}
// Make sure that we don't pick a concrete `(AnyHashable, AnyHashable) -> Bool` overload.
// CHECK: sil private [ossa] @$s34anyhashable_and_operator_filtering4test3arrySayAA1P_pG_tFyAaD_pXEfU_
// CHECK: [[LHS_ARG:%.*]] = alloc_stack $E
// CHECK: [[RHS_ARG:%.*]] = alloc_stack $E
// CHECK: function_ref == infix<A>(_:_:)
// CHECK-NEXT: [[GENERIC_OP:%.*]] = function_ref @$ss2eeoiySbx_xtSYRzSQ8RawValueRpzlF : $@convention(thin) <τ_0_0 where τ_0_0 : RawRepresentable, τ_0_0.RawValue : Equatable> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0) -> Bool
// CHECK-NEXT: apply [[GENERIC_OP]]<E>([[LHS_ARG]], [[RHS_ARG]])
// CHECK: [[GENERIC_OP:%.*]] = witness_method $E, #Equatable."==" : <Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool
// CHECK-NEXT: apply [[GENERIC_OP]]<E>([[LHS_ARG]], [[RHS_ARG]], {{.*}})

View File

@@ -8,9 +8,8 @@ struct Value: Equatable, ExpressibleByNilLiteral {
}
// CHECK-LABEL: sil hidden [ossa] @$s13rdar1580631514test1vyAA5ValueV_tF : $@convention(thin) (Value) -> ()
// function_ref static Value.__derived_struct_equals(_:_:)
// CHECK: [[EQUALS_REF:%.*]] = function_ref @$s13rdar1580631515ValueV23__derived_struct_equalsySbAC_ACtFZ
// CHECK-NEXT: apply [[EQUALS_REF]](%0, {{.*}})
// CHECK: [[EQUALS_REF:%.*]] = witness_method $Value, #Equatable."==" : <Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool
// CHECK-NEXT: apply [[EQUALS_REF]]<Value>({{.*}})
func test(v: Value) {
_ = v == nil
}

View File

@@ -12,6 +12,6 @@ func testFoo() {
// CHECK: [[@LINE+1]]:7 | instance-method/Swift | hash(into:) | s:14swift_ide_test9CustomFooV4hash4intoys6HasherVz_tF | {{.*}}Ref
f.hash(into: &hasher)
hasher.finalize()
// CHECK: [[@LINE+1]]:11 | static-method/Swift | __derived_struct_equals(_:_:) | s:14swift_ide_test9CustomFooV23__derived_struct_equalsySbAC_ACtFZ | {{.*}}Ref
// CHECK: [[@LINE+1]]:11 | static-method/infix-operator/Swift | ==(_:_:) | s:SQ2eeoiySbx_xtFZ | {{.*}}Ref
_ = f == CustomFoo(a: 0, b: "b")
}

View File

@@ -48,15 +48,15 @@ public enum Alphabet : String {
// CHECK-LABEL: sil [ossa] @$s4main14check_alphabetySiAA8AlphabetOF : $@convention(thin) (Alphabet) -> Int {
public func check_alphabet(_ state : Alphabet) -> Int {
// FRAGILE: function_ref @$ss2eeoiySbx_xtSYRzSQ8RawValueRpzlF
// RESILIENT: function_ref @$ss2eeoiySbx_xtSYRzSQ8RawValueRpzlF
// FRAGILE: witness_method $Alphabet, #Equatable."==" : <Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool
// RESILIENT: witness_method $Alphabet, #Equatable."==" : <Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool
return state == .E ? 1 : 0
}
// CHECK-LABEL: sil [ossa] @$s4main9compareItySbAA8AlphabetO_ADtF : $@convention(thin) (Alphabet, Alphabet) -> Bool {
public func compareIt(_ state : Alphabet, _ rhs: Alphabet) -> Bool {
// FRAGILE: function_ref @$ss2eeoiySbx_xtSYRzSQ8RawValueRpzlF
// RESILIENT: function_ref @$ss2eeoiySbx_xtSYRzSQ8RawValueRpzlF
// FRAGILE: witness_method $Alphabet, #Equatable."==" : <Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool
// RESILIENT: witness_method $Alphabet, #Equatable."==" : <Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool
return state == rhs
}
@@ -67,14 +67,14 @@ public enum AlphabetInt : Int {
// CHECK-LABEL: sil [ossa] @$s4main18check_alphabet_intySiAA11AlphabetIntOF : $@convention(thin) (AlphabetInt) -> Int {
public func check_alphabet_int(_ state : AlphabetInt) -> Int {
// FRAGILE: function_ref @$ss2eeoiySbx_xtSYRzSQ8RawValueRpzlF
// RESILIENT: function_ref @$ss2eeoiySbx_xtSYRzSQ8RawValueRpzlF
// FRAGILE: witness_method $AlphabetInt, #Equatable."==" : <Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool
// RESILIENT: witness_method $AlphabetInt, #Equatable."==" : <Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool
return state == .E ? 1 : 0
}
// CHECK-LABEL: sil [ossa] @$s4main9compareItySbAA11AlphabetIntO_ADtF : $@convention(thin) (AlphabetInt, AlphabetInt) -> Bool {
public func compareIt(_ state : AlphabetInt, _ rhs: AlphabetInt) -> Bool {
// FRAGILE: function_ref @$ss2eeoiySbx_xtSYRzSQ8RawValueRpzlF
// RESILIENT: function_ref @$ss2eeoiySbx_xtSYRzSQ8RawValueRpzlF
// FRAGILE: witness_method $AlphabetInt, #Equatable."==" : <Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool
// RESILIENT: witness_method $AlphabetInt, #Equatable."==" : <Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool
return state == rhs
}

View File

@@ -1,4 +1,7 @@
// RUN: %target-swift-frontend -emit-silgen %s | %FileCheck %s
// RUN: %target-swift-frontend -emit-silgen %s | %FileCheck %s --check-prefix=SILGEN
// RUN: %target-swift-frontend -emit-sil %s | %FileCheck %s --check-prefix=OPTIMIZED
// Operators are no longer devirtualized at AST level, it's done during SIL optimization.
infix operator +++
@@ -11,9 +14,13 @@ struct Branch : Twig {
static func doIt(_: Branch, _: Branch) {}
}
// CHECK-LABEL: sil hidden [ossa] @$s18protocol_operators9useBranchyyAA0D0VF : $@convention(thin) (Branch) -> () {
// CHECK: function_ref @$s18protocol_operators6BranchV4doItyyAC_ACtFZ : $@convention(method) (Branch, Branch, @thin Branch.Type) -> ()
// CHECK: return
// SILGEN-LABEL: sil hidden [ossa] @$s18protocol_operators9useBranchyyAA0D0VF : $@convention(thin) (Branch) -> () {
// SILGEN: witness_method $Branch, #Twig."+++" : <Self where Self : Twig> (Self.Type) -> (Self, Self) -> ()
// SILGEN: return
// OPTIMIZED-LABEL: sil hidden @$s18protocol_operators9useBranchyyAA0D0VF : $@convention(thin) (Branch) -> () {
// OPTIMIZED: function_ref @$s18protocol_operators6BranchV4doItyyAC_ACtFZ
// OPTIMIZED: return
func useBranch(_ b: Branch) {
b +++ b
}
@@ -28,11 +35,17 @@ class Stuck : Stick, ExpressibleByIntegerLiteral {
required init(integerLiteral: Int) {}
}
// CHECK-LABEL: sil hidden [ossa] @$s18protocol_operators8useStickyyAA5StuckC_AA0D0CtF : $@convention(thin) (@guaranteed Stuck, @guaranteed Stick) -> () {
// CHECK: function_ref @$s18protocol_operators5StickC3pppoiyyAC_ACtFZ : $@convention(method) (@guaranteed Stick, @guaranteed Stick, @thick Stick.Type) -> ()
// CHECK: function_ref @$s18protocol_operators5StickC3pppoiyyAC_ACtFZ : $@convention(method) (@guaranteed Stick, @guaranteed Stick, @thick Stick.Type) -> ()
// CHECK: witness_method $Stuck, #Twig."+++" : <Self where Self : Twig> (Self.Type) -> (Self, Self) -> () : $@convention(witness_method: Twig) <τ_0_0 where τ_0_0 : Twig> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0, @thick τ_0_0.Type) -> ()
// CHECK: return
// SILGEN-LABEL: sil hidden [ossa] @$s18protocol_operators8useStickyyAA5StuckC_AA0D0CtF : $@convention(thin) (@guaranteed Stuck, @guaranteed Stick) -> () {
// SILGEN: function_ref @$s18protocol_operators5StickC3pppoiyyAC_ACtFZ : $@convention(method) (@guaranteed Stick, @guaranteed Stick, @thick Stick.Type) -> ()
// SILGEN: function_ref @$s18protocol_operators5StickC3pppoiyyAC_ACtFZ : $@convention(method) (@guaranteed Stick, @guaranteed Stick, @thick Stick.Type) -> ()
// SILGEN: witness_method $Stuck, #Twig."+++" : <Self where Self : Twig> (Self.Type) -> (Self, Self) -> () : $@convention(witness_method: Twig) <τ_0_0 where τ_0_0 : Twig> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0, @thick τ_0_0.Type) -> ()
// SILGEN: return
// OPTIMIZED-LABEL: sil hidden @$s18protocol_operators8useStickyyAA5StuckC_AA0D0CtF : $@convention(thin) (@guaranteed Stuck, @guaranteed Stick) -> () {
// OPTIMIZED: function_ref @$s18protocol_operators5StickC3pppoiyyAC_ACtFZ : $@convention(method) (@guaranteed Stick, @guaranteed Stick, @thick Stick.Type) -> ()
// OPTIMIZED: function_ref @$s18protocol_operators5StickC3pppoiyyAC_ACtFZ : $@convention(method) (@guaranteed Stick, @guaranteed Stick, @thick Stick.Type) -> ()
// OPTIMIZED: function_ref @$s18protocol_operators5StickC3pppoiyyAC_ACtFZ : $@convention(method) (@guaranteed Stick, @guaranteed Stick, @thick Stick.Type) -> ()
// OPTIMIZED: return
func useStick(_ a: Stuck, _ b: Stick) {
_ = a +++ b
_ = b +++ b
@@ -49,10 +62,15 @@ class Rope : Twine<Int>, ExpressibleByIntegerLiteral {
required init(integerLiteral: Int) {}
}
// CHECK-LABEL: sil hidden [ossa] @$s18protocol_operators7useRopeyyAA0D0C_ADtF : $@convention(thin) (@guaranteed Rope, @guaranteed Rope) -> () {
// CHECK: function_ref @$s18protocol_operators5TwineC3pppoiyyACyxG_AEtFZ : $@convention(method) <τ_0_0> (@guaranteed Twine<τ_0_0>, @guaranteed Twine<τ_0_0>, @thick Twine<τ_0_0>.Type) -> ()
// CHECK: function_ref @$s18protocol_operators5TwineC3pppoiyyACyxG_AEtFZ : $@convention(method) <τ_0_0> (@guaranteed Twine<τ_0_0>, @guaranteed Twine<τ_0_0>, @thick Twine<τ_0_0>.Type) -> ()
// CHECK: witness_method $Rope, #Twig."+++" : <Self where Self : Twig> (Self.Type) -> (Self, Self) -> () : $@convention(witness_method: Twig) <τ_0_0 where τ_0_0 : Twig> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0, @thick τ_0_0.Type) -> ()
// SILGEN-LABEL: sil hidden [ossa] @$s18protocol_operators7useRopeyyAA0D0C_ADtF : $@convention(thin) (@guaranteed Rope, @guaranteed Rope) -> () {
// SILGEN: function_ref @$s18protocol_operators5TwineC3pppoiyyACyxG_AEtFZ : $@convention(method) <τ_0_0> (@guaranteed Twine<τ_0_0>, @guaranteed Twine<τ_0_0>, @thick Twine<τ_0_0>.Type) -> ()
// SILGEN: function_ref @$s18protocol_operators5TwineC3pppoiyyACyxG_AEtFZ : $@convention(method) <τ_0_0> (@guaranteed Twine<τ_0_0>, @guaranteed Twine<τ_0_0>, @thick Twine<τ_0_0>.Type) -> ()
// SILGEN: witness_method $Rope, #Twig."+++" : <Self where Self : Twig> (Self.Type) -> (Self, Self) -> () : $@convention(witness_method: Twig) <τ_0_0 where τ_0_0 : Twig> (@in_guaranteed τ_0_0, @in_guaranteed τ_0_0, @thick τ_0_0.Type) -> ()
// OPTIMIZED-LABEL: sil hidden @$s18protocol_operators7useRopeyyAA0D0C_ADtF : $@convention(thin) (@guaranteed Rope, @guaranteed Rope) -> () {
// OPTIMIZED: function_ref @$s18protocol_operators5TwineC3pppoiyyACyxG_AEtFZ : $@convention(method) <τ_0_0> (@guaranteed Twine<τ_0_0>, @guaranteed Twine<τ_0_0>, @thick Twine<τ_0_0>.Type) -> ()
// OPTIMIZED: function_ref @$s18protocol_operators5TwineC3pppoiyyACyxG_AEtFZ : $@convention(method) <τ_0_0> (@guaranteed Twine<τ_0_0>, @guaranteed Twine<τ_0_0>, @thick Twine<τ_0_0>.Type) -> ()
// OPTIMIZED: function_ref @$s18protocol_operators5TwineC3pppoiyyACyxG_AEtFZ : $@convention(method) <τ_0_0> (@guaranteed Twine<τ_0_0>, @guaranteed Twine<τ_0_0>, @thick Twine<τ_0_0>.Type) -> ()
func useRope(_ r: Rope, _ s: Rope) {
_ = r +++ s
_ = s +++ s