From 7bdd713bd0ec086791ce2537e0d5674b177af5bd Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Tue, 16 Dec 2025 13:39:04 -0800 Subject: [PATCH 1/5] Bailout of getAllBorrowIntroducingValues for ScopedAddressValue introducers --- lib/SIL/Utils/OwnershipUtils.cpp | 6 ++++++ test/SILOptimizer/semantic-arc-opts.sil | 27 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/lib/SIL/Utils/OwnershipUtils.cpp b/lib/SIL/Utils/OwnershipUtils.cpp index a4405fe579352..0974a54dcf7d5 100644 --- a/lib/SIL/Utils/OwnershipUtils.cpp +++ b/lib/SIL/Utils/OwnershipUtils.cpp @@ -23,6 +23,7 @@ #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILBuilder.h" #include "swift/SIL/SILInstruction.h" +#include "swift/SIL/ScopedAddressUtils.h" #include "swift/SIL/Test.h" using namespace swift; @@ -1245,6 +1246,11 @@ bool swift::getAllBorrowIntroducingValues(SILValue inputValue, continue; } + // If the introducer is a ScopedAddressValue, bailout. + if (auto scopedAddress = ScopedAddressValue(value)) { + return false; + } + // If v produces .none ownership, then we can ignore it. It is important // that we put this before checking for guaranteed forwarding instructions, // since we want to ignore guaranteed forwarding instructions that in this diff --git a/test/SILOptimizer/semantic-arc-opts.sil b/test/SILOptimizer/semantic-arc-opts.sil index a8fa448d597cf..62b9f80be04fb 100644 --- a/test/SILOptimizer/semantic-arc-opts.sil +++ b/test/SILOptimizer/semantic-arc-opts.sil @@ -55,6 +55,10 @@ final class Klass { let baseLet: Klass } +struct Wrapper { + let _k: Klass +} + extension Klass : MyFakeAnyObject { func myFakeMethod() } @@ -1641,3 +1645,26 @@ bb3(%11 : @reborrow $FakeOptional, %12 : @owned $FakeOptional): return %16 } +sil [ossa] @borrow_accessor : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed Klass { +bb0(%0 : $*Wrapper): + %1 = struct_element_addr %0, #Wrapper._k + %2 = load_borrow %1 + return_borrow %2 from_scopes (%2) +} + +sil [ossa] @call_borrow_accessor : $@convention(thin) (@guaranteed Wrapper) -> () { +bb0(%0 : @guaranteed $Wrapper): + %stk = alloc_stack $Wrapper + %sbi = store_borrow %0 to %stk + %func = function_ref @borrow_accessor : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed Klass + %k = apply %func(%sbi) : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed Klass + %copy = copy_value %k + end_borrow %sbi + %f = function_ref @guaranteed_klass_user : $@convention(thin) (@guaranteed Klass) -> () + apply %f(%copy) : $@convention(thin) (@guaranteed Klass) -> () + destroy_value %copy + dealloc_stack %stk + %r = tuple () + return %r +} + From 59c588fd3931492bae98d16a0a79c2612aed7721 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Wed, 17 Dec 2025 13:29:53 -0800 Subject: [PATCH 2/5] Remove bailout from GenericSpecializer for borrow accessors --- lib/SILOptimizer/Transforms/GenericSpecializer.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lib/SILOptimizer/Transforms/GenericSpecializer.cpp b/lib/SILOptimizer/Transforms/GenericSpecializer.cpp index a726870950a65..5592408db8bc0 100644 --- a/lib/SILOptimizer/Transforms/GenericSpecializer.cpp +++ b/lib/SILOptimizer/Transforms/GenericSpecializer.cpp @@ -107,17 +107,6 @@ bool swift::specializeAppliesInFunction(SILFunction &F, if (!Callee) continue; - // TODO: Enable GenericSpecializer for borrow accessors in OSSA - // Currently disabled since GenericSpecializer generates store_borrow to a - // temporary stack location and returns a projection from the - // store_borrow. This does not work for borrow accessors that return the - // projection from within the store_borrow scope. - if (F.hasOwnership() && - (Callee->getConventions().hasAddressResult() || - Callee->getConventions().hasGuaranteedResult())) { - continue; - } - FunctionBuilder.getModule().performOnceForPrespecializedImportedExtensions( [&FunctionBuilder](AbstractFunctionDecl *pre) { transferSpecializeAttributeTargets(FunctionBuilder.getModule(), FunctionBuilder, From b467c5ec3ca566bccb33a8e57c6c99a51e459af3 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Wed, 17 Dec 2025 13:30:06 -0800 Subject: [PATCH 3/5] [NFC] Remove unused DominanceInfo from OSSACompleteLifetime and InteriorLiveness --- include/swift/SIL/OSSACompleteLifetime.h | 22 +++++----------- include/swift/SIL/OwnershipLiveness.h | 3 +-- lib/SIL/Utils/OSSACompleteLifetime.cpp | 13 +++++----- lib/SIL/Utils/OwnershipLiveness.cpp | 26 +++++++------------ .../Mandatory/MoveOnlyAddressCheckerUtils.cpp | 2 +- .../Mandatory/MoveOnlyChecker.cpp | 2 +- .../Mandatory/PredictableMemOpt.cpp | 21 ++++++--------- lib/SILOptimizer/Mandatory/SILGenCleanup.cpp | 2 +- .../Transforms/DeadCodeElimination.cpp | 5 ++-- lib/SILOptimizer/Transforms/SILMem2Reg.cpp | 4 +-- lib/SILOptimizer/Utils/OptimizerBridging.cpp | 3 +-- 11 files changed, 38 insertions(+), 65 deletions(-) diff --git a/include/swift/SIL/OSSACompleteLifetime.h b/include/swift/SIL/OSSACompleteLifetime.h index a36e1052df618..af734344ca10d 100644 --- a/include/swift/SIL/OSSACompleteLifetime.h +++ b/include/swift/SIL/OSSACompleteLifetime.h @@ -43,11 +43,6 @@ class OSSACompleteLifetime { enum HandleTrivialVariable_t { IgnoreTrivialVariable, ExtendTrivialVariable }; private: - /// If domInfo is nullptr, then InteriorLiveness never assumes dominance. As a - /// result it may report extra unenclosedPhis. In that case, any attempt to - /// create a new phi would result in an immediately redundant phi. - const DominanceInfo *domInfo = nullptr; - DeadEndBlocks &deadEndBlocks; /// Cache intructions already handled by the recursive algorithm to avoid @@ -69,13 +64,11 @@ class OSSACompleteLifetime { public: OSSACompleteLifetime( - SILFunction *function, const DominanceInfo *domInfo, - DeadEndBlocks &deadEndBlocks, + SILFunction *function, DeadEndBlocks &deadEndBlocks, HandleTrivialVariable_t handleTrivialVariable = IgnoreTrivialVariable, - bool forceLivenessVerification = false, - bool nonDestroyingEnd = false) - : domInfo(domInfo), deadEndBlocks(deadEndBlocks), - completedValues(function), handleTrivialVariable(handleTrivialVariable), + bool forceLivenessVerification = false, bool nonDestroyingEnd = false) + : deadEndBlocks(deadEndBlocks), completedValues(function), + handleTrivialVariable(handleTrivialVariable), ForceLivenessVerification(forceLivenessVerification), nonDestroyingEnd(nonDestroyingEnd) {} @@ -177,9 +170,6 @@ class OSSACompleteLifetime { class UnreachableLifetimeCompletion { SILFunction *function; - // If domInfo is nullptr, lifetime completion may attempt to recreate - // redundant phis, which should be immediately discarded. - const DominanceInfo *domInfo = nullptr; DeadEndBlocks &deadEndBlocks; BasicBlockSetVector unreachableBlocks; @@ -188,9 +178,9 @@ class UnreachableLifetimeCompletion { bool updatingLifetimes = false; public: - UnreachableLifetimeCompletion(SILFunction *function, DominanceInfo *domInfo, + UnreachableLifetimeCompletion(SILFunction *function, DeadEndBlocks &deadEndBlocks) - : function(function), domInfo(domInfo), deadEndBlocks(deadEndBlocks), + : function(function), deadEndBlocks(deadEndBlocks), unreachableBlocks(function), unreachableInsts(function), incompleteValues(function) {} diff --git a/include/swift/SIL/OwnershipLiveness.h b/include/swift/SIL/OwnershipLiveness.h index e6011231a97c0..a43b470f79810 100644 --- a/include/swift/SIL/OwnershipLiveness.h +++ b/include/swift/SIL/OwnershipLiveness.h @@ -246,8 +246,7 @@ class InteriorLiveness : public OSSALiveness { public: InteriorLiveness(SILValue def): OSSALiveness(def) {} - void compute(const DominanceInfo *domInfo, - InnerScopeHandlerRef handleInnerScope = InnerScopeHandlerRef()); + void compute(InnerScopeHandlerRef handleInnerScope = InnerScopeHandlerRef()); /// Compute the boundary from the blocks discovered during liveness analysis. void computeBoundary(PrunedLivenessBoundary &boundary) const { diff --git a/lib/SIL/Utils/OSSACompleteLifetime.cpp b/lib/SIL/Utils/OSSACompleteLifetime.cpp index 63ecde193afd1..1812c6d4efa7a 100644 --- a/lib/SIL/Utils/OSSACompleteLifetime.cpp +++ b/lib/SIL/Utils/OSSACompleteLifetime.cpp @@ -169,7 +169,7 @@ static FunctionTest LivenessPartialBoundaryOutsideUsersTest( [](auto &function, auto &arguments, auto &test) { SILValue value = arguments.takeValue(); InteriorLiveness liveness(value); - liveness.compute(test.getDominanceInfo()); + liveness.compute(); visitUsersOutsideLinearLivenessBoundary( value, liveness.getLiveness(), [](auto *inst) { inst->print(llvm::outs()); }); @@ -583,7 +583,7 @@ bool OSSACompleteLifetime::analyzeAndUpdateLifetime(SILValue value, nonDestroyingEnd, deadEndBlocks); } InteriorLiveness liveness(value); - liveness.compute(domInfo, handleInnerScope); + liveness.compute(handleInnerScope); if (VerifyLifetimeCompletion && boundary != Boundary::Availability && liveness.getAddressUseKind() != AddressUseKind::NonEscaping) { llvm::errs() << "Incomplete liveness for: " << value; @@ -619,10 +619,9 @@ static FunctionTest OSSACompleteLifetimeTest( auto *deb = test.getDeadEndBlocks(); llvm::outs() << "OSSA lifetime completion on " << kind << " boundary: " << value; - OSSACompleteLifetime completion(&function, /*domInfo*/ nullptr, *deb, - OSSACompleteLifetime::IgnoreTrivialVariable, - /*forceLivenessVerification=*/false, - nonDestroyingEnd); + OSSACompleteLifetime completion( + &function, *deb, OSSACompleteLifetime::IgnoreTrivialVariable, + /*forceLivenessVerification=*/false, nonDestroyingEnd); completion.completeOSSALifetime(value, kind); function.print(llvm::outs()); }); @@ -699,7 +698,7 @@ bool UnreachableLifetimeCompletion::completeLifetimes() { } } - OSSACompleteLifetime completion(function, domInfo, deadEndBlocks); + OSSACompleteLifetime completion(function, deadEndBlocks); bool changed = false; for (auto value : incompleteValues) { diff --git a/lib/SIL/Utils/OwnershipLiveness.cpp b/lib/SIL/Utils/OwnershipLiveness.cpp index 392e08b9720b5..57a60cd12d382 100644 --- a/lib/SIL/Utils/OwnershipLiveness.cpp +++ b/lib/SIL/Utils/OwnershipLiveness.cpp @@ -115,11 +115,6 @@ struct InteriorLivenessVisitor : InteriorLiveness &interiorLiveness; - // If domInfo is nullptr, then InteriorLiveness never assumes dominance. As a - // result it may report extra unenclosedPhis. In that case, any attempt to - // create a new phi would result in an immediately redundant phi. - const DominanceInfo *domInfo = nullptr; - /// handleInnerScopeCallback may add uses to the inner scope, but it may not /// modify the use-list containing \p borrowingOperand. This callback can be /// used to ensure that the inner scope is complete before visiting its scope @@ -134,13 +129,11 @@ struct InteriorLivenessVisitor : NodeSet visited; InteriorLivenessVisitor( - InteriorLiveness &interiorLiveness, - const DominanceInfo *domInfo, - InteriorLiveness::InnerScopeHandlerRef handleInnerScope) - : interiorLiveness(interiorLiveness), - domInfo(domInfo), - handleInnerScopeCallback(handleInnerScope), - visited(interiorLiveness.ownershipDef->getFunction()) {} + InteriorLiveness &interiorLiveness, + InteriorLiveness::InnerScopeHandlerRef handleInnerScope) + : interiorLiveness(interiorLiveness), + handleInnerScopeCallback(handleInnerScope), + visited(interiorLiveness.ownershipDef->getFunction()) {} bool handleUsePoint(Operand *use, UseLifetimeConstraint useConstraint) { interiorLiveness.liveness.updateForUse( @@ -182,11 +175,11 @@ struct InteriorLivenessVisitor : } }; -void InteriorLiveness::compute(const DominanceInfo *domInfo, InnerScopeHandlerRef handleInnerScope) { +void InteriorLiveness::compute(InnerScopeHandlerRef handleInnerScope) { liveness.initializeDef(ownershipDef); addressUseKind = AddressUseKind::NonEscaping; - InteriorLivenessVisitor(*this, domInfo, handleInnerScope) - .visitInteriorUses(ownershipDef); + InteriorLivenessVisitor(*this, handleInnerScope) + .visitInteriorUses(ownershipDef); } void InteriorLiveness::print(llvm::raw_ostream &OS) const { @@ -328,12 +321,11 @@ static FunctionTest SILValue value = arguments.takeValue(); function.print(llvm::outs()); llvm::outs() << "Interior liveness: " << value; - auto *domTree = test.getDominanceInfo(); InteriorLiveness liveness(value); auto handleInnerScope = [](SILValue innerBorrow) { llvm::outs() << "Inner scope: " << innerBorrow; }; - liveness.compute(domTree, handleInnerScope); + liveness.compute(handleInnerScope); liveness.print(llvm::outs()); PrunedLivenessBoundary boundary; diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp index d863a9bf2b27d..61600d73e2923 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp @@ -4106,7 +4106,7 @@ bool MoveOnlyAddressChecker::completeLifetimes() { // Lifetimes must be completed inside out (bottom-up in the CFG). PostOrderFunctionInfo *postOrder = poa->get(fn); - OSSACompleteLifetime completion(fn, domTree, *deadEndBlocksAnalysis->get(fn)); + OSSACompleteLifetime completion(fn, *deadEndBlocksAnalysis->get(fn)); for (auto *block : postOrder->getPostOrder()) { for (SILInstruction &inst : reverse(*block)) { for (auto result : inst.getResults()) { diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyChecker.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyChecker.cpp index b1e0e4e191a11..0b289469909e4 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyChecker.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyChecker.cpp @@ -123,7 +123,7 @@ void MoveOnlyChecker::checkObjects() { void MoveOnlyChecker::completeObjectLifetimes( ArrayRef insts) { // TODO: Delete once OSSACompleteLifetime is run as part of SILGenCleanup. - OSSACompleteLifetime completion(fn, domTree, *deba->get(fn)); + OSSACompleteLifetime completion(fn, *deba->get(fn)); // Collect all values derived from each mark_unresolved_non_copyable_value // instruction via ownership instructions and phis. diff --git a/lib/SILOptimizer/Mandatory/PredictableMemOpt.cpp b/lib/SILOptimizer/Mandatory/PredictableMemOpt.cpp index 436ea624da38d..c0cdfac9f6662 100644 --- a/lib/SILOptimizer/Mandatory/PredictableMemOpt.cpp +++ b/lib/SILOptimizer/Mandatory/PredictableMemOpt.cpp @@ -1921,8 +1921,6 @@ class OptimizeDeadAlloc { InstructionDeleter &deleter; - DominanceInfo *domInfo; - /// A structure that we use to compute our available values. AvailableValueDataflowContext DataflowContext; @@ -1935,21 +1933,19 @@ class OptimizeDeadAlloc { bool isTrivial() const { return MemoryType.isTrivial(getFunction()); } - OptimizeDeadAlloc(AllocationInst *memory, - SmallVectorImpl &uses, + OptimizeDeadAlloc(AllocationInst *memory, SmallVectorImpl &uses, SmallVectorImpl &releases, DeadEndBlocks &deadEndBlocks, InstructionDeleter &deleter, DominanceInfo *domInfo) : Module(memory->getModule()), TheMemory(memory), MemoryType(getMemoryType(memory)), - NumMemorySubElements(getNumSubElements( - MemoryType, *memory->getFunction(), - TypeExpansionContext(*memory->getFunction()))), + NumMemorySubElements( + getNumSubElements(MemoryType, *memory->getFunction(), + TypeExpansionContext(*memory->getFunction()))), Uses(uses), Releases(releases), deadEndBlocks(deadEndBlocks), - deleter(deleter), domInfo(domInfo), - DataflowContext(TheMemory, NumMemorySubElements, - OptimizationMode::ReplaceAlloc, uses, deleter, - deadEndBlocks) {} + deleter(deleter), DataflowContext(TheMemory, NumMemorySubElements, + OptimizationMode::ReplaceAlloc, uses, + deleter, deadEndBlocks) {} /// If the allocation is an autogenerated allocation that is only stored to /// (after load promotion) then remove it completely. @@ -2311,8 +2307,7 @@ void OptimizeDeadAlloc::removeDeadAllocation() { // post-dominating consuming use sets. This can happen if we have an enum that // is known dynamically none along a path. This is dynamically correct, but // can not be represented in OSSA so we insert these destroys along said path. - OSSACompleteLifetime completion(TheMemory->getFunction(), domInfo, - deadEndBlocks); + OSSACompleteLifetime completion(TheMemory->getFunction(), deadEndBlocks); while (!valuesNeedingLifetimeCompletion.empty()) { auto optV = valuesNeedingLifetimeCompletion.pop_back_val(); diff --git a/lib/SILOptimizer/Mandatory/SILGenCleanup.cpp b/lib/SILOptimizer/Mandatory/SILGenCleanup.cpp index f216f94be07ef..d0a6b4ac051ac 100644 --- a/lib/SILOptimizer/Mandatory/SILGenCleanup.cpp +++ b/lib/SILOptimizer/Mandatory/SILGenCleanup.cpp @@ -350,7 +350,7 @@ bool SILGenCleanup::completeOSSALifetimes(SILFunction *function) { } bool changed = false; - OSSACompleteLifetime completion(function, /*DomInfo*/ nullptr, *deba, + OSSACompleteLifetime completion(function, *deba, OSSACompleteLifetime::ExtendTrivialVariable); BasicBlockSet completed(function); for (auto *root : roots) { diff --git a/lib/SILOptimizer/Transforms/DeadCodeElimination.cpp b/lib/SILOptimizer/Transforms/DeadCodeElimination.cpp index 4e3bdb9179fa4..06e3d92567e50 100644 --- a/lib/SILOptimizer/Transforms/DeadCodeElimination.cpp +++ b/lib/SILOptimizer/Transforms/DeadCodeElimination.cpp @@ -148,7 +148,6 @@ class DCE { BasicBlockSet LiveBlocks; llvm::SmallVector Worklist; PostDominanceInfo *PDT; - DominanceInfo *DT; DeadEndBlocks *deadEndBlocks; llvm::DenseMap ControllingInfoMap; SmallBlotSetVector valuesToComplete; @@ -223,7 +222,7 @@ class DCE { DCE(SILFunction *F, PostDominanceInfo *PDT, DominanceInfo *DT, DeadEndBlocks *deadEndBlocks) : F(F), LiveArguments(F), LiveInstructions(F), LiveBlocks(F), PDT(PDT), - DT(DT), deadEndBlocks(deadEndBlocks) {} + deadEndBlocks(deadEndBlocks) {} /// The entry point to the transformation. bool run() { @@ -839,7 +838,7 @@ bool DCE::removeDead() { } } - OSSACompleteLifetime completion(F, DT, *deadEndBlocks); + OSSACompleteLifetime completion(F, *deadEndBlocks); for (auto value : valuesToComplete) { if (!value.has_value()) continue; diff --git a/lib/SILOptimizer/Transforms/SILMem2Reg.cpp b/lib/SILOptimizer/Transforms/SILMem2Reg.cpp index dd9b2a7f384a7..6a005eaf35b29 100644 --- a/lib/SILOptimizer/Transforms/SILMem2Reg.cpp +++ b/lib/SILOptimizer/Transforms/SILMem2Reg.cpp @@ -1831,7 +1831,7 @@ void StackAllocationPromoter::run(BasicBlockSetVector &livePhiBlocks) { // complete their lifetimes with `end_lifetime` instead of `destroy_value`. // This is especially important for embedded swift where we are not allowed // to insert destroys which were not there before. - OSSACompleteLifetime completion(function, domInfo, + OSSACompleteLifetime completion(function, *deadEndBlocksAnalysis->get(function), OSSACompleteLifetime::IgnoreTrivialVariable, /*forceLivenessVerification=*/false, @@ -2116,7 +2116,7 @@ void MemoryToRegisters::removeSingleBlockAllocation(AllocStackInst *asi) { // We may have incomplete lifetimes for enum locations on trivial paths. // After promoting them, complete lifetime here. ASSERT(asi->getElementType().isOrHasEnum()); - OSSACompleteLifetime completion(function, domInfo, *deadEndBlocks, + OSSACompleteLifetime completion(function, *deadEndBlocks, OSSACompleteLifetime::IgnoreTrivialVariable, /*forceLivenessVerification=*/false, /*nonDestroyingEnd=*/true); diff --git a/lib/SILOptimizer/Utils/OptimizerBridging.cpp b/lib/SILOptimizer/Utils/OptimizerBridging.cpp index 46f792bc8197b..38a5ce39f6827 100644 --- a/lib/SILOptimizer/Utils/OptimizerBridging.cpp +++ b/lib/SILOptimizer/Utils/OptimizerBridging.cpp @@ -476,8 +476,7 @@ bool BridgedPassContext::completeLifetime(BridgedValue value) const { SILValue v = value.getSILValue(); SILFunction *f = v->getFunction(); DeadEndBlocks *deb = invocation->getPassManager()->getAnalysis()->get(f); - DominanceInfo *domInfo = invocation->getPassManager()->getAnalysis()->get(f); - OSSACompleteLifetime completion(f, domInfo, *deb); + OSSACompleteLifetime completion(f, *deb); auto result = completion.completeOSSALifetime( v, OSSACompleteLifetime::Boundary::Availability); return result == LifetimeCompletion::WasCompleted; From cfd9e9878a9b4d35eb84007ebfc7ab001ec64cb8 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Thu, 18 Dec 2025 16:15:13 -0800 Subject: [PATCH 4/5] Fix GenericSpecializer for conditionally copyable types Non trivial types can become trivial on generic specialization. OSSA has instructions that are valid for non-trivial types only: load_borrow/end_borrow, load [copy]/[take], store [init]/[assign], copy_value, destroy_value etc Transform such instructions to their trivial counterparts while cloning to prevent asserts firing. --- include/swift/SIL/SILCloner.h | 68 +++++++++++++++++-- .../specialize_conditionally_copyable.sil | 53 +++++++++++++++ 2 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 test/SILOptimizer/specialize_conditionally_copyable.sil diff --git a/include/swift/SIL/SILCloner.h b/include/swift/SIL/SILCloner.h index 9144c582f6855..bd1654321f4f5 100644 --- a/include/swift/SIL/SILCloner.h +++ b/include/swift/SIL/SILCloner.h @@ -1377,6 +1377,15 @@ void SILCloner::visitLoadInst(LoadInst *Inst) { LoadOwnershipQualifier::Unqualified)); } + if (getOpValue(Inst->getOperand()) + ->getType() + .isTrivial(getBuilder().getFunction())) { + return recordClonedInstruction( + Inst, getBuilder().createLoad(getOpLocation(Inst->getLoc()), + getOpValue(Inst->getOperand()), + LoadOwnershipQualifier::Trivial)); + } + return recordClonedInstruction( Inst, getBuilder().createLoad(getOpLocation(Inst->getLoc()), getOpValue(Inst->getOperand()), @@ -1394,6 +1403,15 @@ void SILCloner::visitLoadBorrowInst(LoadBorrowInst *Inst) { LoadOwnershipQualifier::Unqualified)); } + if (getOpValue(Inst->getOperand()) + ->getType() + .isTrivial(getBuilder().getFunction())) { + return recordClonedInstruction( + Inst, getBuilder().createLoad(getOpLocation(Inst->getLoc()), + getOpValue(Inst->getOperand()), + LoadOwnershipQualifier::Trivial)); + } + recordClonedInstruction( Inst, getBuilder().createLoadBorrow(getOpLocation(Inst->getLoc()), getOpValue(Inst->getOperand()))); @@ -1402,7 +1420,10 @@ void SILCloner::visitLoadBorrowInst(LoadBorrowInst *Inst) { template void SILCloner::visitBeginBorrowInst(BeginBorrowInst *Inst) { getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope())); - if (!getBuilder().hasOwnership()) { + if (!getBuilder().hasOwnership() || + getOpValue(Inst->getOperand()) + ->getType() + .isTrivial(getBuilder().getFunction())) { return recordFoldedValue(Inst, getOpValue(Inst->getOperand())); } @@ -1416,7 +1437,10 @@ void SILCloner::visitBeginBorrowInst(BeginBorrowInst *Inst) { template void SILCloner::visitBorrowedFromInst(BorrowedFromInst *bfi) { getBuilder().setCurrentDebugScope(getOpScope(bfi->getDebugScope())); - if (!getBuilder().hasOwnership()) { + if (!getBuilder().hasOwnership() || + getOpValue(bfi->getBorrowedValue()) + ->getType() + .isTrivial(getBuilder().getFunction())) { return recordFoldedValue(bfi, getOpValue(bfi->getBorrowedValue())); } @@ -1456,6 +1480,16 @@ void SILCloner::visitStoreInst(StoreInst *Inst) { StoreOwnershipQualifier::Unqualified)); } + if (getOpValue(Inst->getSrc()) + ->getType() + .isTrivial(getBuilder().getFunction())) { + return recordClonedInstruction( + Inst, getBuilder().createStore(getOpLocation(Inst->getLoc()), + getOpValue(Inst->getSrc()), + getOpValue(Inst->getDest()), + StoreOwnershipQualifier::Trivial)); + } + recordClonedInstruction( Inst, getBuilder().createStore( getOpLocation(Inst->getLoc()), getOpValue(Inst->getSrc()), @@ -1473,6 +1507,16 @@ void SILCloner::visitStoreBorrowInst(StoreBorrowInst *Inst) { return; } + if (getOpValue(Inst->getDest()) + ->getType() + .isTrivial(getBuilder().getFunction())) { + getBuilder().createStore( + getOpLocation(Inst->getLoc()), getOpValue(Inst->getSrc()), + getOpValue(Inst->getDest()), StoreOwnershipQualifier::Trivial); + mapValue(Inst, getOpValue(Inst->getDest())); + return; + } + recordClonedInstruction( Inst, getBuilder().createStoreBorrow(getOpLocation(Inst->getLoc()), getOpValue(Inst->getSrc()), @@ -1484,7 +1528,10 @@ void SILCloner::visitEndBorrowInst(EndBorrowInst *Inst) { getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope())); // Do not clone any end_borrow. - if (!getBuilder().hasOwnership()) + if (!getBuilder().hasOwnership() || + getOpValue(Inst->getOperand()) + ->getType() + .isTrivial(getBuilder().getFunction())) return; recordClonedInstruction( @@ -2314,7 +2361,13 @@ void SILCloner::visitDestroyValueInst(DestroyValueInst *Inst) { return; } } - + + if (getOpValue(Inst->getOperand()) + ->getType() + .isTrivial(getBuilder().getFunction())) { + return; + } + return recordClonedInstruction( Inst, getBuilder().createReleaseValue( getOpLocation(Inst->getLoc()), getOpValue(Inst->getOperand()), @@ -3330,6 +3383,13 @@ template void SILCloner::visitDestroyAddrInst(DestroyAddrInst *Inst) { getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope())); + + if (getOpValue(Inst->getOperand()) + ->getType() + .isTrivial(getBuilder().getFunction())) { + return; + } + recordClonedInstruction( Inst, getBuilder().createDestroyAddr(getOpLocation(Inst->getLoc()), getOpValue(Inst->getOperand()))); diff --git a/test/SILOptimizer/specialize_conditionally_copyable.sil b/test/SILOptimizer/specialize_conditionally_copyable.sil new file mode 100644 index 0000000000000..e9678da075901 --- /dev/null +++ b/test/SILOptimizer/specialize_conditionally_copyable.sil @@ -0,0 +1,53 @@ +import Builtin +import Swift +import SwiftShims + +public struct Container : ~Copyable where Element : ~Copyable { + @_hasStorage var _storage: UnsafeMutableBufferPointer { get set } + @_hasStorage var count: Int32 { get set } +} + +extension Container : Copyable where Element : Copyable { +} + + +sil [ossa] [noinline] @foo1 : $@convention(method) (@in_guaranteed Container) -> Int32 { +bb0(%0 : $*Container): + %ld = load_borrow %0 + %count = struct_extract %ld, #Container.count + end_borrow %ld + return %count +} + +sil [ossa] [noinline] @foo2 : $@convention(method) (@in_guaranteed Container) -> Int32 { +bb0(%0 : $*Container): + %ld = load [copy] %0 + %b = begin_borrow %ld + %count = struct_extract %b, #Container.count + end_borrow %b + destroy_value %ld + return %count +} + +sil [ossa] [noinline] @foo3 : $@convention(method) (@inout Container, @owned Container) -> () { +bb0(%0 : $*Container, %1 : @owned $Container): + %copy = copy_value %1 + store %1 to [assign] %0 + destroy_addr %0 + store %copy to [init] %0 + %t = tuple () + return %t +} + +sil [ossa] [noinline] @bar : $@convention(method) (@in_guaranteed Container, @inout Container, Container) -> () { +bb0(%0 : $*Container, %1 : $*Container, %2 : $Container): + %foo1 = function_ref @foo1 : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (@in_guaranteed Container<τ_0_0>) -> Int32 + %count1 = apply %foo1(%0) : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (@in_guaranteed Container<τ_0_0>) -> Int32 + %foo2 = function_ref @foo2 : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (@in_guaranteed Container<τ_0_0>) -> Int32 + %count2 = apply %foo2(%0) : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (@in_guaranteed Container<τ_0_0>) -> Int32 + %foo3 = function_ref @foo3 : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (@inout Container<τ_0_0>, @owned Container<τ_0_0>) -> () + apply %foo3(%1, %2) : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (@inout Container<τ_0_0>, @owned Container<τ_0_0>) -> () + %t = tuple () + return %t +} + From a5c0a0819c1757347d09a0d58ceb7585b0939045 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Fri, 19 Dec 2025 14:27:51 -0800 Subject: [PATCH 5/5] Update findReturnBB to include return_borrow instruction --- include/swift/SIL/SILFunction.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/swift/SIL/SILFunction.h b/include/swift/SIL/SILFunction.h index e1eab346091b1..93b83fd248069 100644 --- a/include/swift/SIL/SILFunction.h +++ b/include/swift/SIL/SILFunction.h @@ -1564,7 +1564,7 @@ class SILFunction return std::find_if(begin(), end(), [](const SILBasicBlock &BB) -> bool { const TermInst *TI = BB.getTerminator(); - return isa(TI); + return isa(TI) || isa(TI); }); } @@ -1574,7 +1574,7 @@ class SILFunction return std::find_if(begin(), end(), [](const SILBasicBlock &BB) -> bool { const TermInst *TI = BB.getTerminator(); - return isa(TI); + return isa(TI) || isa(TI); }); }