From e116df3628114d1c635a944052bd75a85a5ed7fd Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Thu, 9 Oct 2025 07:18:59 -0700 Subject: [PATCH 01/14] Introduce return_borrow instruction --- .../Sources/SIL/Instruction.swift | 12 ++++++++ .../Sources/SIL/Registration.swift | 1 + include/swift/SIL/AddressWalker.h | 1 + include/swift/SIL/SILBuilder.h | 6 ++++ include/swift/SIL/SILCloner.h | 16 ++++++++++ include/swift/SIL/SILInstruction.h | 29 +++++++++++++++++++ include/swift/SIL/SILNodes.def | 2 ++ include/swift/SILOptimizer/Utils/SCCVisitor.h | 1 + lib/IRGen/IRGenSIL.cpp | 3 ++ lib/SIL/IR/OperandOwnership.cpp | 6 ++++ lib/SIL/IR/SILArgument.cpp | 1 + lib/SIL/IR/SILInstructions.cpp | 23 +++++++++++++++ lib/SIL/IR/SILPrinter.cpp | 15 ++++++++++ lib/SIL/Parser/ParseSIL.cpp | 22 ++++++++++++++ lib/SIL/Utils/BasicBlockUtils.cpp | 1 + lib/SIL/Utils/InstructionUtils.cpp | 1 + lib/SIL/Verifier/DebugInfoVerifier.cpp | 1 + lib/SIL/Verifier/SILVerifier.cpp | 6 ++-- lib/SILOptimizer/ARC/ARCSequenceOptUtils.cpp | 1 + lib/SILOptimizer/Analysis/ColdBlockInfo.cpp | 1 + lib/SILOptimizer/Analysis/RegionAnalysis.cpp | 10 +++++++ .../Mandatory/OwnershipModelEliminator.cpp | 9 ++++++ .../Transforms/DeadCodeElimination.cpp | 2 ++ lib/SILOptimizer/Transforms/SimplifyCFG.cpp | 1 + .../UtilityPasses/SerializeSILPass.cpp | 1 + lib/SILOptimizer/Utils/CFGOptUtils.cpp | 1 + lib/SILOptimizer/Utils/SILInliner.cpp | 1 + lib/Serialization/DeserializeSIL.cpp | 12 ++++++++ lib/Serialization/ModuleFormat.h | 2 +- lib/Serialization/SerializeSIL.cpp | 18 ++++++++++++ test/SIL/Parser/borrow.sil | 19 ++++++++++++ test/Serialization/ossa_sil.sil | 18 ++++++++++++ 32 files changed, 239 insertions(+), 4 deletions(-) diff --git a/SwiftCompilerSources/Sources/SIL/Instruction.swift b/SwiftCompilerSources/Sources/SIL/Instruction.swift index b7b527532448f..73208e990016a 100644 --- a/SwiftCompilerSources/Sources/SIL/Instruction.swift +++ b/SwiftCompilerSources/Sources/SIL/Instruction.swift @@ -1857,6 +1857,18 @@ final public class ReturnInst : TermInst, UnaryInstruction { public override var isFunctionExiting: Bool { true } } +final public class ReturnBorrowInst : TermInst { + public var returnValue: Value { operands[0].value } + public var enclosingOperands: OperandArray { + let ops = operands + return ops[1...Elements, Value> { + enclosingOperands.values + } + public override var isFunctionExiting: Bool { true } +} + final public class ThrowInst : TermInst, UnaryInstruction { public var thrownValue: Value { operand.value } public override var isFunctionExiting: Bool { true } diff --git a/SwiftCompilerSources/Sources/SIL/Registration.swift b/SwiftCompilerSources/Sources/SIL/Registration.swift index 868fac0033694..c16cf54d3cd4a 100644 --- a/SwiftCompilerSources/Sources/SIL/Registration.swift +++ b/SwiftCompilerSources/Sources/SIL/Registration.swift @@ -247,6 +247,7 @@ private func registerSILClasses() { register(UnreachableInst.self) register(ReturnInst.self) + register(ReturnBorrowInst.self) register(ThrowInst.self) register(ThrowAddrInst.self) register(YieldInst.self) diff --git a/include/swift/SIL/AddressWalker.h b/include/swift/SIL/AddressWalker.h index 2b680b69c7038..db2b564f96ed6 100644 --- a/include/swift/SIL/AddressWalker.h +++ b/include/swift/SIL/AddressWalker.h @@ -185,6 +185,7 @@ TransitiveAddressWalker::walk(SILValue projectedAddress) { llvm_unreachable("Never takes an address"); // Point uses. case TermKind::ReturnInst: + case TermKind::ReturnBorrowInst: case TermKind::ThrowInst: case TermKind::YieldInst: case TermKind::TryApplyInst: diff --git a/include/swift/SIL/SILBuilder.h b/include/swift/SIL/SILBuilder.h index 2b45e1d852fac..cb44c52ab31ca 100644 --- a/include/swift/SIL/SILBuilder.h +++ b/include/swift/SIL/SILBuilder.h @@ -2658,6 +2658,12 @@ class SILBuilder { getFunction(), getSILDebugLocation(Loc), ReturnValue)); } + ReturnBorrowInst *createReturnBorrow(SILLocation Loc, SILValue returnValue, + ArrayRef enclosingValues) { + return insertTerminator(ReturnBorrowInst::create( + getSILDebugLocation(Loc), returnValue, enclosingValues, getModule())); + } + ThrowInst *createThrow(SILLocation Loc, SILValue errorValue) { return insertTerminator( new (getModule()) ThrowInst(getSILDebugLocation(Loc), errorValue)); diff --git a/include/swift/SIL/SILCloner.h b/include/swift/SIL/SILCloner.h index 0ac7bcea8a3f8..1a91ce013802f 100644 --- a/include/swift/SIL/SILCloner.h +++ b/include/swift/SIL/SILCloner.h @@ -3417,6 +3417,22 @@ SILCloner::visitReturnInst(ReturnInst *Inst) { getOpValue(Inst->getOperand()))); } +template +void SILCloner::visitReturnBorrowInst(ReturnBorrowInst *rbi) { + getBuilder().setCurrentDebugScope(getOpScope(rbi->getDebugScope())); + if (!getBuilder().hasOwnership()) { + return recordClonedInstruction( + rbi, getBuilder().createReturn(getOpLocation(rbi->getLoc()), + getOpValue(rbi->getReturnValue()))); + } + + auto enclosingValues = getOpValueArray<8>(rbi->getEnclosingValues()); + recordClonedInstruction( + rbi, getBuilder().createReturnBorrow(getOpLocation(rbi->getLoc()), + getOpValue(rbi->getReturnValue()), + enclosingValues)); +} + template void SILCloner::visitThrowInst(ThrowInst *Inst) { diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 29111df02451d..557ba90b1d0b3 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -10123,6 +10123,7 @@ class TermInst : public NonValueInstruction { case TermKind::UnwindInst: case TermKind::UnreachableInst: case TermKind::ReturnInst: + case TermKind::ReturnBorrowInst: case TermKind::ThrowInst: case TermKind::ThrowAddrInst: case TermKind::YieldInst: @@ -10252,6 +10253,34 @@ class ReturnInst } }; +class ReturnBorrowInst final + : public InstructionBaseWithTrailingOperands< + SILInstructionKind::ReturnBorrowInst, ReturnBorrowInst, TermInst> { + friend SILBuilder; + + ReturnBorrowInst(SILDebugLocation DebugLoc, ArrayRef operands); + + static ReturnBorrowInst *create(SILDebugLocation DebugLoc, SILValue value, + ArrayRef enclosingValues, + SILModule &M); + +public: + SILValue getReturnValue() const { return getAllOperands()[0].get(); } + + ArrayRef getEnclosingValueOperands() const { + return getAllOperands().drop_front(); + } + + OperandValueArrayRef getEnclosingValues() const { + return OperandValueArrayRef(getEnclosingValueOperands()); + } + + SuccessorListTy getSuccessors() { + // No Successors. + return SuccessorListTy(); + } +}; + /// ThrowInst - Throw a typed error, returning it via the direct error result. class ThrowInst : public UnaryInstructionBase diff --git a/include/swift/SIL/SILNodes.def b/include/swift/SIL/SILNodes.def index 23c4279c00cd9..af5d70a32a420 100644 --- a/include/swift/SIL/SILNodes.def +++ b/include/swift/SIL/SILNodes.def @@ -731,6 +731,8 @@ ABSTRACT_INST(TermInst, SILInstruction) TermInst, None, DoesNotRelease) TERMINATOR(ReturnInst, return, TermInst, None, DoesNotRelease) + TERMINATOR(ReturnBorrowInst, return_borrow, + TermInst, None, DoesNotRelease) TERMINATOR(ThrowInst, throw, TermInst, None, DoesNotRelease) TERMINATOR(ThrowAddrInst, throw_addr, diff --git a/include/swift/SILOptimizer/Utils/SCCVisitor.h b/include/swift/SILOptimizer/Utils/SCCVisitor.h index 0d8c03c584e43..143a8696a6865 100644 --- a/include/swift/SILOptimizer/Utils/SCCVisitor.h +++ b/include/swift/SILOptimizer/Utils/SCCVisitor.h @@ -130,6 +130,7 @@ class SCCVisitor { case TermKind::UnreachableInst: case TermKind::ReturnInst: + case TermKind::ReturnBorrowInst: case TermKind::SwitchValueInst: case TermKind::ThrowInst: case TermKind::ThrowAddrInst: diff --git a/lib/IRGen/IRGenSIL.cpp b/lib/IRGen/IRGenSIL.cpp index 2dff0464d5bf8..a724fe5238621 100644 --- a/lib/IRGen/IRGenSIL.cpp +++ b/lib/IRGen/IRGenSIL.cpp @@ -1504,6 +1504,9 @@ class IRGenSILFunction : void visitBranchInst(BranchInst *i); void visitCondBranchInst(CondBranchInst *i); void visitReturnInst(ReturnInst *i); + void visitReturnBorrowInst(ReturnBorrowInst *i) { + llvm_unreachable("unimplemented"); + } void visitThrowInst(ThrowInst *i); void visitThrowAddrInst(ThrowAddrInst *i); void visitUnwindInst(UnwindInst *i); diff --git a/lib/SIL/IR/OperandOwnership.cpp b/lib/SIL/IR/OperandOwnership.cpp index 39f6d163720e6..cf91fd77fa975 100644 --- a/lib/SIL/IR/OperandOwnership.cpp +++ b/lib/SIL/IR/OperandOwnership.cpp @@ -636,6 +636,12 @@ OperandOwnership OperandOwnershipClassifier::visitReturnInst(ReturnInst *i) { llvm_unreachable("covered switch"); } +OperandOwnership +OperandOwnershipClassifier::visitReturnBorrowInst(ReturnBorrowInst *rbi) { + return getOperandIndex() == 0 ? OperandOwnership::GuaranteedForwarding + : OperandOwnership::EndBorrow; +} + OperandOwnership OperandOwnershipClassifier::visitAssignInst(AssignInst *i) { if (getValue() != i->getSrc()) { return OperandOwnership::TrivialUse; diff --git a/lib/SIL/IR/SILArgument.cpp b/lib/SIL/IR/SILArgument.cpp index 3cbfc18e350bb..4e0bd8496ce16 100644 --- a/lib/SIL/IR/SILArgument.cpp +++ b/lib/SIL/IR/SILArgument.cpp @@ -315,6 +315,7 @@ getSingleTerminatorOperandForPred(const SILBasicBlock *parentBlock, switch (predTermInst->getTermKind()) { case TermKind::UnreachableInst: case TermKind::ReturnInst: + case TermKind::ReturnBorrowInst: case TermKind::ThrowInst: case TermKind::ThrowAddrInst: case TermKind::UnwindInst: diff --git a/lib/SIL/IR/SILInstructions.cpp b/lib/SIL/IR/SILInstructions.cpp index e88d48ee68277..3c959a16d6d80 100644 --- a/lib/SIL/IR/SILInstructions.cpp +++ b/lib/SIL/IR/SILInstructions.cpp @@ -1814,6 +1814,7 @@ bool TermInst::isFunctionExiting() const { case TermKind::YieldInst: return false; case TermKind::ReturnInst: + case TermKind::ReturnBorrowInst: case TermKind::ThrowInst: case TermKind::ThrowAddrInst: case TermKind::UnwindInst: @@ -1835,6 +1836,7 @@ bool TermInst::isProgramTerminating() const { case TermKind::CheckedCastBranchInst: case TermKind::CheckedCastAddrBranchInst: case TermKind::ReturnInst: + case TermKind::ReturnBorrowInst: case TermKind::ThrowInst: case TermKind::ThrowAddrInst: case TermKind::UnwindInst: @@ -1861,6 +1863,7 @@ const Operand *TermInst::forwardedOperand() const { case TermKind::UnwindInst: case TermKind::UnreachableInst: case TermKind::ReturnInst: + case TermKind::ReturnBorrowInst: case TermKind::ThrowInst: case TermKind::ThrowAddrInst: case TermKind::YieldInst: @@ -3428,6 +3431,26 @@ ReturnInst::ReturnInst(SILFunction &func, SILDebugLocation debugLoc, "result info?!"); } +ReturnBorrowInst *ReturnBorrowInst::create(SILDebugLocation DebugLoc, + SILValue returnValue, + ArrayRef enclosingValues, + SILModule &M) { + auto Size = totalSizeToAlloc(enclosingValues.size() + 1); + auto Buffer = M.allocateInst(Size, alignof(ReturnBorrowInst)); + SmallVector operands; + operands.push_back(returnValue); + for (SILValue ev : enclosingValues) { + operands.push_back(ev); + } + return ::new (Buffer) ReturnBorrowInst(DebugLoc, operands); +} + +ReturnBorrowInst::ReturnBorrowInst(SILDebugLocation DebugLoc, + ArrayRef operands) + : InstructionBaseWithTrailingOperands(operands, DebugLoc) { + assert(operands[0]->getOwnershipKind() == OwnershipKind::Guaranteed); +} + // This may be called in an invalid SIL state. SILCombine creates new // terminators in non-terminator position and defers deleting the original // terminator until after all modification. diff --git a/lib/SIL/IR/SILPrinter.cpp b/lib/SIL/IR/SILPrinter.cpp index d286e37a38e7c..102136db248bc 100644 --- a/lib/SIL/IR/SILPrinter.cpp +++ b/lib/SIL/IR/SILPrinter.cpp @@ -2907,6 +2907,21 @@ class SILPrinter : public SILInstructionVisitor { *this << getIDAndType(RI->getOperand()); } + void visitReturnBorrowInst(ReturnBorrowInst *rbi) { + *this << getIDAndType(rbi->getReturnValue()); + + *this << " from_scopes ("; + bool first = true; + for (SILValue ev : rbi->getEnclosingValues()) { + if (!first) { + *this << ", "; + } + first = false; + *this << getIDAndType(ev); + } + *this << ")"; + } + void visitSpecifyTestInst(SpecifyTestInst *TSI) { *this << QuotedString(TSI->getArgumentsSpecification()); } diff --git a/lib/SIL/Parser/ParseSIL.cpp b/lib/SIL/Parser/ParseSIL.cpp index 7a542149e69ba..cdfe2264db323 100644 --- a/lib/SIL/Parser/ParseSIL.cpp +++ b/lib/SIL/Parser/ParseSIL.cpp @@ -5449,6 +5449,28 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B, ResultVal = B.createReturn(InstLoc, Val); break; } + case SILInstructionKind::ReturnBorrowInst: { + SILValue returnValue; + if (parseTypedValueRef(returnValue, B)) + return true; + + if (parseVerbatim("from_scopes") || + P.parseToken(tok::l_paren, diag::expected_tok_in_sil_instr, "(")) + return true; + + if (P.Tok.isNot(tok::r_paren)) { + do { + if (parseTypedValueRef(Val, B)) + return true; + OpList.push_back(Val); + } while (P.consumeIf(tok::comma)); + } + if (P.parseToken(tok::r_paren, diag::expected_tok_in_sil_instr, ")")) + return true; + + ResultVal = B.createReturnBorrow(InstLoc, returnValue, OpList); + break; + } case SILInstructionKind::ThrowInst: { if (parseTypedValueRef(Val, B) || parseSILDebugLocation(InstLoc, B)) return true; diff --git a/lib/SIL/Utils/BasicBlockUtils.cpp b/lib/SIL/Utils/BasicBlockUtils.cpp index 2154252012698..e234b3c8550a7 100644 --- a/lib/SIL/Utils/BasicBlockUtils.cpp +++ b/lib/SIL/Utils/BasicBlockUtils.cpp @@ -235,6 +235,7 @@ void swift::getEdgeArgs(TermInst *T, unsigned edgeIdx, SILBasicBlock *newEdgeBB, return; case SILInstructionKind::ReturnInst: + case SILInstructionKind::ReturnBorrowInst: case SILInstructionKind::ThrowInst: case SILInstructionKind::ThrowAddrInst: case SILInstructionKind::UnwindInst: diff --git a/lib/SIL/Utils/InstructionUtils.cpp b/lib/SIL/Utils/InstructionUtils.cpp index 9aea7ad4d2d32..14999aa3e132f 100644 --- a/lib/SIL/Utils/InstructionUtils.cpp +++ b/lib/SIL/Utils/InstructionUtils.cpp @@ -592,6 +592,7 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType) case SILInstructionKind::ProjectBlockStorageInst: case SILInstructionKind::UnreachableInst: case SILInstructionKind::ReturnInst: + case SILInstructionKind::ReturnBorrowInst: case SILInstructionKind::ThrowInst: case SILInstructionKind::ThrowAddrInst: case SILInstructionKind::YieldInst: diff --git a/lib/SIL/Verifier/DebugInfoVerifier.cpp b/lib/SIL/Verifier/DebugInfoVerifier.cpp index 146e4e93fbb72..0564403db6a1c 100644 --- a/lib/SIL/Verifier/DebugInfoVerifier.cpp +++ b/lib/SIL/Verifier/DebugInfoVerifier.cpp @@ -48,6 +48,7 @@ void SILInstruction::verifyDebugInfo() const { require( instKind == SILInstructionKind::BranchInst || instKind == SILInstructionKind::ReturnInst || + instKind == SILInstructionKind::ReturnBorrowInst || instKind == SILInstructionKind::UnreachableInst, "return locations are only allowed on branch and return instructions"); diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index 10bfceda86076..90f1291626dce 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -6752,9 +6752,9 @@ class SILVerifier : public SILVerifierBase { bool FoundThrowBlock = false; bool FoundUnwindBlock = false; for (auto &BB : *F) { - if (isa(BB.getTerminator())) { - require(!FoundReturnBlock, - "more than one return block in function"); + if (isa(BB.getTerminator()) || + isa(BB.getTerminator())) { + require(!FoundReturnBlock, "more than one return block in function"); FoundReturnBlock = true; } else if (isa(BB.getTerminator()) || isa(BB.getTerminator())) { diff --git a/lib/SILOptimizer/ARC/ARCSequenceOptUtils.cpp b/lib/SILOptimizer/ARC/ARCSequenceOptUtils.cpp index 064a915c5b72b..4915dbb163820 100644 --- a/lib/SILOptimizer/ARC/ARCSequenceOptUtils.cpp +++ b/lib/SILOptimizer/ARC/ARCSequenceOptUtils.cpp @@ -30,6 +30,7 @@ bool isARCSignificantTerminator(TermInst *TI) { case TermKind::ThrowInst: case TermKind::ThrowAddrInst: case TermKind::ReturnInst: + case TermKind::ReturnBorrowInst: case TermKind::UnwindInst: case TermKind::YieldInst: case TermKind::AwaitAsyncContinuationInst: diff --git a/lib/SILOptimizer/Analysis/ColdBlockInfo.cpp b/lib/SILOptimizer/Analysis/ColdBlockInfo.cpp index 9fde5f1ea0431..d92e4f07ac9d1 100644 --- a/lib/SILOptimizer/Analysis/ColdBlockInfo.cpp +++ b/lib/SILOptimizer/Analysis/ColdBlockInfo.cpp @@ -102,6 +102,7 @@ static bool isColdTerminator(const TermInst *term) { case TermKind::TryApplyInst: case TermKind::YieldInst: case TermKind::ReturnInst: + case TermKind::ReturnBorrowInst: return false; case TermKind::ThrowInst: case TermKind::ThrowAddrInst: diff --git a/lib/SILOptimizer/Analysis/RegionAnalysis.cpp b/lib/SILOptimizer/Analysis/RegionAnalysis.cpp index da7f3c7053e97..b48e0eca443c9 100644 --- a/lib/SILOptimizer/Analysis/RegionAnalysis.cpp +++ b/lib/SILOptimizer/Analysis/RegionAnalysis.cpp @@ -378,6 +378,7 @@ struct TermArgSources { switch (cast(inst)->getTermKind()) { case TermKind::UnreachableInst: case TermKind::ReturnInst: + case TermKind::ReturnBorrowInst: case TermKind::ThrowInst: case TermKind::ThrowAddrInst: case TermKind::YieldInst: @@ -3892,6 +3893,15 @@ TranslationSemantics PartitionOpTranslator::visitReturnInst(ReturnInst *ri) { return TranslationSemantics::Require; } +TranslationSemantics +PartitionOpTranslator::visitReturnBorrowInst(ReturnBorrowInst *ri) { + addEndOfFunctionChecksForInOutSendingParameters(ri); + if (ri->getFunction()->getLoweredFunctionType()->hasSendingResult()) { + return TranslationSemantics::SendingNoResult; + } + return TranslationSemantics::Require; +} + TranslationSemantics PartitionOpTranslator::visitRefToBridgeObjectInst(RefToBridgeObjectInst *r) { translateSILLookThrough( diff --git a/lib/SILOptimizer/Mandatory/OwnershipModelEliminator.cpp b/lib/SILOptimizer/Mandatory/OwnershipModelEliminator.cpp index 7174e21a0203e..62927383e9bd9 100644 --- a/lib/SILOptimizer/Mandatory/OwnershipModelEliminator.cpp +++ b/lib/SILOptimizer/Mandatory/OwnershipModelEliminator.cpp @@ -192,6 +192,15 @@ struct OwnershipModelEliminatorVisitor eraseInstruction(eli); return true; } + + bool visitReturnBorrowInst(ReturnBorrowInst *rbi) { + return withBuilder(rbi, [&](SILBuilder &b, SILLocation loc) { + b.createReturn(loc, rbi->getReturnValue()); + eraseInstruction(rbi); + return true; + }); + } + bool visitUncheckedOwnershipConversionInst( UncheckedOwnershipConversionInst *uoci) { eraseInstructionAndRAUW(uoci, uoci->getOperand()); diff --git a/lib/SILOptimizer/Transforms/DeadCodeElimination.cpp b/lib/SILOptimizer/Transforms/DeadCodeElimination.cpp index f79aab4be0a02..9b880b7986878 100644 --- a/lib/SILOptimizer/Transforms/DeadCodeElimination.cpp +++ b/lib/SILOptimizer/Transforms/DeadCodeElimination.cpp @@ -465,6 +465,7 @@ void DCE::markTerminatorArgsLive(SILBasicBlock *Pred, switch (Term->getTermKind()) { case TermKind::ReturnInst: + case TermKind::ReturnBorrowInst: case TermKind::ThrowInst: case TermKind::ThrowAddrInst: case TermKind::UnwindInst: @@ -576,6 +577,7 @@ void DCE::propagateLiveness(SILInstruction *I) { return; case TermKind::ReturnInst: + case TermKind::ReturnBorrowInst: case TermKind::ThrowInst: case TermKind::CondBranchInst: case TermKind::SwitchEnumInst: diff --git a/lib/SILOptimizer/Transforms/SimplifyCFG.cpp b/lib/SILOptimizer/Transforms/SimplifyCFG.cpp index cf3cc99864b55..98b5c0d87849b 100644 --- a/lib/SILOptimizer/Transforms/SimplifyCFG.cpp +++ b/lib/SILOptimizer/Transforms/SimplifyCFG.cpp @@ -2848,6 +2848,7 @@ bool SimplifyCFG::simplifyBlocks() { case TermKind::ThrowAddrInst: case TermKind::DynamicMethodBranchInst: case TermKind::ReturnInst: + case TermKind::ReturnBorrowInst: case TermKind::UnwindInst: case TermKind::YieldInst: break; diff --git a/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp b/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp index 3cee22cc13a20..f5b5f868313a2 100644 --- a/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp +++ b/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp @@ -241,6 +241,7 @@ static bool hasOpaqueArchetype(TypeExpansionContext context, case SILInstructionKind::KeyPathInst: case SILInstructionKind::UnreachableInst: case SILInstructionKind::ReturnInst: + case SILInstructionKind::ReturnBorrowInst: case SILInstructionKind::ThrowInst: case SILInstructionKind::ThrowAddrInst: case SILInstructionKind::YieldInst: diff --git a/lib/SILOptimizer/Utils/CFGOptUtils.cpp b/lib/SILOptimizer/Utils/CFGOptUtils.cpp index 1b4d7599f0c0c..54563ae998c0b 100644 --- a/lib/SILOptimizer/Utils/CFGOptUtils.cpp +++ b/lib/SILOptimizer/Utils/CFGOptUtils.cpp @@ -519,6 +519,7 @@ bool isSafeNonExitTerminator(TermInst *ti) { return true; case TermKind::UnreachableInst: case TermKind::ReturnInst: + case TermKind::ReturnBorrowInst: case TermKind::ThrowInst: case TermKind::ThrowAddrInst: case TermKind::UnwindInst: diff --git a/lib/SILOptimizer/Utils/SILInliner.cpp b/lib/SILOptimizer/Utils/SILInliner.cpp index d0e52f495672e..d4149bf65811a 100644 --- a/lib/SILOptimizer/Utils/SILInliner.cpp +++ b/lib/SILOptimizer/Utils/SILInliner.cpp @@ -1057,6 +1057,7 @@ InlineCost swift::instructionInlineCost(SILInstruction &I) { // Return and unreachable are free. case SILInstructionKind::UnreachableInst: case SILInstructionKind::ReturnInst: + case SILInstructionKind::ReturnBorrowInst: case SILInstructionKind::ThrowInst: case SILInstructionKind::ThrowAddrInst: case SILInstructionKind::UnwindInst: diff --git a/lib/Serialization/DeserializeSIL.cpp b/lib/Serialization/DeserializeSIL.cpp index 214f2b6aa4c59..4b18cc74001fd 100644 --- a/lib/Serialization/DeserializeSIL.cpp +++ b/lib/Serialization/DeserializeSIL.cpp @@ -3116,6 +3116,18 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn, } break; } + case SILInstructionKind::ReturnBorrowInst: { + SmallVector OpList; + for (unsigned I = 0, E = ListOfValues.size(); I < E; I += 3) { + auto EltTy = MF->getType(ListOfValues[I]); + OpList.push_back(getLocalValue( + Builder.maybeGetFunction(), ListOfValues[I + 2], + getSILType(EltTy, (SILValueCategory)ListOfValues[I + 1], Fn))); + } + ResultInst = Builder.createReturnBorrow(Loc, OpList[0], + ArrayRef(OpList).drop_front()); + break; + } case SILInstructionKind::TupleElementAddrInst: case SILInstructionKind::TupleExtractInst: { // Use OneTypeOneOperand layout where the field number is stored in TypeID. diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 8a431abdcc62c..a7a57755a1dda 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 969; // Module import paths +const uint16_t SWIFTMODULE_VERSION_MINOR = 970; // return_borrow /// A standard hash seed used for all string hashes in a serialized module. /// diff --git a/lib/Serialization/SerializeSIL.cpp b/lib/Serialization/SerializeSIL.cpp index 894a679dca044..78a8d11408362 100644 --- a/lib/Serialization/SerializeSIL.cpp +++ b/lib/Serialization/SerializeSIL.cpp @@ -2588,6 +2588,24 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) { (unsigned)svi->getType().getCategory(), ListOfValues); break; } + + case SILInstructionKind::ReturnBorrowInst: { + // Format: a type followed by a list of typed values. A typed value is + // expressed by 4 IDs: TypeID, TypeCategory, ValueID, ValueResultNumber. + const auto *rbi = cast(&SI); + SmallVector ListOfValues; + for (auto Elt : rbi->getOperandValues()) { + ListOfValues.push_back(S.addTypeRef(Elt->getType().getRawASTType())); + ListOfValues.push_back((unsigned)Elt->getType().getCategory()); + ListOfValues.push_back(addValueRef(Elt)); + } + + SILValuesLayout::emitRecord(Out, ScratchRecord, + SILAbbrCodes[SILValuesLayout::Code], + (unsigned)SI.getKind(), ListOfValues); + break; + } + case SILInstructionKind::TupleElementAddrInst: case SILInstructionKind::TupleExtractInst: { SILValue operand; diff --git a/test/SIL/Parser/borrow.sil b/test/SIL/Parser/borrow.sil index a3488e533deff..12998f39e91e1 100644 --- a/test/SIL/Parser/borrow.sil +++ b/test/SIL/Parser/borrow.sil @@ -53,6 +53,24 @@ sil [ossa] @foo : $@convention(thin) () -> () { return %res : $() } +struct Wrapper { + var _k: C +} + +// CHECK-LABEL: sil [ossa] @return_borrow_test : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed C { +// CHECK: bb0(%0 : $*Wrapper): +// CHECK: %1 = struct_element_addr %0 : $*Wrapper, #Wrapper._k +// CHECK: %2 = load_borrow %1 : $*C +// CHECK: return_borrow %2 : $C from_scopes (%2 : $C) +// CHECK: } +sil [ossa] @return_borrow_test : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed C { +bb0(%0 : $*Wrapper): + %1 = struct_element_addr %0, #Wrapper._k + %2 = load_borrow %1 + return_borrow %2 from_scopes (%2) +} + + // CHECK-LABEL: sil [ossa] @reborrowTest : $@convention(thin) (@owned C) -> () { // CHECK: bb3([[ARG:%.*]] : @reborrow $C): // CHECK-NEXT: {{%.*}} = borrowed [[ARG]] : $C from (%0 : $C) @@ -91,3 +109,4 @@ bb1(%2 : @guaranteed $FakeOptional): %4 = tuple () return %4 : $() } + diff --git a/test/Serialization/ossa_sil.sil b/test/Serialization/ossa_sil.sil index fd34923cf2764..02617d6e8d5f6 100644 --- a/test/Serialization/ossa_sil.sil +++ b/test/Serialization/ossa_sil.sil @@ -19,6 +19,24 @@ bb0(%0 : @guaranteed $AnyObject): return %2 : $X } +struct Wrapper { + var _k: X +} + +// CHECK-LABEL: sil [serialized] [canonical] [ossa] @return_borrow_test : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed X { +// CHECK: bb0(%0 : $*Wrapper): +// CHECK: %1 = struct_element_addr %0 : $*Wrapper, #Wrapper._k +// CHECK: %2 = load_borrow %1 : $*X +// CHECK: return_borrow %2 : $X from_scopes (%2 : $X) +// CHECK: } + +sil [serialized] [ossa] @return_borrow_test : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed X { +bb0(%0 : $*Wrapper): + %1 = struct_element_addr %0, #Wrapper._k + %2 = load_borrow %1 + return_borrow %2 from_scopes (%2) +} + // CHECK-LABEL: sil [serialized] [canonical] [ossa] @reborrowTest : // CHECK: bb1: // CHECK-NEXT: {{%.*}} = begin_borrow %0 : $X From a150c678962297c4561dec631daeda280613ee20 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Thu, 9 Oct 2025 23:46:43 -0700 Subject: [PATCH 02/14] Remove unchecked_ownership_conversion from borrow accessor silgen --- lib/SILGen/SILGenApply.cpp | 6 ------ lib/SILGen/SILGenFunction.h | 2 -- lib/SILGen/SILGenStmt.cpp | 21 ++------------------- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/lib/SILGen/SILGenApply.cpp b/lib/SILGen/SILGenApply.cpp index 8ffef12f8b3b4..7fb54d1e59b8a 100644 --- a/lib/SILGen/SILGenApply.cpp +++ b/lib/SILGen/SILGenApply.cpp @@ -5479,12 +5479,6 @@ ManagedValue SILGenFunction::applyBorrowMutateAccessor( assert(rawResults.size() == 1); auto rawResult = rawResults[0]; - if (fn.getFunction()->getConventions().hasGuaranteedResult()) { - auto selfArg = args.back().getValue(); - if (isa(selfArg)) { - rawResult = emitUncheckedGuaranteedConversion(rawResult); - } - } if (rawResult->getType().isMoveOnly()) { if (rawResult->getType().isAddress()) { auto result = B.createMarkUnresolvedNonCopyableValueInst( diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index 0643672cd3f9e..4b0e20290b782 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -2276,8 +2276,6 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction emitBorrowOrMutateAccessorResult(SILLocation loc, Expr *ret, SmallVectorImpl &directResults); - SILValue emitUncheckedGuaranteedConversion(SILValue value); - void emitYield(SILLocation loc, MutableArrayRef yieldValues, ArrayRef origTypes, JumpDest unwindDest); diff --git a/lib/SILGen/SILGenStmt.cpp b/lib/SILGen/SILGenStmt.cpp index 7092b9ac91844..455c4a84436e4 100644 --- a/lib/SILGen/SILGenStmt.cpp +++ b/lib/SILGen/SILGenStmt.cpp @@ -708,21 +708,6 @@ static Expr *lookThroughProjections(Expr *expr) { return lookThroughProjections(lookupExpr->getBase()); } -SILValue SILGenFunction::emitUncheckedGuaranteedConversion(SILValue value) { - assert(value->getType().isObject()); - assert(F.getConventions().hasGuaranteedResult()); - auto regularLoc = RegularLocation::getAutoGeneratedLocation(); - // Introduce a pair of unchecked_ownership_conversion instructions to - // avoid ownership errors from returning a load borrowed value. - // TODO: Introduce new SIL semantics to allow returning borrowed - // values from within a local borrow scope. - auto result = B.createUncheckedOwnershipConversion(regularLoc, value, - OwnershipKind::Unowned); - result = B.createUncheckedOwnershipConversion(regularLoc, result, - OwnershipKind::Guaranteed); - return result; -} - bool SILGenFunction::emitBorrowOrMutateAccessorResult( SILLocation loc, Expr *ret, SmallVectorImpl &directResults) { auto *afd = cast(FunctionDC->getAsDecl()); @@ -734,9 +719,7 @@ bool SILGenFunction::emitBorrowOrMutateAccessorResult( assert(guaranteedAddress.getValue()->getType().isAddress()); assert(F.getConventions().hasGuaranteedResult()); auto regularLoc = RegularLocation::getAutoGeneratedLocation(); - auto load = - B.createLoadBorrow(regularLoc, guaranteedAddress).getValue(); - return emitUncheckedGuaranteedConversion(load); + return B.createLoadBorrow(regularLoc, guaranteedAddress).getValue(); }; // If the return expression is a literal, emit as a regular return @@ -853,7 +836,7 @@ bool SILGenFunction::emitBorrowOrMutateAccessorResult( // unnecessary copy_value + mark_unresolved_non_copyable_value // instructions. if (selfType.isMoveOnly()) { - result = lookThroughMoveOnlyCheckerPattern(result); + result = lookThroughMoveOnlyCheckerPattern(result); } // If the SIL convention is @guaranteed and the generated result is an // address, emit a load_borrow. From f179e5d60308405708cc66b17a97b28c920a9e37 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Wed, 15 Oct 2025 13:16:32 -0700 Subject: [PATCH 03/14] Verify load_borrow's unchecked bit is off in canonical SIL --- lib/SIL/Verifier/SILVerifier.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index 90f1291626dce..7641badf362af 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -2728,6 +2728,8 @@ class SILVerifier : public SILVerifierBase { requireSameType(LBI->getOperand()->getType().getObjectType(), LBI->getType(), "Load operand type and result type mismatch"); + require(F.getModule().getStage() == SILStage::Raw || !LBI->isUnchecked(), + "load_borrow's unchecked bit is on"); } void checkBeginBorrowInst(BeginBorrowInst *bbi) { From a3aed17d63f33477949c0945754edc97d65470a0 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Fri, 10 Oct 2025 01:10:04 -0700 Subject: [PATCH 04/14] Disable inlining borrow accessors with return_borrow instruction --- lib/SILOptimizer/Utils/SILInliner.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/SILOptimizer/Utils/SILInliner.cpp b/lib/SILOptimizer/Utils/SILInliner.cpp index d4149bf65811a..d81ecee893e38 100644 --- a/lib/SILOptimizer/Utils/SILInliner.cpp +++ b/lib/SILOptimizer/Utils/SILInliner.cpp @@ -77,6 +77,15 @@ bool SILInliner::canInlineApplySite(FullApplySite apply) { if (auto BA = dyn_cast(apply)) return canInlineBeginApply(BA); + if (apply.hasGuaranteedResult()) { + if (auto *callee = apply.getReferencedFunctionOrNull()) { + auto returnBB = callee->findReturnBB(); + if (returnBB != callee->end() && + isa(returnBB->getTerminator())) { + return false; + } + } + } return true; } From 1dc5c9611c3816df35b35e5298e43551d4f7ae9a Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Thu, 16 Oct 2025 12:18:27 -0700 Subject: [PATCH 05/14] Intoduce unchecked_ownership instruction in raw SIL This instruction can be used to disable ownership verification on it's result and will be allowed only in raw SIL. Sometimes SILGen can produce invalid ownership SSA, that cannot be resolved until mandatory passes run. We have a few ways to piecewise disable verification. With unchecked_ownership instruction we can provide a uniform way to disable ownership verification for a value. --- SwiftCompilerSources/Sources/SIL/Instruction.swift | 2 ++ SwiftCompilerSources/Sources/SIL/Registration.swift | 1 + include/swift/SIL/SILBuilder.h | 6 ++++++ include/swift/SIL/SILCloner.h | 13 +++++++++++++ include/swift/SIL/SILInstruction.h | 12 ++++++++++++ include/swift/SIL/SILNodes.def | 5 ++++- lib/IRGen/IRGenSIL.cpp | 4 +++- lib/SIL/IR/OperandOwnership.cpp | 1 + lib/SIL/IR/SILPrinter.cpp | 4 ++++ lib/SIL/IR/ValueOwnership.cpp | 1 + lib/SIL/Parser/ParseSIL.cpp | 9 +++++++++ lib/SIL/Utils/InstructionUtils.cpp | 1 + lib/SILOptimizer/Analysis/RegionAnalysis.cpp | 1 + lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp | 1 + lib/SILOptimizer/Utils/SILInliner.cpp | 1 + lib/Serialization/DeserializeSIL.cpp | 3 +++ lib/Serialization/SerializeSIL.cpp | 3 +++ 17 files changed, 66 insertions(+), 2 deletions(-) diff --git a/SwiftCompilerSources/Sources/SIL/Instruction.swift b/SwiftCompilerSources/Sources/SIL/Instruction.swift index 73208e990016a..dddd61e6e3905 100644 --- a/SwiftCompilerSources/Sources/SIL/Instruction.swift +++ b/SwiftCompilerSources/Sources/SIL/Instruction.swift @@ -1447,6 +1447,8 @@ final public class CopyableToMoveOnlyWrapperAddrInst final public class MoveOnlyWrapperToCopyableAddrInst : SingleValueInstruction, UnaryInstruction {} +final public class UncheckedOwnershipInst: SingleValueInstruction, UnaryInstruction {} + final public class ObjectInst : SingleValueInstruction { public var baseOperands: OperandArray { operands[0..getOwnershipKind())); + } + UnconditionalCheckedCastInst * createUnconditionalCheckedCast(SILLocation Loc, CheckedCastInstOptions options, diff --git a/include/swift/SIL/SILCloner.h b/include/swift/SIL/SILCloner.h index 1a91ce013802f..621ac4cdefff8 100644 --- a/include/swift/SIL/SILCloner.h +++ b/include/swift/SIL/SILCloner.h @@ -2247,6 +2247,19 @@ void SILCloner::visitCopyableToMoveOnlyWrapperValueInst( recordClonedInstruction(inst, cvt); } +template +void SILCloner::visitUncheckedOwnershipInst( + UncheckedOwnershipInst *uoi) { + getBuilder().setCurrentDebugScope(getOpScope(uoi->getDebugScope())); + if (!getBuilder().hasOwnership()) { + return recordFoldedValue(uoi, getOpValue(uoi->getOperand())); + } + + recordClonedInstruction( + uoi, getBuilder().createUncheckedOwnership( + getOpLocation(uoi->getLoc()), getOpValue(uoi->getOperand()))); +} + template void SILCloner::visitReleaseValueInst(ReleaseValueInst *Inst) { getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope())); diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 557ba90b1d0b3..c2ec1085671a3 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -9553,6 +9553,17 @@ class MoveOnlyWrapperToCopyableAddrInst } }; +class UncheckedOwnershipInst final + : public UnaryInstructionBase { + friend class SILBuilder; + + UncheckedOwnershipInst(SILDebugLocation DebugLoc, SILValue operand, + ValueOwnershipKind forwardingOwnershipKind) + : UnaryInstructionBase(DebugLoc, operand, operand->getType(), + forwardingOwnershipKind) {} +}; + /// Given an object reference, return true iff it is non-nil and refers /// to a native swift object with strong reference count of 1. class IsUniqueInst @@ -11696,6 +11707,7 @@ OwnershipForwardingSingleValueInstruction::classof(SILInstructionKind kind) { case SILInstructionKind::DropDeinitInst: case SILInstructionKind::BorrowedFromInst: case SILInstructionKind::ImplicitActorToOpaqueIsolationCastInst: + case SILInstructionKind::UncheckedOwnershipInst: return true; default: return false; diff --git a/include/swift/SIL/SILNodes.def b/include/swift/SIL/SILNodes.def index af5d70a32a420..748c59565457c 100644 --- a/include/swift/SIL/SILNodes.def +++ b/include/swift/SIL/SILNodes.def @@ -469,7 +469,8 @@ ABSTRACT_VALUE_AND_INST(SingleValueInstruction, ValueBase, SILInstruction) SingleValueInstruction, MayHaveSideEffects, DoesNotRelease) #include "swift/AST/ReferenceStorage.def" SINGLE_VALUE_INST(UncheckedOwnershipConversionInst, unchecked_ownership_conversion, - SingleValueInstruction, None, MayRelease) + SingleValueInstruction, None, DoesNotRelease) + // A move_value is an OSSA only instruction. Its result does not have any side // effects relative to other OSSA values like copy_value. SINGLE_VALUE_INST(MoveValueInst, move_value, SingleValueInstruction, None, @@ -520,6 +521,8 @@ ABSTRACT_VALUE_AND_INST(SingleValueInstruction, ValueBase, SILInstruction) SINGLE_VALUE_INST(CopyableToMoveOnlyWrapperAddrInst, copyable_to_moveonlywrapper_addr, SingleValueInstruction, None, DoesNotRelease) + SINGLE_VALUE_INST(UncheckedOwnershipInst, unchecked_ownership, + SingleValueInstruction, None, DoesNotRelease) // IsUnique does not actually write to memory but should be modeled // as such. Its operand is a pointer to an object reference. The diff --git a/lib/IRGen/IRGenSIL.cpp b/lib/IRGen/IRGenSIL.cpp index a724fe5238621..0f8e100984a13 100644 --- a/lib/IRGen/IRGenSIL.cpp +++ b/lib/IRGen/IRGenSIL.cpp @@ -1302,7 +1302,9 @@ class IRGenSILFunction : auto e = getLoweredExplosion(i->getOperand()); setLoweredExplosion(i, e); } - + void visitUncheckedOwnershipInst(UncheckedOwnershipInst *i) { + llvm_unreachable("unimplemented"); + } void visitMergeIsolationRegionInst(MergeIsolationRegionInst *i) { llvm_unreachable("Valid only when ownership is enabled"); } diff --git a/lib/SIL/IR/OperandOwnership.cpp b/lib/SIL/IR/OperandOwnership.cpp index cf91fd77fa975..24ddd68b21a1d 100644 --- a/lib/SIL/IR/OperandOwnership.cpp +++ b/lib/SIL/IR/OperandOwnership.cpp @@ -421,6 +421,7 @@ AGGREGATE_OWNERSHIP(DestructureTuple) AGGREGATE_OWNERSHIP(Enum) AGGREGATE_OWNERSHIP(UncheckedEnumData) AGGREGATE_OWNERSHIP(SwitchEnum) +AGGREGATE_OWNERSHIP(UncheckedOwnership) #undef AGGREGATE_OWNERSHIP // A begin_borrow is conditionally nested. diff --git a/lib/SIL/IR/SILPrinter.cpp b/lib/SIL/IR/SILPrinter.cpp index 102136db248bc..0d6b1f985b7a3 100644 --- a/lib/SIL/IR/SILPrinter.cpp +++ b/lib/SIL/IR/SILPrinter.cpp @@ -2367,6 +2367,10 @@ class SILPrinter : public SILInstructionVisitor { *this << getIDAndType(I->getOperand()); } + void visitUncheckedOwnershipInst(UncheckedOwnershipInst *I) { + *this << getIDAndType(I->getOperand()); + } + void visitUnownedCopyValueInst(UnownedCopyValueInst *I) { *this << getIDAndType(I->getOperand()); } diff --git a/lib/SIL/IR/ValueOwnership.cpp b/lib/SIL/IR/ValueOwnership.cpp index d6f8f710c84a6..762dd1ae579d4 100644 --- a/lib/SIL/IR/ValueOwnership.cpp +++ b/lib/SIL/IR/ValueOwnership.cpp @@ -323,6 +323,7 @@ FORWARDING_OWNERSHIP_INST(MarkUnresolvedReferenceBinding) FORWARDING_OWNERSHIP_INST(MoveOnlyWrapperToCopyableValue) FORWARDING_OWNERSHIP_INST(CopyableToMoveOnlyWrapperValue) FORWARDING_OWNERSHIP_INST(MoveOnlyWrapperToCopyableBox) +FORWARDING_OWNERSHIP_INST(UncheckedOwnership) #undef FORWARDING_OWNERSHIP_INST ValueOwnershipKind diff --git a/lib/SIL/Parser/ParseSIL.cpp b/lib/SIL/Parser/ParseSIL.cpp index cdfe2264db323..b699b15a333f0 100644 --- a/lib/SIL/Parser/ParseSIL.cpp +++ b/lib/SIL/Parser/ParseSIL.cpp @@ -3852,6 +3852,15 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B, break; } + case SILInstructionKind::UncheckedOwnershipInst: { + if (parseTypedValueRef(Val, B)) + return true; + if (parseSILDebugLocation(InstLoc, B)) + return true; + ResultVal = B.createUncheckedOwnership(InstLoc, Val); + break; + } + case SILInstructionKind::LoadInst: { std::optional Qualifier; SourceLoc AddrLoc; diff --git a/lib/SIL/Utils/InstructionUtils.cpp b/lib/SIL/Utils/InstructionUtils.cpp index 14999aa3e132f..429a1e660381f 100644 --- a/lib/SIL/Utils/InstructionUtils.cpp +++ b/lib/SIL/Utils/InstructionUtils.cpp @@ -642,6 +642,7 @@ RuntimeEffect swift::getRuntimeEffect(SILInstruction *inst, SILType &impactType) case SILInstructionKind::TypeValueInst: case SILInstructionKind::IgnoredUseInst: case SILInstructionKind::ImplicitActorToOpaqueIsolationCastInst: + case SILInstructionKind::UncheckedOwnershipInst: return RuntimeEffect::NoEffect; case SILInstructionKind::LoadInst: { diff --git a/lib/SILOptimizer/Analysis/RegionAnalysis.cpp b/lib/SILOptimizer/Analysis/RegionAnalysis.cpp index b48e0eca443c9..8f432796d32a7 100644 --- a/lib/SILOptimizer/Analysis/RegionAnalysis.cpp +++ b/lib/SILOptimizer/Analysis/RegionAnalysis.cpp @@ -3483,6 +3483,7 @@ CONSTANT_TRANSLATION(MoveOnlyWrapperToCopyableBoxInst, LookThrough) CONSTANT_TRANSLATION(MoveOnlyWrapperToCopyableAddrInst, LookThrough) CONSTANT_TRANSLATION(CopyableToMoveOnlyWrapperAddrInst, LookThrough) CONSTANT_TRANSLATION(MarkUninitializedInst, LookThrough) +CONSTANT_TRANSLATION(UncheckedOwnershipInst, LookThrough) // We identify destructured results with their operand's region. CONSTANT_TRANSLATION(DestructureTupleInst, LookThrough) CONSTANT_TRANSLATION(DestructureStructInst, LookThrough) diff --git a/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp b/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp index f5b5f868313a2..1d215d7fac0cc 100644 --- a/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp +++ b/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp @@ -337,6 +337,7 @@ static bool hasOpaqueArchetype(TypeExpansionContext context, case SILInstructionKind::TypeValueInst: case SILInstructionKind::IgnoredUseInst: case SILInstructionKind::ImplicitActorToOpaqueIsolationCastInst: + case SILInstructionKind::UncheckedOwnershipInst: // Handle by operand and result check. break; diff --git a/lib/SILOptimizer/Utils/SILInliner.cpp b/lib/SILOptimizer/Utils/SILInliner.cpp index d81ecee893e38..75be95ec1e62f 100644 --- a/lib/SILOptimizer/Utils/SILInliner.cpp +++ b/lib/SILOptimizer/Utils/SILInliner.cpp @@ -950,6 +950,7 @@ InlineCost swift::instructionInlineCost(SILInstruction &I) { case SILInstructionKind::MoveOnlyWrapperToCopyableBoxInst: case SILInstructionKind::IgnoredUseInst: case SILInstructionKind::ImplicitActorToOpaqueIsolationCastInst: + case SILInstructionKind::UncheckedOwnershipInst: return InlineCost::Free; // Typed GEPs are free. diff --git a/lib/Serialization/DeserializeSIL.cpp b/lib/Serialization/DeserializeSIL.cpp index 4b18cc74001fd..db4874b8d16a8 100644 --- a/lib/Serialization/DeserializeSIL.cpp +++ b/lib/Serialization/DeserializeSIL.cpp @@ -2917,6 +2917,9 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn, CKind, Strict); break; } + case SILInstructionKind::UncheckedOwnershipInst: { + llvm_unreachable("Invalid unchecked_ownership in deserialization"); + } case SILInstructionKind::StoreInst: { auto Ty = MF->getType(TyID); SILType addrType = getSILType(Ty, (SILValueCategory)TyCategory, Fn); diff --git a/lib/Serialization/SerializeSIL.cpp b/lib/Serialization/SerializeSIL.cpp index 78a8d11408362..f728cfb934481 100644 --- a/lib/Serialization/SerializeSIL.cpp +++ b/lib/Serialization/SerializeSIL.cpp @@ -1806,6 +1806,9 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) { writeOneOperandExtraAttributeLayout(SI.getKind(), Attr, SI.getOperand(0)); break; } + case SILInstructionKind::UncheckedOwnershipInst: { + llvm_unreachable("Invalid unchecked_ownership during serialzation"); + } case SILInstructionKind::YieldInst: { auto YI = cast(&SI); SmallVector args; From 33ebf800dedbc3b38af0b7d60f069e806364420a Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Thu, 16 Oct 2025 14:12:07 -0700 Subject: [PATCH 06/14] Update verification for unchecked_ownership --- lib/SIL/Verifier/SILOwnershipVerifier.cpp | 4 ++++ lib/SIL/Verifier/SILVerifier.cpp | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/lib/SIL/Verifier/SILOwnershipVerifier.cpp b/lib/SIL/Verifier/SILOwnershipVerifier.cpp index 9ada227b9b268..fbb243c6bbf3a 100644 --- a/lib/SIL/Verifier/SILOwnershipVerifier.cpp +++ b/lib/SIL/Verifier/SILOwnershipVerifier.cpp @@ -346,6 +346,10 @@ bool SILValueOwnershipChecker::gatherUsers( Operand *op = uses.pop_back_val(); SILInstruction *user = op->getUser(); + if (isa(user)) { + continue; + } + // If this op is a type dependent operand, skip it. It is not interesting // from an ownership perspective. if (user->isTypeDependentOperand(*op)) diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index 7641badf362af..a76a817ef5d5d 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -6711,6 +6711,11 @@ class SILVerifier : public SILVerifierBase { "Result and operand must have the same type, today."); } + void checkUncheckedOwnershipInst(UncheckedOwnershipInst *uoi) { + require(F.getModule().getStage() == SILStage::Raw, + "unchecked_ownership is valid only in raw SIL"); + } + void checkAllocPackMetadataInst(AllocPackMetadataInst *apmi) { require(apmi->getIntroducer()->mayRequirePackMetadata(*apmi->getFunction()), "Introduces instruction of kind which cannot emit on-stack pack " From bef207521da2c077f82502a4e402b72f017b4b4a Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Thu, 16 Oct 2025 12:17:59 -0700 Subject: [PATCH 07/14] Use unchecked_ownership during SILGen of borrow accessors --- lib/SILGen/SILGenApply.cpp | 7 +++++++ lib/SILGen/SILGenStmt.cpp | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/SILGen/SILGenApply.cpp b/lib/SILGen/SILGenApply.cpp index 7fb54d1e59b8a..91fea1b09949d 100644 --- a/lib/SILGen/SILGenApply.cpp +++ b/lib/SILGen/SILGenApply.cpp @@ -5479,6 +5479,13 @@ ManagedValue SILGenFunction::applyBorrowMutateAccessor( assert(rawResults.size() == 1); auto rawResult = rawResults[0]; + if (fn.getFunction()->getConventions().hasGuaranteedResult()) { + auto selfArg = args.back().getValue(); + if (isa(selfArg)) { + rawResult = B.createUncheckedOwnership(loc, rawResult); + } + } + if (rawResult->getType().isMoveOnly()) { if (rawResult->getType().isAddress()) { auto result = B.createMarkUnresolvedNonCopyableValueInst( diff --git a/lib/SILGen/SILGenStmt.cpp b/lib/SILGen/SILGenStmt.cpp index 455c4a84436e4..095cc11e48bda 100644 --- a/lib/SILGen/SILGenStmt.cpp +++ b/lib/SILGen/SILGenStmt.cpp @@ -719,7 +719,8 @@ bool SILGenFunction::emitBorrowOrMutateAccessorResult( assert(guaranteedAddress.getValue()->getType().isAddress()); assert(F.getConventions().hasGuaranteedResult()); auto regularLoc = RegularLocation::getAutoGeneratedLocation(); - return B.createLoadBorrow(regularLoc, guaranteedAddress).getValue(); + auto load = B.createLoadBorrow(regularLoc, guaranteedAddress).getValue(); + return B.createUncheckedOwnership(regularLoc, load); }; // If the return expression is a literal, emit as a regular return From fc7123368804bd04c01a7da16429d9a1e09c35d9 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Thu, 16 Oct 2025 15:07:23 -0700 Subject: [PATCH 08/14] [NFC] Update tests --- test/SILGen/borrow_accessor.swift | 32 +++++++++------------ test/SILGen/borrow_accessor_container.swift | 10 +++---- test/SILGen/borrow_accessor_evolution.swift | 11 ++++--- 3 files changed, 22 insertions(+), 31 deletions(-) diff --git a/test/SILGen/borrow_accessor.swift b/test/SILGen/borrow_accessor.swift index fa1bcbef98f7f..94cc4ca1bd1c4 100644 --- a/test/SILGen/borrow_accessor.swift +++ b/test/SILGen/borrow_accessor.swift @@ -491,10 +491,9 @@ public struct GenNCWrapper : ~Copyable { // CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref // CHECK: [[REG2:%.*]] = struct_element_addr [[REG0]], #GenWrapper._s // CHECK: [[REG3:%.*]] = load_borrow [[REG2]] -// CHECK: [[REG4:%.*]] = unchecked_ownership_conversion [[REG3]], @guaranteed to @unowned -// CHECK: [[REG5:%.*]] = unchecked_ownership_conversion [[REG4]], @unowned to @guaranteed +// CHECK: [[REG4:%.*]] = unchecked_ownership [[REG3]] // CHECK: end_borrow [[REG3]] -// CHECK: return [[REG5]] +// CHECK: return [[REG4]] // CHECK: } // CHECK: sil hidden [ossa] @$s15borrow_accessor10GenWrapperV1sAA1SVvz : $@convention(method) (@inout GenWrapper) -> @inout S { @@ -509,10 +508,9 @@ public struct GenNCWrapper : ~Copyable { // CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref // CHECK: [[REG2:%.*]] = struct_element_addr [[REG0]], #GenWrapper._k // CHECK: [[REG3:%.*]] = load_borrow [[REG2]] -// CHECK: [[REG4:%.*]] = unchecked_ownership_conversion [[REG3]], @guaranteed to @unowned -// CHECK: [[REG5:%.*]] = unchecked_ownership_conversion [[REG4]], @unowned to @guaranteed +// CHECK: [[REG4:%.*]] = unchecked_ownership [[REG3]] // CHECK: end_borrow [[REG3]] -// CHECK: return [[REG5]] +// CHECK: return [[REG4]] // CHECK: } // CHECK: sil hidden [ossa] @$s15borrow_accessor10GenWrapperV1kAA5KlassCvz : $@convention(method) (@inout GenWrapper) -> @inout Klass { @@ -555,10 +553,9 @@ public struct GenNCWrapper : ~Copyable { // CHECK: [[REG3:%.*]] = load_borrow [[REG2]] // CHECK: [[REG4:%.*]] = function_ref @$s15borrow_accessor1SV1kAA5KlassCvb : $@convention(method) (@guaranteed S) -> @guaranteed Klass // CHECK: [[REG5:%.*]] = apply [[REG4]]([[REG3]]) : $@convention(method) (@guaranteed S) -> @guaranteed Klass -// CHECK: [[REG6:%.*]] = unchecked_ownership_conversion [[REG5]], @guaranteed to @unowned -// CHECK: [[REG7:%.*]] = unchecked_ownership_conversion [[REG6]], @unowned to @guaranteed +// CHECK: [[REG6:%.*]] = unchecked_ownership [[REG5]] // CHECK: end_borrow [[REG3]] -// CHECK: return [[REG7]] +// CHECK: return [[REG6]] // CHECK: } // CHECK: sil hidden [ossa] @$s15borrow_accessor10GenWrapperV9nested_k1AA5KlassCvz : $@convention(method) (@inout GenWrapper) -> @inout Klass { @@ -652,10 +649,9 @@ public struct GenNCWrapper : ~Copyable { // CHECK: [[REG2:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG0]] // CHECK: [[REG3:%.*]] = struct_element_addr [[REG2]], #GenNCWrapper._nc // CHECK: [[REG4:%.*]] = load_borrow [[REG3]] -// CHECK: [[REG5:%.*]] = unchecked_ownership_conversion [[REG4]], @guaranteed to @unowned -// CHECK: [[REG6:%.*]] = unchecked_ownership_conversion [[REG5]], @unowned to @guaranteed +// CHECK: [[REG5:%.*]] = unchecked_ownership [[REG4]] // CHECK: end_borrow [[REG4]] -// CHECK: return [[REG6]] +// CHECK: return [[REG5]] // CHECK: } // CHECK: sil hidden [ossa] @$s15borrow_accessor12GenNCWrapperVAARi_zrlE2ncAA2NCVvz : $@convention(method) (@inout GenNCWrapper) -> @inout NC { @@ -672,10 +668,9 @@ public struct GenNCWrapper : ~Copyable { // CHECK: [[REG2:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG0]] // CHECK: [[REG3:%.*]] = struct_element_addr [[REG2]], #GenNCWrapper._ncw // CHECK: [[REG4:%.*]] = load_borrow [[REG3]] -// CHECK: [[REG5:%.*]] = unchecked_ownership_conversion [[REG4]], @guaranteed to @unowned -// CHECK: [[REG6:%.*]] = unchecked_ownership_conversion [[REG5]], @unowned to @guaranteed +// CHECK: [[REG5:%.*]] = unchecked_ownership [[REG4]] // CHECK: end_borrow [[REG4]] -// CHECK: return [[REG6]] +// CHECK: return [[REG5]] // CHECK: } // CHECK: sil hidden [ossa] @$s15borrow_accessor12GenNCWrapperVAARi_zrlE3ncwAA0D0Vvz : $@convention(method) (@inout GenNCWrapper) -> @inout NCWrapper { @@ -736,15 +731,14 @@ public struct GenNCWrapper : ~Copyable { // CHECK: [[REG4:%.*]] = load_borrow [[REG3]] // CHECK: [[REG5:%.*]] = function_ref @$s15borrow_accessor9NCWrapperV2ncAA2NCVvb : $@convention(method) (@guaranteed NCWrapper) -> @guaranteed NC // CHECK: [[REG6:%.*]] = apply [[REG5]]([[REG4]]) : $@convention(method) (@guaranteed NCWrapper) -> @guaranteed NC -// CHECK: [[REG7:%.*]] = unchecked_ownership_conversion [[REG6]], @guaranteed to @unowned -// CHECK: [[REG8:%.*]] = unchecked_ownership_conversion [[REG7]], @unowned to @guaranteed -// CHECK: [[REG9:%.*]] = copy_value [[REG8]] +// CHECK: [[REG7:%.*]] = unchecked_ownership [[REG6]] +// CHECK: [[REG9:%.*]] = copy_value [[REG7]] // CHECK: [[REG10:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG9]] // CHECK: [[REG11:%.*]] = begin_borrow [[REG10]] // CHECK: end_borrow [[REG4]] // CHECK: end_borrow [[REG11]] // CHECK: destroy_value [[REG10]] -// CHECK: return [[REG8]] +// CHECK: return [[REG7]] // CHECK: } // CHECK: sil hidden [ossa] @$s15borrow_accessor12GenNCWrapperVAARi_zrlE10nested_nc1AA2NCVvz : $@convention(method) (@inout GenNCWrapper) -> @inout NC { diff --git a/test/SILGen/borrow_accessor_container.swift b/test/SILGen/borrow_accessor_container.swift index 5dd82f1f2a5bd..a5ebe0cbb7856 100644 --- a/test/SILGen/borrow_accessor_container.swift +++ b/test/SILGen/borrow_accessor_container.swift @@ -224,12 +224,11 @@ public struct CopyableContainer { // CHECK: [[REG27:%.*]] = begin_access [read] [unsafe] [[REG26]] // CHECK: [[REG28:%.*]] = struct_element_addr [[REG27]], #S._k // CHECK: [[REG29:%.*]] = load_borrow [[REG28]] -// CHECK: [[REG31:%.*]] = unchecked_ownership_conversion [[REG29]], @guaranteed to @unowned -// CHECK: [[REG32:%.*]] = unchecked_ownership_conversion [[REG31]], @unowned to @guaranteed +// CHECK: [[REG31:%.*]] = unchecked_ownership [[REG29]] // CHECK: end_access [[REG27]] // CHECK: end_borrow [[REG29]] // CHECK: dealloc_stack [[REG4]] -// CHECK: return [[REG32]] +// CHECK: return [[REG31]] // CHECK: } // CHECK: sil [ossa] @$s25borrow_accessor_container17CopyableContainerVyAA5KlassCSiciz : $@convention(method) (Int, @inout CopyableContainer) -> @inout Klass { @@ -329,13 +328,12 @@ public struct NonCopyableContainer : ~Copyable { // CHECK: [[REG31:%.*]] = begin_access [read] [unsafe] [[REG30]] // CHECK: [[REG32:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG31]] // CHECK: [[REG33:%.*]] = load_borrow [[REG32]] -// CHECK: [[REG35:%.*]] = unchecked_ownership_conversion [[REG33]], @guaranteed to @unowned -// CHECK: [[REG36:%.*]] = unchecked_ownership_conversion [[REG35]], @unowned to @guaranteed +// CHECK: [[REG35:%.*]] = unchecked_ownership [[REG33]] // CHECK: end_access [[REG31]] // CHECK: end_borrow [[REG33]] // CHECK: dealloc_stack [[REG6]] // CHECK: destroy_value [[REG4]] -// CHECK: return [[REG36]] +// CHECK: return [[REG35]] // CHECK: } // CHECK: sil [ossa] @$s25borrow_accessor_container20NonCopyableContainerVyAA2NCVSiciz : $@convention(method) (Int, @inout NonCopyableContainer) -> @inout NC { diff --git a/test/SILGen/borrow_accessor_evolution.swift b/test/SILGen/borrow_accessor_evolution.swift index 9bd9a26030e89..02f7af95c95a7 100644 --- a/test/SILGen/borrow_accessor_evolution.swift +++ b/test/SILGen/borrow_accessor_evolution.swift @@ -131,9 +131,9 @@ func test() { // CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref // CHECK: [[REG2:%.*]] = struct_element_addr [[REG0]], #Wrapper._k // CHECK: [[REG3:%.*]] = load_borrow [[REG2]] -// CHECK: [[REG4:%.*]] = unchecked_ownership_conversion [[REG3]], @guaranteed to @unowned -// CHECK: [[REG5:%.*]] = unchecked_ownership_conversion [[REG4]], @unowned to @guaranteed -// CHECK: return [[REG5]] +// CHECK: [[REG4:%.*]] = unchecked_ownership [[REG3]] +// CHECK: end_borrow [[REG3]] +// CHECK: return [[REG4]] // CHECK: } // CHECK-LABEL: sil hidden [ossa] @$s25borrow_accessor_evolution7WrapperV7nested1AA5KlassCvb : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed Klass { @@ -159,10 +159,9 @@ func test() { // CHECK: debug_value [[REG1]], let, name "self", argno 2, expr op_deref // CHECK: [[REG4:%.*]] = struct_element_addr [[REG1]], #Wrapper._k // CHECK: [[REG5:%.*]] = load_borrow [[REG4]] -// CHECK: [[REG6:%.*]] = unchecked_ownership_conversion [[REG5]], @guaranteed to @unowned -// CHECK: [[REG7:%.*]] = unchecked_ownership_conversion [[REG6]], @unowned to @guaranteed +// CHECK: [[REG6:%.*]] = unchecked_ownership [[REG5]] // CHECK: end_borrow [[REG5]] -// CHECK: return [[REG7]] +// CHECK: return [[REG6]] // CHECK: } // CHECK-LABEL: sil hidden [ossa] @$s25borrow_accessor_evolution7WrapperV16nested_subscriptAA5KlassCvb : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed Klass { From 01680de084168df7f536d5c47997f8f3663a39aa Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Wed, 15 Oct 2025 12:26:41 -0700 Subject: [PATCH 09/14] Fixup ownership of borrow accessor results SILGen may produce a borrow accessor result from within a local borrow scope. Such as: ``` %ld = load_borrow %self %fwd = unchecked_ownership %ld %ex = struct_extract %fwd, #Struct.storedProperty end_borrow %ld return %ex ``` This is illegal OSSA, since the return uses a value outside it's borrow scope. Add a new SILGenCleanup transform, to turn this into valid OSSA: ``` %ld = load_borrow %self %ex = struct_extract %ld, #Struct.storedProperty return_borrow %ex from_scopes %ld ``` --- lib/SIL/Utils/MemAccessUtils.cpp | 11 +++ lib/SILOptimizer/Mandatory/SILGenCleanup.cpp | 86 +++++++++++++++++++- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/lib/SIL/Utils/MemAccessUtils.cpp b/lib/SIL/Utils/MemAccessUtils.cpp index 76a881f9bd7fa..da0b8ad94c25a 100644 --- a/lib/SIL/Utils/MemAccessUtils.cpp +++ b/lib/SIL/Utils/MemAccessUtils.cpp @@ -924,6 +924,17 @@ void swift::findGuaranteedReferenceRoots(SILValue referenceValue, // regardless of whether they were already visited. continue; } + + // Special handling until borrowing apply is represented as a + // ForwardingOperation. + if (auto *apply = + dyn_cast_or_null(value->getDefiningInstruction())) { + if (apply->hasGuaranteedResult()) { + worklist.pushIfNotVisited(apply->getSelfArgument()); + continue; + } + } + // Found a potential root. if (lookThroughNestedBorrows) { if (auto *bbi = dyn_cast(value)) { diff --git a/lib/SILOptimizer/Mandatory/SILGenCleanup.cpp b/lib/SILOptimizer/Mandatory/SILGenCleanup.cpp index 40ee8c02f635f..f216f94be07ef 100644 --- a/lib/SILOptimizer/Mandatory/SILGenCleanup.cpp +++ b/lib/SILOptimizer/Mandatory/SILGenCleanup.cpp @@ -31,6 +31,7 @@ #include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h" #include "swift/SILOptimizer/Utils/CanonicalizeInstruction.h" #include "swift/SILOptimizer/Utils/InstOptUtils.h" +#include "swift/SILOptimizer/Utils/InstructionDeleter.h" #include "llvm/ADT/PostOrderIterator.h" using namespace swift; @@ -107,6 +108,7 @@ namespace { struct SILGenCleanup : SILModuleTransform { void run() override; + bool fixupBorrowAccessors(SILFunction *function); bool completeOSSALifetimes(SILFunction *function); template bool completeLifetimesInRange(Range const &range, @@ -229,6 +231,87 @@ void collectReachableRoots(SILFunction *function, BasicBlockWorklist &backward, } while (changed); } +/* SILGen may produce a borrow accessor result from within a local borrow + * scope. Such as: + * + * ``` + * %ld = load_borrow %self + * %fwd = unchecked_ownership %ld + * %ex = struct_extract %fwd, #Struct.storedProperty + * end_borrow %ld + * return %ex + * ``` + * This is illegal OSSA, since the return uses a value outside it's borrow + * scope. + * + * Transform this into valid OSSA: + * + * ``` + * %ld = load_borrow %self + * %ex = struct_extract %ld, #Struct.storedProperty + * return_borrow %ex from_scopes %ld + * ``` + */ +bool SILGenCleanup::fixupBorrowAccessors(SILFunction *function) { + if (!function->getConventions().hasGuaranteedResult()) { + return false; + } + auto returnBB = function->findReturnBB(); + if (returnBB == function->end()) { + return false; + } + + auto *returnInst = cast(returnBB->getTerminator()); + if (returnInst->getOperand()->getOwnershipKind() != + OwnershipKind::Guaranteed) { + return false; + } + + SmallVector enclosingValues; + findGuaranteedReferenceRoots(returnInst->getOperand(), + /*lookThroughNestedBorrows=*/false, + enclosingValues); + SmallVector scopeEnds; + SmallVector operands; + SmallVector toDelete; + + // For all the local borrow scopes that enclose the return value, delete + // their end_borrow instructions and use them as an encolsing value in + // return_borrow instruction. + for (auto enclosingValue : enclosingValues) { + BorrowedValue borrow(enclosingValue); + if (!borrow.isLocalScope()) { + continue; + } + borrow.getLocalScopeEndingInstructions(scopeEnds); + for (auto *scopeEnd : scopeEnds) { + if (auto *endBorrow = dyn_cast(scopeEnd)) { + operands.push_back(endBorrow->getOperand()); + endBorrow->eraseFromParent(); + } + } + for (auto *uncheckedOwnership : + borrow->getUsersOfType()) { + uncheckedOwnership->replaceAllUsesWith(*borrow); + toDelete.push_back(uncheckedOwnership); + } + } + + for (auto *inst : toDelete) { + inst->eraseFromParent(); + } + + if (operands.empty()) { + return false; + } + + SILBuilderWithScope(returnInst) + .createReturnBorrow(returnInst->getLoc(), returnInst->getOperand(), + operands); + returnInst->eraseFromParent(); + return true; +} + bool SILGenCleanup::completeOSSALifetimes(SILFunction *function) { if (!getModule()->getOptions().OSSACompleteLifetimes) return false; @@ -339,7 +422,8 @@ void SILGenCleanup::run() { LLVM_DEBUG(llvm::dbgs() << "\nRunning SILGenCleanup on " << function.getName() << "\n"); - bool changed = completeOSSALifetimes(&function); + bool changed = fixupBorrowAccessors(&function); + changed |= completeOSSALifetimes(&function); DeadEndBlocks deadEndBlocks(&function); SILGenCanonicalize sgCanonicalize(deadEndBlocks); From 4369c9c56aaddbfdf5901e5aaea801854323f8e7 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Thu, 16 Oct 2025 15:21:26 -0700 Subject: [PATCH 10/14] [NFC] Update test with return_borrow --- test/SILGen/borrow_accessor_evolution.swift | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/SILGen/borrow_accessor_evolution.swift b/test/SILGen/borrow_accessor_evolution.swift index 02f7af95c95a7..4bc30f6976a81 100644 --- a/test/SILGen/borrow_accessor_evolution.swift +++ b/test/SILGen/borrow_accessor_evolution.swift @@ -1,4 +1,5 @@ // RUN:%target-swift-frontend -emit-silgen %s -verify -enable-experimental-feature BorrowAndMutateAccessors -enable-library-evolution | %FileCheck %s +// RUN:%target-swift-frontend -c -Xllvm -sil-print-after=SILGenCleanup %s -verify -enable-experimental-feature BorrowAndMutateAccessors -enable-library-evolution 2>&1 | %FileCheck --check-prefixes=CHECK-SILGEN-CLEANUP %s // REQUIRES: swift_feature_BorrowAndMutateAccessors @@ -136,6 +137,13 @@ func test() { // CHECK: return [[REG4]] // CHECK: } +// CHECK-SILGEN-CLEANUP: sil hidden [ossa] @$s25borrow_accessor_evolution7WrapperV1kAA5KlassCvb : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed Klass { +// CHECK-SILGEN-CLEANUP: bb0([[REG0:%.*]] : $*Wrapper): +// CHECK-SILGEN-CLEANUP: [[REG2:%.*]] = struct_element_addr [[REG0]], #Wrapper._k +// CHECK-SILGEN-CLEANUP: [[REG3:%.*]] = load_borrow [[REG2]] +// CHECK-SILGEN-CLEANUP: return_borrow [[REG3]] from_scopes ([[REG3]]) +// CHECK-SILGEN-CLEANUP: } + // CHECK-LABEL: sil hidden [ossa] @$s25borrow_accessor_evolution7WrapperV7nested1AA5KlassCvb : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed Klass { // CHECK: bb0([[REG0:%.*]] : $*Wrapper): // CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref @@ -164,6 +172,13 @@ func test() { // CHECK: return [[REG6]] // CHECK: } +// CHECK-SILGEN-CLEANUP: sil hidden [ossa] @$s25borrow_accessor_evolution7WrapperVyAA5KlassCSicib : $@convention(method) (Int, @in_guaranteed Wrapper) -> @guaranteed Klass { +// CHECK-SILGEN-CLEANUP: bb0([[REG0:%.*]] : $Int, [[REG1:%.*]] : $*Wrapper): +// CHECK-SILGEN-CLEANUP: [[REG4:%.*]] = struct_element_addr [[REG1]], #Wrapper._k +// CHECK-SILGEN-CLEANUP: [[REG5:%.*]] = load_borrow [[REG4]] +// CHECK-SILGEN-CLEANUP: return_borrow [[REG5]] from_scopes ([[REG5]]) +// CHECK-SILGEN-CLEANUP: } + // CHECK-LABEL: sil hidden [ossa] @$s25borrow_accessor_evolution7WrapperV16nested_subscriptAA5KlassCvb : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed Klass { // CHECK: bb0([[REG0:%.*]] : $*Wrapper): // CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref From d9a2ff4ff2e01aba19643ace4fb30ed3147a61c9 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Mon, 20 Oct 2025 15:24:13 -0700 Subject: [PATCH 11/14] Update lifetime completion utility to consider return_borrow When return_borrow has both lifetime ending and non-lifetime ending use, consider it lifetime ending. --- lib/SIL/Utils/PrunedLiveness.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/SIL/Utils/PrunedLiveness.cpp b/lib/SIL/Utils/PrunedLiveness.cpp index a0eb8871dbccc..0fb623082d49b 100644 --- a/lib/SIL/Utils/PrunedLiveness.cpp +++ b/lib/SIL/Utils/PrunedLiveness.cpp @@ -249,7 +249,7 @@ void PrunedLiveRange::updateForUse( // argument must be copied. auto iterAndSuccess = users.insert({user, lifetimeEnding}); if (!iterAndSuccess.second) { - if (isa(user)) { + if (isa(user) || isa(user)) { branchMeetInPlace(iterAndSuccess.first->second, lifetimeEnding); } else { iterAndSuccess.first->second.meetInPlace(lifetimeEnding); From 6734d4a4e6060c5aeb8e43c29168a387ed9c9b8b Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Mon, 20 Oct 2025 16:34:37 -0700 Subject: [PATCH 12/14] Disable GenericSpecializer for @guaranteed results --- lib/SILOptimizer/Transforms/GenericSpecializer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/SILOptimizer/Transforms/GenericSpecializer.cpp b/lib/SILOptimizer/Transforms/GenericSpecializer.cpp index 782ae108c4ba0..a726870950a65 100644 --- a/lib/SILOptimizer/Transforms/GenericSpecializer.cpp +++ b/lib/SILOptimizer/Transforms/GenericSpecializer.cpp @@ -112,7 +112,9 @@ bool swift::specializeAppliesInFunction(SILFunction &F, // 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()) { + if (F.hasOwnership() && + (Callee->getConventions().hasAddressResult() || + Callee->getConventions().hasGuaranteedResult())) { continue; } From 9fed0413f05a7c0ce65ff77d1d5b123260a33645 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Tue, 21 Oct 2025 11:49:30 -0700 Subject: [PATCH 13/14] [NFC] Add documentation --- docs/SIL/Instructions.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/SIL/Instructions.md b/docs/SIL/Instructions.md index f70c4eb704bdc..dde6385d4d93a 100644 --- a/docs/SIL/Instructions.md +++ b/docs/SIL/Instructions.md @@ -1199,6 +1199,17 @@ the `set` case is passed `self`. This is only valid in Raw SIL. +### unchecked_ownership + +``` +sil-instruction ::= 'unchecked_ownership' sil-operand + +unchecked_ownership %1 : $T +``` + +unchecked_ownership disables the ownership verification of it's operand. This used in cases +we cannot resolve ownership until a mandatory pass runs. This is only valid in Raw SIL. + ### copy_addr ``` @@ -5003,6 +5014,20 @@ does not apply in the `raw` SIL stage. A function must not contain more than one `return` instruction. +### return_borrow + +``` +sil-terminator ::= 'return_borrow' sil-operand 'from_scopes' '(' (sil-operand (',' sil-operand)*)? ')' + +return_borrow %0 : $T from_scopes (%1, %2 ...) +// %0 must be a @guaranteed value +// %1, %2, ... must be borrow introducers for %0, like `load_borrow` +// $T must be the return type of the current function +``` + +return_borrow instruction is valid only for functions @guaranteed results. +It is used to a return a @guaranteed value that maybe produced within borrow scopes local to the function. + ### throw ``` From 7c25466b21703612c0affd9a7808fe0006226100 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Mon, 20 Oct 2025 13:04:20 -0700 Subject: [PATCH 14/14] Avoid unnecessary insertion of mark_unresolved_noncopyable_value SILGen inserts mark_unresolved_noncopyable_value at the introducer in some cases and at uses in other cases. This inconsistency can causes insertion of a redundant mark* instruction which crashes the move-only checker. This change avoids inserting a redundant mark* instruction. --- lib/SILGen/SILGenFunction.cpp | 14 ++++++++++---- test/SILGen/addressable_read.swift | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 test/SILGen/addressable_read.swift diff --git a/lib/SILGen/SILGenFunction.cpp b/lib/SILGen/SILGenFunction.cpp index db567b68fc624..9ceea09ad01be 100644 --- a/lib/SILGen/SILGenFunction.cpp +++ b/lib/SILGen/SILGenFunction.cpp @@ -807,10 +807,16 @@ void SILGenFunction::emitCaptures(SILLocation loc, // If we have a mutable binding for a 'let', such as 'self' in an // 'init' method, load it. if (val->getType().isMoveOnly()) { - val = B.createMarkUnresolvedNonCopyableValueInst( - loc, val, - MarkUnresolvedNonCopyableValueInst::CheckKind:: - NoConsumeOrAssign); + auto *moveOnlyIntroducer = + dyn_cast_or_null(val); + if (!moveOnlyIntroducer || moveOnlyIntroducer->getCheckKind() != + MarkUnresolvedNonCopyableValueInst:: + CheckKind::NoConsumeOrAssign) { + val = B.createMarkUnresolvedNonCopyableValueInst( + loc, val, + MarkUnresolvedNonCopyableValueInst::CheckKind:: + NoConsumeOrAssign); + } } val = emitLoad(loc, val, tl, SGFContext(), IsNotTake).forward(*this); } diff --git a/test/SILGen/addressable_read.swift b/test/SILGen/addressable_read.swift new file mode 100644 index 0000000000000..6fe4a4352cdc9 --- /dev/null +++ b/test/SILGen/addressable_read.swift @@ -0,0 +1,23 @@ +// RUN: %target-swift-frontend -emit-sil -verify -enable-experimental-feature AddressableParameters %s + +// REQUIRES: swift_feature_AddressableParameters + +public struct Container: ~Copyable { + var _storage: UnsafeMutableBufferPointer + var _count: Int + + public subscript(index: Int) -> Element { + @_addressableSelf + _read { + precondition(index >= 0 && index < _count, "Index out of bounds") + yield _storage.baseAddress.unsafelyUnwrapped.advanced(by: index).pointee + } + _modify { + precondition(index >= 0 && index < _count, "Index out of bounds") + yield &_storage.baseAddress.unsafelyUnwrapped.advanced(by: index).pointee + } + } +} + +extension Container: Copyable where Element: Copyable {} +