Skip to content

Commit f93d551

Browse files
committed
embedded: change the function representation of directly called witness methods
This is needed in Embedded Swift because the `witness_method` convention requires passing the witness table to the callee. However, the witness table is not necessarily available. A witness table is only generated if an existential value of a protocol is created. This is a rare situation because only witness thunks have `witness_method` convention and those thunks are created as "transparent" functions, which means they are always inlined (after de-virtualization of a witness method call). However, inlining - even of transparent functions - can fail for some reasons. This change adds a new EmbeddedWitnessCallSpecialization pass: If a function with `witness_method` convention is directly called, the function is specialized by changing the convention to `method` and the call is replaced by a call to the specialized function: ``` %1 = function_ref @callee : $@convention(witness_method: P) (@guaranteed C) -> () %2 = apply %1(%0) : $@convention(witness_method: P) (@guaranteed C) -> () ... sil [ossa] @callee : $@convention(witness_method: P) (@guaranteed C) -> () { ... } ``` -> ``` %1 = function_ref @$e6calleeTfr9 : $@convention(method) (@guaranteed C) -> () %2 = apply %1(%0) : $@convention(method) (@guaranteed C) -> () ... // specialized callee sil shared [ossa] @$e6calleeTfr9 : $@convention(method) (@guaranteed C) -> () { ... } ``` Fixes a compiler crash rdar://165184147
1 parent 27961ec commit f93d551

File tree

7 files changed

+227
-0
lines changed

7 files changed

+227
-0
lines changed

SwiftCompilerSources/Sources/Optimizer/FunctionPasses/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ swift_compiler_sources(Optimizer
2121
DeinitDevirtualizer.swift
2222
DestroyHoisting.swift
2323
DiagnoseInfiniteRecursion.swift
24+
EmbeddedWitnessCallSpecialization.swift
2425
InitializeStaticGlobals.swift
2526
LetPropertyLowering.swift
2627
LifetimeDependenceDiagnostics.swift
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
//===--- EmbeddedWitnessCallSpecialization.swift ---------------------------==//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SIL
14+
15+
/// Changes the function representation of directly called witness methods in Embedded Swift.
16+
///
17+
/// If a function with `witness_method` convention is directly called, the function is specialized
18+
/// by changing the convention to `method` and the call is replaced by a call to the specialized
19+
/// function:
20+
///
21+
/// ```
22+
/// %1 = function_ref @callee : $@convention(witness_method: P) (@guaranteed C) -> ()
23+
/// %2 = apply %1(%0) : $@convention(witness_method: P) (@guaranteed C) -> ()
24+
/// ...
25+
/// sil [ossa] @callee : $@convention(witness_method: P) (@guaranteed C) -> () {
26+
/// ...
27+
/// }
28+
/// ```
29+
/// ->
30+
/// ```
31+
/// %1 = function_ref @$e6calleeTfr9 : $@convention(method) (@guaranteed C) -> ()
32+
/// %2 = apply %1(%0) : $@convention(method) (@guaranteed C) -> ()
33+
/// ...
34+
/// // specialized callee
35+
/// sil shared [ossa] @$e6calleeTfr9 : $@convention(method) (@guaranteed C) -> () {
36+
/// ...
37+
/// }
38+
/// ```
39+
///
40+
/// This is needed in Embedded Swift because the `witness_method` convention requires passing the
41+
/// witness table to the callee. However, the witness table is not necessarily available.
42+
/// A witness table is only generated if an existential value of a protocol is created.
43+
///
44+
/// This is a rare situation because only witness thunks have `witness_method` convention and those
45+
/// thunks are created as "transparent" functions, which means they are always inlined (after de-
46+
/// virtualization of a witness method call). However, inlining - even of transparent functions -
47+
/// can fail for some reasons.
48+
///
49+
let embeddedWitnessCallSpecialization = FunctionPass(name: "embedded-witness-call-specialization") {
50+
(function: Function, context: FunctionPassContext) in
51+
52+
guard context.options.enableEmbeddedSwift,
53+
!function.isGeneric
54+
else {
55+
return
56+
}
57+
for inst in function.instructions {
58+
if let apply = inst as? FullApplySite {
59+
specializeDirectWitnessMethodCall(apply: apply, context)
60+
}
61+
}
62+
}
63+
64+
private func specializeDirectWitnessMethodCall(apply: FullApplySite, _ context: FunctionPassContext) {
65+
guard apply.callee.type.functionTypeRepresentation == .witnessMethod,
66+
let callee = apply.referencedFunction,
67+
callee.isDefinition
68+
else {
69+
return
70+
}
71+
72+
let specializedFunctionName = context.mangle(withChangedRepresentation: callee)
73+
74+
let specializedFunction: Function
75+
76+
if let existingSpecializedFunction = context.lookupFunction(name: specializedFunctionName) {
77+
specializedFunction = existingSpecializedFunction
78+
79+
} else {
80+
specializedFunction = context.createSpecializedFunctionDeclaration(
81+
from: callee, withName: specializedFunctionName,
82+
withParams: Array(callee.convention.parameters),
83+
withRepresentation: .method)
84+
85+
context.buildSpecializedFunction(
86+
specializedFunction: specializedFunction,
87+
buildFn: { (specializedFunction, specializedContext) in
88+
cloneFunction(from: callee, toEmpty: specializedFunction, specializedContext)
89+
})
90+
91+
context.notifyNewFunction(function: specializedFunction, derivedFrom: callee)
92+
}
93+
94+
apply.replace(withCallTo: specializedFunction, arguments: Array(apply.arguments), context)
95+
}

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ private func registerSwiftPasses() {
7373
registerPass(allocBoxToStack, { allocBoxToStack.run($0) })
7474
registerPass(asyncDemotion, { asyncDemotion.run($0) })
7575
registerPass(booleanLiteralFolding, { booleanLiteralFolding.run($0) })
76+
registerPass(embeddedWitnessCallSpecialization, { embeddedWitnessCallSpecialization.run($0) })
7677
registerPass(letPropertyLowering, { letPropertyLowering.run($0) })
7778
registerPass(mergeCondFailsPass, { mergeCondFailsPass.run($0) })
7879
registerPass(constantCapturePropagation, { constantCapturePropagation.run($0) })

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ PASS(MandatoryDestroyHoisting, "mandatory-destroy-hoisting",
7575
"Hoist destroy_value instructions for non-lexical values")
7676
PASS(DeadEndBlockDumper, "dump-deadendblocks",
7777
"Tests the DeadEndBlocks utility")
78+
PASS(EmbeddedWitnessCallSpecialization, "embedded-witness-call-specialization",
79+
"Mandatory witness method call specialization")
7880
PASS(EscapeInfoDumper, "dump-escape-info",
7981
"Dumps escape information")
8082
PASS(AddressEscapeInfoDumper, "dump-addr-escape-info",

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
265265
P.addMandatoryPerformanceOptimizations();
266266
P.addOnoneSimplification();
267267
P.addInitializeStaticGlobals();
268+
P.addEmbeddedWitnessCallSpecialization();
268269

269270
P.addMandatoryDestroyHoisting();
270271

@@ -893,6 +894,10 @@ static void addLastChanceOptPassPipeline(SILPassPipelinePlan &P) {
893894
P.addAssumeSingleThreaded();
894895
}
895896

897+
// Needs to run again at the end of the pipeline (after all de-virtualizations
898+
// are done) in case an optimization pass de-virtualizes a witness method call.
899+
P.addEmbeddedWitnessCallSpecialization();
900+
896901
// Emits remarks on all functions with @_assemblyVision attribute.
897902
P.addAssemblyVisionRemarkGenerator();
898903

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// RUN: %target-sil-opt -enable-experimental-feature Embedded %s -embedded-witness-call-specialization | %FileCheck %s
2+
// RUN: %target-sil-opt %s -embedded-witness-call-specialization | %FileCheck --check-prefix=CHECKNE %s
3+
4+
// REQUIRES: swift_feature_Embedded
5+
6+
sil_stage canonical
7+
8+
import Builtin
9+
10+
protocol P {}
11+
12+
class C: P {}
13+
14+
// CHECK-LABEL: sil [ossa] @tst_simple :
15+
// CHECK: [[F:%.*]] = function_ref @$e6calleeTfr9 : $@convention(method) (@guaranteed C) -> ()
16+
// CHECK: apply [[F]](%0) : $@convention(method) (@guaranteed C) -> ()
17+
// CHECK: } // end sil function 'test_simple'
18+
// CHECKNE-LABEL: sil [ossa] @test_simple :
19+
// CHECKNE: [[F:%.*]] = function_ref @callee : $@convention(witness_method: P) (@guaranteed C) -> ()
20+
// CHECKNE: apply [[F]](%0) : $@convention(witness_method: P) (@guaranteed C) -> ()
21+
// CHECKNE: } // end sil function 'test_simple'
22+
sil [ossa] @test_simple : $@convention(thin) (@guaranteed C) -> () {
23+
bb0(%0 : @guaranteed $C):
24+
%1 = function_ref @callee : $@convention(witness_method: P) (@guaranteed C) -> ()
25+
%2 = apply %1(%0) : $@convention(witness_method: P) (@guaranteed C) -> ()
26+
%3 = tuple ()
27+
return %3
28+
}
29+
30+
// CHECK-LABEL: sil [ossa] @second_use :
31+
// CHECK: [[F:%.*]] = function_ref @$e6calleeTfr9 : $@convention(method) (@guaranteed C) -> ()
32+
// CHECK: apply [[F]](%0) : $@convention(method) (@guaranteed C) -> ()
33+
// CHECK: } // end sil function 'second_use'
34+
sil [ossa] @second_use : $@convention(thin) (@guaranteed C) -> () {
35+
bb0(%0 : @guaranteed $C):
36+
%1 = function_ref @callee : $@convention(witness_method: P) (@guaranteed C) -> ()
37+
%2 = apply %1(%0) : $@convention(witness_method: P) (@guaranteed C) -> ()
38+
%3 = tuple ()
39+
return %3
40+
}
41+
42+
// CHECK-LABEL: sil [ossa] @dont_handle_generic_functions :
43+
// CHECK: [[F:%.*]] = function_ref @callee : $@convention(witness_method: P) (@guaranteed C) -> ()
44+
// CHECK: apply [[F]](%0) : $@convention(witness_method: P) (@guaranteed C) -> ()
45+
// CHECK: } // end sil function 'dont_handle_generic_functions'
46+
sil [ossa] @dont_handle_generic_functions : $@convention(thin) <T> (@guaranteed C, @in_guaranteed T) -> () {
47+
bb0(%0 : @guaranteed $C, %1 : $*T):
48+
%2 = function_ref @callee : $@convention(witness_method: P) (@guaranteed C) -> ()
49+
%3 = apply %2(%0) : $@convention(witness_method: P) (@guaranteed C) -> ()
50+
%4 = tuple ()
51+
return %4
52+
}
53+
54+
// CHECK-LABEL: sil [ossa] @only_specialize_witness_methods :
55+
// CHECK: [[F:%.*]] = function_ref @thin_function : $@convention(thin) (@guaranteed C) -> ()
56+
// CHECK: apply [[F]](%0) : $@convention(thin) (@guaranteed C) -> ()
57+
// CHECK: } // end sil function 'only_specialize_witness_methods'
58+
sil [ossa] @only_specialize_witness_methods : $@convention(thin) (@guaranteed C) -> () {
59+
bb0(%0 : @guaranteed $C):
60+
%1 = function_ref @thin_function : $@convention(thin) (@guaranteed C) -> ()
61+
%2 = apply %1(%0) : $@convention(thin) (@guaranteed C) -> ()
62+
%3 = tuple ()
63+
return %3
64+
}
65+
66+
// CHECK-LABEL: sil [ossa] @callee_must_have_body :
67+
// CHECK: [[F:%.*]] = function_ref @no_body : $@convention(witness_method: P) (@guaranteed C) -> ()
68+
// CHECK: apply [[F]](%0) : $@convention(witness_method: P) (@guaranteed C) -> ()
69+
// CHECK: } // end sil function 'callee_must_have_body'
70+
sil [ossa] @callee_must_have_body : $@convention(thin) (@guaranteed C) -> () {
71+
bb0(%0 : @guaranteed $C):
72+
%1 = function_ref @no_body : $@convention(witness_method: P) (@guaranteed C) -> ()
73+
%2 = apply %1(%0) : $@convention(witness_method: P) (@guaranteed C) -> ()
74+
%3 = tuple ()
75+
return %3
76+
}
77+
78+
79+
// CHECK-LABEL: sil shared [ossa] @$e6calleeTfr9 : $@convention(method) (@guaranteed C) -> () {
80+
// CHECK: fix_lifetime %0
81+
// CHECK-NEXT: %2 = tuple ()
82+
// CHECK-NEXT: return %2
83+
// CHECK: } // end sil function '$e6calleeTfr9'
84+
sil [ossa] @callee : $@convention(witness_method: P) (@guaranteed C) -> () {
85+
bb0(%0 : @guaranteed $C):
86+
fix_lifetime %0
87+
%2 = tuple ()
88+
return %2
89+
}
90+
91+
sil [ossa] @thin_function : $@convention(thin) (@guaranteed C) -> () {
92+
bb0(%0 : @guaranteed $C):
93+
fix_lifetime %0
94+
%2 = tuple ()
95+
return %2
96+
}
97+
98+
sil [ossa] @no_body : $@convention(witness_method: P) (@guaranteed C) -> ()

test/embedded/accessors.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,25 @@ public class C {
2121
var y: Int = 27
2222
}
2323

24+
public protocol P {
25+
var d: [Int : WrappedBool] { get set }
26+
}
27+
28+
extension P {
29+
mutating func set(key: Int) {
30+
d[key]?.b = true
31+
}
32+
}
33+
34+
public struct WrappedBool {
35+
public var b: Bool = true
36+
}
37+
38+
public class S: P {
39+
public var d: [Int : WrappedBool] = [:]
40+
public func foo() {}
41+
}
42+
2443
@main
2544
struct Main {
2645
static func main() {
@@ -33,5 +52,11 @@ struct Main {
3352

3453
print("2") // CHECK: 2
3554
print("")
55+
56+
var handler = S()
57+
handler.d[27] = WrappedBool(b: false)
58+
handler.set(key: 27)
59+
// CHECK: true
60+
print(handler.d[27]!.b ? "true" : "false")
3661
}
3762
}

0 commit comments

Comments
 (0)