// Copyright 2017 the V8 project 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 V8_EXECUTION_SIMULATOR_BASE_H_
#define V8_EXECUTION_SIMULATOR_BASE_H_

#include <type_traits>

#if V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_MIPS64 || V8_TARGET_ARCH_LOONG64 || \
    V8_TARGET_ARCH_RISCV64
#include "include/v8-fast-api-calls.h"
#endif  // V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_MIPS64 || \
        // V8_TARGET_ARCH_LOONG64 || V8_TARGET_ARCH_RISCV64
#include "src/base/hashmap.h"
#include "src/common/globals.h"
#include "src/execution/isolate.h"

#if defined(USE_SIMULATOR)

namespace v8 {
namespace internal {

class Instruction;
class Redirection;

class SimulatorBase {
 public:
  // Call on process start and exit.
  static void InitializeOncePerProcess();
  static void GlobalTearDown();

  static base::Mutex* redirection_mutex() { return redirection_mutex_; }
  static Redirection* redirection() { return redirection_; }
  static void set_redirection(Redirection* r) { redirection_ = r; }

  static base::Mutex* i_cache_mutex() { return i_cache_mutex_; }
  static base::CustomMatcherHashMap* i_cache() { return i_cache_; }

  // Runtime/C function call support.
  // Creates a trampoline to a given C function callable from generated code.
  static Address RedirectExternalReference(Address external_function,
                                           ExternalReference::Type type);

  // Extracts the target C function address from a given redirection trampoline.
  static Address UnwrapRedirection(Address redirection_trampoline);

 protected:
  template <typename Return, typename SimT, typename CallImpl, typename... Args>
  static Return VariadicCall(SimT* sim, CallImpl call, Address entry,
                             Args... args) {
    // Convert all arguments to intptr_t. Fails if any argument is not integral
    // or pointer.
    std::array<intptr_t, sizeof...(args)> args_arr{{ConvertArg(args)...}};
    intptr_t ret = (sim->*call)(entry, args_arr.size(), args_arr.data());
    return ConvertReturn<Return>(ret);
  }

  // Convert back integral return types. This is always a narrowing conversion.
  template <typename T>
  static T ConvertReturn(intptr_t ret)
    requires std::is_integral_v<T>
  {
    static_assert(sizeof(T) <= sizeof(intptr_t), "type bigger than ptrsize");
    return static_cast<T>(ret);
  }

  // Convert back pointer-typed return types.
  template <typename T>
  static T ConvertReturn(intptr_t ret)
    requires std::is_pointer_v<T>
  {
    return reinterpret_cast<T>(ret);
  }

  template <typename T>
  static T ConvertReturn(intptr_t ret)
    requires std::is_base_of_v<Object, T>
  {
    return Tagged<Object>(ret);
  }

#if V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_MIPS64 || V8_TARGET_ARCH_LOONG64 || \
    V8_TARGET_ARCH_RISCV64
  template <typename T>
  static T ConvertReturn(intptr_t ret)
    requires std::is_same_v<T, v8::AnyCType>
  {
    v8::AnyCType result;
    result.int64_value = static_cast<int64_t>(ret);
    return result;
  }
#endif  // V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_MIPS64 || \
        // V8_TARGET_ARCH_LOONG64 || V8_TARGET_ARCH_RISCV64

  // Convert back void return type (i.e. no return).
  template <typename T>
  static T ConvertReturn(intptr_t ret)
    requires std::is_void_v<T>
  {}

  // Helper methods to convert arbitrary integer or pointer arguments to the
  // needed generic argument type intptr_t.

  // Convert integral argument to intptr_t.
  template <typename T>
  static intptr_t ConvertArg(T arg)
    requires std::is_integral_v<T>
  {
    static_assert(sizeof(T) <= sizeof(intptr_t), "type bigger than ptrsize");
#if V8_TARGET_ARCH_MIPS64 || V8_TARGET_ARCH_LOONG64 || \
    V8_TARGET_ARCH_RISCV32 || V8_TARGET_ARCH_RISCV64
    // The MIPS64, LOONG64 and RISCV64 calling convention is to sign extend all
    // values, even unsigned ones.
    using signed_t = std::make_signed_t<T>;
    return static_cast<intptr_t>(static_cast<signed_t>(arg));
#else
    // Standard C++ conversion: Sign-extend signed values, zero-extend unsigned
    // values.
    return static_cast<intptr_t>(arg);
#endif
  }

  // Convert pointer-typed argument to intptr_t.
  template <typename T>
  static intptr_t ConvertArg(T arg)
    requires std::is_pointer_v<T>
  {
    return reinterpret_cast<intptr_t>(arg);
  }

  template <typename T>
  static intptr_t ConvertArg(T arg)
    requires std::is_floating_point_v<T>
  {
    UNREACHABLE();
  }

 private:
  static base::Mutex* redirection_mutex_;
  static Redirection* redirection_;

  static base::Mutex* i_cache_mutex_;
  static base::CustomMatcherHashMap* i_cache_;
};

// When the generated code calls an external reference we need to catch that in
// the simulator.  The external reference will be a function compiled for the
// host architecture.  We need to call that function instead of trying to
// execute it with the simulator.  We do that by redirecting the external
// reference to a trapping instruction that is handled by the simulator.  We
// write the original destination of the jump just at a known offset from the
// trapping instruction so the simulator knows what to call.
//
// The following are trapping instructions used for various architectures:
//  - V8_TARGET_ARCH_ARM: svc (Supervisor Call)
//  - V8_TARGET_ARCH_ARM64: svc (Supervisor Call)
//  - V8_TARGET_ARCH_MIPS64: swi (software-interrupt)
//  - V8_TARGET_ARCH_PPC64: svc (Supervisor Call)
//  - V8_TARGET_ARCH_S390X: svc (Supervisor Call)
//  - V8_TARGET_ARCH_RISCV64: ecall (Supervisor Call)
class Redirection {
 public:
  Redirection(Address external_function, ExternalReference::Type type);

  Address address_of_instruction() {
#if ABI_USES_FUNCTION_DESCRIPTORS
    return reinterpret_cast<Address>(function_descriptor_);
#else
    return reinterpret_cast<Address>(&instruction_);
#endif
  }

  void* external_function() {
    return reinterpret_cast<void*>(external_function_);
  }
  ExternalReference::Type type() { return type_; }

  static Redirection* Get(Address external_function,
                          ExternalReference::Type type);

  static Redirection* FromInstruction(Instruction* instruction) {
    Address addr_of_instruction = reinterpret_cast<Address>(instruction);
    Address addr_of_redirection =
        addr_of_instruction - offsetof(Redirection, instruction_);
    return reinterpret_cast<Redirection*>(addr_of_redirection);
  }

  static void* UnwrapRedirection(intptr_t reg) {
    Redirection* redirection = FromInstruction(
        reinterpret_cast<Instruction*>(reinterpret_cast<void*>(reg)));
    return redirection->external_function();
  }

  static void DeleteChain(Redirection* redirection) {
    while (redirection != nullptr) {
      Redirection* next = redirection->next_;
      delete redirection;
      redirection = next;
    }
  }

 private:
  Address external_function_;
  uint32_t instruction_;
  ExternalReference::Type type_;
  Redirection* next_;
#if ABI_USES_FUNCTION_DESCRIPTORS
  intptr_t function_descriptor_[3];
#endif
};

class SimulatorData {
 public:
  // Calls AddSignatureForTarget for each function and signature, registering
  // an encoded version of the signature within a mapping maintained by the
  // simulator (from function address -> encoded signature). The function
  // is supposed to be called whenever one compiles a fast API function with
  // possibly multiple overloads.
  // Note that this function is called from one or more compiler threads,
  // while the main thread might be reading at the same time from the map, so
  // both Register* and Get* are guarded with a single mutex.
  void RegisterFunctionsAndSignatures(Address* c_functions,
                                      const CFunctionInfo* const* c_signatures,
                                      unsigned num_functions);
  // The following method is used by the simulator itself to query
  // whether a signature is registered for the call target and use this
  // information to address arguments correctly (load them from either GP or
  // FP registers, or from the stack).
  const EncodedCSignature& GetSignatureForTarget(Address target);
  // This method is exposed only for tests, which don't need synchronisation.
  void AddSignatureForTargetForTesting(Address target,
                                       const EncodedCSignature& signature) {
    AddSignatureForTarget(target, signature);
  }

 private:
  void AddSignatureForTarget(Address target,
                             const EncodedCSignature& signature) {
    target_to_signature_table_[target] = signature;
  }

  v8::base::Mutex signature_map_mutex_;
  typedef std::unordered_map<Address, EncodedCSignature> TargetToSignatureTable;
  TargetToSignatureTable target_to_signature_table_;
};

}  // namespace internal
}  // namespace v8

#endif  // defined(USE_SIMULATOR)
#endif  // V8_EXECUTION_SIMULATOR_BASE_H_
