diff --git a/SwiftCompilerSources/Sources/SIL/Function.swift b/SwiftCompilerSources/Sources/SIL/Function.swift index 4c316f5f55f1e..41a78b616872f 100644 --- a/SwiftCompilerSources/Sources/SIL/Function.swift +++ b/SwiftCompilerSources/Sources/SIL/Function.swift @@ -293,6 +293,7 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash case noRuntime case noExistentials case noObjCRuntime + case manualOwnership } public var performanceConstraints: PerformanceConstraints { @@ -303,6 +304,7 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash case .NoRuntime: return .noRuntime case .NoExistentials: return .noExistentials case .NoObjCBridging: return .noObjCRuntime + case .ManualOwnership: return .manualOwnership default: fatalError("unknown performance constraint") } } diff --git a/include/swift/AST/DeclAttr.def b/include/swift/AST/DeclAttr.def index 8da6f1541ac01..734e0ec0fdfc8 100644 --- a/include/swift/AST/DeclAttr.def +++ b/include/swift/AST/DeclAttr.def @@ -815,7 +815,10 @@ SIMPLE_DECL_ATTR(_noObjCBridging, NoObjCBridging, UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove | ForbiddenInABIAttr, 155) -// Unused '156': Used to be `_distributedThunkTarget` but completed implementation in Swift 6.0 does not need it after all +SIMPLE_DECL_ATTR(_manualOwnership, ManualOwnership, + OnAbstractFunction | OnSubscript, + UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove | ForbiddenInABIAttr, + 156) DECL_ATTR(_allowFeatureSuppression, AllowFeatureSuppression, OnAnyDecl, diff --git a/include/swift/AST/DiagnosticsSIL.def b/include/swift/AST/DiagnosticsSIL.def index 0ffdcf6508ef6..b31fe07a8e030 100644 --- a/include/swift/AST/DiagnosticsSIL.def +++ b/include/swift/AST/DiagnosticsSIL.def @@ -428,6 +428,8 @@ ERROR(wrong_linkage_for_serialized_function,none, "function has wrong linkage to be called from %0", (StringRef)) NOTE(performance_called_from,none, "called from here", ()) +ERROR(manualownership_copy,none, + "explicit 'copy' required here", ()) // 'transparent' diagnostics ERROR(circular_transparent,none, diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 19d39a86bec97..0b1dfd6ba8da1 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -2145,6 +2145,10 @@ ERROR(attr_static_exclusive_only_mutating,none, ERROR(attr_static_exclusive_no_setters,none, "variable of type %0 must not have a setter", (Type)) +// @_manualOwnership +ERROR(attr_manual_ownership_experimental,none, + "'@_manualOwnership' requires '-enable-experimental-feature ManualOwnership'", ()) + // @extractConstantsFromMembers ERROR(attr_extractConstantsFromMembers_experimental,none, "'@extractConstantsFromMembers' requires " diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 5c3e165eaf30d..f71e9df63d222 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -447,6 +447,9 @@ EXPERIMENTAL_FEATURE(LifetimeDependence, true) /// Enable the `@_staticExclusiveOnly` attribute. EXPERIMENTAL_FEATURE(StaticExclusiveOnly, true) +/// Enable the `@_manualOwnership` attribute. +EXPERIMENTAL_FEATURE(ManualOwnership, false) + /// Enable the @extractConstantsFromMembers attribute. EXPERIMENTAL_FEATURE(ExtractConstantsFromMembers, false) diff --git a/include/swift/SIL/SILBridging.h b/include/swift/SIL/SILBridging.h index aa3eaca5e51f3..6b7a10a6a09e1 100644 --- a/include/swift/SIL/SILBridging.h +++ b/include/swift/SIL/SILBridging.h @@ -463,7 +463,8 @@ struct BridgedFunction { NoLocks = 2, NoRuntime = 3, NoExistentials = 4, - NoObjCBridging = 5 + NoObjCBridging = 5, + ManualOwnership = 6, }; enum class InlineStrategy { diff --git a/include/swift/SIL/SILBuilder.h b/include/swift/SIL/SILBuilder.h index 01fc361410a07..aa928be7301aa 100644 --- a/include/swift/SIL/SILBuilder.h +++ b/include/swift/SIL/SILBuilder.h @@ -280,6 +280,14 @@ class SILBuilder { return false; } + /// If we have a SILFunction, return true if it has a ManualOwnership + /// PerformanceConstraint, which corresponds to an attribute on the FuncDecl. + bool hasManualOwnershipAttr() const { + if (F) + return F->getPerfConstraints() == PerformanceConstraints::ManualOwnership; + return false; + } + //===--------------------------------------------------------------------===// // Insertion Point Management //===--------------------------------------------------------------------===// diff --git a/include/swift/SIL/SILCloner.h b/include/swift/SIL/SILCloner.h index 78fb5a264eb1f..6a396047c2e95 100644 --- a/include/swift/SIL/SILCloner.h +++ b/include/swift/SIL/SILCloner.h @@ -1698,6 +1698,16 @@ template void SILCloner::visitCopyAddrInst(CopyAddrInst *Inst) { getBuilder().setCurrentDebugScope(getOpScope(Inst->getDebugScope())); + // When cloning into a function using manual ownership, convert to explicit + // copies, in order to preserve the local nature of the perf constraint. + if (getBuilder().hasManualOwnershipAttr() && getBuilder().hasOwnership()) { + recordClonedInstruction( + Inst, getBuilder().createExplicitCopyAddr( + getOpLocation(Inst->getLoc()), getOpValue(Inst->getSrc()), + getOpValue(Inst->getDest()), Inst->isTakeOfSrc(), + Inst->isInitializationOfDest())); + return; + } recordClonedInstruction( Inst, getBuilder().createCopyAddr( getOpLocation(Inst->getLoc()), getOpValue(Inst->getSrc()), @@ -2092,6 +2102,15 @@ void SILCloner::visitCopyValueInst(CopyValueInst *Inst) { return recordFoldedValue(Inst, newValue); } + // When cloning into a function using manual ownership, convert to explicit + // copies, in order to preserve the local nature of the perf constraint. + if (getBuilder().hasManualOwnershipAttr() && getBuilder().hasOwnership()) { + recordClonedInstruction( + Inst, getBuilder().createExplicitCopyValue(getOpLocation(Inst->getLoc()), + getOpValue(Inst->getOperand()))); + return; + } + recordClonedInstruction( Inst, getBuilder().createCopyValue(getOpLocation(Inst->getLoc()), getOpValue(Inst->getOperand()))); diff --git a/include/swift/SIL/SILFunction.h b/include/swift/SIL/SILFunction.h index c1ed6a24b16ad..d85bf7d68d649 100644 --- a/include/swift/SIL/SILFunction.h +++ b/include/swift/SIL/SILFunction.h @@ -93,7 +93,8 @@ enum class PerformanceConstraints : uint8_t { NoLocks = 2, NoRuntime = 3, NoExistentials = 4, - NoObjCBridging = 5 + NoObjCBridging = 5, + ManualOwnership = 6, }; class SILSpecializeAttr final { diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 0ed2c08a21fe5..6852a74275c66 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -5030,6 +5030,7 @@ class PrintAttribute : public AttributeVisitor, TRIVIAL_ATTR_PRINTER(NoLocks, no_locks) TRIVIAL_ATTR_PRINTER(NoMetadata, no_metadata) TRIVIAL_ATTR_PRINTER(NoObjCBridging, no_objc_bridging) + TRIVIAL_ATTR_PRINTER(ManualOwnership, manual_ownership) TRIVIAL_ATTR_PRINTER(NoRuntime, no_runtime) TRIVIAL_ATTR_PRINTER(NonEphemeral, non_ephemeral) TRIVIAL_ATTR_PRINTER(NonEscapable, non_escapable) diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 382453371d536..87902abb0612b 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -138,6 +138,7 @@ static bool usesFeatureInlineArrayTypeSugar(Decl *D) { } UNINTERESTING_FEATURE(StaticExclusiveOnly) +UNINTERESTING_FEATURE(ManualOwnership) UNINTERESTING_FEATURE(ExtractConstantsFromMembers) UNINTERESTING_FEATURE(GroupActorErrors) UNINTERESTING_FEATURE(SameElementRequirements) diff --git a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift index f6a5a96aed226..50b26985daf02 100644 --- a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift +++ b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift @@ -265,6 +265,7 @@ extension ASTGenVisitor { .LexicalLifetimes, .LLDBDebuggerFunction, .MainType, + .ManualOwnership, .Marker, .MoveOnly, .NeverEmitIntoClient, diff --git a/lib/SIL/IR/SILFunctionBuilder.cpp b/lib/SIL/IR/SILFunctionBuilder.cpp index 70ef8c782b3a1..274077a57487e 100644 --- a/lib/SIL/IR/SILFunctionBuilder.cpp +++ b/lib/SIL/IR/SILFunctionBuilder.cpp @@ -203,6 +203,8 @@ void SILFunctionBuilder::addFunctionAttributes( F->setPerfConstraints(PerformanceConstraints::NoExistentials); } else if (Attrs.hasAttribute()) { F->setPerfConstraints(PerformanceConstraints::NoObjCBridging); + } else if (Attrs.hasAttribute()) { + F->setPerfConstraints(PerformanceConstraints::ManualOwnership); } if (Attrs.hasAttribute()) { diff --git a/lib/SIL/IR/SILPrinter.cpp b/lib/SIL/IR/SILPrinter.cpp index 835cb7223e6e0..37e1b5b7fe019 100644 --- a/lib/SIL/IR/SILPrinter.cpp +++ b/lib/SIL/IR/SILPrinter.cpp @@ -3658,6 +3658,7 @@ void SILFunction::print(SILPrintContext &PrintCtx) const { case PerformanceConstraints::NoRuntime: OS << "[no_runtime] "; break; case PerformanceConstraints::NoExistentials: OS << "[no_existentials] "; break; case PerformanceConstraints::NoObjCBridging: OS << "[no_objc_bridging] "; break; + case PerformanceConstraints::ManualOwnership: OS << "[manual_ownership] "; break; } if (isPerformanceConstraint()) diff --git a/lib/SIL/Parser/ParseSIL.cpp b/lib/SIL/Parser/ParseSIL.cpp index ca36191ae4a79..d54c4f9175337 100644 --- a/lib/SIL/Parser/ParseSIL.cpp +++ b/lib/SIL/Parser/ParseSIL.cpp @@ -783,6 +783,8 @@ static bool parseDeclSILOptional( *perfConstraints = PerformanceConstraints::NoExistentials; else if (perfConstraints && SP.P.Tok.getText() == "no_objc_bridging") *perfConstraints = PerformanceConstraints::NoObjCBridging; + else if (perfConstraints && SP.P.Tok.getText() == "manual_ownership") + *perfConstraints = PerformanceConstraints::ManualOwnership; else if (isPerformanceConstraint && SP.P.Tok.getText() == "perf_constraint") *isPerformanceConstraint = true; else if (markedAsUsed && SP.P.Tok.getText() == "used") diff --git a/lib/SIL/Utils/SILBridging.cpp b/lib/SIL/Utils/SILBridging.cpp index 23f463cad8569..0d3ad956fe6f3 100644 --- a/lib/SIL/Utils/SILBridging.cpp +++ b/lib/SIL/Utils/SILBridging.cpp @@ -186,6 +186,7 @@ static_assert((int)BridgedFunction::PerformanceConstraints::NoLocks == (int)swif static_assert((int)BridgedFunction::PerformanceConstraints::NoRuntime == (int)swift::PerformanceConstraints::NoRuntime); static_assert((int)BridgedFunction::PerformanceConstraints::NoExistentials == (int)swift::PerformanceConstraints::NoExistentials); static_assert((int)BridgedFunction::PerformanceConstraints::NoObjCBridging == (int)swift::PerformanceConstraints::NoObjCBridging); +static_assert((int)BridgedFunction::PerformanceConstraints::ManualOwnership == (int)swift::PerformanceConstraints::ManualOwnership); static_assert((int)BridgedFunction::InlineStrategy::InlineDefault == (int)swift::InlineDefault); static_assert((int)BridgedFunction::InlineStrategy::NoInline == (int)swift::NoInline); diff --git a/lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp b/lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp index f3cca112462fa..56802e32cb328 100644 --- a/lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp +++ b/lib/SILOptimizer/Mandatory/PerformanceDiagnostics.cpp @@ -293,6 +293,11 @@ bool PerformanceDiagnostics::visitCallee(SILInstruction *callInst, CalleeList callees, PerformanceConstraints perfConstr, LocWithParent *parentLoc) { + // Manual Ownership is not checked recursively within callees of the function, + // it's a local, non-viral performance annotation. + if (perfConstr == PerformanceConstraints::ManualOwnership) + return false; + LocWithParent asLoc(callInst->getLoc().getSourceLoc(), parentLoc); LocWithParent *loc = &asLoc; if (parentLoc && asLoc.loc == callInst->getFunction()->getLocation().getSourceLoc()) @@ -370,6 +375,66 @@ bool PerformanceDiagnostics::visitInst(SILInstruction *inst, RuntimeEffect impact = getRuntimeEffect(inst, impactType); LocWithParent loc(inst->getLoc().getSourceLoc(), parentLoc); + if (perfConstr == PerformanceConstraints::ManualOwnership) { + if (impact == RuntimeEffect::RefCounting) { + bool shouldDiagnose = false; + switch (inst->getKind()) { + case SILInstructionKind::ExplicitCopyAddrInst: + case SILInstructionKind::ExplicitCopyValueInst: + break; + case SILInstructionKind::LoadInst: { + // FIXME: we don't have an `explicit_load` and transparent functions can + // end up bringing in a `load [copy]`. A better approach is needed to + // handle transparent functions, in general, as rewriting them during + // inlining of transparent functions is also not so great, as it + // may hide a copy that semantically is there, but we happened to + // tuck away in a transparent synthesized function, like those + // synthesized getters! + // + // For now look to see if the load copy's only non-destroying users are + // explicit_copy's, as that would indicate the user has acknowledged it: + // + // %y = load [copy] %x + // %z = explicit_copy_value %y + // destroy_value %y + // + // In all other cases, it's safer to diagnose. + // + auto load = cast(inst); + if (load->getOwnershipQualifier() != LoadOwnershipQualifier::Copy) + break; + + for (auto *use : load->getUsers()) { + if (!isa(use)) { + shouldDiagnose = true; + break; + } + } + break; + } + default: + shouldDiagnose = true; + break; + } + if (shouldDiagnose) { + diagnose(loc, diag::manualownership_copy); + llvm::dbgs() << "function " << inst->getFunction()->getName(); + llvm::dbgs() << "\n has unexpected copying instruction: "; + inst->dump(); + return false; // Don't bail-out early; diagnose more issues in the func. + } + } else if (impact == RuntimeEffect::ExclusivityChecking) { + // TODO: diagnose only the nested exclusivity; perhaps as a warning + // since we don't yet have reference bindings? + + // diagnose(loc, diag::performance_arc); + // inst->dump(); + // return true; + } + return false; + } + if (perfConstr == PerformanceConstraints::NoExistentials && ((impact & RuntimeEffect::Existential) || (impact & RuntimeEffect::ExistentialClassBound))) { diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 0418ad790ca95..bddbea73ee361 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -475,6 +475,11 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, diag::copy_expression_cannot_be_used_with_noncopyable_types); } + /// FIXME: there really is no reason for the restriction on copy not being + /// permitted on fields, other than needing tests to ensure it works. + if (Ctx.LangOpts.hasFeature(ManualOwnership)) + return; + // We only allow for copy_expr to be applied directly to lvalues. We do // not allow currently for it to be applied to fields. auto *subExpr = copyExpr->getSubExpr(); diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index b0580b3fe7be3..7099f26ae5e02 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -470,6 +470,7 @@ class AttributeChecker : public AttributeVisitor { void visitUnsafeNonEscapableResultAttr(UnsafeNonEscapableResultAttr *attr); void visitStaticExclusiveOnlyAttr(StaticExclusiveOnlyAttr *attr); + void visitManualOwnershipAttr(ManualOwnershipAttr *attr); void visitWeakLinkedAttr(WeakLinkedAttr *attr); void visitSILGenNameAttr(SILGenNameAttr *attr); void visitLifetimeAttr(LifetimeAttr *attr); @@ -8358,6 +8359,13 @@ void AttributeChecker::visitStaticExclusiveOnlyAttr( } } +void AttributeChecker::visitManualOwnershipAttr(ManualOwnershipAttr *attr) { + if (Ctx.LangOpts.hasFeature(Feature::ManualOwnership)) + return; + + diagnoseAndRemoveAttr(attr, diag::attr_manual_ownership_experimental); +} + void AttributeChecker::visitWeakLinkedAttr(WeakLinkedAttr *attr) { if (!Ctx.LangOpts.Target.isOSBinFormatCOFF()) return; diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index 00a2ba5c657df..1050d396cfa67 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1621,6 +1621,7 @@ namespace { UNINTERESTING_ATTR(NoRuntime) UNINTERESTING_ATTR(NoExistentials) UNINTERESTING_ATTR(NoObjCBridging) + UNINTERESTING_ATTR(ManualOwnership) UNINTERESTING_ATTR(Inlinable) UNINTERESTING_ATTR(Effects) UNINTERESTING_ATTR(Expose) diff --git a/test/SIL/manual_ownership.swift b/test/SIL/manual_ownership.swift new file mode 100644 index 0000000000000..b4e929542f78d --- /dev/null +++ b/test/SIL/manual_ownership.swift @@ -0,0 +1,154 @@ +// RUN: %target-swift-frontend %s -emit-sil -verify -enable-experimental-feature ManualOwnership + +// REQUIRES: swift_feature_ManualOwnership + +// MARK: types + +public class Whatever { + var a = Pair(x: 0, y: 0) +} + +public struct Pair { + var x: Int + var y: Int + + consuming func midpoint(_ other: borrowing Pair) -> Pair { + return Pair(x: (x + other.x) / 2, y: (y + other.y) / 2) + } +} + +public class Triangle { + var a = Pair(x: 0, y: 0) + var b = Pair(x: 0, y: 1) + var c = Pair(x: 1, y: 1) + + var nontrivial = Whatever() + + consuming func consuming() {} + borrowing func borrowing() {} +} + +/// MARK: return statements + +@_manualOwnership +public func basic_return1() -> Triangle { + let x = Triangle() + return x // expected-error {{explicit 'copy' required here}} +} +@_manualOwnership +public func basic_return1_fixed() -> Triangle { + let x = Triangle() + return copy x +} + + +@_manualOwnership +public func basic_return2(t: Triangle) -> Triangle { + return t // expected-error {{explicit 'copy' required here}} +} +@_manualOwnership +public func basic_return2_fixed(t: Triangle) -> Triangle { + return copy t +} + +@_manualOwnership +public func basic_return3() -> Triangle { + return Triangle() +} + +/// MARK: method calls + +@_manualOwnership +func basic_methods_borrowing(_ t1: Triangle) { + let t2 = Triangle() + t1.borrowing() + t2.borrowing() +} + +@_manualOwnership +func basic_methods_consuming(_ t1: Triangle) { + let t2 = Triangle() + t1.consuming() // expected-error {{explicit 'copy' required here}} + t2.consuming() // expected-error {{explicit 'copy' required here}} +} +@_manualOwnership +func basic_methods_consuming_fixed(_ t1: Triangle) { + let t2 = Triangle() + var t3 = Triangle() + t3 = Triangle() + + (copy t1).consuming() + (copy t2).consuming() + (copy t3).consuming() +} + +func consumingFunc(_ t0: consuming Triangle) {} +func plainFunc(_ t0: Triangle) {} + +@_manualOwnership +func basic_function_call(_ t1: Triangle) { + consumingFunc(t1) // expected-error {{explicit 'copy' required here}} + consumingFunc(copy t1) + plainFunc(t1) +} + +/// MARK: control-flow + + +// FIXME: var's and assignments are a little busted + +// @_manualOwnership +// func reassignments_1() { +// var t3 = Triangle() +// t3 = Triangle() +// t3.borrowing() +// } +// @_manualOwnership +// func ressignments_2() { +// var t3 = Triangle() +// t3 = Triangle() +// t3.consuming() +// } + +@_manualOwnership +public func basic_loop_trivial_values(_ t: Triangle, _ xs: [Triangle]) { + var p: Pair = t.a + for x in xs { // expected-error {{explicit 'copy' required here}} + p = p.midpoint(x.a) + } + t.a = p +} +@_manualOwnership +public func basic_loop_trivial_values_fixed(_ t: Triangle, _ xs: [Triangle]) { + var p: Pair = t.a + for x in copy xs { + p = p.midpoint(x.a) + } + t.a = p +} + +// FIXME: the only reason for so many copies below is because +// `Triangle.nontrivial` only exposes get/set rather than read/modify by default +// +// We should figure out when the coroutine accessors are generated, and ensure +// that when it is available, it is used without copying the result, rather than +// calling the get/set + +@_manualOwnership +public func basic_loop_nontrivial_values(_ t: Triangle, _ xs: [Triangle]) { + var p: Pair = t.nontrivial.a // expected-error {{explicit 'copy' required here}} + for x in xs { // expected-error {{explicit 'copy' required here}} + p = p.midpoint(x.nontrivial.a) // expected-error {{explicit 'copy' required here}} + } + t.nontrivial.a = p // expected-error {{explicit 'copy' required here}} +} + +// FIXME: there should be no copies required in the below, other than what's already written. +@_manualOwnership +public func basic_loop_nontrivial_values_fixed(_ t: Triangle, _ xs: [Triangle]) { + var p: Pair = (copy t.nontrivial).a // expected-error {{explicit 'copy' required here}} + for x in copy xs { + p = p.midpoint((copy x.nontrivial).a) // expected-error {{explicit 'copy' required here}} + } + (copy t.nontrivial).a = p // expected-error {{explicit 'copy' required here}} +}