// 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 #include "include/v8-fast-api-calls.h" #endif // V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_MIPS64 || \ // V8_TARGET_ARCH_LOONG64 #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 call support. static Address RedirectExternalReference(Address external_function, ExternalReference::Type type); 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 typename std::enable_if<std::is_integral<T>::value, T>::type ConvertReturn(intptr_t ret) { 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 typename std::enable_if<std::is_pointer<T>::value, T>::type ConvertReturn(intptr_t ret) { return reinterpret_cast<T>(ret); } template <typename T> static typename std::enable_if<std::is_base_of<Object, T>::value, T>::type ConvertReturn(intptr_t ret) { return Object(ret); } #if V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_MIPS64 || V8_TARGET_ARCH_LOONG64 template <typename T> static typename std::enable_if<std::is_same<T, v8::AnyCType>::value, T>::type ConvertReturn(intptr_t ret) { 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 // Convert back void return type (i.e. no return). template <typename T> static typename std::enable_if<std::is_void<T>::value, T>::type ConvertReturn( intptr_t ret) {} private: static base::Mutex* redirection_mutex_; static Redirection* redirection_; static base::Mutex* i_cache_mutex_; static base::CustomMatcherHashMap* i_cache_; // 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 typename std::enable_if<std::is_integral<T>::value, intptr_t>::type ConvertArg(T arg) { static_assert(sizeof(T) <= sizeof(intptr_t), "type bigger than ptrsize"); #if V8_TARGET_ARCH_MIPS64 || V8_TARGET_ARCH_RISCV64 || V8_TARGET_ARCH_LOONG64 // The MIPS64, LOONG64 and RISCV64 calling convention is to sign extend all // values, even unsigned ones. using signed_t = typename std::make_signed<T>::type; return static_cast<intptr_t>(static_cast<signed_t>(arg)); #else // Standard C++ convertion: 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 typename std::enable_if<std::is_pointer<T>::value, intptr_t>::type ConvertArg(T arg) { return reinterpret_cast<intptr_t>(arg); } template <typename T> static typename std::enable_if<std::is_floating_point<T>::value, intptr_t>::type ConvertArg(T arg) { UNREACHABLE(); } }; // 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_MIPS: swi (software-interrupt) // - V8_TARGET_ARCH_MIPS64: swi (software-interrupt) // - V8_TARGET_ARCH_PPC: svc (Supervisor Call) // - V8_TARGET_ARCH_PPC64: svc (Supervisor Call) // - V8_TARGET_ARCH_S390: 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* ReverseRedirection(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_