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.
This commit is contained in:
Allan Shortlidge
2024-08-28 21:41:49 -07:00
parent 7ad12d73dd
commit 789b795cec
9 changed files with 65 additions and 7 deletions

View File

@@ -56,7 +56,8 @@ enum IsThunk_t {
IsNotThunk,
IsThunk,
IsReabstractionThunk,
IsSignatureOptimizedThunk
IsSignatureOptimizedThunk,
IsBackDeployedThunk,
};
enum IsDynamicallyReplaceable_t {
IsNotDynamic,
@@ -368,7 +369,7 @@ private:
///
/// 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 @@ private:
break;
case IsThunk:
case IsReabstractionThunk:
case IsBackDeployedThunk:
thunkCanHaveSubclassScope = false;
break;
}

View File

@@ -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] ";

View File

@@ -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);

View File

@@ -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;

View File

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

View File

@@ -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

View File

@@ -0,0 +1,6 @@
@backDeployed(before: SwiftStdlib 6.0)
public func backDeployedFunc() {
otherFunc()
}
@usableFromInline internal func otherFunc() {}

View File

@@ -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"

View File

@@ -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