Skip to content

Commit a9120c3

Browse files
authored
Merge pull request #76135 from tshortli/backdeployed-ios-apps-on-macos
SILOptimizer: Allow inlining of transparent functions in `@backDeployed` thunks
2 parents 82db11c + 789b795 commit a9120c3

File tree

14 files changed

+194
-20
lines changed

14 files changed

+194
-20
lines changed

include/swift/SIL/SILFunction.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ enum IsThunk_t {
5656
IsNotThunk,
5757
IsThunk,
5858
IsReabstractionThunk,
59-
IsSignatureOptimizedThunk
59+
IsSignatureOptimizedThunk,
60+
IsBackDeployedThunk,
6061
};
6162
enum IsDynamicallyReplaceable_t {
6263
IsNotDynamic,
@@ -368,7 +369,7 @@ class SILFunction
368369
///
369370
/// The inliner uses this information to avoid inlining (non-trivial)
370371
/// functions into the thunk.
371-
unsigned Thunk : 2;
372+
unsigned Thunk : 3;
372373

373374
/// The scope in which the parent class can be subclassed, if this is a method
374375
/// which is contained in the vtable of that class.
@@ -486,6 +487,7 @@ class SILFunction
486487
break;
487488
case IsThunk:
488489
case IsReabstractionThunk:
490+
case IsBackDeployedThunk:
489491
thunkCanHaveSubclassScope = false;
490492
break;
491493
}

lib/SIL/IR/SILPrinter.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3387,6 +3387,7 @@ void SILFunction::print(SILPrintContext &PrintCtx) const {
33873387

33883388
switch (isThunk()) {
33893389
case IsNotThunk: break;
3390+
case IsBackDeployedThunk: // FIXME: Give this a distinct label
33903391
case IsThunk: OS << "[thunk] "; break;
33913392
case IsSignatureOptimizedThunk:
33923393
OS << "[signature_optimized_thunk] ";

lib/SILGen/SILGen.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,7 @@ void SILGenModule::emitFunctionDefinition(SILDeclRef constant, SILFunction *f) {
906906
preEmitFunction(constant, f, loc);
907907
PrettyStackTraceSILFunction X("silgen emitBackDeploymentThunk", f);
908908
f->setBare(IsBare);
909-
f->setThunk(IsThunk);
909+
f->setThunk(IsBackDeployedThunk);
910910

911911
SILGenFunction(*this, *f, dc).emitBackDeploymentThunk(constant);
912912

lib/SILOptimizer/Mandatory/MandatoryInlining.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,10 +1030,22 @@ class MandatoryInlining : public SILModuleTransform {
10301030

10311031
SILOptFunctionBuilder FuncBuilder(*this);
10321032
for (auto &F : *M) {
1033-
// Don't inline into thunks, even transparent callees.
1034-
if (F.isThunk())
1033+
switch (F.isThunk()) {
1034+
case IsThunk_t::IsThunk:
1035+
case IsThunk_t::IsReabstractionThunk:
1036+
case IsThunk_t::IsSignatureOptimizedThunk:
1037+
// Don't inline into most thunks, even transparent callees.
10351038
continue;
10361039

1040+
case IsThunk_t::IsNotThunk:
1041+
case IsThunk_t::IsBackDeployedThunk:
1042+
// For correctness, inlining _stdlib_isOSVersionAtLeast() when it is
1043+
// declared transparent is mandatory in the thunks of @backDeployed
1044+
// functions. These thunks will not contain calls to other transparent
1045+
// functions.
1046+
break;
1047+
}
1048+
10371049
// Skip deserialized functions.
10381050
if (F.wasDeserializedCanonical())
10391051
continue;

lib/Serialization/ModuleFormat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5858
/// describe what change you made. The content of this comment isn't important;
5959
/// it just ensures a conflict if two people change the module format.
6060
/// Don't worry about adhering to the 80-column limit for this line.
61-
const uint16_t SWIFTMODULE_VERSION_MINOR = 885; // opened existentials
61+
const uint16_t SWIFTMODULE_VERSION_MINOR = 886; // SIL function thunk kind
6262

6363
/// A standard hash seed used for all string hashes in a serialized module.
6464
///

lib/Serialization/SILFormat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ namespace sil_block {
292292
BCRecordLayout<SIL_FUNCTION, SILLinkageField,
293293
BCFixed<1>, // transparent
294294
BCFixed<2>, // serializedKind
295-
BCFixed<2>, // thunks: signature optimized/reabstraction
295+
BCFixed<3>, // thunk kind
296296
BCFixed<1>, // without_actually_escaping
297297
BCFixed<3>, // specialPurpose
298298
BCFixed<2>, // inlineStrategy

stdlib/public/core/Availability.swift

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,73 @@ import SwiftShims
1717
///
1818
/// This is a magic entry point known to the compiler. It is called in
1919
/// generated code for API availability checking.
20+
///
21+
/// This is marked @_transparent on iOS to work around broken availability
22+
/// checking for iOS apps running on macOS (rdar://83378814). libswiftCore uses
23+
/// the macOS platform identifier for its version check in that scenario,
24+
/// causing all queries to return true. When this function is inlined into the
25+
/// caller, the compiler embeds the correct platform identifier in the client
26+
/// code, and we get the right answer.
27+
///
28+
/// @_transparent breaks the optimizer's ability to remove availability checks
29+
/// that are unnecessary due to the current deployment target. We call through
30+
/// to the _stdlib_isOSVersionAtLeast_AEIC function below to work around this,
31+
/// as the optimizer is able to perform this optimization for a
32+
/// @_alwaysEmitIntoClient function. We can't use @_alwaysEmitIntoClient
33+
/// directly on this call because it would break ABI for existing apps.
34+
///
35+
/// `@_transparent` breaks the interpreter mode on macOS, as it creates a direct
36+
/// reference to ___isPlatformVersionAtLeast from compiler-rt, and the
37+
/// interpreter doesn't currently know how to load symbols from compiler-rt.
38+
/// Since `@_transparent` is only necessary for iOS apps, we only apply it on
39+
/// iOS, not any other which would inherit/remap iOS availability.
40+
#if os(iOS) && !os(xrOS)
41+
@_effects(readnone)
42+
@_transparent
43+
public func _stdlib_isOSVersionAtLeast(
44+
_ major: Builtin.Word,
45+
_ minor: Builtin.Word,
46+
_ patch: Builtin.Word
47+
) -> Builtin.Int1 {
48+
return _stdlib_isOSVersionAtLeast_AEIC(major, minor, patch)
49+
}
50+
#else
2051
@_semantics("availability.osversion")
2152
@_effects(readnone)
2253
@_unavailableInEmbedded
2354
public func _stdlib_isOSVersionAtLeast(
2455
_ major: Builtin.Word,
2556
_ minor: Builtin.Word,
2657
_ patch: Builtin.Word
58+
) -> Builtin.Int1 {
59+
return _stdlib_isOSVersionAtLeast_AEIC(major, minor, patch)
60+
}
61+
#endif
62+
63+
@_semantics("availability.osversion")
64+
@_effects(readnone)
65+
@_alwaysEmitIntoClient
66+
public func _stdlib_isOSVersionAtLeast_AEIC(
67+
_ major: Builtin.Word,
68+
_ minor: Builtin.Word,
69+
_ patch: Builtin.Word
2770
) -> Builtin.Int1 {
2871
#if (os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS)) && SWIFT_RUNTIME_OS_VERSIONING
2972
if Int(major) == 9999 {
3073
return true._value
3174
}
32-
let runningVersion = _swift_stdlib_operatingSystemVersion()
33-
34-
let result =
35-
(runningVersion.majorVersion,runningVersion.minorVersion,runningVersion.patchVersion)
36-
>= (Int(major),Int(minor),Int(patch))
3775

38-
return result._value
76+
let queryVersion = (Int(major), Int(minor), Int(patch))
77+
let major32 = Int32(truncatingIfNeeded:Int(queryVersion.0))
78+
let minor32 = Int32(truncatingIfNeeded:Int(queryVersion.1))
79+
let patch32 = Int32(truncatingIfNeeded:Int(queryVersion.2))
80+
81+
// Defer to a builtin that calls clang's version checking builtin from
82+
// compiler-rt.
83+
let result32 = Int32(Builtin.targetOSVersionAtLeast(major32._value,
84+
minor32._value,
85+
patch32._value))
86+
return (result32 != (0 as Int32))._value
3987
#else
4088
// FIXME: As yet, there is no obvious versioning standard for platforms other
4189
// than Darwin-based OSes, so we just assume false for now.

test/IRGen/Inputs/back_deployed.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@backDeployed(before: SwiftStdlib 6.0)
2+
public func backDeployedFunc() {
3+
otherFunc()
4+
}
5+
6+
@usableFromInline internal func otherFunc() {}

test/IRGen/availability.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// RUN: %target-swift-frontend -primary-file %s -emit-ir | %FileCheck %s
22
// RUN: %target-swift-frontend -primary-file %s -O -emit-ir | %FileCheck %s --check-prefix=OPT
33

4-
// REQUIRES: objc_interop
4+
// On iOS stdlib_isOSVersionAtLeast() is @_transparent, which affects optimization.
5+
// REQUIRES: OS=macosx || OS=tvos || OS=watchos || OS=xros
56

67
import Foundation
78

test/IRGen/availability_ios.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// RUN: %target-swift-frontend -primary-file %s -emit-ir | %FileCheck %s
2+
// RUN: %target-swift-frontend -primary-file %s -O -emit-ir | %FileCheck %s --check-prefix=OPT
3+
4+
// On iOS _stdlib_isOSVersionAtLeast() is @_transparent, which affects optimization.
5+
// See IRGen/availability.swift for other Apple platforms.
6+
// REQUIRES: OS=ios
7+
8+
import Foundation
9+
10+
// We mustn't hoist the alloc_stack for measurement out of the availability
11+
// guard.
12+
13+
// CHECK-LABEL: define{{.*}} @{{.*}}dontHoist
14+
// CHECK-NOT: s10Foundation11MeasurementVySo17NSUnitTemperature
15+
// CHECK: call swiftcc i1 @"$ss31_stdlib_isOSVersionAtLeast_AEICyBi1_Bw_BwBwtF"(
16+
// CHECK: s10Foundation11MeasurementVySo17NSUnitTemperature
17+
18+
// OPT-LABEL: define{{.*}} @{{.*}}dontHoist
19+
// OPT-NOT: S10Foundation11MeasurementVySo17NSUnitTemperature
20+
// OPT: call {{.*}} @__isPlatformVersionAtLeast(
21+
// OPT: s10Foundation11MeasurementVySo17NSUnitTemperature
22+
23+
public func dontHoist() {
24+
if #available(macOS 51.0, iOS 54.0, watchOS 57.0, tvOS 54.0, visionOS 51.1, *) {
25+
let measurement = Measurement<UnitTemperature>(value: Double(42), unit: .celsius)
26+
print("\(measurement)")
27+
} else {
28+
print("Not measurement")
29+
}
30+
}
31+
32+
33+
// Now that _isOSVersionAtLeast is no longer inlinable, we do still
34+
// mark it as _effects(readnone).
35+
// This means that unlike in the past the optimizer can now only coalesce
36+
// availability checks with the same availability. It does not determine,
37+
// for example, that a check for iOS 10 is sufficient to guarantee that a check
38+
// for iOS 9 will also succeed.
39+
40+
// With optimizations on, multiple #availability checks should generate only
41+
// a single call into _isOSVersionAtLeast, which after inlining will be a
42+
// call to __isPlatformVersionAtLeast.
43+
44+
// CHECK-LABEL: define{{.*}} @{{.*}}multipleAvailabilityChecks
45+
// CHECK: call swiftcc i1 @"$ss31_stdlib_isOSVersionAtLeast_AEICyBi1_Bw_BwBwtF"(
46+
// CHECK: call swiftcc i1 @"$ss31_stdlib_isOSVersionAtLeast_AEICyBi1_Bw_BwBwtF"(
47+
// CHECK: call swiftcc i1 @"$ss31_stdlib_isOSVersionAtLeast_AEICyBi1_Bw_BwBwtF"(
48+
// CHECK: ret void
49+
50+
// OPT-LABEL: define{{.*}} @{{.*}}multipleAvailabilityChecks
51+
// OPT: call {{.*}} @__isPlatformVersionAtLeast
52+
// OPT-NOT: call {{.*}} @$__isPlatformVersionAtLeast
53+
// OPT: ret void
54+
public func multipleAvailabilityChecks() {
55+
if #available(macOS 51.0, iOS 54.0, watchOS 57.0, tvOS 54.0, visionOS 51.1, *) {
56+
print("test one")
57+
}
58+
if #available(macOS 51.0, iOS 54.0, watchOS 57.0, tvOS 54.0, visionOS 51.1, *) {
59+
print("test two")
60+
}
61+
if #available(macOS 51.0, iOS 54.0, watchOS 57.0, tvOS 54.0, visionOS 51.1, *) {
62+
print("test three")
63+
}
64+
}

test/IRGen/back_deployed_Onone.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module %S/Inputs/back_deployed.swift -o %t/ -swift-version 5 -enable-library-evolution
3+
// RUN: %target-swift-frontend -emit-ir %s -I %t -Onone | %FileCheck %s
4+
5+
// _stdlib_isOSVersionAtLeast() is not @_transparent on macOS, watchOS, and tvOS
6+
// REQUIRES: OS=macosx || OS=watchos || OS=tvos
7+
8+
import back_deployed
9+
10+
public func test() {
11+
backDeployedFunc()
12+
}
13+
14+
// CHECK: define{{.*}} hidden swiftcc void @"$s13back_deployed0A12DeployedFuncyyFTwb"
15+
// CHECK: call swiftcc i1 @"$ss26_stdlib_isOSVersionAtLeastyBi1_Bw_BwBwtF"
16+
// CHECK: call swiftcc void @"$s13back_deployed0A12DeployedFuncyyFTwB"
17+
// CHECK: call swiftcc void @"$s13back_deployed0A12DeployedFuncyyF"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-module %S/Inputs/back_deployed.swift -o %t/ -swift-version 5 -enable-library-evolution
3+
// RUN: %target-swift-frontend -emit-ir %s -I %t -Onone | %FileCheck %s
4+
5+
// _stdlib_isOSVersionAtLeast() is @_transparent on iOS
6+
// REQUIRES: OS=ios
7+
8+
import back_deployed
9+
10+
public func test() {
11+
backDeployedFunc()
12+
}
13+
14+
// CHECK: define{{.*}} hidden swiftcc void @"$s13back_deployed0A12DeployedFuncyyFTwb"
15+
// CHECK: call swiftcc i1 @"$ss31_stdlib_isOSVersionAtLeast_AEICyBi1_Bw_BwBwtF"
16+
// CHECK: call swiftcc void @"$s13back_deployed0A12DeployedFuncyyFTwB"
17+
// CHECK: call swiftcc void @"$s13back_deployed0A12DeployedFuncyyF"
18+
19+
// CHECK: define{{.*}} hidden swiftcc i1 @"$ss31_stdlib_isOSVersionAtLeast_AEICyBi1_Bw_BwBwtF"
20+
// CHECK: call i32 @__isPlatformVersionAtLeast

test/IRGen/temporary_allocation/codegen_very_large_allocation.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
// this function.
88
// UNSUPPORTED: OS=xros
99

10+
// On iOS _stdlib_isOSVersionAtLeast() is @_transparent, which affects codegen.
11+
// UNSUPPORTED: OS=ios
12+
1013
@_silgen_name("blackHole")
1114
func blackHole(_ value: UnsafeMutableRawPointer?) -> Void
1215

test/SILGen/availability_query_maccatalyst_zippered.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
// RUN: %target-swift-emit-silgen %s -target x86_64-apple-macosx10.52 -target-variant x86_64-apple-ios50.0-macabi | %FileCheck %s
2-
// RUN: %target-swift-emit-silgen %s -target x86_64-apple-ios50.0-macabi -target-variant x86_64-apple-macosx10.52 | %FileCheck %s
1+
// RUN: %target-swift-emit-silgen %s -target %target-cpu-apple-macosx10.52 -target-variant %target-cpu-apple-ios50.0-macabi | %FileCheck %s
2+
// RUN: %target-swift-emit-silgen %s -target %target-cpu-apple-ios50.0-macabi -target-variant %target-cpu-apple-macosx10.52 | %FileCheck %s
33

4-
// 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
5-
// 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
4+
// 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
5+
// 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
66

7-
// 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
8-
// 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
7+
// 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
8+
// 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
99

1010
// REQUIRES: OS=macosx || OS=maccatalyst
1111

0 commit comments

Comments
 (0)