Skip to content

Commit 4603530

Browse files
committed
Optimizer: improve simplification of alloc_stack
* Reimplement most of the logic in Swift as an Instruction simplification and remove the old code from SILCombine * support more cases of existential archetype replacements: For example: ``` %0 = alloc_stack $any P %1 = init_existential_addr %0, $T use %1 ``` is transformed to ``` %0 = alloc_stack $T use %0 ``` Also, if the alloc_stack is already an opened existential and the concrete type is known, replace it as well: ``` %0 = metatype $@thick T.Type %1 = init_existential_metatype %0, $@thick any P.Type %2 = open_existential_metatype %1 : $@thick any P.Type to $@thick (@opened("X", P) Self).Type ... %3 = alloc_stack $@opened("X", any P) Self use %3 ``` is transformed to ``` ... %3 = alloc_stack $T use %3 ```
1 parent 5572c83 commit 4603530

18 files changed

+702
-286
lines changed

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
swift_compiler_sources(Optimizer
1010
SimplifyAllocRefDynamic.swift
11+
SimplifyAllocStack.swift
1112
SimplifyApply.swift
1213
SimplifyBeginAndLoadBorrow.swift
1314
SimplifyBeginCOWMutation.swift
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
//===--- SimplifyAllocStack.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+
import AST
15+
16+
extension AllocStackInst : Simplifiable, SILCombineSimplifiable {
17+
func simplify(_ context: SimplifyContext) {
18+
if optimizeEnum(context) {
19+
return
20+
}
21+
_ = optimizeExistential(context)
22+
}
23+
}
24+
25+
private extension AllocStackInst {
26+
/// Replaces an alloc_stack of an enum by an alloc_stack of the payload if only one enum case (with payload)
27+
/// is stored to that location.
28+
///
29+
/// For example:
30+
/// ```
31+
/// %0 = alloc_stack $Optional<T>
32+
/// %1 = init_enum_data_addr %loc
33+
/// store %2 to %1
34+
/// ...
35+
/// %3 = unchecked_take_enum_data_addr %0
36+
/// %4 = load %3
37+
/// ```
38+
/// is transformed to
39+
/// ```
40+
/// %0 = alloc_stack $T
41+
/// store %2 to %0
42+
/// ...
43+
/// %4 = load %0
44+
/// ```
45+
func optimizeEnum(_ context: SimplifyContext) -> Bool {
46+
guard let (payloadType, isSingleInitTakePair) = getEnumInfo() else {
47+
return false
48+
}
49+
50+
let builder = Builder(before: self, context)
51+
let newAlloc = builder.createAllocStack(payloadType,
52+
hasDynamicLifetime: hasDynamicLifetime,
53+
isLexical: isLexical,
54+
isFromVarDecl: isFromVarDecl,
55+
usesMoveableValueDebugInfo: usesMoveableValueDebugInfo)
56+
let oldAllocType = type
57+
if let varInfo = debugVariable {
58+
builder.createDebugValue(value: Undef.get(type: oldAllocType, context), debugVariable: varInfo)
59+
}
60+
self.replace(with: newAlloc, context)
61+
62+
for use in newAlloc.uses {
63+
switch use.instruction {
64+
case let iea as InjectEnumAddrInst:
65+
context.erase(instruction: iea)
66+
case let da as DestroyAddrInst:
67+
if isSingleInitTakePair {
68+
// It's not possible that the enum has a payload at the destroy_addr, because it must have already
69+
// been taken by the take of the single init-take pair.
70+
// We _have_ to remove the destroy_addr, because we also remove all inject_enum_addrs which might
71+
// inject a payload-less case before the destroy_addr.
72+
// Otherwise the enum payload can still be valid at the destroy_addr, so we have to keep the destroy_addr.
73+
// Just replace the enum with the payload (and because it's not a singleInitTakePair, we can be sure
74+
// that the enum cannot have any other case than the payload case).
75+
context.erase(instruction: da)
76+
}
77+
case let ieda as InitEnumDataAddrInst:
78+
ieda.replace(with: newAlloc, context)
79+
case let uteda as UncheckedTakeEnumDataAddrInst:
80+
uteda.replace(with: newAlloc, context)
81+
case is DeallocStackInst:
82+
break
83+
case let dv as DebugValueInst:
84+
// TODO: Add support for op_enum_fragment
85+
dv.operand.set(to: Undef.get(type: oldAllocType, context), context)
86+
default:
87+
fatalError("unexpected alloc_stack user");
88+
}
89+
}
90+
return true
91+
}
92+
93+
func getEnumInfo() -> (payloadType: Type, isSingleInitTakePair: Bool)? {
94+
if !type.isEnum {
95+
return nil
96+
}
97+
var numInits = 0
98+
var numTakes = 0
99+
var initBlock: BasicBlock? = nil
100+
var takeBlock: BasicBlock? = nil
101+
var caseIndex: Int? = nil
102+
var payloadType: Type? = nil
103+
for use in uses {
104+
switch use.instruction {
105+
case is DestroyAddrInst,
106+
is DeallocStackInst,
107+
is DebugValueInst,
108+
// We'll check init_enum_addr below.
109+
is InjectEnumAddrInst:
110+
break
111+
case let ieda as InitEnumDataAddrInst:
112+
if let previouslyFoundCase = caseIndex, previouslyFoundCase != ieda.caseIndex {
113+
return nil
114+
}
115+
caseIndex = ieda.caseIndex
116+
assert(payloadType == nil || payloadType! == ieda.type)
117+
payloadType = ieda.type
118+
numInits += 1
119+
initBlock = ieda.parentBlock
120+
case let uted as UncheckedTakeEnumDataAddrInst:
121+
if let previouslyFoundCase = caseIndex, previouslyFoundCase != uted.caseIndex {
122+
return nil
123+
}
124+
caseIndex = uted.caseIndex
125+
numTakes += 1
126+
takeBlock = uted.parentBlock
127+
default:
128+
return nil
129+
}
130+
}
131+
132+
guard let caseIndex, let payloadType else {
133+
return nil
134+
}
135+
136+
// If the enum has a single init-take pair in a single block, we know that the enum cannot contain any
137+
// valid payload outside that init-take pair.
138+
//
139+
// This also means that we can ignore any inject_enum_addr of another enum case, because this can only
140+
// inject a case without a payload.
141+
if numInits == 1 && numTakes == 1 && initBlock == takeBlock {
142+
return (payloadType, isSingleInitTakePair: true)
143+
}
144+
// No single init-take pair: We cannot ignore inject_enum_addrs with a mismatching case.
145+
if uses.users(ofType: InjectEnumAddrInst.self).contains(where: { $0.caseIndex != caseIndex}) {
146+
return nil
147+
}
148+
return (payloadType, isSingleInitTakePair: false)
149+
}
150+
151+
/// Replaces an alloc_stack of an existential by an alloc_stack of the concrete type.
152+
///
153+
/// For example:
154+
/// ```
155+
/// %0 = alloc_stack $any P
156+
/// %1 = init_existential_addr %0, $T
157+
/// use %1
158+
/// ```
159+
/// is transformed to
160+
/// ```
161+
/// %0 = alloc_stack $T
162+
/// use %0
163+
/// ```
164+
///
165+
/// Also, if the alloc_stack is already an opened existential and the concrete type is known,
166+
/// replace it as well:
167+
/// ```
168+
/// %0 = metatype $@thick T.Type
169+
/// %1 = init_existential_metatype %0, $@thick any P.Type
170+
/// %2 = open_existential_metatype %1 : $@thick any P.Type to $@thick (@opened("X", P) Self).Type
171+
/// ...
172+
/// %3 = alloc_stack $@opened("X", any P) Self
173+
/// use %3
174+
/// ```
175+
/// is transformed to
176+
/// ```
177+
/// ...
178+
/// %3 = alloc_stack $T
179+
/// use %3
180+
/// ```
181+
func optimizeExistential(_ context: SimplifyContext) -> Bool {
182+
guard type.astType.isExistential || type.astType.isExistentialArchetype,
183+
let concreteFormalType = getConcreteTypeOfExistential()
184+
else {
185+
return false
186+
}
187+
188+
let builder = Builder(before: self, context)
189+
let newAlloc = builder.createAllocStack(concreteFormalType.loweredType(in: parentFunction),
190+
hasDynamicLifetime: hasDynamicLifetime,
191+
isLexical: isLexical,
192+
isFromVarDecl: isFromVarDecl,
193+
usesMoveableValueDebugInfo: usesMoveableValueDebugInfo)
194+
for use in uses {
195+
switch use.instruction {
196+
case let dea as DeinitExistentialAddrInst:
197+
context.erase(instruction: dea)
198+
case let iea as InitExistentialAddrInst:
199+
if iea.type != newAlloc.type {
200+
// We need a cast if the concrete type of the init_existential_addr is itself an opened existential
201+
// for which we know the concrete type (which is differnt).
202+
let builder = Builder(before: iea, context)
203+
let addrCast = builder.createUncheckedAddrCast(from: newAlloc, to: iea.type)
204+
iea.replace(with: addrCast, context)
205+
} else {
206+
iea.replace(with: newAlloc, context)
207+
}
208+
case let oea as OpenExistentialAddrInst:
209+
assert(oea.uses.ignoreUsers(ofType: DestroyAddrInst.self).isEmpty)
210+
oea.replace(with: newAlloc, context)
211+
case let cab as CheckedCastAddrBranchInst:
212+
let builder = Builder(before: cab, context)
213+
builder.createCheckedCastAddrBranch(
214+
source: newAlloc, sourceFormalType: concreteFormalType,
215+
destination: cab.destination, targetFormalType: cab.targetFormalType,
216+
consumptionKind: cab.consumptionKind,
217+
successBlock: cab.successBlock, failureBlock: cab.failureBlock)
218+
context.erase(instruction: cab)
219+
case let ucca as UnconditionalCheckedCastAddrInst:
220+
let builder = Builder(before: ucca, context)
221+
builder.createUnconditionalCheckedCastAddr(
222+
source: newAlloc, sourceFormalType: concreteFormalType,
223+
destination: ucca.destination, targetFormalType: ucca.targetFormalType)
224+
context.erase(instruction: ucca)
225+
default:
226+
use.set(to: newAlloc, context)
227+
}
228+
}
229+
context.erase(instruction: self)
230+
return true
231+
}
232+
233+
// Returns the concrete type of this alloc_stack if known.
234+
// Assuming that its type is either an existential or an opened existential.
235+
private func getConcreteTypeOfExistential() -> CanonicalType? {
236+
var initExistential: InitExistentialAddrInst? = nil
237+
var requiresLegalFormalType = false
238+
239+
for use in uses {
240+
switch use.instruction {
241+
case is DestroyAddrInst,
242+
is DeinitExistentialAddrInst,
243+
is DeallocStackInst,
244+
is DebugValueInst:
245+
break
246+
case let oea as OpenExistentialAddrInst:
247+
if !oea.uses.ignoreUsers(ofType: DestroyAddrInst.self).isEmpty {
248+
return nil
249+
}
250+
case let iea as InitExistentialAddrInst:
251+
if initExistential != nil {
252+
return nil
253+
}
254+
initExistential = iea
255+
case is CheckedCastAddrBranchInst, is UnconditionalCheckedCastAddrInst:
256+
// To construct a new cast instruction we need a formal type.
257+
requiresLegalFormalType = true
258+
fallthrough
259+
case is UncheckedAddrCastInst:
260+
if use != use.instruction.operands[0] {
261+
return nil
262+
}
263+
default:
264+
return nil
265+
}
266+
}
267+
let concreteType: CanonicalType
268+
if let initExistential {
269+
assert(self.type.astType.isExistential)
270+
if let cft = initExistential.concreteTypeOfDependentExistentialArchetype {
271+
// Case 1: We will replace the alloc_stack of an existential with the concrete type.
272+
// `alloc_stack $any P` -> `alloc_stack $ConcreteType`
273+
concreteType = cft
274+
} else {
275+
// The instruction which defines the existential archetype must dominate the alloc_stack, because
276+
// after the transformation the alloc_stack will use the existential archetype.
277+
for openArchetypeOp in initExistential.typeDependentOperands {
278+
if !openArchetypeOp.value.definingInstruction!.dominatesInSameBlock(self) {
279+
return nil
280+
}
281+
}
282+
// Case 2: We will replace the alloc_stack of an existential with the existential archetype.
283+
// `alloc_stack $any P` -> `alloc_stack $@opened("...")`
284+
concreteType = initExistential.type.astType
285+
}
286+
} else if self.type.astType.isExistentialArchetype, let cft = self.concreteTypeOfDependentExistentialArchetype {
287+
// Case 3: We will replace the alloc_stack of an existential archetype with the concrete type:
288+
// `alloc_stack $@opened("...")` -> `alloc_stack $ConcreteType`
289+
concreteType = cft
290+
} else {
291+
return nil
292+
}
293+
if requiresLegalFormalType && !concreteType.isLegalFormalType {
294+
return nil
295+
}
296+
return concreteType
297+
}
298+
}

SwiftCompilerSources/Sources/Optimizer/PassManager/PassRegistration.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ private func registerSwiftPasses() {
121121
registerForSILCombine(UncheckedEnumDataInst.self, { run(UncheckedEnumDataInst.self, $0) })
122122
registerForSILCombine(WitnessMethodInst.self, { run(WitnessMethodInst.self, $0) })
123123
registerForSILCombine(UnconditionalCheckedCastInst.self, { run(UnconditionalCheckedCastInst.self, $0) })
124+
registerForSILCombine(AllocStackInst.self, { run(AllocStackInst.self, $0) })
124125
registerForSILCombine(ApplyInst.self, { run(ApplyInst.self, $0) })
125126
registerForSILCombine(TryApplyInst.self, { run(TryApplyInst.self, $0) })
126127

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,7 @@ SWIFT_SILCOMBINE_PASS(PointerToAddressInst)
540540
SWIFT_SILCOMBINE_PASS(TypeValueInst)
541541
SWIFT_SILCOMBINE_PASS(UncheckedEnumDataInst)
542542
SWIFT_SILCOMBINE_PASS(WitnessMethodInst)
543+
SWIFT_SILCOMBINE_PASS_WITH_LEGACY(AllocStackInst)
543544
SWIFT_SILCOMBINE_PASS_WITH_LEGACY(UnconditionalCheckedCastInst)
544545
SWIFT_SILCOMBINE_PASS_WITH_LEGACY(ApplyInst)
545546
SWIFT_SILCOMBINE_PASS_WITH_LEGACY(TryApplyInst)

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,6 @@ class SILCombiner :
256256

257257
SILInstruction *visitIndexAddrInst(IndexAddrInst *IA);
258258
bool optimizeStackAllocatedEnum(AllocStackInst *AS);
259-
SILInstruction *visitAllocStackInst(AllocStackInst *AS);
260259
SILInstruction *visitSwitchEnumAddrInst(SwitchEnumAddrInst *SEAI);
261260
SILInstruction *visitInjectEnumAddrInst(InjectEnumAddrInst *IEAI);
262261
SILInstruction *visitUncheckedAddrCastInst(UncheckedAddrCastInst *UADCI);

0 commit comments

Comments
 (0)