//===--- MetadataLayout.h - Type metadata layout ----------------*- 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
//
//===----------------------------------------------------------------------===//
//
// Information recording the layout of type metadata objects.
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_IRGEN_METADATALAYOUT_H
#define SWIFT_IRGEN_METADATALAYOUT_H

#include "IRGen.h"
#include "swift/SIL/SILDeclRef.h"

namespace swift {
class ClassDecl;
class EnumDecl;
class StructDecl;
class VarDecl;

namespace irgen {
class Address;
class IRGenFunction;
class IRGenModule;

/// The total size and address point of a metadata object.
struct MetadataSize {
  Size FullSize;
  Size AddressPoint;

  /// Return the offset from the address point to the end of the
  /// metadata object.
  Size getOffsetToEnd() const {
    return FullSize - AddressPoint;
  }
};

/// A base class for various kinds of metadata layout.
class MetadataLayout {
public:
  enum class Kind {
    Class,
    Struct,
    Enum,
    ForeignClass,
  };

  class StoredOffset {
  public:
    enum Kind {
      /// The high bits are an integer displacement.
      Static = 0,

      /// The high bits are an integer displacement relative to a offset stored
      /// in a class metadata base offset global variable. This is used to
      /// access members of class metadata where the superclass is resilient to
      /// us, and therefore has an unknown size.
      Dynamic,
    };

  private:
    enum : uint64_t {
      KindBits = 1,
      KindMask = (1 << KindBits) - 1,
      PayloadMask = ~uint64_t(KindMask)
    };

    mutable uintptr_t Data;
  public:
    StoredOffset() : Data(0) {}
    explicit StoredOffset(Size offset, Kind kind)
      : Data((static_cast<uint64_t>(offset.getValue()) << KindBits) | kind) {
      assert(kind == Kind::Dynamic || !offset.isZero() &&
             "cannot store a zero static offset");
      if (kind == Kind::Static)
        assert(getStaticOffset() == offset && "overflow");
      if (kind == Kind::Dynamic)
        assert(getRelativeOffset() == offset && "overflow");
    }

    bool isValid() const { return Data != 0; }
    bool isStatic() const { return isValid() && (Data & KindMask) == Static; }
    bool isDynamic() const { return (Data & KindMask) == Dynamic; }

    /// If this is a metadata offset into a resilient class, returns the offset
    /// relative to the size of the superclass metadata.
    Size getRelativeOffset() const {
      assert(isDynamic());
      return Size(static_cast<int64_t>(Data) >> KindBits);
    }

    /// Returns the offset relative to start of metadata. Only used for
    /// metadata fields whose offset is completely known at compile time.
    Size getStaticOffset() const {
      assert(isStatic());
      return Size(static_cast<int64_t>(Data) >> KindBits);
    }
  };

private:
  Kind TheKind;

protected:
  MetadataSize TheSize;

  MetadataLayout(Kind theKind) : TheKind(theKind) {}

  MetadataLayout(const MetadataLayout &other) = delete;
  MetadataLayout &operator=(const MetadataLayout &other) = delete;

public:
  /// Destruct and deallocate this layout object.
  void destroy() const;

  Kind getKind() const { return TheKind; }

  MetadataSize getSize() const { return TheSize; }
};

/// Base class for nominal type metadata layouts.
class NominalMetadataLayout : public MetadataLayout {
protected:
  NominalTypeDecl *Nominal;
  StoredOffset GenericRequirements;

  NominalMetadataLayout(Kind kind, NominalTypeDecl *nominal)
      : MetadataLayout(kind), Nominal(nominal) {}

  Offset emitOffset(IRGenFunction &IGF, StoredOffset offset) const;

public:
  NominalTypeDecl *getDecl() const {
    return Nominal;
  }

  bool hasGenericRequirements() const {
    return GenericRequirements.isValid();
  }

  /// Should only be used when emitting the nominal type descriptor.
  Size getStaticGenericRequirementsOffset() const;

  Offset getGenericRequirementsOffset(IRGenFunction &IGF) const;

  static bool classof(const MetadataLayout *layout) {
    switch (layout->getKind()) {
    case MetadataLayout::Kind::Class:
    case MetadataLayout::Kind::Enum:
    case MetadataLayout::Kind::Struct:
      return true;

    case MetadataLayout::Kind::ForeignClass:
      return false;
    }
    llvm_unreachable("unhandled kind");
  }
};

/// Layout for class type metadata.
class ClassMetadataLayout : public NominalMetadataLayout {
public:
  class MethodInfo {
  public:
    enum class Kind {
      Offset,
      DirectImpl,
    };
    
  private:
    Kind TheKind;
    union {
      Offset TheOffset;
      llvm::Function *TheImpl;
    };
  public:
    MethodInfo(Offset offset)
      : TheKind(Kind::Offset), TheOffset(offset) {}
    MethodInfo(llvm::Function *impl)
      : TheKind(Kind::DirectImpl), TheImpl(impl) {}

    Kind getKind() const { return TheKind; }
    
    Offset getOffset() const {
      assert(getKind() == Kind::Offset);
      return TheOffset;
    }
    llvm::Function *getDirectImpl() const {
      assert(getKind() == Kind::DirectImpl);
      return TheImpl;
    }
  };

private:
  bool HasResilientSuperclass = false;

  StoredOffset StartOfImmediateMembers;

  StoredOffset MetadataSize;
  StoredOffset MetadataAddressPoint;

  StoredOffset InstanceSize;
  StoredOffset InstanceAlignMask;

  struct StoredMethodInfo {
    MethodInfo::Kind TheKind;
    union {
      StoredOffset TheOffset;
      llvm::Function *TheImpl;
    };
    StoredMethodInfo(StoredOffset offset) : TheKind(MethodInfo::Kind::Offset),
                                            TheOffset(offset) {}
    StoredMethodInfo(llvm::Function *impl)
      : TheKind(MethodInfo::Kind::DirectImpl),
        TheImpl(impl) {}
  };
  llvm::DenseMap<SILDeclRef, StoredMethodInfo> MethodInfos;

  /// Field offsets for various fields.
  llvm::DenseMap<VarDecl*, StoredOffset> FieldOffsets;

  /// The start of the vtable.
  StoredOffset VTableOffset;

  /// The start of the field-offset vector.
  StoredOffset FieldOffsetVector;

  /// The number of members to add after superclass metadata.
  unsigned NumImmediateMembers;

  const StoredMethodInfo &getStoredMethodInfo(SILDeclRef method) const {
    auto it = MethodInfos.find(method);
    assert(it != MethodInfos.end());
    return it->second;
  }

  const StoredOffset &getStoredFieldOffset(VarDecl *field) const {
    auto it = FieldOffsets.find(field);
    assert(it != FieldOffsets.end());
    return it->second;
  }

  friend class IRGenModule;
  ClassMetadataLayout(IRGenModule &IGM, ClassDecl *theClass);

public:
  ClassDecl *getDecl() const {
    return cast<ClassDecl>(Nominal);
  }

  bool hasResilientSuperclass() const {
    return HasResilientSuperclass;
  }

  constexpr static bool areImmediateMembersNegative() {
    return false;
  }

  Size getMetadataSizeOffset() const;

  Size getMetadataAddressPointOffset() const;

  Size getInstanceSizeOffset() const;

  Size getInstanceAlignMaskOffset() const;

  MethodInfo getMethodInfo(IRGenFunction &IGF, SILDeclRef method) const;

  Offset getFieldOffset(IRGenFunction &IGF, VarDecl *field) const;

  /// Assuming that the given field offset is at a static offset in
  /// the metadata, return that static offset.
  ///
  /// DEPRECATED: callers should be updated to handle this in a
  /// more arbitrary fashion.
  Size getStaticFieldOffset(VarDecl *field) const;

  /// Should only be used when emitting the nominal type descriptor.
  Size getRelativeGenericRequirementsOffset() const;

  Size getStaticFieldOffsetVectorOffset() const;
  Size getRelativeFieldOffsetVectorOffset() const;

  Size getStaticVTableOffset() const;
  Size getRelativeVTableOffset() const;

  Offset getFieldOffsetVectorOffset(IRGenFunction &IGF) const;

  /// If the start of the immediate members is statically known, this
  /// method will return it. Otherwise, it will assert.
  Size getStartOfImmediateMembers() const {
    return StartOfImmediateMembers.getStaticOffset();
  }

  /// The number of members to add after superclass metadata. The size of
  /// this metadata is the superclass size plus the number of immediate
  /// members in the class itself.
  unsigned getNumImmediateMembers() const {
    return NumImmediateMembers;
  }

  static bool classof(const MetadataLayout *layout) {
    return layout->getKind() == Kind::Class;
  }
};

/// Layout for enum type metadata.
class EnumMetadataLayout : public NominalMetadataLayout {
  /// The offset of the payload size field, if there is one.
  StoredOffset PayloadSizeOffset;
  StoredOffset TrailingFlagsOffset;

  // TODO: presumably it would be useful to store *something* here
  // for resilience.

  friend class IRGenModule;
  EnumMetadataLayout(IRGenModule &IGM, EnumDecl *theEnum);

public:
  EnumDecl *getDecl() const {
    return cast<EnumDecl>(Nominal);
  }

  bool hasPayloadSizeOffset() const {
    return PayloadSizeOffset.isValid();
  }

  Offset getPayloadSizeOffset() const;
  Offset getTrailingFlagsOffset() const;

  static bool classof(const MetadataLayout *layout) {
    return layout->getKind() == Kind::Enum;
  }
};

/// Layout for struct type metadata.
class StructMetadataLayout : public NominalMetadataLayout {
  llvm::DenseMap<VarDecl*, StoredOffset> FieldOffsets;
  StoredOffset TrailingFlagsOffset;

  /// The start of the field-offset vector.
  StoredOffset FieldOffsetVector;

  const StoredOffset &getStoredFieldOffset(VarDecl *field) const {
    auto it = FieldOffsets.find(field);
    assert(it != FieldOffsets.end());
    return it->second;
  }

  friend class IRGenModule;
  StructMetadataLayout(IRGenModule &IGM, StructDecl *theStruct);

public:
  StructDecl *getDecl() const {
    return cast<StructDecl>(Nominal);
  }

  Offset getFieldOffset(IRGenFunction &IGF, VarDecl *field) const;

  /// Assuming that the given field offset is at a static offset in
  /// the metadata, return that static offset.
  ///
  /// DEPRECATED: callers should be updated to handle this in a
  /// more arbitrary fashion.
  Size getStaticFieldOffset(VarDecl *field) const;

  Offset getFieldOffsetVectorOffset() const;
  Offset getTrailingFlagsOffset() const;

  static bool classof(const MetadataLayout *layout) {
    return layout->getKind() == Kind::Struct;
  }
};

/// Layout for foreign class type metadata.
class ForeignClassMetadataLayout : public MetadataLayout {
  ClassDecl *Class;
  StoredOffset SuperClassOffset;

  friend class IRGenModule;
  ForeignClassMetadataLayout(IRGenModule &IGM, ClassDecl *theClass);

public:
  StoredOffset getSuperClassOffset() const { return SuperClassOffset; }

  static bool classof(const MetadataLayout *layout) {
    return layout->getKind() == Kind::ForeignClass;
  }
};

/// Emit the address of the field-offset slot in the given class metadata.
Address emitAddressOfClassFieldOffset(IRGenFunction &IGF,
                                      llvm::Value *metadata,
                                      ClassDecl *theClass,
                                      VarDecl *field);

/// Get the offset to a field offset in the class type metadata.
///
/// DEPRECATED: callers should be updated to handle this in a more
/// arbitrary fashion.
Size getClassFieldOffsetOffset(IRGenModule &IGM,
                               ClassDecl *theClass,
                               VarDecl *field);

/// Emit the address of the field-offset vector in the given class or struct
/// metadata.
Address emitAddressOfFieldOffsetVector(IRGenFunction &IGF,
                                       llvm::Value *metadata,
                                       NominalTypeDecl *theDecl);

/// Given a reference to class type metadata of the given type,
/// decide the offset to the given field.  This assumes that the
/// offset is stored in the metadata, i.e. its offset is potentially
/// dependent on generic arguments.  The result is a ptrdiff_t.
llvm::Value *emitClassFieldOffset(IRGenFunction &IGF,
                                  ClassDecl *theClass,
                                  VarDecl *field,
                                  llvm::Value *metadata);

/// Given a class metadata pointer, emit the address of its superclass field.  
Address emitAddressOfSuperclassRefInClassMetadata(IRGenFunction &IGF,
                                                  llvm::Value *metadata);

Size getStaticTupleElementOffset(IRGenModule &IGM,
                                 SILType tupleType,
                                 unsigned eltIdx);

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

#endif
