blob: e6a40b3f55d9ad9d2e05ac88a64b494f2c21878a [file] [log] [blame]
//===-- 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<void (SILValue)> 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<KeyPathProjector> 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<KeyPathProjector> 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<KeyPathProjector> parent,
BeginAccessInst *&beginAccess,
SILLocation loc, SILBuilder &builder)
: ComponentProjector(component, std::move(parent), loc, builder),
beginAccess(beginAccess) {}
void project(AccessType accessType,
std::function<void(SILValue addr)> 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<KeyPathProjector> parent,
SILLocation loc, SILBuilder &builder)
: ComponentProjector(component, std::move(parent), loc, builder) {}
void project(AccessType accessType,
std::function<void(SILValue addr)> 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<KeyPathProjector> 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<void(SILValue addr)> 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(
Lowering::AbstractionPattern::getOpaque(), 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<KeyPathProjector> 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<void(SILValue addr)> 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(
Lowering::AbstractionPattern::getOpaque(), 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<KeyPathProjector> parent,
SILLocation loc, SILBuilder &builder)
: ComponentProjector(component, std::move(parent), loc, builder),
keyPath(kpInst) {}
void project(AccessType accessType,
std::function<void(SILValue addr)> 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(
Lowering::AbstractionPattern::getOpaque(), 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<KeyPathProjector> parent,
SILLocation loc, SILBuilder &builder)
: ComponentProjector(component, std::move(parent), loc, builder) {}
void project(AccessType accessType,
std::function<void(SILValue addr)> 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<KeyPathProjector> parent,
SILValue optionalChainResult,
SILLocation loc, SILBuilder &builder)
: ComponentProjector(component, std::move(parent), loc, builder),
optionalChainResult(optionalChainResult) {}
void project(AccessType accessType,
std::function<void(SILValue addr)> 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<void (SILValue)> 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<RootProjector>(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(
Lowering::AbstractionPattern::getOpaque(), 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..<components.end()
std::unique_ptr<KeyPathProjector>
create(size_t index, std::unique_ptr<KeyPathProjector> parent,
BeginAccessInst *&beginAccess, SILValue optionalChainResult) {
auto components = keyPath->getPattern()->getComponents();
if (index >= components.size()) return parent;
auto &comp = components[index];
std::unique_ptr<KeyPathProjector> projector;
// Create a projector for this component.
switch (comp.getKind()) {
case KeyPathPatternComponent::Kind::StoredProperty:
projector = std::make_unique<StoredPropertyProjector>
(comp, std::move(parent), beginAccess, loc, builder);
break;
case KeyPathPatternComponent::Kind::TupleElement:
projector = std::make_unique<TupleElementProjector>
(comp, std::move(parent), loc, builder);
break;
case KeyPathPatternComponent::Kind::GettableProperty:
projector = std::make_unique<GettablePropertyProjector>
(keyPath, comp, std::move(parent), keyPath->getSubstitutions(),
beginAccess, loc, builder);
break;
case KeyPathPatternComponent::Kind::SettableProperty:
projector = std::make_unique<SettablePropertyProjector>
(keyPath, comp, std::move(parent), keyPath->getSubstitutions(),
beginAccess, loc, builder);
break;
case KeyPathPatternComponent::Kind::OptionalWrap:
projector = std::make_unique<OptionalWrapProjector>
(keyPath, comp, std::move(parent), loc, builder);
break;
case KeyPathPatternComponent::Kind::OptionalForce:
projector = std::make_unique<OptionalForceProjector>
(comp, std::move(parent), loc, builder);
break;
case KeyPathPatternComponent::Kind::OptionalChain:
projector = std::make_unique<OptionalChainProjector>
(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);
}
};
KeyPathInst *
KeyPathProjector::getLiteralKeyPath(SILValue keyPath) {
if (auto *upCast = dyn_cast<UpcastInst>(keyPath))
keyPath = upCast->getOperand();
// TODO: Look through other conversions, copies, etc.?
return dyn_cast<KeyPathInst>(keyPath);
}
std::unique_ptr<KeyPathProjector>
KeyPathProjector::create(SILValue keyPath, SILValue root,
SILLocation loc, SILBuilder &builder) {
// Is it a keypath instruction at all?
auto *kpInst = getLiteralKeyPath(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<CompleteKeyPathProjector>(kpInst, root,
loc, builder);
}