Skip to content

Commit

Permalink
Merge pull request #76135 from tshortli/backdeployed-ios-apps-on-macos
Browse files Browse the repository at this point in the history
SILOptimizer: Allow inlining of transparent functions in `@backDeployed` thunks
  • Loading branch information
tshortli authored Aug 29, 2024
2 parents 82db11c + 789b795 commit a9120c3
Show file tree
Hide file tree
Showing 14 changed files with 194 additions and 20 deletions.
6 changes: 4 additions & 2 deletions include/swift/SIL/SILFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ enum IsThunk_t {
IsNotThunk,
IsThunk,
IsReabstractionThunk,
IsSignatureOptimizedThunk
IsSignatureOptimizedThunk,
IsBackDeployedThunk,
};
enum IsDynamicallyReplaceable_t {
IsNotDynamic,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -486,6 +487,7 @@ class SILFunction
break;
case IsThunk:
case IsReabstractionThunk:
case IsBackDeployedThunk:
thunkCanHaveSubclassScope = false;
break;
}
Expand Down
1 change: 1 addition & 0 deletions lib/SIL/IR/SILPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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] ";
Expand Down
2 changes: 1 addition & 1 deletion lib/SILGen/SILGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
16 changes: 14 additions & 2 deletions lib/SILOptimizer/Mandatory/MandatoryInlining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion lib/Serialization/ModuleFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
2 changes: 1 addition & 1 deletion lib/Serialization/SILFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ namespace sil_block {
BCRecordLayout<SIL_FUNCTION, SILLinkageField,
BCFixed<1>, // 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
Expand Down
60 changes: 54 additions & 6 deletions stdlib/public/core/Availability.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,73 @@ 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
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.
Expand Down
6 changes: 6 additions & 0 deletions test/IRGen/Inputs/back_deployed.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@backDeployed(before: SwiftStdlib 6.0)
public func backDeployedFunc() {
otherFunc()
}

@usableFromInline internal func otherFunc() {}
3 changes: 2 additions & 1 deletion test/IRGen/availability.swift
Original file line number Diff line number Diff line change
@@ -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

Expand Down
64 changes: 64 additions & 0 deletions test/IRGen/availability_ios.swift
Original file line number Diff line number Diff line change
@@ -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<UnitTemperature>(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")
}
}
17 changes: 17 additions & 0 deletions test/IRGen/back_deployed_Onone.swift
Original file line number Diff line number Diff line change
@@ -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"
20 changes: 20 additions & 0 deletions test/IRGen/back_deployed_Onone_transparent.swift
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 6 additions & 6 deletions test/SILGen/availability_query_maccatalyst_zippered.swift
Original file line number Diff line number Diff line change
@@ -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

Expand Down

0 comments on commit a9120c3

Please sign in to comment.