Skip to content

[6.0.1] SILOptimizer: Allow inlining of transparent functions in @backDeployed thunks #76218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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(visionOS)
@_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
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def copy_embedded_compiler_rt_builtins_from_darwin_host_toolchain(
' into the local clang build directory {}.'.format(
host_cxx_builtins_dir, dest_builtins_dir))

for _os in ['ios', 'watchos', 'tvos']:
for _os in ['ios', 'watchos', 'tvos', 'xros']:
# Copy over the device .a when necessary
lib_name = 'libclang_rt.{}.a'.format(_os)
host_lib_path = os.path.join(host_cxx_builtins_dir, lib_name)
Expand Down