Skip to content

Commit 0e33ce6

Browse files
committed
Merge remote-tracking branch 'origin/main' into rebranch
2 parents ffe9794 + fef7f53 commit 0e33ce6

17 files changed

+303
-147
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,21 @@ struct BorrowedValue {
630630
/// necessarilly dominated by the borrow scope.
631631
void computeTransitiveLiveness(MultiDefPrunedLiveness &liveness) const;
632632

633+
/// Whether \p insts are completely within this borrow introducer's local
634+
/// scope.
635+
///
636+
/// Precondition: \p insts are dominated by the local borrow introducer.
637+
///
638+
/// This ignores reborrows. The assumption is that, since \p insts are
639+
/// dominated by this local scope, checking the extended borrow scope should
640+
/// not be necessary to determine they are within the scope.
641+
///
642+
/// \p deadEndBlocks is optional during transition. It will be completely
643+
/// removed in an upcoming commit.
644+
template <typename Instructions>
645+
bool areWithinExtendedScope(Instructions insts,
646+
DeadEndBlocks *deadEndBlocks) const;
647+
633648
/// Returns true if \p uses are completely within this borrow introducer's
634649
/// local scope.
635650
///
@@ -1426,6 +1441,43 @@ bool isRedundantMoveValue(MoveValueInst *mvi);
14261441
/// `forEndBorrowValue`, which is the operand value of an `end_borrow`.
14271442
void updateReborrowFlags(SILValue forEndBorrowValue);
14281443

1444+
/// A location at which a value is used. Abstracts over explicit uses
1445+
/// (operands) and implicit uses (instructions).
1446+
struct UsePoint {
1447+
using Value = llvm::PointerUnion<SILInstruction *, Operand *>;
1448+
Value value;
1449+
1450+
UsePoint(Operand *op) : value(op) {}
1451+
UsePoint(SILInstruction *inst) : value(inst) {}
1452+
UsePoint(Value value) : value(value) {}
1453+
1454+
SILInstruction *getInstruction() const {
1455+
if (auto *op = dyn_cast<Operand *>(value)) {
1456+
return op->getUser();
1457+
}
1458+
return cast<SILInstruction *>(value);
1459+
}
1460+
1461+
Operand *getOperandOrNull() const { return dyn_cast<Operand *>(value); }
1462+
1463+
Operand *getOperand() const { return cast<Operand *>(value); }
1464+
};
1465+
1466+
struct UsePointToInstruction {
1467+
SILInstruction *operator()(const UsePoint point) const {
1468+
return point.getInstruction();
1469+
}
1470+
};
1471+
1472+
using UsePointInstructionRange =
1473+
TransformRange<ArrayRef<UsePoint>, UsePointToInstruction>;
1474+
1475+
struct PointToOperand {
1476+
Operand *operator()(const UsePoint point) const { return point.getOperand(); }
1477+
};
1478+
1479+
using PointOperandRange = TransformRange<ArrayRef<UsePoint>, PointToOperand>;
1480+
14291481
} // namespace swift
14301482

14311483
#endif

include/swift/SIL/PrunedLiveness.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,20 @@ class PrunedLiveRange : public PrunedLiveness {
729729
bool isWithinBoundary(SILInstruction *inst,
730730
DeadEndBlocks *deadEndBlocks) const;
731731

732+
/// Whether all \p insts are between this def and the liveness boundary;
733+
/// \p deadEndBlocks is optional.
734+
template <typename Instructions>
735+
bool areWithinBoundary(Instructions insts,
736+
DeadEndBlocks *deadEndBlocks) const {
737+
assert(asImpl().isInitialized());
738+
739+
for (auto *inst : insts) {
740+
if (!isWithinBoundary(inst, deadEndBlocks))
741+
return false;
742+
}
743+
return true;
744+
};
745+
732746
/// Returns true when all \p uses are between this def and the liveness
733747
/// boundary \p deadEndBlocks is optional.
734748
bool areUsesWithinBoundary(ArrayRef<Operand *> uses,

include/swift/SIL/SILInstruction.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,10 @@ class SILInstruction : public llvm::ilist_node<SILInstruction> {
670670
using TransformedOperandValueRange =
671671
OptionalTransformRange<ArrayRef<Operand>, OperandToTransformedValue>;
672672

673+
/// Functor for Operand::getUser()
674+
struct OperandToUser;
675+
using OperandUserRange = TransformRange<ArrayRef<Operand *>, OperandToUser>;
676+
673677
static OperandValueRange getOperandValues(ArrayRef<Operand*> operands);
674678

675679
OperandRefValueRange getOperandValues() const;
@@ -1021,6 +1025,12 @@ struct SILInstruction::OperandRefToValue {
10211025
}
10221026
};
10231027

1028+
struct SILInstruction::OperandToUser {
1029+
SILInstruction *operator()(const Operand *use) const {
1030+
return const_cast<Operand *>(use)->getUser();
1031+
}
1032+
};
1033+
10241034
struct SILInstruction::FilterOperandToRealOperand {
10251035
const SILInstruction &i;
10261036

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -975,8 +975,9 @@ computeTransitiveLiveness(MultiDefPrunedLiveness &liveness) const {
975975
});
976976
}
977977

978-
bool BorrowedValue::areUsesWithinExtendedScope(
979-
ArrayRef<Operand *> uses, DeadEndBlocks *deadEndBlocks) const {
978+
template <typename Instructions>
979+
bool BorrowedValue::areWithinExtendedScope(Instructions insts,
980+
DeadEndBlocks *deadEndBlocks) const {
980981
// First make sure that we actually have a local scope. If we have a non-local
981982
// scope, then we have something (like a SILFunctionArgument) where a larger
982983
// semantic construct (in the case of SILFunctionArgument, the function
@@ -988,7 +989,20 @@ bool BorrowedValue::areUsesWithinExtendedScope(
988989
// Compute the local scope's liveness.
989990
MultiDefPrunedLiveness liveness(value->getFunction());
990991
computeTransitiveLiveness(liveness);
991-
return liveness.areUsesWithinBoundary(uses, deadEndBlocks);
992+
return liveness.areWithinBoundary(insts, deadEndBlocks);
993+
}
994+
995+
template bool BorrowedValue::areWithinExtendedScope<UsePointInstructionRange>(
996+
UsePointInstructionRange insts, DeadEndBlocks *deadEndBlocks) const;
997+
998+
template bool
999+
BorrowedValue::areWithinExtendedScope<SILInstruction::OperandUserRange>(
1000+
SILInstruction::OperandUserRange insts, DeadEndBlocks *deadEndBlocks) const;
1001+
1002+
bool BorrowedValue::areUsesWithinExtendedScope(
1003+
ArrayRef<Operand *> uses, DeadEndBlocks *deadEndBlocks) const {
1004+
SILInstruction::OperandUserRange users(uses, SILInstruction::OperandToUser());
1005+
return areWithinExtendedScope(users, deadEndBlocks);
9921006
}
9931007

9941008
// The visitor \p func is only called on final scope-ending uses, not reborrows.

lib/SIL/Utils/PrunedLiveness.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -796,14 +796,8 @@ static FunctionTest SSAPrunedLiveness__areUsesWithinBoundary(
796796
template <typename LivenessWithDefs>
797797
bool PrunedLiveRange<LivenessWithDefs>::areUsesWithinBoundary(
798798
ArrayRef<Operand *> uses, DeadEndBlocks *deadEndBlocks) const {
799-
assert(asImpl().isInitialized());
800-
801-
for (auto *use : uses) {
802-
auto *user = use->getUser();
803-
if (!isWithinBoundary(user, deadEndBlocks))
804-
return false;
805-
}
806-
return true;
799+
SILInstruction::OperandUserRange users(uses, SILInstruction::OperandToUser());
800+
return areWithinBoundary(users, deadEndBlocks);
807801
}
808802

809803
template <typename LivenessWithDefs>

lib/SILOptimizer/Mandatory/OptimizeHopToExecutor.cpp

Lines changed: 45 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ class OptimizeHopToExecutor {
119119
/// Search for hop_to_executor instructions and add their operands to \p actors.
120120
void OptimizeHopToExecutor::collectActors(Actors &actors) {
121121
int uniqueActorID = 0;
122+
123+
if (auto isolation = function->getActorIsolation();
124+
isolation && isolation->isCallerIsolationInheriting()) {
125+
actors[function->maybeGetIsolatedArgument()] = uniqueActorID++;
126+
}
127+
122128
for (SILBasicBlock &block : *function) {
123129
for (SILInstruction &inst : block) {
124130
if (auto *hop = dyn_cast<HopToExecutorInst>(&inst)) {
@@ -193,10 +199,7 @@ void OptimizeHopToExecutor::solveDataflowBackward() {
193199
/// Returns true if \p inst is a suspension point or an async call.
194200
static bool isSuspensionPoint(SILInstruction *inst) {
195201
if (auto applySite = FullApplySite::isa(inst)) {
196-
// NOTE: For 6.2, we consider nonisolated(nonsending) to be a suspension
197-
// point, when it really isn't. We do this so that we have a truly
198-
// conservative change that does not change output.
199-
if (applySite.isAsync())
202+
if (applySite.isAsync() && !applySite.isCallerIsolationInheriting())
200203
return true;
201204
return false;
202205
}
@@ -213,8 +216,20 @@ bool OptimizeHopToExecutor::removeRedundantHopToExecutors(const Actors &actors)
213216

214217
// Initialize the dataflow.
215218
for (BlockState &state : blockStates) {
216-
state.entry = (state.block == function->getEntryBlock() ?
217-
BlockState::Unknown : BlockState::NotSet);
219+
state.entry = [&]() -> int {
220+
if (state.block != function->getEntryBlock()) {
221+
return BlockState::NotSet;
222+
}
223+
224+
if (auto isolation = function->getActorIsolation();
225+
isolation && isolation->isCallerIsolationInheriting()) {
226+
auto *fArg =
227+
cast<SILFunctionArgument>(function->maybeGetIsolatedArgument());
228+
return actors.lookup(SILValue(fArg));
229+
}
230+
231+
return BlockState::Unknown;
232+
}();
218233
state.intra = BlockState::NotSet;
219234
for (SILInstruction &inst : *state.block) {
220235
if (isSuspensionPoint(&inst)) {
@@ -316,44 +331,11 @@ void OptimizeHopToExecutor::updateNeedExecutor(int &needExecutor,
316331
return;
317332
}
318333

319-
// For 6.2 to be conservative, if we are calling a function with
320-
// caller_isolation_inheriting isolation, treat the callsite as if the
321-
// callsite is an instruction that needs an executor.
322-
//
323-
// DISCUSSION: The reason why we are doing this is that in 6.2, we are going
324-
// to continue treating caller isolation inheriting functions as a suspension
325-
// point for the purpose of eliminating redundant hop to executor to not make
326-
// this optimization more aggressive. Post 6.2, we will stop treating caller
327-
// isolation inheriting functions as suspension points, meaning this code can
328-
// be deleted.
329-
if (auto fas = FullApplySite::isa(inst);
330-
fas && fas.isAsync() && fas.isCallerIsolationInheriting()) {
331-
needExecutor = BlockState::ExecutorNeeded;
332-
return;
333-
}
334-
335-
// For 6.2, if we are in a caller isolation inheriting function, we need to
336-
// treat its return as an executor needing function before
337-
// isSuspensionPoint.
338-
//
339-
// DISCUSSION: We need to do this here since for 6.2, a caller isolation
340-
// inheriting function is going to be considered a suspension point to be
341-
// conservative and make this optimization strictly more conservative. Post
342-
// 6.2, since caller isolation inheriting functions will no longer be
343-
// considered suspension points, we will be able to sink this code into needs
344-
// executor.
345-
if (isa<ReturnInst>(inst)) {
346-
if (auto isolation = inst->getFunction()->getActorIsolation();
347-
isolation && isolation->isCallerIsolationInheriting()) {
348-
needExecutor = BlockState::ExecutorNeeded;
349-
return;
350-
}
351-
}
352-
353334
if (isSuspensionPoint(inst)) {
354335
needExecutor = BlockState::NoExecutorNeeded;
355336
return;
356337
}
338+
357339
if (needsExecutor(inst))
358340
needExecutor = BlockState::ExecutorNeeded;
359341
}
@@ -403,6 +385,29 @@ bool OptimizeHopToExecutor::needsExecutor(SILInstruction *inst) {
403385
if (isa<BeginBorrowInst>(inst) || isa<EndBorrowInst>(inst)) {
404386
return false;
405387
}
388+
389+
// A call to a caller isolation inheriting function does not create dead
390+
// executors since caller isolation inheriting functions do not hop in their
391+
// prologue.
392+
if (auto fas = FullApplySite::isa(inst);
393+
fas && fas.isAsync() && fas.isCallerIsolationInheriting()) {
394+
return true;
395+
}
396+
397+
// Treat returns from a caller isolation inheriting function as requiring the
398+
// liveness of hop to executors before it.
399+
//
400+
// DISCUSSION: We do this since callers of callee functions with isolation
401+
// inheriting isolation are not required to have a hop after the return from
402+
// the callee function... so we have no guarantee that there isn't code in the
403+
// caller that needs this hop to executor to run on the correct actor.
404+
if (isa<ReturnInst>(inst)) {
405+
if (auto isolation = inst->getFunction()->getActorIsolation();
406+
isolation && isolation->isCallerIsolationInheriting()) {
407+
return true;
408+
}
409+
}
410+
406411
return inst->mayReadOrWriteMemory();
407412
}
408413

lib/SILOptimizer/SemanticARC/CopyValueOpts.cpp

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "swift/SIL/MemAccessUtils.h"
2828
#include "swift/SIL/OwnershipUtils.h"
2929
#include "swift/SIL/Projection.h"
30+
#include "swift/SIL/Test.h"
3031

3132
using namespace swift;
3233
using namespace swift::semanticarc;
@@ -170,7 +171,7 @@ bool SemanticARCOptVisitor::performGuaranteedCopyValueOptimization(
170171
return borrowScope.isLocalScope();
171172
});
172173

173-
auto destroys = lr.getDestroyingUses();
174+
auto destroys = lr.getDestroyingInsts();
174175
if (destroys.empty() && haveAnyLocalScopes) {
175176
return false;
176177
}
@@ -196,8 +197,8 @@ bool SemanticARCOptVisitor::performGuaranteedCopyValueOptimization(
196197
// block.
197198
{
198199
if (llvm::any_of(borrowScopeIntroducers, [&](BorrowedValue borrowScope) {
199-
return !borrowScope.areUsesWithinExtendedScope(
200-
lr.getAllConsumingUses(), nullptr);
200+
return !borrowScope.areWithinExtendedScope(lr.getAllConsumingInsts(),
201+
nullptr);
201202
})) {
202203
LLVM_DEBUG(llvm::dbgs() << "copy_value is extending borrow introducer "
203204
"lifetime, bailing out\n");
@@ -238,8 +239,8 @@ bool SemanticARCOptVisitor::performGuaranteedCopyValueOptimization(
238239
}
239240

240241
if (llvm::any_of(borrowScopeIntroducers, [&](BorrowedValue borrowScope) {
241-
return !borrowScope.areUsesWithinExtendedScope(
242-
phiArgLR.getAllConsumingUses(), nullptr);
242+
return !borrowScope.areWithinExtendedScope(
243+
phiArgLR.getAllConsumingInsts(), nullptr);
243244
})) {
244245
return false;
245246
}
@@ -828,6 +829,20 @@ bool SemanticARCOptVisitor::tryPerformOwnedCopyValueOptimization(
828829
return true;
829830
}
830831

832+
namespace swift::test {
833+
static FunctionTest SemanticARCOptsCopyValueOptsGuaranteedValueOptTest(
834+
"semantic_arc_opts__copy_value_opts__guaranteed_value_opt",
835+
[](auto &function, auto &arguments, auto &test) {
836+
SemanticARCOptVisitor visitor(function, test.getPassManager(),
837+
*test.getDeadEndBlocks(),
838+
/*onlyMandatoryOpts=*/false);
839+
840+
visitor.performGuaranteedCopyValueOptimization(
841+
cast<CopyValueInst>(arguments.takeInstruction()));
842+
function.print(llvm::errs());
843+
});
844+
} // end namespace swift::test
845+
831846
//===----------------------------------------------------------------------===//
832847
// Top Level Entrypoint
833848
//===----------------------------------------------------------------------===//

lib/SILOptimizer/SemanticARC/OwnedToGuaranteedPhiOpt.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,11 @@ bool swift::semanticarc::tryConvertOwnedPhisToGuaranteedPhis(Context &ctx) {
212212
SmallVector<std::pair<SILValue, unsigned>, 8> incomingValueUpdates;
213213
for (auto introducer : ownedValueIntroducers) {
214214
SILValue v = introducer.value;
215-
OwnershipLiveRange lr(v);
215+
SmallVector<std::pair<Operand *, SILValue>, 4> extraConsumes;
216+
for (auto op : incomingValueOperandList) {
217+
extraConsumes.push_back({op.getOperand(), op.getOriginal()});
218+
}
219+
OwnershipLiveRange lr(v, extraConsumes);
216220

217221
// For now, we only handle copy_value for simplicity.
218222
//

0 commit comments

Comments
 (0)