Skip to content

Commit

Permalink
SILOptimizer: Allow inlining of transparent functions in @backDeploye…
Browse files Browse the repository at this point in the history
…d 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.
  • Loading branch information
tshortli committed Aug 29, 2024
1 parent 51373ba commit 0612c93
Show file tree
Hide file tree
Showing 9 changed files with 65 additions and 7 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 @@ -488,6 +489,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 @@ -3355,6 +3355,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 @@ -909,7 +909,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 @@ -1027,10 +1027,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 = 870; // SerializePackageEnabled / [serialized_for_package] for SILFunctionLayout / package field in SerializedKind_t
const uint16_t SWIFTMODULE_VERSION_MINOR = 871; // 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
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() {}
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

0 comments on commit 0612c93

Please sign in to comment.