Skip to content

Commit 181b2f1

Browse files
committed
Optimizer: eliminate struct_extracts of an owned struct where the struct_extracts are inside a borrow scope
This is done by splitting the `begin_borrow` of the whole struct into individual borrows of the fields (for trivial fields no borrow is needed). And then sinking the `struct` to it's consuming use(s). ``` %3 = struct $S(%nonTrivialField, %trivialField) // owned ... %4 = begin_borrow %3 %5 = struct_extract %4, #S.nonTrivialField %6 = struct_extract %4, #S.trivialField use %5, %6 end_borrow %4 ... end_of_lifetime %3 ``` -> ``` ... %5 = begin_borrow %nonTrivialField use %5, %trivialField end_borrow %5 ... %3 = struct $S(%nonTrivialField, %trivialField) end_of_lifetime %3 ``` This optimization is important for Array code where the Array buffer is constantly wrapped into structs and then extracted again to access the buffer.
1 parent 87a9d4c commit 181b2f1

File tree

5 files changed

+316
-0
lines changed

5 files changed

+316
-0
lines changed

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ swift_compiler_sources(Optimizer
3838
SimplifyRefCasts.swift
3939
SimplifyRetainReleaseValue.swift
4040
SimplifyStrongRetainRelease.swift
41+
SimplifyStruct.swift
4142
SimplifyStructExtract.swift
4243
SimplifySwitchEnum.swift
4344
SimplifyTuple.swift
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//===--- SimplifyStruct.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+
extension StructInst : Simplifiable, SILCombineSimplifiable {
16+
17+
/// Eliminates `struct_extract`s of an owned `struct` where the `struct_extract`s are inside a
18+
/// a borrow scope.
19+
/// This is done by splitting the `begin_borrow` of the whole struct into individual borrows of the fields
20+
/// (for trivial fields no borrow is needed). And then sinking the `struct` to it's consuming use(s).
21+
///
22+
/// ```
23+
/// %3 = struct $S(%nonTrivialField, %trivialField) // owned
24+
/// ...
25+
/// %4 = begin_borrow %3
26+
/// %5 = struct_extract %4, #S.nonTrivialField
27+
/// %6 = struct_extract %4, #S.trivialField
28+
/// use %5, %6
29+
/// end_borrow %4
30+
/// ...
31+
/// end_of_lifetime %3
32+
/// ```
33+
/// ->
34+
/// ```
35+
/// ...
36+
/// %5 = begin_borrow %nonTrivialField
37+
/// use %5, %trivialField
38+
/// end_borrow %5
39+
/// ...
40+
/// %3 = struct $S(%nonTrivialField, %trivialField)
41+
/// end_of_lifetime %3
42+
/// ```
43+
func simplify(_ context: SimplifyContext) {
44+
guard ownership == .owned,
45+
hasOnlyStructExtractUsesInBorrowScopes()
46+
else {
47+
return
48+
}
49+
50+
for beginBorrow in uses.users(ofType: BeginBorrowInst.self) {
51+
splitAndRemoveStructExtracts(beginBorrow: beginBorrow, context)
52+
}
53+
54+
self.sinkToEndOfLifetime(context)
55+
56+
context.erase(instructionIncludingAllUsers: self)
57+
}
58+
59+
private func hasOnlyStructExtractUsesInBorrowScopes() -> Bool {
60+
var hasStructExtract = false
61+
62+
for use in uses.ignoreDebugUses {
63+
switch use.instruction {
64+
case let beginBorrow as BeginBorrowInst:
65+
for borrowUse in beginBorrow.uses.ignoreDebugUses {
66+
switch borrowUse.instruction {
67+
case is EndBorrowInst:
68+
break
69+
case is StructExtractInst:
70+
hasStructExtract = true
71+
default:
72+
return false
73+
}
74+
}
75+
default:
76+
guard use.endsLifetime else {
77+
return false
78+
}
79+
}
80+
}
81+
return hasStructExtract
82+
}
83+
84+
private func splitAndRemoveStructExtracts(beginBorrow: BeginBorrowInst, _ context: SimplifyContext) {
85+
for structExtract in beginBorrow.uses.users(ofType: StructExtractInst.self) {
86+
let field = self.operands[structExtract.fieldIndex].value
87+
switch structExtract.ownership {
88+
case .none:
89+
structExtract.replace(with: field, context)
90+
case .guaranteed:
91+
let beginBuilder = Builder(before: beginBorrow, context)
92+
let borrowedField = beginBuilder.createBeginBorrow(of: field,
93+
isLexical: beginBorrow.isLexical,
94+
hasPointerEscape: beginBorrow.hasPointerEscape)
95+
structExtract.replace(with: borrowedField, context)
96+
for endBorrow in beginBorrow.endInstructions {
97+
let endBuilder = Builder(before: endBorrow, context)
98+
endBuilder.createEndBorrow(of: borrowedField)
99+
}
100+
case .owned, .unowned:
101+
fatalError("wrong ownership of struct_extract")
102+
}
103+
}
104+
}
105+
private func sinkToEndOfLifetime(_ context: SimplifyContext) {
106+
for use in uses where use.endsLifetime {
107+
let builder = Builder(before: use.instruction, context)
108+
let delayedStruct = builder.createStruct(type: type, elements: Array(operands.values))
109+
use.set(to: delayedStruct, context)
110+
}
111+
}
112+
}

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ private func registerSwiftPasses() {
115115
registerForSILCombine(BuiltinInst.self, { run(BuiltinInst.self, $0) })
116116
registerForSILCombine(FixLifetimeInst.self, { run(FixLifetimeInst.self, $0) })
117117
registerForSILCombine(GlobalValueInst.self, { run(GlobalValueInst.self, $0) })
118+
registerForSILCombine(StructInst.self, { run(StructInst.self, $0) })
118119
registerForSILCombine(StrongRetainInst.self, { run(StrongRetainInst.self, $0) })
119120
registerForSILCombine(StrongReleaseInst.self, { run(StrongReleaseInst.self, $0) })
120121
registerForSILCombine(RetainValueInst.self, { run(RetainValueInst.self, $0) })

lib/SILOptimizer/SILCombiner/Simplifications.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ INSTRUCTION_SIMPLIFICATION(ExplicitCopyAddrInst)
3636
INSTRUCTION_SIMPLIFICATION(ExplicitCopyValueInst)
3737
INSTRUCTION_SIMPLIFICATION(FixLifetimeInst)
3838
INSTRUCTION_SIMPLIFICATION(GlobalValueInst)
39+
INSTRUCTION_SIMPLIFICATION(StructInst)
3940
INSTRUCTION_SIMPLIFICATION(StrongRetainInst)
4041
INSTRUCTION_SIMPLIFICATION(StrongReleaseInst)
4142
INSTRUCTION_SIMPLIFICATION(RetainValueInst)
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// RUN: %target-sil-opt %s -simplification -simplify-instruction=struct | %FileCheck %s
2+
3+
import Swift
4+
import Builtin
5+
6+
struct S {
7+
let a: String
8+
let b: Int
9+
}
10+
11+
struct Outer {
12+
let s: S
13+
}
14+
15+
// CHECK-LABEL: sil [ossa] @simple_delay_struct :
16+
// CHECK: bb0
17+
// CHECK-NEXT: [[B:%.*]] = begin_borrow %0
18+
// CHECK-NEXT: fix_lifetime [[B]]
19+
// CHECK-NEXT: fix_lifetime %1
20+
// CHECK-NEXT: end_borrow [[B]]
21+
// CHECK-NEXT: [[S:%.*]] = struct $S (%0, %1)
22+
// CHECK-NEXT: destroy_value [[S]]
23+
// CHECK: } // end sil function 'simple_delay_struct'
24+
sil [ossa] @simple_delay_struct : $@convention(thin) (@owned String, Int) -> () {
25+
bb0(%0 : @owned $String, %1 : $Int):
26+
%2 = struct $S (%0, %1)
27+
debug_value %2, name "x"
28+
%3 = begin_borrow %2
29+
debug_value %3, name "x"
30+
%4 = struct_extract %3, #S.a
31+
%5 = struct_extract %3, #S.b
32+
fix_lifetime %4
33+
fix_lifetime %5
34+
end_borrow %3
35+
destroy_value %2
36+
%r = tuple ()
37+
return %r : $()
38+
}
39+
40+
// CHECK-LABEL: sil [ossa] @test_nested :
41+
// CHECK: bb0
42+
// CHECK-NEXT: [[B:%.*]] = begin_borrow %0
43+
// CHECK-NEXT: fix_lifetime [[B]]
44+
// CHECK-NEXT: fix_lifetime %1
45+
// CHECK-NEXT: end_borrow [[B]]
46+
// CHECK-NEXT: [[S:%.*]] = struct $S (%0, %1)
47+
// CHECK-NEXT: [[OUTER:%.*]] = struct $Outer ([[S]])
48+
// CHECK-NEXT: destroy_value [[OUTER]]
49+
// CHECK: } // end sil function 'test_nested'
50+
sil [ossa] @test_nested : $@convention(thin) (@owned String, Int) -> () {
51+
bb0(%0 : @owned $String, %1 : $Int):
52+
%2 = struct $S (%0, %1)
53+
%3 = struct $Outer (%2)
54+
%4 = begin_borrow %3
55+
%5 = struct_extract %4, #Outer.s
56+
%6 = struct_extract %5, #S.a
57+
%7 = struct_extract %5, #S.b
58+
fix_lifetime %6
59+
fix_lifetime %7
60+
end_borrow %4
61+
destroy_value %3
62+
%r = tuple ()
63+
return %r : $()
64+
}
65+
66+
// CHECK-LABEL: sil [ossa] @multi_lifetime_ends :
67+
// CHECK: bb0
68+
// CHECK-NEXT: [[B:%.*]] = begin_borrow %0
69+
// CHECK-NEXT: fix_lifetime [[B]]
70+
// CHECK-NEXT: fix_lifetime %1
71+
// CHECK-NEXT: end_borrow [[B]]
72+
// CHECK: bb1:
73+
// CHECK-NEXT: [[S1:%.*]] = struct $S (%0, %1)
74+
// CHECK-NEXT: destroy_value [[S1]]
75+
// CHECK: bb2:
76+
// CHECK-NEXT: [[S2:%.*]] = struct $S (%0, %1)
77+
// CHECK-NEXT: destroy_value [[S2]]
78+
// CHECK: } // end sil function 'multi_lifetime_ends'
79+
sil [ossa] @multi_lifetime_ends : $@convention(thin) (@owned String, Int) -> () {
80+
bb0(%0 : @owned $String, %1 : $Int):
81+
%2 = struct $S (%0, %1)
82+
%3 = begin_borrow %2
83+
%4 = struct_extract %3, #S.a
84+
%5 = struct_extract %3, #S.b
85+
fix_lifetime %4
86+
fix_lifetime %5
87+
end_borrow %3
88+
cond_br undef, bb1, bb2
89+
bb1:
90+
destroy_value %2
91+
br bb3
92+
bb2:
93+
destroy_value %2
94+
br bb3
95+
bb3:
96+
%r = tuple ()
97+
return %r : $()
98+
}
99+
100+
// CHECK-LABEL: sil [ossa] @multi_borrows :
101+
// CHECK: bb1:
102+
// CHECK-NEXT: [[B:%.*]] = begin_borrow %0
103+
// CHECK-NEXT: fix_lifetime [[B]]
104+
// CHECK-NEXT: end_borrow [[B]]
105+
// CHECK: bb2:
106+
// CHECK-NEXT: fix_lifetime %1
107+
// CHECK: bb3:
108+
// CHECK-NEXT: [[S:%.*]] = struct $S (%0, %1)
109+
// CHECK-NEXT: destroy_value [[S]]
110+
// CHECK: } // end sil function 'multi_borrows'
111+
sil [ossa] @multi_borrows : $@convention(thin) (@owned String, Int) -> () {
112+
bb0(%0 : @owned $String, %1 : $Int):
113+
%2 = struct $S (%0, %1)
114+
cond_br undef, bb1, bb2
115+
bb1:
116+
%3 = begin_borrow %2
117+
%4 = struct_extract %3, #S.a
118+
fix_lifetime %4
119+
end_borrow %3
120+
br bb3
121+
bb2:
122+
%7 = begin_borrow %2
123+
%8 = struct_extract %7, #S.b
124+
fix_lifetime %8
125+
end_borrow %7
126+
br bb3
127+
bb3:
128+
destroy_value %2
129+
%r = tuple ()
130+
return %r : $()
131+
}
132+
133+
// CHECK-LABEL: sil [ossa] @unknown_struct_use :
134+
// CHECK: struct_extract
135+
// CHECK: struct_extract
136+
// CHECK: } // end sil function 'unknown_struct_use'
137+
sil [ossa] @unknown_struct_use : $@convention(thin) (@owned String, Int) -> () {
138+
bb0(%0 : @owned $String, %1 : $Int):
139+
%2 = struct $S (%0, %1)
140+
fix_lifetime %2
141+
%3 = begin_borrow %2
142+
%4 = struct_extract %3, #S.a
143+
%5 = struct_extract %3, #S.b
144+
fix_lifetime %4
145+
fix_lifetime %5
146+
end_borrow %3
147+
destroy_value %2
148+
%r = tuple ()
149+
return %r : $()
150+
}
151+
152+
// CHECK-LABEL: sil [ossa] @unknown_borrow_use :
153+
// CHECK: struct_extract
154+
// CHECK: struct_extract
155+
// CHECK: } // end sil function 'unknown_borrow_use'
156+
sil [ossa] @unknown_borrow_use : $@convention(thin) (@owned String, Int) -> () {
157+
bb0(%0 : @owned $String, %1 : $Int):
158+
%2 = struct $S (%0, %1)
159+
%3 = begin_borrow %2
160+
fix_lifetime %3
161+
%4 = struct_extract %3, #S.a
162+
%5 = struct_extract %3, #S.b
163+
fix_lifetime %4
164+
fix_lifetime %5
165+
end_borrow %3
166+
destroy_value %2
167+
%r = tuple ()
168+
return %r : $()
169+
}
170+
171+
// CHECK-LABEL: sil [ossa] @no_borrows :
172+
// CHECK: %2 = struct
173+
// CHECK: destroy_value %2
174+
// CHECK: } // end sil function 'no_borrows'
175+
sil [ossa] @no_borrows : $@convention(thin) (@owned String, Int) -> () {
176+
bb0(%0 : @owned $String, %1 : $Int):
177+
%2 = struct $S (%0, %1)
178+
destroy_value %2
179+
%r = tuple ()
180+
return %r : $()
181+
}
182+
183+
// TODO: remove this test once we require complete OSSA lifetimes
184+
// CHECK-LABEL: sil [ossa] @incomplete_lifetimes :
185+
// CHECK: bb0
186+
// CHECK-NEXT: [[B:%.*]] = begin_borrow %0
187+
// CHECK-NEXT: fix_lifetime [[B]]
188+
// CHECK-NEXT: fix_lifetime %1
189+
// CHECK-NEXT: unreachable
190+
// CHECK: } // end sil function 'incomplete_lifetimes'
191+
sil [ossa] @incomplete_lifetimes : $@convention(thin) (@owned String, Int) -> () {
192+
bb0(%0 : @owned $String, %1 : $Int):
193+
%2 = struct $S (%0, %1)
194+
%3 = begin_borrow %2
195+
%4 = struct_extract %3, #S.a
196+
%5 = struct_extract %3, #S.b
197+
fix_lifetime %4
198+
fix_lifetime %5
199+
unreachable
200+
}
201+

0 commit comments

Comments
 (0)