// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef EXTENSIONS_CXX_DEBUGGING_WASMVENDORPLUGINS_H_
#define EXTENSIONS_CXX_DEBUGGING_WASMVENDORPLUGINS_H_
#include "ApiContext.h"
#include "Plugins/SymbolFile/DWARF/DWARFDeclContext.h"
#include "Plugins/SymbolFile/DWARF/SymbolFileDWARF.h"
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/Unwind.h"
#include "llvm/BinaryFormat/Dwarf.h"

class SymbolVendorWASM;
class SymbolFileDWARF;

namespace lldb_private {
class FileSystem;
class CPlusPlusLanguage;
class ClangASTContext;
namespace wasm {
class ObjectFileWasm;
class SymbolVendorWasm;
}  // namespace wasm
}  // namespace lldb_private

namespace symbols_backend {

template <typename T>
static void initialize() {  // NOLINT
  T::Initialize();
}
template <typename T>
static void terminate() {  // NOLINT
  T::Terminate();
}
template <typename T, typename FirstT, typename... MoreT>
static void initialize() {  // NOLINT
  T::Initialize();
  initialize<FirstT, MoreT...>();
}
template <typename T, typename FirstT, typename... MoreT>
static void terminate() {  // NOLINT
  terminate<FirstT, MoreT...>();
  T::Terminate();
}

template <typename... SystemT>
struct PluginRegistryContext {
  PluginRegistryContext() { initialize<SystemT...>(); }
  ~PluginRegistryContext() { terminate<SystemT...>(); }
};

class WasmPlatform : public lldb_private::Platform {
 public:
  using lldb_private::Platform::Platform;

  static void Initialize();

  static void Terminate();

  class Resolver : public lldb_private::UserIDResolver {
    llvm::Optional<std::string> DoGetUserName(id_t uid) final { return {}; }
    llvm::Optional<std::string> DoGetGroupName(id_t gid) final { return {}; }
  };
  Resolver resolver_;

  llvm::StringRef GetPluginName() final { return "wasm32"; }

  llvm::StringRef GetDescription() final { return "wasm32"; }

  lldb_private::UserIDResolver& GetUserIDResolver() final { return resolver_; }

  std::vector<lldb_private::ArchSpec> GetSupportedArchitectures(
      const lldb_private::ArchSpec& process_host_arch) final {
    return {{lldb_private::ArchSpec("wasm32-unknown-unknown")}};
  }

  void CalculateTrapHandlerSymbolNames() final {}

  lldb::ProcessSP Attach(lldb_private::ProcessAttachInfo& attach_info,
                         lldb_private::Debugger& debugger,
                         lldb_private::Target* target,
                         lldb_private::Status& error) final {
    error.SetErrorString("Cannot attach to processes");
    return {};
  }
};

class WasmRegisters : public lldb_private::RegisterContext {
  using lldb_private::RegisterContext::RegisterContext;
  lldb_private::RegisterInfo fake_pc_register_ = {"PC",
                                                  nullptr,
                                                  4,
                                                  0,
                                                  lldb::Encoding::eEncodingUint,
                                                  lldb::Format::eFormatDefault,
                                                  {0},
                                                  nullptr,
                                                  nullptr};
  size_t frame_offset_;

 public:
  WasmRegisters(lldb_private::Thread& thread, size_t frame_offset);

  void InvalidateAllRegisters() override{};

  size_t GetRegisterCount() override { return 1; }

  const lldb_private::RegisterInfo* GetRegisterInfoAtIndex(size_t reg) override;

  size_t GetRegisterSetCount() override { return 0; }

  const lldb_private::RegisterSet* GetRegisterSet(size_t reg_set) override {
    return nullptr;
  }

  lldb::ByteOrder GetByteOrder() override { return lldb::eByteOrderLittle; }

  bool ReadRegister(const lldb_private::RegisterInfo* reg_info,
                    lldb_private::RegisterValue& reg_value) override;

  bool WriteRegister(const lldb_private::RegisterInfo* reg_info,
                     const lldb_private::RegisterValue& reg_value) override {
    return false;
  }

  friend class WasmThread;
};

class WasmUnwind : public lldb_private::Unwind {
  size_t frame_offset_;

 public:
  explicit WasmUnwind(lldb_private::Thread& thread, size_t frame_offset)
      : Unwind(thread), frame_offset_(frame_offset) {}
  void DoClear() final{};
  uint32_t DoGetFrameCount() final { return 1; }

  bool DoGetFrameInfoAtIndex(uint32_t frame_idx,
                             lldb::addr_t& cfa,
                             lldb::addr_t& pc,
                             bool& behaves_like_zeroth_frame) final;

  lldb::RegisterContextSP DoCreateRegisterContextForFrame(
      lldb_private::StackFrame* frame) final {
    return GetRegisterContext();
  }
  lldb::RegisterContextSP GetRegisterContext() {
    return lldb::RegisterContextSP{
        new WasmRegisters(GetThread(), frame_offset_)};
  }
};

class WasmThread : public lldb_private::Thread {
  friend class WasmUnwind;
  lldb::StackFrameSP stack_frame_;
  WasmUnwind unwind_;

 public:
  WasmThread(lldb_private::Process& process, size_t frame_offset)
      : Thread(process, 0), unwind_(*this, frame_offset) {}

  void RefreshStateAfterStop() override {}
  bool CalculateStopInfo() override { return false; }

  WasmUnwind& GetUnwinder() final { return unwind_; }

  lldb::RegisterContextSP CreateRegisterContextForFrame(
      lldb_private::StackFrame* frame) override;

  lldb::RegisterContextSP GetRegisterContext() override;

  lldb::StackFrameSP GetFrame();
};

class WasmProcess : public lldb_private::Process {
  using lldb_private::Process::Process;
  const api::DebuggerProxy* proxy_ = nullptr;
  size_t frame_offset_ = 0;

 public:
  static void Initialize();

  static void Terminate();

  static lldb::ProcessSP CreateInstance(
      lldb::TargetSP target_sp,
      lldb::ListenerSP listener_sp,
      const lldb_private::FileSpec* crash_file_path,
      bool can_connect);

  static llvm::StringRef GetPluginDescriptionStatic() {
    return "wasm32 proces";
  }

  static llvm::StringRef GetPluginNameStatic() { return "wasm32"; }

  void SetProxyAndFrameOffset(const api::DebuggerProxy& proxy,
                              size_t frame_offset);
  bool CanDebug(lldb::TargetSP target, bool plugin_specified_by_name) override;
  lldb_private::Status DoDestroy() override { return {}; }
  void RefreshStateAfterStop() override {}
  bool DoUpdateThreadList(lldb_private::ThreadList& old_thread_list,
                          lldb_private::ThreadList& new_thread_list) override;

  size_t DoReadMemory(lldb::addr_t vm_addr,
                      void* buf,
                      size_t size,
                      lldb_private::Status& error) override;

  llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); }
};

class SymbolFileWasmDWARF : public ::SymbolFileDWARF {
  static char ID;

 public:
  using SymbolFileDWARF::SymbolFileDWARF;

  bool isA(const void* ClassID) const override {
    return ClassID == &ID || SymbolFileDWARF::isA(ClassID);
  }
  static bool classof(const SymbolFile* obj) { return obj->isA(&ID); }

  static void Initialize();

  static void Terminate();

  static llvm::StringRef GetPluginNameStatic() { return "wasm_dwarf"; }
  llvm::StringRef GetPluginName() final { return GetPluginNameStatic(); }

  static llvm::StringRef GetPluginDescriptionStatic();

  static lldb_private::SymbolFile* CreateInstance(
      lldb::ObjectFileSP objfile_sp);

  struct WasmValueLoader {
    SymbolFileWasmDWARF& symbol_file;
    explicit WasmValueLoader(SymbolFileWasmDWARF& symbol_file)
        : symbol_file(symbol_file) {
      assert(!symbol_file.current_value_loader_ &&
             "Cannot nest wasm eval contexts");
      symbol_file.current_value_loader_ = this;
    }
    ~WasmValueLoader() { symbol_file.current_value_loader_ = nullptr; }

    virtual llvm::Expected<api::DebuggerProxy::WasmValue> LoadWASMValue(
        uint8_t storage_type,
        const lldb_private::DataExtractor& data,
        lldb::offset_t& offset) = 0;
  };

  lldb::offset_t GetVendorDWARFOpcodeSize(
      const lldb_private::DataExtractor& data,
      const lldb::offset_t data_offset,
      const uint8_t op) const final {
    return LLDB_INVALID_OFFSET;
  }

  bool ParseVendorDWARFOpcode(
      uint8_t op,
      const lldb_private::DataExtractor& opcodes,
      lldb::offset_t& offset,
      std::vector<lldb_private::Value>& stack) const final {
    if (!current_value_loader_) {
      return false;
    }
    switch (op) {
      case llvm::dwarf::DW_OP_WASM_location_int:
      case llvm::dwarf::DW_OP_WASM_location: {
        uint8_t storage_type = opcodes.GetU8(&offset);
        auto value =
            current_value_loader_->LoadWASMValue(storage_type, opcodes, offset);
        if (!value) {
          llvm::errs() << llvm::toString(value.takeError());
          return false;
        }
        auto stack_value = std::visit(
            [](auto v) { return lldb_private::Scalar(v); }, value->value);
        stack.push_back(stack_value);
        stack.back().SetValueType(lldb_private::Value::ValueType::Scalar);
        return true;
      }
    }

    return false;
  }

  std::shared_ptr<lldb_private::Type> externref_type_sp;

  lldb::TypeSP FindDefinitionTypeForDWARFDeclContext(
      const DWARFDeclContext& dwarf_decl_ctx) override {
    // We define type externref_t as a 32-bit integer, so as to be
    // able to transfer some information through the interpreter
    const uint32_t dwarf_decl_ctx_count = dwarf_decl_ctx.GetSize();
    if (dwarf_decl_ctx_count > 0) {
      const lldb_private::ConstString type_name(dwarf_decl_ctx[0].name);
      if (type_name == "externref_t") {
        if (!externref_type_sp) {
          const lldb::LanguageType language = dwarf_decl_ctx.GetLanguage();
          auto type_system = GetTypeSystemForLanguage(language);
          bool ok = !type_system.takeError();
          assert(ok);
          auto ast = clang::dyn_cast<lldb_private::TypeSystemClang>(
              type_system->get());
          lldb_private::CompilerType clang_type =
              ast->GetBasicType(lldb::eBasicTypeUnsignedLongLong);
          externref_type_sp = std::make_shared<lldb_private::Type>(
              lldb::user_id_t(0), this, type_name, 4, nullptr, LLDB_INVALID_UID,
              lldb_private::Type::eEncodingIsUID, lldb_private::Declaration(),
              clang_type, lldb_private::Type::ResolveState::Forward);
        }
        return externref_type_sp;
      }
    }
    return SymbolFileDWARF::FindDefinitionTypeForDWARFDeclContext(
        dwarf_decl_ctx);
  }

 private:
  WasmValueLoader* current_value_loader_ = nullptr;
};

}  // namespace symbols_backend

#endif  // EXTENSIONS_CXX_DEBUGGING_WASMVENDORPLUGINS_H_
