//===--- ClassTypeInfo.h - The layout info for class types. -----*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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 contains layout information for class types.
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_IRGEN_CLASSTYPEINFO_H
#define SWIFT_IRGEN_CLASSTYPEINFO_H

#include "ClassLayout.h"
#include "HeapTypeInfo.h"

#include "swift/AST/ASTContext.h"
#include "swift/AST/ClangModuleLoader.h"
#include "clang/AST/Decl.h"

namespace swift {
namespace irgen {

/// Layout information for class types.
class ClassTypeInfo : public HeapTypeInfo<ClassTypeInfo> {
  ClassDecl *TheClass;
  mutable llvm::StructType *classLayoutType;

  // The resilient layout of the class, without making any assumptions
  // that violate resilience boundaries. This is used to allocate
  // and deallocate instances of the class, and to access fields.
  mutable std::optional<ClassLayout> ResilientLayout;

  // A completely fragile layout, used for metadata emission.
  mutable std::optional<ClassLayout> FragileLayout;

  /// Can we use swift reference-counting, or do we have to use
  /// objc_retain/release?
  const ReferenceCounting Refcount;

  ClassLayout generateLayout(IRGenModule &IGM, SILType classType,
                             bool forBackwardDeployment) const;

public:
  ClassTypeInfo(llvm::PointerType *irType, Size size, SpareBitVector spareBits,
                Alignment align, ClassDecl *theClass,
                ReferenceCounting refcount, llvm::StructType *classLayoutType)
      : HeapTypeInfo(refcount, irType, size, std::move(spareBits), align),
        TheClass(theClass), classLayoutType(classLayoutType),
        Refcount(refcount) {}

  ReferenceCounting getReferenceCounting() const { return Refcount; }

  ClassDecl *getClass() const { return TheClass; }

  llvm::Type *getClassLayoutType() const { return classLayoutType; }

  /// For a foreign reference type with custom reference counting, returns the
  /// (retain, release) Clang functions to call. Returns {nullptr, nullptr} for
  /// any type without custom reference counting.
  std::pair<const clang::FunctionDecl *, const clang::FunctionDecl *>
  getCustomRefCountingOperations() const {
    auto *record =
        dyn_cast_or_null<clang::RecordDecl>(getClass()->getClangDecl());
    if (!record)
      return {nullptr, nullptr};
    return getClass()
        ->getASTContext()
        .getClangModuleLoader()
        ->getForeignReferenceTypeOperations(record);
  }

  const ClassLayout &getClassLayout(IRGenModule &IGM, SILType type,
                                    bool forBackwardDeployment) const;

  StructLayout *createLayoutWithTailElems(IRGenModule &IGM, SILType classType,
                                          ArrayRef<SILType> tailTypes) const;

  void emitScalarRelease(IRGenFunction &IGF, llvm::Value *value,
                         Atomicity atomicity) const override {
    if (getReferenceCounting() == ReferenceCounting::Custom) {
      auto releaseFn = getCustomRefCountingOperations().second;
      IGF.emitForeignReferenceTypeLifetimeOperation(releaseFn, value);
      return;
    }

    HeapTypeInfo::emitScalarRelease(IGF, value, atomicity);
  }

  bool canValueWitnessExtraInhabitantsUpTo(IRGenModule &IGM,
                                           unsigned index) const override {
    // Custom refcounting functions might not support null pointers.
    return index == 0 && getReferenceCounting() != ReferenceCounting::Custom;
  }

  void emitScalarRetain(IRGenFunction &IGF, llvm::Value *value,
                        Atomicity atomicity) const override {
    if (getReferenceCounting() == ReferenceCounting::Custom) {
      auto retainFn = getCustomRefCountingOperations().first;
      IGF.emitForeignReferenceTypeLifetimeOperation(retainFn, value);
      return;
    }

    HeapTypeInfo::emitScalarRetain(IGF, value, atomicity);
  }

  void strongCustomRetain(IRGenFunction &IGF, Explosion &e,
                          bool needsNullCheck) const override {
    assert(getReferenceCounting() == ReferenceCounting::Custom &&
           "only supported for custom ref-counting");

    llvm::Value *value = e.claimNext();
    auto retainFn = getCustomRefCountingOperations().first;
    IGF.emitForeignReferenceTypeLifetimeOperation(retainFn, value,
                                                  needsNullCheck);
  }

  // Implement the primary retain/release operations of ReferenceTypeInfo
  // using basic reference counting.
  void strongRetain(IRGenFunction &IGF, Explosion &e,
                    Atomicity atomicity) const override {
    if (getReferenceCounting() == ReferenceCounting::Custom) {
      strongCustomRetain(IGF, e, /*needsNullCheck*/ false);
      return;
    }

    HeapTypeInfo::strongRetain(IGF, e, atomicity);
  }

  void strongCustomRelease(IRGenFunction &IGF, Explosion &e,
                           bool needsNullCheck) const override {
    assert(getReferenceCounting() == ReferenceCounting::Custom &&
           "only supported for custom ref-counting");

    llvm::Value *value = e.claimNext();
    auto releaseFn = getCustomRefCountingOperations().second;
    IGF.emitForeignReferenceTypeLifetimeOperation(releaseFn, value,
                                                  needsNullCheck);
  }

  void strongRelease(IRGenFunction &IGF, Explosion &e,
                     Atomicity atomicity) const override {
    if (getReferenceCounting() == ReferenceCounting::Custom) {
      strongCustomRelease(IGF, e, /*needsNullCheck*/ false);
      return;
    }

    HeapTypeInfo::strongRelease(IGF, e, atomicity);
  }
};

} // namespace irgen
} // namespace swift

#endif
