Skip to content

Commit e7a1747

Browse files
author
jturcotti
committed
Tweak, improve, and debug the PartitionAnalysis engine until a fairly comprehensive suite of simple tests passes (region_based_sendability.swift)
1 parent 29bd728 commit e7a1747

File tree

6 files changed

+710
-185
lines changed

6 files changed

+710
-185
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -856,11 +856,12 @@ NOTE(sil_referencebinding_inout_binding_here, none,
856856
"inout binding here",
857857
())
858858

859-
// SendNonSendable checking
860-
861-
WARNING(send_non_sendable, none,
862-
"Non-Sendable value %0 consumed, then used at this site; could be race",
863-
(unsigned))
859+
// Warnings arising from the flow-sensitive checking of Sendability of
860+
// non-Sendable values
861+
WARNING(consumed_value_used, none,
862+
"Non-Sendable value consumed, then used at this site; could yield race with another thread", ())
863+
WARNING(arg_region_consumed, none,
864+
"This application could pass `self` or a Non-Sendable argument of this function to another thread, potentially yielding a race with the caller", ())
864865

865866
#define UNDEFINE_DIAGNOSTIC_MACROS
866867
#include "DefineDiagnosticMacros.h"

include/swift/SILOptimizer/Utils/PartitionUtils.h

Lines changed: 139 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,25 @@
99

1010
namespace swift {
1111

12+
// PartitionOpKind represents the different kinds of PartitionOps that
13+
// SILInstructions can be translated to
1214
enum class PartitionOpKind : uint8_t {
1315
// Assign one value to the region of another, takes two args, second arg
1416
// must already be tracked with a non-consumed region
1517
Assign,
18+
1619
// Assign one value to a fresh region, takes one arg.
1720
AssignFresh,
18-
// Consume the region of a value, takes one arg
21+
22+
// Consume the region of a value, takes one arg. Region of arg must be
23+
// non-consumed before the op.
1924
Consume,
25+
26+
// Merge the regions of two values, takes two args, both must be from
27+
// non-consumed regions.
2028
Merge,
29+
30+
// Require the region of a value to be non-consumed, takes one arg.
2131
Require
2232
};
2333

@@ -30,7 +40,7 @@ class PartitionOp {
3040
PartitionOpKind OpKind;
3141
llvm::SmallVector<unsigned, 2> OpArgs;
3242

33-
// record the SILInstruction that this PartitionOp was generated from, if
43+
// Record the SILInstruction that this PartitionOp was generated from, if
3444
// generated during compilation from a SILBasicBlock
3545
SILInstruction *sourceInst;
3646

@@ -75,7 +85,7 @@ class PartitionOp {
7585
return sourceInst;
7686
}
7787

78-
void dump() const {
88+
void dump() const LLVM_ATTRIBUTE_USED {
7989
switch (OpKind) {
8090
case PartitionOpKind::Assign:
8191
llvm::dbgs() << "assign %" << OpArgs[0] << " = %" << OpArgs[1] << "\n";
@@ -110,6 +120,7 @@ static void horizontalUpdate(std::map<unsigned, signed> &map, unsigned key,
110120
}
111121

112122
signed oldVal = map[key];
123+
if (val == oldVal) return;
113124

114125
for (auto [otherKey, otherVal] : map)
115126
if (otherVal == oldVal)
@@ -118,20 +129,49 @@ static void horizontalUpdate(std::map<unsigned, signed> &map, unsigned key,
118129

119130
class Partition {
120131
private:
121-
// label each index with a non-negative (unsigned) label if it is associated
132+
// Label each index with a non-negative (unsigned) label if it is associated
122133
// with a valid region, and with -1 if it is associated with a consumed region
123-
// in-order traversal relied upon
134+
// in-order traversal relied upon.
124135
std::map<unsigned, signed> labels;
125136

126-
// track a label that is guaranteed to be fresh
137+
// Track a label that is guaranteed to be strictly larger than all in use,
138+
// and therefore safe for use as a fresh label.
127139
unsigned fresh_label = 0;
128140

129-
// in a canonical partition, all regions are labelled with the smallest index
141+
// In a canonical partition, all regions are labelled with the smallest index
130142
// of any member. Certain operations like join and equals rely on canonicality
131143
// so when it's invalidated this boolean tracks that, and it must be
132-
// reestablished by a call to canonicalize()
144+
// reestablished by a call to canonicalize().
133145
bool canonical;
134146

147+
// Used only in assertions, check that Partitions promised to be canonical
148+
// are actually canonical
149+
bool is_canonical_correct() {
150+
if (!canonical) return true; // vacuously correct
151+
152+
auto fail = [&](unsigned i, int type) {
153+
llvm::dbgs() << "FAIL(i=" << i << "; type=" << type << "): ";
154+
dump();
155+
return false;
156+
};
157+
158+
for (auto &[i, label] : labels) {
159+
// correctness vacuous at consumed indices
160+
if (label < 0) continue;
161+
162+
// this label should not exceed fresh_label
163+
if (label >= fresh_label) return fail(i, 0);
164+
165+
// the label of a region should be at most as large as each index in it
166+
if (i < label) return fail(i, 1);
167+
168+
// each region label should refer to an index in that region
169+
if (labels[label] != label) return fail(i, 2);
170+
}
171+
172+
return true;
173+
}
174+
135175
// linear time - For each region label that occurs, find the first index
136176
// at which it occurs and relabel all instances of it to that index.
137177
// This excludes the -1 label for consumed regions.
@@ -163,11 +203,32 @@ class Partition {
163203
// set fresh_label
164204
fresh_label = i + 1;
165205
}
206+
207+
assert(is_canonical_correct());
208+
}
209+
210+
// linear time - merge the regions of two indices, maintaining canonicality
211+
void merge(unsigned fst, unsigned snd) {
212+
assert(labels.count(fst) && labels.count(snd));
213+
if (labels[fst] == labels[snd])
214+
return;
215+
216+
// maintain canonicality by renaming the greater-numbered region
217+
if (labels[fst] < labels[snd])
218+
horizontalUpdate(labels, snd, labels[fst]);
219+
else
220+
horizontalUpdate(labels, fst, labels[snd]);
221+
222+
assert(is_canonical_correct());
166223
}
167224

168225
public:
169226
Partition() : labels({}), canonical(true) {}
170227

228+
// 1-arg constructor used when canonicality will be immediately invalidated,
229+
// so set to false to begin with
230+
Partition(bool canonical) : labels({}), canonical(canonical) {}
231+
171232
static Partition singleRegion(std::vector<unsigned> indices) {
172233
Partition p;
173234
if (!indices.empty()) {
@@ -177,17 +238,9 @@ class Partition {
177238
p.labels[index] = min_index;
178239
}
179240
}
180-
return p;
181-
}
182241

183-
void dump() const {
184-
llvm::dbgs() << "Partition";
185-
if (canonical)
186-
llvm::dbgs() << "(canonical)";
187-
llvm::dbgs() << "{";
188-
for (const auto &[i, label] : labels)
189-
llvm::dbgs() << "[" << i << ": " << label << "] ";
190-
llvm::dbgs() << "}\n";
242+
assert(p.is_canonical_correct());
243+
return p;
191244
}
192245

193246
// linear time - Test two partititons for equality by first putting them
@@ -204,54 +257,52 @@ class Partition {
204257
// and two indices are in the same region of the join iff they are in the same
205258
// region in either operand.
206259
static Partition join(Partition &fst, Partition &snd) {
207-
fst.canonicalize();
208-
snd.canonicalize();
209-
210-
std::map<unsigned, signed> relabel_fst;
211-
std::map<unsigned, signed> relabel_snd;
212-
auto lookup_fst = [&](unsigned i) {
213-
// signed to unsigned conversion... ?
214-
return relabel_fst.count(fst.labels[i]) ? relabel_fst[fst.labels[i]]
215-
: fst.labels[i];
216-
};
217-
218-
auto lookup_snd = [&](unsigned i) {
219-
// signed to unsigned conversion... safe?
220-
return relabel_snd.count(snd.labels[i]) ? relabel_snd[snd.labels[i]]
221-
: snd.labels[i];
222-
};
223-
224-
for (const auto &[i, _] : fst.labels) {
225-
// only consider indices present in both fst and snd
226-
if (!snd.labels.count(i))
227-
continue;
228-
229-
signed label_joined = std::min(lookup_fst(i), lookup_snd(i));
230-
231-
horizontalUpdate(relabel_fst, fst.labels[i], label_joined);
232-
horizontalUpdate(relabel_snd, snd.labels[i], label_joined);
260+
//ensure copies are made
261+
Partition fst_reduced = false;
262+
Partition snd_reduced = false;
263+
264+
// make canonical copies of fst and snd, reduced to their intersected domain
265+
for (auto [i, _] : fst.labels)
266+
if (snd.labels.count(i)) {
267+
fst_reduced.labels[i] = fst.labels[i];
268+
snd_reduced.labels[i] = snd.labels[i];
269+
}
270+
fst_reduced.canonicalize();
271+
snd_reduced.canonicalize();
272+
273+
// merging each index in fst with its label in snd ensures that all pairs
274+
// of indices that are in the same region in snd are also in the same region
275+
// in fst - the desired property
276+
for (const auto [i, snd_label] : snd_reduced.labels) {
277+
if (snd_label < 0)
278+
// if snd says that the region has been consumed, mark it consumed in fst
279+
horizontalUpdate(fst_reduced.labels, i, -1);
280+
else
281+
fst_reduced.merge(i, snd_label);
233282
}
234283

235-
Partition joined;
236-
joined.canonical = true;
237-
for (const auto &[i, _] : fst.labels) {
238-
if (!snd.labels.count(i))
239-
continue;
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);
243-
}
284+
assert(fst_reduced.is_canonical_correct());
244285

245-
return joined;
286+
// fst_reduced is now the join
287+
return fst_reduced;
246288
}
247289

248290
// Apply the passed PartitionOp to this partition, performing its action.
249291
// A `handleFailure` closure can optionally be passed in that will be called
250292
// if a consumed region is required. The closure is given the PartitionOp that
251293
// failed, and the index of the SIL value that was required but consumed.
294+
// Additionally, a list of "nonconsumable" indices can be passed in along with
295+
// a handleConsumeNonConsumable closure. In the event that a region containing
296+
// one of the nonconsumable indices is consumed, the closure will be called
297+
// with the offending Consume.
252298
void apply(
253-
PartitionOp op, llvm::function_ref<void(const PartitionOp&, unsigned)> handleFailure =
254-
[](const PartitionOp&, unsigned) {}) {
299+
PartitionOp op,
300+
llvm::function_ref<void(const PartitionOp&, unsigned)>
301+
handleFailure = [](const PartitionOp&, unsigned) {},
302+
std::vector<unsigned> nonconsumables = {},
303+
llvm::function_ref<void(const PartitionOp&, unsigned)>
304+
handleConsumeNonConsumable = [](const PartitionOp&, unsigned) {}
305+
) {
255306
switch (op.OpKind) {
256307
case PartitionOpKind::Assign:
257308
assert(op.OpArgs.size() == 2 &&
@@ -290,6 +341,19 @@ class Partition {
290341

291342
// mark region as consumed
292343
horizontalUpdate(labels, op.OpArgs[0], -1);
344+
345+
// check if any nonconsumables were consumed, and handle the failure if so
346+
for (unsigned nonconsumable : nonconsumables) {
347+
assert(labels.count(nonconsumable) &&
348+
"nonconsumables should be function args and self, and therefore"
349+
"always present in the label map because of initialization at "
350+
"entry");
351+
if (labels[nonconsumable] < 0) {
352+
handleConsumeNonConsumable(op, nonconsumable);
353+
break;
354+
}
355+
}
356+
293357
break;
294358
case PartitionOpKind::Merge:
295359
assert(op.OpArgs.size() == 2 &&
@@ -302,14 +366,7 @@ class Partition {
302366
if (labels[op.OpArgs[1]] < 0)
303367
handleFailure(op, op.OpArgs[1]);
304368

305-
if (labels[op.OpArgs[0]] == labels[op.OpArgs[1]])
306-
break;
307-
308-
// maintain canonicality by renaming the greater-numbered region
309-
if (labels[op.OpArgs[0]] < labels[op.OpArgs[1]])
310-
horizontalUpdate(labels, op.OpArgs[1], labels[op.OpArgs[0]]);
311-
else
312-
horizontalUpdate(labels, op.OpArgs[0], labels[op.OpArgs[1]]);
369+
merge(op.OpArgs[0], op.OpArgs[1]);
313370
break;
314371
case PartitionOpKind::Require:
315372
assert(op.OpArgs.size() == 1 &&
@@ -319,9 +376,21 @@ class Partition {
319376
if (labels[op.OpArgs[0]] < 0)
320377
handleFailure(op, op.OpArgs[0]);
321378
}
379+
380+
assert(is_canonical_correct());
322381
}
323382

324-
void dump() {
383+
void dump_labels() const LLVM_ATTRIBUTE_USED {
384+
llvm::dbgs() << "Partition";
385+
if (canonical)
386+
llvm::dbgs() << "(canonical)";
387+
llvm::dbgs() << "(fresh=" << fresh_label << "){";
388+
for (const auto &[i, label] : labels)
389+
llvm::dbgs() << "[" << i << ": " << label << "] ";
390+
llvm::dbgs() << "}\n";
391+
}
392+
393+
void dump() LLVM_ATTRIBUTE_USED {
325394
std::map<signed, std::vector<unsigned>> buckets;
326395

327396
for (auto [i, label] : labels) {
@@ -331,12 +400,15 @@ class Partition {
331400
llvm::dbgs() << "[";
332401
for (auto [label, indices] : buckets) {
333402
llvm::dbgs() << (label < 0 ? "{" : "(");
403+
int j = 0;
334404
for (unsigned i : indices) {
335-
llvm::dbgs() << i << " ";
405+
llvm::dbgs() << (j++? " " : "") << i;
336406
}
337407
llvm::dbgs() << (label < 0 ? "}" : ")");
338408
}
339-
llvm::dbgs() << "]\n";
409+
llvm::dbgs() << "] | ";
410+
411+
dump_labels();
340412
}
341413
};
342414
}

0 commit comments

Comments
 (0)