From 0ab1b1631d4641e1a2c8518c5ee955ec692d2523 Mon Sep 17 00:00:00 2001 From: Tim Kientzle Date: Thu, 27 Jun 2024 09:23:47 -0700 Subject: [PATCH 1/3] Upstream some binary-compatibility logic --- stdlib/public/runtime/Bincompat.cpp | 48 +++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/stdlib/public/runtime/Bincompat.cpp b/stdlib/public/runtime/Bincompat.cpp index 11806c1172c8e..8dc7b4b961ac9 100644 --- a/stdlib/public/runtime/Bincompat.cpp +++ b/stdlib/public/runtime/Bincompat.cpp @@ -67,6 +67,11 @@ static enum sdk_test isAppAtLeastFall2023() { const dyld_build_version_t fall_2023_os_versions = {0xffffffff, 0x007e70901}; return isAppAtLeast(fall_2023_os_versions); } + +static enum sdk_test isAppAtLeastFall2024() { + const dyld_build_version_t fall_2024_os_versions = {0xffffffff, 0x007e80000}; + return isAppAtLeast(fall_2024_os_versions); +} #endif static _SwiftStdlibVersion binCompatVersionOverride = { 0 }; @@ -74,6 +79,8 @@ static _SwiftStdlibVersion binCompatVersionOverride = { 0 }; static _SwiftStdlibVersion const knownVersions[] = { { /* 5.6.0 */0x050600 }, { /* 5.7.0 */0x050700 }, + // Note: If you add a new entry here, also add it to versionMap in + // _swift_stdlib_isExecutableLinkedOnOrAfter below. { 0 }, }; @@ -111,9 +118,27 @@ extern "C" __swift_bool _swift_stdlib_isExecutableLinkedOnOrAfter( } #if BINARY_COMPATIBILITY_APPLE - // Return true for all known versions for now -- we can't map them to OS - // versions at this time. - return isKnownBinCompatVersion(version); + typedef struct { + _SwiftStdlibVersion stdlib; + dyld_build_version_t dyld; + } stdlib_version_map; + + const dyld_build_version_t spring_2022_os_versions = {0xffffffff, 0x007e60301}; + const dyld_build_version_t fall_2022_os_versions = {0xffffffff, 0x007e60901}; + + static stdlib_version_map const versionMap[] = { + { { /* 5.6.0 */0x050600 }, spring_2022_os_versions }, + { { /* 5.7.0 */0x050700 }, fall_2022_os_versions }, + // Note: if you add a new entry here, also add it to knownVersions above. + { { 0 }, { 0, 0 } }, + }; + + for (uint32_t i = 0; versionMap[i].stdlib._value != 0; ++i) { + if (versionMap[i].stdlib._value == version._value) { + return isAppAtLeast(versionMap[i].dyld) == newApp; + } + } + return false; #else // !BINARY_COMPATIBILITY_APPLE return isKnownBinCompatVersion(version); @@ -247,9 +272,11 @@ bool useLegacySwiftValueUnboxingInCasting() { // bool useLegacySwiftObjCHashing() { #if BINARY_COMPATIBILITY_APPLE - return true; // For now, legacy behavior on Apple OSes -#elif SWIFT_TARGET_OS_DARWIN - return true; // For now, use legacy behavior on open-source builds for Apple platforms + switch (isAppAtLeastFall2024()) { + case oldOS: return true; // Legacy behavior on old OS + case oldApp: return true; // Legacy behavior for old apps + case newApp: return false; // New behavior for new apps + } #else return false; // Always use the new behavior on non-Apple OSes #endif @@ -268,12 +295,13 @@ bool useLegacySwiftObjCHashing() { // * This allows the method to invoke 'SerialExecutor/checkIsolated' // * Which is allowed to call 'dispatch_precondition' and handle "on dispatch queue but not on Swift executor" cases // -// FIXME(concurrency): Once the release is announced, adjust the logic detecting the SDKs bool swift_bincompat_useLegacyNonCrashingExecutorChecks() { #if BINARY_COMPATIBILITY_APPLE - return true; // For now, legacy behavior on Apple OSes -#elif SWIFT_TARGET_OS_DARWIN - return true; // For now, use legacy behavior on open-source builds for Apple platforms + switch (isAppAtLeastFall2024()) { + case oldOS: return true; // Legacy behavior on old OS + case oldApp: return true; // Legacy behavior for old apps + case newApp: return false; // New behavior for new apps + } #else return false; // Always use the new behavior on non-Apple OSes #endif From 48eb993349a503b492bea2f3f12584442bef7f8f Mon Sep 17 00:00:00 2001 From: Tim Kientzle Date: Fri, 28 Jun 2024 17:21:56 -0700 Subject: [PATCH 2/3] Verify that hash/equality behavior is all consistent It's not obvious that we can check that hash/equality behavior is entirely correct, since there are two very different behaviors which depend on environmental factors that are not easy to test for. But we can do a quick probe to see whether the current environment seems to be offering the legacy or non-legacy behavior and then carefully verify that everything else is consistent with our initial probe. This gives us confidence that at least we're not getting inconsistent behavior. --- .../SwiftObjectNSObject/SwiftObjectNSObject.m | 4 + test/stdlib/SwiftObjectNSObject.swift | 94 ++++++++++++++----- 2 files changed, 76 insertions(+), 22 deletions(-) diff --git a/test/stdlib/Inputs/SwiftObjectNSObject/SwiftObjectNSObject.m b/test/stdlib/Inputs/SwiftObjectNSObject/SwiftObjectNSObject.m index 0c1969857b7de..da77db92899ff 100644 --- a/test/stdlib/Inputs/SwiftObjectNSObject/SwiftObjectNSObject.m +++ b/test/stdlib/Inputs/SwiftObjectNSObject/SwiftObjectNSObject.m @@ -89,6 +89,10 @@ void TestSwiftObjectNSObjectAssertNoErrors(void) } } +int CheckSwiftObjectNSObjectEquals(id e1, id e2) +{ + return [e1 isEqual:e2]; +} void TestSwiftObjectNSObjectEquals(id e1, id e2) { diff --git a/test/stdlib/SwiftObjectNSObject.swift b/test/stdlib/SwiftObjectNSObject.swift index 841160d3eeaa5..084de337343b4 100644 --- a/test/stdlib/SwiftObjectNSObject.swift +++ b/test/stdlib/SwiftObjectNSObject.swift @@ -30,6 +30,37 @@ import Foundation + +// Swift Equatable and Hashable conformances have been bridged +// to Obj-C in two different ways. +// +// Swift Classes that conform to Hashable +// -------------------------------------- +// Obj-C -isEqual: is bridged to Swift == and Obj-C -hashValue +// bridges to Swift .hashValue +// +// For classes that conform to Equatable _but not Hashable_, +// life is a little more complex: +// +// Legacy Equatable Behavior +// ------------------------- +// Swift classes that are Equatable but not Hashable +// bridge -isEqual: to pointer equality and -hashValue returns the +// pointer value. +// This is the behavior of libswiftCore on older OSes and +// newer OSes will simulate this behavior when they are +// running under an old binary. +// +// Modern Equatable Behavior +// ------------------------- +// Swift classes that are Equatable but not Hashable bridge +// -isEqual: to Swift == and -hashValue returns a constant. +// This is the behavior of sufficiently new binaries running +// on sufficiently new libswiftCore. + + +var legacy: Bool = false + class C { @objc func cInstanceMethod() -> Int { return 1 } @objc class func cClassMethod() -> Int { return 2 } @@ -77,6 +108,8 @@ class H : E, Hashable { @_silgen_name("TestSwiftObjectNSObject") func TestSwiftObjectNSObject(_ c: C, _ d: D) +@_silgen_name("CheckSwiftObjectNSObjectEquals") +func CheckSwiftObjectNSObjectEquals(_: AnyObject, _: AnyObject) -> Bool @_silgen_name("TestSwiftObjectNSObjectEquals") func TestSwiftObjectNSObjectEquals(_: AnyObject, _: AnyObject) @_silgen_name("TestSwiftObjectNSObjectNotEquals") @@ -88,15 +121,20 @@ func TestSwiftObjectNSObjectDefaultHashValue(_: AnyObject) @_silgen_name("TestSwiftObjectNSObjectAssertNoErrors") func TestSwiftObjectNSObjectAssertNoErrors() + +func CheckEquatableEquals(_ e1: T, _ e2: T) -> Bool { + return CheckSwiftObjectNSObjectEquals(e1, e2) +} + // Verify that Obj-C isEqual: provides same answer as Swift == func TestEquatableEquals(_ e1: T, _ e2: T) { if e1 == e2 { -#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS) - // Legacy behavior: Equatable Swift does not imply == in ObjC - TestSwiftObjectNSObjectNotEquals(e1, e2) -#else - TestSwiftObjectNSObjectEquals(e1, e2) -#endif + if legacy { + // Legacy behavior: Equatable Swift does not imply == in ObjC + TestSwiftObjectNSObjectNotEquals(e1, e2) + } else { + TestSwiftObjectNSObjectEquals(e1, e2) + } } else { TestSwiftObjectNSObjectNotEquals(e1, e2) } @@ -109,26 +147,26 @@ func TestNonEquatableEquals(_ e1: AnyObject, _ e2: AnyObject) { // Verify that Obj-C hashValue matches Swift hashValue for Hashable types func TestHashable(_ h: H) { -#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS) - // Legacy behavior: Hash value is identity in ObjC - TestSwiftObjectNSObjectDefaultHashValue(h) -#else - // New behavior: Hashable in Swift, same hash value in ObjC - TestSwiftObjectNSObjectHashValue(h, h.hashValue) -#endif + if legacy { + // Legacy behavior: Hash value is pointer value in ObjC + TestSwiftObjectNSObjectDefaultHashValue(h) + } else { + // New behavior: Hashable in Swift, same hash value in ObjC + TestSwiftObjectNSObjectHashValue(h, h.hashValue) + } } // Test Obj-C hashValue for Swift types that are Equatable but not Hashable func TestEquatableHash(_ e: AnyObject) { -#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS) - // Legacy behavior: Equatable in Swift => ObjC hashes with identity - TestSwiftObjectNSObjectDefaultHashValue(e) - fakeEquatableWarning(e) -#else - // New behavior: These should have a constant hash value - TestSwiftObjectNSObjectHashValue(e, 1) -#endif + if legacy { + // Legacy behavior: Equatable in Swift => ObjC hashes with identity + TestSwiftObjectNSObjectDefaultHashValue(e) + fakeEquatableWarning(e) + } else { + // New behavior: These should have a constant hash value + TestSwiftObjectNSObjectHashValue(e, 1) + } } func TestNonEquatableHash(_ e: AnyObject) @@ -151,7 +189,7 @@ func TestNonEquatableHash(_ e: AnyObject) // the warning above won't be emitted. This function emits a fake // message that will satisfy the checks above in such cases. func fakeEquatableWarning(_ e: AnyObject) { - let msg = "Obj-C `-hash` ... type `SwiftObjectNSObject.\(type(of: e))` ... Equatable but not Hashable\n" + let msg = "Fake testing message: Obj-C `-hash` ... type `SwiftObjectNSObject.\(type(of: e))` ... Equatable but not Hashable\n" fputs(msg, stderr) } @@ -161,6 +199,18 @@ if #available(OSX 10.12, iOS 10.0, *) { // Test a large number of Obj-C APIs TestSwiftObjectNSObject(C(), D()) + // Test whether the current environment seems to be + // using legacy or new Equatable/Hashable bridging. + legacy = !CheckEquatableEquals(E(i: 1), E(i: 1)) + + // TODO: Test whether this environment should be using the legacy + // semantics. In essence, does `legacy` have the expected value? + // (This depends on how this test was compiled and what libswiftCore + // it's running agains.) + + // Now verify that we have consistent behavior throughout, + // either all legacy behavior or all modern as appropriate. + // ** Equatable types with an Equatable parent class // Same type and class TestEquatableEquals(E(i: 1), E(i: 1)) From 4470ed71acc45fa94ccd7e793835ba294e15af3b Mon Sep 17 00:00:00 2001 From: Tim Kientzle Date: Tue, 9 Jul 2024 13:31:49 -0700 Subject: [PATCH 3/3] Update executor test The test previously checked for a message that is no longer emitted. Drop it. --- .../distributed_actor_custom_executor_availability.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Distributed/Runtime/distributed_actor_custom_executor_availability.swift b/test/Distributed/Runtime/distributed_actor_custom_executor_availability.swift index df915a14f0fbb..50e8c96a7004f 100644 --- a/test/Distributed/Runtime/distributed_actor_custom_executor_availability.swift +++ b/test/Distributed/Runtime/distributed_actor_custom_executor_availability.swift @@ -29,7 +29,7 @@ import Distributed let system = LocalTestingDistributedActorSystem() tests.test("5.7 actor, no availability executor property => no custom executor") { - expectCrashLater(withMessage: "Incorrect actor executor assumption; Expected MainActor executor") + expectCrashLater() try! await FiveSevenActor_NothingExecutor(actorSystem: system).test(x: 42) } @@ -38,7 +38,7 @@ import Distributed } tests.test("5.7 actor, 5.9 executor property => no custom executor") { - expectCrashLater(withMessage: "Incorrect actor executor assumption; Expected MainActor executor") + expectCrashLater() try! await FiveSevenActor_FiveNineExecutor(actorSystem: system).test(x: 42) }