From 5c4406b5e85265796bb1edb64d9b8846c5bbd29a Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Thu, 19 Dec 2024 19:06:59 -0800 Subject: [PATCH 1/2] Add an `@_addressableForDependencies` type attribute. This attribute makes it so that a parameter of the annotated type, as well as any type structurally containing that type as a field, becomes passed as if `@_addressable` if the return value of the function has a dependency on the parameter. This allows nonescapable values to take interior pointers into such types. --- include/swift/AST/DeclAttr.def | 10 +- include/swift/AST/DiagnosticsSema.def | 4 + include/swift/Basic/Features.def | 1 + include/swift/SIL/AbstractionPattern.h | 10 ++ include/swift/SIL/TypeLowering.h | 56 +++++++--- lib/AST/ASTDumper.cpp | 1 + lib/AST/FeatureSet.cpp | 8 ++ lib/ASTGen/Sources/ASTGen/DeclAttrs.swift | 1 + lib/SIL/IR/AbstractionPattern.cpp | 66 ++++++++++++ lib/SIL/IR/SILFunctionType.cpp | 75 ++++++++++--- lib/SIL/IR/TypeLowering.cpp | 10 +- lib/SILGen/SILGenApply.cpp | 30 ++++-- lib/SILGen/SILGenFunction.h | 8 +- lib/SILGen/SILGenProlog.cpp | 66 ++++++++++-- lib/Sema/TypeCheckAttr.cpp | 13 +++ lib/Sema/TypeCheckDeclOverride.cpp | 1 + lib/Serialization/ModuleFormat.h | 2 +- .../SILGen/addressable_for_dependencies.swift | 100 ++++++++++++++++++ 18 files changed, 408 insertions(+), 54 deletions(-) create mode 100644 test/SILGen/addressable_for_dependencies.swift diff --git a/include/swift/AST/DeclAttr.def b/include/swift/AST/DeclAttr.def index d8c6de4b1e128..20f3d597049b9 100644 --- a/include/swift/AST/DeclAttr.def +++ b/include/swift/AST/DeclAttr.def @@ -524,18 +524,22 @@ DECL_ATTR(lifetime, Lifetime, 161) SIMPLE_DECL_ATTR(_addressableSelf, AddressableSelf, - OnAccessor | OnConstructor | OnFunc | OnSubscript | ABIBreakingToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove | UserInaccessible, + OnAccessor | OnConstructor | OnFunc | OnSubscript | ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove | UserInaccessible, 162) +SIMPLE_DECL_ATTR(_addressableForDependencies, AddressableForDependencies, + OnNominalType | ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIBreakingToRemove | UserInaccessible, + 163) + DECL_ATTR(safe, Safe, OnAbstractFunction | OnSubscript | OnVar | OnMacro | OnNominalType | OnExtension | OnTypeAlias | OnImport | UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove, - 163) + 164) DECL_ATTR(abi, ABI, OnAbstractFunction | OnVar /* will eventually add types */ | LongAttribute | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove, - 164) + 165) DECL_ATTR_FEATURE_REQUIREMENT(ABI, ABIAttribute) LAST_DECL_ATTR(ABI) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 81d63a284733d..34d4a8aca586f 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -313,6 +313,10 @@ ERROR(addressable_not_enabled,none, "@_addressable is an experimental feature", ()) ERROR(addressableSelf_not_on_method,none, "@_addressableSelf cannot be applied to non-member declarations", ()) +ERROR(addressable_types_not_enabled,none, + "@_addressableForDependencies is an experimental feature", ()) +ERROR(class_cannot_be_addressable_for_dependencies,none, + "a class cannot be @_addressableForDependencies", ()) ERROR(unsupported_closure_attr,none, "%select{attribute |}0 '%1' is not supported on a closure", diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index c0fce2bde8213..34cd074b9fa1a 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -434,6 +434,7 @@ EXPERIMENTAL_FEATURE(CoroutineAccessorsAllocateInCallee, false) EXPERIMENTAL_FEATURE(GenerateForceToMainActorThunks, false) EXPERIMENTAL_FEATURE(AddressableParameters, true) +EXPERIMENTAL_FEATURE(AddressableTypes, true) /// Allow the @abi attribute. SUPPRESSIBLE_EXPERIMENTAL_FEATURE(ABIAttribute, true) diff --git a/include/swift/SIL/AbstractionPattern.h b/include/swift/SIL/AbstractionPattern.h index 00beef86a84a3..3dd0cc196ad00 100644 --- a/include/swift/SIL/AbstractionPattern.h +++ b/include/swift/SIL/AbstractionPattern.h @@ -1558,6 +1558,16 @@ class AbstractionPattern { /// for one of its parameters. ParameterTypeFlags getFunctionParamFlags(unsigned index) const; + /// Given that the value being abstracted is a function type, return whether + /// the indicated parameter should be treated as addressable, meaning + /// calls should preserve the in-memory address of the argument for as + /// long as any dependencies may live. + /// + /// This may be true either because the type is structurally addressable for + /// dependencies, or because it was explicitly marked as `@_addressable` + /// in its declaration. + bool isFunctionParamAddressable(TypeConverter &TC, unsigned index) const; + /// Given that the value being abstracted is a function type, and that /// this is not an opaque abstraction pattern, return the number of /// parameters in the pattern. diff --git a/include/swift/SIL/TypeLowering.h b/include/swift/SIL/TypeLowering.h index 8c1dfa3b98795..36036c1f6345f 100644 --- a/include/swift/SIL/TypeLowering.h +++ b/include/swift/SIL/TypeLowering.h @@ -176,6 +176,17 @@ enum HasPack_t : bool { HasPack = true, }; +/// Is the type addressable-for-dependencies? +/// +/// Values of an addressable-for-dependency type are passed indirectly into +/// functions that specify a return value lifetime dependency on the value. +/// This allows the dependent value to safely contain pointers to the in-memory +/// representation of the source of the dependency. +enum IsAddressableForDependencies_t : bool { + IsNotAddressableForDependencies = false, + IsAddressableForDependencies = true, +}; + /// Extended type information used by SIL. class TypeLowering { public: @@ -184,15 +195,16 @@ class TypeLowering { // // clang-format off enum : unsigned { - NonTrivialFlag = 1 << 0, - NonFixedABIFlag = 1 << 1, - AddressOnlyFlag = 1 << 2, - ResilientFlag = 1 << 3, - TypeExpansionSensitiveFlag = 1 << 4, - InfiniteFlag = 1 << 5, - HasRawPointerFlag = 1 << 6, - LexicalFlag = 1 << 7, - HasPackFlag = 1 << 8, + NonTrivialFlag = 1 << 0, + NonFixedABIFlag = 1 << 1, + AddressOnlyFlag = 1 << 2, + ResilientFlag = 1 << 3, + TypeExpansionSensitiveFlag = 1 << 4, + InfiniteFlag = 1 << 5, + HasRawPointerFlag = 1 << 6, + LexicalFlag = 1 << 7, + HasPackFlag = 1 << 8, + AddressableForDependenciesFlag = 1 << 9, }; // clang-format on @@ -209,7 +221,8 @@ class TypeLowering { IsTypeExpansionSensitive_t isTypeExpansionSensitive = IsNotTypeExpansionSensitive, HasRawPointer_t hasRawPointer = DoesNotHaveRawPointer, - IsLexical_t isLexical = IsNotLexical, HasPack_t hasPack = HasNoPack) + IsLexical_t isLexical = IsNotLexical, HasPack_t hasPack = HasNoPack, + IsAddressableForDependencies_t isAFD = IsAddressableForDependencies) : Flags((isTrivial ? 0U : NonTrivialFlag) | (isFixedABI ? 0U : NonFixedABIFlag) | (isAddressOnly ? AddressOnlyFlag : 0U) | @@ -217,7 +230,8 @@ class TypeLowering { (isTypeExpansionSensitive ? TypeExpansionSensitiveFlag : 0U) | (hasRawPointer ? HasRawPointerFlag : 0U) | (isLexical ? LexicalFlag : 0U) | - (hasPack ? HasPackFlag : 0U)) {} + (hasPack ? HasPackFlag : 0U) | + (isAFD ? AddressableForDependenciesFlag : 0U)) {} constexpr bool operator==(RecursiveProperties p) const { return Flags == p.Flags; @@ -227,6 +241,12 @@ class TypeLowering { return {IsTrivial, IsFixedABI, IsNotAddressOnly, IsNotResilient}; } + static constexpr RecursiveProperties forTrivialOpaque() { + return {IsTrivial, IsFixedABI, IsNotAddressOnly, IsNotResilient, + IsNotTypeExpansionSensitive, HasRawPointer, IsNotLexical, + HasNoPack, IsAddressableForDependencies}; + } + static constexpr RecursiveProperties forRawPointer() { return {IsTrivial, IsFixedABI, IsNotAddressOnly, IsNotResilient, IsNotTypeExpansionSensitive, HasRawPointer}; @@ -239,11 +259,14 @@ class TypeLowering { static constexpr RecursiveProperties forOpaque() { return {IsNotTrivial, IsNotFixedABI, IsAddressOnly, IsNotResilient, - IsNotTypeExpansionSensitive, DoesNotHaveRawPointer, IsLexical, HasNoPack}; + IsNotTypeExpansionSensitive, HasRawPointer, IsLexical, + HasNoPack, IsAddressableForDependencies}; } static constexpr RecursiveProperties forResilient() { - return {IsTrivial, IsFixedABI, IsNotAddressOnly, IsResilient}; + return {IsTrivial, IsFixedABI, IsNotAddressOnly, IsResilient, + IsNotTypeExpansionSensitive, HasRawPointer, IsNotLexical, + HasNoPack, IsAddressableForDependencies}; } void addSubobject(RecursiveProperties other) { @@ -278,6 +301,10 @@ class TypeLowering { HasPack_t isOrContainsPack() const { return HasPack_t((Flags & HasPackFlag) != 0); } + IsAddressableForDependencies_t isAddressableForDependencies() const { + return IsAddressableForDependencies_t( + (Flags & AddressableForDependenciesFlag) != 0); + } void setNonTrivial() { Flags |= NonTrivialFlag; } void setIsOrContainsRawPointer() { Flags |= HasRawPointerFlag; } @@ -294,6 +321,9 @@ class TypeLowering { Flags = (Flags & ~LexicalFlag) | (isLexical ? LexicalFlag : 0); } void setHasPack() { Flags |= HasPackFlag; } + void setAddressableForDependencies() { + Flags |= AddressableForDependenciesFlag; + } }; private: diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index fdfd0b07e13d1..8bbf0cae55b4c 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -3760,6 +3760,7 @@ class PrintAttribute : public AttributeVisitor, TRIVIAL_ATTR_PRINTER(Actor, actor) TRIVIAL_ATTR_PRINTER(AddressableSelf, _addressableSelf) + TRIVIAL_ATTR_PRINTER(AddressableForDependencies, _addressableForDependencies) TRIVIAL_ATTR_PRINTER(AlwaysEmitConformanceMetadata, always_emit_conformance_metadata) TRIVIAL_ATTR_PRINTER(AlwaysEmitIntoClient, always_emit_into_client) diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 9f3df80604faf..c54f492814cb9 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -308,6 +308,14 @@ static bool usesFeatureAddressableParameters(Decl *d) { return false; } +static bool usesFeatureAddressableTypes(Decl *d) { + if (d->getAttrs().hasAttribute()) { + return true; + } + + return false; +} + UNINTERESTING_FEATURE(IsolatedAny2) UNINTERESTING_FEATURE(GlobalActorIsolatedTypesUsability) UNINTERESTING_FEATURE(ObjCImplementation) diff --git a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift index 90b5b8d0282b5..d82291b159d72 100644 --- a/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift +++ b/lib/ASTGen/Sources/ASTGen/DeclAttrs.swift @@ -191,6 +191,7 @@ extension ASTGenVisitor { // Simple attributes. case .addressableSelf, + .addressableForDependencies, .alwaysEmitConformanceMetadata, .alwaysEmitIntoClient, .atReasync, diff --git a/lib/SIL/IR/AbstractionPattern.cpp b/lib/SIL/IR/AbstractionPattern.cpp index e4cd320e4b364..070bdca51f19b 100644 --- a/lib/SIL/IR/AbstractionPattern.cpp +++ b/lib/SIL/IR/AbstractionPattern.cpp @@ -1649,6 +1649,72 @@ AbstractionPattern::getFunctionParamFlags(unsigned index) const { .getParameterFlags(); } +bool +AbstractionPattern::isFunctionParamAddressable(TypeConverter &TC, + unsigned index) const { + switch (getKind()) { + case Kind::Invalid: + case Kind::Tuple: + llvm_unreachable("not any kind of function!"); + case Kind::Opaque: + case Kind::OpaqueFunction: + case Kind::OpaqueDerivativeFunction: + // If the function abstraction pattern is completely opaque, assume we + // may need to preserve the address for dependencies. + return true; + + case Kind::ClangType: + case Kind::ObjCCompletionHandlerArgumentsType: + case Kind::CurriedObjCMethodType: + case Kind::PartialCurriedObjCMethodType: + case Kind::ObjCMethodType: + case Kind::CFunctionAsMethodType: + case Kind::CurriedCFunctionAsMethodType: + case Kind::PartialCurriedCFunctionAsMethodType: + case Kind::CXXMethodType: + case Kind::CurriedCXXMethodType: + case Kind::PartialCurriedCXXMethodType: + case Kind::Type: + case Kind::Discard: { + auto type = getType(); + + if (type->isTypeParameter() || type->is()) { + // If the function abstraction pattern is completely opaque, assume we + // may need to preserve the address for dependencies. + return true; + } + + auto fnTy = cast(getType()); + + // The parameter might directly be marked addressable. + if (fnTy.getParams()[index].getParameterFlags().isAddressable()) { + return true; + } + + // The parameter could be of a type that is addressable for dependencies, + // in which case it becomes addressable when a return has a scoped + // dependency on it. + for (auto &dep : fnTy->getLifetimeDependencies()) { + auto scoped = dep.getScopeIndices(); + if (!scoped) { + continue; + } + + if (scoped->contains(index)) { + auto paramTy = getFunctionParamType(index); + + return TC.getTypeLowering(paramTy, paramTy.getType(), + TypeExpansionContext::minimal()) + .getRecursiveProperties().isAddressableForDependencies(); + } + } + + return false; + } + } + llvm_unreachable("bad kind"); +} + unsigned AbstractionPattern::getNumFunctionParams() const { return cast(getType()).getParams().size(); } diff --git a/lib/SIL/IR/SILFunctionType.cpp b/lib/SIL/IR/SILFunctionType.cpp index 7c1713b95d742..8ca9642c24030 100644 --- a/lib/SIL/IR/SILFunctionType.cpp +++ b/lib/SIL/IR/SILFunctionType.cpp @@ -1573,15 +1573,18 @@ class DestructureInputs { std::optional ForeignSelf; AbstractionPattern TopLevelOrigType = AbstractionPattern::getInvalid(); SmallVectorImpl &Inputs; + ArrayRef Dependencies; unsigned NextOrigParamIndex = 0; public: DestructureInputs(TypeExpansionContext expansion, TypeConverter &TC, const Conventions &conventions, const ForeignInfo &foreign, std::optional isolationInfo, - SmallVectorImpl &inputs) + SmallVectorImpl &inputs, + ArrayRef dependencies) : expansion(expansion), TC(TC), Convs(conventions), Foreign(foreign), - IsolationInfo(isolationInfo), Inputs(inputs) {} + IsolationInfo(isolationInfo), Inputs(inputs), + Dependencies(dependencies) {} void destructure(AbstractionPattern origType, CanAnyFunctionType::CanParamArrayRef params, @@ -1664,7 +1667,17 @@ class DestructureInputs { // of the sequence. visit() will add foreign parameters that are // positioned after any parameters it adds. maybeAddForeignParameters(); - + + // Parameters may lower differently when the function returns values that + // depends on them. + SmallBitVector paramsWithScopedDependencies(params.size(), false); + for (auto &depInfo : Dependencies) { + if (auto scopeIndices = depInfo.getScopeIndices()) { + paramsWithScopedDependencies + |= depInfo.getScopeIndices()->getBitVector(); + } + } + // Process all the non-self parameters. origType.forEachFunctionParam(params.drop_back(hasSelf ? 1 : 0), /*ignore final orig param*/ hasSelf, @@ -1692,13 +1705,14 @@ class DestructureInputs { addPackParameter(packTy, origFlags.getValueOwnership(), origFlags); return; } - + // If the parameter is not a pack expansion, just pull off the // next parameter and destructure it in parallel with the abstraction // pattern for the type. if (!param.isOrigPackExpansion()) { visit(param.getOrigType(), param.getSubstParams()[0], - /*forSelf*/false); + /*forSelf*/false, + paramsWithScopedDependencies[param.getSubstIndex()]); return; } @@ -1728,7 +1742,8 @@ class DestructureInputs { if (hasSelf && !hasForeignSelf) { auto origParamType = origType.getFunctionParamType(numOrigParams - 1); auto substParam = params.back(); - visit(origParamType, substParam, /*forSelf*/true); + visit(origParamType, substParam, /*forSelf*/true, + paramsWithScopedDependencies[params.size() - 1]); } TopLevelOrigType = AbstractionPattern::getInvalid(); @@ -1736,7 +1751,7 @@ class DestructureInputs { } void visit(AbstractionPattern origType, AnyFunctionType::Param substParam, - bool forSelf) { + bool forSelf, bool hasScopedDependency) { // FIXME: we should really be using the flags from the original // parameter here, right? auto flags = substParam.getParameterFlags(); @@ -1755,18 +1770,30 @@ class DestructureInputs { return addPackParameter(packTy, flags.getValueOwnership(), flags); } - visit(flags.getValueOwnership(), forSelf, origType, substType, flags); + visit(flags.getValueOwnership(), forSelf, hasScopedDependency, + origType, substType, flags); } - void visit(ValueOwnership ownership, bool forSelf, + void visit(ValueOwnership ownership, bool forSelf, bool hasScopedDependency, AbstractionPattern origType, CanType substType, ParameterTypeFlags origFlags) { assert(!isa(substType)); - // If the parameter is marked addressable, lower it with maximal - // abstraction. - if (origFlags.isAddressable()) { - origType = AbstractionPattern::getOpaque(); + if (TC.Context.LangOpts.hasFeature(Feature::AddressableTypes) + || TC.Context.LangOpts.hasFeature(Feature::AddressableParameters)) { + // If the parameter is marked addressable, lower it with maximal + // abstraction. + if (origFlags.isAddressable()) { + origType = AbstractionPattern::getOpaque(); + } else if (hasScopedDependency) { + // If there is a scoped dependency on this parameter, and the parameter + // is addressable-for-dependencies, then lower it with maximal abstraction + // as well. + auto &initialSubstTL = TC.getTypeLowering(origType, substType, expansion); + if (initialSubstTL.getRecursiveProperties().isAddressableForDependencies()) { + origType = AbstractionPattern::getOpaque(); + } + } } // Tuples get expanded unless they're inout. @@ -1814,7 +1841,8 @@ class DestructureInputs { origType.forEachTupleElement(substType, [&](TupleElementGenerator &elt) { if (!elt.isOrigPackExpansion()) { - visit(ownership, forSelf, elt.getOrigType(), elt.getSubstTypes()[0], + visit(ownership, forSelf, /*scoped dependency*/ false, + elt.getOrigType(), elt.getSubstTypes()[0], oldFlags); return; } @@ -1915,7 +1943,8 @@ class DestructureInputs { if (ForeignSelf) { // This is a "self", but it's not a Swift self, we handle it differently. visit(ForeignSelf->SubstSelfParam.getValueOwnership(), - /*forSelf=*/false, ForeignSelf->OrigSelfParam, + /*forSelf=*/false, /*scoped dependency=*/false, + ForeignSelf->OrigSelfParam, ForeignSelf->SubstSelfParam.getParameterType(), {}); } return true; @@ -2488,6 +2517,17 @@ static CanSILFunctionType getSILFunctionType( updateResultTypeForForeignInfo(foreignInfo, genericSig, origResultType, substFormalResultType); + // Lifetime dependencies can influence parameter lowering if there are + // address dependencies. + ArrayRef dependencies = {}; + if (constant) { + if (auto afd = dyn_cast_or_null(constant->getDecl())){ + if (auto declDependencies = afd->getLifetimeDependencies()) { + dependencies = *declDependencies; + } + } + } + // Destructure the input tuple type. SmallVector inputs; { @@ -2501,7 +2541,8 @@ static CanSILFunctionType getSILFunctionType( } } DestructureInputs destructurer(expansionContext, TC, conventions, - foreignInfo, actorIsolation, inputs); + foreignInfo, actorIsolation, inputs, + dependencies); destructurer.destructure(origType, substFnInterfaceType.getParams(), extInfoBuilder, unimplementable); } @@ -2591,7 +2632,7 @@ static CanSILFunctionType getSILFunctionTypeForInitAccessor( ForeignInfo foreignInfo; std::optional actorIsolation; // For now always null. DestructureInputs destructurer(context, TC, conventions, foreignInfo, - actorIsolation, inputs); + actorIsolation, inputs, {}); destructurer.destructure( origType, substAccessorType.getParams(), extInfoBuilder.withRepresentation(SILFunctionTypeRepresentation::Thin), diff --git a/lib/SIL/IR/TypeLowering.cpp b/lib/SIL/IR/TypeLowering.cpp index e74be32ba9313..b5f83c0937892 100644 --- a/lib/SIL/IR/TypeLowering.cpp +++ b/lib/SIL/IR/TypeLowering.cpp @@ -313,6 +313,12 @@ namespace { RecursiveProperties::forTrivial()); } + RecursiveProperties + getTrivialOpaqueRecursiveProperties(IsTypeExpansionSensitive_t isSensitive) { + return mergeIsTypeExpansionSensitive(isSensitive, + RecursiveProperties::forTrivial()); + } + RecursiveProperties getReferenceRecursiveProperties(IsTypeExpansionSensitive_t isSensitive) { return mergeIsTypeExpansionSensitive(isSensitive, @@ -740,11 +746,11 @@ namespace { if (LayoutInfo) { if (LayoutInfo->isFixedSizeTrivial()) { return asImpl().handleTrivial( - type, getTrivialRecursiveProperties(isSensitive)); + type, getTrivialOpaqueRecursiveProperties(isSensitive)); } if (LayoutInfo->isAddressOnlyTrivial()) { - auto properties = getTrivialRecursiveProperties(isSensitive); + auto properties = getTrivialOpaqueRecursiveProperties(isSensitive); properties.setAddressOnly(); return asImpl().handleAddressOnly(type, properties); } diff --git a/lib/SILGen/SILGenApply.cpp b/lib/SILGen/SILGenApply.cpp index 3de7a79a88dea..19414ddb7ed06 100644 --- a/lib/SILGen/SILGenApply.cpp +++ b/lib/SILGen/SILGenApply.cpp @@ -3446,6 +3446,7 @@ class ArgEmitter { // origParamType is a parameter type. void emitSingleArg(ArgumentSource &&arg, AbstractionPattern origParamType, + bool addressable, std::optional param = std::nullopt) { // If this is delayed default argument, prepare to emit the default argument // generator later. @@ -3461,7 +3462,7 @@ class ArgEmitter { maybeEmitForeignArgument(); return; } - emit(std::move(arg), origParamType, param); + emit(std::move(arg), origParamType, addressable, param); maybeEmitForeignArgument(); } @@ -3487,7 +3488,9 @@ class ArgEmitter { // single argument. if (!origFormalParamType.isPackExpansion()) { emitSingleArg(std::move(argSources[nextArgSourceIndex]), - origFormalParamType, params[nextArgSourceIndex]); + origFormalParamType, + origFormalType.isFunctionParamAddressable(SGF.SGM.Types, i), + params[nextArgSourceIndex]); ++nextArgSourceIndex; // Otherwise we need to emit a pack argument. } else { @@ -3506,20 +3509,24 @@ class ArgEmitter { private: void emit(ArgumentSource &&arg, AbstractionPattern origParamType, + bool isAddressable, std::optional origParam = std::nullopt) { - if (origParam && origParam->isAddressable()) { + if (isAddressable && origParam) { // If the function takes an addressable parameter, and its argument is // a reference to an addressable declaration with compatible ownership, // forward the address along in-place. if (arg.isExpr()) { - auto expr = std::move(arg).asKnownExpr(); + arg.dump(); + auto origExpr = std::move(arg).asKnownExpr(); + auto expr = origExpr; if (auto le = dyn_cast(expr)) { expr = le->getSubExpr(); } if (auto dre = dyn_cast(expr)) { if (auto param = dyn_cast(dre->getDecl())) { - if (param->isAddressable() + if (SGF.VarLocs.count(param) + && SGF.VarLocs[param].addressable && param->getValueOwnership() == origParam->getValueOwnership()) { auto addr = SGF.VarLocs[param].value; claimNextParameters(1); @@ -3528,7 +3535,9 @@ class ArgEmitter { } } } - arg = ArgumentSource(expr); + llvm::errs() << "couldn't lower as addressable\n"; + arg = ArgumentSource(origExpr); + arg.dump(); } } @@ -3657,7 +3666,8 @@ class ArgEmitter { if (!origElt.isOrigPackExpansion()) { expander.withElement(origElt.getSubstIndex(), [&](ArgumentSource &&eltSource) { - emit(std::move(eltSource), origElt.getOrigType()); + emit(std::move(eltSource), origElt.getOrigType(), + /*addressable*/ false); }); return; } @@ -4460,7 +4470,8 @@ void DelayedArgument::emitDefaultArgument(SILGenFunction &SGF, info.paramsToEmit, loweredArgs, delayedArgs, ForeignInfo{}); emitter.emitSingleArg(ArgumentSource(info.loc, std::move(value)), - info.origResultType); + info.origResultType, + /*addressable*/ false); assert(delayedArgs.empty()); // Splice the emitted default argument into the argument list. @@ -6134,7 +6145,8 @@ void SILGenFunction::emitYield(SILLocation loc, yieldArgs, delayedArgs, ForeignInfo{}); for (auto i : indices(valueSources)) { - emitter.emitSingleArg(std::move(valueSources[i]), origTypes[i]); + emitter.emitSingleArg(std::move(valueSources[i]), origTypes[i], + /*addressable*/ false); } if (!delayedArgs.empty()) diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index 13f2946a343c5..173421c465926 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -465,11 +465,17 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction /// It may be invalid if no box was made for the value (e.g., because it was /// an inout value, or constant emitted to an alloc_stack). SILValue box; + + /// True if the `value` represents the memory location of a value that is + /// stable for the lifetimes of any dependencies on that value. + bool addressable; - static VarLoc get(SILValue value, SILValue box = SILValue()) { + static VarLoc get(SILValue value, SILValue box = SILValue(), + bool addressable = false) { VarLoc Result; Result.value = value; Result.box = box; + Result.addressable = addressable; return Result; } }; diff --git a/lib/SILGen/SILGenProlog.cpp b/lib/SILGen/SILGenProlog.cpp index 70cc99e54dd93..8b76afdf64827 100644 --- a/lib/SILGen/SILGenProlog.cpp +++ b/lib/SILGen/SILGenProlog.cpp @@ -204,14 +204,17 @@ class EmitBBArguments : public CanTypeVisitorisInOut()) { return handleInOut(origType, substType, pd->isAddressable()); } - // `@_addressable` also suppresses exploding the parameter. - if (pd->isAddressable()) { - return handleScalar(claimNextParameter(), origType, substType, + // Addressability also suppresses exploding the parameter. + if ((SGF.getASTContext().LangOpts.hasFeature(Feature::AddressableTypes) + || SGF.getASTContext().LangOpts.hasFeature(Feature::AddressableParameters)) + && isAddressable) { + return handleScalar(claimNextParameter(), + AbstractionPattern::getOpaque(), substType, /*emitInto*/ nullptr, /*inout*/ false, /*addressable*/ true); } @@ -629,10 +632,15 @@ class ArgumentInitHelper { std::optional FormalParamTypes; + SmallPtrSet ScopedDependencies; + SmallPtrSet AddressableParams; + public: ArgumentInitHelper(SILGenFunction &SGF, - unsigned numIgnoredTrailingParameters) - : SGF(SGF), loweredParams(SGF, numIgnoredTrailingParameters) {} + unsigned numIgnoredTrailingParameters, + llvm::SmallPtrSet &&scopedDependencies) + : SGF(SGF), loweredParams(SGF, numIgnoredTrailingParameters), + ScopedDependencies(std::move(scopedDependencies)) {} /// Emit the given list of parameters. unsigned emitParams(std::optional origFnType, @@ -691,6 +699,11 @@ class ArgumentInitHelper { if (FormalParamTypes) FormalParamTypes->finish(); loweredParams.finish(); + + for (auto addressableParam : AddressableParams) { + assert(SGF.VarLocs.contains(addressableParam)); + SGF.VarLocs[addressableParam].addressable = true; + } return ArgNo; } @@ -723,7 +736,22 @@ class ArgumentInitHelper { auto origType = (FormalParamTypes ? FormalParamTypes->getOrigType() : AbstractionPattern(substType)); - paramValue = argEmitter.handleParam(origType, substType, pd); + // A parameter can be directly marked as addressable, or its + // addressability can be implied by a scoped dependency. + bool isAddressable = false; + + if (SGF.getASTContext().LangOpts.hasFeature(Feature::AddressableTypes) + || SGF.getASTContext().LangOpts.hasFeature(Feature::AddressableParameters)) { + isAddressable = pd->isAddressable() + || (ScopedDependencies.contains(pd) + && SGF.getTypeLowering(origType, substType) + .getRecursiveProperties().isAddressableForDependencies()); + if (isAddressable) { + AddressableParams.insert(pd); + } + } + paramValue = argEmitter.handleParam(origType, substType, pd, + isAddressable); } // Reset the parameter data on the lowered parameter generator. @@ -1619,10 +1647,32 @@ uint16_t SILGenFunction::emitBasicProlog( F.getConventions().hasIndirectSILErrorResults()) { emitIndirectErrorParameter(*this, *errorType, *origErrorType, DC); } + + // Parameters with scoped dependencies may lower differently. + llvm::SmallPtrSet scopedDependencyParams; + if (auto afd = dyn_cast(DC)) { + if (auto deps = afd->getLifetimeDependencies()) { + for (auto &dep : *deps) { + auto scoped = dep.getScopeIndices(); + if (!scoped) { + continue; + } + for (unsigned i = 0, e = paramList->size(); i < e; ++i) { + if (scoped->contains(i)) { + scopedDependencyParams.insert((*paramList)[i]); + } + } + if (scoped->contains(paramList->size())) { + scopedDependencyParams.insert(selfParam); + } + } + } + } // Emit the argument variables in calling convention order. unsigned ArgNo = - ArgumentInitHelper(*this, numIgnoredTrailingParameters) + ArgumentInitHelper(*this, numIgnoredTrailingParameters, + std::move(scopedDependencyParams)) .emitParams(origClosureType, paramList, selfParam); // Record the ArgNo of the artificial $error inout argument. diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index fff0dcb9cb2b2..0085cddbfb42b 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -529,6 +529,7 @@ class AttributeChecker : public AttributeVisitor { void visitSafeAttr(SafeAttr *attr); void visitLifetimeAttr(LifetimeAttr *attr); void visitAddressableSelfAttr(AddressableSelfAttr *attr); + void visitAddressableForDependenciesAttr(AddressableForDependenciesAttr *attr); }; } // end anonymous namespace @@ -7932,6 +7933,18 @@ void AttributeChecker::visitAddressableSelfAttr(AddressableSelfAttr *attr) { } } +void +AttributeChecker::visitAddressableForDependenciesAttr( + AddressableForDependenciesAttr *attr) { + if (!Ctx.LangOpts.hasFeature(Feature::AddressableTypes)) { + Ctx.Diags.diagnose(attr->getLocation(), diag::addressable_types_not_enabled); + } + + if (isa(D)) { + Ctx.Diags.diagnose(attr->getLocation(), diag::class_cannot_be_addressable_for_dependencies); + } +} + namespace { class ClosureAttributeChecker diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index ccbbaf9ea5b74..f96c1f0658930 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1740,6 +1740,7 @@ namespace { UNINTERESTING_ATTR(AddressableSelf) UNINTERESTING_ATTR(Unsafe) UNINTERESTING_ATTR(Safe) + UNINTERESTING_ATTR(AddressableForDependencies) #undef UNINTERESTING_ATTR void visitABIAttr(ABIAttr *attr) { diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 7d67b1b763032..1ad90bfc3f587 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 911; // caller inheriting isolation +const uint16_t SWIFTMODULE_VERSION_MINOR = 912; // @_addressableForDependencies /// A standard hash seed used for all string hashes in a serialized module. /// diff --git a/test/SILGen/addressable_for_dependencies.swift b/test/SILGen/addressable_for_dependencies.swift new file mode 100644 index 0000000000000..cf6ef6aad8b91 --- /dev/null +++ b/test/SILGen/addressable_for_dependencies.swift @@ -0,0 +1,100 @@ +// RUN: %target-swift-emit-silgen -enable-experimental-feature AddressableTypes -enable-experimental-feature LifetimeDependence %s | %FileCheck %s + +// REQUIRES: swift_feature_AddressableTypes +// REQUIRES: swift_feature_LifetimeDependence + +@_addressableForDependencies +struct Foo { var x: String } + +struct Bar { var foo: Foo } + +struct Dep: ~Escapable { + var x: Int + + @lifetime(immortal) + init() { } +} + +// CHECK-LABEL: sil {{.*}}@$s{{.*}}12dependencyOn3foo{{.*}} : +// CHECK-SAME: (@in_guaranteed Foo) -> +@lifetime(borrow foo) +func dependencyOn(foo: Foo) -> Dep { + // CHECK-NOT: load_borrow +} +// CHECK-LABEL: sil {{.*}}@$s{{.*}}12dependencyOn3bar{{.*}} : +// CHECK-SAME: (@in_guaranteed Bar) -> +@lifetime(borrow bar) +func dependencyOn(bar: Bar) -> Dep { + // CHECK-NOT: load_borrow +} + +// CHECK-LABEL: sil {{.*}}@$s{{.*}}12dependencyOn3foo6butNot{{.*}} : +// CHECK-SAME: (@in_guaranteed Foo, @guaranteed Foo) -> +@lifetime(borrow foo) +func dependencyOn(foo: Foo, butNot _: Foo) -> Dep { + // CHECK: bb0(%0 : $*Foo, + // CHECK: apply {{.*}}(%0) + return dependencyOn(foo: foo) +} + +// CHECK-LABEL: sil {{.*}}@$s{{.*}}12dependencyOn3bar6butNot{{.*}} : +// CHECK-SAME: (@in_guaranteed Bar, @guaranteed Bar) -> +@lifetime(borrow bar) +func dependencyOn(bar: Bar, butNot _: Bar) -> Dep { + // CHECK: bb0(%0 : $*Bar, + // CHECK: apply {{.*}}(%0) + return dependencyOn(bar: bar) +} + +extension Foo { + // CHECK-LABEL: sil {{.*}}@$s{{.*}}3FooV16dependencyOnSelf{{.*}} : + // CHECK-SAME: (@in_guaranteed Foo) -> + @lifetime(borrow self) + func dependencyOnSelf() -> Dep { + // CHECK-NOT: load_borrow + } + + // CHECK-LABEL: sil {{.*}}@$s{{.*}}3FooV16dependencyOnSelf6butNot{{.*}} : + // CHECK-SAME: (@guaranteed Foo, @in_guaranteed Foo) -> + @lifetime(borrow self) + func dependencyOnSelf(butNot _: Foo) -> Dep { + // CHECK: bb0({{.*}}, %1 : $*Foo) + // CHECK: apply {{.*}}(%1) + return dependencyOnSelf() + } + + // CHECK-LABEL: sil {{.*}}@$s{{.*}}3FooV19dependencyNotOnSelf{{.*}} : + // CHECK-SAME: (@in_guaranteed Foo, @guaranteed Foo) -> + @lifetime(borrow foo) + func dependencyNotOnSelf(butOn foo: Foo) -> Dep { + // CHECK: bb0(%0 : $*Foo, + // CHECK: apply {{.*}}(%0) + return foo.dependencyOnSelf() + } +} + +extension Bar { + // CHECK-LABEL: sil {{.*}}@$s{{.*}}3BarV16dependencyOnSelf{{.*}} : + // CHECK-SAME: (@in_guaranteed Bar) -> + @lifetime(borrow self) + func dependencyOnSelf() -> Dep { + } + + // CHECK-LABEL: sil {{.*}}@$s{{.*}}3BarV16dependencyOnSelf6butNot{{.*}} : + // CHECK-SAME: (@guaranteed Bar, @in_guaranteed Bar) -> + @lifetime(borrow self) + func dependencyOnSelf(butNot _: Bar) -> Dep { + // CHECK: bb0({{.*}}, %1 : $*Bar) + // CHECK: apply {{.*}}(%1) + return dependencyOnSelf() + } + + // CHECK-LABEL: sil {{.*}}@$s{{.*}}3BarV19dependencyNotOnSelf{{.*}} : + // CHECK-SAME: (@in_guaranteed Bar, @guaranteed Bar) -> + @lifetime(borrow bar) + func dependencyNotOnSelf(butOn bar: Bar) -> Dep { + // CHECK: bb0(%0 : $*Bar, + // CHECK: apply {{.*}}(%0) + return bar.dependencyOnSelf() + } +} From a25edb2185f4336e7678e408266008d8dc9d8046 Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Thu, 2 Jan 2025 20:16:56 -0800 Subject: [PATCH 2/2] Make `Builtin.addressForBorrow` work with addressable parameters. --- lib/SILGen/SILGenApply.cpp | 56 +++++++++++-------- lib/SILGen/SILGenBuiltin.cpp | 49 +++++++++------- lib/SILGen/SILGenFunction.h | 10 ++++ .../addressable_dependencies.swift | 38 +++++++++++++ 4 files changed, 111 insertions(+), 42 deletions(-) create mode 100644 test/SILOptimizer/addressable_dependencies.swift diff --git a/lib/SILGen/SILGenApply.cpp b/lib/SILGen/SILGenApply.cpp index 19414ddb7ed06..942ea5784ee04 100644 --- a/lib/SILGen/SILGenApply.cpp +++ b/lib/SILGen/SILGenApply.cpp @@ -3410,6 +3410,34 @@ Expr *ArgumentSource::findStorageReferenceExprForBorrow() && { return lvExpr; } +ManagedValue +SILGenFunction::tryEmitAddressableParameterAsAddress(ArgumentSource &&arg, + ValueOwnership ownership) { + // If the function takes an addressable parameter, and its argument is + // a reference to an addressable declaration with compatible ownership, + // forward the address along in-place. + if (arg.isExpr()) { + auto origExpr = std::move(arg).asKnownExpr(); + auto expr = origExpr; + + if (auto le = dyn_cast(expr)) { + expr = le->getSubExpr(); + } + if (auto dre = dyn_cast(expr)) { + if (auto param = dyn_cast(dre->getDecl())) { + if (VarLocs.count(param) + && VarLocs[param].addressable + && param->getValueOwnership() == ownership) { + auto addr = VarLocs[param].value; + return ManagedValue::forBorrowedAddressRValue(addr); + } + } + } + arg = ArgumentSource(origExpr); + } + return ManagedValue(); +} + namespace { class ArgEmitter { @@ -3515,29 +3543,11 @@ class ArgEmitter { // If the function takes an addressable parameter, and its argument is // a reference to an addressable declaration with compatible ownership, // forward the address along in-place. - if (arg.isExpr()) { - arg.dump(); - auto origExpr = std::move(arg).asKnownExpr(); - auto expr = origExpr; - - if (auto le = dyn_cast(expr)) { - expr = le->getSubExpr(); - } - if (auto dre = dyn_cast(expr)) { - if (auto param = dyn_cast(dre->getDecl())) { - if (SGF.VarLocs.count(param) - && SGF.VarLocs[param].addressable - && param->getValueOwnership() == origParam->getValueOwnership()) { - auto addr = SGF.VarLocs[param].value; - claimNextParameters(1); - Args.push_back(ManagedValue::forBorrowedAddressRValue(addr)); - return; - } - } - } - llvm::errs() << "couldn't lower as addressable\n"; - arg = ArgumentSource(origExpr); - arg.dump(); + if (auto addr = SGF.tryEmitAddressableParameterAsAddress(std::move(arg), + origParam->getValueOwnership())) { + claimNextParameters(1); + Args.push_back(addr); + return; } } diff --git a/lib/SILGen/SILGenBuiltin.cpp b/lib/SILGen/SILGenBuiltin.cpp index 3b073e663c50b..132aa55af27b7 100644 --- a/lib/SILGen/SILGenBuiltin.cpp +++ b/lib/SILGen/SILGenBuiltin.cpp @@ -492,27 +492,38 @@ static ManagedValue emitBuiltinAddressOfBorrowBuiltins(SILGenFunction &SGF, auto argument = (*argsOrError)[0]; SILValue addr; - // Try to borrow the argument at +0. We only support if it's - // naturally emitted borrowed in memory. - auto borrow = SGF.emitRValue(argument, SGFContext::AllowGuaranteedPlusZero) - .getAsSingleValue(SGF, argument); - if (!SGF.F.getConventions().useLoweredAddresses()) { - auto &context = SGF.getASTContext(); - auto identifier = - stackProtected - ? context.getIdentifier("addressOfBorrowOpaque") - : context.getIdentifier("unprotectedAddressOfBorrowOpaque"); - auto builtin = SGF.B.createBuiltin(loc, identifier, rawPointerType, - substitutions, {borrow.getValue()}); - return ManagedValue::forObjectRValueWithoutOwnership(builtin); - } + // Try to borrow the argument at +0 indirect. + // If the argument is a reference to a borrowed addressable parameter, then + // use that parameter's stable address. + if (auto addressableAddr = SGF.tryEmitAddressableParameterAsAddress( + ArgumentSource(argument), + ValueOwnership::Shared)) { + addr = addressableAddr.getValue(); + } else { + // We otherwise only support the builtin applied to values that + // are naturally emitted borrowed in memory. (But it would probably be good + // to phase this out since it's not really well-defined how long + // the resulting pointer is good for without something like addressability.) + auto borrow = SGF.emitRValue(argument, SGFContext::AllowGuaranteedPlusZero) + .getAsSingleValue(SGF, argument); + if (!SGF.F.getConventions().useLoweredAddresses()) { + auto &context = SGF.getASTContext(); + auto identifier = + stackProtected + ? context.getIdentifier("addressOfBorrowOpaque") + : context.getIdentifier("unprotectedAddressOfBorrowOpaque"); + auto builtin = SGF.B.createBuiltin(loc, identifier, rawPointerType, + substitutions, {borrow.getValue()}); + return ManagedValue::forObjectRValueWithoutOwnership(builtin); + } - if (!borrow.isPlusZero() || !borrow.getType().isAddress()) { - SGF.SGM.diagnose(argument->getLoc(), diag::non_borrowed_indirect_addressof); - return SGF.emitUndef(rawPointerType); - } + if (!borrow.isPlusZero() || !borrow.getType().isAddress()) { + SGF.SGM.diagnose(argument->getLoc(), diag::non_borrowed_indirect_addressof); + return SGF.emitUndef(rawPointerType); + } - addr = borrow.getValue(); + addr = borrow.getValue(); + } // Take the address argument and cast it to RawPointer. SILValue result = SGF.B.createAddressToPointer(loc, addr, rawPointerType, diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index 173421c465926..0ee34ecd002d2 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -1424,6 +1424,16 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction ManagedValue manageBufferForExprResult(SILValue buffer, const TypeLowering &bufferTL, SGFContext C); + + /// Tries to emit an argument referring to an addressable parameter as the + /// stable address of the parameter. + /// + /// Returns a null ManagedValue if the argument is not a parameter reference, + /// the referenced parameter is not addressable, or the requested + /// \c ownership is not compatible with the parameter's ownership. \c arg + /// is consumed only if the operation succeeds. + ManagedValue tryEmitAddressableParameterAsAddress(ArgumentSource &&arg, + ValueOwnership ownership); //===--------------------------------------------------------------------===// // Type conversions for expr emission and thunks diff --git a/test/SILOptimizer/addressable_dependencies.swift b/test/SILOptimizer/addressable_dependencies.swift new file mode 100644 index 0000000000000..c1e74200aa887 --- /dev/null +++ b/test/SILOptimizer/addressable_dependencies.swift @@ -0,0 +1,38 @@ +// RUN: %target-swift-frontend -emit-sil -enable-experimental-feature BuiltinModule -enable-experimental-feature LifetimeDependence -enable-experimental-feature AddressableTypes %s | %FileCheck %s + +// REQUIRES: swift_feature_BuiltinModule +// REQUIRES: swift_feature_AddressableTypes +// REQUIRES: swift_feature_LifetimeDependence + +import Builtin + +@_addressableForDependencies +struct Node { + var id: String + + var ref: NodeRef { + // CHECK-LABEL: sil {{.*}}@${{.*}}4NodeV3ref{{.*}}Vvg : + // CHECK-SAME: (@in_guaranteed Node) -> + @lifetime(borrow self) + borrowing get { + // CHECK: bb0(%0 : @noImplicitCopy $*Node): + // CHECK: [[REF:%.*]] = apply {{.*}}(%0, + // CHECK: mark_dependence [nonescaping] [[REF]] on %0 + return NodeRef(node: self) + } + } +} + +struct NodeRef: ~Escapable { + private var parent: UnsafePointer + + // CHECK-LABEL: sil {{.*}}@${{.*}}7NodeRefV4node{{.*}}fC : + // CHECK-SAME: (@in_guaranteed Node, + @lifetime(borrow node) + init(node: borrowing Node) { + // CHECK: bb0(%0 : @noImplicitCopy $*Node, + // CHECK: [[RAW_PTR:%.*]] = address_to_pointer {{.*}}%0 + // CHECK: struct $UnsafePointer ([[RAW_PTR]]) + self.parent = UnsafePointer(Builtin.addressOfBorrow(node)) + } +}