From 19a99ea42bbc587780fe7e211d9eee91c6da629a Mon Sep 17 00:00:00 2001 From: Joe Groff Date: Mon, 10 Mar 2025 20:18:49 -0700 Subject: [PATCH] SILGen: Emit an addressable representation for immutable bindings on demand. To ensure that dependent values have a persistent-enough memory representation to point into, when an immutable binding is referenced as an addressable argument to a call, have SILGen retroactively emit a stack allocation and materialization that covers the binding's scope. --- include/swift/AST/Types.h | 4 +- lib/SIL/IR/AbstractionPattern.cpp | 4 +- lib/SILGen/SILGenApply.cpp | 20 +- lib/SILGen/SILGenBuilder.cpp | 12 ++ lib/SILGen/SILGenBuilder.h | 4 + lib/SILGen/SILGenConstructor.cpp | 15 +- lib/SILGen/SILGenDecl.cpp | 170 ++++++++++++++- lib/SILGen/SILGenFunction.cpp | 2 +- lib/SILGen/SILGenFunction.h | 89 ++++++-- lib/SILGen/SILGenGlobalVariable.cpp | 2 +- lib/SILGen/SILGenLValue.cpp | 104 +++++++++- lib/SILGen/SILGenPattern.cpp | 14 +- lib/SILGen/SILGenProlog.cpp | 52 +++-- test/SILGen/addressable_representation.swift | 205 +++++++++++++++++++ 14 files changed, 625 insertions(+), 72 deletions(-) create mode 100644 test/SILGen/addressable_representation.swift diff --git a/include/swift/AST/Types.h b/include/swift/AST/Types.h index 7ae67663157..77b0ef79ee3 100644 --- a/include/swift/AST/Types.h +++ b/include/swift/AST/Types.h @@ -4337,9 +4337,7 @@ inline bool isConsumedParameterInCaller(ParameterConvention conv) { return isConsumedParameter(conv); } -/// Returns true if conv is a guaranteed parameter. This may look unnecessary -/// but this will allow code to generalize to handle Indirect_Guaranteed -/// parameters when they are added. +/// Returns true if conv is a guaranteed parameter. template bool isGuaranteedParameter(ParameterConvention conv) { switch (conv) { diff --git a/lib/SIL/IR/AbstractionPattern.cpp b/lib/SIL/IR/AbstractionPattern.cpp index 070bdca51f1..82a2d6bc3d4 100644 --- a/lib/SIL/IR/AbstractionPattern.cpp +++ b/lib/SIL/IR/AbstractionPattern.cpp @@ -1679,9 +1679,7 @@ AbstractionPattern::isFunctionParamAddressable(TypeConverter &TC, 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; + return false; } auto fnTy = cast(getType()); diff --git a/lib/SILGen/SILGenApply.cpp b/lib/SILGen/SILGenApply.cpp index 9dc44624610..8af758c4c0a 100644 --- a/lib/SILGen/SILGenApply.cpp +++ b/lib/SILGen/SILGenApply.cpp @@ -3462,11 +3462,9 @@ SILGenFunction::tryEmitAddressableParameterAsAddress(ArgumentSource &&arg, 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; + if (auto param = dyn_cast(dre->getDecl())) { + if (auto addr = getLocalVariableAddressableBuffer(param, expr, + ownership)) { return ManagedValue::forBorrowedAddressRValue(addr); } } @@ -3577,12 +3575,20 @@ private: void emit(ArgumentSource &&arg, AbstractionPattern origParamType, bool isAddressable, std::optional origParam = std::nullopt) { - if (isAddressable && origParam) { + if (isAddressable) { // 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. + ValueOwnership paramOwnership; + if (isGuaranteedParameterInCaller(ParamInfos.front().getConvention())) { + paramOwnership = ValueOwnership::Shared; + } else if (isMutatingParameter(ParamInfos.front().getConvention())) { + paramOwnership = ValueOwnership::InOut; + } else { + paramOwnership = ValueOwnership::Owned; + } if (auto addr = SGF.tryEmitAddressableParameterAsAddress(std::move(arg), - origParam->getValueOwnership())) { + paramOwnership)) { claimNextParameters(1); Args.push_back(addr); return; diff --git a/lib/SILGen/SILGenBuilder.cpp b/lib/SILGen/SILGenBuilder.cpp index d3033170527..9c5489fa7c6 100644 --- a/lib/SILGen/SILGenBuilder.cpp +++ b/lib/SILGen/SILGenBuilder.cpp @@ -1081,6 +1081,18 @@ public: }; } +SILValue +SILGenBuilder::emitBeginAccess(SILLocation loc, + SILValue address, + SILAccessKind kind, + SILAccessEnforcement enforcement) { + auto access = createBeginAccess(loc, address, + kind, enforcement, + /*no nested conflict*/ false, false); + SGF.Cleanups.pushCleanup(access); + return access; +} + ManagedValue SILGenBuilder::createOpaqueBorrowBeginAccess(SILLocation loc, ManagedValue address) { diff --git a/lib/SILGen/SILGenBuilder.h b/lib/SILGen/SILGenBuilder.h index f2fd6ce6186..b02a2c2f1b2 100644 --- a/lib/SILGen/SILGenBuilder.h +++ b/lib/SILGen/SILGenBuilder.h @@ -477,6 +477,10 @@ public: ManagedValue base, MarkDependenceKind dependencekind); + SILValue emitBeginAccess(SILLocation loc, SILValue address, + SILAccessKind kind, + SILAccessEnforcement enforcement); + ManagedValue createOpaqueBorrowBeginAccess(SILLocation loc, ManagedValue address); ManagedValue createOpaqueConsumeBeginAccess(SILLocation loc, diff --git a/lib/SILGen/SILGenConstructor.cpp b/lib/SILGen/SILGenConstructor.cpp index 4348aff2ad4..e369eedc58f 100644 --- a/lib/SILGen/SILGenConstructor.cpp +++ b/lib/SILGen/SILGenConstructor.cpp @@ -1173,7 +1173,8 @@ void SILGenFunction::emitClassConstructorInitializer(ConstructorDecl *ctor) { MarkUnresolvedNonCopyableValueInst::CheckKind:: ConsumableAndAssignable); } - VarLocs[selfDecl] = VarLoc::get(selfArg.getValue()); + VarLocs[selfDecl] = VarLoc(selfArg.getValue(), + SILAccessEnforcement::Static); } } @@ -1696,7 +1697,7 @@ void SILGenFunction::emitIVarInitializer(SILDeclRef ivarInitializer) { selfArg = B.createMarkUninitialized(selfDecl, selfArg, MarkUninitializedInst::RootSelf); assert(selfTy.hasReferenceSemantics() && "can't emit a value type ctor here"); - VarLocs[selfDecl] = VarLoc::get(selfArg); + VarLocs[selfDecl] = VarLoc(selfArg, SILAccessEnforcement::Unknown); auto cleanupLoc = CleanupLocation(loc); prepareEpilog(cd, std::nullopt, std::nullopt, cleanupLoc); @@ -1727,11 +1728,11 @@ void SILGenFunction::emitInitAccessor(AccessorDecl *accessor) { loc.markAutoGenerated(); SILValue argValue = F.begin()->createFunctionArgument(type, arg); - VarLocs[arg] = - markUninitialized - ? VarLoc::get(B.createMarkUninitializedOut(loc, argValue)) - : VarLoc::get(argValue); - + if (markUninitialized) { + argValue = B.createMarkUninitializedOut(loc, argValue); + } + + VarLocs[arg] = VarLoc(argValue, SILAccessEnforcement::Static); InitAccessorArgumentMappings[property] = arg; }; diff --git a/lib/SILGen/SILGenDecl.cpp b/lib/SILGen/SILGenDecl.cpp index 27797440824..a65f4020eac 100644 --- a/lib/SILGen/SILGenDecl.cpp +++ b/lib/SILGen/SILGenDecl.cpp @@ -472,7 +472,7 @@ public: Var->print(llvm::errs()); llvm::errs() << "\n"; if (isActive()) { - auto loc = SGF.VarLocs[Var]; + auto &loc = SGF.VarLocs[Var]; assert((loc.box || loc.value) && "One of box or value should be set"); if (loc.box) { llvm::errs() << "Box: " << loc.box << "\n"; @@ -664,7 +664,8 @@ public: /// decl to. assert(SGF.VarLocs.count(decl) == 0 && "Already emitted the local?"); - SGF.VarLocs[decl] = SILGenFunction::VarLoc::get(Addr, Box); + SGF.VarLocs[decl] = SILGenFunction::VarLoc(Addr, + SILAccessEnforcement::Dynamic, Box); SingleBufferInitialization::finishInitialization(SGF); assert(!DidFinish && @@ -677,6 +678,54 @@ public: } // end anonymous namespace namespace { + +static void deallocateAddressable(SILGenFunction &SGF, + SILLocation l, + SILGenFunction::VarLoc &loc) { + SGF.B.createEndBorrow(l, loc.addressableBuffer.state->storeBorrow); + SGF.B.createDeallocStack(l, loc.addressableBuffer.state->allocStack); + if (loc.addressableBuffer.state->reabstraction) { + SGF.B.createDestroyValue(l, loc.addressableBuffer.state->reabstraction); + } +} + +/// Cleanup to deallocate the addressable buffer for a parameter or let +/// binding. +class DeallocateLocalVariableAddressableBuffer : public Cleanup { + ValueDecl *vd; +public: + DeallocateLocalVariableAddressableBuffer(ValueDecl *vd) : vd(vd) {} + + void emit(SILGenFunction &SGF, CleanupLocation l, + ForUnwind_t forUnwind) override { + auto found = SGF.VarLocs.find(vd); + if (found == SGF.VarLocs.end()) { + return; + } + auto &loc = found->second; + + if (loc.addressableBuffer.state) { + // The addressable buffer was forced, so clean it up now. + deallocateAddressable(SGF, l, loc); + } else { + // Remember this insert location in case we need to force the addressable + // buffer later. + SILInstruction *marker = SGF.B.createTuple(l, {}); + loc.addressableBuffer.cleanupPoints.emplace_back(marker); + } + } + + void dump(SILGenFunction &SGF) const override { +#ifndef NDEBUG + llvm::errs() << "DeallocateLocalVariableAddressableBuffer\n" + << "State:" << getState() << "\n" + << "Decl: "; + vd->print(llvm::errs()); + llvm::errs() << "\n"; +#endif + } +}; + /// Initialize a writeback buffer that receives the value of a 'let' /// declaration. class LetValueInitialization : public Initialization { @@ -755,7 +804,8 @@ public: if (isUninitialized) address = SGF.B.createMarkUninitializedVar(vd, address); DestroyCleanup = SGF.enterDormantTemporaryCleanup(address, *lowering); - SGF.VarLocs[vd] = SILGenFunction::VarLoc::get(address); + SGF.VarLocs[vd] = SILGenFunction::VarLoc(address, + SILAccessEnforcement::Unknown); } // Push a cleanup to destroy the let declaration. This has to be // inactive until the variable is initialized: if control flow exits the @@ -766,6 +816,10 @@ public: SGF.Cleanups.pushCleanupInState( CleanupState::Dormant, vd); DestroyCleanup = SGF.Cleanups.getTopCleanup(); + + // If the binding has an addressable buffer forced, it should be cleaned + // up here. + SGF.enterLocalVariableAddressableBufferScope(vd); } ~LetValueInitialization() override { @@ -883,7 +937,8 @@ public: if (SGF.getASTContext().SILOpts.supportsLexicalLifetimes(SGF.getModule())) value = getValueForLexicalLifetimeBinding(SGF, loc, value, wasPlusOne); - SGF.VarLocs[vd] = SILGenFunction::VarLoc::get(value); + SGF.VarLocs[vd] = SILGenFunction::VarLoc(value, + SILAccessEnforcement::Unknown); // Emit a debug_value[_addr] instruction to record the start of this value's // lifetime, if permitted to do so. @@ -1463,7 +1518,7 @@ SILGenFunction::emitInitializationForVarDecl(VarDecl *vd, bool forceImmutable, assert(SILDebugClient && "Debugger client doesn't support SIL"); SILValue SV = SILDebugClient->emitLValueForVariable(vd, B); - VarLocs[vd] = SILGenFunction::VarLoc::get(SV); + VarLocs[vd] = VarLoc(SV, SILAccessEnforcement::Dynamic); return InitializationPtr(new KnownAddressInitialization(SV)); } @@ -1494,7 +1549,7 @@ SILGenFunction::emitInitializationForVarDecl(VarDecl *vd, bool forceImmutable, if (isUninitialized) addr = B.createMarkUninitializedVar(loc, addr); - VarLocs[vd] = SILGenFunction::VarLoc::get(addr); + VarLocs[vd] = VarLoc(addr, SILAccessEnforcement::Dynamic); Result = InitializationPtr(new KnownAddressInitialization(addr)); } else { std::optional uninitKind; @@ -2309,7 +2364,7 @@ void SILGenFunction::destroyLocalVariable(SILLocation silLoc, VarDecl *vd) { } }; - auto loc = VarLocs[vd]; + auto &loc = VarLocs[vd]; // For a heap variable, the box is responsible for the value. We just need // to give up our retain count on it. @@ -2406,6 +2461,101 @@ void SILGenFunction::destroyLocalVariable(SILLocation silLoc, VarDecl *vd) { llvm_unreachable("unhandled case"); } +void +SILGenFunction::enterLocalVariableAddressableBufferScope(VarDecl *decl) { + Cleanups.pushCleanup(decl); +} + +SILValue +SILGenFunction::getLocalVariableAddressableBuffer(VarDecl *decl, + SILLocation curLoc, + ValueOwnership ownership) { + auto foundVarLoc = VarLocs.find(decl); + if (foundVarLoc == VarLocs.end()) { + return SILValue(); + } + + auto &varLoc = foundVarLoc->second; + SILType fullyAbstractedTy = getLoweredType(AbstractionPattern::getOpaque(), + decl->getTypeInContext()->getRValueType()); + + // Check whether the bound value is inherently suitable for addressability. + // It must already be in memory and fully abstracted. + if (varLoc.value->getType().isAddress() + && fullyAbstractedTy.getASTType()==varLoc.value->getType().getASTType()) { + SILValue address = varLoc.value; + // Begin an access if the address is mutable. + if (varLoc.access != SILAccessEnforcement::Unknown) { + address = B.emitBeginAccess(curLoc, address, + ownership == ValueOwnership::InOut ? SILAccessKind::Modify + : SILAccessKind::Read, + varLoc.access); + } + return address; + } + + // We can't retroactively introduce a reabstracted representation for a + // mutable binding (since we would now have two mutable memory locations + // representing the same value). + if (varLoc.access != SILAccessEnforcement::Unknown) { + return SILValue(); + } + + assert(ownership == ValueOwnership::Shared); + + // Check whether the in-memory representation has already been forced. + if (auto &state = varLoc.addressableBuffer.state) { + return state->storeBorrow; + } + + // Otherwise, force the addressable representation. + SILValue reabstraction, allocStack, storeBorrow; + { + SavedInsertionPointRAII save(B); + B.setInsertionPoint(varLoc.value->getNextInstruction()); + auto declarationLoc = varLoc.value->getDefiningInsertionPoint()->getLoc(); + + // Reabstract if necessary. + auto value = varLoc.value; + reabstraction = SILValue(); + if (value->getType().getASTType() != fullyAbstractedTy.getASTType()){ + auto reabstracted = emitSubstToOrigValue(curLoc, + ManagedValue::forBorrowedRValue(value), + AbstractionPattern::getOpaque(), + decl->getTypeInContext()->getCanonicalType(), + SGFContext()); + reabstraction = reabstracted.forward(*this); + value = reabstraction; + } + // TODO: reabstract + allocStack = B.createAllocStack(declarationLoc, value->getType(), + std::nullopt, + DoesNotHaveDynamicLifetime, + IsNotLexical, + IsNotFromVarDecl, + DoesNotUseMoveableValueDebugInfo, + /*skipVarDeclAssert*/ true); + storeBorrow = B.createStoreBorrow(declarationLoc, value, allocStack); + } + + // Record the addressable representation. + varLoc.addressableBuffer.state + = std::make_unique(reabstraction, + allocStack, + storeBorrow); + + // Emit cleanups on any paths where we previously would have cleaned up + // the addressable representation if it had been forced earlier. + for (SILInstruction *cleanupPoint : varLoc.addressableBuffer.cleanupPoints) { + SavedInsertionPointRAII insertCleanup(B, cleanupPoint); + deallocateAddressable(*this, cleanupPoint->getLoc(), varLoc); + cleanupPoint->eraseFromParent(); + } + varLoc.addressableBuffer.cleanupPoints.clear(); + + return storeBorrow; +} + void BlackHoleInitialization::performPackExpansionInitialization( SILGenFunction &SGF, SILLocation loc, @@ -2437,3 +2587,9 @@ void BlackHoleInitialization::copyOrInitValueInto(SILGenFunction &SGF, SILLocati value = SGF.B.createMoveValue(loc, value); SGF.B.createIgnoredUse(loc, value.getValue()); } + +SILGenFunction::VarLoc::AddressableBuffer::~AddressableBuffer() { + for (auto cleanupPoint : cleanupPoints) { + cleanupPoint->eraseFromParent(); + } +} diff --git a/lib/SILGen/SILGenFunction.cpp b/lib/SILGen/SILGenFunction.cpp index 8038915d1ba..139210944e7 100644 --- a/lib/SILGen/SILGenFunction.cpp +++ b/lib/SILGen/SILGenFunction.cpp @@ -740,7 +740,7 @@ void SILGenFunction::emitCaptures(SILLocation loc, } }; - auto Entry = found->second; + auto &Entry = found->second; auto val = Entry.value; switch (SGM.Types.getDeclCaptureKind(capture, expansion)) { diff --git a/lib/SILGen/SILGenFunction.h b/lib/SILGen/SILGenFunction.h index b9083a16071..caa4e26cd4b 100644 --- a/lib/SILGen/SILGenFunction.h +++ b/lib/SILGen/SILGenFunction.h @@ -55,6 +55,7 @@ class ExecutorBreadcrumb; struct LValueOptions { bool IsNonAccessing = false; + bool TryAddressable = false; /// Derive options for accessing the base of an l-value, given that /// applying the derived component might touch the memory. @@ -63,7 +64,6 @@ struct LValueOptions { // Assume we're going to access the base. copy.IsNonAccessing = false; - return copy; } @@ -73,6 +73,12 @@ struct LValueOptions { auto copy = *this; return copy; } + + LValueOptions withAddressable(bool addressable) const { + auto copy = *this; + copy.TryAddressable = addressable; + return copy; + } }; class PatternMatchContext; @@ -466,24 +472,81 @@ public: /// 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(), - bool addressable = false) { - VarLoc Result; - Result.value = value; - Result.box = box; - Result.addressable = addressable; - return Result; - } + /// What kind of access enforcement should be used to access the variable, + /// or `Unknown` if it's known to be immutable. + SILAccessEnforcement access; + + /// A structure used for bookkeeping the on-demand formation and cleanup + /// of an addressable representation for an immutable value binding. + struct AddressableBuffer { + struct State { + // If the value needs to be reabstracted to provide an addressable + // representation, this SILValue owns the reabstracted representation. + SILValue reabstraction = SILValue(); + // The stack allocation for the addressable representation. + SILValue allocStack = SILValue(); + // The initiation of the in-memory borrow. + SILValue storeBorrow = SILValue(); + + State(SILValue reabstraction, + SILValue allocStack, + SILValue storeBorrow) + : reabstraction(reabstraction), allocStack(allocStack), + storeBorrow(storeBorrow) + {} + }; + + std::unique_ptr state = nullptr; + + // If the variable cleanup is triggered before the addressable + // representation is demanded, but the addressable representation + // gets demanded later, we save the insertion points where the + // representation would be cleaned up so we can backfill them. + llvm::SmallVector cleanupPoints; + + AddressableBuffer() = default; + + AddressableBuffer(AddressableBuffer &&other) + : state(std::move(other.state)) + { + cleanupPoints.swap(other.cleanupPoints); + } + + AddressableBuffer &operator=(AddressableBuffer &&other) { + state = std::move(other.state); + cleanupPoints.swap(other.cleanupPoints); + return *this; + } + + ~AddressableBuffer(); + }; + AddressableBuffer addressableBuffer; + + VarLoc() = default; + + VarLoc(SILValue value, SILAccessEnforcement access, + SILValue box = SILValue()) + : value(value), box(box), access(access) + {} }; /// VarLocs - Entries in this map are generated when a PatternBindingDecl is /// emitted. The map is queried to produce the lvalue for a DeclRefExpr to /// a local variable. llvm::DenseMap VarLocs; + + /// Establish the scope for the addressable buffer that might be allocated + /// for a local variable binding. + /// + /// This must be enclosed within the scope of the value binding for the + /// variable, and cover the scope in which the variable can be referenced. + void enterLocalVariableAddressableBufferScope(VarDecl *decl); + + /// Get a stable address which is suitable for forming dependent pointers + /// if possible. + SILValue getLocalVariableAddressableBuffer(VarDecl *decl, + SILLocation loc, + ValueOwnership ownership); /// The local auxiliary declarations for the parameters of this function that /// need to be emitted inside the next brace statement. diff --git a/lib/SILGen/SILGenGlobalVariable.cpp b/lib/SILGen/SILGenGlobalVariable.cpp index 6dbab1b45d4..1d3e03b5590 100644 --- a/lib/SILGen/SILGenGlobalVariable.cpp +++ b/lib/SILGen/SILGenGlobalVariable.cpp @@ -134,7 +134,7 @@ SILGenFunction::emitGlobalVariableRef(SILLocation loc, VarDecl *var, RegularLocation::getAutoGeneratedLocation(), silG, /*dependencyToken=*/ SILValue()); - VarLocs[var] = SILGenFunction::VarLoc::get(addr); + VarLocs[var] = VarLoc(addr, SILAccessEnforcement::Dynamic); return ManagedValue::forLValue(addr); } diff --git a/lib/SILGen/SILGenLValue.cpp b/lib/SILGen/SILGenLValue.cpp index 57b50b41436..2f8dd3cbbc4 100644 --- a/lib/SILGen/SILGenLValue.cpp +++ b/lib/SILGen/SILGenLValue.cpp @@ -1357,10 +1357,12 @@ static bool areCertainlyEqualArgumentLists(const ArgumentList *l1, } static LValueOptions getBaseOptions(LValueOptions options, - AccessStrategy strategy) { + AccessStrategy strategy, + bool tryAddressable) { return (strategy.getKind() == AccessStrategy::Storage ? options.forProjectedBaseLValue() - : options.forComputedBaseLValue()); + : options.forComputedBaseLValue()) + .withAddressable(tryAddressable); } static ArgumentSource emitBaseValueForAccessor(SILGenFunction &SGF, @@ -3099,17 +3101,30 @@ public: /*useOldABI=*/false); auto baseFormalType = getBaseFormalType(e->getBase()); + CanType formalRValueType = getSubstFormalRValueType(e); + AbstractionPattern orig = AbstractionPattern::getInvalid(); + bool addressable = false; + // If the access produces a dependent value, and the base is addressable, + // then + if (!formalRValueType->isEscapable() + && SGF.getTypeLowering(baseFormalType) + .getRecursiveProperties() + .isAddressableForDependencies()) { + addressable = true; + orig = AbstractionPattern::getOpaque(); + } + LValue lv = visit( e->getBase(), getBaseAccessKind(SGF.SGM, var, accessKind, strategy, baseFormalType, /*for borrow*/ true), - getBaseOptions(options, strategy)); + getBaseOptions(options, strategy, addressable)); std::optional actorIso; if (e->isImplicitlyAsync()) actorIso = getActorIsolation(var); lv.addMemberVarComponent(SGF, e, var, e->getMember().getSubstitutions(), options, e->isSuper(), accessKind, strategy, - getSubstFormalRValueType(e), + formalRValueType, false /*is on self parameter*/, actorIso); SGF.SGM.noteMemberRefExpr(e); @@ -3163,6 +3178,25 @@ public: } }; +static ValueOwnership mapAddressableValueOwnership(SGFAccessKind accessKind) { + switch (accessKind) { + case SGFAccessKind::IgnoredRead: + case SGFAccessKind::BorrowedAddressRead: + case SGFAccessKind::BorrowedObjectRead: + case SGFAccessKind::OwnedAddressRead: + case SGFAccessKind::OwnedObjectRead: + return ValueOwnership::Shared; + + case SGFAccessKind::Write: + case SGFAccessKind::OwnedAddressConsume: + case SGFAccessKind::OwnedObjectConsume: + case SGFAccessKind::ReadWrite: + return ValueOwnership::InOut; + } + llvm_unreachable("covered switch"); + +} + LValue SILGenLValue::visitRec(Expr *e, SGFAccessKind accessKind, LValueOptions options, AbstractionPattern orig) { // First see if we have an lvalue type. If we do, then quickly handle that and @@ -3171,6 +3205,27 @@ LValue SILGenLValue::visitRec(Expr *e, SGFAccessKind accessKind, return visitRecInOut(*this, e, accessKind, options, orig); } + // If the component wants an addressable base, see whether we can provide + // one. + if (options.TryAddressable) { + auto ownership = mapAddressableValueOwnership(accessKind); + if (auto addressable + = SGF.tryEmitAddressableParameterAsAddress(e, ownership)) { + LValue lv; + auto typeData = LValueTypeData{ + accessKind, + AbstractionPattern::getOpaque(), + getSubstFormalRValueType(e), + addressable.getType().getASTType(), + }; + lv.add(addressable, + std::nullopt, + typeData, + /*rvalue*/ ownership != ValueOwnership::InOut); + return lv; + } + } + // If the base is a load of a noncopyable type (or, eventually, when we have // a `borrow x` operator, the operator is used on the base here), we want to // apply the lvalue within a formal access to the original value instead of @@ -3981,18 +4036,32 @@ LValue SILGenLValue::visitMemberRefExpr(MemberRefExpr *e, } } + CanType substFormalRValueType = getSubstFormalRValueType(e); + CanType baseTy = getBaseFormalType(e->getBase()); + AbstractionPattern orig = AbstractionPattern::getInvalid(); + bool addressable = false; + // If the access produces a dependent value, and the base is addressable, + // then + if (!substFormalRValueType->isEscapable() + && SGF.getTypeLowering(baseTy) + .getRecursiveProperties() + .isAddressableForDependencies()) { + addressable = true; + orig = AbstractionPattern::getOpaque(); + } + LValue lv = visitRec(e->getBase(), getBaseAccessKind(SGF.SGM, var, accessKind, strategy, - getBaseFormalType(e->getBase()), + baseTy, /* for borrow */ false), - getBaseOptions(options, strategy)); + getBaseOptions(options, strategy, addressable), + orig); assert(lv.isValid()); std::optional actorIso; if (e->isImplicitlyAsync()) actorIso = getActorIsolation(var); - CanType substFormalRValueType = getSubstFormalRValueType(e); lv.addMemberVarComponent(SGF, e, var, e->getMember().getSubstitutions(), options, e->isSuper(), accessKind, strategy, substFormalRValueType, isOnSelfParameter, actorIso); @@ -4160,7 +4229,6 @@ LValue SILGenLValue::visitSubscriptExpr(SubscriptExpr *e, auto decl = cast(e->getDecl().getDecl()); auto subs = e->getDecl().getSubstitutions(); - auto accessSemantics = e->getAccessSemantics(); auto strategy = decl->getAccessStrategy( accessSemantics, getFormalAccessKind(accessKind), @@ -4189,11 +4257,26 @@ LValue SILGenLValue::visitSubscriptExpr(SubscriptExpr *e, } } + auto baseTy = getBaseFormalType(e->getBase()); + CanType formalRValueType = getSubstFormalRValueType(e); + AbstractionPattern orig = AbstractionPattern::getInvalid(); + bool addressable = false; + // If the access produces a dependent value, and the base is addressable, + // then + if (!formalRValueType->isEscapable() + && SGF.getTypeLowering(baseTy) + .getRecursiveProperties() + .isAddressableForDependencies()) { + addressable = true; + orig = AbstractionPattern::getOpaque(); + } + LValue lv = visitRec(e->getBase(), getBaseAccessKind(SGF.SGM, decl, accessKind, strategy, - getBaseFormalType(e->getBase()), + baseTy, /*for borrow*/ false), - getBaseOptions(options, strategy)); + getBaseOptions(options, strategy, addressable), + orig); assert(lv.isValid()); // Now that the base components have been resolved, check the isolation for @@ -4205,7 +4288,6 @@ LValue SILGenLValue::visitSubscriptExpr(SubscriptExpr *e, auto *argList = e->getArgs(); auto indices = SGF.prepareSubscriptIndices(e, decl, subs, strategy, argList); - CanType formalRValueType = getSubstFormalRValueType(e); lv.addMemberSubscriptComponent(SGF, e, decl, subs, options, e->isSuper(), accessKind, strategy, formalRValueType, std::move(indices), diff --git a/lib/SILGen/SILGenPattern.cpp b/lib/SILGen/SILGenPattern.cpp index 305d3fa0381..593a76b7429 100644 --- a/lib/SILGen/SILGenPattern.cpp +++ b/lib/SILGen/SILGenPattern.cpp @@ -1430,7 +1430,8 @@ void PatternMatchEmission::bindBorrow(Pattern *pattern, VarDecl *var, MarkUnresolvedNonCopyableValueInst::CheckKind::NoConsumeOrAssign, MarkUnresolvedNonCopyableValueInst::IsStrict); - SGF.VarLocs[var] = SILGenFunction::VarLoc::get(bindValue.getValue()); + SGF.VarLocs[var] = SILGenFunction::VarLoc(bindValue.getValue(), + SILAccessEnforcement::Unknown); } /// Evaluate a guard expression and, if it returns false, branch to @@ -3168,8 +3169,9 @@ static void switchCaseStmtSuccessCallback(SILGenFunction &SGF, } // Ok, we found a match. Update the VarLocs for the case block. - auto v = SGF.VarLocs[vd]; - SGF.VarLocs[expected] = v; + auto &v = SGF.VarLocs[vd]; + SGF.VarLocs[expected] + = SILGenFunction::VarLoc(v.value, v.access, v.box); // Emit a debug description for the variable, nested within a scope // for the pattern match. @@ -3831,7 +3833,7 @@ void SILGenFunction::emitSwitchFallthrough(FallthroughStmt *S) { continue; } - auto varLoc = VarLocs[var]; + auto &varLoc = VarLocs[var]; SILValue value = varLoc.value; if (value->getType().isAddressOnly(F)) { @@ -3894,8 +3896,8 @@ void SILGenFunction::emitCatchDispatch(DoCatchStmt *S, ManagedValue exn, } // Ok, we found a match. Update the VarLocs for the case block. - auto v = VarLocs[vd]; - VarLocs[expected] = v; + auto &v = VarLocs[vd]; + VarLocs[expected] = VarLoc(v.value, v.access, v.box); // Emit a debug description of the incoming arg, nested within the scope // for the pattern match. diff --git a/lib/SILGen/SILGenProlog.cpp b/lib/SILGen/SILGenProlog.cpp index f1a52668b36..708e12c5e38 100644 --- a/lib/SILGen/SILGenProlog.cpp +++ b/lib/SILGen/SILGenProlog.cpp @@ -75,7 +75,7 @@ SILValue SILGenFunction::emitSelfDeclForDestructor(VarDecl *selfDecl) { selfValue = addr; } - VarLocs[selfDecl] = VarLoc::get(selfValue); + VarLocs[selfDecl] = VarLoc(selfValue, SILAccessEnforcement::Unknown); SILLocation PrologueLoc(selfDecl); PrologueLoc.markAsPrologue(); B.emitDebugDescription(PrologueLoc, selfValue, dv); @@ -697,12 +697,6 @@ public: if (FormalParamTypes) FormalParamTypes->finish(); loweredParams.finish(); - - for (auto addressableParam : AddressableParams) { - assert(SGF.VarLocs.contains(addressableParam)); - SGF.VarLocs[addressableParam].addressable = true; - } - return ArgNo; } @@ -772,7 +766,8 @@ private: }; auto completeUpdate = [&](ManagedValue value) -> void { SGF.B.emitDebugDescription(loc, value.getValue(), varinfo); - SGF.VarLocs[pd] = SILGenFunction::VarLoc::get(value.getValue()); + SGF.VarLocs[pd] = SILGenFunction::VarLoc(value.getValue(), + SILAccessEnforcement::Unknown); calledCompletedUpdate = true; }; @@ -829,7 +824,10 @@ private: // We manually set calledCompletedUpdate to true since we want to use // the debug info from the box rather than insert a custom debug_value. calledCompletedUpdate = true; - SGF.VarLocs[pd] = SILGenFunction::VarLoc::get(destAddr, box); + SGF.VarLocs[pd] = SILGenFunction::VarLoc(destAddr, + pd->isImmutableInFunctionBody() ? SILAccessEnforcement::Unknown + : SILAccessEnforcement::Dynamic, + box); return; } @@ -914,6 +912,7 @@ private: argrv.ensurePlusOne(SGF, loc).forwardInto(SGF, loc, mutableBox.get()); return; } + // If the variable is immutable, we can bind the value as is. // Leave the cleanup on the argument, if any, in place to consume the // argument if we're responsible for it. @@ -921,6 +920,7 @@ private: if (!argrv.getType().isAddress()) { // NOTE: We setup SGF.VarLocs[pd] in updateArgumentValueForBinding. updateArgumentValueForBinding(argrv, loc, pd, varinfo); + SGF.enterLocalVariableAddressableBufferScope(pd); return; } @@ -929,9 +929,12 @@ private: allocStack->setIsFromVarDecl(); if (SGF.getASTContext().SILOpts.supportsLexicalLifetimes( SGF.getModule()) && - SGF.F.getLifetime(pd, allocStack->getType()).isLexical()) + SGF.F.getLifetime(pd, allocStack->getType()).isLexical()) { allocStack->setIsLexical(); - SGF.VarLocs[pd] = SILGenFunction::VarLoc::get(allocStack); + } + SGF.VarLocs[pd] = SILGenFunction::VarLoc(allocStack, + SILAccessEnforcement::Unknown); + SGF.enterLocalVariableAddressableBufferScope(pd); return; } @@ -1036,7 +1039,22 @@ private: debugInst->moveBefore(valueInst); } } - SGF.VarLocs[pd] = SILGenFunction::VarLoc::get(argrv.getValue()); + + SILAccessEnforcement access; + switch (pd->getValueOwnership()) { + case ValueOwnership::Shared: + case ValueOwnership::Owned: + case ValueOwnership::Default: + access = SILAccessEnforcement::Unknown; + break; + + case ValueOwnership::InOut: + access = SILAccessEnforcement::Static; + break; + } + + SGF.VarLocs[pd] = SILGenFunction::VarLoc(argrv.getValue(), access); + SGF.enterLocalVariableAddressableBufferScope(pd); } void emitParam(ParamDecl *PD) { @@ -1224,6 +1242,7 @@ static void emitCaptureArguments(SILGenFunction &SGF, auto expansion = SGF.getTypeExpansionContext(); auto captureKind = SGF.SGM.Types.getDeclCaptureKind(capture, expansion); + SILAccessEnforcement enforcement; switch (captureKind) { case CaptureKind::Constant: { assert(!isPack); @@ -1270,6 +1289,7 @@ static void emitCaptureArguments(SILGenFunction &SGF, } arg = val.getValue(); + enforcement = SILAccessEnforcement::Unknown; break; } @@ -1293,6 +1313,9 @@ static void emitCaptureArguments(SILGenFunction &SGF, if (isNoImplicitCopy && !arg->getType().isMoveOnly()) { arg = SGF.B.createCopyableToMoveOnlyWrapperAddr(VD, arg); } + enforcement = isMutable + ? SILAccessEnforcement::Dynamic + : SILAccessEnforcement::Unknown; break; } case CaptureKind::StorageAddress: @@ -1355,6 +1378,9 @@ static void emitCaptureArguments(SILGenFunction &SGF, MarkUnresolvedNonCopyableValueInst::CheckKind:: ConsumableAndAssignable); } + enforcement = isInOut + ? SILAccessEnforcement::Static + : SILAccessEnforcement::Unknown; break; } } @@ -1379,7 +1405,7 @@ static void emitCaptureArguments(SILGenFunction &SGF, arg = packValue; } - SGF.VarLocs[VD] = SILGenFunction::VarLoc::get(arg, box); + SGF.VarLocs[VD] = SILGenFunction::VarLoc(arg, enforcement, box); SILDebugVariable DbgVar(VD->isLet(), ArgNo); if (auto *AllocStack = dyn_cast(arg)) { AllocStack->setArgNo(ArgNo); diff --git a/test/SILGen/addressable_representation.swift b/test/SILGen/addressable_representation.swift new file mode 100644 index 00000000000..2301fab3323 --- /dev/null +++ b/test/SILGen/addressable_representation.swift @@ -0,0 +1,205 @@ +// RUN: %target-swift-emit-silgen -enable-experimental-feature LifetimeDependence -enable-experimental-feature BuiltinModule -enable-experimental-feature AddressableTypes -enable-experimental-feature AddressableParameters -module-name main %s -define-availability 'Span 0.1:macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999' | %FileCheck %s + +// REQUIRES: swift_feature_LifetimeDependence +// REQUIRES: swift_feature_BuiltinModule +// REQUIRES: swift_feature_AddressableTypes +// REQUIRES: swift_feature_AddressableParameters + +import Builtin + +// CHECK-LABEL: sil {{.*}} @$s{{.*}}5test1 +@available(Span 0.1, *) +func test1(_ s: String) { + // CHECK: [[ADDRESSABLE:%.*]] = alloc_stack $CO1 + // CHECK: [[ADDRESSABLE_BORROW:%.*]] = store_borrow {{.*}} to [[ADDRESSABLE]] + let c = CO1(s) + // CHECK: function_ref @$s{{.*}}10getStorage + let storage = c.getStorage() + // CHECK: function_ref @$s{{.*}}4utf8{{.*}}vg : + let v = Array(storage[0].utf8) + // CHECK: function_ref @$s{{.*}}5print + print(v) + // CHECK: end_borrow [[ADDRESSABLE_BORROW]] + // CHECK: dealloc_stack [[ADDRESSABLE]] +} + +// CHECK-LABEL: sil {{.*}} @$s{{.*}}5test2 +@available(Span 0.1, *) +func test2(_ s: String, _ c1: Bool, _ c2: Bool) { + // CHECK: [[ADDRESSABLE:%.*]] = alloc_stack $CO1 + // CHECK: [[ADDRESSABLE_BORROW:%.*]] = store_borrow {{.*}} to [[ADDRESSABLE]] + let c = CO1(s) + + // CHECK: cond_br {{.*}}, [[EXIT1:bb[0-9]+]], [[CONT1:bb[0-9]+]] + // CHECK: [[EXIT1]]: + // CHECK: end_borrow [[ADDRESSABLE_BORROW]] + // CHECK: dealloc_stack [[ADDRESSABLE]] + // CHECK: br [[EPILOG:bb[0-9]+]] + // CHECK: [[CONT1]]: + if c1 { + return + } + + let storage = c.getStorage() + + // CHECK: cond_br {{.*}}, [[EXIT2:bb[0-9]+]], [[CONT2:bb[0-9]+]] + // CHECK: [[EXIT2]]: + // CHECK: end_borrow [[ADDRESSABLE_BORROW]] + // CHECK: dealloc_stack [[ADDRESSABLE]] + // CHECK: br [[EPILOG]] + // CHECK: [[CONT2]]: + if c2 { + return + } + + let v = Array(storage[0].utf8) + print(v) + // CHECK: function_ref @$s{{.*}}5print + // CHECK: end_borrow [[ADDRESSABLE_BORROW]] + // CHECK: dealloc_stack [[ADDRESSABLE]] + // CHECK: br [[EPILOG]] + +} + +// CHECK-LABEL: sil {{.*}} @$s{{.*}}5test3 +@available(Span 0.1, *) +func test3(_ c: CO1) { + // CHECK: [[ENTRY:bb[0-9]+]]([[C:%[0-9]+]] : @guaranteed $CO1): + // CHECK: [[ADDRESSABLE:%.*]] = alloc_stack $CO1 + // CHECK: [[ADDRESSABLE_BORROW:%.*]] = store_borrow [[C]] to [[ADDRESSABLE]] + // CHECK: function_ref @$s{{.*}}10getStorage + let storage = c.getStorage() + // CHECK: function_ref @$s{{.*}}4utf8{{.*}}vg : + let v = Array(storage[0].utf8) + // CHECK: function_ref @$s{{.*}}5print + print(v) + // CHECK: end_borrow [[ADDRESSABLE_BORROW]] + // CHECK: dealloc_stack [[ADDRESSABLE]] +} + +// CHECK-LABEL: sil {{.*}} @$s{{.*}}6test3a +@available(Span 0.1, *) +func test3a(_ c: borrowing CO1) { + // CHECK: [[ENTRY:bb[0-9]+]]([[C:%[0-9]+]] : @noImplicitCopy @guaranteed $CO1): + // CHECK: [[WRAP:%.*]] = copyable_to_moveonlywrapper [guaranteed] [[C]] + // CHECK: [[COPY:%.*]] = copy_value [[WRAP]] + // CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[COPY]] + // CHECK: [[ADDRESSABLE:%.*]] = alloc_stack $@moveOnly CO1 + // CHECK: [[ADDRESSABLE_BORROW:%.*]] = store_borrow [[MARK]] to [[ADDRESSABLE]] + // CHECK: function_ref @$s{{.*}}10getStorage + let storage = c.getStorage() + // CHECK: function_ref @$s{{.*}}4utf8{{.*}}vg : + let v = Array(storage[0].utf8) + // CHECK: function_ref @$s{{.*}}5print + print(v) + // CHECK: end_borrow [[ADDRESSABLE_BORROW]] + // CHECK: dealloc_stack [[ADDRESSABLE]] + // CHECK: destroy_value [[MARK]] +} + +// CHECK-LABEL: sil {{.*}} @$s{{.*}}5test4 +@available(Span 0.1, *) +func test4(_ c: CO1, _ c1: Bool, _ c2: Bool) { + // CHECK: [[ENTRY:bb[0-9]+]]([[C:%[0-9]+]] : @guaranteed $CO1, {{.*}}): + // CHECK: [[ADDRESSABLE:%.*]] = alloc_stack $CO1 + // CHECK: [[ADDRESSABLE_BORROW:%.*]] = store_borrow [[C]] to [[ADDRESSABLE]] + + // CHECK: cond_br {{.*}}, [[EXIT1:bb[0-9]+]], [[CONT1:bb[0-9]+]] + // CHECK: [[EXIT1]]: + // CHECK: br [[EPILOG:bb[0-9]+]] + // CHECK: [[CONT1]]: + if c1 { + return + } + + let storage = c.getStorage() + + // CHECK: cond_br {{.*}}, [[EXIT2:bb[0-9]+]], [[CONT2:bb[0-9]+]] + // CHECK: [[EXIT2]]: + // CHECK: br [[EPILOG]] + // CHECK: [[CONT2]]: + if c2 { + return + } + + let v = Array(storage[0].utf8) + print(v) + // CHECK: function_ref @$s{{.*}}5print + // CHECK: br [[EPILOG]] + + // CHECK: [[EPILOG]]: + // CHECK: end_borrow [[ADDRESSABLE_BORROW]] + // CHECK: dealloc_stack [[ADDRESSABLE]] +} + +// CHECK-LABEL: sil {{.*}} @$s{{.*}}5test5 +func test5(_ f: @escaping () -> ()) { + // CHECK: [[THUNKED:%.*]] = partial_apply + // CHECK: [[CONV:%.*]] = convert_function [[THUNKED]] + // CHECK: [[ADDRESSABLE:%.*]] = alloc_stack + // CHECK: [[ADDRESSABLE_BORROW:%.*]] = store_borrow [[CONV]] to [[ADDRESSABLE]] + + // CHECK: function_ref @$s{{.*}}22addressableFunctionArg + addressableFunctionArg(f) + // CHECK: function_ref @$s{{.*}}22addressableFunctionArg + addressableFunctionArg(f) + + // CHECK: end_borrow [[ADDRESSABLE_BORROW]] + // CHECK: dealloc_stack [[ADDRESSABLE]] + // CHECK: destroy_value [[CONV]] +} + +// CHECK-LABEL: sil {{.*}} @$s{{.*}}5test6 +@available(Span 0.1, *) +func test6(_ c: inout CO1) { + // CHECK: [[ENTRY:bb[0-9]+]]([[C:%[0-9]+]] : $*CO1): + + // SILGen still only emits an access around the immediate call, but + // lifetime analysis can extend this access scope. + + // CHECK: [[ACCESS:%.*]] = begin_access [read] [static] [[C]] + // CHECK: function_ref @$s{{.*}}10getStorage + // CHECK: end_access [[ACCESS]] + let storage = c.getStorage() + let v = Array(storage[0].utf8) + print(v) +} + +// CHECK-LABEL: sil {{.*}} @$s{{.*}}5test7 +@available(Span 0.1, *) +func test7(_ c: CO1) { + var c2 = c + + // SILGen still only emits an access around the immediate call, but + // lifetime analysis can extend this access scope. + + // CHECK: [[ACCESS:%.*]] = begin_access [read] [dynamic] + // CHECK: function_ref @$s{{.*}}10getStorage + // CHECK: end_access [[ACCESS]] + let storage = c2.getStorage() + let v = Array(storage[0].utf8) + print(v) +} + +func addressableFunctionArg(_ f: @_addressable @escaping () -> ()) {} + +@available(Span 0.1, *) +@_addressableForDependencies +struct CO1 { + var s: String + + init(_ s: String) { self.s = s } + + var storage: Span { + @lifetime(borrow self) + borrowing get { + fatalError() + } + } + + @lifetime(borrow self) + func getStorage() -> Span { + fatalError() + } +} +