From 2b46c01e37f82c6cfe42173fec8f04d2ec466fff Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Wed, 28 Aug 2024 14:32:49 -0700 Subject: [PATCH 1/4] Tests: Use %target-cpu in availability_query_maccatalyst_zippered.swift. NFC. --- .../availability_query_maccatalyst_zippered.swift | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/SILGen/availability_query_maccatalyst_zippered.swift b/test/SILGen/availability_query_maccatalyst_zippered.swift index bd413bc982582..a2f698c1ea773 100644 --- a/test/SILGen/availability_query_maccatalyst_zippered.swift +++ b/test/SILGen/availability_query_maccatalyst_zippered.swift @@ -1,11 +1,11 @@ -// RUN: %target-swift-emit-silgen %s -target x86_64-apple-macosx10.52 -target-variant x86_64-apple-ios50.0-macabi | %FileCheck %s -// RUN: %target-swift-emit-silgen %s -target x86_64-apple-ios50.0-macabi -target-variant x86_64-apple-macosx10.52 | %FileCheck %s +// RUN: %target-swift-emit-silgen %s -target %target-cpu-apple-macosx10.52 -target-variant %target-cpu-apple-ios50.0-macabi | %FileCheck %s +// RUN: %target-swift-emit-silgen %s -target %target-cpu-apple-ios50.0-macabi -target-variant %target-cpu-apple-macosx10.52 | %FileCheck %s -// RUN: %target-swift-emit-silgen %s -target x86_64-apple-macosx10.14.4 -target-variant x86_64-apple-ios50.0-macabi | %FileCheck %s --check-prefix=CHECK-BACKDEPLOY-MAC -// RUN: %target-swift-emit-silgen %s -target x86_64-apple-ios50.0-macabi -target-variant x86_64-apple-macosx10.14.4 | %FileCheck %s --check-prefix=CHECK-BACKDEPLOY-MAC +// RUN: %target-swift-emit-silgen %s -target %target-cpu-apple-macosx10.14.4 -target-variant %target-cpu-apple-ios50.0-macabi | %FileCheck %s --check-prefix=CHECK-BACKDEPLOY-MAC +// RUN: %target-swift-emit-silgen %s -target %target-cpu-apple-ios50.0-macabi -target-variant %target-cpu-apple-macosx10.14.4 | %FileCheck %s --check-prefix=CHECK-BACKDEPLOY-MAC -// RUN: %target-swift-emit-silgen %s -target x86_64-apple-macosx10.15 -target-variant x86_64-apple-ios50.0-macabi | %FileCheck %s --check-prefix=CHECK-DEPLOY10_15-MAC -// RUN: %target-swift-emit-silgen %s -target x86_64-apple-ios50.0-macabi -target-variant x86_64-apple-macosx10.15 | %FileCheck %s --check-prefix=CHECK-DEPLOY10_15-MAC +// RUN: %target-swift-emit-silgen %s -target %target-cpu-apple-macosx10.15 -target-variant %target-cpu-apple-ios50.0-macabi | %FileCheck %s --check-prefix=CHECK-DEPLOY10_15-MAC +// RUN: %target-swift-emit-silgen %s -target %target-cpu-apple-ios50.0-macabi -target-variant %target-cpu-apple-macosx10.15 | %FileCheck %s --check-prefix=CHECK-DEPLOY10_15-MAC // REQUIRES: OS=macosx || OS=maccatalyst From 51143aa8ea341be51101027285a9b7f62e9fe89c Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Wed, 28 Aug 2024 13:32:54 -0700 Subject: [PATCH 2/4] stdlib: Upstream _stdlib_isOSVersionAtLeast_AEIC(). In the standard library shipped in Apple's SDKs and OSes, the implementation of `_stdlib_isOSVersionAtLeast()` has diverged in order to solve some tricky issues related to supporting iOS applications running on macOS. It's now time to bring that change upstream in order to unblock further changes that depend on it. Originally introduced to resolve rdar://83378814. --- stdlib/public/core/Availability.swift | 60 ++++++++++++++++++++++++--- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/stdlib/public/core/Availability.swift b/stdlib/public/core/Availability.swift index a437a6ce71d7c..4383b86f2e1e6 100644 --- a/stdlib/public/core/Availability.swift +++ b/stdlib/public/core/Availability.swift @@ -17,6 +17,37 @@ import SwiftShims /// /// This is a magic entry point known to the compiler. It is called in /// generated code for API availability checking. +/// +/// This is marked @_transparent on iOS to work around broken availability +/// checking for iOS apps running on macOS (rdar://83378814). libswiftCore uses +/// the macOS platform identifier for its version check in that scenario, +/// causing all queries to return true. When this function is inlined into the +/// caller, the compiler embeds the correct platform identifier in the client +/// code, and we get the right answer. +/// +/// @_transparent breaks the optimizer's ability to remove availability checks +/// that are unnecessary due to the current deployment target. We call through +/// to the _stdlib_isOSVersionAtLeast_AEIC function below to work around this, +/// as the optimizer is able to perform this optimization for a +/// @_alwaysEmitIntoClient function. We can't use @_alwaysEmitIntoClient +/// directly on this call because it would break ABI for existing apps. +/// +/// `@_transparent` breaks the interpreter mode on macOS, as it creates a direct +/// reference to ___isPlatformVersionAtLeast from compiler-rt, and the +/// interpreter doesn't currently know how to load symbols from compiler-rt. +/// Since `@_transparent` is only necessary for iOS apps, we only apply it on +/// iOS, not any other which would inherit/remap iOS availability. +#if os(iOS) && !os(xrOS) +@_effects(readnone) +@_transparent +public func _stdlib_isOSVersionAtLeast( + _ major: Builtin.Word, + _ minor: Builtin.Word, + _ patch: Builtin.Word +) -> Builtin.Int1 { + return _stdlib_isOSVersionAtLeast_AEIC(major, minor, patch) +} +#else @_semantics("availability.osversion") @_effects(readnone) @_unavailableInEmbedded @@ -24,18 +55,35 @@ public func _stdlib_isOSVersionAtLeast( _ major: Builtin.Word, _ minor: Builtin.Word, _ patch: Builtin.Word +) -> Builtin.Int1 { + return _stdlib_isOSVersionAtLeast_AEIC(major, minor, patch) +} +#endif + +@_semantics("availability.osversion") +@_effects(readnone) +@_alwaysEmitIntoClient +public func _stdlib_isOSVersionAtLeast_AEIC( + _ major: Builtin.Word, + _ minor: Builtin.Word, + _ patch: Builtin.Word ) -> Builtin.Int1 { #if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)) && SWIFT_RUNTIME_OS_VERSIONING if Int(major) == 9999 { return true._value } - let runningVersion = _swift_stdlib_operatingSystemVersion() - - let result = - (runningVersion.majorVersion,runningVersion.minorVersion,runningVersion.patchVersion) - >= (Int(major),Int(minor),Int(patch)) - return result._value + let queryVersion = (Int(major), Int(minor), Int(patch)) + let major32 = Int32(truncatingIfNeeded:Int(queryVersion.0)) + let minor32 = Int32(truncatingIfNeeded:Int(queryVersion.1)) + let patch32 = Int32(truncatingIfNeeded:Int(queryVersion.2)) + + // Defer to a builtin that calls clang's version checking builtin from + // compiler-rt. + let result32 = Int32(Builtin.targetOSVersionAtLeast(major32._value, + minor32._value, + patch32._value)) + return (result32 != (0 as Int32))._value #else // FIXME: As yet, there is no obvious versioning standard for platforms other // than Darwin-based OSes, so we just assume false for now. From 7ad12d73dd15855f471981eebf67da08696ea241 Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Thu, 29 Aug 2024 08:42:03 -0700 Subject: [PATCH 3/4] IRGen: Adjust tests for upstreaming of _stdlib_isOSVersionAtLeast_AEIC(). --- test/IRGen/availability.swift | 3 +- test/IRGen/availability_ios.swift | 64 +++++++++++++++++++ .../codegen_very_large_allocation.swift | 3 + 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 test/IRGen/availability_ios.swift diff --git a/test/IRGen/availability.swift b/test/IRGen/availability.swift index 97c3eeb05cac1..4878e17dc6b99 100644 --- a/test/IRGen/availability.swift +++ b/test/IRGen/availability.swift @@ -1,7 +1,8 @@ // RUN: %target-swift-frontend -primary-file %s -emit-ir | %FileCheck %s // RUN: %target-swift-frontend -primary-file %s -O -emit-ir | %FileCheck %s --check-prefix=OPT -// REQUIRES: objc_interop +// On iOS stdlib_isOSVersionAtLeast() is @_transparent, which affects optimization. +// REQUIRES: OS=macosx || OS=tvos || OS=watchos || OS=xros import Foundation diff --git a/test/IRGen/availability_ios.swift b/test/IRGen/availability_ios.swift new file mode 100644 index 0000000000000..75c8bcd55234c --- /dev/null +++ b/test/IRGen/availability_ios.swift @@ -0,0 +1,64 @@ +// RUN: %target-swift-frontend -primary-file %s -emit-ir | %FileCheck %s +// RUN: %target-swift-frontend -primary-file %s -O -emit-ir | %FileCheck %s --check-prefix=OPT + +// On iOS _stdlib_isOSVersionAtLeast() is @_transparent, which affects optimization. +// See IRGen/availability.swift for other Apple platforms. +// REQUIRES: OS=ios + +import Foundation + +// We mustn't hoist the alloc_stack for measurement out of the availability +// guard. + +// CHECK-LABEL: define{{.*}} @{{.*}}dontHoist +// CHECK-NOT: s10Foundation11MeasurementVySo17NSUnitTemperature +// CHECK: call swiftcc i1 @"$ss31_stdlib_isOSVersionAtLeast_AEICyBi1_Bw_BwBwtF"( +// CHECK: s10Foundation11MeasurementVySo17NSUnitTemperature + +// OPT-LABEL: define{{.*}} @{{.*}}dontHoist +// OPT-NOT: S10Foundation11MeasurementVySo17NSUnitTemperature +// OPT: call {{.*}} @__isPlatformVersionAtLeast( +// OPT: s10Foundation11MeasurementVySo17NSUnitTemperature + +public func dontHoist() { + if #available(macOS 51.0, iOS 54.0, watchOS 57.0, tvOS 54.0, visionOS 51.1, *) { + let measurement = Measurement(value: Double(42), unit: .celsius) + print("\(measurement)") + } else { + print("Not measurement") + } +} + + +// Now that _isOSVersionAtLeast is no longer inlinable, we do still +// mark it as _effects(readnone). +// This means that unlike in the past the optimizer can now only coalesce +// availability checks with the same availability. It does not determine, +// for example, that a check for iOS 10 is sufficient to guarantee that a check +// for iOS 9 will also succeed. + +// With optimizations on, multiple #availability checks should generate only +// a single call into _isOSVersionAtLeast, which after inlining will be a +// call to __isPlatformVersionAtLeast. + +// CHECK-LABEL: define{{.*}} @{{.*}}multipleAvailabilityChecks +// CHECK: call swiftcc i1 @"$ss31_stdlib_isOSVersionAtLeast_AEICyBi1_Bw_BwBwtF"( +// CHECK: call swiftcc i1 @"$ss31_stdlib_isOSVersionAtLeast_AEICyBi1_Bw_BwBwtF"( +// CHECK: call swiftcc i1 @"$ss31_stdlib_isOSVersionAtLeast_AEICyBi1_Bw_BwBwtF"( +// CHECK: ret void + +// OPT-LABEL: define{{.*}} @{{.*}}multipleAvailabilityChecks +// OPT: call {{.*}} @__isPlatformVersionAtLeast +// OPT-NOT: call {{.*}} @$__isPlatformVersionAtLeast +// OPT: ret void +public func multipleAvailabilityChecks() { + if #available(macOS 51.0, iOS 54.0, watchOS 57.0, tvOS 54.0, visionOS 51.1, *) { + print("test one") + } + if #available(macOS 51.0, iOS 54.0, watchOS 57.0, tvOS 54.0, visionOS 51.1, *) { + print("test two") + } + if #available(macOS 51.0, iOS 54.0, watchOS 57.0, tvOS 54.0, visionOS 51.1, *) { + print("test three") + } +} diff --git a/test/IRGen/temporary_allocation/codegen_very_large_allocation.swift b/test/IRGen/temporary_allocation/codegen_very_large_allocation.swift index 0773089e36cf7..8b11f67d09b2d 100644 --- a/test/IRGen/temporary_allocation/codegen_very_large_allocation.swift +++ b/test/IRGen/temporary_allocation/codegen_very_large_allocation.swift @@ -7,6 +7,9 @@ // this function. // UNSUPPORTED: OS=xros +// On iOS _stdlib_isOSVersionAtLeast() is @_transparent, which affects codegen. +// UNSUPPORTED: OS=ios + @_silgen_name("blackHole") func blackHole(_ value: UnsafeMutableRawPointer?) -> Void From 789b795cec77539779012b9d6f38dd5b1e339e07 Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Wed, 28 Aug 2024 21:41:49 -0700 Subject: [PATCH 4/4] SILOptimizer: Allow inlining of transparent functions in @backDeployed thunks. In order for availability checks in iOS apps to be evaluated correctly when running on macOS, the application binary must call a copy of `_stdlib_isOSVersionAtLeast_AEIC()` that was emitted into the app, instead of calling the `_stdlib_isOSVersionAtLeast()` function provided by the standard library. This is because the call to the underlying compiler-rt function `__isPlatformVersionAtLeast()` must be given the correct platform identifier argument; if the call is not emitted into the client, then the macOS platform identifier is used and the iOS version number will be mistakenly interpreted as a macOS version number at runtime. The `_stdlib_isOSVersionAtLeast()` function in the standard library is marked `@_transparent` on iOS so that its call to `_stdlib_isOSVersionAtLeast_AEIC()` is always inlined into the client. This works for the code generated by normal `if #available` checks, but for the `@backDeployed` function thunks, the calls to `_stdlib_isOSVersionAtLeast()` were not being inlined and that was causing calls to `@backDeployed` functions to crash in iOS apps running on macOS since their availability checks were being misevaluated. The SIL optimizer has a heuristic which inhibits mandatory inlining in functions that are classified as thunks, in order to save code size. This heuristic needs to be relaxed in `@backDeployed` thunks, so that mandatory inlining of `_stdlib_isOSVersionAtLeast()` can behave as expected. The change should be safe since the only `@_transparent` function a `@backDeployed` thunk is ever expected to call is `_stdlib_isOSVersionAtLeast()`. Resolves rdar://134793410. --- include/swift/SIL/SILFunction.h | 6 ++++-- lib/SIL/IR/SILPrinter.cpp | 1 + lib/SILGen/SILGen.cpp | 2 +- .../Mandatory/MandatoryInlining.cpp | 16 +++++++++++++-- lib/Serialization/ModuleFormat.h | 2 +- lib/Serialization/SILFormat.h | 2 +- test/IRGen/Inputs/back_deployed.swift | 6 ++++++ test/IRGen/back_deployed_Onone.swift | 17 ++++++++++++++++ .../back_deployed_Onone_transparent.swift | 20 +++++++++++++++++++ 9 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 test/IRGen/Inputs/back_deployed.swift create mode 100644 test/IRGen/back_deployed_Onone.swift create mode 100644 test/IRGen/back_deployed_Onone_transparent.swift diff --git a/include/swift/SIL/SILFunction.h b/include/swift/SIL/SILFunction.h index 4b964da15b67c..b56a794a1f415 100644 --- a/include/swift/SIL/SILFunction.h +++ b/include/swift/SIL/SILFunction.h @@ -56,7 +56,8 @@ enum IsThunk_t { IsNotThunk, IsThunk, IsReabstractionThunk, - IsSignatureOptimizedThunk + IsSignatureOptimizedThunk, + IsBackDeployedThunk, }; enum IsDynamicallyReplaceable_t { IsNotDynamic, @@ -368,7 +369,7 @@ class SILFunction /// /// The inliner uses this information to avoid inlining (non-trivial) /// functions into the thunk. - unsigned Thunk : 2; + unsigned Thunk : 3; /// The scope in which the parent class can be subclassed, if this is a method /// which is contained in the vtable of that class. @@ -486,6 +487,7 @@ class SILFunction break; case IsThunk: case IsReabstractionThunk: + case IsBackDeployedThunk: thunkCanHaveSubclassScope = false; break; } diff --git a/lib/SIL/IR/SILPrinter.cpp b/lib/SIL/IR/SILPrinter.cpp index adbeb317c04d9..bb38af313d6f3 100644 --- a/lib/SIL/IR/SILPrinter.cpp +++ b/lib/SIL/IR/SILPrinter.cpp @@ -3387,6 +3387,7 @@ void SILFunction::print(SILPrintContext &PrintCtx) const { switch (isThunk()) { case IsNotThunk: break; + case IsBackDeployedThunk: // FIXME: Give this a distinct label case IsThunk: OS << "[thunk] "; break; case IsSignatureOptimizedThunk: OS << "[signature_optimized_thunk] "; diff --git a/lib/SILGen/SILGen.cpp b/lib/SILGen/SILGen.cpp index accf459f09a81..9262b0daf97cb 100644 --- a/lib/SILGen/SILGen.cpp +++ b/lib/SILGen/SILGen.cpp @@ -906,7 +906,7 @@ void SILGenModule::emitFunctionDefinition(SILDeclRef constant, SILFunction *f) { preEmitFunction(constant, f, loc); PrettyStackTraceSILFunction X("silgen emitBackDeploymentThunk", f); f->setBare(IsBare); - f->setThunk(IsThunk); + f->setThunk(IsBackDeployedThunk); SILGenFunction(*this, *f, dc).emitBackDeploymentThunk(constant); diff --git a/lib/SILOptimizer/Mandatory/MandatoryInlining.cpp b/lib/SILOptimizer/Mandatory/MandatoryInlining.cpp index 36b79f42f12ca..783b9597c107f 100644 --- a/lib/SILOptimizer/Mandatory/MandatoryInlining.cpp +++ b/lib/SILOptimizer/Mandatory/MandatoryInlining.cpp @@ -1030,10 +1030,22 @@ class MandatoryInlining : public SILModuleTransform { SILOptFunctionBuilder FuncBuilder(*this); for (auto &F : *M) { - // Don't inline into thunks, even transparent callees. - if (F.isThunk()) + switch (F.isThunk()) { + case IsThunk_t::IsThunk: + case IsThunk_t::IsReabstractionThunk: + case IsThunk_t::IsSignatureOptimizedThunk: + // Don't inline into most thunks, even transparent callees. continue; + case IsThunk_t::IsNotThunk: + case IsThunk_t::IsBackDeployedThunk: + // For correctness, inlining _stdlib_isOSVersionAtLeast() when it is + // declared transparent is mandatory in the thunks of @backDeployed + // functions. These thunks will not contain calls to other transparent + // functions. + break; + } + // Skip deserialized functions. if (F.wasDeserializedCanonical()) continue; diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 6ec6237148958..b55bb4d504eda 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 885; // opened existentials +const uint16_t SWIFTMODULE_VERSION_MINOR = 886; // SIL function thunk kind /// A standard hash seed used for all string hashes in a serialized module. /// diff --git a/lib/Serialization/SILFormat.h b/lib/Serialization/SILFormat.h index 38196e664fb3a..6f9aa35f661bb 100644 --- a/lib/Serialization/SILFormat.h +++ b/lib/Serialization/SILFormat.h @@ -292,7 +292,7 @@ namespace sil_block { BCRecordLayout, // transparent BCFixed<2>, // serializedKind - BCFixed<2>, // thunks: signature optimized/reabstraction + BCFixed<3>, // thunk kind BCFixed<1>, // without_actually_escaping BCFixed<3>, // specialPurpose BCFixed<2>, // inlineStrategy diff --git a/test/IRGen/Inputs/back_deployed.swift b/test/IRGen/Inputs/back_deployed.swift new file mode 100644 index 0000000000000..452a1e198def5 --- /dev/null +++ b/test/IRGen/Inputs/back_deployed.swift @@ -0,0 +1,6 @@ +@backDeployed(before: SwiftStdlib 6.0) +public func backDeployedFunc() { + otherFunc() +} + +@usableFromInline internal func otherFunc() {} diff --git a/test/IRGen/back_deployed_Onone.swift b/test/IRGen/back_deployed_Onone.swift new file mode 100644 index 0000000000000..4b0a4c6d5c74c --- /dev/null +++ b/test/IRGen/back_deployed_Onone.swift @@ -0,0 +1,17 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-module %S/Inputs/back_deployed.swift -o %t/ -swift-version 5 -enable-library-evolution +// RUN: %target-swift-frontend -emit-ir %s -I %t -Onone | %FileCheck %s + +// _stdlib_isOSVersionAtLeast() is not @_transparent on macOS, watchOS, and tvOS +// REQUIRES: OS=macosx || OS=watchos || OS=tvos + +import back_deployed + +public func test() { + backDeployedFunc() +} + +// CHECK: define{{.*}} hidden swiftcc void @"$s13back_deployed0A12DeployedFuncyyFTwb" +// CHECK: call swiftcc i1 @"$ss26_stdlib_isOSVersionAtLeastyBi1_Bw_BwBwtF" +// CHECK: call swiftcc void @"$s13back_deployed0A12DeployedFuncyyFTwB" +// CHECK: call swiftcc void @"$s13back_deployed0A12DeployedFuncyyF" diff --git a/test/IRGen/back_deployed_Onone_transparent.swift b/test/IRGen/back_deployed_Onone_transparent.swift new file mode 100644 index 0000000000000..46016f80daa42 --- /dev/null +++ b/test/IRGen/back_deployed_Onone_transparent.swift @@ -0,0 +1,20 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend -emit-module %S/Inputs/back_deployed.swift -o %t/ -swift-version 5 -enable-library-evolution +// RUN: %target-swift-frontend -emit-ir %s -I %t -Onone | %FileCheck %s + +// _stdlib_isOSVersionAtLeast() is @_transparent on iOS +// REQUIRES: OS=ios + +import back_deployed + +public func test() { + backDeployedFunc() +} + +// CHECK: define{{.*}} hidden swiftcc void @"$s13back_deployed0A12DeployedFuncyyFTwb" +// CHECK: call swiftcc i1 @"$ss31_stdlib_isOSVersionAtLeast_AEICyBi1_Bw_BwBwtF" +// CHECK: call swiftcc void @"$s13back_deployed0A12DeployedFuncyyFTwB" +// CHECK: call swiftcc void @"$s13back_deployed0A12DeployedFuncyyF" + +// CHECK: define{{.*}} hidden swiftcc i1 @"$ss31_stdlib_isOSVersionAtLeast_AEICyBi1_Bw_BwBwtF" +// CHECK: call i32 @__isPlatformVersionAtLeast