diff --git a/include/swift/SILOptimizer/Utils/KeyPathProjector.h b/include/swift/SILOptimizer/Utils/KeyPathProjector.h new file mode 100644 index 0000000000000..6c64c469bf776 --- /dev/null +++ b/include/swift/SILOptimizer/Utils/KeyPathProjector.h @@ -0,0 +1,84 @@ +//===-- KeyPathProjector.h - Project a static key path ----------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Utility class to project a statically known key path +/// expression to a direct property access sequence. +/// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_SILOPTIMIZER_UTILS_KEYPATHPROJECTOR_H +#define SWIFT_SILOPTIMIZER_UTILS_KEYPATHPROJECTOR_H + +#include "swift/SIL/SILBuilder.h" +#include + +namespace swift { + +/// Projects a statically known key path expression to +/// a direct property access. +class KeyPathProjector { +public: + /// The type of a key path access. + enum class AccessType { + /// A get-only access (i.e. swift_getAtKeyPath). + Get, + + /// A set-only access (i.e. swift_setAtWritableKeyPath). + Set, + + /// A modification (i.e. swift_modifyAtWritableKeyPath). + Modify + }; + + /// Creates a key path projector for a key path. + /// + /// Returns nullptr if \p keyPath is not a keypath instruction or if there is + /// any other reason why the optimization cannot be done. + /// + /// \param keyPath The key path to project. Must be the result of either + /// a keypath instruction or an upcast of a key path instruction. + /// \param root The address of the object the key path is applied to. + /// \param loc The location of the key path application. + /// \param builder The SILBuilder to use. + static std::unique_ptr + create(SILValue keyPath, SILValue root, SILLocation loc, SILBuilder &builder); + + /// Projects the key path to an address. Sets up the projection, + /// invokes the callback, then tears down the projection. + /// \param accessType The access type of the projected address. + /// \param callback A callback to invoke with the projected adddress. + /// The projected address is only valid from within \p callback. + /// If accessType is Get or Modify, the projected addres is an + /// initialized address type. If accessType is set, the projected + /// address points to uninitialized memory. + virtual void project(AccessType accessType, + std::function callback) = 0; + + virtual ~KeyPathProjector() {}; + + /// Whether this projection returns a struct. + virtual bool isStruct() = 0; +protected: + KeyPathProjector(SILLocation loc, SILBuilder &builder) + : loc(loc), builder(builder) {} + + /// The location of the key path application. + SILLocation loc; + + /// The SILBuilder to use. + SILBuilder &builder; +}; + +} // end namespace swift + +#endif /* KeyPathProjector_h */ diff --git a/lib/SILGen/SILGen.cpp b/lib/SILGen/SILGen.cpp index 267950534161e..205746179bc56 100644 --- a/lib/SILGen/SILGen.cpp +++ b/lib/SILGen/SILGen.cpp @@ -1460,6 +1460,7 @@ void SILGenModule::tryEmitPropertyDescriptor(AbstractStorageDecl *decl) { baseOperand, needsGenericContext, subs, decl, {}, baseTy->getCanonicalType(), + M.getSwiftModule(), /*property descriptor*/ true); (void)SILProperty::create(M, /*serialized*/ false, decl, component); diff --git a/lib/SILGen/SILGen.h b/lib/SILGen/SILGen.h index c4c9d1ed92b8f..11774bc0f0a9e 100644 --- a/lib/SILGen/SILGen.h +++ b/lib/SILGen/SILGen.h @@ -339,6 +339,7 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor { AbstractStorageDecl *storage, ArrayRef indexHashables, CanType baseTy, + DeclContext *useDC, bool forPropertyDescriptor); /// Known functions for bridging. diff --git a/lib/SILGen/SILGenExpr.cpp b/lib/SILGen/SILGenExpr.cpp index a7e254f5d797b..e3df8b3b98a41 100644 --- a/lib/SILGen/SILGenExpr.cpp +++ b/lib/SILGen/SILGenExpr.cpp @@ -3401,6 +3401,7 @@ SILGenModule::emitKeyPathComponentForDecl(SILLocation loc, AbstractStorageDecl *storage, ArrayRef indexHashables, CanType baseTy, + DeclContext *useDC, bool forPropertyDescriptor) { auto baseDecl = storage; @@ -3470,8 +3471,8 @@ SILGenModule::emitKeyPathComponentForDecl(SILLocation loc, // supply the settability if needed. We only reference it here if the // setter is public. if (shouldUseExternalKeyPathComponent()) - return storage->isSettable(M.getSwiftModule()) - && storage->isSetterAccessibleFrom(M.getSwiftModule()); + return storage->isSettable(useDC) + && storage->isSetterAccessibleFrom(useDC); return storage->isSettable(storage->getDeclContext()); }; @@ -3625,6 +3626,7 @@ RValue RValueEmitter::visitKeyPathExpr(KeyPathExpr *E, SGFContext C) { decl, component.getSubscriptIndexHashableConformances(), baseTy, + SGF.FunctionDC, /*for descriptor*/ false)); baseTy = loweredComponents.back().getComponentType(); if (kind == KeyPathExpr::Component::Kind::Property) diff --git a/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp b/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp index 7766f3ccea9c2..4b4d37d77dd3f 100644 --- a/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp +++ b/lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp @@ -28,11 +28,13 @@ #include "swift/SILOptimizer/Analysis/ValueTracking.h" #include "swift/SILOptimizer/Utils/CFGOptUtils.h" #include "swift/SILOptimizer/Utils/Existential.h" +#include "swift/SILOptimizer/Utils/KeyPathProjector.h" #include "swift/SILOptimizer/Utils/ValueLifetime.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" +#include using namespace swift; using namespace swift::PatternMatch; @@ -207,92 +209,6 @@ SILCombiner::optimizeApplyOfConvertFunctionInst(FullApplySite AI, return NAI; } -/// Ends the begin_access "scope" if a begin_access was inserted for optimizing -/// a keypath pattern. -static void insertEndAccess(BeginAccessInst *&beginAccess, bool isModify, - SILBuilder &builder) { - if (beginAccess) { - builder.createEndAccess(beginAccess->getLoc(), beginAccess, - /*aborted*/ false); - if (isModify) - beginAccess->setAccessKind(SILAccessKind::Modify); - beginAccess = nullptr; - } -} - -/// Creates the projection pattern for a keypath instruction. -/// -/// Currently only the StoredProperty pattern is handled. -/// TODO: handle other patterns, like getters/setters, optional chaining, etc. -/// -/// Returns false if \p keyPath is not a keypath instruction or if there is any -/// other reason why the optimization cannot be done. -static SILValue createKeypathProjections(SILValue keyPath, SILValue root, - SILLocation loc, - BeginAccessInst *&beginAccess, - SILBuilder &builder) { - if (auto *upCast = dyn_cast(keyPath)) - keyPath = upCast->getOperand(); - - // Is it a keypath instruction at all? - auto *kpInst = dyn_cast(keyPath); - if (!kpInst || !kpInst->hasPattern()) - return SILValue(); - - auto components = kpInst->getPattern()->getComponents(); - - // Check if the keypath only contains patterns which we support. - for (const KeyPathPatternComponent &comp : components) { - if (comp.getKind() != KeyPathPatternComponent::Kind::StoredProperty) - return SILValue(); - } - - SILValue addr = root; - for (const KeyPathPatternComponent &comp : components) { - assert(comp.getKind() == KeyPathPatternComponent::Kind::StoredProperty); - VarDecl *storedProperty = comp.getStoredPropertyDecl(); - SILValue elementAddr; - if (addr->getType().getStructOrBoundGenericStruct()) { - addr = builder.createStructElementAddr(loc, addr, storedProperty); - } else if (addr->getType().getClassOrBoundGenericClass()) { - SingleValueInstruction *Ref = builder.createLoad(loc, addr, - LoadOwnershipQualifier::Unqualified); - insertEndAccess(beginAccess, /*isModify*/ false, builder); - - // Handle the case where the storedProperty is in a super class. - while (Ref->getType().getClassOrBoundGenericClass() != - storedProperty->getDeclContext()) { - SILType superCl = Ref->getType().getSuperclass(); - if (!superCl) { - // This should never happen, because the property should be in the - // decl or in a superclass of it. Just handle this to be on the safe - // side. - return SILValue(); - } - Ref = builder.createUpcast(loc, Ref, superCl); - } - - addr = builder.createRefElementAddr(loc, Ref, storedProperty); - - // Class members need access enforcement. - if (builder.getModule().getOptions().EnforceExclusivityDynamic) { - beginAccess = builder.createBeginAccess(loc, addr, SILAccessKind::Read, - SILAccessEnforcement::Dynamic, - /*noNestedConflict*/ false, - /*fromBuiltin*/ false); - addr = beginAccess; - } - } else { - // This should never happen, as a stored-property pattern can only be - // applied to classes and structs. But to be safe - and future prove - - // let's handle this case and bail. - insertEndAccess(beginAccess, /*isModify*/ false, builder); - return SILValue(); - } - } - return addr; -} - /// Try to optimize a keypath application with an apply instruction. /// /// Replaces (simplified SIL): @@ -311,13 +227,13 @@ bool SILCombiner::tryOptimizeKeypath(ApplyInst *AI) { return false; SILValue keyPath, rootAddr, valueAddr; - bool isModify = false; + bool isSet = false; if (callee->getName() == "swift_setAtWritableKeyPath" || callee->getName() == "swift_setAtReferenceWritableKeyPath") { keyPath = AI->getArgument(1); rootAddr = AI->getArgument(0); valueAddr = AI->getArgument(2); - isModify = true; + isSet = true; } else if (callee->getName() == "swift_getAtKeyPath") { keyPath = AI->getArgument(2); rootAddr = AI->getArgument(1); @@ -325,22 +241,26 @@ bool SILCombiner::tryOptimizeKeypath(ApplyInst *AI) { } else { return false; } - - BeginAccessInst *beginAccess = nullptr; - SILValue projectedAddr = createKeypathProjections(keyPath, rootAddr, - AI->getLoc(), beginAccess, - Builder); - if (!projectedAddr) + + auto projector = KeyPathProjector::create(keyPath, rootAddr, + AI->getLoc(), Builder); + if (!projector) return false; - - if (isModify) { - Builder.createCopyAddr(AI->getLoc(), valueAddr, projectedAddr, - IsTake, IsNotInitialization); - } else { - Builder.createCopyAddr(AI->getLoc(), projectedAddr, valueAddr, - IsNotTake, IsInitialization); - } - insertEndAccess(beginAccess, isModify, Builder); + + KeyPathProjector::AccessType accessType; + if (isSet) accessType = KeyPathProjector::AccessType::Set; + else accessType = KeyPathProjector::AccessType::Get; + + projector->project(accessType, [&](SILValue projectedAddr) { + if (isSet) { + Builder.createCopyAddr(AI->getLoc(), valueAddr, projectedAddr, + IsTake, IsInitialization); + } else { + Builder.createCopyAddr(AI->getLoc(), projectedAddr, valueAddr, + IsNotTake, IsInitialization); + } + }); + eraseInstFromFunction(*AI); ++NumOptimizedKeypaths; return true; @@ -385,19 +305,24 @@ bool SILCombiner::tryOptimizeInoutKeypath(BeginApplyInst *AI) { EndApplyInst *endApply = dyn_cast(AIUse->getUser()); if (!endApply) return false; - - BeginAccessInst *beginAccess = nullptr; - SILValue projectedAddr = createKeypathProjections(keyPath, rootAddr, - AI->getLoc(), beginAccess, - Builder); - if (!projectedAddr) + + auto projector = KeyPathProjector::create(keyPath, rootAddr, + AI->getLoc(), Builder); + if (!projector) return false; + + KeyPathProjector::AccessType accessType; + if (isModify) accessType = KeyPathProjector::AccessType::Modify; + else accessType = KeyPathProjector::AccessType::Get; + + projector->project(accessType, [&](SILValue projectedAddr) { + // Replace the projected address. + valueAddr->replaceAllUsesWith(projectedAddr); + + // Skip to the end of the key path application before cleaning up. + Builder.setInsertionPoint(endApply); + }); - // Replace the projected address. - valueAddr->replaceAllUsesWith(projectedAddr); - - Builder.setInsertionPoint(endApply); - insertEndAccess(beginAccess, isModify, Builder); eraseInstFromFunction(*endApply); eraseInstFromFunction(*AI); ++NumOptimizedKeypaths; diff --git a/lib/SILOptimizer/Utils/CMakeLists.txt b/lib/SILOptimizer/Utils/CMakeLists.txt index e8d871174e021..72587675a5500 100644 --- a/lib/SILOptimizer/Utils/CMakeLists.txt +++ b/lib/SILOptimizer/Utils/CMakeLists.txt @@ -11,6 +11,7 @@ silopt_register_sources( GenericCloner.cpp Generics.cpp InstOptUtils.cpp + KeyPathProjector.cpp LoadStoreOptUtils.cpp LoopUtils.cpp OptimizerStatsUtils.cpp diff --git a/lib/SILOptimizer/Utils/KeyPathProjector.cpp b/lib/SILOptimizer/Utils/KeyPathProjector.cpp new file mode 100644 index 0000000000000..f3d8f6dd0c0f9 --- /dev/null +++ b/lib/SILOptimizer/Utils/KeyPathProjector.cpp @@ -0,0 +1,683 @@ +//===-- KeyPathProjector.cpp - Project a static key path --------*- C++ -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Utility class to project a statically known key path +/// expression to a direct property access sequence. +/// +//===----------------------------------------------------------------------===// + + +#include "swift/SILOptimizer/Utils/KeyPathProjector.h" + +#include "swift/SIL/SILInstruction.h" + +using namespace swift; + + +// Projectors to handle individual key path components. + +/// Projects the root of a key path application. +class RootProjector : public KeyPathProjector { +public: + RootProjector(SILValue root, SILLocation loc, SILBuilder &builder) + : KeyPathProjector(loc, builder), root(root) {} + + void project(AccessType accessType, + std::function callback) override { + if (accessType == AccessType::Set) { + // We're setting the identity key path (\.self). The callback + // expects an uninitialized address, so destroy the old value. + builder.emitDestroyAddr(loc, root); + } + callback(root); + } + + bool isStruct() override { + return root->getType().getStructOrBoundGenericStruct() != nullptr; + } +private: + SILValue root; +}; + +/// Projects a single key path component. +class ComponentProjector : public KeyPathProjector { +protected: + ComponentProjector(const KeyPathPatternComponent &component, + std::unique_ptr parent, + SILLocation loc, SILBuilder &builder) + : KeyPathProjector(loc, builder), + component(component), parent(std::move(parent)) {} + + /// The key path component. + const KeyPathPatternComponent &component; + + /// The projector for the previous components. + std::unique_ptr parent; + + bool isStruct() override { + auto type = component.getComponentType(); + return type.getStructOrBoundGenericStruct() != nullptr; + } + + ~ComponentProjector() override {}; +}; + + +/// Ends the begin_access "scope" if a begin_access was inserted for optimizing +/// a keypath pattern. +static void insertEndAccess(BeginAccessInst *&beginAccess, + SILBuilder &builder) { + if (beginAccess) { + builder.createEndAccess(beginAccess->getLoc(), beginAccess, + /*aborted*/ false); + beginAccess = nullptr; + } +} + +class StoredPropertyProjector : public ComponentProjector { +public: + StoredPropertyProjector(const KeyPathPatternComponent &component, + std::unique_ptr parent, + BeginAccessInst *&beginAccess, + SILLocation loc, SILBuilder &builder) + : ComponentProjector(component, std::move(parent), loc, builder), + beginAccess(beginAccess) {} + + void project(AccessType accessType, + std::function callback) override { + assert(component.getKind() == + KeyPathPatternComponent::Kind::StoredProperty); + + VarDecl *storedProperty = component.getStoredPropertyDecl(); + + if (parent->isStruct()) { + // Reading a struct field -> reading the struct + // Writing or modifying a struct field -> modifying the struct + AccessType parentAccessType; + if (accessType == AccessType::Get) + parentAccessType = AccessType::Get; + else + parentAccessType = AccessType::Modify; + + parent->project(parentAccessType, [&](SILValue parentValue) { + auto addr = builder.createStructElementAddr(loc, parentValue, storedProperty); + // If we're setting, destroy the old value (the callback expects uninitialized memory) + if (accessType == AccessType::Set) + builder.createDestroyAddr(loc, addr); + callback(addr); + }); + } else { + // Accessing a class member -> reading the class + parent->project(AccessType::Get, [&](SILValue parentValue) { + SingleValueInstruction *Ref = builder.createLoad(loc, parentValue, + LoadOwnershipQualifier::Unqualified); + + // If we were previously accessing a class member, we're done now. + insertEndAccess(beginAccess, builder); + + // Handle the case where the storedProperty is in a super class. + while (Ref->getType().getClassOrBoundGenericClass() != + storedProperty->getDeclContext()) { + SILType superCl = Ref->getType().getSuperclass(); + if (!superCl) { + // This should never happen, because the property should be in the + // decl or in a superclass of it. Just handle this to be on the safe + // side. + callback(SILValue()); + return; + } + Ref = builder.createUpcast(loc, Ref, superCl); + } + + SILValue addr = builder.createRefElementAddr(loc, Ref, storedProperty); + + // Class members need access enforcement. + if (builder.getModule().getOptions().EnforceExclusivityDynamic) { + beginAccess = builder.createBeginAccess(loc, addr, SILAccessKind::Read, + SILAccessEnforcement::Dynamic, + /*noNestedConflict*/ false, + /*fromBuiltin*/ false); + if (accessType != AccessType::Get) + beginAccess->setAccessKind(SILAccessKind::Modify); + addr = beginAccess; + } + + // If we're setting, destroy the old value (the callback expects uninitialized memory) + if (accessType == AccessType::Set) + builder.createDestroyAddr(loc, addr); + callback(addr); + + // if a child hasn't started a new access (i.e. beginAccess is unchanged), + // end the access now + if (beginAccess == addr) { + insertEndAccess(beginAccess, builder); + } + }); + } + } +private: + BeginAccessInst *&beginAccess; +}; + +class TupleElementProjector : public ComponentProjector { +public: + TupleElementProjector(const KeyPathPatternComponent &component, + std::unique_ptr parent, + SILLocation loc, SILBuilder &builder) + : ComponentProjector(component, std::move(parent), loc, builder) {} + + void project(AccessType accessType, + std::function callback) override { + assert(component.getKind() == + KeyPathPatternComponent::Kind::TupleElement); + + // Reading a tuple field -> reading the tuple + // Writing or modifying a tuple field -> modifying the tuple + AccessType parentAccessType; + if (accessType == AccessType::Get) + parentAccessType = AccessType::Get; + else + parentAccessType = AccessType::Modify; + + parent->project(parentAccessType, [&](SILValue parentValue) { + auto addr = builder.createTupleElementAddr(loc, parentValue, component.getTupleIndex()); + // If we're setting, destroy the old value (the callback expects uninitialized memory) + if (accessType == AccessType::Set) + builder.createDestroyAddr(loc, addr); + callback(addr); + }); + } +}; + +class GettablePropertyProjector : public ComponentProjector { +public: + GettablePropertyProjector(KeyPathInst *keyPath, + const KeyPathPatternComponent &component, + std::unique_ptr parent, + SubstitutionMap subs, BeginAccessInst *&beginAccess, + SILLocation loc, SILBuilder &builder) + : ComponentProjector(component, std::move(parent), loc, builder), + keyPath(keyPath), subs(subs), beginAccess(beginAccess) {} + + void project(AccessType accessType, + std::function callback) override { + assert(component.getKind() == + KeyPathPatternComponent::Kind::GettableProperty || + component.getKind() == + KeyPathPatternComponent::Kind::SettableProperty); + assert(accessType == AccessType::Get && "property is not settable"); + + parent->project(accessType, [&](SILValue parentValue) { + auto getter = component.getComputedPropertyGetter(); + + // The callback expects a memory address it can read from, + // so allocate a buffer. + auto &function = builder.getFunction(); + auto substType = component.getComponentType().subst(keyPath->getSubstitutions(), + None); + SILType type = function.getLoweredType(substType); + auto addr = builder.createAllocStack(loc, type); + + assertHasNoContext(); + assert(getter->getArguments().size() == 2); + + auto ref = builder.createFunctionRef(loc, getter); + builder.createApply(loc, ref, subs, {addr, parentValue}); + + // If we were previously accessing a class member, we're done now. + insertEndAccess(beginAccess, builder); + + callback(addr); + + builder.createDestroyAddr(loc, addr); + builder.createDeallocStack(loc, addr); + }); + + } +protected: + KeyPathInst *keyPath; + SubstitutionMap subs; + BeginAccessInst *&beginAccess; + + void assertHasNoContext() { + assert(component.getSubscriptIndices().empty() && + component.getExternalSubstitutions().empty() && + "cannot yet optimize key path component with external context; " + "we should have checked for this before trying to project"); + } +}; + +class SettablePropertyProjector : public GettablePropertyProjector { +public: + SettablePropertyProjector(KeyPathInst *keyPath, + const KeyPathPatternComponent &component, + std::unique_ptr parent, + SubstitutionMap subs, BeginAccessInst *&beginAccess, + SILLocation loc, SILBuilder &builder) + : GettablePropertyProjector(keyPath, component, std::move(parent), + subs, beginAccess, loc, builder) {} + + + void project(AccessType accessType, + std::function callback) override { + assert(component.getKind() == + KeyPathPatternComponent::Kind::GettableProperty || + component.getKind() == + KeyPathPatternComponent::Kind::SettableProperty); + + switch (accessType) { + case AccessType::Get: + GettablePropertyProjector::project(accessType, callback); + break; + + case AccessType::Modify: + case AccessType::Set: + AccessType parentAccessType; + if (component.isComputedSettablePropertyMutating()) { + // A mutating setter modifies the parent + parentAccessType = AccessType::Modify; + if (beginAccess) { + beginAccess->setAccessKind(SILAccessKind::Modify); + } + } else { + parentAccessType = AccessType::Get; + } + + parent->project(parentAccessType, [&](SILValue parentValue) { + auto getter = component.getComputedPropertyGetter(); + auto setter = component.getComputedPropertySetter(); + + // The callback expects a memory address it can write to, + // so allocate a writeback buffer. + auto &function = builder.getFunction(); + auto substType = component.getComponentType().subst(keyPath->getSubstitutions(), + None); + SILType type = function.getLoweredType(substType); + auto addr = builder.createAllocStack(loc, type); + + assertHasNoContext(); + assert(getter->getArguments().size() == 2); + assert(setter->getArguments().size() == 2); + + // If this is a modify, we need to call the getter and + // store the result in the writeback buffer. + if (accessType == AccessType::Modify) { + auto getterRef = builder.createFunctionRef(loc, getter); + builder.createApply(loc, getterRef, subs, {addr, parentValue}); + } + + // The callback function will write into the writeback buffer. + callback(addr); + + // Pass the value from the writeback buffer to the setter. + auto setterRef = builder.createFunctionRef(loc, setter); + builder.createApply(loc, setterRef, subs, {addr, parentValue}); + + // Deallocate the writeback buffer. + builder.createDestroyAddr(loc, addr); + builder.createDeallocStack(loc, addr); + }); + break; + } + } +}; + +class OptionalWrapProjector : public ComponentProjector { +public: + OptionalWrapProjector(KeyPathInst *kpInst, + const KeyPathPatternComponent &component, + std::unique_ptr parent, + SILLocation loc, SILBuilder &builder) + : ComponentProjector(component, std::move(parent), loc, builder), + keyPath(kpInst) {} + + void project(AccessType accessType, + std::function callback) override { + assert(component.getKind() == + KeyPathPatternComponent::Kind::OptionalWrap); + assert(accessType == AccessType::Get && "optional wrap components are immutable"); + + parent->project(AccessType::Get, [&](SILValue parentValue) { + auto &function = builder.getFunction(); + auto substType = component.getComponentType().subst(keyPath->getSubstitutions(), + None); + SILType optType = function.getLoweredType(substType); + SILType objType = optType.getOptionalObjectType().getAddressType(); + + assert(objType && "optional wrap must return an optional"); + + // Allocate a buffer for the result. + auto optAddr = builder.createAllocStack(loc, optType); + + // Store the parent result in the enum payload address. + auto someDecl = builder.getASTContext().getOptionalSomeDecl(); + auto objAddr = builder.createInitEnumDataAddr(loc, optAddr, + someDecl, objType); + builder.createCopyAddr(loc, parentValue, objAddr, IsNotTake, IsInitialization); + + // Initialize the Optional enum. + builder.createInjectEnumAddr(loc, optAddr, someDecl); + + callback(optAddr); + + // Destroy the Optional. + builder.createDestroyAddr(loc, optAddr); + builder.createDeallocStack(loc, optAddr); + }); + } + +private: + KeyPathInst *keyPath; +}; + +class OptionalForceProjector : public ComponentProjector { +public: + OptionalForceProjector(const KeyPathPatternComponent &component, + std::unique_ptr parent, + SILLocation loc, SILBuilder &builder) + : ComponentProjector(component, std::move(parent), loc, builder) {} + + void project(AccessType accessType, + std::function callback) override { + assert(component.getKind() == + KeyPathPatternComponent::Kind::OptionalForce); + + parent->project(accessType, [&](SILValue optAddr) { + auto &ctx = builder.getASTContext(); + + auto noneDecl = ctx.getOptionalNoneDecl(); + auto someDecl = ctx.getOptionalSomeDecl(); + + SILType optType = optAddr->getType(); + SILType objType = optType.getOptionalObjectType(); + + if (accessType != AccessType::Set) { + // We're getting (or modifying), so we need to unwrap the optional. + auto int1Type = SILType::getBuiltinIntegerType(1, ctx); + auto falseLiteral = builder.createIntegerLiteral(loc, int1Type, false); + auto trueLiteral = builder.createIntegerLiteral(loc, int1Type, true); + + auto isNil = builder.createSelectEnumAddr(loc, optAddr, int1Type, SILValue(), { + {noneDecl, trueLiteral}, {someDecl, falseLiteral} + }); + builder.createCondFail(loc, isNil, "unexpectedly found nil while " + "unwrapping an Optional key-path expression"); + } + + switch (accessType) { + case AccessType::Get: { + // We have to copy the optional, since unwrapping is destructive. + auto tempAddr = builder.createAllocStack(loc, optType); + builder.createCopyAddr(loc, optAddr, tempAddr, IsNotTake, IsInitialization); + + // Unwrap the optional. + auto objAddr = builder.createUncheckedTakeEnumDataAddr(loc, tempAddr, someDecl, objType); + + callback(objAddr); + + builder.createDestroyAddr(loc, objAddr); + builder.createDeallocStack(loc, tempAddr); + break; + } + case AccessType::Set: { + // Write the new value directly into optAddr. + auto objAddr = builder.createInitEnumDataAddr(loc, optAddr, someDecl, objType); + + callback(objAddr); + + // Finish creating the enum. + builder.createInjectEnumAddr(loc, optAddr, someDecl); + break; + } + case AccessType::Modify: { + // We have to copy the old value out, perform the modification, + // and copy the new value back in. + auto objAddr = builder.createAllocStack(loc, objType); + + // Unwrap the optional and copy it to the new buffer. + auto unwrappedAddr = builder.createUncheckedTakeEnumDataAddr(loc, optAddr, someDecl, objType); + builder.createCopyAddr(loc, unwrappedAddr, objAddr, IsTake, IsInitialization); + + callback(objAddr); + + auto initAddr = builder.createInitEnumDataAddr(loc, optAddr, someDecl, objType); + builder.createCopyAddr(loc, objAddr, initAddr, IsTake, IsInitialization); + builder.createDeallocStack(loc, objAddr); + builder.createInjectEnumAddr(loc, optAddr, someDecl); + break; + } + } + }); + } +}; + +class OptionalChainProjector : public ComponentProjector { +public: + OptionalChainProjector(const KeyPathPatternComponent &component, + std::unique_ptr parent, + SILValue optionalChainResult, + SILLocation loc, SILBuilder &builder) + : ComponentProjector(component, std::move(parent), loc, builder), + optionalChainResult(optionalChainResult) {} + + void project(AccessType accessType, + std::function callback) override { + assert(component.getKind() == + KeyPathPatternComponent::Kind::OptionalChain); + assert(accessType == AccessType::Get && + "Optional chain components are immutable"); + + parent->project(accessType, [&](SILValue optAddr) { + auto &ctx = builder.getASTContext(); + + auto noneDecl = ctx.getOptionalNoneDecl(); + auto someDecl = ctx.getOptionalSomeDecl(); + + SILType optType = optAddr->getType(); + SILType objType = optType.getOptionalObjectType(); + + // Continue projecting only if the optional is non-nil + // i.e. if let objAddr = optAddr { + auto continuation = builder.splitBlockForFallthrough(); + auto ifSome = builder.getFunction().createBasicBlockAfter(builder.getInsertionBB()); + auto ifNone = builder.getFunction().createBasicBlockAfter(ifSome); + builder.createSwitchEnumAddr(loc, optAddr, /*defaultBB*/ nullptr, + {{noneDecl, ifNone}, {someDecl, ifSome}}); + + assert(ifSome->empty()); + builder.setInsertionPoint(ifSome); + + // We have to copy the optional, since unwrapping is destructive. + auto tempAddr = builder.createAllocStack(loc, optType); + builder.createCopyAddr(loc, optAddr, tempAddr, IsNotTake, IsInitialization); + + // Unwrap the optional. + auto objAddr = builder.createUncheckedTakeEnumDataAddr(loc, tempAddr, someDecl, objType); + + // at the end of the projection, callback will store a value in optionalChainResult + callback(objAddr); + + builder.createDestroyAddr(loc, objAddr); + builder.createDeallocStack(loc, tempAddr); + + builder.createBranch(loc, continuation); + // else, store nil in the result + builder.setInsertionPoint(ifNone); + builder.createInjectEnumAddr(loc, optionalChainResult, noneDecl); + + builder.createBranch(loc, continuation); + // end if, allow parents to clean up regardless of whether the chain continued + builder.setInsertionPoint(continuation, continuation->begin()); + }); + } + +private: + SILValue optionalChainResult; +}; + +/// A projector to handle a complete key path. +class CompleteKeyPathProjector : public KeyPathProjector { +public: + CompleteKeyPathProjector(KeyPathInst *keyPath, SILValue root, + SILLocation loc, SILBuilder &builder) + : KeyPathProjector(loc, builder), keyPath(keyPath), root(root) {} + + void project(AccessType accessType, + std::function callback) override { + auto components = keyPath->getPattern()->getComponents(); + + // Check if the keypath has an optional chain. + bool isOptionalChain = false; + for (const KeyPathPatternComponent &comp : components) { + if (comp.getKind() == KeyPathPatternComponent::Kind::OptionalChain) { + isOptionalChain = true; + break; + } + } + + // Root projector + auto rootProjector = std::make_unique(root, loc, builder); + + BeginAccessInst *beginAccess = nullptr; + + if (isOptionalChain) { + assert(accessType == AccessType::Get && "Optional chains are read-only"); + + // If we're reading an optional chain, create an optional result. + auto resultCanType = components.back().getComponentType(); + auto &function = builder.getFunction(); + auto substType = resultCanType.subst(keyPath->getSubstitutions(), None); + auto optType = function.getLoweredType(substType); + + assert(optType.getOptionalObjectType() && + "Optional-chained key path should result in an optional"); + SILValue optionalChainResult = builder.createAllocStack(loc, optType); + + // Get the (conditional) result projector. + auto projector = create(0, std::move(rootProjector), + beginAccess, optionalChainResult); + + projector->project(accessType, [&](SILValue result) { + // This will only run if all optional chains succeeded. + // Store the result in optionalChainResult. + builder.createCopyAddr(loc, result, optionalChainResult, + IsNotTake, IsInitialization); + }); + + // If the optional chain succeeded, optionalChainResult will have + // .some(result). Otherwise, projectOptionalChain will have written .none. + callback(optionalChainResult); + builder.createDestroyAddr(loc, optionalChainResult); + builder.createDeallocStack(loc, optionalChainResult); + } else { + // If we're not optional chaining, or we're writing to an optional chain, + // we don't need an optional result. + auto projector = create(0, std::move(rootProjector), + beginAccess, /*optionalChainResult*/ nullptr); + projector->project(accessType, callback); + } + assert(beginAccess == nullptr && + "key path projector returned with dangling access enforcement"); + } + + bool isStruct() override { + auto components = keyPath->getPattern()->getComponents(); + auto resultType = components.back().getComponentType(); + return resultType.getStructOrBoundGenericStruct() != nullptr; + } + +private: + KeyPathInst *keyPath; + SILValue root; + + /// Recursively creates a chain of key path projectors + /// for components from index.. + create(size_t index, std::unique_ptr parent, + BeginAccessInst *&beginAccess, SILValue optionalChainResult) { + auto components = keyPath->getPattern()->getComponents(); + + if (index >= components.size()) return parent; + + auto &comp = components[index]; + std::unique_ptr projector; + + // Create a projector for this component. + switch (comp.getKind()) { + case KeyPathPatternComponent::Kind::StoredProperty: + projector = std::make_unique + (comp, std::move(parent), beginAccess, loc, builder); + break; + case KeyPathPatternComponent::Kind::TupleElement: + projector = std::make_unique + (comp, std::move(parent), loc, builder); + break; + case KeyPathPatternComponent::Kind::GettableProperty: + projector = std::make_unique + (keyPath, comp, std::move(parent), keyPath->getSubstitutions(), + beginAccess, loc, builder); + break; + case KeyPathPatternComponent::Kind::SettableProperty: + projector = std::make_unique + (keyPath, comp, std::move(parent), keyPath->getSubstitutions(), + beginAccess, loc, builder); + break; + case KeyPathPatternComponent::Kind::OptionalWrap: + projector = std::make_unique + (keyPath, comp, std::move(parent), loc, builder); + break; + case KeyPathPatternComponent::Kind::OptionalForce: + projector = std::make_unique + (comp, std::move(parent), loc, builder); + break; + case KeyPathPatternComponent::Kind::OptionalChain: + projector = std::make_unique + (comp, std::move(parent), optionalChainResult, loc, builder); + break; + } + + // Project the rest of the chain on top of this component. + return create(index + 1, std::move(projector), + beginAccess, optionalChainResult); + } +}; + +std::unique_ptr +KeyPathProjector::create(SILValue keyPath, SILValue root, + SILLocation loc, SILBuilder &builder) { + if (auto *upCast = dyn_cast(keyPath)) + keyPath = upCast->getOperand(); + + // Is it a keypath instruction at all? + auto *kpInst = dyn_cast(keyPath); + if (!kpInst || !kpInst->hasPattern()) + return nullptr; + + // Check if the keypath only contains patterns which we support. + auto components = kpInst->getPattern()->getComponents(); + for (const KeyPathPatternComponent &comp : components) { + if (comp.getKind() == KeyPathPatternComponent::Kind::GettableProperty || + comp.getKind() == KeyPathPatternComponent::Kind::SettableProperty) { + if (!comp.getExternalSubstitutions().empty() || + !comp.getSubscriptIndices().empty()) { + // TODO: right now we can't optimize computed properties that require + // additional context for subscript indices or generic environment + // See https://github.com/apple/swift/pull/28799#issuecomment-570299845 + return nullptr; + } + } + } + + return std::make_unique(kpInst, root, + loc, builder); +} diff --git a/test/SILOptimizer/optimize_keypath.swift b/test/SILOptimizer/optimize_keypath.swift index 578270e19d937..37f6b25ffa8eb 100644 --- a/test/SILOptimizer/optimize_keypath.swift +++ b/test/SILOptimizer/optimize_keypath.swift @@ -9,15 +9,19 @@ // REQUIRES: CPU=arm64 || CPU=x86_64 protocol P { - func modifyIt() + mutating func modifyIt() + var computed: Int { get set } } struct GenStruct : P { var st: T + var computed: Int { get { st.computed } set { st.computed = newValue } } + + var computedGeneric: T { get { st} set { st = newValue} } init(_ st: T) { self.st = st } - func modifyIt() { + mutating func modifyIt() { st.modifyIt() } } @@ -26,9 +30,13 @@ var numGenClassObjs = 0 final class GenClass : P { var ct: T + var computed: Int { get { ct.computed } set { ct.computed = newValue } } + + var gs: GenStruct init(_ ct: T) { self.ct = ct + self.gs = .init(ct) numGenClassObjs += 1 } @@ -54,9 +62,20 @@ final class DerivedClass2 : DerivedClass { final class SimpleClass : P { var i: Int static var numObjs = 0 + + var tuple = (0, 1) + + struct Nested { + var i: Int = 0 + + @inline(never) + var computedGenClass: GenClass { GenClass(SimpleStruct(i: i)) } + } + var opt: Nested? - init(_ i: Int) { + init(_ i: Int, nested: Int? = nil) { self.i = i + self.opt = nested.map { Nested(i: $0) } Self.numObjs += 1 } @@ -67,6 +86,32 @@ final class SimpleClass : P { func modifyIt() { i += 10 } + + var computed: Int { get { i + 1 } set { i = newValue - 1} } +} + +struct SimpleStruct: P { + var tuple = (0, 1) + + struct Nested { + var i: Int + } + var opt: Nested? + + struct Nested2 { + var opt: Nested? + } + var opt2: Nested2? + + var i = 0 + + init(i: Int = 0) { self.i = i } + + mutating func modifyIt() { + i += 10 + } + + var computed: Int { get { i + 1 } set { i = newValue - 1} } } // Check if all keypath instructions have been optimized away @@ -85,7 +130,8 @@ func testGenStructRead(_ s: GenStruct) -> T { // CHECK-LABEL: sil {{.*}}testGenStructWrite // CHECK: [[A:%[0-9]+]] = struct_element_addr %0 -// CHECK: copy_addr %1 to [[A]] +// CHECK: destroy_addr [[A]] +// CHECK: copy_addr {{.*}} to [initialization] [[A]] // CHECK: return @inline(never) @_semantics("optimize.sil.specialize.generic.never") @@ -138,7 +184,8 @@ func testDerivedClass2Read(_ c: DerivedClass2) -> Int { // CHECK: [[S:%[0-9]+]] = alloc_stack $T // CHECK: [[E:%[0-9]+]] = ref_element_addr %0 // CHECK: [[A:%[0-9]+]] = begin_access [modify] [dynamic] [[E]] -// CHECK: copy_addr [take] [[S]] to [[A]] +// CHECK: destroy_addr [[A]] +// CHECK: copy_addr [take] [[S]] to [initialization] [[A]] // CHECK: end_access [[A]] // CHECK: return @inline(never) @@ -252,6 +299,305 @@ func testNestedModify(_ s: inout GenStruct>>) { modifyGeneric(&s[keyPath: kp]) } +// CHECK-LABEL: sil {{.*}}testTuple +// CHECK: [[E:%[0-9]+]] = struct_element_addr +// CHECK: [[T1:%[0-9]+]] = tuple_element_addr [[E]] +// CHECK: [[I:%[0-9]+]] = load [[T1]] +// CHECK: [[T2:%[0-9]+]] = tuple_element_addr [[E]] +// CHECK: store [[I]] to [[T2]] +// CHECK: return +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testTuple(_ s: inout SimpleStruct) { + let first = \SimpleStruct.tuple.0 + let second = \SimpleStruct.tuple.1 + s[keyPath: first] = s[keyPath: second] +} + +// CHECK-LABEL: sil {{.*}} [noinline] {{.*}}testGetter +// CHECK: [[A:%[0-9]+]] = alloc_stack $Int +// CHECK: [[F:%[0-9]+]] = function_ref {{.*}}computed +// CHECK: apply [[F]]([[A]], %0) +// destroy_addr gets optimized out +// CHECK: dealloc_stack [[A]] +// CHECK: return +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testGetter(_ s: GenStruct) -> Int { + let kp = \GenStruct.computed + return s[keyPath: kp] +} + +// CHECK-LABEL: sil {{.*}} [noinline] {{.*}}testClassMemberGetter +// CHECK: [[E:%[0-9]+]] = ref_element_addr +// CHECK: [[M:%[0-9]+]] = begin_access [read] [dynamic] [[E]] +// CHECK: [[A:%[0-9]+]] = alloc_stack $Int +// CHECK: [[F:%[0-9]+]] = function_ref {{.*}}computed +// CHECK: apply [[F]]([[A]], [[M]]) +// CHECK: end_access +// destroy_addr gets optimized out +// CHECK: dealloc_stack [[A]] +// CHECK: return +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testClassMemberGetter(_ c: GenClass) -> Int { + let kp = \GenClass.gs.computed + return c[keyPath: kp] +} + +// CHECK-LABEL: sil {{.*}}testComputedModify +// CHECK: [[A:%[0-9]+]] = alloc_stack $Int +// CHECK: [[G:%[0-9]+]] = function_ref {{.*}}computed +// CHECK: apply [[G]]([[A]], %0) +// CHECK: store {{%[0-9]+}} to [[A]] +// CHECK: [[S:%[0-9]+]] = function_ref {{.*}}computed +// CHECK: apply [[S]]([[A]], %0) +// destroy_addr gets optimized out +// CHECK: dealloc_stack [[A]] +// CHECK: return +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testComputedModify(_ s: inout GenStruct) { + let kp = \GenStruct.computed + s[keyPath: kp] += 10 +} + +// CHECK-LABEL: sil {{.*}}testComputedModify +// CHECK: [[E:%[0-9]+]] = ref_element_addr +// CHECK: [[M:%[0-9]+]] = begin_access [modify] [dynamic] [[E]] +// CHECK: [[A:%[0-9]+]] = alloc_stack $Int +// CHECK: [[G:%[0-9]+]] = function_ref {{.*}}computed +// CHECK: apply [[G]]([[A]], [[M]]) +// CHECK: store {{%[0-9]+}} to [[A]] +// CHECK: [[S:%[0-9]+]] = function_ref {{.*}}computed +// CHECK: apply [[S]]([[A]], [[M]]) +// destroy_addr gets optimized out +// CHECK: dealloc_stack [[A]] +// CHECK: end_access +// CHECK: return +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testClassMemberComputedModify(_ s: inout GenClass) { + let kp = \GenClass.gs.computed + s[keyPath: kp] += 10 +} + +// CHECK-LABEL: sil {{.*}}testModifyOptionalForce +// CHECK: [[F:%[0-9]+]] = select_enum [[O:%[0-9]+]] +// CHECK: cond_fail [[F]] +// CHECK: unchecked_enum_data [[O]] +// CHECK: [[E2:%[0-9]+]] = init_enum_data_addr [[E1:%[0-9]+]] +// CHECK: store {{%[0-9]+}} to [[E2]] +// CHECK: inject_enum_addr [[E1]] +// CHECK: return +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testModifyOptionalForce(_ s: inout SimpleStruct) { + let kp = \SimpleStruct.opt!.i + s[keyPath: kp] += 10 +} + +// CHECK-LABEL: sil {{.*}}testModifyOptionalForceClass +// CHECK: [[O:%[0-9]+]] = ref_element_addr +// CHECK: begin_access [modify] [dynamic] [no_nested_conflict] [[O]] +// CHECK: [[F:%[0-9]+]] = select_enum +// CHECK: cond_fail [[F]] +// CHECK: unchecked_enum_data [[E1:%[0-9]+]] +// CHECK: [[E2:%[0-9]+]] = init_enum_data_addr [[E1:%[0-9]+]] +// CHECK: store {{%[0-9]+}} to [[E2]] +// CHECK: inject_enum_addr [[E1]] +// CHECK: end_access +// CHECK: return +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testModifyOptionalForceClass(_ s: inout SimpleClass) { + let kp = \SimpleClass.opt!.i + s[keyPath: kp] += 10 +} + + + +// CHECK-LABEL: sil {{.*}}testOptionalChain +// By the time the test gets run, lots of stack +// allocations have been promoted to registers. +// +// Check if value is null +// CHECK: switch_enum [[O:%[0-9]+]] +// CHECK: {{bb.}}: +// Unwrap value +// CHECK: [[A1:%[0-9]+]] = alloc_stack +// CHECK: store [[O]] to [[A1]] +// CHECK: [[U:%[0-9]+]] = unchecked_take_enum_data_addr [[A1]] +// Access stored property & re-wrap result +// CHECK: [[I:%[0-9]+]] = struct_element_addr [[U]] +// CHECK: [[R1:%[0-9]+]] = enum +// CHECK: dealloc_stack [[A1]] +// CHECK: br [[CONTINUATION:bb.]]([[R1]] : $Optional) +// CHECK: {{bb.}}: +// Store nil in result +// CHECK: [[R2:%[0-9]+]] = enum +// CHECK: br [[CONTINUATION]]([[R2]] : $Optional) +// CHECK: [[CONTINUATION]]([[R:%[0-9]+]] : $Optional): +// CHECK: return [[R]] +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testOptionalChain(_ s: SimpleStruct) -> Int? { + let kp = \SimpleStruct.opt?.i + return s[keyPath: kp] +} + +// CHECK-LABEL: sil {{.*}}testOptionalChainClass +// By the time the test gets run, lots of stack +// allocations have been promoted to registers. +// +// CHECK: [[E1:%[0-9]+]] = ref_element_addr +// CHECK: [[E2:%[0-9]+]] = begin_access [read] [dynamic] [no_nested_conflict] [[E1]] +// Check if value is null +// CHECK: switch_enum [[O:%[0-9]+]] +// CHECK: {{bb.}}: +// Unwrap value +// CHECK: [[A1:%[0-9]+]] = alloc_stack +// CHECK: store [[O]] to [[A1]] +// CHECK: [[U:%[0-9]+]] = unchecked_take_enum_data_addr [[A1]] +// Access stored property & re-wrap result +// CHECK: [[I:%[0-9]+]] = struct_element_addr [[U]] +// CHECK: [[R1:%[0-9]+]] = enum +// CHECK: dealloc_stack [[A1]] +// CHECK: br [[CONTINUATION:bb.]]([[R1]] : $Optional) +// CHECK: {{bb.}}: +// Store nil in result +// CHECK: [[R2:%[0-9]+]] = enum +// CHECK: br [[CONTINUATION]]([[R2]] : $Optional) +// CHECK: [[CONTINUATION]]([[R:%[0-9]+]] : $Optional): +// CHECK: end_access [[E2]] +// CHECK: return [[R]] +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testOptionalChainClass(_ s: SimpleClass) -> Int? { + let kp = \SimpleClass.opt?.i + return s[keyPath: kp] +} + +// CHECK-LABEL: sil {{.*}}testNestedOptionalChain +// By the time the test gets run, lots of stack +// allocations have been promoted to registers. +// +// Check if value is null +// CHECK: switch_enum [[O:%[0-9]+]] +// CHECK: {{bb.}}: +// Unwrap value +// CHECK: [[A1:%[0-9]+]] = alloc_stack +// CHECK: store [[O]] to [[A1]] +// CHECK: [[U:%[0-9]+]] = unchecked_take_enum_data_addr [[A1]] +// +// Unwrap nested optional +// CHECK: switch_enum [[O2:%[0-9]+]] +// CHECK: {{bb.}}: +// CHECK: [[A2:%[0-9]+]] = alloc_stack +// CHECK: store [[O2]] to [[A2]] +// CHECK: [[U2:%[0-9]+]] = unchecked_take_enum_data_addr [[A2]] +// Access stored property & re-wrap result +// CHECK: [[I:%[0-9]+]] = struct_element_addr [[U2]] +// CHECK: [[R1:%[0-9]+]] = enum +// CHECK: dealloc_stack [[A2]] +// CHECK: br [[CONT2:bb.]]([[R1]] : $Optional +// CHECK: {{bb.}}: +// Store nil in result +// CHECK: [[R2:%[0-9]+]] = enum +// CHECK: br [[CONT2]]([[R2]] : $Optional +// CHECK: [[CONT2]]([[R3:%[0-9]+]] : $Optional): +// CHECK: dealloc_stack [[A1]] +// CHECK: br [[CONT1:bb.]]([[R3]] : $Optional) +// CHECK: {{bb.}}: +// Store nil in result +// CHECK: [[R2:%[0-9]+]] = enum +// CHECK: br [[CONT1]]([[R2]] : $Optional) +// CHECK: [[CONT1]]([[R:%[0-9]+]] : $Optional): +// CHECK: return [[R]] +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testNestedOptionalChain(_ s: SimpleStruct) -> Int? { + let kp = \SimpleStruct.opt2?.opt?.i + return s[keyPath: kp] +} + +// CHECK-LABEL: sil {{.*}}testGetOptionalForce +// CHECK: [[F:%[0-9]+]] = select_enum [[O:%[0-9]+]] +// CHECK: cond_fail [[F]] +// CHECK: [[A:%[0-9]+]] = alloc_stack +// CHECK: store [[O]] to [[A]] +// CHECK: [[E2:%[0-9]+]] = unchecked_take_enum_data_addr [[A]] +// CHECK: [[E3:%[0-9]+]] = struct_element_addr [[E2]] +// CHECK: [[I:%[0-9]+]] = load [[E3]] +// CHECK: dealloc_stack [[A]] +// CHECK: return [[I]] +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testGetOptionalForce(_ s: SimpleStruct) -> Int { + let kp = \SimpleStruct.opt!.i + return s[keyPath: kp] +} + +// CHECK-LABEL: sil {{.*}}testGetOptionalForceClass +// CHECK: [[R1:%[0-9]+]] = ref_element_addr +// CHECK: [[R2:%[0-9]+]] = begin_access [read] [dynamic] [no_nested_conflict] [[R1]] +// CHECK: [[F:%[0-9]+]] = select_enum [[O:%[0-9]+]] +// CHECK: cond_fail [[F]] +// CHECK: [[A:%[0-9]+]] = alloc_stack +// CHECK: store [[O]] to [[A]] +// CHECK: [[E2:%[0-9]+]] = unchecked_take_enum_data_addr [[A]] +// CHECK: [[E3:%[0-9]+]] = struct_element_addr [[E2]] +// CHECK: [[I:%[0-9]+]] = load [[E3]] +// CHECK: dealloc_stack [[A]] +// CHECK: end_access [[R2]] +// CHECK: return [[I]] +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testGetOptionalForceClass(_ s: SimpleClass) -> Int { + let kp = \SimpleClass.opt!.i + return s[keyPath: kp] +} + +// CHECK-LABEL: sil {{.*}}testGetComplex +// opt +// CHECK: [[E1:%[0-9]+]] = ref_element_addr +// CHECK: [[B1:%[0-9]+]] = begin_access [read] [dynamic] [[E1]] +// ! +// CHECK: [[F:%[0-9]+]] = select_enum [[O:%[0-9]+]] +// CHECK: cond_fail [[F]] +// computedGenClass +// CHECK: [[F:%[0-9]+]] = function_ref +// CHECK: apply [[F]] +// CHECK: end_access [[B1]] +// ct +// CHECK: [[E3:%[0-9]+]] = ref_element_addr +// CHECK: [[B2:%[0-9]+]] = begin_access [read] [dynamic] [no_nested_conflict] [[E3]] +// tuple +// CHECK: [[E4:%[0-9]+]] = struct_element_addr +// 0 +// CHECK: [[E5:%[0-9]+]] = tuple_element_addr [[E4]] +// CHECK: load [[E5]] +// CHECK: end_access [[B2]] +@inline(never) +@_semantics("optimize.sil.specialize.generic.never") +func testGetComplex(_ s: SimpleClass) -> Int { + let kp = \SimpleClass.opt!.computedGenClass.ct.tuple.1 + return s[keyPath: kp] +} + +// allow exactly one unoptimzedkey path instruction, in this function +// CHECK-ALL: sil {{.*}}makeKeyPathInGenericContext +// CHECK-ALL: = keypath +func makeKeyPathInGenericContext(of: T.Type) -> WritableKeyPath, T> { + \.computedGeneric +} + +// CHECK-ALL-NOT: = keypath + +func testGenericResult(_ s: inout GenStruct) { + let kp = makeKeyPathInGenericContext(of: SimpleStruct.self) + s[keyPath: kp].i += 1 +} // CHECK-LABEL: sil {{.*}}testit func testit() { @@ -300,6 +646,63 @@ func testit() { var s2 = GenStruct(GenClass(GenClass(SimpleClass(34)))) testNestedModify(&s2) print("NestedModify: \(s2.st.ct.ct.i)") + + // CHECK-OUTPUT: Getter: 51 + var s3 = GenStruct(SimpleClass(50)) + print("Getter: \(testGetter(s3))") + // CHECK-OUTPUT: ClassMemberGetter: 52 + var c3 = GenClass(SimpleClass(51)) + print("ClassMemberGetter: \(testClassMemberGetter(c3))") + + // CHECK-OUTPUT: ComputedModify: 61 + testComputedModify(&s3) + print("ComputedModify: \(s3.computed)") + // CHECK-OUTPUT: ClassComputedModify: 62 + testClassMemberComputedModify(&c3) + print("ClassComputedModify: \(c3.computed)") + + var s4 = SimpleStruct() + // CHECK-OUTPUT: Tuple: 1 + testTuple(&s4) + print("Tuple: \(s4.tuple.0)") + + var c4 = SimpleClass(0) + + // CHECK-OUTPUT: OptionalChain1: nil + print("OptionalChain1: \(String(describing: testOptionalChain(s4)))") + // CHECK-OUTPUT: ClassOptionalChain1: nil + print("ClassOptionalChain1: \(String(describing: testOptionalChainClass(c4)))") + + // CHECK-OUTPUT: OptionalChain2: Optional(70) + s4.opt = .init(i: 70) + print("OptionalChain2: \(String(describing: testOptionalChain(s4)))") + // CHECK-OUTPUT: ClassOptionalChain2: Optional(71) + c4.opt = .init(i: 71) + print("ClassOptionalChain2: \(String(describing: testOptionalChainClass(c4)))") + + // CHECK-OUTPUT: OptionalForce: 80 + testModifyOptionalForce(&s4) + print("OptionalForce: \(testGetOptionalForce(s4))") + // CHECK-OUTPUT: ClassOptionalForce: 81 + testModifyOptionalForceClass(&c4) + print("ClassOptionalForce: \(testGetOptionalForceClass(c4))") + + // CHECK-OUTPUT: NestedOptionalChain1: nil + print("NestedOptionalChain1: \(String(describing: testNestedOptionalChain(s4)))") + // CHECK-OUTPUT: NestedOptionalChain2: nil + s4.opt2 = .init() + print("NestedOptionalChain2: \(String(describing: testNestedOptionalChain(s4)))") + // CHECK-OUTPUT: NestedOptionalChain3: Optional(90) + s4.opt2!.opt = .init(i: 90) + print("NestedOptionalChain3: \(String(describing: testNestedOptionalChain(s4)))") + + // CHECK-OUTPUT: testGetComplex: 1 + print("testGetComplex: \(testGetComplex(c4))") + + // CHECK-OUTPUT: testGenericResult: 2 + var s5 = GenStruct(SimpleStruct(i: 1)) + testGenericResult(&s5) + print("testGenericResult: \(s5.st.i)") } testit()