Skip to content

Commit fa7281d

Browse files
committed
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.
1 parent 1fabaaf commit fa7281d

File tree

8 files changed

+158
-42
lines changed

8 files changed

+158
-42
lines changed

include/swift/AST/DeclAttr.def

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,12 @@ DECL_ATTR(specialized, Specialized,
902902
AllowMultipleAttributes | LongAttribute | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove | ForbiddenInABIAttr,
903903
172)
904904

905-
LAST_DECL_ATTR(Specialized)
905+
SIMPLE_DECL_ATTR(_unsafeSelfDependentResult, UnsafeSelfDependentResult,
906+
OnAccessor,
907+
UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIBreakingToRemove | EquivalentInABIAttr,
908+
173)
909+
910+
LAST_DECL_ATTR(UnsafeSelfDependentResult)
906911

907912
#undef DECL_ATTR_ALIAS
908913
#undef CONTEXTUAL_DECL_ATTR_ALIAS

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8929,5 +8929,8 @@ ERROR(attr_inline_always_requires_inlinable,none,
89298929
ERROR(attr_inline_always_no_usable_from_inline,none,
89308930
"cannot use '@inline(always)' together with '@usableFromInline'", ())
89318931

8932+
ERROR(unsafe_self_dependent_result_attr_on_invalid_decl,none,
8933+
"invalid use of @_unsafeSelfDependentResult", ())
8934+
89328935
#define UNDEFINE_DIAGNOSTIC_MACROS
89338936
#include "DefineDiagnosticMacros.h"

lib/AST/ASTDumper.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5062,6 +5062,7 @@ class PrintAttribute : public AttributeVisitor<PrintAttribute, void, Label>,
50625062
TRIVIAL_ATTR_PRINTER(WeakLinked, weak_linked)
50635063
TRIVIAL_ATTR_PRINTER(Nonexhaustive, nonexhaustive)
50645064
TRIVIAL_ATTR_PRINTER(Concurrent, concurrent)
5065+
TRIVIAL_ATTR_PRINTER(UnsafeSelfDependentResult, unsafe_self_dependent_result)
50655066

50665067
#undef TRIVIAL_ATTR_PRINTER
50675068

lib/ASTGen/Sources/ASTGen/DeclAttrs.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,8 @@ extension ASTGenVisitor {
309309
.UsableFromInline,
310310
.Used,
311311
.WarnUnqualifiedAccess,
312-
.WeakLinked:
312+
.WeakLinked,
313+
.UnsafeSelfDependentResult:
313314

314315
return handle(self.generateSimpleDeclAttr(attribute: node, kind: attrKind!))
315316

lib/SILGen/SILGenStmt.cpp

Lines changed: 68 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -738,35 +738,26 @@ void SILGenFunction::emitReturnExpr(SILLocation branchLoc,
738738
}
739739
} else if (F.getConventions().hasGuaranteedResult() ||
740740
F.getConventions().hasGuaranteedAddressResult()) {
741-
// If the return expression is a literal, emit as a regular return
742-
// expression/
741+
auto *afd = cast<AbstractFunctionDecl>(FunctionDC->getAsDecl());
743742
if (isa<LiteralExpr>(ret)) {
743+
// If the return expression is a literal, emit as a regular return
744+
// expression.
744745
auto RV = emitRValue(ret);
745746
std::move(RV).forwardAll(*this, directResults);
746747
} else {
747-
// If the return expression is not a projection, diagnose as error.
748748
FormalEvaluationScope scope(*this);
749749
auto storageRefResult =
750750
StorageRefResult::findStorageReferenceExprForBorrow(ret);
751751
auto lvExpr = storageRefResult.getTransitiveRoot();
752+
// If the return expression is not an lvalue, diagnose.
752753
if (!lvExpr) {
753754
diagnose(getASTContext(), ret->getStartLoc(),
754755
diag::invalid_borrow_accessor_return);
755756
diagnose(getASTContext(), ret->getStartLoc(),
756757
diag::borrow_accessor_not_a_projection_note);
757758
return;
758759
}
759-
// If the return expression is not a projection of self, diagnose as
760-
// error.
761-
auto *baseExpr = lookThroughProjections(storageRefResult.getStorageRef());
762-
if (!baseExpr->isSelfExprOf(
763-
cast<AbstractFunctionDecl>(FunctionDC->getAsDecl()))) {
764-
diagnose(getASTContext(), ret->getStartLoc(),
765-
diag::invalid_borrow_accessor_return);
766-
diagnose(getASTContext(), ret->getStartLoc(),
767-
diag::borrow_accessor_not_a_projection_note);
768-
return;
769-
}
760+
770761
// Emit return value at +0.
771762
LValueOptions options;
772763
auto lvalue = emitLValue(ret,
@@ -776,34 +767,71 @@ void SILGenFunction::emitReturnExpr(SILLocation branchLoc,
776767
F.getConventions().hasGuaranteedResult()
777768
? options.forGuaranteedReturn(true)
778769
: options.forGuaranteedAddressReturn(true));
779-
auto result =
780-
tryEmitProjectedLValue(ret, std::move(lvalue), TSanKind::None);
781-
if (!result) {
782-
diagnose(getASTContext(), ret->getStartLoc(),
783-
diag::invalid_borrow_accessor_return);
784-
diagnose(getASTContext(), ret->getStartLoc(),
785-
diag::borrow_accessor_not_a_projection_note);
786-
return;
787-
}
788-
// For now diagnose multiple return statements in borrow/mutate accessors.
789-
// We need additional support for this.
790-
// 1. Address phis are banned in SIL.
791-
// 2. borrowed from is not inserted in SILGenCleanup.
792-
if (!ReturnDest.getBlock()->getPredecessorBlocks().empty()) {
793-
diagnose(getASTContext(), ret->getStartLoc(),
794-
diag::invalid_multiple_return_borrow_accessor);
795-
return;
796-
}
797770

798-
auto resultValue = result->getValue();
799-
SILType selfType = F.getSelfArgument()->getType();
800-
if (selfType.isMoveOnly() && F.getConventions().hasGuaranteedResult()) {
801-
// If we are returning the result of borrow accessor, strip the
802-
// unnecessary copy_value + mark_unresolved_non_copyable_value
803-
// instructions.
804-
resultValue = lookThroughMoveOnlyCheckerPattern(resultValue);
771+
if (afd->getAttrs().hasAttribute<UnsafeSelfDependentResultAttr>()) {
772+
// If the accessor is annotated with @_unsafeSelfDependentResultAttr,
773+
// disable diagnosing the return expression.
774+
// This is needed to implement borrow accessors for Unsafe*Pointer based
775+
// Container types where the compiler cannot analyze the safety of
776+
// return expressions based on pointer arithmetic and unsafe addressors.
777+
// Example:
778+
// public struct Container<Element: ~Copyable>: ~Copyable {
779+
// var _storage: UnsafeMutableBufferPointer<Element>
780+
// var _count: Int
781+
//
782+
// public subscript(index: Int) -> Element {
783+
// @_unsafeSelfDependentResult
784+
// borrow {
785+
// precondition(index >= 0 && index < _count, "Index out of
786+
// bounds") return
787+
// _storage.baseAddress.unsafelyUnwrapped.advanced(by:
788+
// index).pointee
789+
// }
790+
// }
791+
// }
792+
auto resultValue = emitBorrowedLValue(ret, std::move(lvalue));
793+
directResults.push_back(resultValue.getValue());
794+
} else {
795+
// If the return expression is not a transitive projection of self,
796+
// diagnose.
797+
auto *baseExpr =
798+
lookThroughProjections(storageRefResult.getStorageRef());
799+
if (!baseExpr->isSelfExprOf(afd)) {
800+
diagnose(getASTContext(), ret->getStartLoc(),
801+
diag::invalid_borrow_accessor_return);
802+
diagnose(getASTContext(), ret->getStartLoc(),
803+
diag::borrow_accessor_not_a_projection_note);
804+
return;
805+
}
806+
auto result =
807+
tryEmitProjectedLValue(ret, std::move(lvalue), TSanKind::None);
808+
if (!result) {
809+
diagnose(getASTContext(), ret->getStartLoc(),
810+
diag::invalid_borrow_accessor_return);
811+
diagnose(getASTContext(), ret->getStartLoc(),
812+
diag::borrow_accessor_not_a_projection_note);
813+
return;
814+
}
815+
// For now diagnose multiple return statements in borrow/mutate
816+
// accessors. We need additional support for this.
817+
// 1. Address phis are banned in SIL.
818+
// 2. borrowed from is not inserted in SILGenCleanup.
819+
if (!ReturnDest.getBlock()->getPredecessorBlocks().empty()) {
820+
diagnose(getASTContext(), ret->getStartLoc(),
821+
diag::invalid_multiple_return_borrow_accessor);
822+
return;
823+
}
824+
825+
auto resultValue = result->getValue();
826+
SILType selfType = F.getSelfArgument()->getType();
827+
if (selfType.isMoveOnly() && F.getConventions().hasGuaranteedResult()) {
828+
// If we are returning the result of borrow accessor, strip the
829+
// unnecessary copy_value + mark_unresolved_non_copyable_value
830+
// instructions.
831+
resultValue = lookThroughMoveOnlyCheckerPattern(resultValue);
832+
}
833+
directResults.push_back(resultValue);
805834
}
806-
directResults.push_back(resultValue);
807835
}
808836
} else {
809837
// SILValue return.

lib/Sema/TypeCheckAttr.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,7 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
483483
void visitAddressableSelfAttr(AddressableSelfAttr *attr);
484484
void visitAddressableForDependenciesAttr(AddressableForDependenciesAttr *attr);
485485
void visitUnsafeAttr(UnsafeAttr *attr);
486+
void visitUnsafeSelfDependentResultAttr(UnsafeSelfDependentResultAttr *attr);
486487
};
487488

488489
} // end anonymous namespace
@@ -8461,6 +8462,18 @@ void AttributeChecker::visitUnsafeAttr(UnsafeAttr *attr) {
84618462
}
84628463
}
84638464

8465+
void AttributeChecker::visitUnsafeSelfDependentResultAttr(
8466+
UnsafeSelfDependentResultAttr *attr) {
8467+
// TODO: Introduce a new experimental feature and check for presence
8468+
auto *accessor = dyn_cast<AccessorDecl>(D);
8469+
if (accessor &&
8470+
(accessor->isBorrowAccessor() || accessor->isMutateAccessor())) {
8471+
return;
8472+
}
8473+
Ctx.Diags.diagnose(attr->getLocation(),
8474+
diag::unsafe_self_dependent_result_attr_on_invalid_decl);
8475+
}
8476+
84648477
namespace {
84658478

84668479
class ClosureAttributeChecker

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1754,6 +1754,7 @@ namespace {
17541754
UNINTERESTING_ATTR(Unsafe)
17551755
UNINTERESTING_ATTR(Safe)
17561756
UNINTERESTING_ATTR(AddressableForDependencies)
1757+
UNINTERESTING_ATTR(UnsafeSelfDependentResult)
17571758
#undef UNINTERESTING_ATTR
17581759

17591760
void visitABIAttr(ABIAttr *attr) {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// RUN:%target-swift-frontend -emit-silgen %s -verify -enable-experimental-feature BorrowAndMutateAccessors | %FileCheck %s
2+
3+
// REQUIRES: swift_feature_BorrowAndMutateAccessors
4+
5+
public struct Container<Element: ~Copyable >: ~Copyable {
6+
var _storage: UnsafeMutableBufferPointer<Element>
7+
var _count: Int
8+
9+
var first: Element {
10+
@_unsafeSelfDependentResult
11+
borrow {
12+
return _storage.baseAddress.unsafelyUnwrapped.pointee
13+
}
14+
}
15+
16+
public subscript(index: Int) -> Element {
17+
@_unsafeSelfDependentResult
18+
borrow {
19+
precondition(index >= 0 && index < _count, "Index out of bounds")
20+
return _storage.baseAddress.unsafelyUnwrapped.advanced(by: index).pointee
21+
}
22+
}
23+
}
24+
25+
extension Container: Copyable where Element: Copyable {}
26+
27+
28+
// CHECK: sil [ossa] @$s25borrow_accessor_container9ContainerVAARi_zrlEyxSicib : $@convention(method) <Element where Element : ~Copyable> (Int, @guaranteed Container<Element>) -> @guaranteed_addr Element {
29+
// CHECK: bb0([[REG0:%.*]] : $Int, [[REG1:%.*]] : @guaranteed $Container<Element>):
30+
// CHECK: [[REG3:%.*]] = copy_value [[REG1]]
31+
// CHECK: [[REG4:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG3]]
32+
// CHECK: [[REG6:%.*]] = alloc_stack $UnsafeMutablePointer<Element>
33+
// CHECK: [[REG7:%.*]] = begin_borrow [[REG4]]
34+
// CHECK: [[REG8:%.*]] = struct_extract [[REG7]], #Container._storage
35+
// CHECK: [[REG9:%.*]] = function_ref @$sSr11baseAddressSpyxGSgvg : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutableBufferPointer<τ_0_0>) -> Optional<UnsafeMutablePointer<τ_0_0>>
36+
// CHECK: [[REG10:%.*]] = apply [[REG9]]<Element>([[REG8]]) : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutableBufferPointer<τ_0_0>) -> Optional<UnsafeMutablePointer<τ_0_0>>
37+
// CHECK: [[REG11:%.*]] = alloc_stack $Optional<UnsafeMutablePointer<Element>>
38+
// CHECK: store [[REG10]] to [trivial] [[REG11]]
39+
// CHECK: [[REG13:%.*]] = alloc_stack $UnsafeMutablePointer<Element>
40+
// CHECK: [[REG14:%.*]] = function_ref @$sSq17unsafelyUnwrappedxvg : $@convention(method) <τ_0_0 where τ_0_0 : ~Escapable> (@in_guaranteed Optional<τ_0_0>) -> @lifetime(copy 0) @out τ_0_0
41+
// CHECK: [[REG15:%.*]] = apply [[REG14]]<UnsafeMutablePointer<Element>>([[REG13]], [[REG11]]) : $@convention(method) <τ_0_0 where τ_0_0 : ~Escapable> (@in_guaranteed Optional<τ_0_0>) -> @lifetime(copy 0) @out τ_0_0
42+
// CHECK: [[REG16:%.*]] = load [trivial] [[REG13]]
43+
// CHECK: [[REG17:%.*]] = alloc_stack $UnsafeMutablePointer<Element>
44+
// CHECK: store [[REG16]] to [trivial] [[REG17]]
45+
// CHECK: [[REG19:%.*]] = function_ref @$ss8_PointerPsE8advanced2byxSi_tF : $@convention(method) <τ_0_0 where τ_0_0 : _Pointer> (Int, @in_guaranteed τ_0_0) -> @out τ_0_0
46+
// CHECK: [[REG20:%.*]] = apply [[REG19]]<UnsafeMutablePointer<Element>>([[REG6]], [[REG0]], [[REG17]]) : $@convention(method) <τ_0_0 where τ_0_0 : _Pointer> (Int, @in_guaranteed τ_0_0) -> @out τ_0_0
47+
// CHECK: dealloc_stack [[REG17]]
48+
// CHECK: dealloc_stack [[REG13]]
49+
// CHECK: dealloc_stack [[REG11]]
50+
// CHECK: end_borrow [[REG7]]
51+
// CHECK: [[REG25:%.*]] = load [trivial] [[REG6]]
52+
// CHECK: [[REG26:%.*]] = function_ref @$sSpsRi_zrlE7pointeexvlu : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutablePointer<τ_0_0>) -> UnsafePointer<τ_0_0>
53+
// CHECK: [[REG27:%.*]] = apply [[REG26]]<Element>([[REG25]]) : $@convention(method) <τ_0_0 where τ_0_0 : ~Copyable> (UnsafeMutablePointer<τ_0_0>) -> UnsafePointer<τ_0_0>
54+
// CHECK: [[REG28:%.*]] = struct_extract [[REG27]], #UnsafePointer._rawValue
55+
// CHECK: [[REG29:%.*]] = pointer_to_address [[REG28]] to [strict] $*Element
56+
// CHECK: [[REG30:%.*]] = mark_dependence [unresolved] [[REG29]] on [[REG25]]
57+
// CHECK: [[REG31:%.*]] = begin_access [read] [unsafe] [[REG30]]
58+
// CHECK: [[REG32:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[REG31]]
59+
// CHECK: end_access [[REG31]]
60+
// CHECK: dealloc_stack [[REG6]]
61+
// CHECK: destroy_value [[REG4]]
62+
// CHECK: return [[REG32]]
63+
// CHECK: }
64+

0 commit comments

Comments
 (0)