Skip to content

Commit 74ac12c

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.
1 parent 2de13df commit 74ac12c

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)
@@ -2741,6 +2752,40 @@ LOOKTHROUGH_IF_NONSENDABLE_RESULT_AND_OPERAND(UncheckedTakeEnumDataAddrInst)
27412752
// Custom Handling
27422753
//
27432754

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