Skip to content

Commit 82bffd3

Browse files
committed
Simplification: fold tuple - copy_value - destructure_tuple sequences
We already do this for structs. Also, refactor the simplification to share the logic between structs and tuples
1 parent b14dc22 commit 82bffd3

File tree

2 files changed

+158
-97
lines changed

2 files changed

+158
-97
lines changed

SwiftCompilerSources/Sources/Optimizer/InstructionSimplification/SimplifyDestructure.swift

Lines changed: 87 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -14,148 +14,138 @@ import SIL
1414

1515
extension DestructureTupleInst : OnoneSimplifiable, SILCombineSimplifiable {
1616
func simplify(_ context: SimplifyContext) {
17+
foldWithAggregateConstruction(context)
18+
}
19+
}
1720

18-
// If the tuple is trivial, replace
19-
// ```
20-
// (%1, %2) = destructure_tuple %t
21-
// ```
22-
// ->
23-
// ```
24-
// %1 = tuple_extract %t, 0
25-
// %2 = tuple_extract %t, 1
26-
// ```
27-
// This canonicalization helps other optimizations to e.g. CSE tuple_extracts.
28-
//
29-
if replaceWithTupleExtract(context) {
30-
return
31-
}
21+
extension DestructureStructInst : OnoneSimplifiable, SILCombineSimplifiable {
22+
func simplify(_ context: SimplifyContext) {
23+
foldWithAggregateConstruction(context)
24+
}
25+
}
3226

33-
// Eliminate the redundant instruction pair
34-
// ```
35-
// %t = tuple (%0, %1, %2)
36-
// (%3, %4, %5) = destructure_tuple %t
37-
// ```
38-
// and replace the results %3, %4, %5 with %0, %1, %2, respectively
39-
//
40-
if let tuple = self.tuple as? TupleInst {
41-
tryReplaceConstructDestructPair(construct: tuple, destruct: self, context)
42-
}
27+
private protocol DestructureInstruction : MultipleValueInstruction {
28+
func createExtract(of aggregate: Value, elementIndex: Int, using builder: Builder) -> Value
29+
}
30+
31+
extension DestructureTupleInst: DestructureInstruction {
32+
func createExtract(of aggregate: Value, elementIndex: Int, using builder: Builder) -> Value {
33+
return builder.createTupleExtract(tuple: aggregate, elementIndex: elementIndex)
4334
}
35+
}
4436

45-
private func replaceWithTupleExtract(_ context: SimplifyContext) -> Bool {
46-
guard self.tuple.type.isTrivial(in: parentFunction) else {
47-
return false
48-
}
49-
let builder = Builder(before: self, context)
50-
for (elementIdx, result) in results.enumerated() {
51-
let elementValue = builder.createTupleExtract(tuple: self.tuple, elementIndex: elementIdx)
52-
result.uses.replaceAll(with: elementValue, context)
53-
}
54-
context.erase(instruction: self)
55-
return true
37+
extension DestructureStructInst: DestructureInstruction {
38+
func createExtract(of aggregate: Value, elementIndex: Int, using builder: Builder) -> Value {
39+
return builder.createStructExtract(struct: aggregate, fieldIndex: elementIndex)
5640
}
5741
}
5842

59-
extension DestructureStructInst : OnoneSimplifiable, SILCombineSimplifiable {
60-
func simplify(_ context: SimplifyContext) {
43+
private protocol ConstructureInstruction : SingleValueInstruction {}
44+
45+
extension TupleInst: ConstructureInstruction {}
46+
extension StructInst: ConstructureInstruction {}
47+
48+
private extension DestructureInstruction {
49+
var aggregate: Value { operands[0].value }
50+
51+
func foldWithAggregateConstruction(_ context: SimplifyContext) {
6152

62-
// If the struct is trivial, replace
63-
// ```
64-
// (%1, %2) = destructure_struct %s
65-
// ```
66-
// ->
67-
// ```
68-
// %1 = struct_extract %s, #S.field0
69-
// %2 = struct_extract %s, #S.field1
70-
// ```
71-
// This canonicalization helps other optimizations to e.g. CSE tuple_extracts.
72-
//
73-
if replaceWithStructExtract(context) {
53+
if aggregate.type.isTrivial(in: parentFunction) {
54+
// ```
55+
// (%1, %2) = destructure_tuple %t
56+
// ```
57+
// ->
58+
// ```
59+
// %1 = tuple_extract %t, 0
60+
// %2 = tuple_extract %t, 1
61+
// ```
62+
replaceWithAggregateExtract(context)
7463
return
7564
}
7665

77-
switch self.struct {
78-
case let str as StructInst:
66+
switch aggregate {
67+
case let constructInst as ConstructureInstruction:
7968
// Eliminate the redundant instruction pair
8069
// ```
81-
// %s = struct (%0, %1, %2)
82-
// (%3, %4, %5) = destructure_struct %s
70+
// %t = tuple (%0, %1, %2)
71+
// (%3, %4, %5) = destructure_tuple %t
8372
// ```
8473
// and replace the results %3, %4, %5 with %0, %1, %2, respectively
8574
//
86-
tryReplaceConstructDestructPair(construct: str, destruct: self, context)
75+
tryFoldWithConstructure(constructure: constructInst, context)
8776

8877
case let copy as CopyValueInst:
8978
// Similar to the pattern above, but with a copy_value:
9079
// Replace
9180
// ```
92-
// %s = struct (%0, %1, %2)
93-
// %c = copy_value %s // can also be a chain of multiple copies
94-
// (%3, %4, %5) = destructure_struct %c
81+
// %t = tuple (%0, %1, %2)
82+
// %c = copy_value %t // can also be a chain of multiple copies
83+
// (%3, %4, %5) = destructure_tuple %c
9584
// ```
9685
// with
9786
// ```
9887
// %c0 = copy_value %0
9988
// %c1 = copy_value %1
10089
// %c2 = copy_value %2
101-
// %s = struct (%0, %1, %2) // keep the original struct
90+
// %s = tuple (%0, %1, %2) // keep the original tuple/struct instruction
10291
// ```
10392
// and replace the results %3, %4, %5 with %c0, %c1, %c2, respectively.
10493
//
105-
// This transformation has the advantage that we can do it even if the `struct` instruction
94+
// This transformation has the advantage that we can do it even if the `tuple`/`struct`
10695
// has other uses than the `copy_value`.
10796
//
108-
if copy.uses.singleUse?.instruction == self,
109-
let structInst = copy.fromValue.lookThroughCopy as? StructInst,
110-
structInst.parentBlock == self.parentBlock
111-
{
112-
for (result, operand) in zip(self.results, structInst.operands) {
113-
if operand.value.type.isTrivial(in: parentFunction) {
114-
result.uses.replaceAll(with: operand.value, context)
115-
} else {
116-
let builder = Builder(before: structInst, context)
117-
let copiedOperand = builder.createCopyValue(operand: operand.value)
118-
result.uses.replaceAll(with: copiedOperand, context)
119-
}
120-
}
121-
context.erase(instruction: self)
122-
context.erase(instruction: copy)
123-
}
97+
tryFoldWithCopyOfConstructure(copy: copy, context)
98+
12499
default:
125100
break
126101
}
127102
}
128103

129-
private func replaceWithStructExtract(_ context: SimplifyContext) -> Bool {
130-
guard self.struct.type.isTrivial(in: parentFunction) else {
131-
return false
132-
}
104+
private func replaceWithAggregateExtract(_ context: SimplifyContext) {
133105
let builder = Builder(before: self, context)
134-
for (fieldIdx, result) in results.enumerated() {
135-
let fieldValue = builder.createStructExtract(struct: self.struct, fieldIndex: fieldIdx)
136-
result.uses.replaceAll(with: fieldValue, context)
106+
for (elementIdx, result) in results.enumerated() {
107+
let elementValue = createExtract(of: aggregate, elementIndex: elementIdx, using: builder)
108+
result.uses.replaceAll(with: elementValue, context)
137109
}
138110
context.erase(instruction: self)
139-
return true
140111
}
141-
}
142112

143-
private func tryReplaceConstructDestructPair(construct: SingleValueInstruction,
144-
destruct: MultipleValueInstruction,
145-
_ context: SimplifyContext) {
146-
let singleUse = context.preserveDebugInfo ? construct.uses.singleUse : construct.uses.ignoreDebugUses.singleUse
147-
let canEraseFirst = singleUse?.instruction == destruct
113+
private func tryFoldWithConstructure(constructure: ConstructureInstruction, _ context: SimplifyContext) {
114+
let singleConstructureUse = context.preserveDebugInfo ? constructure.uses.singleUse : constructure.uses.ignoreDebugUses.singleUse
115+
let canEraseConstructure = singleConstructureUse?.instruction == self
148116

149-
if !canEraseFirst && construct.parentFunction.hasOwnership && construct.ownership == .owned {
150-
// We cannot add more uses to this tuple without inserting a copy.
151-
return
152-
}
117+
if !canEraseConstructure && constructure.ownership == .owned {
118+
// We cannot add more uses to this tuple/struct without inserting a copy.
119+
return
120+
}
121+
122+
for (result, operand) in zip(self.results, constructure.operands) {
123+
result.uses.replaceAll(with: operand.value, context)
124+
}
153125

154-
for (result, operand) in zip(destruct.results, construct.operands) {
155-
result.uses.replaceAll(with: operand.value, context)
126+
context.erase(instruction: self)
127+
if canEraseConstructure {
128+
context.erase(instructionIncludingDebugUses: constructure)
129+
}
156130
}
157131

158-
if canEraseFirst {
159-
context.erase(instructionIncludingDebugUses: destruct)
132+
private func tryFoldWithCopyOfConstructure(copy: CopyValueInst, _ context: SimplifyContext) {
133+
guard copy.uses.singleUse?.instruction == self,
134+
let constructure = copy.fromValue.lookThroughCopy as? ConstructureInstruction,
135+
constructure.parentBlock == self.parentBlock
136+
else {
137+
return
138+
}
139+
for (result, operand) in zip(self.results, constructure.operands) {
140+
if operand.value.type.isTrivial(in: parentFunction) {
141+
result.uses.replaceAll(with: operand.value, context)
142+
} else {
143+
let builder = Builder(before: constructure, context)
144+
let copiedOperand = builder.createCopyValue(operand: operand.value)
145+
result.uses.replaceAll(with: copiedOperand, context)
146+
}
147+
}
148+
context.erase(instruction: self)
149+
context.erase(instruction: copy)
160150
}
161151
}

test/SILOptimizer/simplify_destructure_tuple.sil

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,77 @@ bb0(%0 : @owned $String, %1 : $Int):
7070
return %4 : $String
7171
}
7272

73+
// CHECK-LABEL: sil [ossa] @forward_with_copy :
74+
// CHECK: %2 = copy_value %0
75+
// CHECK: %3 = tuple (%0 : $String, %1 : $Int)
76+
// CHECK: fix_lifetime %1
77+
// CHECK: destroy_value %3
78+
// CHECK: return %2
79+
// CHECK: } // end sil function 'forward_with_copy'
80+
sil [ossa] @forward_with_copy : $@convention(thin) (@owned String, Int) -> @owned String {
81+
bb0(%0 : @owned $String, %1 : $Int):
82+
%2 = tuple (%0, %1)
83+
%3 = copy_value %2
84+
(%4, %5) = destructure_tuple %3
85+
fix_lifetime %5
86+
destroy_value %2
87+
return %4
88+
}
89+
90+
// CHECK-LABEL: sil [ossa] @forward_with_two_copies :
91+
// CHECK: %2 = copy_value %0
92+
// CHECK: %3 = tuple (%0 : $String, %1 : $Int)
93+
// CHECK: %4 = copy_value %3
94+
// CHECK: fix_lifetime %1
95+
// CHECK: destroy_value %3
96+
// CHECK: destroy_value %4
97+
// CHECK: return %2
98+
// CHECK: } // end sil function 'forward_with_two_copies'
99+
sil [ossa] @forward_with_two_copies : $@convention(thin) (@owned String, Int) -> @owned String {
100+
bb0(%0 : @owned $String, %1 : $Int):
101+
%2 = tuple (%0, %1)
102+
%3 = copy_value %2
103+
%4 = copy_value %3
104+
(%5, %6) = destructure_tuple %4
105+
fix_lifetime %6
106+
destroy_value %2
107+
destroy_value %3
108+
return %5 : $String
109+
}
110+
111+
// CHECK-LABEL: sil [ossa] @copy_has_other_uses :
112+
// CHECK: destructure_tuple
113+
// CHECK: } // end sil function 'copy_has_other_uses'
114+
sil [ossa] @copy_has_other_uses : $@convention(thin) (@owned String, Int) -> @owned String {
115+
bb0(%0 : @owned $String, %1 : $Int):
116+
%2 = tuple (%0, %1)
117+
%3 = copy_value %2
118+
fix_lifetime %3
119+
(%5, %6) = destructure_tuple %3
120+
fix_lifetime %6
121+
destroy_value %2
122+
return %5
123+
}
124+
125+
// CHECK-LABEL: sil [ossa] @different_basic_block :
126+
// CHECK: bb2:
127+
// CHECK: destructure_tuple
128+
// CHECK: } // end sil function 'different_basic_block'
129+
sil [ossa] @different_basic_block : $@convention(thin) (@owned String, Int) -> @owned String {
130+
bb0(%0 : @owned $String, %1 : $Int):
131+
%2 = tuple (%0, %1)
132+
%3 = copy_value %2
133+
cond_br undef, bb1, bb2
134+
bb1:
135+
destroy_value %3
136+
unreachable
137+
bb2:
138+
(%4, %5) = destructure_tuple %3
139+
fix_lifetime %5
140+
destroy_value %2
141+
return %4
142+
}
143+
73144
// CHECK-LABEL: sil [ossa] @to_tuple_extract :
74145
// CHECK: %1 = tuple_extract %0 : $(Int, Int), 0
75146
// CHECK: %2 = tuple_extract %0 : $(Int, Int), 1

0 commit comments

Comments
 (0)