Files
swift-mirror/include/swift/AST/StorageImpl.h
Michael Gottesman 5acb6c939a [move-only] Perform an exclusive borrow when passing a var to a consuming var.
Consider the following example:

```
class Klass {}

@_moveOnly struct Butt {
  var k = Klass()
}

func mixedUse(_: inout Butt, _: __owned Butt) {}

func foo() {
    var y = Butt()
    mixedUse(&y, y)
}
```

In this case, we want to have an exclusivity violation. Before this patch, we
did a by-value load [copy] of y and then performed the inout access. Since the
access scopes did not overlap, we would not get an exclusivity violation.
Additionally, since the checker assumes that exclusivity violations will be
caught in such a situation, we convert the load [copy] to a load [take] causing
a later memory lifetime violation as seen in the following SIL:

```
sil hidden [ossa] @$s4test3fooyyF : $@convention(thin) () -> () {
bb0:
  %0 = alloc_stack [lexical] $Butt, var, name "y" // users: %4, %5, %8, %12, %13
  %1 = metatype $@thin Butt.Type                  // user: %3
  // function_ref Butt.init()
  %2 = function_ref @$s4test4ButtVACycfC : $@convention(method) (@thin Butt.Type) -> @owned Butt // user: %3
  %3 = apply %2(%1) : $@convention(method) (@thin Butt.Type) -> @owned Butt // user: %4
  store %3 to [init] %0 : $*Butt                  // id: %4
  %5 = begin_access [modify] [static] %0 : $*Butt // users: %7, %6
  %6 = load [take] %5 : $*Butt                    // user: %10                // <————————— This was a load [copy].
  end_access %5 : $*Butt                          // id: %7
  %8 = begin_access [modify] [static] %0 : $*Butt // users: %11, %10
  // function_ref mixedUse2(_:_:)
  %9 = function_ref @$s4test9mixedUse2yyAA4ButtVz_ADntF : $@convention(thin) (@inout Butt, @owned Butt) -> () // user: %10
  %10 = apply %9(%8, %6) : $@convention(thin) (@inout Butt, @owned Butt) -> ()
  end_access %8 : $*Butt                          // id: %11
  destroy_addr %0 : $*Butt                        // id: %12
  dealloc_stack %0 : $*Butt                       // id: %13
  %14 = tuple ()                                  // user: %15
  return %14 : $()                                // id: %15
} // end sil function '$s4test3fooyyF'
```

Now, instead we create a [consume] access and get the nice exclusivity error we
are looking for.

NOTE: As part of this I needed to tweak the verifier so that [deinit] accesses
are now allowed to have any form of access enforcement before we are in
LoweredSIL. I left in the original verifier error in LoweredSIL and additionally
left in the original error in IRGen. The reason why I am doing this is that I
need the deinit access to represent semantically what consuming from a
ref_element_addr, global, or escaping mutable var look like at the SIL level so
that the move checker can error upon it. Since we will error upon such
consumptions in Canonical SIL, such code patterns will never actually hit
Lowered/IRGen SIL, so it is safe to do so (and the verifier/errors will help us
if we make any mistakes). In the case of a non-escaping var though, we will be
able to use deinit statically and the move checker will make sure that it is not
reused before it is reinitialized.

rdar://101767439
2023-02-10 19:43:58 -08:00

427 lines
12 KiB
C++

//===--- 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 {
enum StorageIsMutable_t : bool {
StorageIsNotMutable = false,
StorageIsMutable = true
};
enum class OpaqueReadOwnership : uint8_t {
/// An opaque read produces an owned value.
Owned,
/// An opaque read produces a borrowed value.
Borrowed,
/// An opaque read can be either owned or borrowed, depending on the
/// preference of the caller.
OwnedOrBorrowed
};
// 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"
#undef ACCESSOR
#undef LAST_ACCESSOR
};
const unsigned NumAccessorKinds = unsigned(AccessorKind::Last) + 1;
static inline IntRange<AccessorKind> allAccessorKinds() {
return IntRange<AccessorKind>(AccessorKind(0),
AccessorKind(NumAccessorKinds));
}
/// \returns a user-readable string name for the accessor kind
static inline StringRef accessorKindName(AccessorKind ak) {
switch(ak) {
#define ACCESSOR(ID) ID
#define SINGLETON_ACCESSOR(ID, KEYWORD) \
case AccessorKind::ID: \
return #KEYWORD;
#include "swift/AST/AccessorKinds.def"
#undef ACCESSOR_KEYWORD
#undef SINGLETON_ACCESSOR
}
}
/// 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,
/// 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,
/// The access is to a computed distributed property, and thus the
/// get-accessor is a distributed thunk which may perform a remote call.
DispatchToDistributedThunk,
};
private:
Kind TheKind;
Kind FirstKind;
AccessorKind FirstAccessor;
Kind SecondKind;
AccessorKind SecondAccessor;
AccessStrategy(Kind kind)
: TheKind(kind) {
assert(kind == Storage);
}
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 getAccessor(AccessorKind accessor, bool dispatched) {
return { dispatched ? DispatchToAccessor : DirectToAccessor, accessor };
}
static AccessStrategy getDistributedThunkDispatchStrategy() {
return {DispatchToDistributedThunk, AccessorKind::Get};
}
static AccessStrategy getMaterializeToTemporary(AccessStrategy read,
AccessStrategy write) {
return { read, write };
}
Kind getKind() const { return TheKind; }
bool hasAccessor() const {
return TheKind == DirectToAccessor || TheKind == DispatchToAccessor ||
TheKind == DispatchToDistributedThunk;
}
AccessorKind getAccessor() const {
assert(hasAccessor());
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,
/// There's a read coroutine.
Read,
};
enum { NumReadImplKindBits = 4 };
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,
/// There's a modify coroutine.
Modify,
};
enum { NumWriteImplKindBits = 4 };
StringRef getWriteImplKindName(WriteImplKind kind);
/// How are read-write accesses implemented?
enum class ReadWriteImplKind {
/// It's immutable.
Immutable,
/// There's storage.
Stored,
/// There's a mutable addressor.
MutableAddress,
/// Do a read into a temporary and then a write back.
MaterializeToTemporary,
/// There's a modify coroutine.
Modify,
/// We have a didSet, so we're either going to use
/// MaterializeOrTemporary or the "simple didSet"
// access pattern.
StoredWithDidSet,
InheritedWithDidSet,
};
enum { NumReadWriteImplKindBits = 4 };
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 ||
readWriteImpl == ReadWriteImplKind::StoredWithDidSet);
return;
case WriteImplKind::InheritedWithObservers:
assert(readImpl == ReadImplKind::Inherited);
assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary ||
readWriteImpl == ReadWriteImplKind::InheritedWithDidSet);
return;
case WriteImplKind::Set:
assert(readImpl == ReadImplKind::Get ||
readImpl == ReadImplKind::Address ||
readImpl == ReadImplKind::Read);
assert(readWriteImpl == ReadWriteImplKind::MaterializeToTemporary ||
readWriteImpl == ReadWriteImplKind::Modify);
return;
case WriteImplKind::Modify:
assert(readImpl == ReadImplKind::Get ||
readImpl == ReadImplKind::Address ||
readImpl == ReadImplKind::Read);
assert(readWriteImpl == ReadWriteImplKind::Modify);
return;
case WriteImplKind::MutableAddress:
assert(readImpl == ReadImplKind::Get ||
readImpl == ReadImplKind::Address ||
readImpl == ReadImplKind::Read);
assert(readWriteImpl == ReadWriteImplKind::MutableAddress);
return;
}
llvm_unreachable("bad write impl kind");
#endif
}
static StorageImplInfo getSimpleStored(StorageIsMutable_t isMutable) {
return { ReadImplKind::Stored,
isMutable ? WriteImplKind::Stored
: WriteImplKind::Immutable,
isMutable ? ReadWriteImplKind::Stored
: ReadWriteImplKind::Immutable };
}
static StorageImplInfo getOpaque(StorageIsMutable_t isMutable,
OpaqueReadOwnership ownership) {
return (isMutable ? getMutableOpaque(ownership)
: getImmutableOpaque(ownership));
}
/// Describe the implementation of a immutable property implemented opaquely.
static StorageImplInfo getImmutableOpaque(OpaqueReadOwnership ownership) {
return { getOpaqueReadImpl(ownership) };
}
/// Describe the implementation of a mutable property implemented opaquely.
static StorageImplInfo getMutableOpaque(OpaqueReadOwnership ownership) {
return { getOpaqueReadImpl(ownership), WriteImplKind::Set,
ReadWriteImplKind::Modify };
}
static StorageImplInfo getComputed(StorageIsMutable_t isMutable) {
return (isMutable ? 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?
StorageIsMutable_t supportsMutation() const {
return StorageIsMutable_t(getWriteImpl() != WriteImplKind::Immutable);
}
ReadImplKind getReadImpl() const {
return ReadImplKind(Read);
}
WriteImplKind getWriteImpl() const {
return WriteImplKind(Write);
}
ReadWriteImplKind getReadWriteImpl() const {
return ReadWriteImplKind(ReadWrite);
}
private:
static ReadImplKind getOpaqueReadImpl(OpaqueReadOwnership ownership) {
switch (ownership) {
case OpaqueReadOwnership::Owned:
return ReadImplKind::Get;
case OpaqueReadOwnership::OwnedOrBorrowed:
case OpaqueReadOwnership::Borrowed:
return ReadImplKind::Read;
}
llvm_unreachable("bad read-ownership kind");
}
};
StringRef getAccessorLabel(AccessorKind kind);
void simple_display(llvm::raw_ostream &out, AccessorKind kind);
} // end namespace swift
#endif