From 6c2be2b88d1407641504a976db160fd4907d0ec1 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Fri, 28 Mar 2025 10:57:37 -0700 Subject: [PATCH] [SILOptimizer] Prevent devirtualization of call to distributed witness requirements This is a narrow fix, we are going to work on fixing this properly and allowing both devirtualization and specialization for distributed requirement witnesses. Anything that uses an ad-hoc serialization requirement scheme cannot be devirtualized because that would result in loss of ad-hoc conformance in new substitution map. Resolves: https://github.com/swiftlang/swift/issues/79318 Resolves: rdar://146101172 (cherry picked from commit 0415b40a3cea02c7ef7315cd7c7f390686669ccd) --- lib/SILOptimizer/Utils/Devirtualize.cpp | 18 +++ ...r_adhoc_requirements_optimized_build.swift | 139 ++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 test/Distributed/distributed_actor_adhoc_requirements_optimized_build.swift diff --git a/lib/SILOptimizer/Utils/Devirtualize.cpp b/lib/SILOptimizer/Utils/Devirtualize.cpp index 5d039cfd0c930..78b0e62b88850 100644 --- a/lib/SILOptimizer/Utils/Devirtualize.cpp +++ b/lib/SILOptimizer/Utils/Devirtualize.cpp @@ -810,6 +810,24 @@ bool swift::canDevirtualizeClassMethod(FullApplySite applySite, ClassDecl *cd, return false; } + // A narrow fix for https://github.com/swiftlang/swift/issues/79318 + // to make sure that uses of distributed requirement witnesses are + // not devirtualized because that results in a loss of the ad-hoc + // requirement infomation in the re-created substitution map. + // + // We have a similar check in `canSpecializeFunction` which presents + // specialization for exactly the same reason. + // + // TODO: A better way to fix this would be to record the ad-hoc conformance + // requirement in `RequirementEnvironment` and adjust IRGen to handle it. + if (f->hasLocation()) { + if (auto *funcDecl = + dyn_cast_or_null(f->getLocation().getAsDeclContext())) { + if (funcDecl->isDistributedWitnessWithAdHocSerializationRequirement()) + return false; + } + } + return true; } diff --git a/test/Distributed/distributed_actor_adhoc_requirements_optimized_build.swift b/test/Distributed/distributed_actor_adhoc_requirements_optimized_build.swift new file mode 100644 index 0000000000000..38c7c2c95e16f --- /dev/null +++ b/test/Distributed/distributed_actor_adhoc_requirements_optimized_build.swift @@ -0,0 +1,139 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-ir -swift-version 6 -O -I %t %s +// RUN: %target-swift-frontend -emit-sil -swift-version 6 -O -I %t %s | %FileCheck %s + +// REQUIRES: concurrency +// REQUIRES: distributed + +// REQUIRES: OS=macosx || OS=ios + +import Distributed + +// NOTE: None of the ad-hoc protocol requirement implementations + +public protocol Transferable: Sendable {} + +// NOT final on purpose +public class TheSpecificResultHandlerWhichIsANonFinalClass: DistributedTargetInvocationResultHandler { + public typealias SerializationRequirement = Transferable + + public func onReturn(value: Success) async throws where Success: Transferable { + } + + public func onReturnVoid() async throws { + fatalError() + } + + public func onThrow(error: Err) async throws where Err : Error { + fatalError() + } +} + +// NOT final on purpose +public class FakeInvocationDecoder: DistributedTargetInvocationDecoder { + public typealias SerializationRequirement = Transferable + + public func decodeGenericSubstitutions() throws -> [Any.Type] { + [] + } + + public func decodeNextArgument() throws -> Argument { + fatalError() + } + + public func decodeErrorType() throws -> Any.Type? { + nil + } + + public func decodeReturnType() throws -> Any.Type? { + nil + } +} + +// NOT final on purpose +public class FakeInvocationEncoder : DistributedTargetInvocationEncoder { + public typealias SerializationRequirement = Transferable + + public func recordArgument( + _ argument: RemoteCallArgument) throws { + } + + public func recordGenericSubstitution(_ type: T.Type) throws { + } + + public func recordErrorType(_ type: E.Type) throws { + } + + public func recordReturnType(_ type: R.Type) throws { + } + + public func doneRecording() throws { + } +} + +// NOT final on purpose +public class NotFinalActorSystemForAdHocRequirementTest: DistributedActorSystem, @unchecked Sendable { + public typealias ActorID = String + public typealias InvocationEncoder = FakeInvocationEncoder + public typealias InvocationDecoder = FakeInvocationDecoder + public typealias SerializationRequirement = Transferable + public typealias ResultHandler = TheSpecificResultHandlerWhichIsANonFinalClass + + public init() {} + + public func resolve(id: ActorID, as actorType: Act.Type) + throws -> Act? where Act: DistributedActor { + fatalError() + } + + public func assignID(_ actorType: Act.Type) -> ActorID + where Act: DistributedActor { + fatalError() + } + + public func actorReady(_ actor: Act) where Act: DistributedActor, Act.ID == ActorID { + fatalError() + } + + public func resignID(_ id: ActorID) { + fatalError() + } + + public func makeInvocationEncoder() -> InvocationEncoder { + fatalError() + } + + public func remoteCall( + on actor: Act, + target: RemoteCallTarget, + invocation: inout InvocationEncoder, + throwing errorType: Err.Type, + returning returnType: Res.Type + ) async throws -> Res + where Act: DistributedActor, + Act.ID == ActorID, + Err: Error, + Res: SerializationRequirement { + fatalError() + } + + public func remoteCallVoid( + on actor: Act, + target: RemoteCallTarget, + invocation: inout InvocationEncoder, + throwing errorType: Err.Type + ) async throws + where Act: DistributedActor, + Act.ID == ActorID, + Err: Error { + fatalError() + } +} + +// FIXME: This call should be devirtualized but it cannot be done at the moment due to issues with ad-hoc serialization requirement. + +// CHECK-LABEL: sil shared [transparent] [thunk] @$s52distributed_actor_adhoc_requirements_optimized_build42NotFinalActorSystemForAdHocRequirementTestC11Distributed0piJ0AadEP10remoteCall2on6target10invocation8throwing9returningqd_1_qd___AD06RemoteR6TargetV17InvocationEncoderQzzqd_0_mqd_1_mtYaKAD0pI0Rd__s5ErrorRd_0_2IDQyd__0I2IDRtzr1_lFTW +// CHECK: bb0(%0 : $*τ_0_2, %1 : $τ_0_0, %2 : $*RemoteCallTarget, %3 : $*FakeInvocationEncoder, %4 : $@thick τ_0_1.Type, %5 : $@thick τ_0_2.Type, %6 : $*NotFinalActorSystemForAdHocRequirementTest): +// CHECK-NEXT: [[DIST_IMPL:%.*]] = load %6 +// CHECK-NEXT: [[REMOTE_CALL_WITNESS:%.*]] = class_method [[DIST_IMPL]] : $NotFinalActorSystemForAdHocRequirementTest, #NotFinalActorSystemForAdHocRequirementTest.remoteCall +// CHECK-NEXT: try_apply [[REMOTE_CALL_WITNESS]]<τ_0_0, τ_0_1, τ_0_2>(%0, %1, %2, %3, %4, %5, [[DIST_IMPL]])