@@ -14,8 +14,147 @@ import SIL
1414
1515extension DestroyValueInst : OnoneSimplifiable , SILCombineSimplifiable {
1616 func simplify( _ context: SimplifyContext ) {
17+ // If the value has `.none` ownership, the destroy is a no-op. Note that a value can have `.none`
18+ // ownership even if it's type is not trivial, e.g.
19+ //
20+ // ```
21+ // %1 = enum $NonTrivialEnum, #NonTrivialEnum.trivialCase!enumelt // ownership: none
22+ // %2 = destroy_value %1
23+ // ```
24+ //
1725 if destroyedValue. ownership == . none {
1826 context. erase ( instruction: self )
27+ return
1928 }
29+
30+ tryRemoveForwardingOperandInstruction ( context)
31+ }
32+
33+ /// Attempt to optimize by forwarding the destroy to operands of forwarding instructions.
34+ ///
35+ /// ```
36+ /// %3 = struct $S (%1, %2)
37+ /// destroy_value %3 // the only use of %3
38+ /// ```
39+ /// ->
40+ /// ```
41+ /// destroy_value %1
42+ /// destroy_value %2
43+ /// ```
44+ ///
45+ /// The benefit of this transformation is that the forwarding instruction can be removed.
46+ ///
47+ private func tryRemoveForwardingOperandInstruction( _ context: SimplifyContext ) {
48+ guard context. preserveDebugInfo ? destroyedValue. uses. isSingleUse
49+ : destroyedValue. uses. ignoreDebugUses. isSingleUse
50+ else {
51+ return
52+ }
53+
54+ let destroyedInst : Instruction
55+ switch destroyedValue {
56+ case is StructInst ,
57+ is EnumInst :
58+ if destroyedValue. type. nominal!. valueTypeDestructor != nil {
59+ // Moving the destroy to a non-copyable struct/enum's operands would drop the deinit call!
60+ return
61+ }
62+ destroyedInst = destroyedValue as! SingleValueInstruction
63+
64+ // Handle various "forwarding" instructions that simply pass through values
65+ // without performing operations that would affect destruction semantics.
66+ //
67+ // We are intentionally _not_ handling `unchecked_enum_data`, because that would not necessarily be
68+ // a simplification, because destroying the whole enum is more effort than to destroy an enum payload.
69+ // We are also not handling `destructure_struct` and `destructure_tuple`. That would end up in
70+ // an infinite simplification loop in MandatoryPerformanceOptimizations because there we "split" such
71+ // destroys again when de-virtualizing deinits of non-copyable types.
72+ //
73+ case is TupleInst ,
74+ is RefToBridgeObjectInst ,
75+ is ConvertFunctionInst ,
76+ is ThinToThickFunctionInst ,
77+ is UpcastInst ,
78+ is UncheckedRefCastInst ,
79+ is UnconditionalCheckedCastInst ,
80+ is BridgeObjectToRefInst ,
81+ is InitExistentialRefInst ,
82+ is OpenExistentialRefInst :
83+ destroyedInst = destroyedValue as! SingleValueInstruction
84+
85+ case let arg as Argument :
86+ tryRemovePhiArgument ( arg, context)
87+ return
88+
89+ default :
90+ return
91+ }
92+
93+ let builder = Builder ( before: self , context)
94+ for op in destroyedInst. definedOperands where op. value. ownership == . owned {
95+ builder. createDestroyValue ( operand: op. value)
96+ }
97+
98+ // Users include `debug_value` instructions and this `destroy_value`
99+ context. erase ( instructionIncludingAllUsers: destroyedInst)
100+ }
101+
102+ /// Handles the optimization of `destroy_value` instructions for phi arguments.
103+ /// This is a more complex case where the destroyed value comes from different predecessors
104+ /// via a phi argument. The optimization moves the `destroy_value` to each predecessor block.
105+ ///
106+ /// ```
107+ /// bb1:
108+ /// br bb3(%0)
109+ /// bb2:
110+ /// br bb3(%1)
111+ /// bb3(%3 : @owned T):
112+ /// ... // no deinit-barriers
113+ /// destroy_value %3 // the only use of %3
114+ /// ```
115+ /// ->
116+ /// ```
117+ /// bb1:
118+ /// destroy_value %0
119+ /// br bb3
120+ /// bb2:
121+ /// destroy_value %1
122+ /// br bb3
123+ /// bb3:
124+ /// ...
125+ /// ```
126+ ///
127+ private func tryRemovePhiArgument( _ arg: Argument , _ context: SimplifyContext ) {
128+ guard let phi = Phi ( arg) ,
129+ arg. parentBlock == parentBlock,
130+ !isDeinitBarrierInBlock( before: self , context)
131+ else {
132+ return
133+ }
134+
135+ for incomingOp in phi. incomingOperands {
136+ let oldBranch = incomingOp. instruction as! BranchInst
137+ let builder = Builder ( before: oldBranch, context)
138+ builder. createDestroyValue ( operand: incomingOp. value)
139+ builder. createBranch ( to: parentBlock, arguments: oldBranch. arguments ( excluding: incomingOp) )
140+ context. erase ( instruction: oldBranch)
141+ }
142+
143+ // Users of `arg` include `debug_value` instructions and this `destroy_value`
144+ context. erase ( instructions: arg. uses. users)
145+
146+ arg. parentBlock. eraseArgument ( at: arg. index, context)
147+ }
148+ }
149+
150+ private func isDeinitBarrierInBlock( before instruction: Instruction , _ context: SimplifyContext ) -> Bool {
151+ return ReverseInstructionList ( first: instruction. previous) . contains ( where: {
152+ $0. isDeinitBarrier ( context. calleeAnalysis)
153+ } )
154+ }
155+
156+ private extension BranchInst {
157+ func arguments( excluding excludeOp: Operand ) -> [ Value ] {
158+ return Array ( operands. filter { $0 != excludeOp } . values)
20159 }
21160}
0 commit comments