1
1
#ifndef SWIFT_PARTITIONUTILS_H
2
2
#define SWIFT_PARTITIONUTILS_H
3
3
4
- #include " llvm/Support/Debug.h"
4
+ #include " swift/Basic/LLVM.h"
5
+ #include " swift/SIL/SILInstruction.h"
5
6
#include " llvm/ADT/SmallVector.h"
7
+ #include " llvm/Support/Debug.h"
8
+ #include < algorithm>
6
9
7
10
namespace swift {
8
11
9
12
enum class PartitionOpKind : uint8_t {
13
+ // Assign one value to the region of another, takes two args, second arg
14
+ // must already be tracked with a non-consumed region
10
15
Assign,
16
+ // Assign one value to a fresh region, takes one arg.
11
17
AssignFresh,
18
+ // Consume the region of a value, takes one arg
12
19
Consume,
13
20
Merge,
14
21
Require
15
22
};
16
23
24
+ // PartitionOp represents a primitive operation that can be performed on
25
+ // Partitions. This is part of the SendNonSendable SIL pass workflow:
26
+ // first SILBasicBlocks are compiled to vectors of PartitionOps, then a fixed
27
+ // point partition is found over the CFG.
17
28
class PartitionOp {
18
29
private:
19
30
PartitionOpKind OpKind;
20
31
llvm::SmallVector<unsigned , 2 > OpArgs;
21
32
22
- // TODO: can the following two declarations be merged?
23
- PartitionOp (PartitionOpKind OpKind, unsigned arg1)
24
- : OpKind(OpKind), OpArgs({arg1}) {}
33
+ // record the SILInstruction that this PartitionOp was generated from, if
34
+ // generated during compilation from a SILBasicBlock
35
+ SILInstruction *sourceInst;
36
+
37
+ // TODO: can the following declarations be merged?
38
+ PartitionOp (PartitionOpKind OpKind, unsigned arg1,
39
+ SILInstruction *sourceInst = nullptr )
40
+ : OpKind(OpKind), OpArgs({arg1}), sourceInst(sourceInst) {}
25
41
26
- PartitionOp (PartitionOpKind OpKind, unsigned arg1, unsigned arg2)
27
- : OpKind(OpKind), OpArgs({arg1, arg2}) {}
42
+ PartitionOp (PartitionOpKind OpKind, unsigned arg1, unsigned arg2,
43
+ SILInstruction *sourceInst = nullptr )
44
+ : OpKind(OpKind), OpArgs({arg1, arg2}), sourceInst(sourceInst) {}
28
45
29
46
friend class Partition ;
30
47
31
48
public:
32
- static PartitionOp Assign (unsigned tgt, unsigned src) {
33
- return PartitionOp (PartitionOpKind::Assign, tgt, src);
49
+ static PartitionOp Assign (unsigned tgt, unsigned src,
50
+ SILInstruction *sourceInst = nullptr ) {
51
+ return PartitionOp (PartitionOpKind::Assign, tgt, src, sourceInst);
52
+ }
53
+
54
+ static PartitionOp AssignFresh (unsigned tgt,
55
+ SILInstruction *sourceInst = nullptr ) {
56
+ return PartitionOp (PartitionOpKind::AssignFresh, tgt, sourceInst);
34
57
}
35
58
36
- static PartitionOp AssignFresh (unsigned tgt) {
37
- return PartitionOp (PartitionOpKind::AssignFresh, tgt);
59
+ static PartitionOp Consume (unsigned tgt,
60
+ SILInstruction *sourceInst = nullptr ) {
61
+ return PartitionOp (PartitionOpKind::Consume, tgt, sourceInst);
38
62
}
39
63
40
- static PartitionOp Consume (unsigned tgt) {
41
- return PartitionOp (PartitionOpKind::Consume, tgt);
64
+ static PartitionOp Merge (unsigned tgt1, unsigned tgt2,
65
+ SILInstruction *sourceInst = nullptr ) {
66
+ return PartitionOp (PartitionOpKind::Merge, tgt1, tgt2, sourceInst);
42
67
}
43
68
44
- static PartitionOp Merge (unsigned tgt1, unsigned tgt2) {
45
- return PartitionOp (PartitionOpKind::Merge, tgt1, tgt2);
69
+ static PartitionOp Require (unsigned tgt,
70
+ SILInstruction *sourceInst = nullptr ) {
71
+ return PartitionOp (PartitionOpKind::Require, tgt, sourceInst);
46
72
}
47
73
48
- static PartitionOp Require (unsigned tgt) {
49
- return PartitionOp (PartitionOpKind::Require, tgt);
74
+ SILInstruction *getSourceInst () const {
75
+ return sourceInst;
76
+ }
77
+
78
+ void dump () const {
79
+ switch (OpKind) {
80
+ case PartitionOpKind::Assign:
81
+ llvm::dbgs () << " assign %" << OpArgs[0 ] << " = %" << OpArgs[1 ] << " \n " ;
82
+ break ;
83
+ case PartitionOpKind::AssignFresh:
84
+ llvm::dbgs () << " assign_fresh %" << OpArgs[0 ] << " \n " ;
85
+ break ;
86
+ case PartitionOpKind::Consume:
87
+ llvm::dbgs () << " consume %" << OpArgs[0 ] << " \n " ;
88
+ break ;
89
+ case PartitionOpKind::Merge:
90
+ llvm::dbgs () << " merge %" << OpArgs[0 ] << " with %" << OpArgs[1 ] << " \n " ;
91
+ break ;
92
+ case PartitionOpKind::Require:
93
+ llvm::dbgs () << " require %" << OpArgs[0 ] << " \n " ;
94
+ break ;
95
+ }
50
96
}
51
97
};
52
98
@@ -72,22 +118,33 @@ static void horizontalUpdate(std::map<unsigned, signed> &map, unsigned key,
72
118
73
119
class Partition {
74
120
private:
75
- std::map<unsigned , signed > labels = {};
121
+ // label each index with a non-negative (unsigned) label if it is associated
122
+ // with a valid region, and with -1 if it is associated with a consumed region
123
+ // in-order traversal relied upon
124
+ std::map<unsigned , signed > labels;
125
+
126
+ // track a label that is guaranteed to be fresh
127
+ unsigned fresh_label = 0 ;
76
128
77
- bool canonical = true ;
129
+ // in a canonical partition, all regions are labelled with the smallest index
130
+ // of any member. Certain operations like join and equals rely on canonicality
131
+ // so when it's invalidated this boolean tracks that, and it must be
132
+ // reestablished by a call to canonicalize()
133
+ bool canonical;
78
134
79
135
// linear time - For each region label that occurs, find the first index
80
136
// at which it occurs and relabel all instances of it to that index.
81
- // This excludes the -1 label for missing region .
137
+ // This excludes the -1 label for consumed regions .
82
138
void canonicalize () {
83
139
if (canonical)
84
140
return ;
85
141
canonical = true ;
86
142
87
143
std::map<signed , unsigned > relabel;
88
144
145
+ // relies on in-order traversal of labels
89
146
for (auto &[i, label] : labels) {
90
- // leave -1 (missing region) as is
147
+ // leave -1 (consumed region) as is
91
148
if (label < 0 )
92
149
continue ;
93
150
@@ -98,11 +155,31 @@ class Partition {
98
155
relabel[label] = i;
99
156
}
100
157
158
+ // update this label with either its own index, or a prior index that
159
+ // shared a region with it
101
160
label = relabel[label];
161
+
162
+ // the maximum index iterated over will be used here to appropriately
163
+ // set fresh_label
164
+ fresh_label = i + 1 ;
102
165
}
103
166
}
104
167
105
168
public:
169
+ Partition () : labels({}), canonical(true ) {}
170
+
171
+ static Partition singleRegion (std::vector<unsigned > indices) {
172
+ Partition p;
173
+ if (!indices.empty ()) {
174
+ unsigned min_index = *std::min_element (indices.begin (), indices.end ());
175
+ p.fresh_label = min_index + 1 ;
176
+ for (unsigned index : indices) {
177
+ p.labels [index] = min_index;
178
+ }
179
+ }
180
+ return p;
181
+ }
182
+
106
183
void dump () const {
107
184
llvm::dbgs () << " Partition" ;
108
185
if (canonical)
@@ -160,42 +237,35 @@ class Partition {
160
237
for (const auto &[i, _] : fst.labels ) {
161
238
if (!snd.labels .count (i))
162
239
continue ;
163
- joined.labels [i] = lookup_fst (i);
240
+ signed label_i = lookup_fst (i);
241
+ joined.labels [i] = label_i;
242
+ joined.fresh_label = std::max (joined.fresh_label , (unsigned ) label_i + 1 );
164
243
}
165
244
166
245
return joined;
167
246
}
168
247
169
- // It's possible for all PartitionOps' to maintain canonicality,
170
- // but it comes at the cost of making Assign operations worst-case
171
- // linear time instead of constant. This is likely not worth it,
172
- // so it's disabled by default, but leaving this flag here in case
173
- // it becomes useful as a performance optimization.
174
- static const bool ALWAYS_CANONICAL = false ;
175
-
248
+ // Apply the passed PartitionOp to this partition, performing its action.
249
+ // A `handleFailure` closure can optionally be passed in that will be called
250
+ // if a consumed region is required. The closure is given the PartitionOp that
251
+ // failed, and the index of the SIL value that was required but consumed.
176
252
void apply (
177
- PartitionOp op, std::function <void (const PartitionOp & )> handleFailure =
178
- [](const PartitionOp &_ ) {}) {
253
+ PartitionOp op, llvm::function_ref <void (const PartitionOp&, unsigned )> handleFailure =
254
+ [](const PartitionOp&, unsigned ) {}) {
179
255
switch (op.OpKind ) {
180
256
case PartitionOpKind::Assign:
181
257
assert (op.OpArgs .size () == 2 &&
182
258
" Assign PartitionOp should be passed 2 arguments" );
183
- assert (labels.count (op.OpArgs [0 ]) && labels. count (op. OpArgs [ 1 ]) &&
184
- " Assign PartitionOp's arguments should be already tracked" );
259
+ assert (labels.count (op.OpArgs [1 ]) &&
260
+ " Assign PartitionOp's source argument should be already tracked" );
185
261
// if assigning to a missing region, handle the failure
186
262
if (labels[op.OpArgs [1 ]] < 0 )
187
- handleFailure (op);
263
+ handleFailure (op, op. OpArgs [ 1 ] );
188
264
189
265
labels[op.OpArgs [0 ]] = labels[op.OpArgs [1 ]];
190
266
191
- if (ALWAYS_CANONICAL) {
192
- // if seeking to maintain canonicality, then do so
193
- if (op.OpArgs [0 ] < labels[op.OpArgs [0 ]])
194
- horizontalUpdate (labels, op.OpArgs [0 ], op.OpArgs [0 ]);
195
- break ;
196
- }
197
-
198
- // assignment could have invalidated canonicality
267
+ // assignment could have invalidated canonicality of either the old region
268
+ // of op.OpArgs[0] or the region of op.OpArgs[1], or both
199
269
canonical = false ;
200
270
break ;
201
271
case PartitionOpKind::AssignFresh:
@@ -204,30 +274,33 @@ class Partition {
204
274
assert (!labels.count (op.OpArgs [0 ]) &&
205
275
" AssignFresh PartitionOp's argument should NOT already be tracked" );
206
276
207
- // fresh region generated by mapping the passed index to itself
208
- labels[op.OpArgs [0 ]] = op.OpArgs [0 ];
277
+ // map index op.OpArgs[0] to a fresh label
278
+ labels[op.OpArgs [0 ]] = fresh_label++;
279
+ canonical = false ;
209
280
break ;
210
281
case PartitionOpKind::Consume:
211
282
assert (op.OpArgs .size () == 1 &&
212
283
" Consume PartitionOp should be passed 1 argument" );
213
284
assert (labels.count (op.OpArgs [0 ]) &&
214
285
" Consume PartitionOp's argument should already be tracked" );
215
286
216
- // if attempting to consume a missing region, handle the failure
287
+ // if attempting to consume a consumed region, handle the failure
217
288
if (labels[op.OpArgs [0 ]] < 0 )
218
- handleFailure (op);
289
+ handleFailure (op, op. OpArgs [ 0 ] );
219
290
220
- // mark region as missing
291
+ // mark region as consumed
221
292
horizontalUpdate (labels, op.OpArgs [0 ], -1 );
222
293
break ;
223
294
case PartitionOpKind::Merge:
224
295
assert (op.OpArgs .size () == 2 &&
225
296
" Merge PartitionOp should be passed 2 arguments" );
226
297
assert (labels.count (op.OpArgs [0 ]) && labels.count (op.OpArgs [1 ]) &&
227
298
" Merge PartitionOp's arguments should already be tracked" );
228
- // if attempting to merge a missing region, handle the failure
229
- if (labels[op.OpArgs [0 ]] < 0 || labels[op.OpArgs [1 ]] < 0 )
230
- handleFailure (op);
299
+ // if attempting to merge a consumed region, handle the failure
300
+ if (labels[op.OpArgs [0 ]] < 0 )
301
+ handleFailure (op, op.OpArgs [0 ]);
302
+ if (labels[op.OpArgs [1 ]] < 0 )
303
+ handleFailure (op, op.OpArgs [1 ]);
231
304
232
305
if (labels[op.OpArgs [0 ]] == labels[op.OpArgs [1 ]])
233
306
break ;
@@ -244,8 +317,26 @@ class Partition {
244
317
assert (labels.count (op.OpArgs [0 ]) &&
245
318
" Require PartitionOp's argument should already be tracked" );
246
319
if (labels[op.OpArgs [0 ]] < 0 )
247
- handleFailure (op);
320
+ handleFailure (op, op.OpArgs [0 ]);
321
+ }
322
+ }
323
+
324
+ void dump () {
325
+ std::map<signed , std::vector<unsigned >> buckets;
326
+
327
+ for (auto [i, label] : labels) {
328
+ buckets[label].push_back (i);
329
+ }
330
+
331
+ llvm::dbgs () << " [" ;
332
+ for (auto [label, indices] : buckets) {
333
+ llvm::dbgs () << (label < 0 ? " {" : " (" );
334
+ for (unsigned i : indices) {
335
+ llvm::dbgs () << i << " " ;
336
+ }
337
+ llvm::dbgs () << (label < 0 ? " }" : " )" );
248
338
}
339
+ llvm::dbgs () << " ]\n " ;
249
340
}
250
341
};
251
342
}
0 commit comments