Skip to content

Commit 630326f

Browse files
committed
[region-isolation] Make temporary alloc_stack that we form for returning values from a non-final class field take on the class method's isolation.
The reason why we are doing this is that otherwise, we have that the alloc_stack formed for the result is disconnected and despite the fact that we merge it into the actor region of the class method, we do not have that the alloc_stack specifically is marked when we attempt to squelch Please. This patch fixes that problem by detecting when an alloc_stack is being used as a temporary for an out parameter and makes the alloc_stack initially isolated as appropriate. It only does this in the specific cases where we can pattern match it which in my limited testing has handled everything. (cherry picked from commit 74ac12c)
1 parent 4a6fce6 commit 630326f

File tree

5 files changed

+992
-23
lines changed

5 files changed

+992
-23
lines changed

include/swift/SILOptimizer/Utils/PartitionUtils.h

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,27 +1073,23 @@ struct PartitionOpEvaluator {
10731073
}
10741074
std::tie(transferredRegionIsolation, isClosureCapturedElt) = *pairOpt;
10751075

1076-
// Before we do anything, see if our dynamic isolation kind is the same as
1077-
// the isolation info for our partition op. If they match, this is not a
1078-
// real transfer operation.
1079-
//
1080-
// DISCUSSION: We couldn't not emit this earlier since we needed the
1081-
// dynamic isolation info of our value.
1082-
if (auto calleeIsolationInfo = getIsolationInfo(op)) {
1083-
if (transferredRegionIsolation.hasSameIsolation(calleeIsolationInfo)) {
1084-
return;
1085-
}
1086-
}
1087-
1088-
// If we merged anything, we need to handle a transfer
1089-
// non-transferrable. We pass in the dynamic isolation region info of our
1090-
// region.
1091-
if (bool(transferredRegionIsolation) &&
1076+
// If we merged anything, we need to handle a transfer non-transferrable
1077+
// unless our value has the same isolation info as our callee.
1078+
auto calleeIsolationInfo = getIsolationInfo(op);
1079+
if (!(calleeIsolationInfo &&
1080+
transferredRegionIsolation.hasSameIsolation(calleeIsolationInfo)) &&
10921081
!transferredRegionIsolation.isDisconnected()) {
10931082
return handleTransferNonTransferrableHelper(op, op.getOpArgs()[0],
10941083
transferredRegionIsolation);
10951084
}
10961085

1086+
// Next see if we are disconnected and have the same isolation. In such a
1087+
// case, we do not transfer since the disconnected value is allowed to be
1088+
// resued after we return.
1089+
if (transferredRegionIsolation.isDisconnected() && calleeIsolationInfo &&
1090+
transferredRegionIsolation.hasSameIsolation(calleeIsolationInfo))
1091+
return;
1092+
10971093
// Mark op.getOpArgs()[0] as transferred.
10981094
TransferringOperandState &state = operandToStateMap.get(op.getSourceOp());
10991095
state.isClosureCaptured |= isClosureCapturedElt;

lib/SILOptimizer/Analysis/RegionAnalysis.cpp

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "swift/SIL/OperandDatastructures.h"
3030
#include "swift/SIL/OwnershipUtils.h"
3131
#include "swift/SIL/PatternMatch.h"
32+
#include "swift/SIL/PrunedLiveness.h"
3233
#include "swift/SIL/SILBasicBlock.h"
3334
#include "swift/SIL/SILBuilder.h"
3435
#include "swift/SIL/SILFunction.h"
@@ -2018,6 +2019,17 @@ class PartitionOpTranslator {
20182019
TinyPtrVector<SILValue>());
20192020
}
20202021

2022+
void translateSILAssignFresh(SILValue val, SILIsolationInfo info) {
2023+
auto v = initializeTrackedValue(val, info);
2024+
if (!v)
2025+
return translateSILAssignFresh(val);
2026+
2027+
if (!v->second)
2028+
return translateUnknownPatternError(val);
2029+
2030+
return translateSILAssignFresh(v->first.getRepresentative().getValue());
2031+
}
2032+
20212033
template <typename Collection>
20222034
void translateSILMerge(SILValue dest, Collection collection) {
20232035
auto trackableDest = tryToTrackValue(dest);
@@ -2386,7 +2398,6 @@ CONSTANT_TRANSLATION(AllocBoxInst, AssignFresh)
23862398
CONSTANT_TRANSLATION(AllocPackInst, AssignFresh)
23872399
CONSTANT_TRANSLATION(AllocRefDynamicInst, AssignFresh)
23882400
CONSTANT_TRANSLATION(AllocRefInst, AssignFresh)
2389-
CONSTANT_TRANSLATION(AllocStackInst, AssignFresh)
23902401
CONSTANT_TRANSLATION(AllocVectorInst, AssignFresh)
23912402
CONSTANT_TRANSLATION(KeyPathInst, AssignFresh)
23922403
CONSTANT_TRANSLATION(FunctionRefInst, AssignFresh)
@@ -2740,6 +2751,40 @@ LOOKTHROUGH_IF_NONSENDABLE_RESULT_AND_OPERAND(UncheckedTakeEnumDataAddrInst)
27402751
// Custom Handling
27412752
//
27422753

2754+
TranslationSemantics
2755+
PartitionOpTranslator::visitAllocStackInst(AllocStackInst *asi) {
2756+
// Before we do anything, see if asi is Sendable or if it is non-Sendable,
2757+
// that it is from a var decl. In both cases, we can just return assign fresh
2758+
// and exit early.
2759+
if (!SILIsolationInfo::isNonSendableType(asi) || asi->isFromVarDecl())
2760+
return TranslationSemantics::AssignFresh;
2761+
2762+
// Ok at this point we know that our value is a non-Sendable temporary.
2763+
auto isolationInfo = SILIsolationInfo::get(asi);
2764+
if (!bool(isolationInfo)) {
2765+
return TranslationSemantics::AssignFresh;
2766+
}
2767+
2768+
if (isolationInfo.isDisconnected()) {
2769+
return TranslationSemantics::AssignFresh;
2770+
}
2771+
2772+
// Ok, we can handle this and have a valid isolation. Initialize the value.
2773+
auto v = initializeTrackedValue(asi, isolationInfo);
2774+
if (!v)
2775+
return TranslationSemantics::AssignFresh;
2776+
2777+
// If we already had a value for this alloc_stack (which we shouldn't
2778+
// ever)... emit an unknown pattern error.
2779+
if (!v->second) {
2780+
translateUnknownPatternError(asi);
2781+
return TranslationSemantics::Special;
2782+
}
2783+
2784+
translateSILAssignFresh(v->first.getRepresentative().getValue());
2785+
return TranslationSemantics::Special;
2786+
}
2787+
27432788
TranslationSemantics
27442789
PartitionOpTranslator::visitMoveValueInst(MoveValueInst *mvi) {
27452790
if (mvi->isFromVarDecl())

lib/SILOptimizer/Utils/SILIsolationInfo.cpp

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414

1515
#include "swift/AST/ASTWalker.h"
1616
#include "swift/AST/Expr.h"
17+
#include "swift/SIL/AddressWalker.h"
1718
#include "swift/SIL/ApplySite.h"
1819
#include "swift/SIL/InstructionUtils.h"
1920
#include "swift/SIL/PatternMatch.h"
2021
#include "swift/SIL/SILGlobalVariable.h"
22+
#include "swift/SIL/Test.h"
2123
#include "swift/SILOptimizer/Utils/VariableNameUtils.h"
2224

2325
using namespace swift;
@@ -141,6 +143,204 @@ bool DeclRefExprAnalysis::compute(Expr *expr) {
141143
return result;
142144
}
143145

146+
static SILIsolationInfo
147+
inferIsolationInfoForTempAllocStack(AllocStackInst *asi) {
148+
// We want to search for an alloc_stack that is not from a VarDecl and that is
149+
// initially isolated along all paths to the same actor isolation. If they
150+
// differ, then we emit a we do not understand error.
151+
struct AddressWalkerState {
152+
AllocStackInst *asi = nullptr;
153+
SmallVector<Operand *, 8> indirectResultUses;
154+
llvm::SmallSetVector<SILInstruction *, 8> writes;
155+
Operand *sameBlockIndirectResultUses = nullptr;
156+
};
157+
158+
struct AddressWalker final : TransitiveAddressWalker<AddressWalker> {
159+
AddressWalkerState &state;
160+
161+
AddressWalker(AddressWalkerState &state) : state(state) {
162+
assert(state.asi);
163+
}
164+
165+
bool visitUse(Operand *use) {
166+
// If we do not write to memory, then it is harmless.
167+
if (!use->getUser()->mayWriteToMemory())
168+
return true;
169+
170+
if (auto fas = FullApplySite::isa(use->getUser())) {
171+
if (fas.isIndirectResultOperand(*use)) {
172+
// If our indirect result use is in the same block...
173+
auto *parentBlock = state.asi->getParent();
174+
if (fas.getParent() == parentBlock) {
175+
// If we haven't seen any indirect result use yet... just cache it
176+
// and return true.
177+
if (!state.sameBlockIndirectResultUses) {
178+
state.sameBlockIndirectResultUses = use;
179+
return true;
180+
}
181+
182+
// If by walking from the alloc stack to the full apply site, we do
183+
// not see the current sameBlockIndirectResultUses, we have a new
184+
// newest use.
185+
if (llvm::none_of(
186+
llvm::make_range(state.asi->getIterator(),
187+
fas->getIterator()),
188+
[&](const SILInstruction &inst) {
189+
return &inst ==
190+
state.sameBlockIndirectResultUses->getUser();
191+
})) {
192+
state.sameBlockIndirectResultUses = use;
193+
}
194+
return true;
195+
}
196+
197+
// If not, just stash it into the non-same block indirect result use
198+
// array.
199+
state.indirectResultUses.push_back(use);
200+
return true;
201+
}
202+
}
203+
204+
state.writes.insert(use->getUser());
205+
return true;
206+
}
207+
};
208+
209+
AddressWalkerState state;
210+
state.asi = asi;
211+
AddressWalker walker(state);
212+
213+
// If we fail to walk, emit an unknown patten error.
214+
if (AddressUseKind::Unknown == std::move(walker).walk(asi)) {
215+
return SILIsolationInfo();
216+
}
217+
218+
// If we do not have any indirect result uses... we can just assign fresh.
219+
if (!state.sameBlockIndirectResultUses && state.indirectResultUses.empty())
220+
return SILIsolationInfo::getDisconnected(false /*isUnsafeNonIsolated*/);
221+
222+
// Otherwise, lets see if we had a same block indirect result.
223+
if (state.sameBlockIndirectResultUses) {
224+
// If we do not have any writes in between the alloc stack and the
225+
// initializer, then we have a good target. Otherwise, we just return
226+
// AssignFresh.
227+
if (llvm::none_of(
228+
llvm::make_range(
229+
asi->getIterator(),
230+
state.sameBlockIndirectResultUses->getUser()->getIterator()),
231+
[&](SILInstruction &inst) { return state.writes.count(&inst); })) {
232+
auto isolationInfo =
233+
SILIsolationInfo::get(state.sameBlockIndirectResultUses->getUser());
234+
if (isolationInfo) {
235+
return isolationInfo;
236+
}
237+
}
238+
239+
// If we did not find an isolation info, just do a normal assign fresh.
240+
return SILIsolationInfo::getDisconnected(false /*is unsafe non isolated*/);
241+
}
242+
243+
// Check if any of our writes are within the first block. This would
244+
// automatically stop our search and we should assign fresh. Since we are
245+
// going over the writes here, also setup a writeBlocks set.
246+
auto *defBlock = asi->getParent();
247+
BasicBlockSet writeBlocks(defBlock->getParent());
248+
for (auto *write : state.writes) {
249+
if (write->getParent() == defBlock)
250+
return SILIsolationInfo::getDisconnected(false /*unsafe non isolated*/);
251+
writeBlocks.insert(write->getParent());
252+
}
253+
254+
// Ok, at this point we know that we do not have any indirect result uses in
255+
// the def block and also we do not have any writes in that initial
256+
// block. This sets us up for our global analysis. Our plan is as follows:
257+
//
258+
// 1. We are going to create a set of writeBlocks and a map from SILBasicBlock
259+
// -> first indirect result block if there isn't a write before it.
260+
//
261+
// 2. We walk from our def block until we reach the first indirect result
262+
// block. We stop processing successor if we find a write block successor that
263+
// is not also an indirect result block. This makes sense since we earlier
264+
// required that any notates indirect result block do not have any writes in
265+
// between the indirect result and the beginning of the block.
266+
llvm::SmallDenseMap<SILBasicBlock *, Operand *, 2> blockToOperandMap;
267+
for (auto *use : state.indirectResultUses) {
268+
// If our indirect result use has a write before it in the block, do not
269+
// store it. It cannot be our indirect result initializer.
270+
if (writeBlocks.contains(use->getParentBlock()) &&
271+
llvm::any_of(
272+
use->getParentBlock()->getRangeEndingAtInst(use->getUser()),
273+
[&](SILInstruction &inst) { return state.writes.contains(&inst); }))
274+
continue;
275+
276+
// Ok, we now know that there aren't any writes before us in the block. Now
277+
// try to insert.
278+
auto iter = blockToOperandMap.try_emplace(use->getParentBlock(), use);
279+
280+
// If we actually inserted, then we are done.
281+
if (iter.second) {
282+
continue;
283+
}
284+
285+
// Otherwise, if we are before the current value, set us to be the value
286+
// instead.
287+
if (llvm::none_of(
288+
use->getParentBlock()->getRangeEndingAtInst(use->getUser()),
289+
[&](const SILInstruction &inst) {
290+
return &inst == iter.first->second->getUser();
291+
})) {
292+
iter.first->getSecond() = use;
293+
}
294+
}
295+
296+
// Ok, we now have our data all setup.
297+
BasicBlockWorklist worklist(asi->getFunction());
298+
for (auto *succBlock : asi->getParentBlock()->getSuccessorBlocks()) {
299+
worklist.pushIfNotVisited(succBlock);
300+
}
301+
302+
Operand *targetOperand = nullptr;
303+
while (auto *next = worklist.pop()) {
304+
// First check if this is one of our target blocks.
305+
auto iter = blockToOperandMap.find(next);
306+
307+
// If this is our target blocks...
308+
if (iter != blockToOperandMap.end()) {
309+
// If we already have an assigned target block, make sure this is the same
310+
// one. If it is, just continue. Otherwise, something happened we do not
311+
// understand... assign fresh.
312+
if (!targetOperand) {
313+
targetOperand = iter->second;
314+
continue;
315+
}
316+
317+
if (targetOperand->getParentBlock() == iter->first) {
318+
continue;
319+
}
320+
321+
return SILIsolationInfo::getDisconnected(
322+
false /*is unsafe non isolated*/);
323+
}
324+
325+
// Otherwise, see if this block is a write block. If so, we have a path to a
326+
// write block that does not go through one of our blockToOperandMap
327+
// blocks... return assign fresh.
328+
if (writeBlocks.contains(next))
329+
return SILIsolationInfo::getDisconnected(
330+
false /*is unsafe non isolated*/);
331+
332+
// Otherwise, visit this blocks successors if we have not yet visited them.
333+
for (auto *succBlock : next->getSuccessorBlocks()) {
334+
worklist.pushIfNotVisited(succBlock);
335+
}
336+
}
337+
338+
// At this point, we know that we have a single indirect result use that
339+
// dominates all writes and other indirect result uses. We can say that our
340+
// alloc_stack temporary is that indirect result use's isolation.
341+
return SILIsolationInfo::get(targetOperand->getUser());
342+
}
343+
144344
SILIsolationInfo SILIsolationInfo::get(SILInstruction *inst) {
145345
if (auto fas = FullApplySite::isa(inst)) {
146346
if (auto crossing = fas.getIsolationCrossing()) {
@@ -475,6 +675,12 @@ SILIsolationInfo SILIsolationInfo::get(SILInstruction *inst) {
475675
true /*is nonisolated(unsafe)*/);
476676
}
477677
}
678+
} else {
679+
// Ok, we have a temporary. If it is non-Sendable...
680+
if (SILIsolationInfo::isNonSendableType(asi)) {
681+
if (auto isolation = inferIsolationInfoForTempAllocStack(asi))
682+
return isolation;
683+
}
478684
}
479685
}
480686

@@ -842,3 +1048,28 @@ SILDynamicMergedIsolationInfo::merge(SILIsolationInfo other) const {
8421048
// Otherwise, just return other.
8431049
return other;
8441050
}
1051+
1052+
//===----------------------------------------------------------------------===//
1053+
// MARK: Tests
1054+
//===----------------------------------------------------------------------===//
1055+
1056+
namespace swift::test {
1057+
1058+
// Arguments:
1059+
// - SILValue: value to emit a name for.
1060+
// Dumps:
1061+
// - The inferred isolation.
1062+
static FunctionTest
1063+
IsolationInfoInferrence("sil-isolation-info-inference",
1064+
[](auto &function, auto &arguments, auto &test) {
1065+
auto value = arguments.takeValue();
1066+
1067+
SILIsolationInfo info =
1068+
SILIsolationInfo::get(value);
1069+
llvm::outs() << "Input Value: " << *value;
1070+
llvm::outs() << "Isolation: ";
1071+
info.printForOneLineLogging(llvm::outs());
1072+
llvm::outs() << "\n";
1073+
});
1074+
1075+
} // namespace swift::test

0 commit comments

Comments
 (0)