//===--- MemberAccessStrategy.h - Abstract member access --------*- 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 an interface for summarizing how to access a
//  particular physical member.
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_IRGEN_MEMBERACCESSSTRATEGY_H
#define SWIFT_IRGEN_MEMBERACCESSSTRATEGY_H

#include <string>
#include "IRGen.h"

namespace swift {
namespace irgen {

/// A class which describes how to access a particular physical member.
class MemberAccessStrategy {
public:
  enum class OffsetKind : unsigned char {
    /// A word-size offset that represents a number of bytes.
    Bytes_Word,
  };

  enum class Kind : unsigned char {
    /// The member has complex or overlapping storage.  It cannot be
    /// accessed simply by projecting out to a particular offset.
    Complex,

    /// The member is at an offset which is statically known.
    DirectFixed,

    /// The member is at an offset which must be loaded from global memory.
    DirectGlobal,

    /// The member is at an offset which must be loaded from a
    /// statically-known offset within the type's metadata.
    IndirectFixed,

    /// The member is at an offset which must be loaded from an offset
    /// within the type's metadata, which itself must be loaded from
    /// a global symbol.
    IndirectGlobal,
  };

private:
  union StorageType {
    std::string GlobalSymbol;
    Size FixedOffset;

    StorageType() {}
    ~StorageType() {}
  } Storage;
  Kind TheKind;
  OffsetKind DirectOffsetKind;
  OffsetKind IndirectOffsetKind;

  explicit MemberAccessStrategy(Kind kind) : TheKind(kind) {}

public:
  // These are implementable, but there's no need.
  MemberAccessStrategy(const MemberAccessStrategy &other) = delete;
  MemberAccessStrategy &operator=(const MemberAccessStrategy &other) = delete;

  MemberAccessStrategy(MemberAccessStrategy &&other)
      : TheKind(other.TheKind) {
    switch (TheKind) {
    case Kind::Complex:
      return;
    case Kind::DirectFixed:
      Storage.FixedOffset = other.Storage.FixedOffset;
      return;
    case Kind::DirectGlobal:
      DirectOffsetKind = other.DirectOffsetKind;
      ::new (&Storage.GlobalSymbol)
          std::string(std::move(other.Storage.GlobalSymbol));
      return;
    case Kind::IndirectFixed:
      DirectOffsetKind = other.DirectOffsetKind;
      Storage.FixedOffset = other.Storage.FixedOffset;
      return;
    case Kind::IndirectGlobal:
      DirectOffsetKind = other.DirectOffsetKind;
      IndirectOffsetKind = other.IndirectOffsetKind;
      ::new (&Storage.GlobalSymbol)
          std::string(std::move(other.Storage.GlobalSymbol));
      return;
    }
    llvm_unreachable("bad member access strategy kind");
  }

  MemberAccessStrategy &operator=(MemberAccessStrategy &&other) {
    this->~MemberAccessStrategy();
    ::new (this) MemberAccessStrategy(std::move(other));
    return *this;
  }

  ~MemberAccessStrategy() {
    switch (TheKind) {
    case Kind::Complex:
    case Kind::DirectFixed:
    case Kind::IndirectFixed:
      return;
    case Kind::DirectGlobal:
    case Kind::IndirectGlobal:
      Storage.GlobalSymbol.~basic_string();
      return;
    }
    llvm_unreachable("bad member access strategy kind");
  }

  static MemberAccessStrategy
  getComplex() {
    return MemberAccessStrategy(Kind::Complex);
  }

  static MemberAccessStrategy
  getDirectFixed(Size directOffset) {
    MemberAccessStrategy result(Kind::DirectFixed);
    result.Storage.FixedOffset = directOffset;
    return result;
  }

  static MemberAccessStrategy
  getDirectGlobal(std::string &&directOffsetSymbol,
                  OffsetKind directOffsetKind) {
    MemberAccessStrategy result(Kind::DirectGlobal);
    ::new(&result.Storage.GlobalSymbol)
        std::string(std::move(directOffsetSymbol));
    result.DirectOffsetKind = directOffsetKind;
    return result;
  }

  static MemberAccessStrategy
  getIndirectFixed(Size indirectOffset, OffsetKind directOffsetKind) {
    MemberAccessStrategy result(Kind::IndirectFixed);
    result.Storage.FixedOffset = indirectOffset;
    result.DirectOffsetKind = directOffsetKind;
    return result;
  }

  static MemberAccessStrategy
  getIndirectGlobal(std::string &&indirectOffsetSymbol,
                    OffsetKind indirectOffsetKind,
                    OffsetKind directOffsetKind) {
    MemberAccessStrategy result(Kind::DirectGlobal);
    ::new(&result.Storage.GlobalSymbol)
        std::string(std::move(indirectOffsetSymbol));
    result.IndirectOffsetKind = indirectOffsetKind;
    result.DirectOffsetKind = directOffsetKind;
    return result;
  }

  Kind getKind() const { return TheKind; }
  OffsetKind getDirectOffsetKind() const {
    assert(TheKind == Kind::DirectGlobal ||
           TheKind == Kind::IndirectFixed ||
           TheKind == Kind::IndirectGlobal);
    return DirectOffsetKind;
  }
  OffsetKind getIndirectOffsetKind() const {
    assert(TheKind == Kind::IndirectGlobal);
    return IndirectOffsetKind;
  }

  Size getDirectOffset() const {
    assert(TheKind == Kind::DirectFixed);
    return Storage.FixedOffset;
  }

  Size getIndirectOffset() const {
    assert(TheKind == Kind::IndirectFixed);
    return Storage.FixedOffset;
  }

  const std::string &getDirectGlobalSymbol() const {
    assert(TheKind == Kind::DirectGlobal);
    return Storage.GlobalSymbol;
  }

  const std::string &getIndirectGlobalSymbol() const {
    assert(TheKind == Kind::IndirectGlobal);
    return Storage.GlobalSymbol;
  }
};

} // end namespace irgen
} // end namespace swift

#endif
