Skip to content

Commit 29bd728

Browse files
author
jturcotti
committed
Begin to fill in the SendNonSendable SIL pass using a PartitionAnalysis engine, works for some simple examples but needs to address address values in SIL.
1 parent aae9f43 commit 29bd728

File tree

4 files changed

+713
-62
lines changed

4 files changed

+713
-62
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,5 +856,11 @@ 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))
864+
859865
#define UNDEFINE_DIAGNOSTIC_MACROS
860866
#include "DefineDiagnosticMacros.h"

include/swift/SILOptimizer/Utils/PartitionUtils.h

Lines changed: 141 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,98 @@
11
#ifndef SWIFT_PARTITIONUTILS_H
22
#define SWIFT_PARTITIONUTILS_H
33

4-
#include "llvm/Support/Debug.h"
4+
#include "swift/Basic/LLVM.h"
5+
#include "swift/SIL/SILInstruction.h"
56
#include "llvm/ADT/SmallVector.h"
7+
#include "llvm/Support/Debug.h"
8+
#include <algorithm>
69

710
namespace swift {
811

912
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
1015
Assign,
16+
// Assign one value to a fresh region, takes one arg.
1117
AssignFresh,
18+
// Consume the region of a value, takes one arg
1219
Consume,
1320
Merge,
1421
Require
1522
};
1623

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.
1728
class PartitionOp {
1829
private:
1930
PartitionOpKind OpKind;
2031
llvm::SmallVector<unsigned, 2> OpArgs;
2132

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) {}
2541

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) {}
2845

2946
friend class Partition;
3047

3148
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);
3457
}
3558

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);
3862
}
3963

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);
4267
}
4368

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);
4672
}
4773

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+
}
5096
}
5197
};
5298

@@ -72,22 +118,33 @@ static void horizontalUpdate(std::map<unsigned, signed> &map, unsigned key,
72118

73119
class Partition {
74120
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;
76128

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;
78134

79135
// linear time - For each region label that occurs, find the first index
80136
// 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.
82138
void canonicalize() {
83139
if (canonical)
84140
return;
85141
canonical = true;
86142

87143
std::map<signed, unsigned> relabel;
88144

145+
// relies on in-order traversal of labels
89146
for (auto &[i, label] : labels) {
90-
// leave -1 (missing region) as is
147+
// leave -1 (consumed region) as is
91148
if (label < 0)
92149
continue;
93150

@@ -98,11 +155,31 @@ class Partition {
98155
relabel[label] = i;
99156
}
100157

158+
// update this label with either its own index, or a prior index that
159+
// shared a region with it
101160
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;
102165
}
103166
}
104167

105168
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+
106183
void dump() const {
107184
llvm::dbgs() << "Partition";
108185
if (canonical)
@@ -160,42 +237,35 @@ class Partition {
160237
for (const auto &[i, _] : fst.labels) {
161238
if (!snd.labels.count(i))
162239
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);
164243
}
165244

166245
return joined;
167246
}
168247

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.
176252
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) {}) {
179255
switch (op.OpKind) {
180256
case PartitionOpKind::Assign:
181257
assert(op.OpArgs.size() == 2 &&
182258
"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");
185261
// if assigning to a missing region, handle the failure
186262
if (labels[op.OpArgs[1]] < 0)
187-
handleFailure(op);
263+
handleFailure(op, op.OpArgs[1]);
188264

189265
labels[op.OpArgs[0]] = labels[op.OpArgs[1]];
190266

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
199269
canonical = false;
200270
break;
201271
case PartitionOpKind::AssignFresh:
@@ -204,30 +274,33 @@ class Partition {
204274
assert(!labels.count(op.OpArgs[0]) &&
205275
"AssignFresh PartitionOp's argument should NOT already be tracked");
206276

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;
209280
break;
210281
case PartitionOpKind::Consume:
211282
assert(op.OpArgs.size() == 1 &&
212283
"Consume PartitionOp should be passed 1 argument");
213284
assert(labels.count(op.OpArgs[0]) &&
214285
"Consume PartitionOp's argument should already be tracked");
215286

216-
// if attempting to consume a missing region, handle the failure
287+
// if attempting to consume a consumed region, handle the failure
217288
if (labels[op.OpArgs[0]] < 0)
218-
handleFailure(op);
289+
handleFailure(op, op.OpArgs[0]);
219290

220-
// mark region as missing
291+
// mark region as consumed
221292
horizontalUpdate(labels, op.OpArgs[0], -1);
222293
break;
223294
case PartitionOpKind::Merge:
224295
assert(op.OpArgs.size() == 2 &&
225296
"Merge PartitionOp should be passed 2 arguments");
226297
assert(labels.count(op.OpArgs[0]) && labels.count(op.OpArgs[1]) &&
227298
"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]);
231304

232305
if (labels[op.OpArgs[0]] == labels[op.OpArgs[1]])
233306
break;
@@ -244,8 +317,26 @@ class Partition {
244317
assert(labels.count(op.OpArgs[0]) &&
245318
"Require PartitionOp's argument should already be tracked");
246319
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 ? "}" : ")");
248338
}
339+
llvm::dbgs() << "]\n";
249340
}
250341
};
251342
}

0 commit comments

Comments
 (0)