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/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. 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/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/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 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 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