//===------------ Value.cpp - Definition of interpreter value -------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file defines the class that used to represent a value in incremental
// C++.
//
//===----------------------------------------------------------------------===//

#include "clang/Interpreter/Value.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Type.h"
#include "clang/Interpreter/Interpreter.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_os_ostream.h"
#include <cassert>
#include <cstdint>
#include <utility>

namespace {

// This is internal buffer maintained by Value, used to hold temporaries.
class ValueStorage {
public:
  using DtorFunc = void (*)(void *);

  static unsigned char *CreatePayload(void *DtorF, size_t AllocSize,
                                      size_t ElementsSize) {
    if (AllocSize < sizeof(Canary))
      AllocSize = sizeof(Canary);
    unsigned char *Buf =
        new unsigned char[ValueStorage::getPayloadOffset() + AllocSize];
    ValueStorage *VS = new (Buf) ValueStorage(DtorF, AllocSize, ElementsSize);
    std::memcpy(VS->getPayload(), Canary, sizeof(Canary));
    return VS->getPayload();
  }

  unsigned char *getPayload() { return Storage; }
  const unsigned char *getPayload() const { return Storage; }

  static unsigned getPayloadOffset() {
    static ValueStorage Dummy(nullptr, 0, 0);
    return Dummy.getPayload() - reinterpret_cast<unsigned char *>(&Dummy);
  }

  static ValueStorage *getFromPayload(void *Payload) {
    ValueStorage *R = reinterpret_cast<ValueStorage *>(
        (unsigned char *)Payload - getPayloadOffset());
    return R;
  }

  void Retain() { ++RefCnt; }

  void Release() {
    assert(RefCnt > 0 && "Can't release if reference count is already zero");
    if (--RefCnt == 0) {
      // We have a non-trivial dtor.
      if (Dtor && IsAlive()) {
        assert(Elements && "We at least should have 1 element in Value");
        size_t Stride = AllocSize / Elements;
        for (size_t Idx = 0; Idx < Elements; ++Idx)
          (*Dtor)(getPayload() + Idx * Stride);
      }
      delete[] reinterpret_cast<unsigned char *>(this);
    }
  }

  // Check whether the storage is valid by validating the canary bits.
  // If someone accidentally write some invalid bits in the storage, the canary
  // will be changed first, and `IsAlive` will return false then.
  bool IsAlive() const {
    return std::memcmp(getPayload(), Canary, sizeof(Canary)) != 0;
  }

private:
  ValueStorage(void *DtorF, size_t AllocSize, size_t ElementsNum)
      : RefCnt(1), Dtor(reinterpret_cast<DtorFunc>(DtorF)),
        AllocSize(AllocSize), Elements(ElementsNum) {}

  mutable unsigned RefCnt;
  DtorFunc Dtor = nullptr;
  size_t AllocSize = 0;
  size_t Elements = 0;
  unsigned char Storage[1];

  // These are some canary bits that are used for protecting the storage been
  // damaged.
  static constexpr unsigned char Canary[8] = {0x4c, 0x37, 0xad, 0x8f,
                                              0x2d, 0x23, 0x95, 0x91};
};
} // namespace

namespace clang {

static Value::Kind ConvertQualTypeToKind(const ASTContext &Ctx, QualType QT) {
  if (Ctx.hasSameType(QT, Ctx.VoidTy))
    return Value::K_Void;

  if (const auto *ET = QT->getAs<EnumType>())
    QT = ET->getDecl()->getIntegerType();

  const auto *BT = QT->getAs<BuiltinType>();
  if (!BT || BT->isNullPtrType())
    return Value::K_PtrOrObj;

  switch (QT->castAs<BuiltinType>()->getKind()) {
  default:
    assert(false && "Type not supported");
    return Value::K_Unspecified;
#define X(type, name)                                                          \
  case BuiltinType::name:                                                      \
    return Value::K_##name;
    REPL_BUILTIN_TYPES
#undef X
  }
}

Value::Value(Interpreter *In, void *Ty) : Interp(In), OpaqueType(Ty) {
  setKind(ConvertQualTypeToKind(getASTContext(), getType()));
  if (ValueKind == K_PtrOrObj) {
    QualType Canon = getType().getCanonicalType();
    if ((Canon->isPointerType() || Canon->isObjectType() ||
         Canon->isReferenceType()) &&
        (Canon->isRecordType() || Canon->isConstantArrayType() ||
         Canon->isMemberPointerType())) {
      IsManuallyAlloc = true;
      // Compile dtor function.
      Interpreter &Interp = getInterpreter();
      void *DtorF = nullptr;
      size_t ElementsSize = 1;
      QualType DtorTy = getType();

      if (const auto *ArrTy =
              llvm::dyn_cast<ConstantArrayType>(DtorTy.getTypePtr())) {
        DtorTy = ArrTy->getElementType();
        llvm::APInt ArrSize(sizeof(size_t) * 8, 1);
        do {
          ArrSize *= ArrTy->getSize();
          ArrTy = llvm::dyn_cast<ConstantArrayType>(
              ArrTy->getElementType().getTypePtr());
        } while (ArrTy);
        ElementsSize = static_cast<size_t>(ArrSize.getZExtValue());
      }
      if (const auto *RT = DtorTy->getAs<RecordType>()) {
        if (CXXRecordDecl *CXXRD =
                llvm::dyn_cast<CXXRecordDecl>(RT->getDecl())) {
          if (llvm::Expected<llvm::orc::ExecutorAddr> Addr =
                  Interp.CompileDtorCall(CXXRD))
            DtorF = reinterpret_cast<void *>(Addr->getValue());
          else
            llvm::logAllUnhandledErrors(Addr.takeError(), llvm::errs());
        }
      }

      size_t AllocSize =
          getASTContext().getTypeSizeInChars(getType()).getQuantity();
      unsigned char *Payload =
          ValueStorage::CreatePayload(DtorF, AllocSize, ElementsSize);
      setPtr((void *)Payload);
    }
  }
}

Value::Value(const Value &RHS)
    : Interp(RHS.Interp), OpaqueType(RHS.OpaqueType), Data(RHS.Data),
      ValueKind(RHS.ValueKind), IsManuallyAlloc(RHS.IsManuallyAlloc) {
  if (IsManuallyAlloc)
    ValueStorage::getFromPayload(getPtr())->Retain();
}

Value::Value(Value &&RHS) noexcept {
  Interp = std::exchange(RHS.Interp, nullptr);
  OpaqueType = std::exchange(RHS.OpaqueType, nullptr);
  Data = RHS.Data;
  ValueKind = std::exchange(RHS.ValueKind, K_Unspecified);
  IsManuallyAlloc = std::exchange(RHS.IsManuallyAlloc, false);

  if (IsManuallyAlloc)
    ValueStorage::getFromPayload(getPtr())->Release();
}

Value &Value::operator=(const Value &RHS) {
  if (IsManuallyAlloc)
    ValueStorage::getFromPayload(getPtr())->Release();

  Interp = RHS.Interp;
  OpaqueType = RHS.OpaqueType;
  Data = RHS.Data;
  ValueKind = RHS.ValueKind;
  IsManuallyAlloc = RHS.IsManuallyAlloc;

  if (IsManuallyAlloc)
    ValueStorage::getFromPayload(getPtr())->Retain();

  return *this;
}

Value &Value::operator=(Value &&RHS) noexcept {
  if (this != &RHS) {
    if (IsManuallyAlloc)
      ValueStorage::getFromPayload(getPtr())->Release();

    Interp = std::exchange(RHS.Interp, nullptr);
    OpaqueType = std::exchange(RHS.OpaqueType, nullptr);
    ValueKind = std::exchange(RHS.ValueKind, K_Unspecified);
    IsManuallyAlloc = std::exchange(RHS.IsManuallyAlloc, false);

    Data = RHS.Data;
  }
  return *this;
}

void Value::clear() {
  if (IsManuallyAlloc)
    ValueStorage::getFromPayload(getPtr())->Release();
  ValueKind = K_Unspecified;
  OpaqueType = nullptr;
  Interp = nullptr;
  IsManuallyAlloc = false;
}

Value::~Value() { clear(); }

void *Value::getPtr() const {
  assert(ValueKind == K_PtrOrObj);
  return Data.m_Ptr;
}

QualType Value::getType() const {
  return QualType::getFromOpaquePtr(OpaqueType);
}

Interpreter &Value::getInterpreter() {
  assert(Interp != nullptr &&
         "Can't get interpreter from a default constructed value");
  return *Interp;
}

const Interpreter &Value::getInterpreter() const {
  assert(Interp != nullptr &&
         "Can't get interpreter from a default constructed value");
  return *Interp;
}

ASTContext &Value::getASTContext() { return getInterpreter().getASTContext(); }

const ASTContext &Value::getASTContext() const {
  return getInterpreter().getASTContext();
}

void Value::dump() const { print(llvm::outs()); }

void Value::printType(llvm::raw_ostream &Out) const {
  Out << "Not implement yet.\n";
}
void Value::printData(llvm::raw_ostream &Out) const {
  Out << "Not implement yet.\n";
}
void Value::print(llvm::raw_ostream &Out) const {
  assert(OpaqueType != nullptr && "Can't print default Value");
  Out << "Not implement yet.\n";
}

} // namespace clang