diff --git a/include/swift/AST/ASTBridging.h b/include/swift/AST/ASTBridging.h index 57ea7a76f43b1..8c3b09c316275 100644 --- a/include/swift/AST/ASTBridging.h +++ b/include/swift/AST/ASTBridging.h @@ -1235,6 +1235,7 @@ enum ENUM_EXTENSIBILITY_ATTR(open) BridgedAttributedTypeSpecifier : size_t { BridgedAttributedTypeSpecifierConst, BridgedAttributedTypeSpecifierIsolated, BridgedAttributedTypeSpecifierResultDependsOn, + BridgedAttributedTypeSpecifierTransferring, }; SWIFT_NAME("BridgedSimpleIdentTypeRepr.createParsed(_:loc:name:)") diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index fdcd1a48935aa..e9cdeacdc09fe 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -549,6 +549,10 @@ SIMPLE_DECL_ATTR(_noObjCBridging, NoObjCBridging, CONTEXTUAL_SIMPLE_DECL_ATTR(_resultDependsOn, ResultDependsOn, OnParam | DeclModifier | UserInaccessible | ABIBreakingToAdd | ABIStableToRemove | APIBreakingToAdd | APIStableToRemove, 156) +CONTEXTUAL_SIMPLE_DECL_ATTR(transferring, Transferring, + OnParam | DeclModifier | UserInaccessible | NotSerialized | ABIBreakingToAdd | ABIBreakingToRemove | APIBreakingToAdd | APIStableToRemove, + 157) + #undef TYPE_ATTR #undef DECL_ATTR_ALIAS #undef CONTEXTUAL_DECL_ATTR_ALIAS diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 33b9b7cc1570c..07c06ba096cd3 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -6727,6 +6727,7 @@ class ParamDecl : public VarDecl { return true; case Specifier::Consuming: case Specifier::InOut: + case Specifier::Transferring: return false; } llvm_unreachable("unhandled specifier"); @@ -6744,6 +6745,7 @@ class ParamDecl : public VarDecl { case ParamSpecifier::LegacyShared: return ValueOwnership::Shared; case ParamSpecifier::Consuming: + case ParamSpecifier::Transferring: case ParamSpecifier::LegacyOwned: return ValueOwnership::Owned; case ParamSpecifier::Default: diff --git a/include/swift/AST/DiagnosticsSIL.def b/include/swift/AST/DiagnosticsSIL.def index 0d3969747c69f..f77e18d11e7cc 100644 --- a/include/swift/AST/DiagnosticsSIL.def +++ b/include/swift/AST/DiagnosticsSIL.def @@ -898,6 +898,12 @@ WARNING(regionbasedisolation_transfer_yields_race_no_isolation, none, WARNING(regionbasedisolation_transfer_yields_race_with_isolation, none, "passing argument of non-sendable type %0 from %1 context to %2 context at this call site could yield a race with accesses later in this function", (Type, ActorIsolation, ActorIsolation)) +WARNING(regionbasedisolation_transfer_yields_race_transferring_parameter, none, + "transferred value of non-Sendable type %0 into transferring parameter; later accesses could result in races", + (Type)) +WARNING(regionbasedisolation_transfer_yields_race_stronglytransferred_binding, none, + "binding of non-Sendable type %0 accessed after being transferred; later accesses could result in races", + (Type)) NOTE(regionbasedisolation_maybe_race, none, "access here could race", ()) ERROR(regionbasedisolation_unknown_pattern, none, diff --git a/include/swift/AST/Types.h b/include/swift/AST/Types.h index 19af7d5c529c9..d3820cf634091 100644 --- a/include/swift/AST/Types.h +++ b/include/swift/AST/Types.h @@ -2211,7 +2211,11 @@ enum class ParamSpecifier : uint8_t { /// `__shared`, a legacy spelling of `borrowing`. LegacyShared = 4, /// `__owned`, a legacy spelling of `consuming`. - LegacyOwned = 5 + LegacyOwned = 5, + + /// `transferring`. Indicating the transfer of a value from one isolation + /// domain to another. + Transferring = 6, }; /// Provide parameter type relevant flags, i.e. variadic, autoclosure, and @@ -2228,7 +2232,8 @@ class ParameterTypeFlags { Isolated = 1 << 7, CompileTimeConst = 1 << 8, ResultDependsOn = 1 << 9, - NumBits = 10 + Transferring = 1 << 10, + NumBits = 11 }; OptionSet value; static_assert(NumBits <= 8*sizeof(OptionSet), "overflowed"); @@ -2243,20 +2248,22 @@ class ParameterTypeFlags { ParameterTypeFlags(bool variadic, bool autoclosure, bool nonEphemeral, ParamSpecifier specifier, bool isolated, bool noDerivative, - bool compileTimeConst, bool hasResultDependsOn) + bool compileTimeConst, bool hasResultDependsOn, + bool isTransferring) : value((variadic ? Variadic : 0) | (autoclosure ? AutoClosure : 0) | (nonEphemeral ? NonEphemeral : 0) | uint8_t(specifier) << SpecifierShift | (isolated ? Isolated : 0) | (noDerivative ? NoDerivative : 0) | (compileTimeConst ? CompileTimeConst : 0) | - (hasResultDependsOn ? ResultDependsOn : 0)) {} + (hasResultDependsOn ? ResultDependsOn : 0) | + (isTransferring ? Transferring : 0)) {} /// Create one from what's present in the parameter type inline static ParameterTypeFlags fromParameterType(Type paramTy, bool isVariadic, bool isAutoClosure, bool isNonEphemeral, ParamSpecifier ownership, bool isolated, bool isNoDerivative, bool compileTimeConst, - bool hasResultDependsOn); + bool hasResultDependsOn, bool isTransferring); bool isNone() const { return !value; } bool isVariadic() const { return value.contains(Variadic); } @@ -2269,6 +2276,7 @@ class ParameterTypeFlags { bool isCompileTimeConst() const { return value.contains(CompileTimeConst); } bool isNoDerivative() const { return value.contains(NoDerivative); } bool hasResultDependsOn() const { return value.contains(ResultDependsOn); } + bool isTransferring() const { return value.contains(Transferring); } /// Get the spelling of the parameter specifier used on the parameter. ParamSpecifier getOwnershipSpecifier() const { @@ -2331,6 +2339,12 @@ class ParameterTypeFlags { : value - ParameterTypeFlags::NoDerivative); } + ParameterTypeFlags withTransferring(bool withTransferring) const { + return ParameterTypeFlags(withTransferring + ? value | ParameterTypeFlags::Transferring + : value - ParameterTypeFlags::Transferring); + } + bool operator ==(const ParameterTypeFlags &other) const { return value.toRaw() == other.value.toRaw(); } @@ -2424,7 +2438,8 @@ class YieldTypeFlags { /*nonEphemeral*/ false, getOwnershipSpecifier(), /*isolated*/ false, /*noDerivative*/ false, /*compileTimeConst*/ false, - /*hasResultDependsOn*/ false); + /*hasResultDependsOn*/ false, + /*is transferring*/ false); } bool operator ==(const YieldTypeFlags &other) const { @@ -4111,6 +4126,9 @@ class SILParameterInfo { /// - If the function type is `@differentiable`, the function is /// differentiable with respect to this parameter. NotDifferentiable = 0x1, + + /// Set if the given parameter is transferring. + Transferring = 0x2, }; using Options = OptionSet; @@ -7627,7 +7645,7 @@ inline TupleTypeElt TupleTypeElt::getWithType(Type T) const { inline ParameterTypeFlags ParameterTypeFlags::fromParameterType( Type paramTy, bool isVariadic, bool isAutoClosure, bool isNonEphemeral, ParamSpecifier ownership, bool isolated, bool isNoDerivative, - bool compileTimeConst, bool hasResultDependsOn) { + bool compileTimeConst, bool hasResultDependsOn, bool isTransferring) { // FIXME(Remove InOut): The last caller that needs this is argument // decomposition. Start by enabling the assertion there and fixing up those // callers, then remove this, then remove @@ -7637,8 +7655,9 @@ inline ParameterTypeFlags ParameterTypeFlags::fromParameterType( ownership == ParamSpecifier::InOut); ownership = ParamSpecifier::InOut; } - return {isVariadic, isAutoClosure, isNonEphemeral, ownership, - isolated, isNoDerivative, compileTimeConst, hasResultDependsOn}; + return {isVariadic, isAutoClosure, isNonEphemeral, + ownership, isolated, isNoDerivative, + compileTimeConst, hasResultDependsOn, isTransferring}; } inline const Type *BoundGenericType::getTrailingObjectsPointer() const { diff --git a/include/swift/SIL/ApplySite.h b/include/swift/SIL/ApplySite.h index 38a95b0a2ec0f..ac06eef3ce5b3 100644 --- a/include/swift/SIL/ApplySite.h +++ b/include/swift/SIL/ApplySite.h @@ -721,6 +721,18 @@ class FullApplySite : public ApplySite { getNumIndirectSILErrorResults()); } + MutableArrayRef getOperandsWithoutIndirectResults() const { + return getArgumentOperands().slice(getNumIndirectSILResults() + + getNumIndirectSILErrorResults()); + } + + MutableArrayRef getOperandsWithoutIndirectResultsOrSelf() const { + auto ops = getOperandsWithoutIndirectResults(); + if (!hasSelfArgument()) + return ops; + return ops.drop_back(); + } + InoutArgumentRange getInoutArguments() const { switch (getKind()) { case FullApplySiteKind::ApplyInst: @@ -789,6 +801,15 @@ class FullApplySite : public ApplySite { } } + SILParameterInfo getArgumentParameterInfo(const Operand &oper) const { + assert(!getArgumentConvention(oper).isIndirectOutParameter() && + "Can only be applied to non-out parameters"); + + // The ParameterInfo is going to be the parameter in the caller. + unsigned calleeArgIndex = getCalleeArgIndex(oper); + return getSubstCalleeConv().getParamInfoForSILArg(calleeArgIndex); + } + static FullApplySite getFromOpaqueValue(void *p) { return FullApplySite(p); } static bool classof(const SILInstruction *inst) { diff --git a/include/swift/SIL/SILArgument.h b/include/swift/SIL/SILArgument.h index b464b1c41336f..6fd5a0aa5899a 100644 --- a/include/swift/SIL/SILArgument.h +++ b/include/swift/SIL/SILArgument.h @@ -434,6 +434,10 @@ class SILFunctionArgument : public SILArgument { sharedUInt32().SILFunctionArgument.hasResultDependsOn = flag; } + bool isTransferring() const { + return getKnownParameterInfo().hasOption(SILParameterInfo::Transferring); + } + Lifetime getLifetime() const { return getType() .getLifetime(*getFunction()) diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 750d42c0ec612..55f3a37591c06 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -3009,6 +3009,10 @@ class ApplyInstBase return getArguments().slice(getNumIndirectResults()); } + MutableArrayRef getOperandsWithoutIndirectResults() const { + return getArgumentOperands().slice(getNumIndirectResults()); + } + /// Returns all `@inout` and `@inout_aliasable` arguments passed to the /// instruction. InoutArgumentRange getInoutArguments() const { diff --git a/include/swift/SIL/SILValue.h b/include/swift/SIL/SILValue.h index ac00befbbab77..52fef5a844031 100644 --- a/include/swift/SIL/SILValue.h +++ b/include/swift/SIL/SILValue.h @@ -1655,6 +1655,15 @@ namespace llvm { enum { NumLowBitsAvailable = swift::SILValue::NumLowBitsAvailable }; }; + /// A SILValue can be checked if a value is present, so we can use it with + /// dyn_cast_or_null. + template <> + struct ValueIsPresent { + using SILValue = swift::SILValue; + using UnwrappedType = SILValue; + static inline bool isPresent(const SILValue &t) { return bool(t); } + static inline decltype(auto) unwrapValue(SILValue &t) { return t; } + }; } // end namespace llvm #endif diff --git a/include/swift/SILOptimizer/Analysis/RegionAnalysis.h b/include/swift/SILOptimizer/Analysis/RegionAnalysis.h index a1e1c33b74c95..a3c350a339c01 100644 --- a/include/swift/SILOptimizer/Analysis/RegionAnalysis.h +++ b/include/swift/SILOptimizer/Analysis/RegionAnalysis.h @@ -254,6 +254,10 @@ class regionanalysisimpl::TrackableValue { TrackableValueState getValueState() const { return valueState; } + /// Returns true if this TrackableValue is an alloc_stack from a transferring + /// parameter. + bool isTransferringParameter() const; + void print(llvm::raw_ostream &os) const { os << "TrackableValue. State: "; valueState.print(os); diff --git a/lib/AST/ASTBridging.cpp b/lib/AST/ASTBridging.cpp index c9757afe6fe46..259e53f1ced55 100644 --- a/lib/AST/ASTBridging.cpp +++ b/lib/AST/ASTBridging.cpp @@ -1704,6 +1704,10 @@ BridgedSpecifierTypeRepr BridgedSpecifierTypeRepr_createParsed( return new (context) OwnershipTypeRepr(baseType, ParamSpecifier::LegacyOwned, loc); } + case BridgedAttributedTypeSpecifierTransferring: { + return new (context) + OwnershipTypeRepr(baseType, ParamSpecifier::Transferring, loc); + } case BridgedAttributedTypeSpecifierConst: { return new (context) CompileTimeConstTypeRepr(baseType, loc); } diff --git a/lib/AST/ASTMangler.cpp b/lib/AST/ASTMangler.cpp index a81d80d482fcd..1ba3aadab771d 100644 --- a/lib/AST/ASTMangler.cpp +++ b/lib/AST/ASTMangler.cpp @@ -2875,7 +2875,8 @@ getParameterFlagsForMangling(ParameterTypeFlags flags, // `inout` should already be specified in the flags. case ParamSpecifier::InOut: return flags; - + + case ParamSpecifier::Transferring: case ParamSpecifier::Consuming: case ParamSpecifier::Borrowing: // Only mangle the ownership if it diverges from the default. diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index 62a033164aa37..f012a604e0240 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -3906,7 +3906,15 @@ static bool usesFeatureExtractConstantsFromMembers(Decl *decl) { static bool usesFeatureBitwiseCopyable(Decl *decl) { return false; } -static bool usesFeatureTransferringArgsAndResults(Decl *decl) { return false; } +static bool usesFeatureTransferringArgsAndResults(Decl *decl) { + if (auto *pd = dyn_cast(decl)) + if (pd->getSpecifier() == ParamSpecifier::Transferring) + return true; + + // TODO: Results. + + return false; +} static bool usesFeaturePreconcurrencyConformances(Decl *decl) { auto usesPreconcurrencyConformance = [&](const InheritedTypes &inherited) { @@ -4567,6 +4575,8 @@ static void printParameterFlags(ASTPrinter &printer, printer.printAttrName("@autoclosure "); if (!options.excludeAttrKind(TAK_noDerivative) && flags.isNoDerivative()) printer.printAttrName("@noDerivative "); + if (flags.isTransferring()) + printer.printAttrName("@transferring "); switch (flags.getOwnershipSpecifier()) { case ParamSpecifier::Default: @@ -4587,6 +4597,9 @@ static void printParameterFlags(ASTPrinter &printer, case ParamSpecifier::LegacyOwned: printer.printKeyword("__owned", options, " "); break; + case ParamSpecifier::Transferring: + printer.printKeyword("transferring", options, " "); + break; } if (flags.isIsolated()) @@ -8068,6 +8081,11 @@ void SILParameterInfo::print(ASTPrinter &Printer, Printer << "@noDerivative "; } + if (options.contains(SILParameterInfo::Transferring)) { + options -= SILParameterInfo::Transferring; + Printer << "@transferring "; + } + // If we did not handle a case in Options, this code was not updated // appropriately. assert(!bool(options) && "Code not updated for introduced option"); diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index 2b703ab7b6ffb..f8e85fb01a726 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -7746,6 +7746,7 @@ void ParamDecl::setSpecifier(Specifier specifier) { // `inout` and `consuming` parameters are locally mutable. case ParamSpecifier::InOut: case ParamSpecifier::Consuming: + case ParamSpecifier::Transferring: introducer = VarDecl::Introducer::Var; break; } @@ -7809,6 +7810,8 @@ StringRef ParamDecl::getSpecifierSpelling(ParamSpecifier specifier) { return "__shared"; case ParamSpecifier::LegacyOwned: return "__owned"; + case ParamSpecifier::Transferring: + return "transferring"; } llvm_unreachable("invalid ParamSpecifier"); } @@ -8410,7 +8413,8 @@ AnyFunctionType::Param ParamDecl::toFunctionParam(Type type) const { auto flags = ParameterTypeFlags::fromParameterType( type, isVariadic(), isAutoClosure(), isNonEphemeral(), getSpecifier(), isIsolated(), /*isNoDerivative*/ false, isCompileTimeConst(), - hasResultDependsOn()); + hasResultDependsOn(), + getSpecifier() == ParamDecl::Specifier::Transferring /*is transferring*/); return AnyFunctionType::Param(type, label, flags, internalLabel); } diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 9c5cd12b6b62a..656432d829d18 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -5137,6 +5137,7 @@ ParserStatus Parser::ParsedTypeAttributeList::slowParse(Parser &P) { Tok.isContextualKeyword("isolated") || Tok.isContextualKeyword("consuming") || Tok.isContextualKeyword("borrowing") || + Tok.isContextualKeyword("transferring") || Tok.isContextualKeyword("_const") || Tok.isContextualKeyword("_resultDependsOn")))) { @@ -5164,6 +5165,15 @@ ParserStatus Parser::ParsedTypeAttributeList::slowParse(Parser &P) { continue; } + // Perform an extra check for transferring. Since it is a specifier, we use + // the actual parsing logic below. + if (Tok.isContextualKeyword("transferring")) { + if (!P.Context.LangOpts.hasFeature(Feature::TransferringArgsAndResults)) { + P.diagnose(Tok, diag::requires_experimental_feature, "transferring", + false, getFeatureName(Feature::TransferringArgsAndResults)); + } + } + if (SpecifierLoc.isValid()) { P.diagnose(Tok, diag::parameter_specifier_repeated) .fixItRemove(SpecifierLoc); @@ -5177,6 +5187,8 @@ ParserStatus Parser::ParsedTypeAttributeList::slowParse(Parser &P) { Specifier = ParamDecl::Specifier::LegacyOwned; } else if (Tok.getRawText().equals("borrowing")) { Specifier = ParamDecl::Specifier::Borrowing; + } else if (Tok.getRawText().equals("transferring")) { + Specifier = ParamDecl::Specifier::Transferring; } else if (Tok.getRawText().equals("consuming")) { Specifier = ParamDecl::Specifier::Consuming; } @@ -7539,6 +7551,8 @@ static ParserStatus parseAccessorIntroducer(Parser &P, P.parseNewDeclAttribute(Attributes, /*AtLoc*/ {}, DAK_Consuming); } else if (P.Tok.isContextualKeyword("borrowing")) { P.parseNewDeclAttribute(Attributes, /*AtLoc*/ {}, DAK_Borrowing); + } else if (P.Tok.isContextualKeyword("transferring")) { + P.parseNewDeclAttribute(Attributes, /*AtLoc*/ {}, DAK_Transferring); } } diff --git a/lib/Parse/ParsePattern.cpp b/lib/Parse/ParsePattern.cpp index dd81cd0ec4f5b..e5d34dd230553 100644 --- a/lib/Parse/ParsePattern.cpp +++ b/lib/Parse/ParsePattern.cpp @@ -139,6 +139,8 @@ bool Parser::startsParameterName(bool isClosure) { !Tok.isContextualKeyword("__shared") && !Tok.isContextualKeyword("__owned") && !Tok.isContextualKeyword("borrowing") && + (!Context.LangOpts.hasFeature(Feature::TransferringArgsAndResults) || + !Tok.isContextualKeyword("transferring")) && !Tok.isContextualKeyword("consuming") && !Tok.is(tok::kw_repeat) && (!Context.LangOpts.hasFeature(Feature::NonescapableTypes) || !Tok.isContextualKeyword("_resultDependsOn"))) @@ -236,7 +238,9 @@ Parser::parseParameterClause(SourceLoc &leftParenLoc, Tok.isContextualKeyword("isolated") || Tok.isContextualKeyword("_const") || (Context.LangOpts.hasFeature(Feature::NonescapableTypes) && - Tok.isContextualKeyword("_resultDependsOn"))))) { + Tok.isContextualKeyword("_resultDependsOn")) || + (Context.LangOpts.hasFeature( + Feature::TransferringArgsAndResults))))) { // is this token the identifier of an argument label? `inout` is a // reserved keyword but the other modifiers are not. if (!Tok.is(tok::kw_inout)) { @@ -288,7 +292,13 @@ Parser::parseParameterClause(SourceLoc &leftParenLoc, } else if (Tok.isContextualKeyword("__owned")) { param.SpecifierKind = ParamDecl::Specifier::LegacyOwned; param.SpecifierLoc = consumeToken(); + } else if (Context.LangOpts.hasFeature( + Feature::TransferringArgsAndResults) && + Tok.isContextualKeyword("transferring")) { + param.SpecifierKind = ParamDecl::Specifier::Transferring; + param.SpecifierLoc = consumeToken(); } + hasSpecifier = true; } else { // Redundant specifiers are fairly common, recognize, reject, and diff --git a/lib/Parse/ParseType.cpp b/lib/Parse/ParseType.cpp index 6c791dae062bf..e7b9550a12336 100644 --- a/lib/Parse/ParseType.cpp +++ b/lib/Parse/ParseType.cpp @@ -163,7 +163,9 @@ ParserResult Parser::parseTypeSimple( Tok.getRawText().equals("consuming") || Tok.getRawText().equals("borrowing") || (Context.LangOpts.hasFeature(Feature::NonescapableTypes) && - Tok.getRawText().equals("resultDependsOn"))))) { + Tok.getRawText().equals("resultDependsOn")) || + (Context.LangOpts.hasFeature(Feature::TransferringArgsAndResults) && + Tok.getRawText().equals("transferring"))))) { // Type specifier should already be parsed before here. This only happens // for construct like 'P1 & inout P2'. diagnose(Tok.getLoc(), diag::attr_only_on_parameters, Tok.getRawText()); diff --git a/lib/SIL/IR/SILFunctionType.cpp b/lib/SIL/IR/SILFunctionType.cpp index cf5c3e1409c45..7f5abed153e90 100644 --- a/lib/SIL/IR/SILFunctionType.cpp +++ b/lib/SIL/IR/SILFunctionType.cpp @@ -1584,8 +1584,7 @@ class DestructureInputs { auto packTy = SILPackType::get(TC.Context, extInfo, {loweredParamTy}); auto origFlags = param.getOrigFlags(); - addPackParameter(packTy, origFlags.getValueOwnership(), - origFlags.isNoDerivative()); + addPackParameter(packTy, origFlags.getValueOwnership(), origFlags); return; } @@ -1615,8 +1614,7 @@ class DestructureInputs { auto packTy = SILPackType::get(TC.Context, extInfo, packElts); auto origFlags = param.getOrigFlags(); - addPackParameter(packTy, origFlags.getValueOwnership(), - origFlags.isNoDerivative()); + addPackParameter(packTy, origFlags.getValueOwnership(), origFlags); }); // Process the self parameter. But if we have a formal foreign self @@ -1649,23 +1647,20 @@ class DestructureInputs { bool indirect = true; SILPackType::ExtInfo extInfo(/*address*/ indirect); auto packTy = SILPackType::get(TC.Context, extInfo, {substType}); - return addPackParameter(packTy, flags.getValueOwnership(), - flags.isNoDerivative()); + return addPackParameter(packTy, flags.getValueOwnership(), flags); } - visit(flags.getValueOwnership(), forSelf, origType, substType, - flags.isNoDerivative()); + visit(flags.getValueOwnership(), forSelf, origType, substType, flags); } void visit(ValueOwnership ownership, bool forSelf, AbstractionPattern origType, CanType substType, - bool isNonDifferentiable) { + ParameterTypeFlags origFlags) { assert(!isa(substType)); // Tuples get expanded unless they're inout. if (origType.isTuple() && ownership != ValueOwnership::InOut) { - expandTuple(ownership, forSelf, origType, substType, - isNonDifferentiable); + expandTuple(ownership, forSelf, origType, substType, origFlags); return; } @@ -1696,20 +1691,20 @@ class DestructureInputs { assert(!isIndirectFormalParameter(convention)); } - addParameter(loweredType, convention, isNonDifferentiable); + addParameter(loweredType, convention, origFlags); } /// Recursively expand a tuple type into separate parameters. void expandTuple(ValueOwnership ownership, bool forSelf, AbstractionPattern origType, CanType substType, - bool isNonDifferentiable) { + ParameterTypeFlags oldFlags) { assert(ownership != ValueOwnership::InOut); assert(origType.isTuple()); origType.forEachTupleElement(substType, [&](TupleElementGenerator &elt) { if (!elt.isOrigPackExpansion()) { visit(ownership, forSelf, elt.getOrigType(), elt.getSubstTypes()[0], - isNonDifferentiable); + oldFlags); return; } @@ -1728,27 +1723,30 @@ class DestructureInputs { SILPackType::ExtInfo extInfo(/*address*/ indirect); auto packTy = SILPackType::get(TC.Context, extInfo, packElts); - addPackParameter(packTy, ownership, isNonDifferentiable); + addPackParameter(packTy, ownership, oldFlags); }); } /// Add a parameter that we derived from deconstructing the /// formal type. void addParameter(CanType loweredType, ParameterConvention convention, - bool isNonDifferentiable) { + ParameterTypeFlags origFlags) { SILParameterInfo param(loweredType, convention); - if (isNonDifferentiable) + + if (origFlags.isNoDerivative()) param = param.addingOption(SILParameterInfo::NotDifferentiable); - Inputs.push_back(param); + if (origFlags.isTransferring()) + param = param.addingOption(SILParameterInfo::Transferring); + Inputs.push_back(param); maybeAddForeignParameters(); } void addPackParameter(CanSILPackType packTy, ValueOwnership ownership, - bool isNonDifferentiable) { + ParameterTypeFlags origFlags) { unsigned origParamIndex = NextOrigParamIndex++; auto convention = Convs.getPack(ownership, origParamIndex); - addParameter(packTy, convention, isNonDifferentiable); + addParameter(packTy, convention, origFlags); } /// Given that we've just reached an argument index for the @@ -1803,7 +1801,7 @@ class DestructureInputs { // This is a "self", but it's not a Swift self, we handle it differently. visit(ForeignSelf->SubstSelfParam.getValueOwnership(), /*forSelf=*/false, ForeignSelf->OrigSelfParam, - ForeignSelf->SubstSelfParam.getParameterType(), false); + ForeignSelf->SubstSelfParam.getParameterType(), {}); } return true; } diff --git a/lib/SILGen/SILGenLValue.cpp b/lib/SILGen/SILGenLValue.cpp index 97e42f47fde36..ad7cbb6875ff1 100644 --- a/lib/SILGen/SILGenLValue.cpp +++ b/lib/SILGen/SILGenLValue.cpp @@ -3008,6 +3008,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenBorrowedBaseVisitor case ParamSpecifier::InOut: case ParamSpecifier::LegacyShared: case ParamSpecifier::LegacyOwned: + // For now, transferring isn't implicitly copyable. + case ParamSpecifier::Transferring: return false; } if (pd->hasResultDependsOn()) { diff --git a/lib/SILGen/SILGenProlog.cpp b/lib/SILGen/SILGenProlog.cpp index 0a864017621cc..52e3608261500 100644 --- a/lib/SILGen/SILGenProlog.cpp +++ b/lib/SILGen/SILGenProlog.cpp @@ -860,6 +860,7 @@ class ArgumentInitHelper { argrv = ManagedValue::forBorrowedAddressRValue( SGF.B.createCopyableToMoveOnlyWrapperAddr(pd, argrv.getValue())); break; + case swift::ParamSpecifier::Transferring: case swift::ParamSpecifier::Consuming: case swift::ParamSpecifier::Default: case swift::ParamSpecifier::InOut: diff --git a/lib/SILOptimizer/Analysis/RegionAnalysis.cpp b/lib/SILOptimizer/Analysis/RegionAnalysis.cpp index 152abcbef7122..86828dbd6b1a7 100644 --- a/lib/SILOptimizer/Analysis/RegionAnalysis.cpp +++ b/lib/SILOptimizer/Analysis/RegionAnalysis.cpp @@ -57,8 +57,10 @@ using namespace swift::regionanalysisimpl; static bool isIsolationBoundaryCrossingApply(SILInstruction *inst) { if (ApplyExpr *apply = inst->getLoc().getAsASTNode()) return apply->getIsolationCrossing().has_value(); - if (auto fas = FullApplySite::isa(inst)) - return bool(fas.getIsolationCrossing()); + if (auto fas = FullApplySite::isa(inst)) { + if (bool(fas.getIsolationCrossing())) + return true; + } // We assume that any instruction that does not correspond to an ApplyExpr // cannot cross an isolation domain. @@ -249,8 +251,6 @@ static SILValue getUnderlyingTrackedValue(SILValue value) { UseDefChainVisitor visitor; SILValue base = visitor.visitAll(value); assert(base); - if (isa(base)) - return value; if (base->getType().isObject()) return getUnderlyingObject(base); return base; @@ -400,6 +400,85 @@ static bool isGlobalActorInit(SILFunction *fn) { return globalDecl->getGlobalActorAttr() != std::nullopt; } +/// Returns true if this is a function argument that is able to be transferred +/// in the body of our function. +static bool isTransferrableFunctionArgument(SILFunctionArgument *arg) { + // Indirect out parameters cannot be an input transferring parameter. + if (arg->getArgumentConvention().isIndirectOutParameter()) + return false; + + // If we have a function argument that is closure captured by a Sendable + // closure, allow for the argument to be transferred. + // + // DISCUSSION: The reason that we do this is that in the case of us + // having an actual Sendable closure there are two cases we can see: + // + // 1. If we have an actual Sendable closure, the AST will emit an + // earlier error saying that we are capturing a non-Sendable value in a + // Sendable closure. So we want to squelch the error that we would emit + // otherwise. This only occurs when we are not in swift-6 mode since in + // swift-6 mode we will error on the earlier error... but in the case of + // us not being in swift 6 mode lets not emit extra errors. + // + // 2. If we have an async-let based Sendable closure, we want to allow + // for the argument to be transferred in the async let's statement and + // not emit an error. + if (arg->isClosureCapture() && + arg->getFunction()->getLoweredFunctionType()->isSendable()) + return true; + + // Otherwise, we only allow for the argument to be transferred if it is + // explicitly marked as a strong transferring parameter. + return arg->isTransferring(); +} + +//===----------------------------------------------------------------------===// +// MARK: TrackableValue +//===----------------------------------------------------------------------===// + +bool TrackableValue::isTransferringParameter() const { + // First get our alloc_stack. + // + // TODO: We should just put a flag on the alloc_stack, so we /know/ 100% that + // it is from a consuming parameter. We don't have that so we pattern match. + auto *asi = + dyn_cast_or_null(representativeValue.maybeGetValue()); + if (!asi) + return false; + + if (asi->getParent() != asi->getFunction()->getEntryBlock()) + return false; + + // See if we are initialized from a transferring parameter and are the only + // use of the parameter. + for (auto *use : asi->getUses()) { + auto *user = use->getUser(); + + if (auto *si = dyn_cast(user)) { + // Check if our store inst is from a function argument that is + // transferring. If not, then this isn't the consuming parameter + // alloc_stack. + auto *fArg = dyn_cast(si->getSrc()); + if (!fArg || !fArg->isTransferring()) + return false; + return fArg->getSingleUse(); + } + + if (auto *copyAddr = dyn_cast(user)) { + // Check if our store inst is from a function argument that is + // transferring. If not, then this isn't the consuming parameter + // alloc_stack. + auto *fArg = dyn_cast(copyAddr->getSrc()); + if (!fArg || !fArg->isTransferring()) + return false; + return fArg->getSingleUse(); + } + } + + // Otherwise, this isn't a consuming parameter. + return false; +} + //===----------------------------------------------------------------------===// // MARK: Partial Apply Reachability //===----------------------------------------------------------------------===// @@ -683,7 +762,8 @@ void InferredCallerArgumentTypeInfo::initForApply( void InferredCallerArgumentTypeInfo::initForApply(const Operand *op, ApplyExpr *sourceApply) { - auto isolationCrossing = *sourceApply->getIsolationCrossing(); + auto isolationCrossing = sourceApply->getIsolationCrossing(); + assert(isolationCrossing && "Should have valid isolation crossing?!"); // Grab out full apply site and see if we can find a better expr. SILInstruction *i = const_cast(op->getUser()); @@ -1160,31 +1240,22 @@ class PartitionOpTranslator { llvm::SmallVector nonSendableSeparateIndices; for (SILArgument *arg : functionArguments) { if (auto state = tryToTrackValue(arg)) { - LLVM_DEBUG(llvm::dbgs() << " %%" << state->getID() << ": "); + LLVM_DEBUG(llvm::dbgs() << " %%" << state->getID() << ": " << *arg); - // If we have a function argument that is closure captured by a Sendable - // closure, allow for the argument to be transferred. - // - // DISCUSSION: The reason that we do this is that in the case of us - // having an actual Sendable closure there are two cases we can see: - // - // 1. If we have an actual Sendable closure, the AST will emit an - // earlier error saying that we are capturing a non-Sendable value in a - // Sendable closure. So we want to squelch the error that we would emit - // otherwise. This only occurs when we are not in swift-6 mode since in - // swift-6 mode we will error on the earlier error... but in the case of - // us not being in swift 6 mode lets not emit extra errors. + // If we can transfer our parameter, just add it to + // nonSendableSeparateIndices. // - // 2. If we have an async-let based Sendable closure, we want to allow - // for the value to be transferred and not emit an error. - if (!cast(arg)->isClosureCapture() || - !function->getLoweredFunctionType()->isSendable()) { - addNeverTransferredValueID(state->getID()); - nonSendableJoinedIndices.push_back(state->getID()); - } else { + // NOTE: We do not support today the ability to have multiple parameters + // transfer together as part of the same region. + if (isTransferrableFunctionArgument(cast(arg))) { nonSendableSeparateIndices.push_back(state->getID()); + continue; } - LLVM_DEBUG(llvm::dbgs() << *arg); + + // Otherwise, it is one of our merged parameters. Add it to the never + // transfer list and to the region join list. + valueMap.addNeverTransferredValueID(state->getID()); + nonSendableJoinedIndices.push_back(state->getID()); } } @@ -1464,43 +1535,89 @@ class PartitionOpTranslator { translateSILMultiAssign(applyResults, pai->getOperandValues(), options); } - void translateSILApply(SILInstruction *inst) { - if (auto *bi = dyn_cast(inst)) { - if (auto kind = bi->getBuiltinKind()) { - if (kind == BuiltinValueKind::StartAsyncLetWithLocalBuffer) { - return translateAsyncLetStart(bi); - } + void translateSILBuiltin(BuiltinInst *bi) { + if (auto kind = bi->getBuiltinKind()) { + if (kind == BuiltinValueKind::StartAsyncLetWithLocalBuffer) { + return translateAsyncLetStart(bi); } } - if (auto fas = FullApplySite::isa(inst)) { - if (auto *f = fas.getCalleeFunction()) { - // Check against the actual SILFunction. - if (f->getName() == "swift_asyncLet_get") { - return translateAsyncLetGet(cast(*fas)); - } + // If we do not have a special builtin, just do a multi-assign. Builtins do + // not cross async boundaries. + return translateSILMultiAssign(bi->getResults(), bi->getOperandValues(), + {}); + } + + void translateNonIsolationCrossingSILApply(FullApplySite fas) { + SILMultiAssignOptions options; + if (fas.hasSelfArgument()) { + if (auto self = fas.getSelfArgument()) { + if (self->getType().isActor()) + options |= SILMultiAssignFlags::PropagatesActorSelf; } } - // If this apply does not cross isolation domains, it has normal - // non-transferring multi-assignment semantics - if (!isIsolationBoundaryCrossingApply(inst)) { - SILMultiAssignOptions options; - if (auto fas = FullApplySite::isa(inst)) { - if (fas.hasSelfArgument()) { - if (auto self = fas.getSelfArgument()) { - if (self->getType().isActor()) - options |= SILMultiAssignFlags::PropagatesActorSelf; + // For non-self parameters, gather all of the transferring parameters and + // gather our non-transferring parameters. + SmallVector nonTransferringParameters; + if (fas.getNumArguments()) { + // NOTE: We want to process indirect parameters as if they are + // parameters... so we process them in nonTransferringParameters. + for (auto &op : fas.getOperandsWithoutSelf()) { + if (!fas.getArgumentConvention(op).isIndirectOutParameter() && + fas.getArgumentParameterInfo(op).hasOption( + SILParameterInfo::Transferring)) { + if (auto value = tryToTrackValue(op.get())) { + builder.addTransfer(value->getRepresentative().getValue(), &op); } + } else { + nonTransferringParameters.push_back(op.get()); } } + } - SmallVector applyResults; - getApplyResults(inst, applyResults); - return translateSILMultiAssign(applyResults, inst->getOperandValues(), - options); + // If our self parameter was transferring, transfer it. Otherwise, just + // stick it in the non seld operand values array and run multiassign on + // it. + if (fas.hasSelfArgument()) { + auto &selfOperand = fas.getSelfArgumentOperand(); + if (fas.getArgumentParameterInfo(selfOperand) + .hasOption(SILParameterInfo::Transferring)) { + if (auto value = tryToTrackValue(selfOperand.get())) { + builder.addTransfer(value->getRepresentative().getValue(), + &selfOperand); + } + } else { + nonTransferringParameters.push_back(selfOperand.get()); + } } + SmallVector applyResults; + getApplyResults(*fas, applyResults); + return translateSILMultiAssign(applyResults, nonTransferringParameters, + options); + } + + void translateSILApply(SILInstruction *inst) { + if (auto *bi = dyn_cast(inst)) { + return translateSILBuiltin(bi); + } + + auto fas = FullApplySite::isa(inst); + assert(bool(fas) && "Builtins should be handled above"); + + if (auto *f = fas.getCalleeFunction()) { + // Check against the actual SILFunction. + if (f->getName() == "swift_asyncLet_get") { + return translateAsyncLetGet(cast(*fas)); + } + } + + // If this apply does not cross isolation domains, it has normal + // non-transferring multi-assignment semantics + if (!isIsolationBoundaryCrossingApply(inst)) + return translateNonIsolationCrossingSILApply(fas); + if (auto cast = dyn_cast(inst)) return translateIsolationCrossingSILApply(cast); if (auto cast = dyn_cast(inst)) @@ -1521,7 +1638,7 @@ class PartitionOpTranslator { "only ApplyExpr's should cross isolation domains"); // require all operands - for (auto op : applySite->getOperandValues()) + for (auto op : applySite.getArguments()) if (auto value = tryToTrackValue(op)) builder.addRequire(value->getRepresentative().getValue()); @@ -1632,13 +1749,52 @@ class PartitionOpTranslator { return translateSILMerge(dest, TinyPtrVector(src)); } - /// If tgt is known to be unaliased (computed thropugh a combination of + void translateSILAssignmentToTransferringParameter(TrackableValue destRoot, + Operand *destOperand, + TrackableValue srcRoot, + Operand *srcOperand) { + assert(isa(destRoot.getRepresentative().getValue()) && + "Destination should always be an alloc_stack"); + + // Transfer src. This ensures that we cannot use src again locally in this + // function... which makes sense since its value is now in the transferring + // parameter. + builder.addTransfer(srcRoot.getRepresentative().getValue(), srcOperand); + + // Then check if we are assigning into an aggregate projection. In such a + // case, we want to ensure that we keep tracking the elements already in the + // region of transferring. This is more conservative than we need to be + // (since we could forget anything reachable from the aggregate + // field)... but being more conservative is ok. + if (isProjectedFromAggregate(destOperand->get())) + return; + + // If we are assigning over the entire value though, we perform an assign + // fresh since we are guaranteed that any value that could be referenced via + // the old value is gone. + builder.addAssignFresh(destRoot.getRepresentative().getValue()); + } + + /// If \p dest is known to be unaliased (computed through a combination of /// AccessStorage's inUniquelyIdenfitied check and a custom search for - /// captures by applications), then these can be treated as assignments of tgt - /// to src. If the tgt could be aliased, then we must instead treat them as - /// merges, to ensure any aliases of tgt are also updated. - void translateSILStore(SILValue dest, SILValue src) { - if (auto nonSendableTgt = tryToTrackValue(dest)) { + /// captures by applications), then these can be treated as assignments of \p + /// dest to src. If the \p dest could be aliased, then we must instead treat + /// them as merges, to ensure any aliases of \p dest are also updated. + void translateSILStore(Operand *dest, Operand *src) { + SILValue destValue = dest->get(); + SILValue srcValue = src->get(); + + if (auto nonSendableDest = tryToTrackValue(destValue)) { + // Before we do anything check if we have an assignment into an + // alloc_stack for a consuming transferring parameter... in such a case, + // we need to handle this specially. + if (nonSendableDest->isTransferringParameter()) { + if (auto nonSendableSrc = tryToTrackValue(srcValue)) { + return translateSILAssignmentToTransferringParameter( + *nonSendableDest, dest, *nonSendableSrc, src); + } + } + // In the following situations, we can perform an assign: // // 1. A store to unaliased storage. @@ -1650,11 +1806,12 @@ class PartitionOpTranslator { // specifically in this projection... but that is better than // miscompiling. For memory like this, we probably need to track it on a // per field basis to allow for us to assign. - if (nonSendableTgt.value().isNoAlias() && !isProjectedFromAggregate(dest)) - return translateSILAssign(dest, src); + if (nonSendableDest.value().isNoAlias() && + !isProjectedFromAggregate(destValue)) + return translateSILAssign(destValue, srcValue); // Stores to possibly aliased storage must be treated as merges. - return translateSILMerge(dest, src); + return translateSILMerge(destValue, srcValue); } // Stores to storage of non-Sendable type can be ignored. @@ -1790,8 +1947,9 @@ class PartitionOpTranslator { return translateSILLookThrough(inst->getResults(), inst->getOperand(0)); case TranslationSemantics::Store: - return translateSILStore(inst->getOperand(CopyLikeInstruction::Dest), - inst->getOperand(CopyLikeInstruction::Src)); + return translateSILStore( + &inst->getAllOperands()[CopyLikeInstruction::Dest], + &inst->getAllOperands()[CopyLikeInstruction::Src]); case TranslationSemantics::Special: return; diff --git a/lib/SILOptimizer/Mandatory/TransferNonSendable.cpp b/lib/SILOptimizer/Mandatory/TransferNonSendable.cpp index 14f4f4383286e..d810c24cb472c 100644 --- a/lib/SILOptimizer/Mandatory/TransferNonSendable.cpp +++ b/lib/SILOptimizer/Mandatory/TransferNonSendable.cpp @@ -41,6 +41,7 @@ using namespace swift; using namespace swift::PartitionPrimitives; using namespace swift::PatternMatch; +using namespace swift::regionanalysisimpl; namespace { using TransferringOperandSetFactory = Partition::TransferringOperandSetFactory; @@ -86,13 +87,75 @@ static InFlightDiagnostic diagnose(const SILInstruction *inst, Diag diag, namespace { +enum class UseDiagnosticInfoKind { + Invalid = 0, + + /// Used if we have an isolation crossing for our error. + IsolationCrossing = 1, + + /// In certain cases, we think a race can happen, but we couldn't find the + /// isolation crossing specifically to emit a better error. Still emit an + /// error though. + RaceWithoutKnownIsolationCrossing = 2, + + /// Used if the error is due to a transfer into an assignment into a + /// transferring parameter. + AssignmentIntoTransferringParameter = 3, + + /// Set to true if this is a use of a normal value that was strongly + /// transferred. + UseOfStronglyTransferredValue = 4, +}; + +class UseDiagnosticInfo { + UseDiagnosticInfoKind kind; + std::optional isolationCrossing; + +public: + static UseDiagnosticInfo + forIsolationCrossing(ApplyIsolationCrossing isolationCrossing) { + return UseDiagnosticInfo(UseDiagnosticInfoKind::IsolationCrossing, + isolationCrossing); + } + + static UseDiagnosticInfo forIsolationCrossingWithUnknownIsolation() { + return UseDiagnosticInfo( + UseDiagnosticInfoKind::RaceWithoutKnownIsolationCrossing, {}); + } + + static UseDiagnosticInfo forAssignmentIntoTransferringParameter() { + return UseDiagnosticInfo( + UseDiagnosticInfoKind::AssignmentIntoTransferringParameter, {}); + } + + static UseDiagnosticInfo forUseOfStronglyTransferredValue() { + return UseDiagnosticInfo( + UseDiagnosticInfoKind::UseOfStronglyTransferredValue, {}); + } + + UseDiagnosticInfoKind getKind() const { return kind; } + + ApplyIsolationCrossing getIsolationCrossing() const { + // assert(isolationCrossing && "Isolation crossing must be non-null"); + return isolationCrossing.value(); + } + +private: + UseDiagnosticInfo(UseDiagnosticInfoKind kind, + std::optional isolationCrossing) + : kind(kind), isolationCrossing(isolationCrossing) {} +}; + struct InferredCallerArgumentTypeInfo { + RegionAnalysisValueMap &valueMap; Type baseInferredType; - SmallVector>, 4> - applyUses; + SmallVector, 4> applyUses; + InferredCallerArgumentTypeInfo(RegionAnalysisValueMap &valueMap) + : valueMap(valueMap) {} void init(const Operand *op); +private: /// Init for an apply that does not have an associated apply expr. /// /// This should only occur when writing SIL test cases today. In the future, @@ -103,6 +166,18 @@ struct InferredCallerArgumentTypeInfo { void initForApply(const Operand *op, ApplyExpr *expr); void initForAutoclosure(const Operand *op, AutoClosureExpr *expr); + void initForAssignmentToTransferringParameter(const Operand *op) { + applyUses.emplace_back( + op->get()->getType().getASTType(), + UseDiagnosticInfo::forAssignmentIntoTransferringParameter()); + } + + void initForUseOfStronglyTransferredValue(const Operand *op) { + applyUses.emplace_back( + op->get()->getType().getASTType(), + UseDiagnosticInfo::forUseOfStronglyTransferredValue()); + } + Expr *getFoundExprForSelf(ApplyExpr *sourceApply) { if (auto callExpr = dyn_cast(sourceApply)) if (auto calledExpr = @@ -128,12 +203,14 @@ struct InferredCallerArgumentTypeInfo { void InferredCallerArgumentTypeInfo::initForApply( ApplyIsolationCrossing isolationCrossing) { - applyUses.emplace_back(baseInferredType, isolationCrossing); + applyUses.emplace_back( + baseInferredType, + UseDiagnosticInfo::forIsolationCrossing(isolationCrossing)); } void InferredCallerArgumentTypeInfo::initForApply(const Operand *op, ApplyExpr *sourceApply) { - auto isolationCrossing = *sourceApply->getIsolationCrossing(); + auto isolationCrossing = sourceApply->getIsolationCrossing().value(); // Grab out full apply site and see if we can find a better expr. SILInstruction *i = const_cast(op->getUser()); @@ -157,7 +234,9 @@ void InferredCallerArgumentTypeInfo::initForApply(const Operand *op, auto inferredArgType = foundExpr ? foundExpr->findOriginalType() : baseInferredType; - applyUses.emplace_back(inferredArgType, isolationCrossing); + applyUses.emplace_back( + inferredArgType, + UseDiagnosticInfo::forIsolationCrossing(isolationCrossing)); } namespace { @@ -203,8 +282,9 @@ struct Walker : ASTWalker { if (!visitedCallExprDeclRefExprs.count(declRef)) { if (declRef->getDecl() == targetDecl) { visitedCallExprDeclRefExprs.insert(declRef); - foundTypeInfo.applyUses.emplace_back(declRef->findOriginalType(), - std::nullopt); + foundTypeInfo.applyUses.emplace_back( + declRef->findOriginalType(), + UseDiagnosticInfo::forIsolationCrossingWithUnknownIsolation()); return Action::Continue(expr); } } @@ -220,8 +300,9 @@ struct Walker : ASTWalker { if (declRef->getDecl() == targetDecl) { // Found our target! visitedCallExprDeclRefExprs.insert(declRef); - foundTypeInfo.applyUses.emplace_back(declRef->findOriginalType(), - isolationCrossing); + foundTypeInfo.applyUses.emplace_back( + declRef->findOriginalType(), + UseDiagnosticInfo::forIsolationCrossing(*isolationCrossing)); return Action::Continue(expr); } } @@ -235,10 +316,38 @@ struct Walker : ASTWalker { } // namespace +static SILValue getDestOfStoreOrCopyAddr(Operand *op) { + if (auto *si = dyn_cast(op->getUser())) + return si->getDest(); + + if (auto *copyAddr = dyn_cast(op->getUser())) + return copyAddr->getDest(); + + return SILValue(); +} + void InferredCallerArgumentTypeInfo::init(const Operand *op) { baseInferredType = op->get()->getType().getASTType(); auto *nonConstOp = const_cast(op); + // Before we do anything, see if the transfer instruction was into a + // transferring parameter alloc_stack. In such a case, we emit a special + // message. + if (auto destValue = getDestOfStoreOrCopyAddr(nonConstOp)) { + auto trackedValue = valueMap.getTrackableValue(destValue); + if (trackedValue.isTransferringParameter()) { + return initForAssignmentToTransferringParameter(op); + } + } + + // Otherwise, see if our operand's instruction is a transferring parameter. + if (auto fas = FullApplySite::isa(nonConstOp->getUser())) { + if (fas.getArgumentParameterInfo(*nonConstOp) + .hasOption(SILParameterInfo::Transferring)) { + return initForUseOfStronglyTransferredValue(op); + } + } + auto loc = op->getUser()->getLoc(); if (auto *sourceApply = loc.getAsASTNode()) { return initForApply(op, sourceApply); @@ -252,7 +361,7 @@ void InferredCallerArgumentTypeInfo::init(const Operand *op) { auto *autoClosureExpr = loc.getAsASTNode(); if (!autoClosureExpr) { - llvm::report_fatal_error("Unknown node"); + llvm::report_fatal_error("Transfer error emission missing a case?!"); } auto *i = const_cast(op->getUser()); @@ -599,7 +708,7 @@ static void emitDiagnostics(RegionAnalysisFunctionInfo *regionInfo) { ++blockLivenessInfoGeneration; liveness.process(requireInsts); - InferredCallerArgumentTypeInfo argTypeInfo; + InferredCallerArgumentTypeInfo argTypeInfo(regionInfo->getValueMap()); argTypeInfo.init(transferOp); // If we were supposed to emit an error and we failed to do so, emit a @@ -614,17 +723,40 @@ static void emitDiagnostics(RegionAnalysisFunctionInfo *regionInfo) { } for (auto &info : argTypeInfo.applyUses) { - if (auto isolation = info.second) { + switch (info.second.getKind()) { + case UseDiagnosticInfoKind::Invalid: + llvm_unreachable("Should never see this!"); + case UseDiagnosticInfoKind::IsolationCrossing: { + auto isolation = info.second.getIsolationCrossing(); diagnose(transferOp, diag::regionbasedisolation_transfer_yields_race_with_isolation, - info.first, isolation->getCallerIsolation(), - isolation->getCalleeIsolation()) + info.first, isolation.getCallerIsolation(), + isolation.getCalleeIsolation()) .highlight(transferOp->get().getLoc().getSourceRange()); - } else { + break; + } + case UseDiagnosticInfoKind::RaceWithoutKnownIsolationCrossing: diagnose(transferOp, diag::regionbasedisolation_transfer_yields_race_no_isolation, info.first) .highlight(transferOp->get().getLoc().getSourceRange()); + break; + case UseDiagnosticInfoKind::UseOfStronglyTransferredValue: + diagnose( + transferOp, + diag:: + regionbasedisolation_transfer_yields_race_stronglytransferred_binding, + info.first) + .highlight(transferOp->get().getLoc().getSourceRange()); + break; + case UseDiagnosticInfoKind::AssignmentIntoTransferringParameter: + diagnose( + transferOp, + diag:: + regionbasedisolation_transfer_yields_race_transferring_parameter, + info.first) + .highlight(transferOp->get().getLoc().getSourceRange()); + break; } // Ok, we now have our requires... emit the errors. diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 135169d27691c..f730386f5dbdb 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -206,6 +206,7 @@ class AttributeChecker : public AttributeVisitor { void visitNonMutatingAttr(NonMutatingAttr *attr) { visitMutationAttr(attr); } void visitBorrowingAttr(BorrowingAttr *attr) { visitMutationAttr(attr); } void visitConsumingAttr(ConsumingAttr *attr) { visitMutationAttr(attr); } + void visitTransferringAttr(TransferringAttr *attr) {} void visitLegacyConsumingAttr(LegacyConsumingAttr *attr) { visitMutationAttr(attr); } void visitResultDependsOnSelfAttr(ResultDependsOnSelfAttr *attr) { FuncDecl *FD = cast(D); diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index a5fde03d959a6..abdd2c345a791 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1559,6 +1559,7 @@ namespace { UNINTERESTING_ATTR(PrivateImport) UNINTERESTING_ATTR(MainType) UNINTERESTING_ATTR(Preconcurrency) + UNINTERESTING_ATTR(Transferring) // Differentiation-related attributes. UNINTERESTING_ATTR(Differentiable) diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 8670c4e31ad35..d3112c290288f 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -3510,12 +3510,14 @@ TypeResolver::resolveASTFunctionTypeParams(TupleTypeRepr *inputRepr, bool isolated = false; bool compileTimeConst = false; bool hasResultDependsOn = false; + bool isTransferring = false; while (true) { if (auto *specifierRepr = dyn_cast(nestedRepr)) { switch (specifierRepr->getKind()) { case TypeReprKind::Ownership: ownership = cast(specifierRepr)->getSpecifier(); nestedRepr = specifierRepr->getBase(); + isTransferring = ownership == ParamSpecifier::Transferring; continue; case TypeReprKind::Isolated: isolated = true; @@ -3625,7 +3627,8 @@ TypeResolver::resolveASTFunctionTypeParams(TupleTypeRepr *inputRepr, auto paramFlags = ParameterTypeFlags::fromParameterType( ty, variadic, autoclosure, /*isNonEphemeral*/ false, ownership, - isolated, noDerivative, compileTimeConst, hasResultDependsOn); + isolated, noDerivative, compileTimeConst, hasResultDependsOn, + isTransferring); elements.emplace_back(ty, argumentLabel, paramFlags, parameterName); } @@ -4480,6 +4483,7 @@ TypeResolver::resolveOwnershipTypeRepr(OwnershipTypeRepr *repr, case ParamSpecifier::LegacyOwned: case ParamSpecifier::Borrowing: break; + case ParamSpecifier::Transferring: case ParamSpecifier::Consuming: if (auto *fnTy = result->getAs()) { if (fnTy->isNoEscape()) { diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 27dee921c6767..c513305be51b3 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -2861,6 +2861,7 @@ getActualParamDeclSpecifier(serialization::ParamDeclSpecifier raw) { CASE(Consuming) CASE(LegacyShared) CASE(LegacyOwned) + CASE(Transferring) } #undef CASE return llvm::None; @@ -6688,12 +6689,12 @@ detail::function_deserializer::deserialize(ModuleFile &MF, TypeID typeID; bool isVariadic, isAutoClosure, isNonEphemeral, isIsolated, isCompileTimeConst, hasResultDependsOn; - bool isNoDerivative; + bool isNoDerivative, isTransferring; unsigned rawOwnership; decls_block::FunctionParamLayout::readRecord( scratch, labelID, internalLabelID, typeID, isVariadic, isAutoClosure, isNonEphemeral, rawOwnership, isIsolated, isNoDerivative, - isCompileTimeConst, hasResultDependsOn); + isCompileTimeConst, hasResultDependsOn, isTransferring); auto ownership = getActualParamDeclSpecifier( (serialization::ParamDeclSpecifier)rawOwnership); @@ -6704,12 +6705,13 @@ detail::function_deserializer::deserialize(ModuleFile &MF, if (!paramTy) return paramTy.takeError(); - params.emplace_back( - paramTy.get(), MF.getIdentifier(labelID), - ParameterTypeFlags(isVariadic, isAutoClosure, isNonEphemeral, - *ownership, isIsolated, isNoDerivative, - isCompileTimeConst, hasResultDependsOn), - MF.getIdentifier(internalLabelID)); + params.emplace_back(paramTy.get(), MF.getIdentifier(labelID), + ParameterTypeFlags(isVariadic, isAutoClosure, + isNonEphemeral, *ownership, + isIsolated, isNoDerivative, + isCompileTimeConst, + hasResultDependsOn, isTransferring), + MF.getIdentifier(internalLabelID)); } if (!isGeneric) { diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 169fef9976b51..8a4e7a7ff42ec 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 = 837; // @preconcurrency conformances +const uint16_t SWIFTMODULE_VERSION_MINOR = 838; // transferring param /// A standard hash seed used for all string hashes in a serialized module. /// @@ -359,6 +359,7 @@ enum class ParamDeclSpecifier : uint8_t { Consuming = 3, LegacyShared = 4, LegacyOwned = 5, + Transferring = 6, }; using ParamDeclSpecifierField = BCFixed<3>; @@ -1227,7 +1228,8 @@ namespace decls_block { BCFixed<1>, // isolated BCFixed<1>, // noDerivative? BCFixed<1>, // compileTimeConst - BCFixed<1> // _resultDependsOn + BCFixed<1>, // _resultDependsOn + BCFixed<1> // transferring >; TYPE_LAYOUT(MetatypeTypeLayout, diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index fd1c1bde7f625..f815ec6f71886 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -2551,6 +2551,8 @@ static uint8_t getRawStableParamDeclSpecifier(swift::ParamDecl::Specifier sf) { return uint8_t(serialization::ParamDeclSpecifier::LegacyShared); case swift::ParamDecl::Specifier::LegacyOwned: return uint8_t(serialization::ParamDeclSpecifier::LegacyOwned); + case swift::ParamDecl::Specifier::Transferring: + return uint8_t(serialization::ParamDeclSpecifier::Transferring); } llvm_unreachable("bad param decl specifier kind"); } @@ -5426,7 +5428,8 @@ class Serializer::TypeSerializer : public TypeVisitor { S.addTypeRef(param.getPlainType()), paramFlags.isVariadic(), paramFlags.isAutoClosure(), paramFlags.isNonEphemeral(), rawOwnership, paramFlags.isIsolated(), paramFlags.isNoDerivative(), - paramFlags.isCompileTimeConst(), paramFlags.hasResultDependsOn()); + paramFlags.isCompileTimeConst(), paramFlags.hasResultDependsOn(), + paramFlags.isTransferring()); } } diff --git a/test/Concurrency/transfernonsendable_strong_transferring.swift b/test/Concurrency/transfernonsendable_strong_transferring.swift new file mode 100644 index 0000000000000..ec9baa81e2559 --- /dev/null +++ b/test/Concurrency/transfernonsendable_strong_transferring.swift @@ -0,0 +1,235 @@ +// RUN: %target-swift-frontend -emit-sil -disable-availability-checking -enable-experimental-feature TransferringArgsAndResults -verify -enable-experimental-feature RegionBasedIsolation %s + +// REQUIRES: asserts + +//////////////////////// +// MARK: Declarations // +//////////////////////// + +class Klass {} + +struct NonSendableStruct { + var first = Klass() + var second = Klass() +} + +func useValue(_ t: T) {} +func getAny() -> Any { fatalError() } +@MainActor func transferToMain(_ t: T) {} + +func transferArg(_ x: transferring Klass) { +} + +func transferArgWithOtherParam(_ x: transferring Klass, _ y: Klass) { +} + +func transferArgWithOtherParam2(_ x: Klass, _ y: transferring Klass) { +} + +@MainActor var globalKlass = Klass() + +///////////////// +// MARK: Tests // +///////////////// + +func testSimpleTransferLet() { + let k = Klass() + transferArg(k) // expected-warning {{binding of non-Sendable type 'Klass' accessed after being transferred; later accesses could result in races}} + useValue(k) // expected-note {{access here could race}} +} + +func testSimpleTransferVar() { + var k = Klass() + k = Klass() + transferArg(k) // expected-warning {{binding of non-Sendable type 'Klass' accessed after being transferred; later accesses could result in races}} + useValue(k) // expected-note {{access here could race}} +} + +func testSimpleTransferUseOfOtherParamNoError() { + let k = Klass() + let k2 = Klass() + transferArgWithOtherParam(k, k2) + useValue(k2) +} + +func testSimpleTransferUseOfOtherParamNoError2() { + let k = Klass() + let k2 = Klass() + transferArgWithOtherParam2(k, k2) + useValue(k) +} + +@MainActor func transferToMain2(_ x: transferring Klass, _ y: Klass, _ z: Klass) async { + +} + +// TODO: How to test this? +func testNonStrongTransferDoesntMerge() async { +} + +////////////////////////////////// +// MARK: Transferring Parameter // +////////////////////////////////// + +func testTransferringParameter_canTransfer(_ x: transferring Klass, _ y: Klass) async { + await transferToMain(x) + await transferToMain(y) // expected-warning {{call site passes `self` or a non-sendable argument of this function to another thread, potentially yielding a race with the caller}} +} + +func testTransferringParameter_cannotTransferTwice(_ x: transferring Klass, _ y: Klass) async { + await transferToMain(x) // expected-warning {{passing argument of non-sendable type 'Klass' from nonisolated context to main actor-isolated context at this call site could yield a race with accesses later in this function}} + await transferToMain(x) // expected-note {{access here could race}} +} + +func testTransferringParameter_cannotUseAfterTransfer(_ x: transferring Klass, _ y: Klass) async { + await transferToMain(x) // expected-warning {{passing argument of non-sendable type 'Klass' from nonisolated context to main actor-isolated context at this call site could yield a race with accesses later in this function}} + useValue(x) // expected-note {{access here could race}} +} + +actor MyActor { + var field = Klass() + + func canTransferWithTransferringMethodArg(_ x: transferring Klass, _ y: Klass) async { + await transferToMain(x) + await transferToMain(y) // expected-warning {{call site passes `self` or a non-sendable argument of this function to another thread, potentially yielding a race with the caller}} + } + + func getNormalErrorIfTransferTwice(_ x: transferring Klass) async { + await transferToMain(x) // expected-warning {{passing argument of non-sendable type 'Klass' from actor-isolated context to main actor-isolated context at this call site could yield a race with accesses later in this function}} + await transferToMain(x) // expected-note {{access here could race}} + } + + func getNormalErrorIfUseAfterTransfer(_ x: transferring Klass) async { + await transferToMain(x) // expected-warning {{passing argument of non-sendable type 'Klass' from actor-isolated context to main actor-isolated context at this call site could yield a race with accesses later in this function}} + useValue(x) // expected-note {{access here could race}} + } + + // After assigning into the actor, we can still use x in the actor as long as + // we don't transfer it. + func assignTransferringIntoActor(_ x: transferring Klass) async { + field = x + useValue(x) + } + + // Once we assign into the actor, we cannot transfer further. + func assignTransferringIntoActor2(_ x: transferring Klass) async { + field = x + await transferToMain(x) // expected-warning {{call site passes `self` or a non-sendable argument of this function to another thread, potentially yielding a race with the caller}} + } +} + +@MainActor func canAssignTransferringIntoGlobalActor(_ x: transferring Klass) async { + globalKlass = x +} + +func canTransferAssigningIntoLocal(_ x: transferring Klass) async { + let _ = x + await transferToMain(x) +} + +////////////////////////////////////// +// MARK: Transferring is "var" like // +////////////////////////////////////// + +// Assigning into a transferring parameter is a transfer! +func assigningIsATransfer(_ x: transferring Klass) async { + // Ok, this is disconnected. + let y = Klass() + + // y is transferred into x. + x = y // expected-warning {{transferred value of non-Sendable type 'Klass' into transferring parameter; later accesses could result in races}} + + // Since y is now in x, we can't use y anymore so we emit an error. + useValue(y) // expected-note {{access here could race}} +} + +func assigningIsATransferNoError(_ x: transferring Klass) async { + // Ok, this is disconnected. + let y = Klass() + + // y is transferred into x. + x = y + + useValue(x) +} + +func assigningIsATransferAny(_ x: transferring Any) async { + // Ok, this is disconnected. + let y = getAny() + + // y is transferred into x. + x = y // expected-warning {{transferred value of non-Sendable type 'Any' into transferring parameter; later accesses could result in races}} + + // Since y is now in x, we can't use y anymore so we emit an error. + useValue(y) // expected-note {{access here could race}} +} + +func canTransferAfterAssign(_ x: transferring Any) async { + // Ok, this is disconnected. + let y = getAny() + + // y is transferred into x. + x = y + + await transferToMain(x) +} + +func canTransferAfterAssignButUseIsError(_ x: transferring Any) async { + // Ok, this is disconnected. + let y = getAny() + + // y is transferred into x. + x = y + + // TODO: Change this to refer to task context? Or to transferring parameter? + await transferToMain(x) // expected-warning {{passing argument of non-sendable type 'Any' from nonisolated context to main actor-isolated context at this call site could yield a race with accesses later in this function}} + + useValue(x) // expected-note {{access here could race}} +} + +func assignToEntireValueEliminatesEarlierTransfer(_ x: transferring Any) async { + // Ok, this is disconnected. + let y = getAny() + + useValue(x) + + // Transfer x + await transferToMain(x) + + // y is transferred into x. This shouldn't error. + x = y + + useValue(x) +} + +func mergeDoesNotEliminateEarlierTransfer(_ x: transferring NonSendableStruct) async { + // Ok, this is disconnected. + let y = Klass() + + useValue(x) + + // Transfer x + await transferToMain(x) // expected-warning {{passing argument of non-sendable type 'NonSendableStruct' from nonisolated context to main actor-isolated context at this call site could yield a race with accesses later in this function}} + + // y is assigned into a field of x, so we treat this like a merge. + x.first = y + + useValue(x) // expected-note {{access here could race}} +} + +func mergeDoesNotEliminateEarlierTransfer2(_ x: transferring NonSendableStruct) async { + // Ok, this is disconnected. + let y = Klass() + + useValue(x) + + // Transfer x + await transferToMain(x) // expected-warning {{passing argument of non-sendable type 'NonSendableStruct' from nonisolated context to main actor-isolated context at this call site could yield a race with accesses later in this function}} + + // y is assigned into a field of x, so we treat this like a merge. + x.first = y // expected-warning {{transferred value of non-Sendable type 'Klass' into transferring parameter; later accesses could result in races}} + + useValue(x) // expected-note {{access here could race}} + + useValue(y) // expected-note {{access here could race}} +} diff --git a/test/Parse/transferring.swift b/test/Parse/transferring.swift new file mode 100644 index 0000000000000..db193609ed041 --- /dev/null +++ b/test/Parse/transferring.swift @@ -0,0 +1,22 @@ +// RUN: %target-typecheck-verify-swift -disable-availability-checking -enable-experimental-feature TransferringArgsAndResults + +// REQUIRES: asserts + +func testArg(_ x: transferring String) { +} + +// Error only on results. +func testResult() -> transferring String { + // expected-error @-1 {{'transferring' may only be used on parameter}} + "" +} + +// Error on the result. +func testArgResult(_ x: transferring String) -> transferring String { + // expected-error @-1 {{'transferring' may only be used on parameter}} +} + +func testVarDeclDoesntWork() { + var x: transferring String // expected-error {{'transferring' may only be used on parameter}} +} +