Skip to content

Commit 9aff288

Browse files
committed
Optimizer: re-implement the pointer_to_address SILCombine peephole optimizations in swift
Which consists of * removing redundant `address_to_pointer`-`pointer_to_address` pairs * optimize `index_raw_pointer` of a manually computed stride to `index_addr` * remove or increase the alignment based on a "assumeAlignment" builtin This is a big code cleanup but also has some functional differences for the `address_to_pointer`-`pointer_to_address` pair removal: * It's not done if the resulting SIL would result in a (detectable) use-after-dealloc_stack memory lifetime failure. * It's not done if `copy_value`s must be inserted or borrow-scopes must be extended to comply with ownership rules (this was the task of the OwnershipRAUWHelper). Inserting copies is bad anyway. Extending borrow-scopes would only be required if the original lifetime of the pointer extends a borrow scope - which shouldn't happen in save code. Therefore this is a very rare case which is not worth handling.
1 parent 51c6a60 commit 9aff288

File tree

13 files changed

+578
-436
lines changed

13 files changed

+578
-436
lines changed

SwiftCompilerSources/Sources/Optimizer/DataStructures/Worklist.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ typealias ValueWorklist = Worklist<ValueSet>
7979
typealias OperandWorklist = Worklist<OperandSet>
8080

8181
extension InstructionWorklist {
82-
mutating func pushPredecessors(of inst: Instruction, ignoring ignoreInst: SingleValueInstruction) {
82+
mutating func pushPredecessors(of inst: Instruction, ignoring ignoreInst: Instruction) {
8383
if let prev = inst.previous {
8484
if prev != ignoreInst {
8585
pushIfNotVisited(prev)

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyPointerToAddress.swift

Lines changed: 311 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,319 @@
1212

1313
import SIL
1414

15-
extension PointerToAddressInst : OnoneSimplifyable {
15+
extension PointerToAddressInst : OnoneSimplifyable, SILCombineSimplifyable {
1616

17-
/// For a redundant pair of pointer-address conversions, e.g.
18-
///
19-
/// %2 = address_to_pointer %1
20-
/// %3 = pointer_to_address %2 [strict]
21-
///
22-
/// replace all uses of %3 with %1.
23-
///
2417
func simplify(_ context: SimplifyContext) {
25-
if let atp = self.pointer as? AddressToPointerInst,
26-
atp.address.type == self.type,
27-
self.isStrict,
28-
29-
// If the pointer is within an ownership scope, the transformation can break ownership rules, e.g.
30-
// %2 = begin_borrow %1
31-
// %3 = ref_tail_addr %2
32-
// %4 = address_to_pointer %3
33-
// end_borrow %2
34-
// %5 = pointer_to_address %4 <- cannot replace %5 with %3!
35-
//
36-
!atp.address.accessBase.hasLocalOwnershipLifetime
37-
{
38-
self.uses.replaceAll(with: atp.address, context)
18+
if removeAddressToPointerToAddressPair(of: self, context) {
19+
return
20+
}
21+
if simplifyIndexRawPointer(of: self, context) {
22+
return
23+
}
24+
_ = optimizeAlignment(of: self, context)
25+
}
26+
}
27+
28+
/// Remove a redundant pair of pointer-address conversions:
29+
/// ```
30+
/// %2 = address_to_pointer %1
31+
/// %3 = pointer_to_address %2 [strict]
32+
/// ```
33+
/// -> replace all uses of %3 with %1.
34+
///
35+
private func removeAddressToPointerToAddressPair(
36+
of ptr2Addr: PointerToAddressInst,
37+
_ context: SimplifyContext
38+
) -> Bool {
39+
guard let addr2Ptr = ptr2Addr.pointer as? AddressToPointerInst,
40+
ptr2Addr.isStrict,
41+
!ptr2Addr.hasIllegalUsesAfterLifetime(of: addr2Ptr, context)
42+
else {
43+
return false
44+
}
45+
46+
if ptr2Addr.type == addr2Ptr.address.type {
47+
ptr2Addr.replace(with: addr2Ptr.address, context)
48+
} else {
49+
let cast = Builder(before: ptr2Addr, context).createUncheckedAddrCast(from: addr2Ptr.address, to: ptr2Addr.type)
50+
ptr2Addr.replace(with: cast, context)
51+
}
52+
return true
53+
}
54+
55+
/// Replace an `index_raw_pointer` with a manually computed stride with `index_addr`:
56+
/// ```
57+
/// %1 = metatype $T.Type
58+
/// %2 = builtin "strideof"<T>(%1) :
59+
/// %3 = builtin "smul_with_overflow_Int64"(%idx, %2)
60+
/// %4 = tuple_extract %3, 0
61+
/// %5 = index_raw_pointer %ptr, %4
62+
/// %6 = pointer_to_address %5 to [strict] $*T
63+
/// ```
64+
/// ->
65+
/// ```
66+
/// %2 = pointer_to_address %ptr to [strict] $*T
67+
/// %3 = index_addr %2, %idx
68+
/// ```
69+
///
70+
private func simplifyIndexRawPointer(of ptr2Addr: PointerToAddressInst, _ context: SimplifyContext) -> Bool {
71+
guard let indexRawPtr = ptr2Addr.pointer as? IndexRawPointerInst,
72+
let tupleExtract = indexRawPtr.index.lookThroughTruncOrBitCast as? TupleExtractInst,
73+
let strideMul = tupleExtract.tuple as? BuiltinInst, strideMul.id == .SMulOver,
74+
let (index, strideType) = strideMul.indexAndStrideOfMultiplication,
75+
strideType == ptr2Addr.type.objectType
76+
else {
77+
return false
78+
}
79+
80+
let builder = Builder(before: ptr2Addr, context)
81+
let newPtr2Addr = builder.createPointerToAddress(pointer: indexRawPtr.base, addressType: ptr2Addr.type,
82+
isStrict: ptr2Addr.isStrict, isInvariant: ptr2Addr.isInvariant)
83+
let newIndex = builder.createCastIfNeeded(of: index, toIndexTypeOf: indexRawPtr)
84+
let indexAddr = builder.createIndexAddr(base: newPtr2Addr, index: newIndex, needStackProtection: false)
85+
ptr2Addr.replace(with: indexAddr, context)
86+
return true
87+
}
88+
89+
/// Optimize the alignment of a `pointer_to_address` based on `Builtin.assumeAlignment`
90+
/// ```
91+
/// %1 = builtin "assumeAlignment"(%ptr, %align)
92+
/// %2 = pointer_to_address %1 to [align=1] $*T
93+
/// ```
94+
/// ->
95+
/// ```
96+
/// %2 = pointer_to_address %ptr to [align=8] $*T
97+
/// ```
98+
/// or
99+
/// ```
100+
/// %2 = pointer_to_address %ptr to $*T
101+
/// ```
102+
///
103+
/// The goal is to increase the alignment or to remove the attribute completely, which means that
104+
/// the resulting address is naturaly aligned to its type.
105+
///
106+
private func optimizeAlignment(of ptr2Addr: PointerToAddressInst, _ context: SimplifyContext) -> Bool {
107+
guard let assumeAlign = ptr2Addr.pointer as? BuiltinInst, assumeAlign.id == .AssumeAlignment else {
108+
return false
109+
}
110+
111+
if optimizeConstantAlignment(of: ptr2Addr, assumed: assumeAlign, context) {
112+
return true
113+
}
114+
return optimizeTypeAlignment(of: ptr2Addr, assumed: assumeAlign, context)
115+
}
116+
117+
/// Optimize the alignment based on an integer literal
118+
/// ```
119+
/// %align = integer_literal $Builtin.Int64, 16
120+
/// %1 = builtin "assumeAlignment"(%ptr, %align)
121+
/// %2 = pointer_to_address %1 to [align=1] $*T
122+
/// ```
123+
/// ->
124+
/// ```
125+
/// %2 = pointer_to_address %ptr to [align=16] $*T
126+
/// ```
127+
private func optimizeConstantAlignment(
128+
of ptr2Addr: PointerToAddressInst,
129+
assumed assumeAlign: BuiltinInst,
130+
_ context: SimplifyContext
131+
) -> Bool {
132+
guard let alignLiteral = assumeAlign.arguments[1] as? IntegerLiteralInst,
133+
let assumedAlignment = alignLiteral.value
134+
else {
135+
return false
136+
}
137+
138+
ptr2Addr.operand.set(to: assumeAlign.arguments[0], context)
139+
140+
if assumedAlignment == 0 {
141+
// A zero alignment means that the pointer is aligned to the natural alignment of the address type.
142+
ptr2Addr.set(alignment: nil, context)
143+
} else {
144+
if let oldAlignment = ptr2Addr.alignment, assumedAlignment <= oldAlignment {
145+
// Avoid decreasing the alignment, which would be a pessimisation.
146+
return true
147+
}
148+
ptr2Addr.set(alignment: assumedAlignment, context)
149+
}
150+
return true
151+
}
152+
153+
/// Remove the alignment attribute if the alignment is assumed to be the natural alignment of the address type.
154+
/// ```
155+
// %align = builtin "alignof"<T>(%0 : $@thin T.Type)
156+
/// %1 = builtin "assumeAlignment"(%ptr, %align)
157+
/// %2 = pointer_to_address %1 to [align=1] $*T
158+
/// ```
159+
/// ->
160+
/// ```
161+
/// %2 = pointer_to_address %ptr to $*T
162+
/// ```
163+
private func optimizeTypeAlignment(
164+
of ptr2Addr: PointerToAddressInst,
165+
assumed assumeAlign: BuiltinInst,
166+
_ context: SimplifyContext
167+
) -> Bool {
168+
guard let alignOf = assumeAlign.arguments[1].lookThroughIntCasts as? BuiltinInst, alignOf.id == .Alignof,
169+
alignOf.alignOrStrideType == ptr2Addr.type.objectType
170+
else {
171+
return false
172+
}
173+
let pointer = assumeAlign.arguments[0]
174+
ptr2Addr.set(alignment: nil, context)
175+
ptr2Addr.operand.set(to: pointer, context)
176+
return true
177+
}
178+
179+
private extension PointerToAddressInst {
180+
181+
/// Checks if the `pointer_to_address` has uses outside the scope of the `baseAddress`.
182+
/// In such a case removing the `address_to_pointer`-`pointer_to_address` pair would result in
183+
/// invalid SIL. For example:
184+
/// ```
185+
/// %1 = alloc_stack $T
186+
/// %2 = address_to_pointer %1
187+
/// dealloc_stack %1
188+
/// %3 = pointer_to_address %2
189+
/// %4 = load %3
190+
/// ```
191+
/// or
192+
/// ```
193+
/// %1 = begin_borrow %0
194+
/// %2 = ref_element_addr %1, #C.x
195+
/// %3 = address_to_pointer %2
196+
/// end_borrow %1
197+
/// %4 = pointer_to_address %3
198+
/// %5 = load %4
199+
/// ```
200+
func hasIllegalUsesAfterLifetime(of baseAddress: AddressToPointerInst, _ context: SimplifyContext) -> Bool {
201+
202+
var lifetimeFrontier = InstructionSet(context)
203+
defer { lifetimeFrontier.deinitialize() }
204+
205+
switch baseAddress.address.accessBase.addEndLifetimeUses(to: &lifetimeFrontier, context) {
206+
case .unknownLifetime:
207+
return true
208+
case .unlimitedLifetime:
209+
return false
210+
case .limitedLifetime:
211+
var addressUses = AddressUses(of: self, context)
212+
defer { addressUses.deinitialize() }
213+
return addressUses.hasUsesOutside(of: lifetimeFrontier, beginInstruction: baseAddress)
214+
}
215+
}
216+
}
217+
218+
private extension AccessBase {
219+
func addEndLifetimeUses(to frontier: inout InstructionSet, _ context: SimplifyContext) -> Result {
220+
switch self {
221+
case .stack(let allocStack):
222+
frontier.insert(contentsOf: allocStack.deallocations)
223+
return .limitedLifetime
224+
case .global, .argument, .pointer:
225+
return .unlimitedLifetime
226+
case .storeBorrow(let storeBorrow):
227+
frontier.insert(contentsOf: storeBorrow.endBorrows)
228+
return .limitedLifetime
229+
default:
230+
guard let ref = reference else {
231+
return .unknownLifetime
232+
}
233+
switch ref.ownership {
234+
case .owned:
235+
frontier.insert(contentsOf: ref.uses.endingLifetime.users)
236+
return .limitedLifetime
237+
case .guaranteed:
238+
for borrowIntroducer in ref.getBorrowIntroducers(context) {
239+
frontier.insert(contentsOf: borrowIntroducer.scopeEndingOperands.users)
240+
}
241+
return .limitedLifetime
242+
case .none:
243+
// Not in an OSSA function.
244+
return .unlimitedLifetime
245+
case .unowned:
246+
return .unknownLifetime
247+
}
248+
}
249+
}
250+
251+
enum Result {
252+
case unknownLifetime, unlimitedLifetime, limitedLifetime
253+
}
254+
}
255+
256+
private struct AddressUses : AddressDefUseWalker {
257+
var users: InstructionWorklist
258+
259+
init(of address: Value, _ context: SimplifyContext) {
260+
users = InstructionWorklist(context)
261+
_ = walkDownUses(ofAddress: address, path: UnusedWalkingPath())
262+
}
263+
264+
mutating func deinitialize() {
265+
users.deinitialize()
266+
}
267+
268+
mutating func leafUse(address: Operand, path: UnusedWalkingPath) -> WalkResult {
269+
users.pushIfNotVisited(address.instruction)
270+
return .continueWalk
271+
}
272+
273+
mutating func hasUsesOutside(of lifetimeFrontier: InstructionSet, beginInstruction: Instruction) -> Bool {
274+
while let inst = users.pop() {
275+
if lifetimeFrontier.contains(inst) {
276+
return true
277+
}
278+
users.pushPredecessors(of: inst, ignoring: beginInstruction)
279+
}
280+
return false
281+
}
282+
}
283+
284+
private extension Value {
285+
var lookThroughIntCasts: Value {
286+
guard let builtin = self as? BuiltinInst else {
287+
return self
288+
}
289+
switch builtin.id {
290+
case .ZExtOrBitCast, .SExtOrBitCast, .TruncOrBitCast:
291+
return builtin.arguments[0].lookThroughIntCasts
292+
default:
293+
return self
294+
}
295+
}
296+
297+
var lookThroughTruncOrBitCast: Value {
298+
if let truncOrBitCast = self as? BuiltinInst, truncOrBitCast.id == .TruncOrBitCast {
299+
return truncOrBitCast.arguments[0]
300+
}
301+
return self
302+
}
303+
}
304+
305+
private extension BuiltinInst {
306+
var indexAndStrideOfMultiplication : (index: Value, strideType: Type)? {
307+
assert(id == .SMulOver)
308+
if let strideOf = arguments[0].lookThroughIntCasts as? BuiltinInst, strideOf.id == .Strideof {
309+
return (index: arguments[1], strideType: strideOf.alignOrStrideType)
310+
}
311+
if let strideOf = arguments[1].lookThroughIntCasts as? BuiltinInst, strideOf.id == .Strideof {
312+
return (index: arguments[0], strideType: strideOf.alignOrStrideType)
313+
}
314+
return nil
315+
}
316+
317+
var alignOrStrideType: Type {
318+
substitutionMap.replacementTypes[0].loweredType(in: parentFunction)
319+
}
320+
}
321+
322+
private extension Builder {
323+
func createCastIfNeeded(of index: Value, toIndexTypeOf indexRawPtr: IndexRawPointerInst) -> Value {
324+
if let truncOrBitCast = indexRawPtr.index as? BuiltinInst {
325+
assert(truncOrBitCast.id == .TruncOrBitCast)
326+
return createBuiltin(name: truncOrBitCast.name, type: truncOrBitCast.type, arguments: [index])
39327
}
328+
return index
40329
}
41330
}

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ private func registerSwiftPasses() {
117117
registerForSILCombine(DestructureTupleInst.self, { run(DestructureTupleInst.self, $0) })
118118
registerForSILCombine(TypeValueInst.self, { run(TypeValueInst.self, $0) })
119119
registerForSILCombine(ClassifyBridgeObjectInst.self, { run(ClassifyBridgeObjectInst.self, $0) })
120+
registerForSILCombine(PointerToAddressInst.self, { run(PointerToAddressInst.self, $0) })
120121
registerForSILCombine(UncheckedEnumDataInst.self, { run(UncheckedEnumDataInst.self, $0) })
121122

122123
// Test passes

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,7 @@ SWIFT_SILCOMBINE_PASS(CopyValueInst)
533533
SWIFT_SILCOMBINE_PASS(DestroyValueInst)
534534
SWIFT_SILCOMBINE_PASS(DestructureStructInst)
535535
SWIFT_SILCOMBINE_PASS(DestructureTupleInst)
536+
SWIFT_SILCOMBINE_PASS(PointerToAddressInst)
536537
SWIFT_SILCOMBINE_PASS(TypeValueInst)
537538
SWIFT_SILCOMBINE_PASS(UncheckedEnumDataInst)
538539

lib/SILOptimizer/Analysis/SimplifyInstruction.cpp

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ namespace {
4848
SILValue visitEnumInst(EnumInst *EI);
4949
SILValue visitSelectEnumInst(SelectEnumInst *SEI);
5050
SILValue visitAddressToPointerInst(AddressToPointerInst *ATPI);
51-
SILValue visitPointerToAddressInst(PointerToAddressInst *PTAI);
5251
SILValue visitRefToRawPointerInst(RefToRawPointerInst *RRPI);
5352
SILValue
5453
visitUnconditionalCheckedCastInst(UnconditionalCheckedCastInst *UCCI);
@@ -286,16 +285,6 @@ SILValue InstSimplifier::visitAddressToPointerInst(AddressToPointerInst *ATPI) {
286285
return SILValue();
287286
}
288287

289-
SILValue InstSimplifier::visitPointerToAddressInst(PointerToAddressInst *PTAI) {
290-
// If this address is not strict, then it cannot be replaced by an address
291-
// that may be strict.
292-
if (auto *ATPI = dyn_cast<AddressToPointerInst>(PTAI->getOperand()))
293-
if (ATPI->getOperand()->getType() == PTAI->getType() && PTAI->isStrict())
294-
return ATPI->getOperand();
295-
296-
return SILValue();
297-
}
298-
299288
SILValue InstSimplifier::visitRefToRawPointerInst(RefToRawPointerInst *RefToRaw) {
300289
// Perform the following simplification:
301290
//

0 commit comments

Comments
 (0)