From e4213056a5922cd2037594497c5ad6c05eb4effb Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Sat, 13 Sep 2025 04:54:45 -0700 Subject: [PATCH 01/17] Ban multiple returns in borrow accessors returning values as well --- lib/SILGen/SILGenStmt.cpp | 10 +++++----- test/SILGen/borrow_accessor.swift | 26 +++++++++++++------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lib/SILGen/SILGenStmt.cpp b/lib/SILGen/SILGenStmt.cpp index da5e5aedae375..37386d08ea31f 100644 --- a/lib/SILGen/SILGenStmt.cpp +++ b/lib/SILGen/SILGenStmt.cpp @@ -782,11 +782,11 @@ void SILGenFunction::emitReturnExpr(SILLocation branchLoc, diag::borrow_accessor_not_a_projection_note); return; } - // Address type phis are banned in SIL. - // For now diagnose multiple return statements in such cases. - // TODO: Support multiple epilog blocks in SILGen and SILOptimizer. - if (F.getConventions().hasGuaranteedAddressResult() && - !ReturnDest.getBlock()->getPredecessorBlocks().empty()) { + // For now diagnose multiple return statements in borrow/mutate accessors. + // We need additional support for this. + // 1. Address phis are banned in SIL. + // 2. borrowed from is not inserted in SILGenCleanup. + if (!ReturnDest.getBlock()->getPredecessorBlocks().empty()) { diagnose(getASTContext(), ret->getStartLoc(), diag::invalid_multiple_return_borrow_accessor); return; diff --git a/test/SILGen/borrow_accessor.swift b/test/SILGen/borrow_accessor.swift index 86a5e3a702789..59f8a741b647b 100644 --- a/test/SILGen/borrow_accessor.swift +++ b/test/SILGen/borrow_accessor.swift @@ -104,7 +104,7 @@ public struct Wrapper { if Int.random(in: 1..<100) == 0 { return _k } - return _k + return _k // expected-error{{multiple return statements in borrow accessors are not yet supported}} } } @@ -401,18 +401,18 @@ func nctest() { // CHECK: return [[REG7]] // CHECK: } -// CHECK: sil hidden [ossa] @$s15borrow_accessor7WrapperV8if_klassAA5KlassCvb : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass { -// CHECK: bb0([[REG0:%.*]] : @guaranteed $Wrapper): -// CHECK: cond_br {{.*}}, bb1, bb2 -// CHECK: bb1: -// CHECK: [[EX1:%.*]] = struct_extract [[REG0]], #Wrapper._k -// CHECK: br bb3([[EX1]]) -// CHECK: bb2: -// CHECK: [[EX2:%.*]] = struct_extract [[REG0]], #Wrapper._k -// CHECK: br bb3([[EX2]]) -// CHECK: bb3([[PHI:%.*]] : @guaranteed $Klass): -// CHECK: return [[PHI]] -// CHECK: } +// TODO-CHECK: sil hidden [ossa] @$s15borrow_accessor7WrapperV8if_klassAA5KlassCvb : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass { +// TODO-CHECK: bb0([[REG0:%.*]] : @guaranteed $Wrapper): +// TODO-CHECK: cond_br {{.*}}, bb1, bb2 +// TODO-CHECK: bb1: +// TODO-CHECK: [[EX1:%.*]] = struct_extract [[REG0]], #Wrapper._k +// TODO-CHECK: br bb3([[EX1]]) +// TODO-CHECK: bb2: +// TODO-CHECK: [[EX2:%.*]] = struct_extract [[REG0]], #Wrapper._k +// TODO-CHECK: br bb3([[EX2]]) +// TODO-CHECK: bb3([[PHI:%.*]] : @guaranteed $Klass): +// TODO-CHECK: return [[PHI]] +// TODO-CHECK: } // CHECK: sil [ossa] @$s15borrow_accessor10GenWrapperV4propxvb : $@convention(method) (@in_guaranteed GenWrapper) -> @guaranteed_addr T { // CHECK: bb0([[REG0:%.*]] : $*GenWrapper): From e98f3522c8799392a2aaa3c129f5a18c1ec834bd Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Mon, 15 Sep 2025 11:43:01 -0700 Subject: [PATCH 02/17] [NFC] Rename LVOptions::withBorrow -> LVOptions::forGuaranteedReturn Also add forGuaranteedAddressReturn --- lib/SILGen/SILGenFunction.h | 13 ++++++++++--- lib/SILGen/SILGenLValue.cpp | 18 ++++++++++++------ lib/SILGen/SILGenStmt.cpp | 2 +- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index 3711fa02f46f7..734b7c60f781c 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -58,7 +58,8 @@ class ExecutorBreadcrumb; struct LValueOptions { bool IsNonAccessing = false; bool TryAddressable = false; - bool NeedsBorrow = false; + bool ForGuaranteedReturn = false; + bool ForGuaranteedAddressReturn = false; /// Derive options for accessing the base of an l-value, given that /// applying the derived component might touch the memory. @@ -83,9 +84,15 @@ struct LValueOptions { return copy; } - LValueOptions withBorrow(bool borrow) const { + LValueOptions forGuaranteedReturn(bool value) const { auto copy = *this; - copy.NeedsBorrow = borrow; + copy.ForGuaranteedReturn = value; + return copy; + } + + LValueOptions forGuaranteedAddressReturn(bool value) const { + auto copy = *this; + copy.ForGuaranteedAddressReturn = value; return copy; } }; diff --git a/lib/SILGen/SILGenLValue.cpp b/lib/SILGen/SILGenLValue.cpp index 3dc98324d91da..08f39ef509451 100644 --- a/lib/SILGen/SILGenLValue.cpp +++ b/lib/SILGen/SILGenLValue.cpp @@ -3053,11 +3053,16 @@ class LLVM_LIBRARY_VISIBILITY SILGenBorrowedBaseVisitor SILGenLValue &SGL; SILGenFunction &SGF; AbstractionPattern Orig; + bool forGuaranteedReturn; + bool forGuaranteedAddressReturn; - SILGenBorrowedBaseVisitor(SILGenLValue &SGL, - SILGenFunction &SGF, - AbstractionPattern Orig) - : SGL(SGL), SGF(SGF), Orig(Orig) {} + SILGenBorrowedBaseVisitor(SILGenLValue &SGL, SILGenFunction &SGF, + AbstractionPattern Orig, + bool forGuaranteedReturn = false, + bool forGuaranteedAddressReturn = false) + : SGL(SGL), SGF(SGF), Orig(Orig), + forGuaranteedReturn(forGuaranteedReturn), + forGuaranteedAddressReturn(forGuaranteedAddressReturn) {} static bool isNonCopyableBaseBorrow(SILGenFunction &SGF, Expr *e) { if (auto *m = dyn_cast(e)) { @@ -3261,8 +3266,9 @@ LValue SILGenLValue::visitRec(Expr *e, SGFAccessKind accessKind, // apply the lvalue within a formal access to the original value instead of // an actual loaded copy. if (SILGenBorrowedBaseVisitor::isNonCopyableBaseBorrow(SGF, e) || - options.NeedsBorrow) { - SILGenBorrowedBaseVisitor visitor(*this, SGF, orig); + options.ForGuaranteedReturn) { + SILGenBorrowedBaseVisitor visitor(*this, SGF, orig, + options.ForGuaranteedReturn); auto accessKind = SGFAccessKind::BorrowedObjectRead; assert(!e->getType()->is() && "maybe need SGFAccessKind::BorrowedAddressRead ?"); diff --git a/lib/SILGen/SILGenStmt.cpp b/lib/SILGen/SILGenStmt.cpp index 37386d08ea31f..114ea29099a75 100644 --- a/lib/SILGen/SILGenStmt.cpp +++ b/lib/SILGen/SILGenStmt.cpp @@ -772,7 +772,7 @@ void SILGenFunction::emitReturnExpr(SILLocation branchLoc, F.getConventions().hasGuaranteedResult() ? SGFAccessKind::BorrowedObjectRead : SGFAccessKind::BorrowedAddressRead, - options.withBorrow(true)); + options.forGuaranteedReturn(true)); auto result = tryEmitProjectedLValue(ret, std::move(lvalue), TSanKind::None); if (!result) { From ab07ca9f328e5e12bc90aece0102a70dc52ccfcd Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Wed, 17 Sep 2025 13:06:00 -0700 Subject: [PATCH 03/17] [NFC] Remove unused AddressableParams set --- lib/SILGen/SILGenProlog.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/SILGen/SILGenProlog.cpp b/lib/SILGen/SILGenProlog.cpp index 94d59fd1df74f..463e38579098b 100644 --- a/lib/SILGen/SILGenProlog.cpp +++ b/lib/SILGen/SILGenProlog.cpp @@ -631,7 +631,6 @@ class ArgumentInitHelper { std::optional FormalParamTypes; SmallPtrSet ScopedDependencies; - SmallPtrSet AddressableParams; public: ArgumentInitHelper(SILGenFunction &SGF, @@ -736,9 +735,6 @@ class ArgumentInitHelper { || (ScopedDependencies.contains(pd) && SGF.getTypeProperties(origType, substType) .isAddressableForDependencies()); - if (isAddressable) { - AddressableParams.insert(pd); - } paramValue = argEmitter.handleParam(origType, substType, pd, isAddressable); } From c2dab5887605638fcfd53f65b9240ad8ae873da0 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Mon, 15 Sep 2025 12:35:48 -0700 Subject: [PATCH 04/17] Update SILGen for ~Copyable borrow accessors Introduce copy_value + mark_unresolved_non_copyable_value + begin_borrow at the return value of borrow accessor apply to drive move-only diagnostics. Also strip the copy_value + mark_unresolved_non_copyable_value + begin_borrow trio in a few places, since they create an artificial scope out of which we cannot return values in a borrow accessor without resorting to unsafe SIL operations currently. Borrow accessor diagnostics allow stripping these instructions safely in the following places: - return value of a borrow accessor - self argument reference in the borrow accessor return expression and borrow accessor apply --- include/swift/SIL/InstructionUtils.h | 2 + include/swift/SIL/SILValue.h | 2 + lib/SIL/IR/SILValue.cpp | 13 ++-- lib/SIL/Utils/InstructionUtils.cpp | 16 ++++ lib/SILGen/SILGenApply.cpp | 29 +++++-- lib/SILGen/SILGenLValue.cpp | 21 +++++ lib/SILGen/SILGenStmt.cpp | 12 ++- test/SILGen/borrow_accessor.swift | 111 +++++++++++++++++++++++++++ 8 files changed, 195 insertions(+), 11 deletions(-) diff --git a/include/swift/SIL/InstructionUtils.h b/include/swift/SIL/InstructionUtils.h index 9fa2a3ca1bb06..cad6d3d7c5e44 100644 --- a/include/swift/SIL/InstructionUtils.h +++ b/include/swift/SIL/InstructionUtils.h @@ -41,6 +41,8 @@ SILValue stripCastsWithoutMarkDependence(SILValue V); /// begin_borrow instructions. SILValue lookThroughOwnershipInsts(SILValue v); +SILValue lookThroughMoveOnlyCheckerPattern(SILValue value); + /// Reverse of lookThroughOwnershipInsts. /// /// Return true if \p visitor returned true for all uses. diff --git a/include/swift/SIL/SILValue.h b/include/swift/SIL/SILValue.h index 22b1a6aea28fe..660ff93ab1c2b 100644 --- a/include/swift/SIL/SILValue.h +++ b/include/swift/SIL/SILValue.h @@ -616,6 +616,8 @@ class ValueBase : public SILNode, public SILAllocated { bool isBeginApplyToken() const; + bool isBorrowAccessorResult() const; + /// Unsafely eliminate moveonly from this value's type. Returns true if the /// value's underlying type was move only and thus was changed. Returns false /// otherwise. diff --git a/lib/SIL/IR/SILValue.cpp b/lib/SIL/IR/SILValue.cpp index 37531564fdf92..f4d898b6b56d3 100644 --- a/lib/SIL/IR/SILValue.cpp +++ b/lib/SIL/IR/SILValue.cpp @@ -196,11 +196,7 @@ bool ValueBase::isGuaranteedForwarding() const { return phi->isGuaranteedForwarding(); } - auto *applyInst = dyn_cast_or_null(getDefiningInstruction()); - if (!applyInst) { - return false; - } - return applyInst->hasGuaranteedResult(); + return isBorrowAccessorResult(); } bool ValueBase::isBeginApplyToken() const { @@ -210,6 +206,13 @@ bool ValueBase::isBeginApplyToken() const { return result->isBeginApplyToken(); } +bool ValueBase::isBorrowAccessorResult() const { + auto *apply = dyn_cast_or_null(getDefiningInstruction()); + if (!apply) + return false; + return apply->hasGuaranteedResult(); +} + bool ValueBase::hasDebugTrace() const { for (auto *op : getUses()) { if (auto *debugValue = dyn_cast(op->getUser())) { diff --git a/lib/SIL/Utils/InstructionUtils.cpp b/lib/SIL/Utils/InstructionUtils.cpp index 60e73bf267ee6..e07ff4fc2e8e7 100644 --- a/lib/SIL/Utils/InstructionUtils.cpp +++ b/lib/SIL/Utils/InstructionUtils.cpp @@ -48,6 +48,22 @@ SILValue swift::lookThroughOwnershipInsts(SILValue v) { } } +SILValue swift::lookThroughMoveOnlyCheckerPattern(SILValue value) { + auto *bbi = dyn_cast(value); + if (!bbi) { + return value; + } + auto *muncvi = cast(bbi->getOperand()); + if (!muncvi) { + return value; + } + auto *cvi = cast(muncvi->getOperand()); + if (!cvi) { + return value; + } + return cvi->getOperand(); +} + bool swift::visitNonOwnershipUses(SILValue value, function_ref visitor) { // All ownership insts have a single operand, so a recursive walk is diff --git a/lib/SILGen/SILGenApply.cpp b/lib/SILGen/SILGenApply.cpp index 4b6896abb7826..bc28c898c1b67 100644 --- a/lib/SILGen/SILGenApply.cpp +++ b/lib/SILGen/SILGenApply.cpp @@ -5442,6 +5442,17 @@ ManagedValue CallEmission::applyBorrowAccessor() { origFormalType.getLifetimeDependencies(), calleeTypeInfo.foreign, uncurriedArgs, uncurriedLoc); + auto selfArgMV = uncurriedArgs.back(); + + // Strip the unnecessary copy_value + mark_unresolved_non_copyable_value + + // begin_borrow instructions added for move-only self argument. + if (selfArgMV.getValue()->getType().isMoveOnly() && + selfArgMV.getValue()->getType().isObject()) { + uncurriedArgs[uncurriedArgs.size() - 1] = + ManagedValue::forBorrowedObjectRValue( + lookThroughMoveOnlyCheckerPattern(selfArgMV.getValue())); + } + auto value = SGF.applyBorrowAccessor(uncurriedLoc.value(), fnValue, canUnwind, callee.getSubstitutions(), uncurriedArgs, calleeTypeInfo.substFnType, options); @@ -5459,13 +5470,21 @@ ManagedValue SILGenFunction::applyBorrowAccessor( /*indirect results*/ {}, /*indirect errors*/ {}, rawResults, ExecutorBreadcrumb()); assert(rawResults.size() == 1); - auto result = rawResults[0]; - if (result->getType().isMoveOnly()) { - result = B.createMarkUnresolvedNonCopyableValueInst( - loc, result, + auto rawResult = rawResults[0]; + if (!rawResult->getType().isMoveOnly()) { + return ManagedValue::forForwardedRValue(*this, rawResult); + } + if (rawResult->getType().isAddress()) { + auto result = B.createMarkUnresolvedNonCopyableValueInst( + loc, rawResult, MarkUnresolvedNonCopyableValueInst::CheckKind::NoConsumeOrAssign); + return ManagedValue::forRValueWithoutOwnership(result); } - return ManagedValue::forForwardedRValue(*this, result); + auto result = emitManagedCopy(loc, rawResult); + result = B.createMarkUnresolvedNonCopyableValueInst( + loc, result, + MarkUnresolvedNonCopyableValueInst::CheckKind::NoConsumeOrAssign); + return emitManagedBeginBorrow(loc, result.getValue()); } RValue CallEmission::applyFirstLevelCallee(SGFContext C) { diff --git a/lib/SILGen/SILGenLValue.cpp b/lib/SILGen/SILGenLValue.cpp index 08f39ef509451..f97b63fe18bf5 100644 --- a/lib/SILGen/SILGenLValue.cpp +++ b/lib/SILGen/SILGenLValue.cpp @@ -3171,6 +3171,27 @@ class LLVM_LIBRARY_VISIBILITY SILGenBorrowedBaseVisitor // We are going to immediately use this base value, so we want to borrow it. ManagedValue mv = SGF.emitRValueAsSingleValue(e, SGFContext::AllowImmediatePlusZero); + + if (forGuaranteedReturn && mv.getValue()->getType().isMoveOnly() && + !mv.getValue()->getType().isAddress()) { + // SILGen eagerly generates copy_value + + // mark_unresolved_non_copyable_value for ~Copyable base values. The + // generated mark_unresolved_non_copyable_value instructions drive + // move-only checker diagnostics ensuring there are no consuming uses of + // this value. However, the copy_value + + // mark_unresolved_non_copyable_value pair creates an artifical scope out + // of which we cannot legally return a + // @guaranteed value. + // We have already diagosed borrow accessor returns, ensuring they return + // only projections, since projections don't create any consuming used we + // can safely look through copy_value + mark_unresolved_non_copyable_value + begin_borrow + // instructions while emitting the base value. + auto baseValue = lookThroughMoveOnlyCheckerPattern(mv.getValue()); + assert(cast(baseValue) == + SGF.getFunction().getSelfArgument()); + return ManagedValue::forBorrowedObjectRValue(baseValue); + } + if (mv.isPlusZeroRValueOrTrivial()) return mv; diff --git a/lib/SILGen/SILGenStmt.cpp b/lib/SILGen/SILGenStmt.cpp index 114ea29099a75..051decadd67f7 100644 --- a/lib/SILGen/SILGenStmt.cpp +++ b/lib/SILGen/SILGenStmt.cpp @@ -28,6 +28,7 @@ #include "swift/Basic/ProfileCounter.h" #include "swift/SIL/AbstractionPatternGenerators.h" #include "swift/SIL/BasicBlockUtils.h" +#include "swift/SIL/InstructionUtils.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILProfiler.h" #include "llvm/Support/SaveAndRestore.h" @@ -791,7 +792,16 @@ void SILGenFunction::emitReturnExpr(SILLocation branchLoc, diag::invalid_multiple_return_borrow_accessor); return; } - directResults.push_back(result->forward(*this)); + + auto resultValue = result->getValue(); + SILType selfType = F.getSelfArgument()->getType(); + if (selfType.isMoveOnly() && F.getConventions().hasGuaranteedResult()) { + // If we are returning the result of borrow accessor, strip the + // unnecessary copy_value + mark_unresolved_non_copyable_value + // instructions. + resultValue = lookThroughMoveOnlyCheckerPattern(resultValue); + } + directResults.push_back(resultValue); } } else { // SILValue return. diff --git a/test/SILGen/borrow_accessor.swift b/test/SILGen/borrow_accessor.swift index 59f8a741b647b..8a9bfe28a82ef 100644 --- a/test/SILGen/borrow_accessor.swift +++ b/test/SILGen/borrow_accessor.swift @@ -333,6 +333,55 @@ public struct GenNCWrapper : ~Copyable { } } +public struct NCS: ~Copyable { + var _nc: NC + + var nc: NC { + borrow { + return _nc + } + } +} + +public struct NCWrapper: ~Copyable { + var _nc: NC + var _s: NCS + + var nc: NC { + borrow { + return _nc + } + } + var nested1: NC { + borrow { + return _s.nc + } + } + + var nested2: NC { + borrow { + return nc + } + } + + subscript(index: Int) -> NC { + borrow { + return _nc + } + } + + var nested_subscript: NC { + borrow { + return self[0] + } + } + + var literal: Int { + borrow { + return 0 + } + } +} func test() { let w1 = Wrapper(_k: Klass(), _s: S(_k: Klass())) @@ -451,3 +500,65 @@ func nctest() { // CHECK: [[REG7:%.*]] = apply [[REG6]]([[REG5]], [[REG0]]) : $@convention(method) <τ_0_0> (Int, @in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed_addr τ_0_0 // CHECK: return [[REG7]] // CHECK: } + +// CHECK-LABEL: sil hidden [ossa] @$s15borrow_accessor9NCWrapperV2ncAA2NCVvb : $@convention(method) (@guaranteed NCWrapper) -> @guaranteed NC { +// CHECK: bb0([[REG0]] : @guaranteed $NCWrapper): +// CHECK: [[REG1:%.*]] = copy_value [[REG0]] +// CHECK: [[REG2:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG1]] +// CHECK: [[REG4:%.*]] = begin_borrow [[REG2]] +// CHECK: [[REG5:%.*]] = struct_extract [[REG0]], #NCWrapper._nc +// CHECK: end_borrow [[REG4]] +// CHECK: destroy_value [[REG2]] +// CHECK: return [[REG5]] +// CHECK: } + +// CHECK-LABEL: sil hidden [ossa] @$s15borrow_accessor9NCWrapperV7nested1AA2NCVvb : $@convention(method) (@guaranteed NCWrapper) -> @guaranteed NC { +// CHECK: bb0([[REG0]] : @guaranteed $NCWrapper): +// CHECK: [[REG1:%.*]] = copy_value [[REG0]] +// CHECK: [[REG2:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG1]] +// CHECK: [[REG4:%.*]] = begin_borrow [[REG2]] +// CHECK: [[REG5:%.*]] = struct_extract [[REG0]], #NCWrapper._s +// CHECK: [[REG6:%.*]] = function_ref @$s15borrow_accessor3NCSV2ncAA2NCVvb : $@convention(method) (@guaranteed NCS) -> @guaranteed NC +// CHECK: [[REG7:%.*]] = apply [[REG6]]([[REG5]]) : $@convention(method) (@guaranteed NCS) -> @guaranteed NC +// CHECK: [[REG8:%.*]] = copy_value [[REG7]] +// CHECK: [[REG9:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG8]] +// CHECK: [[REG10:%.*]] = begin_borrow [[REG9]] +// CHECK: end_borrow [[REG10]] +// CHECK: destroy_value [[REG9]] +// CHECK: end_borrow [[REG4]] +// CHECK: destroy_value [[REG2]] +// CHECK: return [[REG7]] +// CHECK-LABEL: } + +// CHECK-LABEL: sil hidden [ossa] @$s15borrow_accessor9NCWrapperVyAA2NCVSicib : $@convention(method) (Int, @guaranteed NCWrapper) -> @guaranteed NC { +// CHECK: bb0([[REG0]] : $Int, [[REG1]] : @guaranteed $NCWrapper): +// CHECK: [[REG3:%.*]] = copy_value [[REG1]] +// CHECK: [[REG4:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG3]] +// CHECK: [[REG6:%.*]] = begin_borrow [[REG4]] +// CHECK: [[REG7:%.*]] = struct_extract [[REG1]], #NCWrapper._nc +// CHECK: end_borrow [[REG6]] +// CHECK: destroy_value [[REG4]] +// CHECK: return [[REG7]] +// CHECK: } + +// CHECK-LABEL: sil hidden [ossa] @$s15borrow_accessor9NCWrapperV16nested_subscriptAA2NCVvb : $@convention(method) (@guaranteed NCWrapper) -> @guaranteed NC { +// CHECK: bb0([[REG0]] : @guaranteed $NCWrapper): +// CHECK: [[REG1:%.*]] = copy_value [[REG0]] +// CHECK: [[REG2:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG1]] +// CHECK: [[REG4:%.*]] = begin_borrow [[REG2]] +// CHECK: [[REG5:%.*]] = integer_literal $Builtin.IntLiteral, 0 +// CHECK: [[REG6:%.*]] = metatype $@thin Int.Type +// CHECK: [[REG7:%.*]] = function_ref @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: [[REG8:%.*]] = apply [[REG7]]([[REG5]], [[REG6]]) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: [[REG9:%.*]] = function_ref @$s15borrow_accessor9NCWrapperVyAA2NCVSicib : $@convention(method) (Int, @guaranteed NCWrapper) -> @guaranteed NC +// CHECK: [[REG10:%.*]] = apply [[REG9]]([[REG8]], [[REG0]]) : $@convention(method) (Int, @guaranteed NCWrapper) -> @guaranteed NC +// CHECK: [[REG11:%.*]] = copy_value [[REG10]] +// CHECK: [[REG12:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG11]] +// CHECK: [[REG13:%.*]] = begin_borrow [[REG12]] +// CHECK: end_borrow [[REG13]] +// CHECK: destroy_value [[REG12]] +// CHECK: end_borrow [[REG4]] +// CHECK: destroy_value [[REG2]] +// CHECK: return [[REG10]] +// CHECK: } + From d54044c2311fec0343ead0265efe3f64d8da352e Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Tue, 16 Sep 2025 06:57:39 -0700 Subject: [PATCH 05/17] Update MoveOnlyObjectChecker for borrow accessors --- .../Mandatory/MoveOnlyAddressCheckerUtils.cpp | 6 + .../MoveOnlyBorrowToDestructureUtils.cpp | 11 ++ .../Mandatory/MoveOnlyObjectCheckerUtils.cpp | 9 +- .../moveonly_borrow_accessors.swift | 144 ++++++++++++++++++ 4 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 test/SILOptimizer/moveonly_borrow_accessors.swift diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp index 59592e4b99351..34f5730d0b933 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp @@ -2421,6 +2421,12 @@ bool GatherUsesVisitor::visitUse(Operand *op) { return true; } + if (operand->isBorrowAccessorResult()) { + moveChecker.diagnosticEmitter.emitObjectGuaranteedDiagnostic( + markedValue); + return true; + } + // Finally try to emit either a global or class field error... if (!moveChecker.diagnosticEmitter .emitGlobalOrClassFieldLoadedAndConsumed(markedValue)) { diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.cpp index ad7614ec6f396..cb247d16eecdd 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.cpp @@ -302,6 +302,17 @@ bool Implementation::gatherUses(SILValue value) { // copyable values as normal uses. if (auto *cvi = dyn_cast(nextUse->getUser())) { if (cvi->getOperand()->getType().isMoveOnly()) { + // Borrow accessor's callsite consists of copy_value + + // mark_unresolved_non_copyable_value instructions. + // It's diagnostics are driven by the + // mark_unresolved_non_copyable_value instruction. + // Ignore the copy_value uses to avoid an incoherent error. + if (cvi->getOperand()->isBorrowAccessorResult()) { + assert(isa( + cvi->getSingleConsumingUse()->getUser())); + continue; + } + LLVM_DEBUG(llvm::dbgs() << " Found copy value of move only " "field... looking through!\n"); for (auto *use : cvi->getUses()) diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerUtils.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerUtils.cpp index e77a473c1a683..cc4dfb3f9af3b 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerUtils.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerUtils.cpp @@ -561,7 +561,13 @@ bool MoveOnlyObjectCheckerPImpl::eraseMarkWithCopiedOperand( // %copy = copy_value %yield // %mark = mark_unresolved_noncopyable_value [no_consume_or_assign] // %copy - if (isa_and_nonnull(orig->getDefiningInstruction())) { + // Borrow accessors: + // %borrowed_return = apply + // %copy = copy_value %borrowed_return + // %mark = mark_unresolved_noncopyable_value [no_consume_or_assign] + // %copy + if (isa_and_nonnull(orig->getDefiningInstruction()) || + isa_and_nonnull(orig->getDefiningInstruction())) { if (orig->getOwnershipKind() == OwnershipKind::Guaranteed) { for (auto *use : markedInst->getConsumingUses()) { destroys.push_back(cast(use->getUser())); @@ -574,6 +580,7 @@ bool MoveOnlyObjectCheckerPImpl::eraseMarkWithCopiedOperand( return true; } } + return false; } diff --git a/test/SILOptimizer/moveonly_borrow_accessors.swift b/test/SILOptimizer/moveonly_borrow_accessors.swift new file mode 100644 index 0000000000000..77cdd7e1c5eb8 --- /dev/null +++ b/test/SILOptimizer/moveonly_borrow_accessors.swift @@ -0,0 +1,144 @@ +// RUN: %target-swift-frontend -sil-verify-all -verify -emit-sil -enable-experimental-feature BorrowAndMutateAccessors %s + +// REQUIRES: swift_feature_BorrowAndMutateAccessors + +public struct NC : ~Copyable {} + +func use(_ t: borrowing T) {} +func consume(_ t: consuming T) {} + +public struct GenSimpleNCWrapper : ~Copyable { + var _prop: T + + var prop: T { + borrow { + return _prop + } + } +} + +public struct GenNCWrapper : ~Copyable { + var _prop: T + var _w: GenSimpleNCWrapper + + public var prop: T { + borrow { + return _prop + } + } + + var nested1: T { + borrow { + return _w.prop + } + } + + var nested2: T { + borrow { + return prop + } + } + + subscript(index: Int) -> T { + borrow { + return _prop + } + } + + var nested_subscript: T { + borrow { + return self[0] + } + } + + var literal: Int { + borrow { + return 0 + } + } +} + +public struct SimpleNCWrapper: ~Copyable { + var _nc: NC + + var nc: NC { + borrow { + return _nc + } + } +} + +public struct NCWrapper: ~Copyable { + var _nc: NC + var _s: SimpleNCWrapper + + var nc: NC { + borrow { + return _nc + } + } + + var nested1: NC { + borrow { + return _s.nc + } + } + + var nested2: NC { + borrow { + return nc + } + } + + subscript(index: Int) -> NC { + borrow { + return _nc + } + } + + var nested_subscript: NC { + borrow { + return self[0] + } + } + + var literal: Int { + borrow { + return 0 + } + } +} + +func nctest() { + let w1 = GenNCWrapper(_prop: NC(), _w: GenSimpleNCWrapper(_prop: NC())) + use(w1.prop) + use(w1.nested1) + use(w1.nested2) + use(w1._w.prop) + consume(w1.prop) //expected-error{{'w1.prop' is borrowed and cannot be consumed}} expected-note{{consumed here}} + + let w2 = GenNCWrapper(_prop: NC(), _w: GenSimpleNCWrapper(_prop: NC())) + var k1 = w2.prop //expected-error{{'w2.prop' is borrowed and cannot be consumed}} expected-note{{consumed here}} + + use(k1) + k1 = NC() + use(k1) + + let w3 = GenNCWrapper(_prop: NC(), _w: GenSimpleNCWrapper(_prop: NC())) //expected-error{{'w3' used after consume}} + consume(w3) // expected-note{{consumed here}} + use(w3.prop) // expected-note{{used here}} + + let w4 = NCWrapper(_nc: NC(), _s: SimpleNCWrapper(_nc: NC())) + use(w4.nc) + use(w4.nested1) + use(w4.nested2) + use(w4._s.nc) + consume(w4.nc) // expected-error{{'w4.nc' is borrowed and cannot be consumed}} expected-note{{consumed here}} + + + let w5 = NCWrapper(_nc: NC(), _s: SimpleNCWrapper(_nc: NC())) + var k2 = w5.nc // expected-error{{'w5.nc' is borrowed and cannot be consumed}} expected-note{{consumed here}} + use(k2) + k2 = NC() + use(k2) +} From 75d040f257f99e7b08b8256ea0a362cb3f51b762 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Tue, 16 Sep 2025 15:31:40 -0700 Subject: [PATCH 06/17] Update VariableNameInferrer for borrow/mutate accessors --- lib/SIL/IR/SILValue.cpp | 2 +- lib/SILOptimizer/Utils/VariableNameUtils.cpp | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/SIL/IR/SILValue.cpp b/lib/SIL/IR/SILValue.cpp index f4d898b6b56d3..8b2231f828cb3 100644 --- a/lib/SIL/IR/SILValue.cpp +++ b/lib/SIL/IR/SILValue.cpp @@ -210,7 +210,7 @@ bool ValueBase::isBorrowAccessorResult() const { auto *apply = dyn_cast_or_null(getDefiningInstruction()); if (!apply) return false; - return apply->hasGuaranteedResult(); + return apply->hasGuaranteedResult() || apply->hasGuaranteedAddressResult(); } bool ValueBase::hasDebugTrace() const { diff --git a/lib/SILOptimizer/Utils/VariableNameUtils.cpp b/lib/SILOptimizer/Utils/VariableNameUtils.cpp index 34fac736c4bf5..2273f67f8c590 100644 --- a/lib/SILOptimizer/Utils/VariableNameUtils.cpp +++ b/lib/SILOptimizer/Utils/VariableNameUtils.cpp @@ -599,6 +599,17 @@ SILValue VariableNameInferrer::findDebugInfoProvidingValueHelper( } } + // Borrow/mutate accessor + if (searchValue->isBorrowAccessorResult()) { + if (auto fas = + FullApplySite::isa(searchValue->getDefiningInstruction())) { + if (auto selfParam = getNamePathComponentFromCallee(fas)) { + searchValue = selfParam; + continue; + } + } + } + // Addressor accessor. if (auto ptrToAddr = dyn_cast(stripAccessMarkers(searchValue))) { From 78a165d3ef233db53750456358ec643eb324d430 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Tue, 30 Sep 2025 06:20:13 -0700 Subject: [PATCH 07/17] Fix SILGenBorrowedBaseVisitor::isNonCopyableBaseBorrow --- lib/SILGen/SILGenLValue.cpp | 33 ++++++++++++++++++++++++--------- lib/SILGen/SILGenStmt.cpp | 4 +++- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/lib/SILGen/SILGenLValue.cpp b/lib/SILGen/SILGenLValue.cpp index f97b63fe18bf5..02d9b8ded63cd 100644 --- a/lib/SILGen/SILGenLValue.cpp +++ b/lib/SILGen/SILGenLValue.cpp @@ -3064,7 +3064,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenBorrowedBaseVisitor forGuaranteedReturn(forGuaranteedReturn), forGuaranteedAddressReturn(forGuaranteedAddressReturn) {} - static bool isNonCopyableBaseBorrow(SILGenFunction &SGF, Expr *e) { + static bool isNonCopyableBaseBorrow(SILGenFunction &SGF, Expr *e, + LValueOptions options) { if (auto *m = dyn_cast(e)) { // If our m is a pure noncopyable type or our base is, we need to perform // a noncopyable base borrow. @@ -3072,8 +3073,14 @@ class LLVM_LIBRARY_VISIBILITY SILGenBorrowedBaseVisitor // DISCUSSION: We can have a noncopyable member_ref_expr with a copyable // base if the noncopyable member_ref_expr is from a computed method. In // such a case, we want to ensure that we wrap things the right way. - return m->getType()->isNoncopyable() || - m->getBase()->getType()->isNoncopyable(); + if (m->getType()->isNoncopyable() || + m->getBase()->getType()->isNoncopyable()) { + return true; + } + + if (options.ForGuaranteedAddressReturn || options.ForGuaranteedReturn) { + return true; + } } if (auto *le = dyn_cast(e)) { @@ -3081,6 +3088,9 @@ class LLVM_LIBRARY_VISIBILITY SILGenBorrowedBaseVisitor if (le->getType()->isNoncopyable()) { return true; } + if (options.ForGuaranteedAddressReturn || options.ForGuaranteedReturn) { + return true; + } // Otherwise, check if the thing we're loading from is a noncopyable // param decl. e = le->getSubExpr(); @@ -3092,6 +3102,9 @@ class LLVM_LIBRARY_VISIBILITY SILGenBorrowedBaseVisitor if (de->getType()->isNoncopyable()) { return true; } + if (options.ForGuaranteedAddressReturn || options.ForGuaranteedReturn) { + return true; + } // If the decl ref refers to a parameter with an explicit ownership // modifier, it is not implicitly copyable. if (auto pd = dyn_cast(de->getDecl())) { @@ -3115,7 +3128,7 @@ class LLVM_LIBRARY_VISIBILITY SILGenBorrowedBaseVisitor /// define a visitor stub, defer back to SILGenLValue's visitRec as it is /// most-likely a non-lvalue root expression. LValue visitExpr(Expr *e, SGFAccessKind accessKind, LValueOptions options) { - assert(!isNonCopyableBaseBorrow(SGF, e) + assert(!isNonCopyableBaseBorrow(SGF, e, options) && "unexpected recursion in SILGenLValue::visitRec!"); return SGL.visitRec(e, accessKind, options, Orig); @@ -3172,8 +3185,7 @@ class LLVM_LIBRARY_VISIBILITY SILGenBorrowedBaseVisitor ManagedValue mv = SGF.emitRValueAsSingleValue(e, SGFContext::AllowImmediatePlusZero); - if (forGuaranteedReturn && mv.getValue()->getType().isMoveOnly() && - !mv.getValue()->getType().isAddress()) { + if (forGuaranteedReturn && mv.getValue()->getType().isMoveOnly()) { // SILGen eagerly generates copy_value + // mark_unresolved_non_copyable_value for ~Copyable base values. The // generated mark_unresolved_non_copyable_value instructions drive @@ -3286,11 +3298,14 @@ LValue SILGenLValue::visitRec(Expr *e, SGFAccessKind accessKind, // a `borrow x` operator, the operator is used on the base here), we want to // apply the lvalue within a formal access to the original value instead of // an actual loaded copy. - if (SILGenBorrowedBaseVisitor::isNonCopyableBaseBorrow(SGF, e) || - options.ForGuaranteedReturn) { + if (SILGenBorrowedBaseVisitor::isNonCopyableBaseBorrow(SGF, e, options)) { SILGenBorrowedBaseVisitor visitor(*this, SGF, orig, - options.ForGuaranteedReturn); + options.ForGuaranteedReturn, + options.ForGuaranteedAddressReturn); auto accessKind = SGFAccessKind::BorrowedObjectRead; + if (options.ForGuaranteedAddressReturn) { + accessKind = SGFAccessKind::BorrowedAddressRead; + } assert(!e->getType()->is() && "maybe need SGFAccessKind::BorrowedAddressRead ?"); return visitor.visit(e, accessKind, options); diff --git a/lib/SILGen/SILGenStmt.cpp b/lib/SILGen/SILGenStmt.cpp index 051decadd67f7..e8a1cc8b42839 100644 --- a/lib/SILGen/SILGenStmt.cpp +++ b/lib/SILGen/SILGenStmt.cpp @@ -773,7 +773,9 @@ void SILGenFunction::emitReturnExpr(SILLocation branchLoc, F.getConventions().hasGuaranteedResult() ? SGFAccessKind::BorrowedObjectRead : SGFAccessKind::BorrowedAddressRead, - options.forGuaranteedReturn(true)); + F.getConventions().hasGuaranteedResult() + ? options.forGuaranteedReturn(true) + : options.forGuaranteedAddressReturn(true)); auto result = tryEmitProjectedLValue(ret, std::move(lvalue), TSanKind::None); if (!result) { From 36f2955d39c0849e20aaca269fc24b57a6024a89 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Wed, 10 Sep 2025 16:09:12 -0700 Subject: [PATCH 08/17] IRGen support for GuaranteedAddress result convention --- lib/IRGen/CallEmission.h | 1 + lib/IRGen/GenCall.cpp | 36 +++ lib/IRGen/GenCall.h | 4 + lib/IRGen/IRGenSIL.cpp | 21 +- test/IRGen/borrow_accessor.swift | 393 +++++++++++++++++++++++++ test/IRGen/borrow_accessor_large.swift | 256 ++++++++++++++++ 6 files changed, 710 insertions(+), 1 deletion(-) create mode 100644 test/IRGen/borrow_accessor.swift create mode 100644 test/IRGen/borrow_accessor_large.swift diff --git a/lib/IRGen/CallEmission.h b/lib/IRGen/CallEmission.h index 89ac2baa265f7..ef18221d33c71 100644 --- a/lib/IRGen/CallEmission.h +++ b/lib/IRGen/CallEmission.h @@ -103,6 +103,7 @@ class CallEmission { virtual void emitCallToUnmappedExplosion(llvm::CallBase *call, Explosion &out) = 0; void emitYieldsToExplosion(Explosion &out); + void emitGuaranteedAddressToExplosion(Explosion &out); void setKeyPathAccessorArguments(Explosion &in, bool isOutlined, Explosion &out); virtual FunctionPointer getCalleeFunctionPointer() = 0; diff --git a/lib/IRGen/GenCall.cpp b/lib/IRGen/GenCall.cpp index 6e622da2773d7..395388b9ff1d6 100644 --- a/lib/IRGen/GenCall.cpp +++ b/lib/IRGen/GenCall.cpp @@ -591,6 +591,10 @@ namespace { /// function type. void expandCoroutineContinuationType(); + /// Initializes the result type for functions with @guaranteed_addr return + /// convention. + void expandGuaranteedAddressResult(); + // Expand the components for the async continuation entrypoint of the // function type (the function to be called on returning). void expandAsyncReturnType(); @@ -694,6 +698,10 @@ void SignatureExpansion::expandResult( auto fnConv = getSILFuncConventions(); + if (fnConv.hasGuaranteedAddressResult()) { + return expandGuaranteedAddressResult(); + } + // Disable the use of sret if we have multiple indirect results. if (fnConv.getNumIndirectSILResults() > 1) CanUseSRet = false; @@ -911,6 +919,11 @@ void SignatureExpansion::expandCoroutineContinuationParameters() { } } +void SignatureExpansion::expandGuaranteedAddressResult() { + CanUseSRet = false; + ResultIRType = IGM.PtrTy; +} + void SignatureExpansion::addAsyncParameters() { // using TaskContinuationFunction = // SWIFT_CC(swift) @@ -3933,6 +3946,11 @@ void CallEmission::emitYieldsToExplosion(Explosion &out) { } } +void CallEmission::emitGuaranteedAddressToExplosion(Explosion &out) { + auto call = emitCallSite(); + out.add(call); +} + /// Emit the result of this call to an explosion. void CallEmission::emitToExplosion(Explosion &out, bool isOutlined) { assert(state == State::Emitting); @@ -3948,6 +3966,14 @@ void CallEmission::emitToExplosion(Explosion &out, bool isOutlined) { SILFunctionConventions fnConv(getCallee().getSubstFunctionType(), IGF.getSILModule()); + + if (fnConv.hasGuaranteedAddressResult()) { + assert(LastArgWritten == 0 && + "@guaranteed_addr along with indirect result?"); + emitGuaranteedAddressToExplosion(out); + return; + } + SILType substResultType = fnConv.getSILResultType(IGF.IGM.getMaximalTypeExpansionContext()); @@ -7001,6 +7027,16 @@ void irgen::emitYieldOnceCoroutineResult(IRGenFunction &IGF, Explosion &result, } } +void irgen::emitGuaranteedAddressResult(IRGenFunction &IGF, Explosion &result, + SILType funcResultType, + SILType returnResultType) { + assert(funcResultType == returnResultType); + assert(funcResultType.isAddress()); + auto &Builder = IGF.Builder; + Builder.CreateRet(result.claimNext()); + assert(result.empty()); +} + FunctionPointer IRGenFunction::getFunctionPointerForResumeIntrinsic(llvm::Value *resume) { auto *fnTy = llvm::FunctionType::get( diff --git a/lib/IRGen/GenCall.h b/lib/IRGen/GenCall.h index 66194a70bd070..ec2cd8a5c1b40 100644 --- a/lib/IRGen/GenCall.h +++ b/lib/IRGen/GenCall.h @@ -281,6 +281,10 @@ namespace irgen { void emitYieldOnceCoroutineResult(IRGenFunction &IGF, Explosion &result, SILType funcResultType, SILType returnResultType); + void emitGuaranteedAddressResult(IRGenFunction &IGF, Explosion &result, + SILType funcResultType, + SILType returnResultType); + Address emitAutoDiffCreateLinearMapContextWithType( IRGenFunction &IGF, llvm::Value *topLevelSubcontextMetatype); diff --git a/lib/IRGen/IRGenSIL.cpp b/lib/IRGen/IRGenSIL.cpp index adc1a267e061a..6b18afd4e0398 100644 --- a/lib/IRGen/IRGenSIL.cpp +++ b/lib/IRGen/IRGenSIL.cpp @@ -2032,6 +2032,11 @@ static ArrayRef emitEntryPointIndirectReturn( SILFunctionConventions fnConv(funcTy, IGF.getSILModule()); SILType directResultType = IGF.CurSILFn->mapTypeIntoContext( fnConv.getSILResultType(IGF.IGM.getMaximalTypeExpansionContext())); + + if (fnConv.hasGuaranteedAddressResult()) { + return entry->getArguments(); + } + if (requiresIndirectResult(directResultType)) { auto ¶mTI = IGF.IGM.getTypeInfo(directResultType); auto &retTI = @@ -3934,7 +3939,11 @@ void IRGenSILFunction::visitFullApplySite(FullApplySite site) { // For a simple apply, just bind the apply result to the result of the call. if (auto apply = dyn_cast(i)) { - setLoweredExplosion(apply, result); + if (apply->hasGuaranteedAddressResult()) { + setCorrespondingLoweredValues(apply->getResults(), result); + } else { + setLoweredExplosion(apply, result); + } emission->end(); // For begin_apply, we have to destructure the call. @@ -4451,6 +4460,16 @@ static void emitReturnInst(IRGenSILFunction &IGF, return; } + if (conv.hasGuaranteedAddressResult()) { + assert(IGF.CurSILFn->getLoweredFunctionType()->getLanguage() == + SILFunctionLanguage::Swift); + auto funcResultType = IGF.CurSILFn->mapTypeIntoContext( + conv.getSILResultType(IGF.IGM.getMaximalTypeExpansionContext())); + + emitGuaranteedAddressResult(IGF, result, funcResultType, resultTy); + return; + } + // The invariant on the out-parameter is that it's always zeroed, so // there's nothing to do here. diff --git a/test/IRGen/borrow_accessor.swift b/test/IRGen/borrow_accessor.swift new file mode 100644 index 0000000000000..b329b8e065101 --- /dev/null +++ b/test/IRGen/borrow_accessor.swift @@ -0,0 +1,393 @@ +// RUN:%target-swift-frontend -emit-irgen %s -verify -enable-experimental-feature BorrowAndMutateAccessors | %FileCheck %s + +// REQUIRES: swift_feature_BorrowAndMutateAccessors +// REQUIRES: OS=macosx + +public class Klass { + var id: Int = 0 +} + +func getKlass() -> Klass { + return Klass() +} + +@inline(never) +func use(_ k: Klass) { + print(k.id) +} + +@inline(never) +func use(_ tuple: (Klass, Klass, Klass, Klass, Klass, Klass, Klass, Klass)) { + print(tuple.4.id) +} + +public struct NC : ~Copyable { + var id: Int = 0 +} + +@inline(never) +func use(_ t: borrowing NC) { + print(t.id) +} + +public struct S { + var _k: Klass + + var k: Klass { + borrow { + return _k + } + } +} + +public struct Wrapper { + var _k: Klass + var _s: S + + var s: S { + borrow { + return _s + } + } + + var k: Klass { + borrow { + return _k + } + } + + var nested1: Klass { + borrow { + return _s.k + } + } + + var nested2: Klass { + borrow { + return k + } + } + + subscript(index: Int) -> Klass { + borrow { + return _k + } + } + + var nested_subscript: Klass { + borrow { + return self[0] + } + } +} + +public struct SimpleWrapper { + var _prop: T + + var prop: T { + borrow { + return _prop + } + } +} + +public struct GenWrapper { + var _prop: T + var _s: SimpleWrapper + var _k: Klass + + public var prop: T { + borrow { + return _prop + } + } + + var nested1: T { + borrow { + return _s.prop + } + } + + var nested2: T { + borrow { + return prop + } + } + + var k: Klass { + borrow { + return _k + } + } + + subscript(index: Int) -> T { + borrow { + return _prop + } + } + + var nested_subscript: T { + borrow { + return self[0] + } + } +} + +public struct SimpleNCWrapper : ~Copyable { + var _prop: T + + var prop: T { + borrow { + return _prop + } + } +} + +public struct GenNCWrapper : ~Copyable { + var _prop: T + var _s: SimpleNCWrapper + + public var prop: T { + borrow { + return _prop + } + } + + var nested1: T { + borrow { + return _s.prop + } + } + + var nested2: T { + borrow { + return prop + } + } + + subscript(index: Int) -> T { + borrow { + return _prop + } + } + + var nested_subscript: T { + borrow { + return self[0] + } + } +} + +public struct NCS: ~Copyable { + var _nc: NC + + var nc: NC { + borrow { + return _nc + } + } +} + +public struct NCWrapper: ~Copyable { + var _nc: NC + var _s: NCS + + var nc: NC { + borrow { + return _nc + } + } + var nested1: NC { + borrow { + return _s.nc + } + } + + var nested2: NC { + borrow { + return nc + } + } + + subscript(index: Int) -> NC { + borrow { + return _nc + } + } + + var nested_subscript: NC { + borrow { + return self[0] + } + } +} + +public struct LargeStruct { + var _k: Klass + var _t: (Int, Int, Int, Int, Int, Int, Int, Int) + var _tuple: (Klass, Klass, Klass, Klass, Klass, Klass, Klass, Klass) + + var borrowKlass: Klass { + borrow { + return _k + } + } + var borrowTuple: (Klass, Klass, Klass, Klass, Klass, Klass, Klass, Klass) { + borrow { + return _tuple + } + } +} + +func test() { + let w1 = Wrapper(_k: Klass(), _s: S(_k: Klass())) + use(w1.k) + use(w1.s.k) + use(w1.nested1) + use(w1.nested2) + + let w2 = GenWrapper(_prop: Klass(), _s: SimpleWrapper(_prop: Klass()), _k: Klass()) + use(w2.prop) + use(w2.nested1) + use(w2.nested2) + + let w3 = GenNCWrapper(_prop: NC(), _s: SimpleNCWrapper(_prop: NC())) + use(w3.prop) + use(w3.nested1) + use(w3.nested2) + + let l = LargeStruct(_k: Klass(), _t: (1,2,3,4,5,6,7,8), _tuple: (Klass(), Klass(), Klass(), Klass(), Klass(), Klass(), Klass(), Klass())) + use(l.borrowKlass) + use(l.borrowTuple) +} + +// IRGen explodes the struct parameter and returns the specified field +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperV1kAA5KlassCvb"(ptr [[REG0:%.*]], ptr [[REG1:%.*]]) #0 { +// CHECK: entry: +// CHECK: ret ptr [[REG0]] +// CHECK: } + +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperV7nested1AA5KlassCvb"(ptr [[REG0:%.*]], ptr [[REG1:%.*]]) #0 { +// CHECK: entry: +// CHECK: [[REG2:%.*]] = call swiftcc ptr @"$s15borrow_accessor1SV1kAA5KlassCvb"(ptr [[REG1]]) +// CHECK: ret ptr [[REG2]] +// CHECK: } + +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperV7nested2AA5KlassCvb"(ptr [[REG0:%.*]], ptr [[REG1:%.*]]) #0 { +// CHECK: entry: +// CHECK: [[REG2:%.*]] = call swiftcc ptr @"$s15borrow_accessor7WrapperV1kAA5KlassCvb"(ptr [[REG0]], ptr [[REG1]]) +// CHECK: ret ptr [[REG2]] +// CHECK: } + +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperVyAA5KlassCSicib"(i64 [[REG0:%.*]], ptr [[REG1:%.*]], ptr [[REG2:%.*]]) #0 { +// CHECK: entry: +// CHECK: ret ptr [[REG1]] +// CHECK: } + +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperV16nested_subscriptAA5KlassCvb"(ptr [[REG0:%.*]], ptr [[REG1:%.*]]) #0 { +// CHECK: entry: +// CHECK: [[REG2:%.*]] = call swiftcc ptr @"$s15borrow_accessor7WrapperVyAA5KlassCSicib"(i64 0, ptr [[REG0]], ptr [[REG1]]) +// CHECK: ret ptr [[REG2]] +// CHECK: } + +// CHECK: define swiftcc ptr @"$s15borrow_accessor10GenWrapperV4propxvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: entry: +// CHECK: ret ptr [[REG0]] +// CHECK: } + +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperV7nested1xvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: entry: +// CHECK: [[REG2:%.*]] = getelementptr inbounds i32, ptr %"GenWrapper", i64 7 +// CHECK: [[REG3:%.*]] = load i32, ptr [[REG2]], align 8 +// CHECK: [[REG4:%.*]] = getelementptr inbounds i8, ptr [[REG0]], i32 [[REG3]] +// CHECK: [[REG5:%.*]] = call swiftcc %swift.metadata_response @"$s15borrow_accessor13SimpleWrapperVMa"(i64 0, ptr %T) #17 +// CHECK: [[REG6:%.*]] = extractvalue %swift.metadata_response [[REG5]], 0 +// CHECK: [[REG7:%.*]] = call swiftcc ptr @"$s15borrow_accessor13SimpleWrapperV4propxvb"(ptr [[REG6]], ptr noalias swiftself [[REG4]]) +// CHECK: ret ptr [[REG7]] +// CHECK: } + +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperV7nested2xvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: entry: +// CHECK: [[REG2:%.*]] = call swiftcc ptr @"$s15borrow_accessor10GenWrapperV4propxvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0]]) +// CHECK: ret ptr [[REG2]] +// CHECK: } + +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperV1kAA5KlassCvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: entry: +// CHECK: [[REG2:%.*]] = getelementptr inbounds i32, ptr %"GenWrapper", i64 8 +// CHECK: [[REG3:%.*]] = load i32, ptr [[REG2]], align 8 +// CHECK: [[REG4:%.*]] = getelementptr inbounds i8, ptr [[REG0]], i32 [[REG3]] +// CHECK: ret ptr [[REG4]] +// CHECK: } + +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperVyxSicib"(i64 [[REG0:%.*]], ptr %"GenWrapper", ptr noalias swiftself [[REG1:%.*]]) #0 { +// CHECK: entry: +// CHECK: ret ptr [[REG1]] +// CHECK: } + +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperV16nested_subscriptxvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: entry: +// CHECK: [[REG2:%.*]] = call swiftcc ptr @"$s15borrow_accessor10GenWrapperVyxSicib"(i64 0, ptr %"GenWrapper", ptr noalias swiftself [[REG0]]) +// CHECK: ret ptr [[REG2]] +// CHECK: } + +// CHECK: define swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlE4propxvb"(ptr %"GenNCWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: entry: +// CHECK: ret ptr [[REG0]] +// CHECK: } + +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlE7nested1xvb"(ptr %"GenNCWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: entry: +// CHECK: [[REG2:%.*]] = getelementptr inbounds i32, ptr %"GenNCWrapper", i64 7 +// CHECK: [[REG3:%.*]] = load i32, ptr [[REG2]], align 8 +// CHECK: [[REG4:%.*]] = getelementptr inbounds i8, ptr [[REG0]], i32 [[REG3]] +// CHECK: [[REG5:%.*]] = call swiftcc %swift.metadata_response @"$s15borrow_accessor15SimpleNCWrapperVMa"(i64 0, ptr %T) #17 +// CHECK: [[REG6:%.*]] = extractvalue %swift.metadata_response [[REG5]], 0 +// CHECK: [[REG7:%.*]] = call swiftcc ptr @"$s15borrow_accessor15SimpleNCWrapperVAARi_zrlE4propxvb"(ptr [[REG6]], ptr noalias swiftself [[REG4]]) +// CHECK: ret ptr [[REG7]] +// CHECK: } + +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlE7nested2xvb"(ptr %"GenNCWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: entry: +// CHECK: [[REG2:%.*]] = call swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlE4propxvb"(ptr %"GenNCWrapper", ptr noalias swiftself [[REG0]]) +// CHECK: ret ptr [[REG2]] +// CHECK: } + +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlEyxSicib"(i64 [[REG0:%.*]], ptr %"GenNCWrapper", ptr noalias swiftself [[REG1:%.*]]) #0 { +// CHECK: entry: +// CHECK: ret ptr [[REG1]] +// CHECK: } + +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlE16nested_subscriptxvb"(ptr %"GenNCWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: entry: +// CHECK: [[REG2:%.*]] = call swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlEyxSicib"(i64 0, ptr %"GenNCWrapper", ptr noalias swiftself [[REG0]]) +// CHECK: ret ptr [[REG2]] +// CHECK: } + +// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperV2ncAA2NCVvb"(i64 [[REG0:%.*]], i64 [[REG1:%.*]]) #0 { +// CHECK: entry: +// CHECK: ret i64 [[REG0]] +// CHECK: } + +// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperV7nested1AA2NCVvb"(i64 [[REG0:%.*]], i64 [[REG1:%.*]]) #0 { +// CHECK: entry: +// CHECK: [[REG2:%.*]] = call swiftcc i64 @"$s15borrow_accessor3NCSV2ncAA2NCVvb"(i64 [[REG1]]) +// CHECK: ret i64 [[REG2]] +// CHECK: } + +// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperV7nested2AA2NCVvb"(i64 [[REG0:%.*]], i64 [[REG1:%.*]]) #0 { +// CHECK: entry: +// CHECK: [[REG2:%.*]] = call swiftcc i64 @"$s15borrow_accessor9NCWrapperV2ncAA2NCVvb"(i64 [[REG0]], i64 [[REG1]]) +// CHECK: ret i64 [[REG2]] +// CHECK: } + +// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperVyAA2NCVSicib"(i64 [[REG0]], i64 [[REG1]], i64 [[REG2]]) #0 { +// CHECK: entry: +// CHECK: ret i64 [[REG1]] +// CHECK: } + +// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperV16nested_subscriptAA2NCVvb"(i64 [[REG0:%.*]], i64 [[REG1:%.*]]) #0 { +// CHECK: entry: +// CHECK: [[REG2:%.*]] = call swiftcc i64 @"$s15borrow_accessor9NCWrapperVyAA2NCVSicib"(i64 0, i64 [[REG0]], i64 [[REG1]]) +// CHECK: ret i64 [[REG2]] +// CHECK: } diff --git a/test/IRGen/borrow_accessor_large.swift b/test/IRGen/borrow_accessor_large.swift new file mode 100644 index 0000000000000..a4daa90343d5d --- /dev/null +++ b/test/IRGen/borrow_accessor_large.swift @@ -0,0 +1,256 @@ +// RUN: %target-swift-frontend -c %s -Xllvm -sil-print-after=loadable-address -enable-experimental-feature BorrowAndMutateAccessors 2>&1 | %FileCheck %s + +// REQUIRES: swift_feature_BorrowAndMutateAccessors +// REQUIRES: OS=macosx + +public class Klass { + var id: Int = 0 +} + +@inline(never) +func use(_ k: Klass) { + print(k.id) +} + +@inline(never) +func use(_ s: SmallStruct) { + print(s.id) +} + +@inline(never) +func use(_ tuple: (Klass, Klass, Klass, Klass, Klass, Klass, Klass, Klass)) { + print(tuple.4.id) +} + +@inline(never) +func use(_ ls: LargeProp) { + use(ls._largeTuple) +} + +@inline(never) +func use(_ k: borrowing NC) { + print(k.id) +} + +@inline(never) +func use(_ ls: borrowing LargeNCProp) { + use(ls.nc1) + use(ls.nc2) + use(ls.nc3) + use(ls.nc4) + use(ls.nc5) + use(ls.nc6) + use(ls.nc7) + use(ls.nc8) +} + +public struct LargeProp { + var _largeTuple = (Klass(), Klass(), Klass(), Klass(), Klass(), Klass(), Klass(), Klass()) +} + +public struct LargeNCProp : ~Copyable { + var nc1 = NC() + var nc2 = NC() + var nc3 = NC() + var nc4 = NC() + var nc5 = NC() + var nc6 = NC() + var nc7 = NC() + var nc8 = NC() +} + +public struct SmallStruct { + var id = 0 +} + +public struct LargeStruct { + var _k = Klass() + var _t = (1, 2, 3, 4, 5, 6, 7, 8) + var _largeTuple = (Klass(), Klass(), Klass(), Klass(), Klass(), Klass(), Klass(), Klass()) + var _largeProp = LargeProp() + var _smallStruct = SmallStruct() + + var borrowKlass: Klass { + borrow { + return _k + } + } + var borrowSmallStruct: SmallStruct { + borrow { + return _smallStruct + } + } + var largePropBorrow: LargeProp { + borrow { + return _largeProp + } + } + var largeTupleBorrow: (Klass, Klass, Klass, Klass, Klass, Klass, Klass, Klass) { + borrow { + return _largeTuple + } + } +} + +func test() { + let l = LargeStruct() + use(l.borrowKlass) + use(l.borrowSmallStruct) + let k = l.borrowKlass + use(k) + use(l.largeTupleBorrow) + use(l.largePropBorrow) + use(k) +} + +public struct NC : ~Copyable { + var id: Int = 0 +} + +public struct NCLargeStruct : ~Copyable { + var _k = NC() + var _largeProp = LargeNCProp() + + var borrowNC: NC { + borrow { + return _k + } + } + var largePropBorrow: LargeNCProp { + borrow { + return _largeProp + } + } +} + +func nctest() { + let l = NCLargeStruct() + use(l.borrowNC) + use(l.largePropBorrow) +} + +// CHECK: sil hidden @$s21borrow_accessor_large11LargeStructV0A5KlassAA0F0Cvb : $@convention(method) (@in_guaranteed LargeStruct) -> @guaranteed Klass { +// CHECK: bb0([[REG0:%.*]] : $*LargeStruct): +// CHECK: [[REG2:%.*]] = struct_element_addr [[REG0]], #LargeStruct._k +// CHECK: [[REG3:%.*]] = load [[REG2]] +// CHECK: return [[REG3]] +// CHECK: } + +// IRGen result type is PtrTy because we are returning a class reference +// CHECK-IRGEN: define hidden swiftcc ptr @"$s21borrow_accessor_large11LargeStructV0A5KlassAA0F0Cvb"(ptr noalias nocapture swiftself dereferenceable(208) [[REG0]]) #0 { +// CHECK-IRGEN: entry: +// CHECK-IRGEN: %._k = getelementptr inbounds %T21borrow_accessor_large11LargeStructV, ptr [[REG0]], i32 0, i32 0 +// CHECK-IRGEN: [[REG1:%.*]] = load ptr, ptr %._k, align 8 +// CHECK-IRGEN: ret ptr [[REG1]] +// CHECK-IRGEN: } + +// CHECK: sil hidden @$s21borrow_accessor_large11LargeStructV0a5SmallE0AA0fE0Vvb : $@convention(method) (@in_guaranteed LargeStruct) -> SmallStruct { +// CHECK: bb0([[REG0:%.*]] : $*LargeStruct): +// CHECK: [[REG2:%.*]] = struct_element_addr [[REG0]], #LargeStruct._smallStruct +// CHECK: [[REG3:%.*]] = load [[REG2]] +// CHECK: return [[REG3]] +// CHECK: } + +// CHECK-IRGEN: define hidden swiftcc i64 @"$s21borrow_accessor_large11LargeStructV0a5SmallE0AA0fE0Vvb"(ptr noalias nocapture swiftself dereferenceable(208) [[REG0]]) #0 { +// CHECK-IRGEN: entry: +// CHECK-IRGEN: %._smallStruct = getelementptr inbounds %T21borrow_accessor_large11LargeStructV, ptr [[REG0]], i32 0, i32 4 +// CHECK-IRGEN: %._smallStruct.id = getelementptr inbounds %T21borrow_accessor_large11SmallStructV, ptr %._smallStruct, i32 0, i32 0 +// CHECK-IRGEN: %._smallStruct.id._value = getelementptr inbounds %TSi, ptr %._smallStruct.id, i32 0, i32 0 +// CHECK-IRGEN: [[REG1:%.*]] = load i64, ptr %._smallStruct.id._value, align 8 +// CHECK-IRGEN: ret i64 [[REG1]] +// CHECK-IRGEN: } + +// LoadableByAddress transforms to an indirect result +// CHECK: sil hidden @$s21borrow_accessor_large11LargeStructV0C10PropBorrowAA0dF0Vvb : $@convention(method) (@in_guaranteed LargeStruct) -> @out LargeProp { +// CHECK: bb0([[REG0:%.*]] : $*LargeProp, [[REG1:%.*]] : $*LargeStruct): +// CHECK: [[REG3:%.*]] = struct_element_addr [[REG1]], #LargeStruct._largeProp +// CHECK: copy_addr [take] [[REG3]] to [init] [[REG0]] +// CHECK: [[REG5:%.*]] = tuple () +// CHECK: return [[REG5]] +// CHECK: } + +// CHECK-IRGEN: define hidden swiftcc void @"$s21borrow_accessor_large11LargeStructV0C10PropBorrowAA0dF0Vvb"(ptr noalias nocapture sret(%T21borrow_accessor_large9LargePropV) [[REG0]], ptr noalias nocapture swiftself dereferenceable(208) [[REG1]]) #0 { +// CHECK-IRGEN: entry: +// CHECK-IRGEN: %._largeProp = getelementptr inbounds %T21borrow_accessor_large11LargeStructV, ptr [[REG1]], i32 0, i32 3 +// CHECK-IRGEN: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[REG0]], ptr align 8 %._largeProp, i64 64, i1 false) +// CHECK-IRGEN: ret void +// CHECK-IRGEN: } + +// LoadableByAddress does not transform large tuples +// CHECK: sil hidden @$s21borrow_accessor_large11LargeStructV0C11TupleBorrowAA5KlassC_A7Ftvb : $@convention(method) (@in_guaranteed LargeStruct) -> @guaranteed (Klass, Klass, Klass, Klass, Klass, Klass, Klass, Klass) { +// CHECK: bb0([[REG0:%.*]] : $*LargeStruct): +// CHECK: [[REG2:%.*]] = struct_element_addr [[REG0]], #LargeStruct._largeTuple +// CHECK: [[REG3:%.*]] = load [[REG2]] +// CHECK: return [[REG3]] +// CHECK: } + +// CHECK-IRGEN: define hidden swiftcc void @"$s21borrow_accessor_large11LargeStructV0C11TupleBorrowAA5KlassC_A7Ftvb"(ptr noalias nocapture sret(<{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>) [[REG0]], ptr noalias nocapture swiftself dereferenceable(208) [[REG1]]) #0 { +// CHECK-IRGEN: entry: +// CHECK-IRGEN: %._largeTuple = getelementptr inbounds %T21borrow_accessor_large11LargeStructV, ptr [[REG1]], i32 0, i32 2 +// CHECK-IRGEN: %._largeTuple.elt = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr %._largeTuple, i32 0, i32 0 +// CHECK-IRGEN: [[REG2:%.*]] = load ptr, ptr %._largeTuple.elt, align 8 +// CHECK-IRGEN: %._largeTuple.elt1 = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr %._largeTuple, i32 0, i32 1 +// CHECK-IRGEN: [[REG3:%.*]] = load ptr, ptr %._largeTuple.elt1, align 8 +// CHECK-IRGEN: %._largeTuple.elt2 = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr %._largeTuple, i32 0, i32 2 +// CHECK-IRGEN: [[REG4:%.*]] = load ptr, ptr %._largeTuple.elt2, align 8 +// CHECK-IRGEN: %._largeTuple.elt3 = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr %._largeTuple, i32 0, i32 3 +// CHECK-IRGEN: [[REG5:%.*]] = load ptr, ptr %._largeTuple.elt3, align 8 +// CHECK-IRGEN: %._largeTuple.elt4 = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr %._largeTuple, i32 0, i32 4 +// CHECK-IRGEN: [[REG6:%.*]] = load ptr, ptr %._largeTuple.elt4, align 8 +// CHECK-IRGEN: %._largeTuple.elt5 = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr %._largeTuple, i32 0, i32 5 +// CHECK-IRGEN: [[REG7:%.*]] = load ptr, ptr %._largeTuple.elt5, align 8 +// CHECK-IRGEN: %._largeTuple.elt6 = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr %._largeTuple, i32 0, i32 6 +// CHECK-IRGEN: [[REG8:%.*]] = load ptr, ptr %._largeTuple.elt6, align 8 +// CHECK-IRGEN: %._largeTuple.elt7 = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr %._largeTuple, i32 0, i32 7 +// CHECK-IRGEN: [[REG9:%.*]] = load ptr, ptr %._largeTuple.elt7, align 8 +// CHECK-IRGEN: %.elt = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr [[REG0]], i32 0, i32 0 +// CHECK-IRGEN: store ptr [[REG2]], ptr %.elt, align 8 +// CHECK-IRGEN: %.elt8 = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr [[REG0]], i32 0, i32 1 +// CHECK-IRGEN: store ptr [[REG3]], ptr %.elt8, align 8 +// CHECK-IRGEN: %.elt9 = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr [[REG0]], i32 0, i32 2 +// CHECK-IRGEN: store ptr [[REG4]], ptr %.elt9, align 8 +// CHECK-IRGEN: %.elt10 = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr [[REG0]], i32 0, i32 3 +// CHECK-IRGEN: store ptr [[REG5]], ptr %.elt10, align 8 +// CHECK-IRGEN: %.elt11 = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr [[REG0]], i32 0, i32 4 +// CHECK-IRGEN: store ptr [[REG6]], ptr %.elt11, align 8 +// CHECK-IRGEN: %.elt12 = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr [[REG0]], i32 0, i32 5 +// CHECK-IRGEN: store ptr [[REG7]], ptr %.elt12, align 8 +// CHECK-IRGEN: %.elt13 = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr [[REG0]], i32 0, i32 6 +// CHECK-IRGEN: store ptr [[REG8]], ptr %.elt13, align 8 +// CHECK-IRGEN: %.elt14 = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr [[REG0]], i32 0, i32 7 +// CHECK-IRGEN: store ptr [[REG9]], ptr %.elt14, align 8 +// CHECK-IRGEN: ret void +// CHECK-IRGEN: } + +// CHECK: sil hidden @$s21borrow_accessor_large13NCLargeStructV0A2NCAA0F0Vvb : $@convention(method) (@in_guaranteed NCLargeStruct) -> @guaranteed NC { +// CHECK: bb0([[REG0:%.*]] : $*NCLargeStruct): +// CHECK: [[REG2:%.*]] = struct_element_addr [[REG0]], #NCLargeStruct._k +// CHECK: [[REG3:%.*]] = load [[REG2]] +// CHECK: return [[REG3]] +// CHECK: } + +// CHECK-IRGEN: define hidden swiftcc i64 @"$s21borrow_accessor_large13NCLargeStructV0A2NCAA0F0Vvb"(ptr noalias nocapture swiftself dereferenceable(72) [[REG0]]) #0 { +// CHECK-IRGEN: entry: +// CHECK-IRGEN: %._k = getelementptr inbounds %T21borrow_accessor_large13NCLargeStructV, ptr [[REG0]], i32 0, i32 0 +// CHECK-IRGEN: %._k.id = getelementptr inbounds %T21borrow_accessor_large2NCV, ptr %._k, i32 0, i32 0 +// CHECK-IRGEN: %._k.id._value = getelementptr inbounds %TSi, ptr %._k.id, i32 0, i32 0 +// CHECK-IRGEN: [[REG1:%.*]] = load i64, ptr %._k.id._value, align 8 +// CHECK-IRGEN: ret i64 [[REG1]] +// CHECK-IRGEN: } + +// Note: ~Copyable types are returned indirectly via copy_addy which in turn generates a memcopy +// CHECK: sil hidden @$s21borrow_accessor_large13NCLargeStructV0C10PropBorrowAA11LargeNCPropVvb : $@convention(method) (@in_guaranteed NCLargeStruct) -> @out LargeNCProp { +// CHECK: bb0([[REG0:%.*]] : $*LargeNCProp, [[REG1:%.*]] : $*NCLargeStruct): +// CHECK: [[REG3:%.*]] = struct_element_addr [[REG1]], #NCLargeStruct._largeProp +// CHECK: copy_addr [take] [[REG3]] to [init] [[REG0]] +// CHECK: [[REG5:%.*]] = tuple () +// CHECK: return [[REG5]] +// CHECK: } + +// CHECK-IRGEN: define hidden swiftcc void @"$s21borrow_accessor_large13NCLargeStructV0C10PropBorrowAA11LargeNCPropVvb"(ptr noalias nocapture sret(%T21borrow_accessor_large11LargeNCPropV) [[REG0]], ptr noalias nocapture swiftself dereferenceable(72) [[REG1]]) #0 { +// CHECK-IRGEN: entry: +// CHECK-IRGEN: %._largeProp = getelementptr inbounds %T21borrow_accessor_large13NCLargeStructV, ptr [[REG1]], i32 0, i32 1 +// CHECK-IRGEN: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[REG0]], ptr align 8 %._largeProp, i64 64, i1 false) +// CHECK-IRGEN: ret void +// CHECK-IRGEN: } + From 5e3ff1ea39aae05f52204bb7ff7af4087ad2c18f Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Thu, 18 Sep 2025 17:24:26 -0700 Subject: [PATCH 09/17] [NFC] Add hasGuaranteedResult api --- include/swift/AST/Types.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/swift/AST/Types.h b/include/swift/AST/Types.h index 6b257866dc283..f64b878ca12a0 100644 --- a/include/swift/AST/Types.h +++ b/include/swift/AST/Types.h @@ -5410,6 +5410,13 @@ class SILFunctionType final return hasErrorResult() && getErrorResult().isFormalIndirect(); } + bool hasGuaranteedResult() const { + if (getNumResults() != 1) { + return false; + } + return getResults()[0].isGuaranteedResult(); + } + bool hasGuaranteedAddressResult() const { if (getNumResults() != 1) { return false; From 65a59a5847067755d1b2df114fc51a5920952987 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Fri, 19 Sep 2025 15:57:38 -0700 Subject: [PATCH 10/17] Update borrow accessor diagnostics --- lib/SILGen/SILGenLValue.cpp | 15 ++++++++---- test/SILGen/borrow_accessor.swift | 39 +++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/lib/SILGen/SILGenLValue.cpp b/lib/SILGen/SILGenLValue.cpp index 02d9b8ded63cd..4d99d47ebac2c 100644 --- a/lib/SILGen/SILGenLValue.cpp +++ b/lib/SILGen/SILGenLValue.cpp @@ -5707,15 +5707,20 @@ SILGenFunction::tryEmitProjectedLValue(SILLocation loc, LValue &&src, TSanKind tsanKind) { assert(src.getAccessKind() == SGFAccessKind::BorrowedAddressRead || src.getAccessKind() == SGFAccessKind::BorrowedObjectRead); + + for (auto component = src.begin(); component != src.end(); component++) { + if (component->get()->getKind() != PathComponent::BorrowMutateKind && + component->get()->getKind() != PathComponent::StructElementKind && + component->get()->getKind() != PathComponent::TupleElementKind && + component->get()->getKind() != PathComponent::ValueKind) { + return std::nullopt; + } + } + ManagedValue base; PathComponent &&component = drillToLastComponent(loc, std::move(src), base, tsanKind); - if (component.getKind() != PathComponent::BorrowMutateKind && - component.getKind() != PathComponent::StructElementKind && - component.getKind() != PathComponent::TupleElementKind) { - return std::nullopt; - } auto value = drillIntoComponent(*this, loc, std::move(component), base, tsanKind); ASSERT(!value.hasCleanup()); diff --git a/test/SILGen/borrow_accessor.swift b/test/SILGen/borrow_accessor.swift index 8a9bfe28a82ef..f867f7bbdc9b6 100644 --- a/test/SILGen/borrow_accessor.swift +++ b/test/SILGen/borrow_accessor.swift @@ -14,6 +14,8 @@ public struct NC : ~Copyable {} func use(_ t: borrowing NC) {} +func consume(_ t: consuming T) {} + public struct S { var _k: Klass @@ -44,30 +46,62 @@ public struct Wrapper { } } + var s_get: S { + get { + return _s + } + } + + var s_read: S { + _read { + yield _s + } + } + var k: Klass { borrow { return _k } } + var k_complex: Klass { + borrow { + consume(self) + consume(_k) + return _k + } + } + var nested_borrow: Klass { borrow { return _s.borrowKlass } } - var nested_get: Klass { + var nested_get1: Klass { borrow { return _s.getKlass // expected-error{{invalid return value from borrow accessor}} // expected-note{{borrow accessors can return either literals, stored properties or computed properties that have borrow accessors}} } } - var nested_read: Klass { + var nested_get2: Klass { + borrow { + return s_get.borrowKlass // expected-error{{invalid return value from borrow accessor}} // expected-note{{borrow accessors can return either literals, stored properties or computed properties that have borrow accessors}} + } + } + + var nested_read1: Klass { borrow { return _s.readKlass // expected-error{{invalid return value from borrow accessor}} // expected-note{{borrow accessors can return either literals, stored properties or computed properties that have borrow accessors}} } } + var nested_read2: Klass { + borrow { + return s_read.readKlass // expected-error{{invalid return value from borrow accessor}} // expected-note{{borrow accessors can return either literals, stored properties or computed properties that have borrow accessors}} + } + } + var owned_value_direct: Klass { borrow { return getKlass() // expected-error{{invalid return value from borrow accessor}} // expected-note{{borrow accessors can return either literals, stored properties or computed properties that have borrow accessors}} @@ -352,6 +386,7 @@ public struct NCWrapper: ~Copyable { return _nc } } + var nested1: NC { borrow { return _s.nc From 1fabaaf1d294eba62e2ff53b137a42d79f16bc3f Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Fri, 19 Sep 2025 15:58:12 -0700 Subject: [PATCH 11/17] Fix SILFunctionType of borrow accessors returning trivial types --- lib/SIL/IR/SILFunctionType.cpp | 8 +++-- test/SILGen/borrow_accessor.swift | 51 ++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/lib/SIL/IR/SILFunctionType.cpp b/lib/SIL/IR/SILFunctionType.cpp index 3957ce7ff8102..f8b40acf43e95 100644 --- a/lib/SIL/IR/SILFunctionType.cpp +++ b/lib/SIL/IR/SILFunctionType.cpp @@ -1452,9 +1452,11 @@ class DestructureResults { ResultConvention convention; if (isBorrowOrMutateAccessor) { - if ((hasSelfWithAddressType && !substResultTL.isTrivial()) || - isFormallyReturnedIndirectly(origType, substType, - substResultTLForConvention)) { + if (substResultTL.isTrivial()) { + convention = ResultConvention::Unowned; + } else if (hasSelfWithAddressType || + isFormallyReturnedIndirectly(origType, substType, + substResultTLForConvention)) { assert(Convs.getResult(substResultTLForConvention) == ResultConvention::Guaranteed); convention = ResultConvention::GuaranteedAddress; diff --git a/test/SILGen/borrow_accessor.swift b/test/SILGen/borrow_accessor.swift index f867f7bbdc9b6..100d688adf6cc 100644 --- a/test/SILGen/borrow_accessor.swift +++ b/test/SILGen/borrow_accessor.swift @@ -2,7 +2,9 @@ // REQUIRES: swift_feature_BorrowAndMutateAccessors -public class Klass {} +public class Klass { + var id: Int = 0 +} func getKlass() -> Klass { return Klass() @@ -90,6 +92,18 @@ public struct Wrapper { } } + var nested_get3: Int { + borrow { + return _s.getKlass.id + } + } + + var nested_get4: Int { + borrow { + return s_get.borrowKlass.id + } + } + var nested_read1: Klass { borrow { return _s.readKlass // expected-error{{invalid return value from borrow accessor}} // expected-note{{borrow accessors can return either literals, stored properties or computed properties that have borrow accessors}} @@ -461,6 +475,41 @@ func nctest() { // CHECK: return [[REG4]] // CHECK: } +// CHECK: sil hidden [ossa] @$s15borrow_accessor7WrapperV11nested_get3Sivb : $@convention(method) (@guaranteed Wrapper) -> Int { +// CHECK: bb0([[REG0:%.*]] : @guaranteed $Wrapper): +// CHECK: [[REG2:%.*]] = struct_extract [[REG0]], #Wrapper._s +// CHECK: [[REG3:%.*]] = copy_value [[REG2]] +// CHECK: [[REG4:%.*]] = begin_borrow [[REG3]] +// CHECK: [[REG5:%.*]] = function_ref @$s15borrow_accessor1SV8getKlassAA0D0Cvg : $@convention(method) (@guaranteed S) -> @owned Klass +// CHECK: [[REG6:%.*]] = apply [[REG5]]([[REG4]]) : $@convention(method) (@guaranteed S) -> @owned Klass +// CHECK: end_borrow [[REG4]] +// CHECK: destroy_value [[REG3]] +// CHECK: [[REG9:%.*]] = begin_borrow [[REG6]] +// CHECK: [[REG10:%.*]] = class_method [[REG9]], #Klass.id!getter : (Klass) -> () -> Int, $@convention(method) (@guaranteed Klass) -> Int +// CHECK: [[REG11:%.*]] = apply [[REG10]]([[REG9]]) : $@convention(method) (@guaranteed Klass) -> Int +// CHECK: end_borrow [[REG9]] +// CHECK: destroy_value [[REG6]] +// CHECK: return [[REG11]] +// CHECK: } + +// CHECK: sil hidden [ossa] @$s15borrow_accessor7WrapperV11nested_get4Sivb : $@convention(method) (@guaranteed Wrapper) -> Int { +// CHECK: bb0([[REG0:%.*]] : @guaranteed $Wrapper): +// CHECK: [[REG2:%.*]] = function_ref @$s15borrow_accessor7WrapperV5s_getAA1SVvg : $@convention(method) (@guaranteed Wrapper) -> @owned S +// CHECK: [[REG3:%.*]] = apply [[REG2]]([[REG0]]) : $@convention(method) (@guaranteed Wrapper) -> @owned S +// CHECK: [[REG4:%.*]] = begin_borrow [[REG3]] +// CHECK: [[REG5:%.*]] = function_ref @$s15borrow_accessor1SV0A5KlassAA0C0Cvb : $@convention(method) (@guaranteed S) -> @guaranteed Klass +// CHECK: [[REG6:%.*]] = apply [[REG5]]([[REG4]]) : $@convention(method) (@guaranteed S) -> @guaranteed Klass +// CHECK: [[REG7:%.*]] = copy_value [[REG6]] +// CHECK: end_borrow [[REG4]] +// CHECK: destroy_value [[REG3]] +// CHECK: [[REG10:%.*]] = begin_borrow [[REG7]] +// CHECK: [[REG11:%.*]] = class_method [[REG10]], #Klass.id!getter : (Klass) -> () -> Int, $@convention(method) (@guaranteed Klass) -> Int +// CHECK: [[REG12:%.*]] = apply [[REG11]]([[REG10]]) : $@convention(method) (@guaranteed Klass) -> Int +// CHECK: end_borrow [[REG10]] +// CHECK: destroy_value [[REG7]] +// CHECK: return [[REG12]] +// CHECK: } + // CHECK: sil hidden [ossa] @$s15borrow_accessor7WrapperV6nestedAA5KlassCvb : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass { // CHECK: bb0([[REG0:%.*]] : @guaranteed $Wrapper): // CHECK: [[REG2:%.*]] = function_ref @$s15borrow_accessor7WrapperV1kAA5KlassCvb : $@convention(method) (@guaranteed Wrapper) -> @guaranteed Klass From fa7281d66ee6c963c1c0f5058d6b2254b0ceb499 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Thu, 25 Sep 2025 14:28:37 -0700 Subject: [PATCH 12/17] Introduce a new attribute @_unsafeSelfDependentResult It can be used in borrow/mutate accessors to unsafely specify that the result is dependent on the self access. --- include/swift/AST/DeclAttr.def | 7 +- include/swift/AST/DiagnosticsSema.def | 3 + lib/AST/ASTDumper.cpp | 1 + lib/ASTGen/Sources/ASTGen/DeclAttrs.swift | 3 +- lib/SILGen/SILGenStmt.cpp | 108 ++++++++++++-------- lib/Sema/TypeCheckAttr.cpp | 13 +++ lib/Sema/TypeCheckDeclOverride.cpp | 1 + test/SILGen/borrow_accessor_container.swift | 64 ++++++++++++ 8 files changed, 158 insertions(+), 42 deletions(-) create mode 100644 test/SILGen/borrow_accessor_container.swift diff --git a/include/swift/AST/DeclAttr.def b/include/swift/AST/DeclAttr.def index c8f93f3463e5e..632cdebe58d95 100644 --- a/include/swift/AST/DeclAttr.def +++ b/include/swift/AST/DeclAttr.def @@ -902,7 +902,12 @@ DECL_ATTR(specialized, Specialized, AllowMultipleAttributes | LongAttribute | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove | ForbiddenInABIAttr, 172) -LAST_DECL_ATTR(Specialized) +SIMPLE_DECL_ATTR(_unsafeSelfDependentResult, UnsafeSelfDependentResult, + OnAccessor, + UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIBreakingToRemove | EquivalentInABIAttr, + 173) + +LAST_DECL_ATTR(UnsafeSelfDependentResult) #undef DECL_ATTR_ALIAS #undef CONTEXTUAL_DECL_ATTR_ALIAS diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index ee2a6eb92a3ee..2d59641ed3daf 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8929,5 +8929,8 @@ ERROR(attr_inline_always_requires_inlinable,none, ERROR(attr_inline_always_no_usable_from_inline,none, "cannot use '@inline(always)' together with '@usableFromInline'", ()) +ERROR(unsafe_self_dependent_result_attr_on_invalid_decl,none, + "invalid use of @_unsafeSelfDependentResult", ()) + #define UNDEFINE_DIAGNOSTIC_MACROS #include "DefineDiagnosticMacros.h" diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 38e59fcd1618e..15b95e799c7e0 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -5062,6 +5062,7 @@ class PrintAttribute : public AttributeVisitor, TRIVIAL_ATTR_PRINTER(WeakLinked, weak_linked) TRIVIAL_ATTR_PRINTER(Nonexhaustive, nonexhaustive) TRIVIAL_ATTR_PRINTER(Concurrent, concurrent) + TRIVIAL_ATTR_PRINTER(UnsafeSelfDependentResult, unsafe_self_dependent_result) #undef TRIVIAL_ATTR_PRINTER diff --git a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift index 0667d845421d0..e77859ba11836 100644 --- a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift +++ b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift @@ -309,7 +309,8 @@ extension ASTGenVisitor { .UsableFromInline, .Used, .WarnUnqualifiedAccess, - .WeakLinked: + .WeakLinked, + .UnsafeSelfDependentResult: return handle(self.generateSimpleDeclAttr(attribute: node, kind: attrKind!)) diff --git a/lib/SILGen/SILGenStmt.cpp b/lib/SILGen/SILGenStmt.cpp index e8a1cc8b42839..fe5f3993583cb 100644 --- a/lib/SILGen/SILGenStmt.cpp +++ b/lib/SILGen/SILGenStmt.cpp @@ -738,17 +738,18 @@ void SILGenFunction::emitReturnExpr(SILLocation branchLoc, } } else if (F.getConventions().hasGuaranteedResult() || F.getConventions().hasGuaranteedAddressResult()) { - // If the return expression is a literal, emit as a regular return - // expression/ + auto *afd = cast(FunctionDC->getAsDecl()); if (isa(ret)) { + // If the return expression is a literal, emit as a regular return + // expression. auto RV = emitRValue(ret); std::move(RV).forwardAll(*this, directResults); } else { - // If the return expression is not a projection, diagnose as error. FormalEvaluationScope scope(*this); auto storageRefResult = StorageRefResult::findStorageReferenceExprForBorrow(ret); auto lvExpr = storageRefResult.getTransitiveRoot(); + // If the return expression is not an lvalue, diagnose. if (!lvExpr) { diagnose(getASTContext(), ret->getStartLoc(), diag::invalid_borrow_accessor_return); @@ -756,17 +757,7 @@ void SILGenFunction::emitReturnExpr(SILLocation branchLoc, diag::borrow_accessor_not_a_projection_note); return; } - // If the return expression is not a projection of self, diagnose as - // error. - auto *baseExpr = lookThroughProjections(storageRefResult.getStorageRef()); - if (!baseExpr->isSelfExprOf( - cast(FunctionDC->getAsDecl()))) { - diagnose(getASTContext(), ret->getStartLoc(), - diag::invalid_borrow_accessor_return); - diagnose(getASTContext(), ret->getStartLoc(), - diag::borrow_accessor_not_a_projection_note); - return; - } + // Emit return value at +0. LValueOptions options; auto lvalue = emitLValue(ret, @@ -776,34 +767,71 @@ void SILGenFunction::emitReturnExpr(SILLocation branchLoc, F.getConventions().hasGuaranteedResult() ? options.forGuaranteedReturn(true) : options.forGuaranteedAddressReturn(true)); - auto result = - tryEmitProjectedLValue(ret, std::move(lvalue), TSanKind::None); - if (!result) { - diagnose(getASTContext(), ret->getStartLoc(), - diag::invalid_borrow_accessor_return); - diagnose(getASTContext(), ret->getStartLoc(), - diag::borrow_accessor_not_a_projection_note); - return; - } - // For now diagnose multiple return statements in borrow/mutate accessors. - // We need additional support for this. - // 1. Address phis are banned in SIL. - // 2. borrowed from is not inserted in SILGenCleanup. - if (!ReturnDest.getBlock()->getPredecessorBlocks().empty()) { - diagnose(getASTContext(), ret->getStartLoc(), - diag::invalid_multiple_return_borrow_accessor); - return; - } - auto resultValue = result->getValue(); - SILType selfType = F.getSelfArgument()->getType(); - if (selfType.isMoveOnly() && F.getConventions().hasGuaranteedResult()) { - // If we are returning the result of borrow accessor, strip the - // unnecessary copy_value + mark_unresolved_non_copyable_value - // instructions. - resultValue = lookThroughMoveOnlyCheckerPattern(resultValue); + if (afd->getAttrs().hasAttribute()) { + // If the accessor is annotated with @_unsafeSelfDependentResultAttr, + // disable diagnosing the return expression. + // This is needed to implement borrow accessors for Unsafe*Pointer based + // Container types where the compiler cannot analyze the safety of + // return expressions based on pointer arithmetic and unsafe addressors. + // Example: + // public struct Container: ~Copyable { + // var _storage: UnsafeMutableBufferPointer + // var _count: Int + // + // public subscript(index: Int) -> Element { + // @_unsafeSelfDependentResult + // borrow { + // precondition(index >= 0 && index < _count, "Index out of + // bounds") return + // _storage.baseAddress.unsafelyUnwrapped.advanced(by: + // index).pointee + // } + // } + // } + auto resultValue = emitBorrowedLValue(ret, std::move(lvalue)); + directResults.push_back(resultValue.getValue()); + } else { + // If the return expression is not a transitive projection of self, + // diagnose. + auto *baseExpr = + lookThroughProjections(storageRefResult.getStorageRef()); + if (!baseExpr->isSelfExprOf(afd)) { + diagnose(getASTContext(), ret->getStartLoc(), + diag::invalid_borrow_accessor_return); + diagnose(getASTContext(), ret->getStartLoc(), + diag::borrow_accessor_not_a_projection_note); + return; + } + auto result = + tryEmitProjectedLValue(ret, std::move(lvalue), TSanKind::None); + if (!result) { + diagnose(getASTContext(), ret->getStartLoc(), + diag::invalid_borrow_accessor_return); + diagnose(getASTContext(), ret->getStartLoc(), + diag::borrow_accessor_not_a_projection_note); + return; + } + // For now diagnose multiple return statements in borrow/mutate + // accessors. We need additional support for this. + // 1. Address phis are banned in SIL. + // 2. borrowed from is not inserted in SILGenCleanup. + if (!ReturnDest.getBlock()->getPredecessorBlocks().empty()) { + diagnose(getASTContext(), ret->getStartLoc(), + diag::invalid_multiple_return_borrow_accessor); + return; + } + + auto resultValue = result->getValue(); + SILType selfType = F.getSelfArgument()->getType(); + if (selfType.isMoveOnly() && F.getConventions().hasGuaranteedResult()) { + // If we are returning the result of borrow accessor, strip the + // unnecessary copy_value + mark_unresolved_non_copyable_value + // instructions. + resultValue = lookThroughMoveOnlyCheckerPattern(resultValue); + } + directResults.push_back(resultValue); } - directResults.push_back(resultValue); } } else { // SILValue return. diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 4190a9ce40b1f..e690c94e8b89f 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -483,6 +483,7 @@ class AttributeChecker : public AttributeVisitor { void visitAddressableSelfAttr(AddressableSelfAttr *attr); void visitAddressableForDependenciesAttr(AddressableForDependenciesAttr *attr); void visitUnsafeAttr(UnsafeAttr *attr); + void visitUnsafeSelfDependentResultAttr(UnsafeSelfDependentResultAttr *attr); }; } // end anonymous namespace @@ -8461,6 +8462,18 @@ void AttributeChecker::visitUnsafeAttr(UnsafeAttr *attr) { } } +void AttributeChecker::visitUnsafeSelfDependentResultAttr( + UnsafeSelfDependentResultAttr *attr) { + // TODO: Introduce a new experimental feature and check for presence + auto *accessor = dyn_cast(D); + if (accessor && + (accessor->isBorrowAccessor() || accessor->isMutateAccessor())) { + return; + } + Ctx.Diags.diagnose(attr->getLocation(), + diag::unsafe_self_dependent_result_attr_on_invalid_decl); +} + namespace { class ClosureAttributeChecker diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index 1050d396cfa67..ff0a0d532b8f9 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1754,6 +1754,7 @@ namespace { UNINTERESTING_ATTR(Unsafe) UNINTERESTING_ATTR(Safe) UNINTERESTING_ATTR(AddressableForDependencies) + UNINTERESTING_ATTR(UnsafeSelfDependentResult) #undef UNINTERESTING_ATTR void visitABIAttr(ABIAttr *attr) { diff --git a/test/SILGen/borrow_accessor_container.swift b/test/SILGen/borrow_accessor_container.swift new file mode 100644 index 0000000000000..0a589255ed02f --- /dev/null +++ b/test/SILGen/borrow_accessor_container.swift @@ -0,0 +1,64 @@ +// RUN:%target-swift-frontend -emit-silgen %s -verify -enable-experimental-feature BorrowAndMutateAccessors | %FileCheck %s + +// REQUIRES: swift_feature_BorrowAndMutateAccessors + +public struct Container: ~Copyable { + var _storage: UnsafeMutableBufferPointer + var _count: Int + + var first: Element { + @_unsafeSelfDependentResult + borrow { + return _storage.baseAddress.unsafelyUnwrapped.pointee + } + } + + public subscript(index: Int) -> Element { + @_unsafeSelfDependentResult + borrow { + precondition(index >= 0 && index < _count, "Index out of bounds") + return _storage.baseAddress.unsafelyUnwrapped.advanced(by: index).pointee + } + } +} + +extension Container: Copyable where Element: Copyable {} + + +// CHECK: sil [ossa] @$s25borrow_accessor_container9ContainerVAARi_zrlEyxSicib : $@convention(method) (Int, @guaranteed Container) -> @guaranteed_addr Element { +// CHECK: bb0([[REG0:%.*]] : $Int, [[REG1:%.*]] : @guaranteed $Container): +// CHECK: [[REG3:%.*]] = copy_value [[REG1]] +// CHECK: [[REG4:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG3]] +// CHECK: [[REG6:%.*]] = alloc_stack $UnsafeMutablePointer +// CHECK: [[REG7:%.*]] = begin_borrow [[REG4]] +// CHECK: [[REG8:%.*]] = struct_extract [[REG7]], #Container._storage +// CHECK: [[REG9:%.*]] = function_ref @$sSr11baseAddressSpyxGSgvg : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutableBufferPointer<τ_0_0>) -> Optional> +// CHECK: [[REG10:%.*]] = apply [[REG9]]([[REG8]]) : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutableBufferPointer<τ_0_0>) -> Optional> +// CHECK: [[REG11:%.*]] = alloc_stack $Optional> +// CHECK: store [[REG10]] to [trivial] [[REG11]] +// CHECK: [[REG13:%.*]] = alloc_stack $UnsafeMutablePointer +// CHECK: [[REG14:%.*]] = function_ref @$sSq17unsafelyUnwrappedxvg : $@convention(method) <τ_0_0 where τ_0_0 : ~Escapable> (@in_guaranteed Optional<τ_0_0>) -> @lifetime(copy 0) @out τ_0_0 +// CHECK: [[REG15:%.*]] = apply [[REG14]]>([[REG13]], [[REG11]]) : $@convention(method) <τ_0_0 where τ_0_0 : ~Escapable> (@in_guaranteed Optional<τ_0_0>) -> @lifetime(copy 0) @out τ_0_0 +// CHECK: [[REG16:%.*]] = load [trivial] [[REG13]] +// CHECK: [[REG17:%.*]] = alloc_stack $UnsafeMutablePointer +// CHECK: store [[REG16]] to [trivial] [[REG17]] +// CHECK: [[REG19:%.*]] = function_ref @$ss8_PointerPsE8advanced2byxSi_tF : $@convention(method) <τ_0_0 where τ_0_0 : _Pointer> (Int, @in_guaranteed τ_0_0) -> @out τ_0_0 +// CHECK: [[REG20:%.*]] = apply [[REG19]]>([[REG6]], [[REG0]], [[REG17]]) : $@convention(method) <τ_0_0 where τ_0_0 : _Pointer> (Int, @in_guaranteed τ_0_0) -> @out τ_0_0 +// CHECK: dealloc_stack [[REG17]] +// CHECK: dealloc_stack [[REG13]] +// CHECK: dealloc_stack [[REG11]] +// CHECK: end_borrow [[REG7]] +// CHECK: [[REG25:%.*]] = load [trivial] [[REG6]] +// CHECK: [[REG26:%.*]] = function_ref @$sSpsRi_zrlE7pointeexvlu : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutablePointer<τ_0_0>) -> UnsafePointer<τ_0_0> +// CHECK: [[REG27:%.*]] = apply [[REG26]]([[REG25]]) : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutablePointer<τ_0_0>) -> UnsafePointer<τ_0_0> +// CHECK: [[REG28:%.*]] = struct_extract [[REG27]], #UnsafePointer._rawValue +// CHECK: [[REG29:%.*]] = pointer_to_address [[REG28]] to [strict] $*Element +// CHECK: [[REG30:%.*]] = mark_dependence [unresolved] [[REG29]] on [[REG25]] +// CHECK: [[REG31:%.*]] = begin_access [read] [unsafe] [[REG30]] +// CHECK: [[REG32:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG31]] +// CHECK: end_access [[REG31]] +// CHECK: dealloc_stack [[REG6]] +// CHECK: destroy_value [[REG4]] +// CHECK: return [[REG32]] +// CHECK: } + From b6c8f8994a30e69cc3f8954cf82c98bdf44c7d73 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Fri, 26 Sep 2025 12:08:19 -0700 Subject: [PATCH 13/17] [NFC] Move SILGen of ReturnExpr in borrow accessors to a new function --- lib/SILGen/SILGenFunction.h | 3 + lib/SILGen/SILGenStmt.cpp | 202 +++++++++++++++++++----------------- 2 files changed, 111 insertions(+), 94 deletions(-) diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index 734b7c60f781c..a9a11ea0d19c7 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -2273,6 +2273,9 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction void emitReturnExpr(SILLocation loc, Expr *ret); + bool emitGuaranteedReturn(SILLocation loc, Expr *ret, + SmallVectorImpl &directResults); + void emitYield(SILLocation loc, MutableArrayRef yieldValues, ArrayRef origTypes, JumpDest unwindDest); diff --git a/lib/SILGen/SILGenStmt.cpp b/lib/SILGen/SILGenStmt.cpp index fe5f3993583cb..c906069338106 100644 --- a/lib/SILGen/SILGenStmt.cpp +++ b/lib/SILGen/SILGenStmt.cpp @@ -708,6 +708,110 @@ static Expr *lookThroughProjections(Expr *expr) { return lookThroughProjections(lookupExpr->getBase()); } +bool SILGenFunction::emitGuaranteedReturn( + SILLocation loc, Expr *ret, SmallVectorImpl &directResults) { + auto *afd = cast(FunctionDC->getAsDecl()); + assert(cast(afd)->isBorrowAccessor()); + + // If the return expression is a literal, emit as a regular return + // expression. + if (isa(ret)) { + auto RV = emitRValue(ret); + std::move(RV).forwardAll(*this, directResults); + return false; + } + + auto storageRefResult = + StorageRefResult::findStorageReferenceExprForBorrow(ret); + auto lvExpr = storageRefResult.getTransitiveRoot(); + // If the return expression is not an lvalue, diagnose. + if (!lvExpr) { + diagnose(getASTContext(), ret->getStartLoc(), + diag::invalid_borrow_accessor_return); + diagnose(getASTContext(), ret->getStartLoc(), + diag::borrow_accessor_not_a_projection_note); + return true; + } + + // Emit return value at +0. + FormalEvaluationScope scope(*this); + LValueOptions options; + auto lvalue = emitLValue(ret, + F.getConventions().hasGuaranteedResult() + ? SGFAccessKind::BorrowedObjectRead + : SGFAccessKind::BorrowedAddressRead, + F.getConventions().hasGuaranteedResult() + ? options.forGuaranteedReturn(true) + : options.forGuaranteedAddressReturn(true)); + + // If the accessor is annotated with @_unsafeSelfDependentResultAttr, + // disable diagnosing the return expression. + // If the accessor is annotated with @_unsafeSelfDependentResultAttr, + // disable diagnosing the return expression. + // This is needed to implement borrow accessors for Unsafe*Pointer based + // Container types where the compiler cannot analyze the safety of return + // expressions based on pointer arithmetic and unsafe addressors. + // Example: + // public struct Container: ~Copyable { + // var _storage: UnsafeMutableBufferPointer + // var _count: Int + // + // public subscript(index: Int) -> Element { + // @_unsafeSelfDependentResult + // borrow { + // precondition(index >= 0 && index < _count, "Index out of bounds") + // return _storage.baseAddress.unsafelyUnwrapped.advanced(by: + // index).pointee + // } + // } + // } + if (afd->getAttrs().hasAttribute()) { + auto resultValue = emitBorrowedLValue(ret, std::move(lvalue)); + directResults.push_back(resultValue.getValue()); + return false; + } + + // If the return expression is not a transitive projection of self, + // diagnose. + auto *baseExpr = lookThroughProjections(storageRefResult.getStorageRef()); + if (!baseExpr->isSelfExprOf(afd)) { + diagnose(getASTContext(), ret->getStartLoc(), + diag::invalid_borrow_accessor_return); + diagnose(getASTContext(), ret->getStartLoc(), + diag::borrow_accessor_not_a_projection_note); + return true; + } + + auto result = tryEmitProjectedLValue(ret, std::move(lvalue), TSanKind::None); + if (!result) { + diagnose(getASTContext(), ret->getStartLoc(), + diag::invalid_borrow_accessor_return); + diagnose(getASTContext(), ret->getStartLoc(), + diag::borrow_accessor_not_a_projection_note); + return true; + } + // For now diagnose multiple return statements in borrow/mutate accessors. + // We need additional support for this. + // 1. Address phis are banned in SIL. + // 2. borrowed from is not inserted in SILGenCleanup. + if (!ReturnDest.getBlock()->getPredecessorBlocks().empty()) { + diagnose(getASTContext(), ret->getStartLoc(), + diag::invalid_multiple_return_borrow_accessor); + return true; + } + + auto resultValue = result->getValue(); + SILType selfType = F.getSelfArgument()->getType(); + if (selfType.isMoveOnly() && F.getConventions().hasGuaranteedResult()) { + // If we are returning the result of borrow accessor, strip the + // unnecessary copy_value + mark_unresolved_non_copyable_value + // instructions. + resultValue = lookThroughMoveOnlyCheckerPattern(resultValue); + } + directResults.push_back(resultValue); + return false; +} + void SILGenFunction::emitReturnExpr(SILLocation branchLoc, Expr *ret) { SmallVector directResults; @@ -738,101 +842,11 @@ void SILGenFunction::emitReturnExpr(SILLocation branchLoc, } } else if (F.getConventions().hasGuaranteedResult() || F.getConventions().hasGuaranteedAddressResult()) { - auto *afd = cast(FunctionDC->getAsDecl()); - if (isa(ret)) { - // If the return expression is a literal, emit as a regular return - // expression. - auto RV = emitRValue(ret); - std::move(RV).forwardAll(*this, directResults); - } else { - FormalEvaluationScope scope(*this); - auto storageRefResult = - StorageRefResult::findStorageReferenceExprForBorrow(ret); - auto lvExpr = storageRefResult.getTransitiveRoot(); - // If the return expression is not an lvalue, diagnose. - if (!lvExpr) { - diagnose(getASTContext(), ret->getStartLoc(), - diag::invalid_borrow_accessor_return); - diagnose(getASTContext(), ret->getStartLoc(), - diag::borrow_accessor_not_a_projection_note); - return; - } - - // Emit return value at +0. - LValueOptions options; - auto lvalue = emitLValue(ret, - F.getConventions().hasGuaranteedResult() - ? SGFAccessKind::BorrowedObjectRead - : SGFAccessKind::BorrowedAddressRead, - F.getConventions().hasGuaranteedResult() - ? options.forGuaranteedReturn(true) - : options.forGuaranteedAddressReturn(true)); - - if (afd->getAttrs().hasAttribute()) { - // If the accessor is annotated with @_unsafeSelfDependentResultAttr, - // disable diagnosing the return expression. - // This is needed to implement borrow accessors for Unsafe*Pointer based - // Container types where the compiler cannot analyze the safety of - // return expressions based on pointer arithmetic and unsafe addressors. - // Example: - // public struct Container: ~Copyable { - // var _storage: UnsafeMutableBufferPointer - // var _count: Int - // - // public subscript(index: Int) -> Element { - // @_unsafeSelfDependentResult - // borrow { - // precondition(index >= 0 && index < _count, "Index out of - // bounds") return - // _storage.baseAddress.unsafelyUnwrapped.advanced(by: - // index).pointee - // } - // } - // } - auto resultValue = emitBorrowedLValue(ret, std::move(lvalue)); - directResults.push_back(resultValue.getValue()); - } else { - // If the return expression is not a transitive projection of self, - // diagnose. - auto *baseExpr = - lookThroughProjections(storageRefResult.getStorageRef()); - if (!baseExpr->isSelfExprOf(afd)) { - diagnose(getASTContext(), ret->getStartLoc(), - diag::invalid_borrow_accessor_return); - diagnose(getASTContext(), ret->getStartLoc(), - diag::borrow_accessor_not_a_projection_note); - return; - } - auto result = - tryEmitProjectedLValue(ret, std::move(lvalue), TSanKind::None); - if (!result) { - diagnose(getASTContext(), ret->getStartLoc(), - diag::invalid_borrow_accessor_return); - diagnose(getASTContext(), ret->getStartLoc(), - diag::borrow_accessor_not_a_projection_note); - return; - } - // For now diagnose multiple return statements in borrow/mutate - // accessors. We need additional support for this. - // 1. Address phis are banned in SIL. - // 2. borrowed from is not inserted in SILGenCleanup. - if (!ReturnDest.getBlock()->getPredecessorBlocks().empty()) { - diagnose(getASTContext(), ret->getStartLoc(), - diag::invalid_multiple_return_borrow_accessor); - return; - } - - auto resultValue = result->getValue(); - SILType selfType = F.getSelfArgument()->getType(); - if (selfType.isMoveOnly() && F.getConventions().hasGuaranteedResult()) { - // If we are returning the result of borrow accessor, strip the - // unnecessary copy_value + mark_unresolved_non_copyable_value - // instructions. - resultValue = lookThroughMoveOnlyCheckerPattern(resultValue); - } - directResults.push_back(resultValue); - } + bool hasError = emitGuaranteedReturn(branchLoc, ret, directResults); + if (hasError) { + return; } + assert(!directResults.empty()); } else { // SILValue return. FullExpr scope(Cleanups, CleanupLocation(ret)); From 19ab4cce0eca09250233588b136d3d1bbf62e477 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Tue, 30 Sep 2025 07:55:27 -0700 Subject: [PATCH 14/17] Update SILGen for borrow accessors under library evolution Under library evolution, loadable self arguments are passed as @in_guaranteed. SILGen generates load_borrow for such self arguments proactively. load_borrow creates an artifical scope and returning values produced within this scope will be illegal without resorting to unsafe operations today. This change avoids creating a load_borrow proactively for borrow accessors. --- lib/SILGen/SILGenApply.cpp | 6 +++++- lib/SILGen/SILGenProlog.cpp | 16 +++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/SILGen/SILGenApply.cpp b/lib/SILGen/SILGenApply.cpp index bc28c898c1b67..ce692020b475f 100644 --- a/lib/SILGen/SILGenApply.cpp +++ b/lib/SILGen/SILGenApply.cpp @@ -7306,12 +7306,16 @@ bool AccessorBaseArgPreparer::shouldLoadBaseAddress() const { return base.isLValue() || base.isPlusZeroRValueOrTrivial() || !SGF.silConv.useLoweredAddresses(); - case ParameterConvention::Indirect_In_Guaranteed: + case ParameterConvention::Indirect_In_Guaranteed: { + if (accessor.isBorrowAccessor()) { + return false; + } // We can pass the memory we already have at +0. The memory referred to // by the base should be already immutable unless it was lowered as an // lvalue. return base.isLValue() || !SGF.silConv.useLoweredAddresses(); + } // If the accessor wants the value directly, we definitely have to // load. diff --git a/lib/SILGen/SILGenProlog.cpp b/lib/SILGen/SILGenProlog.cpp index 463e38579098b..d5590ca1cf557 100644 --- a/lib/SILGen/SILGenProlog.cpp +++ b/lib/SILGen/SILGenProlog.cpp @@ -728,13 +728,15 @@ class ArgumentInitHelper { : AbstractionPattern(substType)); // A parameter can be directly marked as addressable, or its - // addressability can be implied by a scoped dependency. - bool isAddressable = false; - - isAddressable = pd->isAddressable() - || (ScopedDependencies.contains(pd) - && SGF.getTypeProperties(origType, substType) - .isAddressableForDependencies()); + // addressability can be implied by a scoped dependency or a borrow + // dependency. + bool isAddressable = + pd->isAddressable() || + (ScopedDependencies.contains(pd) && + SGF.getTypeProperties(origType, substType) + .isAddressableForDependencies()) || + SGF.getFunction().getConventions().hasGuaranteedAddressResult() || + SGF.getFunction().getConventions().hasGuaranteedResult(); paramValue = argEmitter.handleParam(origType, substType, pd, isAddressable); } From e4d1123d10bafd6efe153051babb82f967bd6fd0 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Tue, 30 Sep 2025 06:20:39 -0700 Subject: [PATCH 15/17] Fix SILGen of borrow accessors returning guaranteed values from within a local borrow Borrow accessor result can sometimes be generated from a local borrow and returning the result produced within the local borrow scope will cause ownership errors. We have to introduce new SIL semantics to make this possible. Until then use a pair of unchecked_ownership_conversion instructions to silence the ownership errors. We encounter this when we have: Address-only self and @guaranteed result Loadable self and @guaranteed result derived from an unsafe pointer stored property This change also updates the result convention of borrow accessors with loadable result types and indirect self argument type. --- lib/SIL/IR/SILFunctionType.cpp | 20 +- lib/SILGen/SILGenApply.cpp | 30 ++- lib/SILGen/SILGenFunction.h | 2 + lib/SILGen/SILGenStmt.cpp | 62 ++++- test/IRGen/borrow_accessor.swift | 3 +- test/SILGen/borrow_accessor.swift | 112 ++++++++- test/SILGen/borrow_accessor_container.swift | 126 +++++++++++ test/SILGen/borrow_accessor_evolution.swift | 239 ++++++++++++++++++++ 8 files changed, 556 insertions(+), 38 deletions(-) create mode 100644 test/SILGen/borrow_accessor_evolution.swift diff --git a/lib/SIL/IR/SILFunctionType.cpp b/lib/SIL/IR/SILFunctionType.cpp index f8b40acf43e95..ef314cc6a3531 100644 --- a/lib/SIL/IR/SILFunctionType.cpp +++ b/lib/SIL/IR/SILFunctionType.cpp @@ -1387,18 +1387,15 @@ class DestructureResults { TypeExpansionContext context; bool hasSendingResult; bool isBorrowOrMutateAccessor; - bool hasSelfWithAddressType; public: DestructureResults(TypeExpansionContext context, TypeConverter &TC, const Conventions &conventions, SmallVectorImpl &results, - bool hasSendingResult, bool isBorrowOrMutateAccessor, - bool hasSelfWithAddressType) + bool hasSendingResult, bool isBorrowOrMutateAccessor) : TC(TC), Convs(conventions), Results(results), context(context), hasSendingResult(hasSendingResult), - isBorrowOrMutateAccessor(isBorrowOrMutateAccessor), - hasSelfWithAddressType(hasSelfWithAddressType) {} + isBorrowOrMutateAccessor(isBorrowOrMutateAccessor) {} void destructure(AbstractionPattern origType, CanType substType) { // Recur into tuples. @@ -1454,8 +1451,7 @@ class DestructureResults { if (isBorrowOrMutateAccessor) { if (substResultTL.isTrivial()) { convention = ResultConvention::Unowned; - } else if (hasSelfWithAddressType || - isFormallyReturnedIndirectly(origType, substType, + } else if (isFormallyReturnedIndirectly(origType, substType, substResultTLForConvention)) { assert(Convs.getResult(substResultTLForConvention) == ResultConvention::Guaranteed); @@ -2728,16 +2724,12 @@ static CanSILFunctionType getSILFunctionType( coroutineOrigYieldType, coroutineSubstYieldType, yields, coroutineKind); - bool hasSelfWithAddressType = - extInfoBuilder.hasSelfParam() && - inputs.back().getSILStorageInterfaceType().isAddress(); - // Destructure the result tuple type. SmallVector results; { - DestructureResults destructurer( - expansionContext, TC, conventions, results, hasSendingResult, - isBorrowOrMutateAccessor(constant), hasSelfWithAddressType); + DestructureResults destructurer(expansionContext, TC, conventions, results, + hasSendingResult, + isBorrowOrMutateAccessor(constant)); destructurer.destructure(origResultType, substFormalResultType); } diff --git a/lib/SILGen/SILGenApply.cpp b/lib/SILGen/SILGenApply.cpp index ce692020b475f..9493f7ae886b8 100644 --- a/lib/SILGen/SILGenApply.cpp +++ b/lib/SILGen/SILGenApply.cpp @@ -5471,20 +5471,28 @@ ManagedValue SILGenFunction::applyBorrowAccessor( ExecutorBreadcrumb()); assert(rawResults.size() == 1); auto rawResult = rawResults[0]; - if (!rawResult->getType().isMoveOnly()) { - return ManagedValue::forForwardedRValue(*this, rawResult); + + if (fn.getFunction()->getConventions().hasGuaranteedResult()) { + auto selfArg = args.back().getValue(); + if (isa(selfArg)) { + rawResult = emitUncheckedGuaranteedConversion(rawResult); + } } - if (rawResult->getType().isAddress()) { - auto result = B.createMarkUnresolvedNonCopyableValueInst( - loc, rawResult, + if (rawResult->getType().isMoveOnly()) { + if (rawResult->getType().isAddress()) { + auto result = B.createMarkUnresolvedNonCopyableValueInst( + loc, rawResult, + MarkUnresolvedNonCopyableValueInst::CheckKind::NoConsumeOrAssign); + return ManagedValue::forRValueWithoutOwnership(result); + } + auto result = emitManagedCopy(loc, rawResult); + result = B.createMarkUnresolvedNonCopyableValueInst( + loc, result, MarkUnresolvedNonCopyableValueInst::CheckKind::NoConsumeOrAssign); - return ManagedValue::forRValueWithoutOwnership(result); + return emitManagedBeginBorrow(loc, result.getValue()); } - auto result = emitManagedCopy(loc, rawResult); - result = B.createMarkUnresolvedNonCopyableValueInst( - loc, result, - MarkUnresolvedNonCopyableValueInst::CheckKind::NoConsumeOrAssign); - return emitManagedBeginBorrow(loc, result.getValue()); + + return ManagedValue::forForwardedRValue(*this, rawResult); } RValue CallEmission::applyFirstLevelCallee(SGFContext C) { diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index a9a11ea0d19c7..7f254b17d9864 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -2276,6 +2276,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction bool emitGuaranteedReturn(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 c906069338106..97db94e7072c6 100644 --- a/lib/SILGen/SILGenStmt.cpp +++ b/lib/SILGen/SILGenStmt.cpp @@ -708,11 +708,37 @@ 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::emitGuaranteedReturn( SILLocation loc, Expr *ret, SmallVectorImpl &directResults) { auto *afd = cast(FunctionDC->getAsDecl()); assert(cast(afd)->isBorrowAccessor()); + + auto emitLoadBorrowFromGuaranteedAddress = + [&](ManagedValue guaranteedAddress) -> SILValue { + assert(guaranteedAddress.getValue()->getType().isAddress()); + assert(F.getConventions().hasGuaranteedResult()); + auto regularLoc = RegularLocation::getAutoGeneratedLocation(); + auto load = + B.createLoadBorrow(regularLoc, guaranteedAddress).getValue(); + return emitUncheckedGuaranteedConversion(load); + }; + // If the return expression is a literal, emit as a regular return // expression. if (isa(ret)) { @@ -737,10 +763,10 @@ bool SILGenFunction::emitGuaranteedReturn( FormalEvaluationScope scope(*this); LValueOptions options; auto lvalue = emitLValue(ret, - F.getConventions().hasGuaranteedResult() + F.getSelfArgument()->getType().isObject() ? SGFAccessKind::BorrowedObjectRead : SGFAccessKind::BorrowedAddressRead, - F.getConventions().hasGuaranteedResult() + F.getSelfArgument()->getType().isObject() ? options.forGuaranteedReturn(true) : options.forGuaranteedAddressReturn(true)); @@ -766,8 +792,14 @@ bool SILGenFunction::emitGuaranteedReturn( // } // } if (afd->getAttrs().hasAttribute()) { - auto resultValue = emitBorrowedLValue(ret, std::move(lvalue)); - directResults.push_back(resultValue.getValue()); + auto regularLoc = RegularLocation::getAutoGeneratedLocation(); + auto resultMV = emitBorrowedLValue(regularLoc, std::move(lvalue)); + SILValue result = resultMV.getValue(); + if (resultMV.getType().isAddress() && + F.getConventions().hasGuaranteedResult()) { + result = emitLoadBorrowFromGuaranteedAddress(resultMV); + } + directResults.push_back(result); return false; } @@ -782,8 +814,9 @@ bool SILGenFunction::emitGuaranteedReturn( return true; } - auto result = tryEmitProjectedLValue(ret, std::move(lvalue), TSanKind::None); - if (!result) { + auto resultMV = + tryEmitProjectedLValue(ret, std::move(lvalue), TSanKind::None); + if (!resultMV) { diagnose(getASTContext(), ret->getStartLoc(), diag::invalid_borrow_accessor_return); diagnose(getASTContext(), ret->getStartLoc(), @@ -800,15 +833,24 @@ bool SILGenFunction::emitGuaranteedReturn( return true; } - auto resultValue = result->getValue(); + SILValue result = resultMV->getValue(); SILType selfType = F.getSelfArgument()->getType(); - if (selfType.isMoveOnly() && F.getConventions().hasGuaranteedResult()) { + + if (F.getConventions().hasGuaranteedResult()) { // If we are returning the result of borrow accessor, strip the // unnecessary copy_value + mark_unresolved_non_copyable_value // instructions. - resultValue = lookThroughMoveOnlyCheckerPattern(resultValue); + if (selfType.isMoveOnly()) { + result = lookThroughMoveOnlyCheckerPattern(result); + } + // If the SIL convention is @guaranteed and the generated result is an + // address, emit a load_borrow. + if (result->getType().isAddress()) { + result = emitLoadBorrowFromGuaranteedAddress(*resultMV); + } } - directResults.push_back(resultValue); + + directResults.push_back(result); return false; } diff --git a/test/IRGen/borrow_accessor.swift b/test/IRGen/borrow_accessor.swift index b329b8e065101..054eb8ed7fd9f 100644 --- a/test/IRGen/borrow_accessor.swift +++ b/test/IRGen/borrow_accessor.swift @@ -317,7 +317,8 @@ func test() { // CHECK: [[REG2:%.*]] = getelementptr inbounds i32, ptr %"GenWrapper", i64 8 // CHECK: [[REG3:%.*]] = load i32, ptr [[REG2]], align 8 // CHECK: [[REG4:%.*]] = getelementptr inbounds i8, ptr [[REG0]], i32 [[REG3]] -// CHECK: ret ptr [[REG4]] +// CHECK: [[REG5:%.*]] = load ptr, ptr [[REG4]], align 8 +// CHECK: ret ptr [[REG5]] // CHECK: } // CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperVyxSicib"(i64 [[REG0:%.*]], ptr %"GenWrapper", ptr noalias swiftself [[REG1:%.*]]) #0 { diff --git a/test/SILGen/borrow_accessor.swift b/test/SILGen/borrow_accessor.swift index 100d688adf6cc..7ec8d8ba65d1b 100644 --- a/test/SILGen/borrow_accessor.swift +++ b/test/SILGen/borrow_accessor.swift @@ -204,6 +204,7 @@ public struct GenWrapper { var _prop: T var _w: SimpleWrapper var _klass: Klass + var _s: S public var prop: T { borrow { @@ -230,6 +231,12 @@ public struct GenWrapper { } } + var s: S { + borrow { + return _s + } + } + var nested: T { borrow { return prop @@ -242,6 +249,18 @@ public struct GenWrapper { } } + var nested_klass1: Klass { + borrow { + return _s.borrowKlass + } + } + + var nested_klass2: Klass { + borrow { + return s.borrowKlass + } + } + subscript(index: Int) -> T { borrow { return _prop @@ -311,6 +330,8 @@ public struct SimpleNCWrapper : ~Copyable { public struct GenNCWrapper : ~Copyable { var _prop: T var _w: SimpleNCWrapper + var _nc: NC + var _ncw: NCWrapper public var prop: T { borrow { @@ -324,6 +345,30 @@ public struct GenNCWrapper : ~Copyable { } } + var nc: NC { + borrow { + return _nc + } + } + + var ncw: NCWrapper { + borrow { + return _ncw + } + } + + var nested_nc1: NC { + borrow { + return _ncw.nc + } + } + + var nested_nc2: NC { + borrow { + return ncw.nc + } + } + var nested_get: T { borrow { return _w.get_prop // expected-error{{invalid return value from borrow accessor}} // expected-note{{borrow accessors can return either literals, stored properties or computed properties that have borrow accessors}} @@ -442,7 +487,7 @@ func test() { use(w1.nested_borrow) use(w1.nested) - let w2 = GenWrapper(_prop: Klass(), _w: SimpleWrapper(_prop: Klass()), _klass: Klass()) + let w2 = GenWrapper(_prop: Klass(), _w: SimpleWrapper(_prop: Klass()), _klass: Klass(), _s: S(_k: Klass())) use(w2.prop) var k2 = w2.prop use(k2) @@ -453,7 +498,7 @@ func test() { } func nctest() { - let w1 = GenNCWrapper(_prop: NC(), _w: SimpleNCWrapper(_prop: NC())) + let w1 = GenNCWrapper(_prop: NC(), _w: SimpleNCWrapper(_prop: NC()), _nc: NC(), _ncw: NCWrapper(_nc: NC(), _s: NCS(_nc: NC()))) use(w1.prop) var k2 = w1.prop // MoveOnlyChecker diagnoses the copy use(k2) @@ -568,6 +613,29 @@ func nctest() { // CHECK: return [[REG3]] // CHECK: } +// CHECK: sil hidden [ossa] @$s15borrow_accessor10GenWrapperV13nested_klass1AA5KlassCvb : $@convention(method) (@in_guaranteed GenWrapper) -> @guaranteed Klass { +// CHECK: bb0([[REG0:%.*]] : $*GenWrapper): +// 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:%.*]] = function_ref @$s15borrow_accessor1SV0A5KlassAA0C0Cvb : $@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: end_borrow [[REG3]] +// CHECK: return [[REG7]] +// CHECK: } + +// CHECK: sil hidden [ossa] @$s15borrow_accessor10GenWrapperV13nested_klass2AA5KlassCvb : $@convention(method) (@in_guaranteed GenWrapper) -> @guaranteed Klass { +// CHECK: bb0([[REG0:%.*]] : $*GenWrapper): +// CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref +// CHECK: [[REG2:%.*]] = function_ref @$s15borrow_accessor10GenWrapperV1sAA1SVvb : $@convention(method) <τ_0_0> (@in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed S +// CHECK: [[REG3:%.*]] = apply [[REG2]]([[REG0]]) : $@convention(method) <τ_0_0> (@in_guaranteed GenWrapper<τ_0_0>) -> @guaranteed S +// CHECK: [[REG4:%.*]] = function_ref @$s15borrow_accessor1SV0A5KlassAA0C0Cvb : $@convention(method) (@guaranteed S) -> @guaranteed Klass +// CHECK: [[REG5:%.*]] = apply [[REG4]]([[REG3]]) : $@convention(method) (@guaranteed S) -> @guaranteed Klass +// CHECK: return [[REG5]] +// CHECK: } + // CHECK: sil hidden [ossa] @$s15borrow_accessor10GenWrapperVyxSicib : $@convention(method) (Int, @in_guaranteed GenWrapper) -> @guaranteed_addr T { // CHECK: bb0([[REG0:%.*]] : $Int, [[REG1:%.*]] : $*GenWrapper): // CHECK: [[REG4:%.*]] = struct_element_addr [[REG1]], #GenWrapper._prop @@ -585,6 +653,25 @@ func nctest() { // CHECK: return [[REG7]] // CHECK: } +// CHECK: sil hidden [ossa] @$s15borrow_accessor12GenNCWrapperVAARi_zrlE10nested_nc1AA2NCVvb : $@convention(method) (@in_guaranteed GenNCWrapper) -> @guaranteed NC { +// CHECK: bb0([[REG0:%.*]] : $*GenNCWrapper): +// CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref +// 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:%.*]] = 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: [[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: } + // CHECK-LABEL: sil hidden [ossa] @$s15borrow_accessor9NCWrapperV2ncAA2NCVvb : $@convention(method) (@guaranteed NCWrapper) -> @guaranteed NC { // CHECK: bb0([[REG0]] : @guaranteed $NCWrapper): // CHECK: [[REG1:%.*]] = copy_value [[REG0]] @@ -596,6 +683,27 @@ func nctest() { // CHECK: return [[REG5]] // CHECK: } +// CHECK: sil hidden [ossa] @$s15borrow_accessor12GenNCWrapperVAARi_zrlE10nested_nc2AA2NCVvb : $@convention(method) (@in_guaranteed GenNCWrapper) -> @guaranteed NC { +// CHECK: bb0([[REG0:%.*]] : $*GenNCWrapper): +// CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref +// CHECK: [[REG2:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG0]] +// CHECK: [[REG3:%.*]] = function_ref @$s15borrow_accessor12GenNCWrapperVAARi_zrlE3ncwAA0D0Vvb : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (@in_guaranteed GenNCWrapper<τ_0_0>) -> @guaranteed NCWrapper +// CHECK: [[REG4:%.*]] = apply [[REG3]]([[REG2]]) : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (@in_guaranteed GenNCWrapper<τ_0_0>) -> @guaranteed NCWrapper +// CHECK: [[REG5:%.*]] = copy_value [[REG4]] +// CHECK: [[REG6:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG5]] +// CHECK: [[REG7:%.*]] = begin_borrow [[REG6]] +// CHECK: [[REG8:%.*]] = function_ref @$s15borrow_accessor9NCWrapperV2ncAA2NCVvb : $@convention(method) (@guaranteed NCWrapper) -> @guaranteed NC +// CHECK: [[REG9:%.*]] = apply [[REG8]]([[REG4]]) : $@convention(method) (@guaranteed NCWrapper) -> @guaranteed NC +// CHECK: [[REG10:%.*]] = copy_value [[REG9]] +// CHECK: [[REG11:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG10]] +// CHECK: [[REG12:%.*]] = begin_borrow [[REG11]] +// CHECK: end_borrow [[REG12]] +// CHECK: destroy_value [[REG11]] +// CHECK: end_borrow [[REG7]] +// CHECK: destroy_value [[REG6]] +// CHECK: return [[REG9]] +// CHECK: } + // CHECK-LABEL: sil hidden [ossa] @$s15borrow_accessor9NCWrapperV7nested1AA2NCVvb : $@convention(method) (@guaranteed NCWrapper) -> @guaranteed NC { // CHECK: bb0([[REG0]] : @guaranteed $NCWrapper): // CHECK: [[REG1:%.*]] = copy_value [[REG0]] diff --git a/test/SILGen/borrow_accessor_container.swift b/test/SILGen/borrow_accessor_container.swift index 0a589255ed02f..e6a8c7cc1aad7 100644 --- a/test/SILGen/borrow_accessor_container.swift +++ b/test/SILGen/borrow_accessor_container.swift @@ -62,3 +62,129 @@ extension Container: Copyable where Element: Copyable {} // CHECK: return [[REG32]] // CHECK: } +public class Klass {} + +public struct S { + var _k: Klass +} + +public struct CopyableContainer { + var _storage: UnsafeMutableBufferPointer + var _count: Int + + var first: S { + @_unsafeSelfDependentResult + borrow { + return _storage.baseAddress.unsafelyUnwrapped.pointee + } + } + + public subscript(index: Int) -> Klass { + @_unsafeSelfDependentResult + borrow { + precondition(index >= 0 && index < _count, "Index out of bounds") + return _storage.baseAddress.unsafelyUnwrapped.advanced(by: index).pointee._k + } + } +} + +// CHECK: sil [ossa] @$s25borrow_accessor_container17CopyableContainerVyAA5KlassCSicib : $@convention(method) (Int, CopyableContainer) -> @guaranteed Klass { +// CHECK: bb0([[REG0:%.*]] : $Int, [[REG1:%.*]] : $CopyableContainer): +// CHECK: [[REG4:%.*]] = alloc_stack $UnsafeMutablePointer +// CHECK: [[REG5:%.*]] = struct_extract [[REG1]], #CopyableContainer._storage +// CHECK: [[REG6:%.*]] = function_ref @$sSr11baseAddressSpyxGSgvg : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutableBufferPointer<τ_0_0>) -> Optional> +// CHECK: [[REG7:%.*]] = apply [[REG6]]([[REG5]]) : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutableBufferPointer<τ_0_0>) -> Optional> +// CHECK: [[REG8:%.*]] = alloc_stack $Optional> +// CHECK: store [[REG7]] to [trivial] [[REG8]] +// CHECK: [[REG10:%.*]] = alloc_stack $UnsafeMutablePointer +// CHECK: [[REG11:%.*]] = function_ref @$sSq17unsafelyUnwrappedxvg : $@convention(method) <τ_0_0 where τ_0_0 : ~Escapable> (@in_guaranteed Optional<τ_0_0>) -> @lifetime(copy 0) @out τ_0_0 +// CHECK: [[REG12:%.*]] = apply [[REG11]]>([[REG10]], [[REG8]]) : $@convention(method) <τ_0_0 where τ_0_0 : ~Escapable> (@in_guaranteed Optional<τ_0_0>) -> @lifetime(copy 0) @out τ_0_0 +// CHECK: [[REG13:%.*]] = load [trivial] [[REG10]] +// CHECK: [[REG14:%.*]] = alloc_stack $UnsafeMutablePointer +// CHECK: store [[REG13]] to [trivial] [[REG14]] +// CHECK: [[REG16:%.*]] = function_ref @$ss8_PointerPsE8advanced2byxSi_tF : $@convention(method) <τ_0_0 where τ_0_0 : _Pointer> (Int, @in_guaranteed τ_0_0) -> @out τ_0_0 +// CHECK: [[REG17:%.*]] = apply [[REG16]]>([[REG4]], [[REG0]], [[REG14]]) : $@convention(method) <τ_0_0 where τ_0_0 : _Pointer> (Int, @in_guaranteed τ_0_0) -> @out τ_0_0 +// CHECK: dealloc_stack [[REG14]] +// CHECK: dealloc_stack [[REG10]] +// CHECK: dealloc_stack [[REG8]] +// CHECK: [[REG21:%.*]] = load [trivial] [[REG4]] +// CHECK: [[REG22:%.*]] = function_ref @$sSpsRi_zrlE7pointeexvlu : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutablePointer<τ_0_0>) -> UnsafePointer<τ_0_0> +// CHECK: [[REG23:%.*]] = apply [[REG22]]([[REG21]]) : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutablePointer<τ_0_0>) -> UnsafePointer<τ_0_0> +// CHECK: [[REG24:%.*]] = struct_extract [[REG23]], #UnsafePointer._rawValue +// CHECK: [[REG25:%.*]] = pointer_to_address [[REG24]] to [strict] $*S +// CHECK: [[REG26:%.*]] = mark_dependence [unresolved] [[REG25]] on [[REG21]] +// 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: end_access [[REG27]] +// CHECK: end_borrow [[REG29]] +// CHECK: dealloc_stack [[REG4]] +// CHECK: return [[REG32]] +// CHECK: } + +public struct NC : ~Copyable {} + +public struct NonCopyableContainer : ~Copyable { + var _storage: UnsafeMutableBufferPointer + var _count: Int + + var first: NC { + @_unsafeSelfDependentResult + borrow { + return _storage.baseAddress.unsafelyUnwrapped.pointee + } + } + + public subscript(index: Int) -> NC { + @_unsafeSelfDependentResult + borrow { + precondition(index >= 0 && index < _count, "Index out of bounds") + return _storage.baseAddress.unsafelyUnwrapped.advanced(by: index).pointee + } + } +} + +// CHECK: sil [ossa] @$s25borrow_accessor_container20NonCopyableContainerVyAA2NCVSicib : $@convention(method) (Int, @guaranteed NonCopyableContainer) -> @guaranteed NC { +// CHECK: bb0([[REG0:%.*]] : $Int, [[REG1:%.*]] : @guaranteed $NonCopyableContainer): +// CHECK: [[REG3:%.*]] = copy_value [[REG1]] +// CHECK: [[REG4:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG3]] +// CHECK: debug_value [[REG4]], let, name "self", argno 2 +// CHECK: [[REG6:%.*]] = alloc_stack $UnsafeMutablePointer +// CHECK: [[REG7:%.*]] = begin_borrow [[REG4]] +// CHECK: [[REG8:%.*]] = struct_extract [[REG7]], #NonCopyableContainer._storage +// CHECK: [[REG9:%.*]] = function_ref @$sSr11baseAddressSpyxGSgvg : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutableBufferPointer<τ_0_0>) -> Optional> +// CHECK: [[REG10:%.*]] = apply [[REG9]]([[REG8]]) : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutableBufferPointer<τ_0_0>) -> Optional> +// CHECK: [[REG11:%.*]] = alloc_stack $Optional> +// CHECK: store [[REG10]] to [trivial] [[REG11]] +// CHECK: [[REG13:%.*]] = alloc_stack $UnsafeMutablePointer +// CHECK: [[REG14:%.*]] = function_ref @$sSq17unsafelyUnwrappedxvg : $@convention(method) <τ_0_0 where τ_0_0 : ~Escapable> (@in_guaranteed Optional<τ_0_0>) -> @lifetime(copy 0) @out τ_0_0 +// CHECK: [[REG15:%.*]] = apply [[REG14]]>([[REG13]], [[REG11]]) : $@convention(method) <τ_0_0 where τ_0_0 : ~Escapable> (@in_guaranteed Optional<τ_0_0>) -> @lifetime(copy 0) @out τ_0_0 +// CHECK: [[REG16:%.*]] = load [trivial] [[REG13]] +// CHECK: [[REG17:%.*]] = alloc_stack $UnsafeMutablePointer +// CHECK: store [[REG16]] to [trivial] [[REG17]] +// CHECK: [[REG19:%.*]] = function_ref @$ss8_PointerPsE8advanced2byxSi_tF : $@convention(method) <τ_0_0 where τ_0_0 : _Pointer> (Int, @in_guaranteed τ_0_0) -> @out τ_0_0 +// CHECK: [[REG20:%.*]] = apply [[REG19]]>([[REG6]], [[REG0]], [[REG17]]) : $@convention(method) <τ_0_0 where τ_0_0 : _Pointer> (Int, @in_guaranteed τ_0_0) -> @out τ_0_0 +// CHECK: dealloc_stack [[REG17]] +// CHECK: dealloc_stack [[REG13]] +// CHECK: dealloc_stack [[REG11]] +// CHECK: end_borrow [[REG7]] +// CHECK: [[REG25:%.*]] = load [trivial] [[REG6]] +// CHECK: [[REG26:%.*]] = function_ref @$sSpsRi_zrlE7pointeexvlu : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutablePointer<τ_0_0>) -> UnsafePointer<τ_0_0> +// CHECK: [[REG27:%.*]] = apply [[REG26]]([[REG25]]) : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutablePointer<τ_0_0>) -> UnsafePointer<τ_0_0> +// CHECK: [[REG28:%.*]] = struct_extract [[REG27]], #UnsafePointer._rawValue +// CHECK: [[REG29:%.*]] = pointer_to_address [[REG28]] to [strict] $*NC +// CHECK: [[REG30:%.*]] = mark_dependence [unresolved] [[REG29]] on [[REG25]] +// 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: end_access [[REG31]] +// CHECK: end_borrow [[REG33]] +// CHECK: dealloc_stack [[REG6]] +// CHECK: destroy_value [[REG4]] +// CHECK: return [[REG36]] +// CHECK: } + diff --git a/test/SILGen/borrow_accessor_evolution.swift b/test/SILGen/borrow_accessor_evolution.swift new file mode 100644 index 0000000000000..7e1fbb65ae921 --- /dev/null +++ b/test/SILGen/borrow_accessor_evolution.swift @@ -0,0 +1,239 @@ +// RUN:%target-swift-frontend -emit-silgen %s -verify -enable-experimental-feature BorrowAndMutateAccessors -enable-library-evolution | %FileCheck %s + +// REQUIRES: swift_feature_BorrowAndMutateAccessors + +public class Klass {} + +func getKlass() -> Klass { + return Klass() +} + +func use(_ t: Klass) {} + +public struct NC : ~Copyable {} + +func use(_ t: borrowing NC) {} + +public struct S { + var _k: Klass + var k: Klass { + borrow { + return _k + } + } +} + +public struct Wrapper { + var _k: Klass + var _s: S + + var s: S { + borrow { + return _s + } + } + + var k: Klass { + borrow { + return _k + } + } + + var nested1: Klass { + borrow { + return _s.k + } + } + + var nested2: Klass { + borrow { + return k + } + } + + subscript(index: Int) -> Klass { + borrow { + return _k + } + } + + var nested_subscript: Klass { + borrow { + return self[0] + } + } +} + +public struct NCS: ~Copyable { + var _nc: NC + + var nc: NC { + borrow { + return _nc + } + } +} + +public struct NCWrapper: ~Copyable { + var _nc: NC + var _s: NCS + + var nc: NC { + borrow { + return _nc + } + } + var nested1: NC { + borrow { + return _s.nc + } + } + + var nested2: NC { + borrow { + return nc + } + } + + subscript(index: Int) -> NC { + borrow { + return _nc + } + } + + var nested_subscript: NC { + borrow { + return self[0] + } + } +} + +func test() { + let w1 = Wrapper(_k: Klass(), _s: S(_k: Klass())) + use(w1.k) + var k1 = w1.k + use(k1) + k1 = Klass() + use(k1) + use(w1.nested1) + use(w1.nested2) +} + +// CHECK-LABEL: sil hidden [ossa] @$s25borrow_accessor_evolution7WrapperV1sAA1SVvb : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed_addr S { +// CHECK: bb0([[REG0:%.*]] : $*Wrapper): +// CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref +// CHECK: [[REG2:%.*]] = struct_element_addr [[REG0]], #Wrapper._s +// CHECK: return [[REG2]] +// CHECK: } + +// CHECK-LABEL: sil hidden [ossa] @$s25borrow_accessor_evolution7WrapperV1kAA5KlassCvb : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed Klass { +// CHECK: bb0([[REG0:%.*]] : $*Wrapper): +// 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: } + +// 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 +// CHECK: [[REG2:%.*]] = struct_element_addr [[REG0]], #Wrapper._s +// CHECK: [[REG3:%.*]] = function_ref @$s25borrow_accessor_evolution1SV1kAA5KlassCvb : $@convention(method) (@in_guaranteed S) -> @guaranteed Klass +// CHECK: [[REG4:%.*]] = apply [[REG3]]([[REG2]]) : $@convention(method) (@in_guaranteed S) -> @guaranteed Klass +// CHECK: return [[REG4]] +// CHECK: } + +// CHECK-LABEL: sil hidden [ossa] @$s25borrow_accessor_evolution7WrapperV7nested2AA5KlassCvb : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed Klass { +// CHECK: bb0([[REG0:%.*]] : $*Wrapper): +// CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref +// CHECK: [[REG2:%.*]] = function_ref @$s25borrow_accessor_evolution7WrapperV1kAA5KlassCvb : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed Klass +// CHECK: [[REG3:%.*]] = apply [[REG2]]([[REG0]]) : $@convention(method) (@in_guaranteed Wrapper) -> @guaranteed Klass +// CHECK: return [[REG3]] +// CHECK: } + +// CHECK-LABEL: sil hidden [ossa] @$s25borrow_accessor_evolution7WrapperVyAA5KlassCSicib : $@convention(method) (Int, @in_guaranteed Wrapper) -> @guaranteed Klass { +// CHECK: bb0([[REG0:%.*]] : $Int, [[REG1:%.*]] : $*Wrapper): +// CHECK: debug_value [[REG0]], let, name "index", argno 1 +// 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: end_borrow [[REG5]] +// CHECK: return [[REG7]] +// CHECK: } + +// 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 +// CHECK: [[REG2:%.*]] = integer_literal $Builtin.IntLiteral, 0 +// CHECK: [[REG3:%.*]] = metatype $@thin Int.Type +// CHECK: [[REG4:%.*]] = function_ref @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: [[REG5:%.*]] = apply [[REG4]]([[REG2]], [[REG3]]) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: [[REG6:%.*]] = function_ref @$s25borrow_accessor_evolution7WrapperVyAA5KlassCSicib : $@convention(method) (Int, @in_guaranteed Wrapper) -> @guaranteed Klass +// CHECK: [[REG7:%.*]] = apply [[REG6]]([[REG5]], [[REG0]]) : $@convention(method) (Int, @in_guaranteed Wrapper) -> @guaranteed Klass +// CHECK: return [[REG7]] +// CHECK: } + +// CHECK: sil hidden [ossa] @$s25borrow_accessor_evolution3NCSV2ncAA2NCVvb : $@convention(method) (@in_guaranteed NCS) -> @guaranteed_addr NC { +// CHECK: bb0([[REG0:%.*]] : $*NCS): +// CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref +// CHECK: [[REG2:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG0]] +// CHECK: [[REG3:%.*]] = struct_element_addr [[REG2]], #NCS._nc +// CHECK: return [[REG3]] +// CHECK: } + +// CHECK: sil hidden [ossa] @$s25borrow_accessor_evolution9NCWrapperV2ncAA2NCVvb : $@convention(method) (@in_guaranteed NCWrapper) -> @guaranteed_addr NC { +// CHECK: bb0([[REG0:%.*]] : $*NCWrapper): +// CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref +// CHECK: [[REG2:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG0]] +// CHECK: [[REG3:%.*]] = struct_element_addr [[REG2]], #NCWrapper._nc +// CHECK: return [[REG3]] +// CHECK: } + +// CHECK: sil hidden [ossa] @$s25borrow_accessor_evolution9NCWrapperV7nested1AA2NCVvb : $@convention(method) (@in_guaranteed NCWrapper) -> @guaranteed_addr NC { +// CHECK: bb0([[REG0:%.*]] : $*NCWrapper): +// CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref +// CHECK: [[REG2:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG0]] +// CHECK: [[REG3:%.*]] = struct_element_addr [[REG2]], #NCWrapper._s +// CHECK: [[REG4:%.*]] = function_ref @$s25borrow_accessor_evolution3NCSV2ncAA2NCVvb : $@convention(method) (@in_guaranteed NCS) -> @guaranteed_addr NC +// CHECK: [[REG5:%.*]] = apply [[REG4]]([[REG3]]) : $@convention(method) (@in_guaranteed NCS) -> @guaranteed_addr NC +// CHECK: [[REG6:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG5]] +// CHECK: return [[REG6]] +// CHECK: } + +// CHECK: sil hidden [ossa] @$s25borrow_accessor_evolution9NCWrapperV7nested2AA2NCVvb : $@convention(method) (@in_guaranteed NCWrapper) -> @guaranteed_addr NC { +// CHECK: bb0([[REG0:%.*]] : $*NCWrapper): +// CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref +// CHECK: [[REG2:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG0]] +// CHECK: [[REG3:%.*]] = function_ref @$s25borrow_accessor_evolution9NCWrapperV2ncAA2NCVvb : $@convention(method) (@in_guaranteed NCWrapper) -> @guaranteed_addr NC +// CHECK: [[REG4:%.*]] = apply [[REG3]]([[REG2]]) : $@convention(method) (@in_guaranteed NCWrapper) -> @guaranteed_addr NC +// CHECK: [[REG5:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG4]] +// CHECK: return [[REG5]] +// CHECK: } + +// CHECK: sil hidden [ossa] @$s25borrow_accessor_evolution9NCWrapperVyAA2NCVSicib : $@convention(method) (Int, @in_guaranteed NCWrapper) -> @guaranteed_addr NC { +// CHECK: bb0([[REG0:%.*]] : $Int, [[REG1:%.*]] : $*NCWrapper): +// CHECK: debug_value [[REG0]], let, name "index", argno 1 +// CHECK: debug_value [[REG1]], let, name "self", argno 2, expr op_deref +// CHECK: [[REG4:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG1]] +// CHECK: [[REG5:%.*]] = struct_element_addr [[REG4]], #NCWrapper._nc +// CHECK: return [[REG5]] +// CHECK: } + +// CHECK: sil hidden [ossa] @$s25borrow_accessor_evolution9NCWrapperV16nested_subscriptAA2NCVvb : $@convention(method) (@in_guaranteed NCWrapper) -> @guaranteed_addr NC { +// CHECK: bb0([[REG0:%.*]] : $*NCWrapper): +// CHECK: debug_value [[REG0]], let, name "self", argno 1, expr op_deref +// CHECK: [[REG2:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG0]] +// CHECK: [[REG3:%.*]] = integer_literal $Builtin.IntLiteral, 0 +// CHECK: [[REG4:%.*]] = metatype $@thin Int.Type +// CHECK: [[REG5:%.*]] = function_ref @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: [[REG6:%.*]] = apply [[REG5]]([[REG3]], [[REG4]]) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int +// CHECK: [[REG7:%.*]] = function_ref @$s25borrow_accessor_evolution9NCWrapperVyAA2NCVSicib : $@convention(method) (Int, @in_guaranteed NCWrapper) -> @guaranteed_addr NC +// CHECK: [[REG8:%.*]] = apply [[REG7]]([[REG6]], [[REG2]]) : $@convention(method) (Int, @in_guaranteed NCWrapper) -> @guaranteed_addr NC +// CHECK: [[REG9:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG8]] +// CHECK: return [[REG9]] +// CHECK: } + From c86f2c0dbc9012a6911b7a6a9375dc5fc6b31780 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Tue, 30 Sep 2025 12:21:41 -0700 Subject: [PATCH 16/17] Add a simple borrow accessor end to end test --- include/swift/SIL/MemAccessUtils.h | 8 +++-- test/SIL/borrow_accessor_e2e.swift | 47 ++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 test/SIL/borrow_accessor_e2e.swift diff --git a/include/swift/SIL/MemAccessUtils.h b/include/swift/SIL/MemAccessUtils.h index 41a42f10275bd..0a10889c2e21d 100644 --- a/include/swift/SIL/MemAccessUtils.h +++ b/include/swift/SIL/MemAccessUtils.h @@ -1811,9 +1811,11 @@ Result AccessUseDefChainVisitor::visit(SILValue sourceAddr) { return asImpl().visitUnidentified(sourceAddr); if (isGuaranteedAddressReturn(sourceAddr)) { - return asImpl().visitAccessProjection( - cast(sourceAddr), - &cast(sourceAddr)->getSelfArgumentOperand()); + auto *selfOp = &cast(sourceAddr)->getSelfArgumentOperand(); + if (selfOp->get()->getType().isObject()) { + return asImpl().visitUnidentified(sourceAddr); + } + return asImpl().visitAccessProjection(cast(sourceAddr), selfOp); } // Don't currently allow any other calls to return an accessed address. diff --git a/test/SIL/borrow_accessor_e2e.swift b/test/SIL/borrow_accessor_e2e.swift new file mode 100644 index 0000000000000..2a4e497d42489 --- /dev/null +++ b/test/SIL/borrow_accessor_e2e.swift @@ -0,0 +1,47 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift %s -emit-executable -enable-experimental-feature BorrowAndMutateAccessors -o %t/a.out +// RUN: %target-codesign %t/a.out +// RUN: %target-run %t/a.out + +// REQUIRES: swift_feature_BorrowAndMutateAccessors +// REQUIRES: executable_test + +public struct Container: ~Copyable { + var _storage: UnsafeBufferPointer + var _count: Int + + var first: Element { + @_unsafeSelfDependentResult + borrow { + return _storage.baseAddress.unsafelyUnwrapped.pointee + } + } + + public subscript(index: Int) -> Element { + @_unsafeSelfDependentResult + borrow { + precondition(index >= 0 && index < _count, "Index out of bounds") + return _storage.baseAddress.unsafelyUnwrapped.advanced(by: index).pointee + } + } +} + +extension Container: Copyable where Element: Copyable {} + +func test() { + let n = 10_000 + let arr = Array(0...n) + let sum = arr.withUnsafeBufferPointer { ubpointer in + let container = Container(_storage: ubpointer, _count: arr.count) + var sum = 0 + for i in 0.. Date: Tue, 30 Sep 2025 20:17:01 -0700 Subject: [PATCH 17/17] [NFC] Update IRGen tests to remote attribute identifiers --- test/IRGen/borrow_accessor.swift | 46 +++++++++++++------------- test/IRGen/borrow_accessor_large.swift | 12 +++---- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/test/IRGen/borrow_accessor.swift b/test/IRGen/borrow_accessor.swift index 054eb8ed7fd9f..9efd55f5ab085 100644 --- a/test/IRGen/borrow_accessor.swift +++ b/test/IRGen/borrow_accessor.swift @@ -262,57 +262,57 @@ func test() { } // IRGen explodes the struct parameter and returns the specified field -// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperV1kAA5KlassCvb"(ptr [[REG0:%.*]], ptr [[REG1:%.*]]) #0 { +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperV1kAA5KlassCvb"(ptr [[REG0:%.*]], ptr [[REG1:%.*]]) {{.*}} { // CHECK: entry: // CHECK: ret ptr [[REG0]] // CHECK: } -// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperV7nested1AA5KlassCvb"(ptr [[REG0:%.*]], ptr [[REG1:%.*]]) #0 { +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperV7nested1AA5KlassCvb"(ptr [[REG0:%.*]], ptr [[REG1:%.*]]) {{.*}} { // CHECK: entry: // CHECK: [[REG2:%.*]] = call swiftcc ptr @"$s15borrow_accessor1SV1kAA5KlassCvb"(ptr [[REG1]]) // CHECK: ret ptr [[REG2]] // CHECK: } -// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperV7nested2AA5KlassCvb"(ptr [[REG0:%.*]], ptr [[REG1:%.*]]) #0 { +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperV7nested2AA5KlassCvb"(ptr [[REG0:%.*]], ptr [[REG1:%.*]]) {{.*}} { // CHECK: entry: // CHECK: [[REG2:%.*]] = call swiftcc ptr @"$s15borrow_accessor7WrapperV1kAA5KlassCvb"(ptr [[REG0]], ptr [[REG1]]) // CHECK: ret ptr [[REG2]] // CHECK: } -// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperVyAA5KlassCSicib"(i64 [[REG0:%.*]], ptr [[REG1:%.*]], ptr [[REG2:%.*]]) #0 { +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperVyAA5KlassCSicib"(i64 [[REG0:%.*]], ptr [[REG1:%.*]], ptr [[REG2:%.*]]) {{.*}} { // CHECK: entry: // CHECK: ret ptr [[REG1]] // CHECK: } -// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperV16nested_subscriptAA5KlassCvb"(ptr [[REG0:%.*]], ptr [[REG1:%.*]]) #0 { +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor7WrapperV16nested_subscriptAA5KlassCvb"(ptr [[REG0:%.*]], ptr [[REG1:%.*]]) {{.*}} { // CHECK: entry: // CHECK: [[REG2:%.*]] = call swiftcc ptr @"$s15borrow_accessor7WrapperVyAA5KlassCSicib"(i64 0, ptr [[REG0]], ptr [[REG1]]) // CHECK: ret ptr [[REG2]] // CHECK: } -// CHECK: define swiftcc ptr @"$s15borrow_accessor10GenWrapperV4propxvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: define swiftcc ptr @"$s15borrow_accessor10GenWrapperV4propxvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) {{.*}} { // CHECK: entry: // CHECK: ret ptr [[REG0]] // CHECK: } -// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperV7nested1xvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperV7nested1xvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) {{.*}} { // CHECK: entry: // CHECK: [[REG2:%.*]] = getelementptr inbounds i32, ptr %"GenWrapper", i64 7 // CHECK: [[REG3:%.*]] = load i32, ptr [[REG2]], align 8 // CHECK: [[REG4:%.*]] = getelementptr inbounds i8, ptr [[REG0]], i32 [[REG3]] -// CHECK: [[REG5:%.*]] = call swiftcc %swift.metadata_response @"$s15borrow_accessor13SimpleWrapperVMa"(i64 0, ptr %T) #17 +// CHECK: [[REG5:%.*]] = call swiftcc %swift.metadata_response @"$s15borrow_accessor13SimpleWrapperVMa"(i64 0, ptr %T) // CHECK: [[REG6:%.*]] = extractvalue %swift.metadata_response [[REG5]], 0 // CHECK: [[REG7:%.*]] = call swiftcc ptr @"$s15borrow_accessor13SimpleWrapperV4propxvb"(ptr [[REG6]], ptr noalias swiftself [[REG4]]) // CHECK: ret ptr [[REG7]] // CHECK: } -// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperV7nested2xvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperV7nested2xvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) {{.*}} { // CHECK: entry: // CHECK: [[REG2:%.*]] = call swiftcc ptr @"$s15borrow_accessor10GenWrapperV4propxvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0]]) // CHECK: ret ptr [[REG2]] // CHECK: } -// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperV1kAA5KlassCvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperV1kAA5KlassCvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) {{.*}} { // CHECK: entry: // CHECK: [[REG2:%.*]] = getelementptr inbounds i32, ptr %"GenWrapper", i64 8 // CHECK: [[REG3:%.*]] = load i32, ptr [[REG2]], align 8 @@ -321,73 +321,73 @@ func test() { // CHECK: ret ptr [[REG5]] // CHECK: } -// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperVyxSicib"(i64 [[REG0:%.*]], ptr %"GenWrapper", ptr noalias swiftself [[REG1:%.*]]) #0 { +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperVyxSicib"(i64 [[REG0:%.*]], ptr %"GenWrapper", ptr noalias swiftself [[REG1:%.*]]) {{.*}} { // CHECK: entry: // CHECK: ret ptr [[REG1]] // CHECK: } -// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperV16nested_subscriptxvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor10GenWrapperV16nested_subscriptxvb"(ptr %"GenWrapper", ptr noalias swiftself [[REG0:%.*]]) {{.*}} { // CHECK: entry: // CHECK: [[REG2:%.*]] = call swiftcc ptr @"$s15borrow_accessor10GenWrapperVyxSicib"(i64 0, ptr %"GenWrapper", ptr noalias swiftself [[REG0]]) // CHECK: ret ptr [[REG2]] // CHECK: } -// CHECK: define swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlE4propxvb"(ptr %"GenNCWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: define swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlE4propxvb"(ptr %"GenNCWrapper", ptr noalias swiftself [[REG0:%.*]]) {{.*}} { // CHECK: entry: // CHECK: ret ptr [[REG0]] // CHECK: } -// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlE7nested1xvb"(ptr %"GenNCWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlE7nested1xvb"(ptr %"GenNCWrapper", ptr noalias swiftself [[REG0:%.*]]) {{.*}} { // CHECK: entry: // CHECK: [[REG2:%.*]] = getelementptr inbounds i32, ptr %"GenNCWrapper", i64 7 // CHECK: [[REG3:%.*]] = load i32, ptr [[REG2]], align 8 // CHECK: [[REG4:%.*]] = getelementptr inbounds i8, ptr [[REG0]], i32 [[REG3]] -// CHECK: [[REG5:%.*]] = call swiftcc %swift.metadata_response @"$s15borrow_accessor15SimpleNCWrapperVMa"(i64 0, ptr %T) #17 +// CHECK: [[REG5:%.*]] = call swiftcc %swift.metadata_response @"$s15borrow_accessor15SimpleNCWrapperVMa"(i64 0, ptr %T) // CHECK: [[REG6:%.*]] = extractvalue %swift.metadata_response [[REG5]], 0 // CHECK: [[REG7:%.*]] = call swiftcc ptr @"$s15borrow_accessor15SimpleNCWrapperVAARi_zrlE4propxvb"(ptr [[REG6]], ptr noalias swiftself [[REG4]]) // CHECK: ret ptr [[REG7]] // CHECK: } -// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlE7nested2xvb"(ptr %"GenNCWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlE7nested2xvb"(ptr %"GenNCWrapper", ptr noalias swiftself [[REG0:%.*]]) {{.*}} { // CHECK: entry: // CHECK: [[REG2:%.*]] = call swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlE4propxvb"(ptr %"GenNCWrapper", ptr noalias swiftself [[REG0]]) // CHECK: ret ptr [[REG2]] // CHECK: } -// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlEyxSicib"(i64 [[REG0:%.*]], ptr %"GenNCWrapper", ptr noalias swiftself [[REG1:%.*]]) #0 { +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlEyxSicib"(i64 [[REG0:%.*]], ptr %"GenNCWrapper", ptr noalias swiftself [[REG1:%.*]]) {{.*}} { // CHECK: entry: // CHECK: ret ptr [[REG1]] // CHECK: } -// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlE16nested_subscriptxvb"(ptr %"GenNCWrapper", ptr noalias swiftself [[REG0:%.*]]) #0 { +// CHECK: define hidden swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlE16nested_subscriptxvb"(ptr %"GenNCWrapper", ptr noalias swiftself [[REG0:%.*]]) {{.*}} { // CHECK: entry: // CHECK: [[REG2:%.*]] = call swiftcc ptr @"$s15borrow_accessor12GenNCWrapperVAARi_zrlEyxSicib"(i64 0, ptr %"GenNCWrapper", ptr noalias swiftself [[REG0]]) // CHECK: ret ptr [[REG2]] // CHECK: } -// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperV2ncAA2NCVvb"(i64 [[REG0:%.*]], i64 [[REG1:%.*]]) #0 { +// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperV2ncAA2NCVvb"(i64 [[REG0:%.*]], i64 [[REG1:%.*]]) {{.*}} { // CHECK: entry: // CHECK: ret i64 [[REG0]] // CHECK: } -// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperV7nested1AA2NCVvb"(i64 [[REG0:%.*]], i64 [[REG1:%.*]]) #0 { +// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperV7nested1AA2NCVvb"(i64 [[REG0:%.*]], i64 [[REG1:%.*]]) {{.*}} { // CHECK: entry: // CHECK: [[REG2:%.*]] = call swiftcc i64 @"$s15borrow_accessor3NCSV2ncAA2NCVvb"(i64 [[REG1]]) // CHECK: ret i64 [[REG2]] // CHECK: } -// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperV7nested2AA2NCVvb"(i64 [[REG0:%.*]], i64 [[REG1:%.*]]) #0 { +// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperV7nested2AA2NCVvb"(i64 [[REG0:%.*]], i64 [[REG1:%.*]]) {{.*}} { // CHECK: entry: // CHECK: [[REG2:%.*]] = call swiftcc i64 @"$s15borrow_accessor9NCWrapperV2ncAA2NCVvb"(i64 [[REG0]], i64 [[REG1]]) // CHECK: ret i64 [[REG2]] // CHECK: } -// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperVyAA2NCVSicib"(i64 [[REG0]], i64 [[REG1]], i64 [[REG2]]) #0 { +// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperVyAA2NCVSicib"(i64 [[REG0]], i64 [[REG1]], i64 [[REG2]]) {{.*}} { // CHECK: entry: // CHECK: ret i64 [[REG1]] // CHECK: } -// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperV16nested_subscriptAA2NCVvb"(i64 [[REG0:%.*]], i64 [[REG1:%.*]]) #0 { +// CHECK: define hidden swiftcc i64 @"$s15borrow_accessor9NCWrapperV16nested_subscriptAA2NCVvb"(i64 [[REG0:%.*]], i64 [[REG1:%.*]]) {{.*}} { // CHECK: entry: // CHECK: [[REG2:%.*]] = call swiftcc i64 @"$s15borrow_accessor9NCWrapperVyAA2NCVSicib"(i64 0, i64 [[REG0]], i64 [[REG1]]) // CHECK: ret i64 [[REG2]] diff --git a/test/IRGen/borrow_accessor_large.swift b/test/IRGen/borrow_accessor_large.swift index a4daa90343d5d..753e6a95e3f9c 100644 --- a/test/IRGen/borrow_accessor_large.swift +++ b/test/IRGen/borrow_accessor_large.swift @@ -137,7 +137,7 @@ func nctest() { // CHECK: } // IRGen result type is PtrTy because we are returning a class reference -// CHECK-IRGEN: define hidden swiftcc ptr @"$s21borrow_accessor_large11LargeStructV0A5KlassAA0F0Cvb"(ptr noalias nocapture swiftself dereferenceable(208) [[REG0]]) #0 { +// CHECK-IRGEN: define hidden swiftcc ptr @"$s21borrow_accessor_large11LargeStructV0A5KlassAA0F0Cvb"(ptr noalias nocapture swiftself dereferenceable(208) [[REG0]]) {{.*}} { // CHECK-IRGEN: entry: // CHECK-IRGEN: %._k = getelementptr inbounds %T21borrow_accessor_large11LargeStructV, ptr [[REG0]], i32 0, i32 0 // CHECK-IRGEN: [[REG1:%.*]] = load ptr, ptr %._k, align 8 @@ -151,7 +151,7 @@ func nctest() { // CHECK: return [[REG3]] // CHECK: } -// CHECK-IRGEN: define hidden swiftcc i64 @"$s21borrow_accessor_large11LargeStructV0a5SmallE0AA0fE0Vvb"(ptr noalias nocapture swiftself dereferenceable(208) [[REG0]]) #0 { +// CHECK-IRGEN: define hidden swiftcc i64 @"$s21borrow_accessor_large11LargeStructV0a5SmallE0AA0fE0Vvb"(ptr noalias nocapture swiftself dereferenceable(208) [[REG0]]) {{.*}} { // CHECK-IRGEN: entry: // CHECK-IRGEN: %._smallStruct = getelementptr inbounds %T21borrow_accessor_large11LargeStructV, ptr [[REG0]], i32 0, i32 4 // CHECK-IRGEN: %._smallStruct.id = getelementptr inbounds %T21borrow_accessor_large11SmallStructV, ptr %._smallStruct, i32 0, i32 0 @@ -169,7 +169,7 @@ func nctest() { // CHECK: return [[REG5]] // CHECK: } -// CHECK-IRGEN: define hidden swiftcc void @"$s21borrow_accessor_large11LargeStructV0C10PropBorrowAA0dF0Vvb"(ptr noalias nocapture sret(%T21borrow_accessor_large9LargePropV) [[REG0]], ptr noalias nocapture swiftself dereferenceable(208) [[REG1]]) #0 { +// CHECK-IRGEN: define hidden swiftcc void @"$s21borrow_accessor_large11LargeStructV0C10PropBorrowAA0dF0Vvb"(ptr noalias nocapture sret(%T21borrow_accessor_large9LargePropV) [[REG0]], ptr noalias nocapture swiftself dereferenceable(208) [[REG1]]) {{.*}} { // CHECK-IRGEN: entry: // CHECK-IRGEN: %._largeProp = getelementptr inbounds %T21borrow_accessor_large11LargeStructV, ptr [[REG1]], i32 0, i32 3 // CHECK-IRGEN: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[REG0]], ptr align 8 %._largeProp, i64 64, i1 false) @@ -184,7 +184,7 @@ func nctest() { // CHECK: return [[REG3]] // CHECK: } -// CHECK-IRGEN: define hidden swiftcc void @"$s21borrow_accessor_large11LargeStructV0C11TupleBorrowAA5KlassC_A7Ftvb"(ptr noalias nocapture sret(<{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>) [[REG0]], ptr noalias nocapture swiftself dereferenceable(208) [[REG1]]) #0 { +// CHECK-IRGEN: define hidden swiftcc void @"$s21borrow_accessor_large11LargeStructV0C11TupleBorrowAA5KlassC_A7Ftvb"(ptr noalias nocapture sret(<{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>) [[REG0]], ptr noalias nocapture swiftself dereferenceable(208) [[REG1]]) {{.*}} { // CHECK-IRGEN: entry: // CHECK-IRGEN: %._largeTuple = getelementptr inbounds %T21borrow_accessor_large11LargeStructV, ptr [[REG1]], i32 0, i32 2 // CHECK-IRGEN: %._largeTuple.elt = getelementptr inbounds <{ ptr, ptr, ptr, ptr, ptr, ptr, ptr, ptr }>, ptr %._largeTuple, i32 0, i32 0 @@ -229,7 +229,7 @@ func nctest() { // CHECK: return [[REG3]] // CHECK: } -// CHECK-IRGEN: define hidden swiftcc i64 @"$s21borrow_accessor_large13NCLargeStructV0A2NCAA0F0Vvb"(ptr noalias nocapture swiftself dereferenceable(72) [[REG0]]) #0 { +// CHECK-IRGEN: define hidden swiftcc i64 @"$s21borrow_accessor_large13NCLargeStructV0A2NCAA0F0Vvb"(ptr noalias nocapture swiftself dereferenceable(72) [[REG0]]) {{.*}} { // CHECK-IRGEN: entry: // CHECK-IRGEN: %._k = getelementptr inbounds %T21borrow_accessor_large13NCLargeStructV, ptr [[REG0]], i32 0, i32 0 // CHECK-IRGEN: %._k.id = getelementptr inbounds %T21borrow_accessor_large2NCV, ptr %._k, i32 0, i32 0 @@ -247,7 +247,7 @@ func nctest() { // CHECK: return [[REG5]] // CHECK: } -// CHECK-IRGEN: define hidden swiftcc void @"$s21borrow_accessor_large13NCLargeStructV0C10PropBorrowAA11LargeNCPropVvb"(ptr noalias nocapture sret(%T21borrow_accessor_large11LargeNCPropV) [[REG0]], ptr noalias nocapture swiftself dereferenceable(72) [[REG1]]) #0 { +// CHECK-IRGEN: define hidden swiftcc void @"$s21borrow_accessor_large13NCLargeStructV0C10PropBorrowAA11LargeNCPropVvb"(ptr noalias nocapture sret(%T21borrow_accessor_large11LargeNCPropV) [[REG0]], ptr noalias nocapture swiftself dereferenceable(72) [[REG1]]) {{.*}} { // CHECK-IRGEN: entry: // CHECK-IRGEN: %._largeProp = getelementptr inbounds %T21borrow_accessor_large13NCLargeStructV, ptr [[REG1]], i32 0, i32 1 // CHECK-IRGEN: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[REG0]], ptr align 8 %._largeProp, i64 64, i1 false)