Skip to content

[SILOptimizer] Generalize optimization of static keypaths #28799

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions include/swift/SILOptimizer/Utils/KeyPathProjector.h
Original file line number Diff line number Diff line change
@@ -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 <memory>

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<KeyPathProjector>
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<void(SILValue addr)> 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 */
1 change: 1 addition & 0 deletions lib/SILGen/SILGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,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);
Expand Down
1 change: 1 addition & 0 deletions lib/SILGen/SILGen.h
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor<SILGenModule> {
AbstractStorageDecl *storage,
ArrayRef<ProtocolConformanceRef> indexHashables,
CanType baseTy,
DeclContext *useDC,
bool forPropertyDescriptor);

/// Known functions for bridging.
Expand Down
6 changes: 4 additions & 2 deletions lib/SILGen/SILGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3354,6 +3354,7 @@ SILGenModule::emitKeyPathComponentForDecl(SILLocation loc,
AbstractStorageDecl *storage,
ArrayRef<ProtocolConformanceRef> indexHashables,
CanType baseTy,
DeclContext *useDC,
bool forPropertyDescriptor) {
auto baseDecl = storage;

Expand Down Expand Up @@ -3423,8 +3424,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());
};

Expand Down Expand Up @@ -3578,6 +3579,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)
Expand Down
153 changes: 39 additions & 114 deletions lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <utility>

using namespace swift;
using namespace swift::PatternMatch;
Expand Down Expand Up @@ -199,92 +201,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<UpcastInst>(keyPath))
keyPath = upCast->getOperand();

// Is it a keypath instruction at all?
auto *kpInst = dyn_cast<KeyPathInst>(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):
Expand All @@ -303,36 +219,40 @@ 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);
valueAddr = AI->getArgument(0);
} 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;
Expand Down Expand Up @@ -377,19 +297,24 @@ bool SILCombiner::tryOptimizeInoutKeypath(BeginApplyInst *AI) {
EndApplyInst *endApply = dyn_cast<EndApplyInst>(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;
Expand Down
1 change: 1 addition & 0 deletions lib/SILOptimizer/Utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ silopt_register_sources(
GenericCloner.cpp
Generics.cpp
InstOptUtils.cpp
KeyPathProjector.cpp
LoadStoreOptUtils.cpp
LoopUtils.cpp
OptimizerStatsUtils.cpp
Expand Down
Loading