mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
Generalize storage implementations to support generalized accessors.
The storage kind has been replaced with three separate "impl kinds", one for each of the basic access kinds (read, write, and read/write). This makes it far easier to mix-and-match implementations of different accessors, as well as subtleties like implementing both a setter and an independent read/write operation. AccessStrategy has become a bit more explicit about how exactly the access should be implemented. For example, the accessor-based kinds now carry the exact accessor intended to be used. Also, I've shifted responsibilities slightly between AccessStrategy and AccessSemantics so that AccessSemantics::Ordinary can be used except in the sorts of semantic-bypasses that accessor synthesis wants. This requires knowing the correct DC of the access when computing the access strategy; the upshot is that SILGenFunction now needs a DC. Accessor synthesis has been reworked so that only the declarations are built immediately; body synthesis can be safely delayed out of the main decl-checking path. This caused a large number of ramifications, especially for lazy properties, and greatly inflated the size of this patch. That is... really regrettable. The impetus for changing this was necessity: I needed to rework accessor synthesis to end its reliance on distinctions like Stored vs. StoredWithTrivialAccessors, and those fixes were exposing serious re-entrancy problems, and fixing that... well. Breaking the fixes apart at this point would be a serious endeavor.
This commit is contained in:
362
include/swift/AST/StorageImpl.h
Normal file
362
include/swift/AST/StorageImpl.h
Normal file
@@ -0,0 +1,362 @@
|
||||
//===--- StorageImpl.h - Storage declaration access impl --------*- C++ -*-===//
|
||||
//
|
||||
// This source file is part of the Swift.org open source project
|
||||
//
|
||||
// Copyright (c) 2014 - 2017 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This file defines types for describing the implementation of an
|
||||
// AbstractStorageDecl.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef SWIFT_STORAGEIMPL_H
|
||||
#define SWIFT_STORAGEIMPL_H
|
||||
|
||||
#include "swift/Basic/Range.h"
|
||||
|
||||
namespace swift {
|
||||
|
||||
// Note that the values of these enums line up with %select values in
|
||||
// diagnostics.
|
||||
enum class AccessorKind {
|
||||
#define ACCESSOR(ID) ID,
|
||||
#define LAST_ACCESSOR(ID) Last = ID
|
||||
#include "swift/AST/AccessorKinds.def"
|
||||
};
|
||||
|
||||
const unsigned NumAccessorKinds = unsigned(AccessorKind::Last) + 1;
|
||||
|
||||
static inline IntRange<AccessorKind> allAccessorKinds() {
|
||||
return IntRange<AccessorKind>(AccessorKind(0),
|
||||
AccessorKind(NumAccessorKinds));
|
||||
}
|
||||
|
||||
/// The safety semantics of this addressor.
|
||||
enum class AddressorKind : uint8_t {
|
||||
/// \brief This is not an addressor.
|
||||
NotAddressor,
|
||||
/// \brief This is an unsafe addressor; it simply returns an address.
|
||||
Unsafe,
|
||||
/// \brief This is an owning addressor; it returns an AnyObject
|
||||
/// which should be released when the caller is done with the object.
|
||||
Owning,
|
||||
/// \brief This is an owning addressor; it returns a Builtin.NativeObject
|
||||
/// which should be released when the caller is done with the object.
|
||||
NativeOwning,
|
||||
/// \brief This is a pinning addressor; it returns a Builtin.NativeObject?
|
||||
/// which should be unpinned when the caller is done with the object.
|
||||
NativePinning,
|
||||
};
|
||||
|
||||
/// Whether an access to storage is for reading, writing, or both.
|
||||
enum class AccessKind : uint8_t {
|
||||
/// The access is just to read the current value.
|
||||
Read,
|
||||
|
||||
/// The access is just to overwrite the current value.
|
||||
Write,
|
||||
|
||||
/// The access may require either reading or writing the current value.
|
||||
ReadWrite
|
||||
};
|
||||
|
||||
/// Produce the aggregate access kind of the combination of two accesses.
|
||||
inline AccessKind combineAccessKinds(AccessKind a, AccessKind b) {
|
||||
// If they're the same, use that; otherwise, all combinations combine
|
||||
// ReadWrite.
|
||||
return (a == b ? a : AccessKind::ReadWrite);
|
||||
}
|
||||
|
||||
class AccessStrategy {
|
||||
public:
|
||||
enum Kind : uint8_t {
|
||||
/// The declaration is a VarDecl with its own physical storage; access
|
||||
/// that storage directly.
|
||||
Storage,
|
||||
|
||||
/// The decl is a VarDecl with storage defined by a property behavior;
|
||||
/// this access may initialize or reassign the storage based on dataflow.
|
||||
BehaviorStorage,
|
||||
|
||||
/// Directly call an accessor of some sort. The strategy includes
|
||||
/// an accessor kind.
|
||||
DirectToAccessor,
|
||||
|
||||
/// Dispatch to an accessor of some sort. The strategy includes an
|
||||
/// accessor kind.
|
||||
DispatchToAccessor,
|
||||
|
||||
/// The access is a ReadWrite access and should be implemented by
|
||||
/// separately performing a Read into a temporary variable followed by
|
||||
/// a Write access back into the storage.
|
||||
MaterializeToTemporary,
|
||||
};
|
||||
|
||||
private:
|
||||
Kind TheKind;
|
||||
Kind FirstKind;
|
||||
AccessorKind FirstAccessor;
|
||||
Kind SecondKind;
|
||||
AccessorKind SecondAccessor;
|
||||
|
||||
AccessStrategy(Kind kind)
|
||||
: TheKind(kind) {
|
||||
assert(kind == Storage || kind == BehaviorStorage);
|
||||
}
|
||||
|
||||
AccessStrategy(Kind kind, AccessorKind accessor)
|
||||
: TheKind(kind), FirstAccessor(accessor) {
|
||||
// Generally used for one of the accessor strategies, but also used
|
||||
// for constructing a first or second strategy.
|
||||
}
|
||||
|
||||
AccessStrategy(AccessStrategy readStrategy, AccessStrategy writeStrategy)
|
||||
: TheKind(MaterializeToTemporary),
|
||||
FirstKind(readStrategy.TheKind),
|
||||
FirstAccessor(readStrategy.FirstAccessor),
|
||||
SecondKind(writeStrategy.TheKind),
|
||||
SecondAccessor(writeStrategy.FirstAccessor) {
|
||||
assert(readStrategy.TheKind != MaterializeToTemporary);
|
||||
assert(writeStrategy.TheKind != MaterializeToTemporary);
|
||||
}
|
||||
|
||||
public:
|
||||
static AccessStrategy getStorage() {
|
||||
return { Storage };
|
||||
}
|
||||
|
||||
static AccessStrategy getBehaviorStorage() {
|
||||
return { BehaviorStorage };
|
||||
}
|
||||
|
||||
static AccessStrategy getAccessor(AccessorKind accessor, bool dispatched) {
|
||||
return { dispatched ? DispatchToAccessor : DirectToAccessor, accessor };
|
||||
}
|
||||
|
||||
static AccessStrategy getMaterializeToTemporary(AccessStrategy read,
|
||||
AccessStrategy write) {
|
||||
return { read, write };
|
||||
}
|
||||
|
||||
Kind getKind() const { return TheKind; }
|
||||
|
||||
AccessorKind getAccessor() const {
|
||||
assert(TheKind == DirectToAccessor || TheKind == DispatchToAccessor);
|
||||
return FirstAccessor;
|
||||
}
|
||||
|
||||
AccessStrategy getReadStrategy() const {
|
||||
assert(TheKind == MaterializeToTemporary);
|
||||
return { FirstKind, FirstAccessor };
|
||||
}
|
||||
AccessStrategy getWriteStrategy() const {
|
||||
assert(TheKind == MaterializeToTemporary);
|
||||
return { SecondKind, SecondAccessor };
|
||||
}
|
||||
};
|
||||
|
||||
/// How are read accesses implemented?
|
||||
enum class ReadImplKind {
|
||||
/// There's storage.
|
||||
Stored,
|
||||
|
||||
/// The superclass's read implementation is directly inherited.
|
||||
Inherited,
|
||||
|
||||
/// There's a getter.
|
||||
Get,
|
||||
|
||||
/// There's an immutable addressor.
|
||||
Address,
|
||||
};
|
||||
enum { NumReadImplKindBits = 2 };
|
||||
|
||||
StringRef getReadImplKindName(ReadImplKind kind);
|
||||
|
||||
/// How are simple write accesses implemented?
|
||||
enum class WriteImplKind {
|
||||
/// It's immutable.
|
||||
Immutable,
|
||||
|
||||
/// There's storage.
|
||||
Stored,
|
||||
|
||||
/// There are observers on top of the storage.
|
||||
/// TODO: maybe add a StoredWithDidSet here and to ReadWriteImplKind?
|
||||
StoredWithObservers,
|
||||
|
||||
/// There are observers on top of the superclass's write implementation.
|
||||
InheritedWithObservers,
|
||||
|
||||
/// There's a setter.
|
||||
Set,
|
||||
|
||||
/// There's a mutable addressor.
|
||||
MutableAddress,
|
||||
};
|
||||
enum { NumWriteImplKindBits = 3 };
|
||||
|
||||
StringRef getWriteImplKindName(WriteImplKind kind);
|
||||
|
||||
/// How are read-write accesses implemented?
|
||||
enum class ReadWriteImplKind {
|
||||
/// It's immutable.
|
||||
Immutable,
|
||||
|
||||
/// There's storage.
|
||||
Stored,
|
||||
|
||||
/// There's a materializeForSet. (This is currently only used for opaque
|
||||
/// declarations.)
|
||||
MaterializeForSet,
|
||||
|
||||
/// There's a mutable addressor.
|
||||
MutableAddress,
|
||||
|
||||
/// Do a read into a temporary and then a write back.
|
||||
MaterializeToTemporary,
|
||||
};
|
||||
enum { NumReadWriteImplKindBits = 3 };
|
||||
|
||||
StringRef getReadWriteImplKindName(ReadWriteImplKind kind);
|
||||
|
||||
class StorageImplInfo {
|
||||
using IntType = uint16_t;
|
||||
static_assert(NumReadImplKindBits + NumWriteImplKindBits
|
||||
+ NumReadWriteImplKindBits <= 16,
|
||||
"bit count exceeds IntType range");
|
||||
IntType Read : NumReadImplKindBits;
|
||||
IntType Write : NumWriteImplKindBits;
|
||||
IntType ReadWrite : NumReadWriteImplKindBits;
|
||||
|
||||
public:
|
||||
/// A convenience constructor for building immutable storage.
|
||||
StorageImplInfo(ReadImplKind readImpl)
|
||||
: StorageImplInfo(readImpl, WriteImplKind::Immutable,
|
||||
ReadWriteImplKind::Immutable) {}
|
||||
|
||||
/// The primary constructor.
|
||||
StorageImplInfo(ReadImplKind readImpl,
|
||||
WriteImplKind writeImpl,
|
||||
ReadWriteImplKind readWriteImpl)
|
||||
: Read(IntType(readImpl)),
|
||||
Write(IntType(writeImpl)),
|
||||
ReadWrite(IntType(readWriteImpl)) {
|
||||
#ifndef NDEBUG
|
||||
assert((writeImpl == WriteImplKind::Immutable)
|
||||
== (readWriteImpl == ReadWriteImplKind::Immutable) &&
|
||||
"write and read-write disagree about immutability");
|
||||
|
||||
switch (writeImpl) {
|
||||
case WriteImplKind::Immutable:
|
||||
// No other consistency checks are required if the storage is immutable.
|
||||
return;
|
||||
|
||||
case WriteImplKind::Stored:
|
||||
assert(readImpl == ReadImplKind::Stored);
|
||||
assert(readWriteImpl == ReadWriteImplKind::Stored);
|
||||
return;
|
||||
|
||||
case WriteImplKind::StoredWithObservers:
|
||||
assert(readImpl == ReadImplKind::Stored);
|
||||
assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary);
|
||||
return;
|
||||
|
||||
case WriteImplKind::InheritedWithObservers:
|
||||
assert(readImpl == ReadImplKind::Inherited);
|
||||
assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary);
|
||||
return;
|
||||
|
||||
case WriteImplKind::Set:
|
||||
assert(readImpl == ReadImplKind::Get);
|
||||
assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary ||
|
||||
readWriteImpl == ReadWriteImplKind::MaterializeForSet);
|
||||
return;
|
||||
|
||||
case WriteImplKind::MutableAddress:
|
||||
assert(readImpl == ReadImplKind::Get ||
|
||||
readImpl == ReadImplKind::Address);
|
||||
assert(readWriteImpl == ReadWriteImplKind::MutableAddress);
|
||||
return;
|
||||
}
|
||||
llvm_unreachable("bad write impl kind");
|
||||
#endif
|
||||
}
|
||||
|
||||
static StorageImplInfo getSimpleStored(bool supportsMutation) {
|
||||
return { ReadImplKind::Stored,
|
||||
supportsMutation ? WriteImplKind::Stored
|
||||
: WriteImplKind::Immutable,
|
||||
supportsMutation ? ReadWriteImplKind::Stored
|
||||
: ReadWriteImplKind::Immutable };
|
||||
}
|
||||
|
||||
static StorageImplInfo getOpaque(bool supportsMutation) {
|
||||
return (supportsMutation ? getMutableOpaque() : getImmutableOpaque());
|
||||
}
|
||||
|
||||
/// Describe the implementation of a immutable property implemented opaquely.
|
||||
static StorageImplInfo getImmutableOpaque() {
|
||||
return { ReadImplKind::Get };
|
||||
}
|
||||
|
||||
/// Describe the implementation of a mutable property implemented opaquely.
|
||||
static StorageImplInfo getMutableOpaque() {
|
||||
return { ReadImplKind::Get, WriteImplKind::Set,
|
||||
ReadWriteImplKind::MaterializeForSet };
|
||||
}
|
||||
|
||||
static StorageImplInfo getComputed(bool supportsMutation) {
|
||||
return (supportsMutation ? getMutableComputed() : getImmutableComputed());
|
||||
}
|
||||
|
||||
/// Describe the implementation of an immutable property implemented
|
||||
/// with just a getter.
|
||||
static StorageImplInfo getImmutableComputed() {
|
||||
return { ReadImplKind::Get };
|
||||
}
|
||||
|
||||
/// Describe the implementation of a mutable property implemented with
|
||||
/// getter and setter.
|
||||
static StorageImplInfo getMutableComputed() {
|
||||
return { ReadImplKind::Get, WriteImplKind::Set,
|
||||
ReadWriteImplKind::MaterializeToTemporary };
|
||||
}
|
||||
|
||||
/// Does this implementation description require storage?
|
||||
bool hasStorage() const {
|
||||
return getReadImpl() == ReadImplKind::Stored;
|
||||
}
|
||||
|
||||
/// Does this describe a simply-stored variable?
|
||||
bool isSimpleStored() const {
|
||||
return getReadImpl() == ReadImplKind::Stored &&
|
||||
(getWriteImpl() == WriteImplKind::Stored ||
|
||||
getWriteImpl() == WriteImplKind::Immutable);
|
||||
}
|
||||
|
||||
/// Does this describe storage that supports mutation?
|
||||
bool supportsMutation() const {
|
||||
return getWriteImpl() != WriteImplKind::Immutable;
|
||||
}
|
||||
|
||||
ReadImplKind getReadImpl() const {
|
||||
return ReadImplKind(Read);
|
||||
}
|
||||
WriteImplKind getWriteImpl() const {
|
||||
return WriteImplKind(Write);
|
||||
}
|
||||
ReadWriteImplKind getReadWriteImpl() const {
|
||||
return ReadWriteImplKind(ReadWrite);
|
||||
}
|
||||
};
|
||||
|
||||
} // end namespace swift
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user