Skip to content

Commit bcb8f1b

Browse files
committed
[region-isolation] Implement the dataflow correctly.
This involved fixing a few different bugs. 1. We were just performing dataflow by setting that only the initial block needs to be updated. This means that if there isn’t anything in the initial dataflow block, we won’t visit any successor blocks. Instead the correct thing to do here is to visit all blocks in the initial round. 2. I also needed to fix a separate issue where we were updating our union-find data structure incorrectly as found by an assert on transfernonsendable.swift that was triggered once I fixed 1. Put simply, we needed to set a new max label + 1 when our new max element is less than or equal to the old max label + 1… before we just did less than so if we had a new max element that is the same as our fresh label, we wouldn’t increment the fresh label. rdar://119584497
1 parent c0b9f40 commit bcb8f1b

File tree

5 files changed

+108
-50
lines changed

5 files changed

+108
-50
lines changed

include/swift/SILOptimizer/Utils/PartitionUtils.h

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,9 @@ class Partition {
577577
fstReduced.elementToRegionMap.find(Element(sndRegionNumber));
578578
if (iter != fstReduced.elementToRegionMap.end()) {
579579
fstReduced.elementToRegionMap.insert({sndEltNumber, iter->second});
580-
if (fstReduced.fresh_label < Region(sndEltNumber))
580+
// We want fresh_label to always be one element larger than our
581+
// maximum element.
582+
if (fstReduced.fresh_label <= Region(sndEltNumber))
581583
fstReduced.fresh_label = Region(sndEltNumber + 1);
582584
continue;
583585
}
@@ -597,15 +599,10 @@ class Partition {
597599
fstIter.first->getSecond() =
598600
fstIter.first->second->merge(sndIter->second);
599601
}
600-
if (fstReduced.fresh_label < sndRegionNumber)
602+
if (fstReduced.fresh_label <= sndRegionNumber)
601603
fstReduced.fresh_label = Region(sndEltNumber + 1);
602604
}
603605

604-
LLVM_DEBUG(llvm::dbgs() << "JOIN PEFORMED: \nFST: ";
605-
fst.print(llvm::dbgs()); llvm::dbgs() << "SND: ";
606-
snd.print(llvm::dbgs()); llvm::dbgs() << "RESULT: ";
607-
fstReduced.print(llvm::dbgs()););
608-
609606
assert(fstReduced.is_canonical_correct());
610607

611608
// fst_reduced is now the join

lib/SILOptimizer/Mandatory/TransferNonSendable.cpp

Lines changed: 26 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2639,9 +2639,6 @@ class BlockPartitionState {
26392639
/// Set if this block in the next iteration needs to be visited.
26402640
bool needsUpdate = false;
26412641

2642-
/// Set if we have ever visited this block at all.
2643-
bool reached = false;
2644-
26452642
/// The partition of elements into regions at the top of the block.
26462643
Partition entryPartition;
26472644

@@ -2698,8 +2695,12 @@ class BlockPartitionState {
26982695
// will be suppressed
26992696
eval.apply(partitionOp);
27002697
}
2698+
LLVM_DEBUG(llvm::dbgs() << " Working Partition: ";
2699+
workingPartition.print(llvm::dbgs()));
27012700
bool exitUpdated = !Partition::equals(exitPartition, workingPartition);
27022701
exitPartition = workingPartition;
2702+
LLVM_DEBUG(llvm::dbgs() << " Exit Partition: ";
2703+
exitPartition.print(llvm::dbgs()));
27032704
return exitUpdated;
27042705
}
27052706

@@ -2713,8 +2714,8 @@ class BlockPartitionState {
27132714
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
27142715

27152716
void print(llvm::raw_ostream &os) const {
2716-
os << SEP_STR << "BlockPartitionState[reached=" << reached
2717-
<< ", needsUpdate=" << needsUpdate << "]\nid: ";
2717+
os << SEP_STR << "BlockPartitionState[needsUpdate=" << needsUpdate
2718+
<< "]\nid: ";
27182719
#ifndef NDEBUG
27192720
auto printID = [&](SILBasicBlock *block) {
27202721
block->printID(os);
@@ -2974,9 +2975,11 @@ class PartitionAnalysis {
29742975
}),
29752976
allocator(), ptrSetFactory(allocator), function(fn), pofi(pofi),
29762977
solved(false) {
2977-
// Initialize the entry block as needing an update, and having a partition
2978-
// that places all its non-sendable args in a single region
2979-
blockStates[fn->getEntryBlock()].needsUpdate = true;
2978+
// Mark all blocks as needing to be updated.
2979+
for (auto &block : *fn) {
2980+
blockStates[&block].needsUpdate = true;
2981+
}
2982+
// Set our entry partition to have the "entry partition".
29802983
blockStates[fn->getEntryBlock()].entryPartition =
29812984
translator.getEntryPartition();
29822985
}
@@ -3001,57 +3004,32 @@ class PartitionAnalysis {
30013004
continue;
30023005
}
30033006

3004-
// mark this block as no longer needing an update
3007+
// Mark this block as no longer needing an update.
30053008
blockState.needsUpdate = false;
30063009

3007-
// mark this block as reached by the analysis
3008-
blockState.reached = true;
3009-
3010-
// compute the new entry partition to this block
3011-
Partition newEntryPartition;
3012-
bool firstPred = true;
3010+
// Compute the new entry partition to this block.
3011+
Partition newEntryPartition = blockState.entryPartition;
30133012

30143013
LLVM_DEBUG(llvm::dbgs() << " Visiting Preds!\n");
30153014

3016-
// This loop computes the join of the exit partitions of all
3015+
// This loop computes the union of the exit partitions of all
30173016
// predecessors of this block
30183017
for (SILBasicBlock *predBlock : block->getPredecessorBlocks()) {
30193018
BlockPartitionState &predState = blockStates[predBlock];
3020-
// ignore predecessors that haven't been reached by the analysis yet
3021-
if (!predState.reached)
3022-
continue;
3023-
3024-
if (firstPred) {
3025-
firstPred = false;
3026-
newEntryPartition = predState.exitPartition;
3027-
LLVM_DEBUG(llvm::dbgs() << " First Pred. bb"
3028-
<< predBlock->getDebugID() << ": ";
3029-
newEntryPartition.print(llvm::dbgs()));
3030-
continue;
3031-
}
30323019

3020+
// Predecessors that have not been reached yet will have an empty pred
3021+
// state... so just merge them all. Also our initial value of
30333022
LLVM_DEBUG(llvm::dbgs()
30343023
<< " Pred. bb" << predBlock->getDebugID() << ": ";
30353024
predState.exitPartition.print(llvm::dbgs()));
30363025
newEntryPartition =
30373026
Partition::join(newEntryPartition, predState.exitPartition);
30383027
}
30393028

3040-
// If we found predecessor blocks, then attempt to use them to update
3041-
// the entry partition for this block, and abort this block's update if
3042-
// the entry partition was not updated.
3043-
if (!firstPred) {
3044-
// if the recomputed entry partition is the same as the current one,
3045-
// perform no update
3046-
if (Partition::equals(newEntryPartition, blockState.entryPartition)) {
3047-
LLVM_DEBUG(llvm::dbgs()
3048-
<< " Entry partition is the same... skipping!\n");
3049-
continue;
3050-
}
3051-
3052-
// otherwise update the entry partition
3053-
blockState.entryPartition = newEntryPartition;
3054-
}
3029+
// Update the entry partition. We need to still try to
3030+
// recomputeExitFromEntry before we know if we made a difference to the
3031+
// exit partition after applying the instructions of the block.
3032+
blockState.entryPartition = newEntryPartition;
30553033

30563034
// recompute this block's exit partition from its (updated) entry
30573035
// partition, and if this changed the exit partition notify all
@@ -3169,6 +3147,8 @@ class PartitionAnalysis {
31693147
LLVM_DEBUG(llvm::dbgs() << "Walking blocks for diagnostics.\n");
31703148
for (auto [block, blockState] : blockStates) {
31713149
LLVM_DEBUG(llvm::dbgs() << "|--> Block bb" << block.getDebugID() << "\n");
3150+
LLVM_DEBUG(llvm::dbgs() << "Entry Partition: ";
3151+
blockState.getEntryPartition().print(llvm::dbgs()));
31723152

31733153
// Grab its entry partition and setup an evaluator for the partition that
31743154
// has callbacks that emit diagnsotics...
@@ -3180,6 +3160,9 @@ class PartitionAnalysis {
31803160
for (auto &partitionOp : blockState.getPartitionOps()) {
31813161
eval.apply(partitionOp);
31823162
}
3163+
3164+
LLVM_DEBUG(llvm::dbgs() << "Exit Partition: ";
3165+
workingPartition.print(llvm::dbgs()));
31833166
}
31843167

31853168
// Now that we have found all of our transferInsts/Requires emit errors.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
@interface MyNotificationCenter
3+
- (id)init;
4+
- (void)post;
5+
@end
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// RUN: %target-sil-opt -transfer-non-sendable -enable-experimental-feature RegionBasedIsolation -strict-concurrency=complete %s -verify
2+
3+
// PLEASE READ THIS!
4+
//
5+
// In this file, we test out how the checker handles dataflow in a CFG. We want
6+
// to validate that we properly propagate dataflow given instructions occur in
7+
// specific locations.
8+
9+
sil_stage raw
10+
11+
import Swift
12+
13+
////////////////////////
14+
// MARK: Declarations //
15+
////////////////////////
16+
17+
class NonSendableKlass {}
18+
19+
sil @transferKlass : $@convention(thin) @async (@guaranteed NonSendableKlass) -> ()
20+
sil @useKlass : $@convention(thin) (@guaranteed NonSendableKlass) -> ()
21+
sil @constructKlass : $@convention(thin) () -> @owned NonSendableKlass
22+
23+
/////////////////
24+
// MARK: Tests //
25+
/////////////////
26+
27+
// The problem this validates that we properly propagate dataflow through blocks
28+
// if the initial block does not have any partition state.
29+
sil [ossa] @haveIntroducerInMiddleOfCFG : $@convention(thin) @async () -> () {
30+
bb0:
31+
br bb0a
32+
33+
bb0a:
34+
%constructKlass = function_ref @constructKlass : $@convention(thin) () -> @owned NonSendableKlass
35+
%klass = apply %constructKlass() : $@convention(thin) () -> @owned NonSendableKlass
36+
cond_br undef, bb1, bb2
37+
38+
bb1:
39+
br bb3
40+
41+
bb2:
42+
br bb3
43+
44+
bb3:
45+
%transferKlass = function_ref @transferKlass : $@convention(thin) @async (@guaranteed NonSendableKlass) -> ()
46+
apply [caller_isolation=nonisolated] [callee_isolation=global_actor] %transferKlass(%klass) : $@convention(thin) @async (@guaranteed NonSendableKlass) -> ()
47+
// expected-warning @-1 {{passing argument of non-sendable type 'NonSendableKlass' from nonisolated context to global actor '<null>'-isolated context at this call site could yield a race with accesses later in this function}}
48+
%useKlass = function_ref @useKlass : $@convention(thin) (@guaranteed NonSendableKlass) -> ()
49+
apply %useKlass(%klass) : $@convention(thin) (@guaranteed NonSendableKlass) -> ()
50+
// expected-note @-1 {{access here could race}}
51+
destroy_value %klass : $NonSendableKlass
52+
%9999 = tuple ()
53+
return %9999 : $()
54+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// RUN: %target-swift-frontend -emit-sil -strict-concurrency=complete -enable-experimental-feature RegionBasedIsolation -disable-availability-checking %s -o /dev/null -import-objc-header %S/Inputs/transfernonsendable_crashers_objc.h
2+
3+
// REQUIRES: objc_interop
4+
5+
import Foundation
6+
7+
extension MyNotificationCenter {
8+
static var value = MyNotificationCenter()
9+
}
10+
11+
public func handleFile(at location: URL) throws {
12+
// createDownloadDirectoryIfRequired()
13+
let movedFileLocation = try moveTheme(from: location)
14+
let unzippedFileLocation = try unzipFile(at: movedFileLocation)
15+
MyNotificationCenter.value!.post()
16+
}
17+
18+
private func moveTheme(from location: URL) throws -> URL { fatalError() }
19+
private func unzipFile(at location: URL) throws -> URL { fatalError() }

0 commit comments

Comments
 (0)