/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: */ // Copyright 2021 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifdef JS_SIMULATOR_RISCV64 # include "jit/riscv64/Simulator-riscv64.h" # include "mozilla/Casting.h" # include "mozilla/FloatingPoint.h" # include "mozilla/IntegerPrintfMacros.h" # include "mozilla/Likely.h" # include "mozilla/MathAlgorithms.h" # include # include # include # include "jit/AtomicOperations.h" # include "jit/riscv64/Assembler-riscv64.h" # include "js/Conversions.h" # include "js/UniquePtr.h" # include "js/Utility.h" # include "threading/LockGuard.h" # include "vm/JSContext.h" # include "vm/Runtime.h" # include "wasm/WasmInstance.h" # include "wasm/WasmSignalHandlers.h" # define I8(v) static_cast(v) # define I16(v) static_cast(v) # define U16(v) static_cast(v) # define I32(v) static_cast(v) # define U32(v) static_cast(v) # define I64(v) static_cast(v) # define U64(v) static_cast(v) # define I128(v) static_cast<__int128_t>(v) # define U128(v) static_cast<__uint128_t>(v) # define REGIx_FORMAT PRIx64 # define REGId_FORMAT PRId64 # define I32_CHECK(v) \ ({ \ MOZ_ASSERT(I64(I32(v)) == I64(v)); \ I32((v)); \ }) namespace js { namespace jit { bool Simulator::FLAG_trace_sim = false; bool Simulator::FLAG_debug_sim = false; bool Simulator::FLAG_riscv_trap_to_simulator_debugger = false; bool Simulator::FLAG_riscv_print_watchpoint = false; static void UNIMPLEMENTED() { printf("UNIMPLEMENTED instruction.\n"); MOZ_CRASH(); } static void UNREACHABLE() { printf("UNREACHABLE instruction.\n"); MOZ_CRASH(); } # define UNSUPPORTED() \ std::cout << "Unrecognized instruction [@pc=0x" << std::hex \ << registers_[pc] << "]: 0x" << instr_.InstructionBits() \ << std::endl; \ printf("Unsupported instruction.\n"); \ MOZ_CRASH(); static char* ReadLine(const char* prompt) { UniqueChars result; char lineBuf[256]; int offset = 0; bool keepGoing = true; fprintf(stdout, "%s", prompt); fflush(stdout); while (keepGoing) { if (fgets(lineBuf, sizeof(lineBuf), stdin) == nullptr) { // fgets got an error. Just give up. return nullptr; } int len = strlen(lineBuf); if (len > 0 && lineBuf[len - 1] == '\n') { // Since we read a new line we are done reading the line. This // will exit the loop after copying this buffer into the result. keepGoing = false; } if (!result) { // Allocate the initial result and make room for the terminating '\0' result.reset(js_pod_malloc(len + 1)); if (!result) { return nullptr; } } else { // Allocate a new result with enough room for the new addition. int new_len = offset + len + 1; char* new_result = js_pod_malloc(new_len); if (!new_result) { return nullptr; } // Copy the existing input into the new array and set the new // array as the result. memcpy(new_result, result.get(), offset * sizeof(char)); result.reset(new_result); } // Copy the newly read line into the result. memcpy(result.get() + offset, lineBuf, len * sizeof(char)); offset += len; } MOZ_ASSERT(result); result[offset] = '\0'; return result.release(); } // ----------------------------------------------------------------------------- // Riscv assembly various constants. // C/C++ argument slots size. const int kCArgSlotCount = 0; const int kCArgsSlotsSize = kCArgSlotCount * sizeof(uintptr_t); const int kBranchReturnOffset = 2 * kInstrSize; class CachePage { public: static const int LINE_VALID = 0; static const int LINE_INVALID = 1; static const int kPageShift = 12; static const int kPageSize = 1 << kPageShift; static const int kPageMask = kPageSize - 1; static const int kLineShift = 2; // The cache line is only 4 bytes right now. static const int kLineLength = 1 << kLineShift; static const int kLineMask = kLineLength - 1; CachePage() { memset(&validity_map_, LINE_INVALID, sizeof(validity_map_)); } char* validityByte(int offset) { return &validity_map_[offset >> kLineShift]; } char* cachedData(int offset) { return &data_[offset]; } private: char data_[kPageSize]; // The cached data. static const int kValidityMapSize = kPageSize >> kLineShift; char validity_map_[kValidityMapSize]; // One byte per line. }; // Protects the icache() and redirection() properties of the // Simulator. class AutoLockSimulatorCache : public LockGuard { using Base = LockGuard; public: explicit AutoLockSimulatorCache() : Base(SimulatorProcess::singleton_->cacheLock_) {} }; mozilla::Atomic SimulatorProcess::ICacheCheckingDisableCount( 1); // Checking is disabled by default. SimulatorProcess* SimulatorProcess::singleton_ = nullptr; int64_t Simulator::StopSimAt = -1; static bool IsFlag(const char* found, const char* flag) { return strlen(found) == strlen(flag) && strcmp(found, flag) == 0; } Simulator* Simulator::Create() { auto sim = MakeUnique(); if (!sim) { return nullptr; } if (!sim->init()) { return nullptr; } int64_t stopAt; char* stopAtStr = getenv("RISCV_SIM_STOP_AT"); if (stopAtStr && sscanf(stopAtStr, "%" PRIi64, &stopAt) == 1) { fprintf(stderr, "\nStopping simulation at icount %" PRIi64 "\n", stopAt); Simulator::StopSimAt = stopAt; } char* str = getenv("RISCV_TRACE_SIM"); if (str != nullptr && IsFlag(str, "true")) { FLAG_trace_sim = true; } return sim.release(); } void Simulator::Destroy(Simulator* sim) { js_delete(sim); } # if JS_CODEGEN_RISCV64 void Simulator::TraceRegWr(int64_t value, TraceType t) { if (FLAG_trace_sim) { union { int64_t fmt_int64; int32_t fmt_int32[2]; float fmt_float[2]; double fmt_double; } v; v.fmt_int64 = value; switch (t) { case WORD: SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRId64 ") int32:%" PRId32 " uint32:%" PRIu32, v.fmt_int64, icount_, v.fmt_int32[0], v.fmt_int32[0]); break; case DWORD: SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRId64 ") int64:%" REGId_FORMAT " uint64:%" PRIu64, value, icount_, value, value); break; case FLOAT: SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRId64 ") flt:%e", v.fmt_int64, icount_, v.fmt_float[0]); break; case DOUBLE: SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRId64 ") dbl:%e", v.fmt_int64, icount_, v.fmt_double); break; default: UNREACHABLE(); } } } # elif JS_CODEGEN_RISCV32 template void Simulator::TraceRegWr(T value, TraceType t) { if (::v8::internal::FLAG_trace_sim) { union { int32_t fmt_int32; float fmt_float; double fmt_double; } v; if (t != DOUBLE) { v.fmt_int32 = value; } else { DCHECK_EQ(sizeof(T), 8); v.fmt_double = value; } switch (t) { case WORD: SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRId64 ") int32:%" REGId_FORMAT " uint32:%" PRIu32, v.fmt_int32, icount_, v.fmt_int32, v.fmt_int32); break; case FLOAT: SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRId64 ") flt:%e", v.fmt_int32, icount_, v.fmt_float); break; case DOUBLE: SNPrintF(trace_buf_, "%016" PRIx64 " (%" PRId64 ") dbl:%e", static_cast(v.fmt_double), icount_, v.fmt_double); break; default: UNREACHABLE(); } } } # endif // The RiscvDebugger class is used by the simulator while debugging simulated // code. class RiscvDebugger { public: explicit RiscvDebugger(Simulator* sim) : sim_(sim) {} void Debug(); // Print all registers with a nice formatting. void PrintRegs(char name_prefix, int start_index, int end_index); void printAllRegs(); void printAllRegsIncludingFPU(); static const Instr kNopInstr = 0x0; private: Simulator* sim_; int64_t GetRegisterValue(int regnum); int64_t GetFPURegisterValue(int regnum); float GetFPURegisterValueFloat(int regnum); double GetFPURegisterValueDouble(int regnum); # ifdef CAN_USE_RVV_INSTRUCTIONS __int128_t GetVRegisterValue(int regnum); # endif bool GetValue(const char* desc, int64_t* value); }; int64_t RiscvDebugger::GetRegisterValue(int regnum) { if (regnum == Simulator::Register::kNumSimuRegisters) { return sim_->get_pc(); } else { return sim_->getRegister(regnum); } } int64_t RiscvDebugger::GetFPURegisterValue(int regnum) { if (regnum == Simulator::FPURegister::kNumFPURegisters) { return sim_->get_pc(); } else { return sim_->getFpuRegister(regnum); } } float RiscvDebugger::GetFPURegisterValueFloat(int regnum) { if (regnum == Simulator::FPURegister::kNumFPURegisters) { return sim_->get_pc(); } else { return sim_->getFpuRegisterFloat(regnum); } } double RiscvDebugger::GetFPURegisterValueDouble(int regnum) { if (regnum == Simulator::FPURegister::kNumFPURegisters) { return sim_->get_pc(); } else { return sim_->getFpuRegisterDouble(regnum); } } # ifdef CAN_USE_RVV_INSTRUCTIONS __int128_t RiscvDebugger::GetVRegisterValue(int regnum) { if (regnum == kNumVRegisters) { return sim_->get_pc(); } else { return sim_->get_vregister(regnum); } } # endif bool RiscvDebugger::GetValue(const char* desc, int64_t* value) { int regnum = Registers::FromName(desc); int fpuregnum = FloatRegisters::FromName(desc); if (regnum != Registers::invalid_reg) { *value = GetRegisterValue(regnum); return true; } else if (fpuregnum != FloatRegisters::invalid_reg) { *value = GetFPURegisterValue(fpuregnum); return true; } else if (strncmp(desc, "0x", 2) == 0) { return sscanf(desc + 2, "%" SCNx64, reinterpret_cast(value)) == 1; } else { return sscanf(desc, "%" SCNu64, reinterpret_cast(value)) == 1; } } # define REG_INFO(name) \ name, GetRegisterValue(Registers::FromName(name)), \ GetRegisterValue(Registers::FromName(name)) void RiscvDebugger::PrintRegs(char name_prefix, int start_index, int end_index) { EmbeddedVector name1, name2; MOZ_ASSERT(name_prefix == 'a' || name_prefix == 't' || name_prefix == 's'); MOZ_ASSERT(start_index >= 0 && end_index <= 99); int num_registers = (end_index - start_index) + 1; for (int i = 0; i < num_registers / 2; i++) { SNPrintF(name1, "%c%d", name_prefix, start_index + 2 * i); SNPrintF(name2, "%c%d", name_prefix, start_index + 2 * i + 1); printf("%3s: 0x%016" REGIx_FORMAT " %14" REGId_FORMAT " \t%3s: 0x%016" REGIx_FORMAT " %14" REGId_FORMAT " \n", REG_INFO(name1.start()), REG_INFO(name2.start())); } if (num_registers % 2 == 1) { SNPrintF(name1, "%c%d", name_prefix, end_index); printf("%3s: 0x%016" REGIx_FORMAT " %14" REGId_FORMAT " \n", REG_INFO(name1.start())); } } void RiscvDebugger::printAllRegs() { printf("\n"); // ra, sp, gp printf("%3s: 0x%016" REGIx_FORMAT " %14" REGId_FORMAT "\t%3s: 0x%016" REGIx_FORMAT " %14" REGId_FORMAT "\t%3s: 0x%016" REGIx_FORMAT " %14" REGId_FORMAT "\n", REG_INFO("ra"), REG_INFO("sp"), REG_INFO("gp")); // tp, fp, pc printf("%3s: 0x%016" REGIx_FORMAT " %14" REGId_FORMAT "\t%3s: 0x%016" REGIx_FORMAT " %14" REGId_FORMAT "\t%3s: 0x%016" REGIx_FORMAT " %14" REGId_FORMAT "\n", REG_INFO("tp"), REG_INFO("fp"), REG_INFO("pc")); // print register a0, .., a7 PrintRegs('a', 0, 7); // print registers s1, ..., s11 PrintRegs('s', 1, 11); // print registers t0, ..., t6 PrintRegs('t', 0, 6); } # undef REG_INFO void RiscvDebugger::printAllRegsIncludingFPU() { # define FPU_REG_INFO(n) \ FloatRegisters::GetName(n), GetFPURegisterValue(n), \ GetFPURegisterValueDouble(n) printAllRegs(); printf("\n\n"); // f0, f1, f2, ... f31. MOZ_ASSERT(kNumFPURegisters % 2 == 0); for (int i = 0; i < kNumFPURegisters; i += 2) printf("%3s: 0x%016" PRIx64 " %16.4e \t%3s: 0x%016" PRIx64 " %16.4e\n", FPU_REG_INFO(i), FPU_REG_INFO(i + 1)); # undef FPU_REG_INFO } void RiscvDebugger::Debug() { intptr_t last_pc = -1; bool done = false; # define COMMAND_SIZE 63 # define ARG_SIZE 255 # define STR(a) #a # define XSTR(a) STR(a) char cmd[COMMAND_SIZE + 1]; char arg1[ARG_SIZE + 1]; char arg2[ARG_SIZE + 1]; char* argv[3] = {cmd, arg1, arg2}; // Make sure to have a proper terminating character if reaching the limit. cmd[COMMAND_SIZE] = 0; arg1[ARG_SIZE] = 0; arg2[ARG_SIZE] = 0; while (!done && (sim_->get_pc() != Simulator::end_sim_pc)) { if (last_pc != sim_->get_pc()) { disasm::NameConverter converter; disasm::Disassembler dasm(converter); // Use a reasonably large buffer. EmbeddedVector buffer; dasm.InstructionDecode(buffer, reinterpret_cast(sim_->get_pc())); printf(" 0x%016" REGIx_FORMAT " %s\n", sim_->get_pc(), buffer.start()); last_pc = sim_->get_pc(); } char* line = ReadLine("sim> "); if (line == nullptr) { break; } else { char* last_input = sim_->lastDebuggerInput(); if (strcmp(line, "\n") == 0 && last_input != nullptr) { line = last_input; } else { // Ownership is transferred to sim_; sim_->setLastDebuggerInput(line); } // Use sscanf to parse the individual parts of the command line. At the // moment no command expects more than two parameters. int argc = sscanf( line, "%" XSTR(COMMAND_SIZE) "s " "%" XSTR(ARG_SIZE) "s " "%" XSTR(ARG_SIZE) "s", cmd, arg1, arg2); if ((strcmp(cmd, "si") == 0) || (strcmp(cmd, "stepi") == 0)) { SimInstruction* instr = reinterpret_cast(sim_->get_pc()); if (!(instr->IsTrap()) || instr->InstructionBits() == rtCallRedirInstr) { sim_->icount_++; sim_->InstructionDecode( reinterpret_cast(sim_->get_pc())); } else { // Allow si to jump over generated breakpoints. printf("/!\\ Jumping over generated breakpoint.\n"); sim_->set_pc(sim_->get_pc() + kInstrSize); } } else if ((strcmp(cmd, "c") == 0) || (strcmp(cmd, "cont") == 0)) { // Leave the debugger shell. done = true; } else if ((strcmp(cmd, "p") == 0) || (strcmp(cmd, "print") == 0)) { if (argc == 2) { int64_t value; int64_t fvalue; double dvalue; if (strcmp(arg1, "all") == 0) { printAllRegs(); } else if (strcmp(arg1, "allf") == 0) { printAllRegsIncludingFPU(); } else { int regnum = Registers::FromName(arg1); int fpuregnum = FloatRegisters::FromName(arg1); # ifdef CAN_USE_RVV_INSTRUCTIONS int vregnum = VRegisters::FromName(arg1); # endif if (regnum != Registers::invalid_reg) { value = GetRegisterValue(regnum); printf("%s: 0x%08" REGIx_FORMAT " %" REGId_FORMAT " \n", arg1, value, value); } else if (fpuregnum != FloatRegisters::invalid_reg) { fvalue = GetFPURegisterValue(fpuregnum); dvalue = GetFPURegisterValueDouble(fpuregnum); printf("%3s: 0x%016" PRIx64 " %16.4e\n", FloatRegisters::GetName(fpuregnum), fvalue, dvalue); # ifdef CAN_USE_RVV_INSTRUCTIONS } else if (vregnum != kInvalidVRegister) { __int128_t v = GetVRegisterValue(vregnum); printf("\t%s:0x%016" REGIx_FORMAT "%016" REGIx_FORMAT "\n", VRegisters::GetName(vregnum), (uint64_t)(v >> 64), (uint64_t)v); # endif } else { printf("%s unrecognized\n", arg1); } } } else { if (argc == 3) { if (strcmp(arg2, "single") == 0) { int64_t value; float fvalue; int fpuregnum = FloatRegisters::FromName(arg1); if (fpuregnum != FloatRegisters::invalid_reg) { value = GetFPURegisterValue(fpuregnum); value &= 0xFFFFFFFFUL; fvalue = GetFPURegisterValueFloat(fpuregnum); printf("%s: 0x%08" PRIx64 " %11.4e\n", arg1, value, fvalue); } else { printf("%s unrecognized\n", arg1); } } else { printf("print single\n"); } } else { printf("print or print single\n"); } } } else if ((strcmp(cmd, "po") == 0) || (strcmp(cmd, "printobject") == 0)) { UNIMPLEMENTED(); } else if (strcmp(cmd, "stack") == 0 || strcmp(cmd, "mem") == 0) { int64_t* cur = nullptr; int64_t* end = nullptr; int next_arg = 1; if (argc < 2) { printf("Need to specify
to memhex command\n"); continue; } int64_t value; if (!GetValue(arg1, &value)) { printf("%s unrecognized\n", arg1); continue; } cur = reinterpret_cast(value); next_arg++; int64_t words; if (argc == next_arg) { words = 10; } else { if (!GetValue(argv[next_arg], &words)) { words = 10; } } end = cur + words; while (cur < end) { printf(" 0x%012" PRIxPTR " : 0x%016" REGIx_FORMAT " %14" REGId_FORMAT " ", reinterpret_cast(cur), *cur, *cur); printf("\n"); cur++; } } else if ((strcmp(cmd, "watch") == 0)) { if (argc < 2) { printf("Need to specify
to mem command\n"); continue; } int64_t value; if (!GetValue(arg1, &value)) { printf("%s unrecognized\n", arg1); continue; } sim_->watch_address_ = reinterpret_cast(value); sim_->watch_value_ = *(sim_->watch_address_); } else if ((strcmp(cmd, "disasm") == 0) || (strcmp(cmd, "dpc") == 0) || (strcmp(cmd, "di") == 0)) { disasm::NameConverter converter; disasm::Disassembler dasm(converter); // Use a reasonably large buffer. EmbeddedVector buffer; byte* cur = nullptr; byte* end = nullptr; if (argc == 1) { cur = reinterpret_cast(sim_->get_pc()); end = cur + (10 * kInstrSize); } else if (argc == 2) { auto regnum = Registers::FromName(arg1); if (regnum != Registers::invalid_reg || strncmp(arg1, "0x", 2) == 0) { // The argument is an address or a register name. sreg_t value; if (GetValue(arg1, &value)) { cur = reinterpret_cast(value); // Disassemble 10 instructions at . end = cur + (10 * kInstrSize); } } else { // The argument is the number of instructions. sreg_t value; if (GetValue(arg1, &value)) { cur = reinterpret_cast(sim_->get_pc()); // Disassemble instructions. end = cur + (value * kInstrSize); } } } else { sreg_t value1; sreg_t value2; if (GetValue(arg1, &value1) && GetValue(arg2, &value2)) { cur = reinterpret_cast(value1); end = cur + (value2 * kInstrSize); } } while (cur < end) { dasm.InstructionDecode(buffer, cur); printf(" 0x%08" PRIxPTR " %s\n", reinterpret_cast(cur), buffer.start()); cur += kInstrSize; } } else if (strcmp(cmd, "trace") == 0) { Simulator::FLAG_trace_sim = true; Simulator::FLAG_riscv_print_watchpoint = true; } else if (strcmp(cmd, "break") == 0 || strcmp(cmd, "b") == 0 || strcmp(cmd, "tbreak") == 0) { bool is_tbreak = strcmp(cmd, "tbreak") == 0; if (argc == 2) { int64_t value; if (GetValue(arg1, &value)) { sim_->SetBreakpoint(reinterpret_cast(value), is_tbreak); } else { printf("%s unrecognized\n", arg1); } } else { sim_->ListBreakpoints(); printf("Use `break
` to set or disable a breakpoint\n"); printf( "Use `tbreak
` to set or disable a temporary " "breakpoint\n"); } } else if (strcmp(cmd, "flags") == 0) { printf("No flags on RISC-V !\n"); } else if (strcmp(cmd, "stop") == 0) { int64_t value; if (argc == 3) { // Print information about all/the specified breakpoint(s). if (strcmp(arg1, "info") == 0) { if (strcmp(arg2, "all") == 0) { printf("Stop information:\n"); for (uint32_t i = kMaxWatchpointCode + 1; i <= kMaxStopCode; i++) { sim_->printStopInfo(i); } } else if (GetValue(arg2, &value)) { sim_->printStopInfo(value); } else { printf("Unrecognized argument.\n"); } } else if (strcmp(arg1, "enable") == 0) { // Enable all/the specified breakpoint(s). if (strcmp(arg2, "all") == 0) { for (uint32_t i = kMaxWatchpointCode + 1; i <= kMaxStopCode; i++) { sim_->enableStop(i); } } else if (GetValue(arg2, &value)) { sim_->enableStop(value); } else { printf("Unrecognized argument.\n"); } } else if (strcmp(arg1, "disable") == 0) { // Disable all/the specified breakpoint(s). if (strcmp(arg2, "all") == 0) { for (uint32_t i = kMaxWatchpointCode + 1; i <= kMaxStopCode; i++) { sim_->disableStop(i); } } else if (GetValue(arg2, &value)) { sim_->disableStop(value); } else { printf("Unrecognized argument.\n"); } } } else { printf("Wrong usage. Use help command for more information.\n"); } } else if ((strcmp(cmd, "stat") == 0) || (strcmp(cmd, "st") == 0)) { UNIMPLEMENTED(); } else if ((strcmp(cmd, "h") == 0) || (strcmp(cmd, "help") == 0)) { printf("cont (alias 'c')\n"); printf(" Continue execution\n"); printf("stepi (alias 'si')\n"); printf(" Step one instruction\n"); printf("print (alias 'p')\n"); printf(" print \n"); printf(" Print register content\n"); printf(" Use register name 'all' to print all GPRs\n"); printf(" Use register name 'allf' to print all GPRs and FPRs\n"); printf("printobject (alias 'po')\n"); printf(" printobject \n"); printf(" Print an object from a register\n"); printf("stack\n"); printf(" stack []\n"); printf(" Dump stack content, default dump 10 words)\n"); printf("mem\n"); printf(" mem
[]\n"); printf(" Dump memory content, default dump 10 words)\n"); printf("watch\n"); printf(" watch
\n"); printf(" watch memory content.)\n"); printf("flags\n"); printf(" print flags\n"); printf("disasm (alias 'di')\n"); printf(" disasm []\n"); printf(" disasm [
] (e.g., disasm pc) \n"); printf(" disasm [[
] ]\n"); printf(" Disassemble code, default is 10 instructions\n"); printf(" from pc\n"); printf("gdb \n"); printf(" Return to gdb if the simulator was started with gdb\n"); printf("break (alias 'b')\n"); printf(" break : list all breakpoints\n"); printf(" break
: set / enable / disable a breakpoint.\n"); printf("tbreak\n"); printf(" tbreak : list all breakpoints\n"); printf( " tbreak
: set / enable / disable a temporary " "breakpoint.\n"); printf(" Set a breakpoint enabled only for one stop. \n"); printf("stop feature:\n"); printf(" Description:\n"); printf(" Stops are debug instructions inserted by\n"); printf(" the Assembler::stop() function.\n"); printf(" When hitting a stop, the Simulator will\n"); printf(" stop and give control to the Debugger.\n"); printf(" All stop codes are watched:\n"); printf(" - They can be enabled / disabled: the Simulator\n"); printf(" will / won't stop when hitting them.\n"); printf(" - The Simulator keeps track of how many times they \n"); printf(" are met. (See the info command.) Going over a\n"); printf(" disabled stop still increases its counter. \n"); printf(" Commands:\n"); printf(" stop info all/ : print infos about number \n"); printf(" or all stop(s).\n"); printf(" stop enable/disable all/ : enables / disables\n"); printf(" all or number stop(s)\n"); } else { printf("Unknown command: %s\n", cmd); } } } # undef COMMAND_SIZE # undef ARG_SIZE # undef STR # undef XSTR } void Simulator::SetBreakpoint(SimInstruction* location, bool is_tbreak) { for (unsigned i = 0; i < breakpoints_.size(); i++) { if (breakpoints_.at(i).location == location) { if (breakpoints_.at(i).is_tbreak != is_tbreak) { printf("Change breakpoint at %p to %s breakpoint\n", reinterpret_cast(location), is_tbreak ? "temporary" : "regular"); breakpoints_.at(i).is_tbreak = is_tbreak; return; } printf("Existing breakpoint at %p was %s\n", reinterpret_cast(location), breakpoints_.at(i).enabled ? "disabled" : "enabled"); breakpoints_.at(i).enabled = !breakpoints_.at(i).enabled; return; } } Breakpoint new_breakpoint = {location, true, is_tbreak}; breakpoints_.push_back(new_breakpoint); printf("Set a %sbreakpoint at %p\n", is_tbreak ? "temporary " : "", reinterpret_cast(location)); } void Simulator::ListBreakpoints() { printf("Breakpoints:\n"); for (unsigned i = 0; i < breakpoints_.size(); i++) { printf("%p : %s %s\n", reinterpret_cast(breakpoints_.at(i).location), breakpoints_.at(i).enabled ? "enabled" : "disabled", breakpoints_.at(i).is_tbreak ? ": temporary" : ""); } } void Simulator::CheckBreakpoints() { bool hit_a_breakpoint = false; bool is_tbreak = false; SimInstruction* pc_ = reinterpret_cast(get_pc()); for (unsigned i = 0; i < breakpoints_.size(); i++) { if ((breakpoints_.at(i).location == pc_) && breakpoints_.at(i).enabled) { hit_a_breakpoint = true; if (breakpoints_.at(i).is_tbreak) { // Disable a temporary breakpoint. is_tbreak = true; breakpoints_.at(i).enabled = false; } break; } } if (hit_a_breakpoint) { printf("Hit %sa breakpoint at %p.\n", is_tbreak ? "and disabled " : "", reinterpret_cast(pc_)); RiscvDebugger dbg(this); dbg.Debug(); } } static bool AllOnOnePage(uintptr_t start, int size) { intptr_t start_page = (start & ~CachePage::kPageMask); intptr_t end_page = ((start + size) & ~CachePage::kPageMask); return start_page == end_page; } void Simulator::setLastDebuggerInput(char* input) { js_free(lastDebuggerInput_); lastDebuggerInput_ = input; } static CachePage* GetCachePageLocked(SimulatorProcess::ICacheMap& i_cache, void* page) { SimulatorProcess::ICacheMap::AddPtr p = i_cache.lookupForAdd(page); if (p) { return p->value(); } AutoEnterOOMUnsafeRegion oomUnsafe; CachePage* new_page = js_new(); if (!new_page || !i_cache.add(p, page, new_page)) { oomUnsafe.crash("Simulator CachePage"); } return new_page; } // Flush from start up to and not including start + size. static void FlushOnePageLocked(SimulatorProcess::ICacheMap& i_cache, intptr_t start, int size) { MOZ_ASSERT(size <= CachePage::kPageSize); MOZ_ASSERT(AllOnOnePage(start, size - 1)); MOZ_ASSERT((start & CachePage::kLineMask) == 0); MOZ_ASSERT((size & CachePage::kLineMask) == 0); void* page = reinterpret_cast(start & (~CachePage::kPageMask)); int offset = (start & CachePage::kPageMask); CachePage* cache_page = GetCachePageLocked(i_cache, page); char* valid_bytemap = cache_page->validityByte(offset); memset(valid_bytemap, CachePage::LINE_INVALID, size >> CachePage::kLineShift); } static void FlushICacheLocked(SimulatorProcess::ICacheMap& i_cache, void* start_addr, size_t size) { intptr_t start = reinterpret_cast(start_addr); int intra_line = (start & CachePage::kLineMask); start -= intra_line; size += intra_line; size = ((size - 1) | CachePage::kLineMask) + 1; int offset = (start & CachePage::kPageMask); while (!AllOnOnePage(start, size - 1)) { int bytes_to_flush = CachePage::kPageSize - offset; FlushOnePageLocked(i_cache, start, bytes_to_flush); start += bytes_to_flush; size -= bytes_to_flush; MOZ_ASSERT((start & CachePage::kPageMask) == 0); offset = 0; } if (size != 0) { FlushOnePageLocked(i_cache, start, size); } } /* static */ void SimulatorProcess::checkICacheLocked(SimInstruction* instr) { intptr_t address = reinterpret_cast(instr); void* page = reinterpret_cast(address & (~CachePage::kPageMask)); void* line = reinterpret_cast(address & (~CachePage::kLineMask)); int offset = (address & CachePage::kPageMask); CachePage* cache_page = GetCachePageLocked(icache(), page); char* cache_valid_byte = cache_page->validityByte(offset); bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID); char* cached_line = cache_page->cachedData(offset & ~CachePage::kLineMask); if (cache_hit) { // Check that the data in memory matches the contents of the I-cache. int cmpret = memcmp(reinterpret_cast(instr), cache_page->cachedData(offset), kInstrSize); MOZ_ASSERT(cmpret == 0); } else { // Cache miss. Load memory into the cache. memcpy(cached_line, line, CachePage::kLineLength); *cache_valid_byte = CachePage::LINE_VALID; } } HashNumber SimulatorProcess::ICacheHasher::hash(const Lookup& l) { return U32(reinterpret_cast(l)) >> 2; } bool SimulatorProcess::ICacheHasher::match(const Key& k, const Lookup& l) { MOZ_ASSERT((reinterpret_cast(k) & CachePage::kPageMask) == 0); MOZ_ASSERT((reinterpret_cast(l) & CachePage::kPageMask) == 0); return k == l; } /* static */ void SimulatorProcess::FlushICache(void* start_addr, size_t size) { if (!ICacheCheckingDisableCount) { AutoLockSimulatorCache als; js::jit::FlushICacheLocked(icache(), start_addr, size); } } Simulator::Simulator() { // Set up simulator support first. Some of this information is needed to // setup the architecture state. // Note, allocation and anything that depends on allocated memory is // deferred until init(), in order to handle OOM properly. stack_ = nullptr; stackLimit_ = 0; pc_modified_ = false; icount_ = 0; break_count_ = 0; break_pc_ = nullptr; break_instr_ = 0; single_stepping_ = false; single_step_callback_ = nullptr; single_step_callback_arg_ = nullptr; // Set up architecture state. // All registers are initialized to zero to start with. for (int i = 0; i < Simulator::Register::kNumSimuRegisters; i++) { registers_[i] = 0; } for (int i = 0; i < Simulator::FPURegister::kNumFPURegisters; i++) { FPUregisters_[i] = 0; } FCSR_ = 0; LLBit_ = false; LLAddr_ = 0; lastLLValue_ = 0; // The ra and pc are initialized to a known bad value that will cause an // access violation if the simulator ever tries to execute it. registers_[pc] = bad_ra; registers_[ra] = bad_ra; for (int i = 0; i < kNumExceptions; i++) { exceptions[i] = 0; } lastDebuggerInput_ = nullptr; } bool Simulator::init() { // Allocate 2MB for the stack. Note that we will only use 1MB, see below. static const size_t stackSize = 2 * 1024 * 1024; stack_ = js_pod_malloc(stackSize); if (!stack_) { return false; } // Leave a safety margin of 1MB to prevent overrunning the stack when // pushing values (total stack size is 2MB). stackLimit_ = reinterpret_cast(stack_) + 1024 * 1024; // The sp is initialized to point to the bottom (high address) of the // allocated stack area. To be safe in potential stack underflows we leave // some buffer below. registers_[sp] = reinterpret_cast(stack_) + stackSize - 64; return true; } // 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 swi (software-interrupt) instruction that is handled by // the simulator. We write the original destination of the jump just at a known // offset from the swi instruction so the simulator knows what to call. class Redirection { friend class SimulatorProcess; // sim's lock must already be held. Redirection(void* nativeFunction, ABIFunctionType type) : nativeFunction_(nativeFunction), swiInstruction_(rtCallRedirInstr), type_(type), next_(nullptr) { next_ = SimulatorProcess::redirection(); if (!SimulatorProcess::ICacheCheckingDisableCount) { FlushICacheLocked(SimulatorProcess::icache(), addressOfSwiInstruction(), kInstrSize); } SimulatorProcess::setRedirection(this); } public: void* addressOfSwiInstruction() { return &swiInstruction_; } void* nativeFunction() const { return nativeFunction_; } ABIFunctionType type() const { return type_; } static Redirection* Get(void* nativeFunction, ABIFunctionType type) { AutoLockSimulatorCache als; Redirection* current = SimulatorProcess::redirection(); for (; current != nullptr; current = current->next_) { if (current->nativeFunction_ == nativeFunction) { MOZ_ASSERT(current->type() == type); return current; } } // Note: we can't use js_new here because the constructor is private. AutoEnterOOMUnsafeRegion oomUnsafe; Redirection* redir = js_pod_malloc(1); if (!redir) { oomUnsafe.crash("Simulator redirection"); } new (redir) Redirection(nativeFunction, type); return redir; } static Redirection* FromSwiInstruction(Instruction* swiInstruction) { uint8_t* addrOfSwi = reinterpret_cast(swiInstruction); uint8_t* addrOfRedirection = addrOfSwi - offsetof(Redirection, swiInstruction_); return reinterpret_cast(addrOfRedirection); } private: void* nativeFunction_; uint32_t swiInstruction_; ABIFunctionType type_; Redirection* next_; }; Simulator::~Simulator() { js_free(stack_); } SimulatorProcess::SimulatorProcess() : cacheLock_(mutexid::SimulatorCacheLock), redirection_(nullptr) { if (getenv("MIPS_SIM_ICACHE_CHECKS")) { ICacheCheckingDisableCount = 0; } } SimulatorProcess::~SimulatorProcess() { Redirection* r = redirection_; while (r) { Redirection* next = r->next_; js_delete(r); r = next; } } /* static */ void* Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type) { Redirection* redirection = Redirection::Get(nativeFunction, type); return redirection->addressOfSwiInstruction(); } // Get the active Simulator for the current thread. Simulator* Simulator::Current() { JSContext* cx = TlsContext.get(); MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); return cx->simulator(); } // Sets the register in the architecture state. It will also deal with updating // Simulator internal state for special registers such as PC. void Simulator::setRegister(int reg, int64_t value) { MOZ_ASSERT((reg >= 0) && (reg < Simulator::Register::kNumSimuRegisters)); if (reg == pc) { pc_modified_ = true; } // Zero register always holds 0. registers_[reg] = (reg == 0) ? 0 : value; } void Simulator::setFpuRegister(int fpureg, int64_t value) { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); FPUregisters_[fpureg] = value; } void Simulator::setFpuRegisterLo(int fpureg, int32_t value) { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); *mozilla::BitwiseCast(&FPUregisters_[fpureg]) = value; } void Simulator::setFpuRegisterHi(int fpureg, int32_t value) { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); *((mozilla::BitwiseCast(&FPUregisters_[fpureg])) + 1) = value; } void Simulator::setFpuRegisterFloat(int fpureg, float value) { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); *mozilla::BitwiseCast(&FPUregisters_[fpureg]) = box_float(value); } void Simulator::setFpuRegisterFloat(int fpureg, Float32 value) { MOZ_ASSERT((fpureg >= 0) && (fpureg < kNumFPURegisters)); Float64 t = Float64::FromBits(box_float(value.get_bits())); memcpy(&FPUregisters_[fpureg], &t, 8); } void Simulator::setFpuRegisterDouble(int fpureg, double value) { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); *mozilla::BitwiseCast(&FPUregisters_[fpureg]) = value; } void Simulator::setFpuRegisterDouble(int fpureg, Float64 value) { MOZ_ASSERT((fpureg >= 0) && (fpureg < kNumFPURegisters)); memcpy(&FPUregisters_[fpureg], &value, 8); } // Get the register from the architecture state. This function does handle // the special case of accessing the PC register. int64_t Simulator::getRegister(int reg) const { MOZ_ASSERT((reg >= 0) && (reg < Simulator::Register::kNumSimuRegisters)); if (reg == 0) { return 0; } return registers_[reg] + ((reg == pc) ? SimInstruction::kPCReadOffset : 0); } int64_t Simulator::getFpuRegister(int fpureg) const { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); return FPUregisters_[fpureg]; } int32_t Simulator::getFpuRegisterLo(int fpureg) const { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); return *mozilla::BitwiseCast(&FPUregisters_[fpureg]); } int32_t Simulator::getFpuRegisterHi(int fpureg) const { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); return *((mozilla::BitwiseCast(&FPUregisters_[fpureg])) + 1); } float Simulator::getFpuRegisterFloat(int fpureg) const { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); return *mozilla::BitwiseCast(&FPUregisters_[fpureg]); } Float32 Simulator::getFpuRegisterFloat32(int fpureg) const { MOZ_ASSERT((fpureg >= 0) && (fpureg < kNumFPURegisters)); if (!is_boxed_float(FPUregisters_[fpureg])) { return Float32::FromBits(0x7ffc0000); } return Float32::FromBits( *bit_cast(const_cast(&FPUregisters_[fpureg]))); } double Simulator::getFpuRegisterDouble(int fpureg) const { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); return *mozilla::BitwiseCast(&FPUregisters_[fpureg]); } Float64 Simulator::getFpuRegisterFloat64(int fpureg) const { MOZ_ASSERT((fpureg >= 0) && (fpureg < kNumFPURegisters)); return Float64::FromBits(FPUregisters_[fpureg]); } void Simulator::setCallResultDouble(double result) { setFpuRegisterDouble(fa0, result); } void Simulator::setCallResultFloat(float result) { setFpuRegisterFloat(fa0, result); } void Simulator::setCallResult(int64_t res) { setRegister(a0, res); } void Simulator::setCallResult(__int128_t res) { setRegister(a0, I64(res)); setRegister(a1, I64(res >> 64)); } // Raw access to the PC register. void Simulator::set_pc(int64_t value) { pc_modified_ = true; registers_[pc] = value; } bool Simulator::has_bad_pc() const { return ((registers_[pc] == bad_ra) || (registers_[pc] == end_sim_pc)); } // Raw access to the PC register without the special adjustment when reading. int64_t Simulator::get_pc() const { return registers_[pc]; } JS::ProfilingFrameIterator::RegisterState Simulator::registerState() { wasm::RegisterState state; state.pc = (void*)get_pc(); state.fp = (void*)getRegister(fp); state.sp = (void*)getRegister(sp); state.lr = (void*)getRegister(ra); return state; } // TODO(plind): consider making icount_ printing a flag option. template void Simulator::TraceMemRd(sreg_t addr, T value, sreg_t reg_value) { if (FLAG_trace_sim) { if (std::is_integral::value) { switch (sizeof(T)) { case 1: SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRId64 ") int8:%" PRId8 " uint8:%" PRIu8 " <-- [addr: %" REGIx_FORMAT "]", reg_value, icount_, static_cast(value), static_cast(value), addr); break; case 2: SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRId64 ") int16:%" PRId16 " uint16:%" PRIu16 " <-- [addr: %" REGIx_FORMAT "]", reg_value, icount_, static_cast(value), static_cast(value), addr); break; case 4: SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRId64 ") int32:%" PRId32 " uint32:%" PRIu32 " <-- [addr: %" REGIx_FORMAT "]", reg_value, icount_, static_cast(value), static_cast(value), addr); break; case 8: SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRId64 ") int64:%" PRId64 " uint64:%" PRIu64 " <-- [addr: %" REGIx_FORMAT "]", reg_value, icount_, static_cast(value), static_cast(value), addr); break; default: UNREACHABLE(); } } else if (std::is_same::value) { SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRId64 ") flt:%e <-- [addr: %" REGIx_FORMAT "]", reg_value, icount_, static_cast(value), addr); } else if (std::is_same::value) { SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRId64 ") dbl:%e <-- [addr: %" REGIx_FORMAT "]", reg_value, icount_, static_cast(value), addr); } else { UNREACHABLE(); } } } void Simulator::TraceMemRdFloat(sreg_t addr, Float32 value, int64_t reg_value) { if (FLAG_trace_sim) { SNPrintF(trace_buf_, "%016" PRIx64 " (%" PRId64 ") flt:%e <-- [addr: %" REGIx_FORMAT "]", reg_value, icount_, static_cast(value.get_scalar()), addr); } } void Simulator::TraceMemRdDouble(sreg_t addr, double value, int64_t reg_value) { if (FLAG_trace_sim) { SNPrintF(trace_buf_, "%016" PRIx64 " (%" PRId64 ") dbl:%e <-- [addr: %" REGIx_FORMAT "]", reg_value, icount_, static_cast(value), addr); } } void Simulator::TraceMemRdDouble(sreg_t addr, Float64 value, int64_t reg_value) { if (FLAG_trace_sim) { SNPrintF(trace_buf_, "%016" PRIx64 " (%" PRId64 ") dbl:%e <-- [addr: %" REGIx_FORMAT "]", reg_value, icount_, static_cast(value.get_scalar()), addr); } } template void Simulator::TraceMemWr(sreg_t addr, T value) { if (FLAG_trace_sim) { switch (sizeof(T)) { case 1: SNPrintF(trace_buf_, " (%" PRIu64 ") int8:%" PRId8 " uint8:%" PRIu8 " --> [addr: %" REGIx_FORMAT "]", icount_, static_cast(value), static_cast(value), addr); break; case 2: SNPrintF(trace_buf_, " (%" PRIu64 ") int16:%" PRId16 " uint16:%" PRIu16 " --> [addr: %" REGIx_FORMAT "]", icount_, static_cast(value), static_cast(value), addr); break; case 4: if (std::is_integral::value) { SNPrintF(trace_buf_, " (%" PRIu64 ") int32:%" PRId32 " uint32:%" PRIu32 " --> [addr: %" REGIx_FORMAT "]", icount_, static_cast(value), static_cast(value), addr); } else { SNPrintF(trace_buf_, " (%" PRIu64 ") flt:%e --> [addr: %" REGIx_FORMAT "]", icount_, static_cast(value), addr); } break; case 8: if (std::is_integral::value) { SNPrintF(trace_buf_, " (%" PRIu64 ") int64:%" PRId64 " uint64:%" PRIu64 " --> [addr: %" REGIx_FORMAT "]", icount_, static_cast(value), static_cast(value), addr); } else { SNPrintF(trace_buf_, " (%" PRIu64 ") dbl:%e --> [addr: %" REGIx_FORMAT "]", icount_, static_cast(value), addr); } break; default: UNREACHABLE(); } } } void Simulator::TraceMemWrDouble(sreg_t addr, double value) { if (FLAG_trace_sim) { SNPrintF(trace_buf_, " (%" PRIu64 ") dbl:%e --> [addr: %" REGIx_FORMAT "]", icount_, value, addr); } } template void Simulator::TraceLr(sreg_t addr, T value, sreg_t reg_value) { if (FLAG_trace_sim) { if (std::is_integral::value) { switch (sizeof(T)) { case 4: SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRId64 ") int32:%" PRId32 " uint32:%" PRIu32 " <-- [addr: %" REGIx_FORMAT "]", reg_value, icount_, static_cast(value), static_cast(value), addr); break; case 8: SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRId64 ") int64:%" PRId64 " uint64:%" PRIu64 " <-- [addr: %" REGIx_FORMAT "]", reg_value, icount_, static_cast(value), static_cast(value), addr); break; default: UNREACHABLE(); } } else { UNREACHABLE(); } } } template void Simulator::TraceSc(sreg_t addr, T value) { if (FLAG_trace_sim) { switch (sizeof(T)) { case 4: SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRIu64 ") int32:%" PRId32 " uint32:%" PRIu32 " --> [addr: %" REGIx_FORMAT "]", getRegister(rd_reg()), icount_, static_cast(value), static_cast(value), addr); break; case 8: SNPrintF(trace_buf_, "%016" REGIx_FORMAT " (%" PRIu64 ") int64:%" PRId64 " uint64:%" PRIu64 " --> [addr: %" REGIx_FORMAT "]", getRegister(rd_reg()), icount_, static_cast(value), static_cast(value), addr); break; default: UNREACHABLE(); } } } // TODO(RISCV): check whether the specific board supports unaligned load/store // (determined by EEI). For now, we assume the board does not support unaligned // load/store (e.g., trapping) template T Simulator::ReadMem(sreg_t addr, Instruction* instr) { if (handleWasmSegFault(addr, sizeof(T))) { return -1; } if (addr >= 0 && addr < 0x400) { // This has to be a nullptr-dereference, drop into debugger. printf("Memory read from bad address: 0x%08" REGIx_FORMAT " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } T* ptr = reinterpret_cast(addr); T value = *ptr; return value; } template void Simulator::WriteMem(sreg_t addr, T value, Instruction* instr) { if (handleWasmSegFault(addr, sizeof(T))) { value = -1; return; } if (addr >= 0 && addr < 0x400) { // This has to be a nullptr-dereference, drop into debugger. printf("Memory write to bad address: 0x%08" REGIx_FORMAT " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } T* ptr = reinterpret_cast(addr); if (!std::is_same::value) { TraceMemWr(addr, value); } else { TraceMemWrDouble(addr, value); } *ptr = value; } template <> void Simulator::WriteMem(sreg_t addr, Float32 value, Instruction* instr) { if (handleWasmSegFault(addr, 4)) { value = Float32(-1.0f); return; } if (addr >= 0 && addr < 0x400) { // This has to be a nullptr-dereference, drop into debugger. printf("Memory write to bad address: 0x%08" REGIx_FORMAT " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } float* ptr = reinterpret_cast(addr); TraceMemWr(addr, value.get_scalar()); memcpy(ptr, &value, 4); } template <> void Simulator::WriteMem(sreg_t addr, Float64 value, Instruction* instr) { if (handleWasmSegFault(addr, 8)) { value = Float64(-1.0); return; } if (addr >= 0 && addr < 0x400) { // This has to be a nullptr-dereference, drop into debugger. printf("Memory write to bad address: 0x%08" REGIx_FORMAT " , pc=0x%08" PRIxPTR " \n", addr, reinterpret_cast(instr)); DieOrDebug(); } double* ptr = reinterpret_cast(addr); TraceMemWrDouble(addr, value.get_scalar()); memcpy(ptr, &value, 8); } uintptr_t Simulator::stackLimit() const { return stackLimit_; } uintptr_t* Simulator::addressOfStackLimit() { return &stackLimit_; } bool Simulator::overRecursed(uintptr_t newsp) const { if (newsp == 0) { newsp = getRegister(sp); } return newsp <= stackLimit(); } bool Simulator::overRecursedWithExtra(uint32_t extra) const { uintptr_t newsp = getRegister(sp) - extra; return newsp <= stackLimit(); } // Unsupported instructions use format to print an error and stop execution. void Simulator::format(SimInstruction* instr, const char* format) { printf("Simulator found unsupported instruction:\n 0x%016lx: %s\n", reinterpret_cast(instr), format); MOZ_CRASH(); } // Note: With the code below we assume that all runtime calls return a 64 bits // result. If they don't, the v1 result register contains a bogus value, which // is fine because it is caller-saved. typedef int64_t (*Prototype_General0)(); typedef int64_t (*Prototype_General1)(int64_t arg0); typedef int64_t (*Prototype_General2)(int64_t arg0, int64_t arg1); typedef int64_t (*Prototype_General3)(int64_t arg0, int64_t arg1, int64_t arg2); typedef int64_t (*Prototype_General4)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3); typedef int64_t (*Prototype_General5)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4); typedef int64_t (*Prototype_General6)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4, int64_t arg5); typedef int64_t (*Prototype_General7)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4, int64_t arg5, int64_t arg6); typedef int64_t (*Prototype_General8)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, int64_t arg4, int64_t arg5, int64_t arg6, int64_t arg7); typedef int64_t (*Prototype_GeneralGeneralGeneralInt64)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3); typedef int64_t (*Prototype_GeneralGeneralInt64Int64)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3); typedef int64_t (*Prototype_Int_Double)(double arg0); typedef int64_t (*Prototype_Int_IntDouble)(int64_t arg0, double arg1); typedef int64_t (*Prototype_Int_DoubleInt)(double arg0, int64_t arg1); typedef int64_t (*Prototype_Int_DoubleIntInt)(double arg0, int64_t arg1, int64_t arg2); typedef int64_t (*Prototype_Int_IntDoubleIntInt)(int64_t arg0, double arg1, int64_t arg2, int64_t arg3); typedef float (*Prototype_Float32_Float32)(float arg0); typedef int64_t (*Prototype_Int_Float32)(float arg0); typedef float (*Prototype_Float32_Float32Float32)(float arg0, float arg1); typedef double (*Prototype_Double_None)(); typedef double (*Prototype_Double_Double)(double arg0); typedef double (*Prototype_Double_Int)(int64_t arg0); typedef double (*Prototype_Double_DoubleInt)(double arg0, int64_t arg1); typedef double (*Prototype_Double_IntDouble)(int64_t arg0, double arg1); typedef double (*Prototype_Double_DoubleDouble)(double arg0, double arg1); typedef double (*Prototype_Double_DoubleDoubleDouble)(double arg0, double arg1, double arg2); typedef double (*Prototype_Double_DoubleDoubleDoubleDouble)(double arg0, double arg1, double arg2, double arg3); typedef int32_t (*Prototype_Int32_General)(int64_t); typedef int32_t (*Prototype_Int32_GeneralInt32)(int64_t, int32_t); typedef int32_t (*Prototype_Int32_GeneralInt32Int32)(int64_t, int32_t, int32_t); typedef int32_t (*Prototype_Int32_GeneralInt32Int32Int32Int32)(int64_t, int32_t, int32_t, int32_t, int32_t); typedef int32_t (*Prototype_Int32_GeneralInt32Int32Int32Int32Int32)( int64_t, int32_t, int32_t, int32_t, int32_t, int32_t); typedef int32_t (*Prototype_Int32_GeneralInt32Int32Int32Int32General)( int64_t, int32_t, int32_t, int32_t, int32_t, int64_t); typedef int32_t (*Prototype_Int32_GeneralInt32Int32Int32General)( int64_t, int32_t, int32_t, int32_t, int64_t); typedef int32_t (*Prototype_Int32_GeneralInt32Int32Int64)(int64_t, int32_t, int32_t, int64_t); typedef int32_t (*Prototype_Int32_GeneralInt32Int32General)(int64_t, int32_t, int32_t, int64_t); typedef int32_t (*Prototype_Int32_GeneralInt32Int64Int64)(int64_t, int32_t, int64_t, int64_t); typedef int32_t (*Prototype_Int32_GeneralInt32GeneralInt32)(int64_t, int32_t, int64_t, int32_t); typedef int32_t (*Prototype_Int32_GeneralInt32GeneralInt32Int32)( int64_t, int32_t, int64_t, int32_t, int32_t); typedef int32_t (*Prototype_Int32_GeneralGeneral)(int64_t, int64_t); typedef int32_t (*Prototype_Int32_GeneralGeneralGeneral)(int64_t, int64_t, int64_t); typedef int32_t (*Prototype_Int32_GeneralGeneralInt32Int32)(int64_t, int64_t, int32_t, int32_t); typedef int32_t (*Prototype_Int32_GeneralInt64Int32Int32Int32)(int64_t, int64_t, int32_t, int32_t, int32_t); typedef int32_t (*Prototype_Int32_GeneralInt64Int32)(int64_t, int64_t, int32_t); typedef int32_t (*Prototype_Int32_GeneralGeneralInt32General)(int32_t, int32_t, int32_t, int32_t); typedef int32_t (*Prototype_Int32_GeneralInt64Int32Int64)(int64_t, int64_t, int32_t, int64_t); typedef int32_t (*Prototype_Int32_GeneralInt64Int32Int64General)( int64_t, int64_t, int32_t, int64_t, int64_t); typedef int32_t (*Prototype_Int32_GeneralInt64Int64Int64)(int64_t, int64_t, int64_t, int64_t); typedef int32_t (*Prototype_Int32_GeneralInt64Int64Int64General)( int64_t, int64_t, int64_t, int64_t, int64_t); typedef int64_t (*Prototype_General_GeneralInt32)(int64_t, int32_t); typedef int64_t (*Prototype_General_GeneralInt32Int32)(int64_t, int32_t, int32_t); typedef int64_t (*Prototype_General_GeneralInt32General)(int64_t, int32_t, int64_t); typedef int64_t (*Prototype_General_GeneralInt32Int32GeneralInt32)( int64_t, int32_t, int32_t, int64_t, int32_t); typedef int32_t (*Prototype_Int32_GeneralGeneralInt32GeneralInt32Int32Int32)( int64_t, int64_t, int32_t, int64_t, int32_t, int32_t, int32_t); typedef int64_t (*Prototype_Int64_General)(int64_t); typedef int64_t (*Prototype_Int64_GeneralInt64)(int64_t, int64_t); typedef int32_t (*Prototype_Int32_GeneralInt64Int64General)(int64_t, int64_t, int64_t, int64_t); // Generated by Assembler::break_()/stop(), ebreak code is passed as immediate // field of a subsequent LUI instruction; otherwise returns -1 static inline uint32_t get_ebreak_code(Instruction* instr) { MOZ_ASSERT(instr->InstructionBits() == kBreakInstr); uint8_t* cur = reinterpret_cast(instr); Instruction* next_instr = reinterpret_cast(cur + kInstrSize); if (next_instr->BaseOpcodeFieldRaw() == LUI) return (next_instr->Imm20UValue()); else return -1; } // Software interrupt instructions are used by the simulator to call into C++. void Simulator::SoftwareInterrupt() { // There are two instructions that could get us here, the ebreak or ecall // instructions are "SYSTEM" class opcode distinuished by Imm12Value field w/ // the rest of instruction fields being zero // We first check if we met a call_rt_redirected. if (instr_.InstructionBits() == rtCallRedirInstr) { Redirection* redirection = Redirection::FromSwiInstruction(instr_.instr()); uintptr_t nativeFn = reinterpret_cast(redirection->nativeFunction()); intptr_t arg0 = getRegister(a0); intptr_t arg1 = getRegister(a1); intptr_t arg2 = getRegister(a2); intptr_t arg3 = getRegister(a3); intptr_t arg4 = getRegister(a4); intptr_t arg5 = getRegister(a5); intptr_t arg6 = getRegister(a6); intptr_t arg7 = getRegister(a7); // This is dodgy but it works because the C entry stubs are never moved. // See comment in codegen-arm.cc and bug 1242173. intptr_t saved_ra = getRegister(ra); intptr_t external = reinterpret_cast(redirection->nativeFunction()); bool stack_aligned = (getRegister(sp) & (ABIStackAlignment - 1)) == 0; if (!stack_aligned) { fprintf(stderr, "Runtime call with unaligned stack!\n"); MOZ_CRASH(); } if (single_stepping_) { single_step_callback_(single_step_callback_arg_, this, nullptr); } if (FLAG_trace_sim) { printf( "Call to host function at %p with args %ld, %ld, %ld, %ld, %ld, %ld, " "%ld, %ld\n", reinterpret_cast(external), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); } switch (redirection->type()) { case Args_General0: { Prototype_General0 target = reinterpret_cast(external); int64_t result = target(); if (FLAG_trace_sim) printf("ret %ld\n", result); setCallResult(result); break; } case Args_General1: { Prototype_General1 target = reinterpret_cast(external); int64_t result = target(arg0); if (FLAG_trace_sim) printf("ret %ld\n", result); setCallResult(result); break; } case Args_General2: { Prototype_General2 target = reinterpret_cast(external); int64_t result = target(arg0, arg1); if (FLAG_trace_sim) printf("ret %ld\n", result); setCallResult(result); break; } case Args_General3: { Prototype_General3 target = reinterpret_cast(external); int64_t result = target(arg0, arg1, arg2); if (FLAG_trace_sim) printf("ret %ld\n", result); if (external == intptr_t(&js::wasm::Instance::wake_m32)) { result = int32_t(result); } setCallResult(result); break; } case Args_General4: { Prototype_General4 target = reinterpret_cast(external); int64_t result = target(arg0, arg1, arg2, arg3); if (FLAG_trace_sim) printf("ret %ld\n", result); setCallResult(result); break; } case Args_General5: { Prototype_General5 target = reinterpret_cast(external); int64_t result = target(arg0, arg1, arg2, arg3, arg4); if (FLAG_trace_sim) printf("ret %ld\n", result); setCallResult(result); break; } case Args_General6: { Prototype_General6 target = reinterpret_cast(external); int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5); if (FLAG_trace_sim) printf("ret %ld\n", result); setCallResult(result); break; } case Args_General7: { Prototype_General7 target = reinterpret_cast(external); int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5, arg6); if (FLAG_trace_sim) printf("ret %ld\n", result); setCallResult(result); break; } case Args_General8: { Prototype_General8 target = reinterpret_cast(external); int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); if (FLAG_trace_sim) printf("ret %ld\n", result); setCallResult(result); break; } case Args_Double_None: { Prototype_Double_None target = reinterpret_cast(external); double dresult = target(); if (FLAG_trace_sim) printf("ret %f\n", dresult); setCallResultDouble(dresult); break; } case Args_Int_Double: { double dval0 = getFpuRegisterDouble(fa0); Prototype_Int_Double target = reinterpret_cast(external); int64_t result = target(dval0); if (FLAG_trace_sim) printf("ret %ld\n", result); if (external == intptr_t((int32_t(*)(double))JS::ToInt32)) { result = int32_t(result); } setRegister(a0, result); break; } case Args_Int_GeneralGeneralGeneralInt64: { Prototype_GeneralGeneralGeneralInt64 target = reinterpret_cast(external); int64_t result = target(arg0, arg1, arg2, arg3); if (FLAG_trace_sim) printf("ret %ld\n", result); if (external == intptr_t(&js::wasm::Instance::wait_i32_m32)) { result = int32_t(result); } setRegister(a0, result); break; } case Args_Int_GeneralGeneralInt64Int64: { Prototype_GeneralGeneralInt64Int64 target = reinterpret_cast(external); int64_t result = target(arg0, arg1, arg2, arg3); if (FLAG_trace_sim) printf("ret %ld\n", result); if (external == intptr_t(&js::wasm::Instance::wait_i64_m32)) { result = int32_t(result); } setRegister(a0, result); break; } case Args_Int_DoubleInt: { double dval = getFpuRegisterDouble(fa0); Prototype_Int_DoubleInt target = reinterpret_cast(external); int64_t result = target(dval, arg0); if (FLAG_trace_sim) printf("ret %ld\n", result); setRegister(a0, result); break; } case Args_Int_DoubleIntInt: { double dval = getFpuRegisterDouble(fa0); Prototype_Int_DoubleIntInt target = reinterpret_cast(external); int64_t result = target(dval, arg1, arg2); if (FLAG_trace_sim) printf("ret %ld\n", result); setRegister(a0, result); break; } case Args_Int_IntDoubleIntInt: { double dval = getFpuRegisterDouble(fa0); Prototype_Int_IntDoubleIntInt target = reinterpret_cast(external); int64_t result = target(arg0, dval, arg2, arg3); if (FLAG_trace_sim) printf("ret %ld\n", result); setRegister(a0, result); break; } case Args_Double_Double: { double dval0 = getFpuRegisterDouble(fa0); Prototype_Double_Double target = reinterpret_cast(external); double dresult = target(dval0); if (FLAG_trace_sim) printf("ret %f\n", dresult); setCallResultDouble(dresult); break; } case Args_Float32_Float32: { float fval0; fval0 = getFpuRegisterFloat(fa0); Prototype_Float32_Float32 target = reinterpret_cast(external); float fresult = target(fval0); if (FLAG_trace_sim) printf("ret %f\n", fresult); setCallResultFloat(fresult); break; } case Args_Int_Float32: { float fval0; fval0 = getFpuRegisterFloat(fa0); Prototype_Int_Float32 target = reinterpret_cast(external); int64_t result = target(fval0); if (FLAG_trace_sim) printf("ret %ld\n", result); setRegister(a0, result); break; } case Args_Float32_Float32Float32: { float fval0; float fval1; fval0 = getFpuRegisterFloat(fa0); fval1 = getFpuRegisterFloat(fa1); Prototype_Float32_Float32Float32 target = reinterpret_cast(external); float fresult = target(fval0, fval1); if (FLAG_trace_sim) printf("ret %f\n", fresult); setCallResultFloat(fresult); break; } case Args_Double_Int: { Prototype_Double_Int target = reinterpret_cast(external); double dresult = target(arg0); if (FLAG_trace_sim) printf("ret %f\n", dresult); setCallResultDouble(dresult); break; } case Args_Double_DoubleInt: { double dval0 = getFpuRegisterDouble(fa0); Prototype_Double_DoubleInt target = reinterpret_cast(external); double dresult = target(dval0, arg0); if (FLAG_trace_sim) printf("ret %f\n", dresult); setCallResultDouble(dresult); break; } case Args_Double_DoubleDouble: { double dval0 = getFpuRegisterDouble(fa0); double dval1 = getFpuRegisterDouble(fa1); Prototype_Double_DoubleDouble target = reinterpret_cast(external); double dresult = target(dval0, dval1); if (FLAG_trace_sim) printf("ret %f\n", dresult); setCallResultDouble(dresult); break; } case Args_Double_IntDouble: { double dval0 = getFpuRegisterDouble(fa0); Prototype_Double_IntDouble target = reinterpret_cast(external); double dresult = target(arg0, dval0); if (FLAG_trace_sim) printf("ret %f\n", dresult); setCallResultDouble(dresult); break; } case Args_Int_IntDouble: { double dval0 = getFpuRegisterDouble(fa0); Prototype_Int_IntDouble target = reinterpret_cast(external); int64_t result = target(arg0, dval0); if (FLAG_trace_sim) printf("ret %ld\n", result); setRegister(a0, result); break; } case Args_Double_DoubleDoubleDouble: { double dval0 = getFpuRegisterDouble(fa0); double dval1 = getFpuRegisterDouble(fa1); double dval2 = getFpuRegisterDouble(fa2); Prototype_Double_DoubleDoubleDouble target = reinterpret_cast(external); double dresult = target(dval0, dval1, dval2); if (FLAG_trace_sim) printf("ret %f\n", dresult); setCallResultDouble(dresult); break; } case Args_Double_DoubleDoubleDoubleDouble: { double dval0 = getFpuRegisterDouble(fa0); double dval1 = getFpuRegisterDouble(fa1); double dval2 = getFpuRegisterDouble(fa2); double dval3 = getFpuRegisterDouble(fa3); Prototype_Double_DoubleDoubleDoubleDouble target = reinterpret_cast( external); double dresult = target(dval0, dval1, dval2, dval3); if (FLAG_trace_sim) printf("ret %f\n", dresult); setCallResultDouble(dresult); break; } case Args_Int32_General: { int32_t ret = reinterpret_cast(nativeFn)(arg0); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_Int32_GeneralInt32: { int32_t ret = reinterpret_cast(nativeFn)( arg0, I32(arg1)); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_Int32_GeneralInt32Int32: { int32_t ret = reinterpret_cast( nativeFn)(arg0, I32(arg1), I32(arg2)); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_Int32_GeneralInt32Int32Int32Int32: { int32_t ret = reinterpret_cast( nativeFn)(arg0, I32(arg1), I32(arg2), I32(arg3), I32(arg4)); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_Int32_GeneralInt32Int32Int32Int32Int32: { int32_t ret = reinterpret_cast( nativeFn)(arg0, I32(arg1), I32(arg2), I32(arg3), I32(arg4), I32(arg5)); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_Int32_GeneralInt32Int32Int32Int32General: { int32_t ret = reinterpret_cast< Prototype_Int32_GeneralInt32Int32Int32Int32General>(nativeFn)( arg0, I32(arg1), I32(arg2), I32(arg3), I32(arg4), arg5); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_Int32_GeneralInt32Int32Int32General: { int32_t ret = reinterpret_cast( nativeFn)(arg0, I32(arg1), I32(arg2), I32(arg3), arg4); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_Int32_GeneralInt32Int32Int64: { int32_t ret = reinterpret_cast( nativeFn)(arg0, I32(arg1), I32(arg2), arg3); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_Int32_GeneralInt32Int32General: { int32_t ret = reinterpret_cast( nativeFn)(arg0, I32(arg1), I32(arg2), arg3); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_Int32_GeneralInt32Int64Int64: { int32_t ret = reinterpret_cast( nativeFn)(arg0, I32(arg1), arg2, arg3); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_Int32_GeneralInt32GeneralInt32: { int32_t ret = reinterpret_cast( nativeFn)(arg0, I32(arg1), arg2, I32(arg3)); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_Int32_GeneralInt32GeneralInt32Int32: { int32_t ret = reinterpret_cast( nativeFn)(arg0, I32(arg1), arg2, I32(arg3), I32(arg4)); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_Int32_GeneralGeneral: { int32_t ret = reinterpret_cast( nativeFn)(arg0, arg1); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_Int32_GeneralGeneralGeneral: { int32_t ret = reinterpret_cast( nativeFn)(arg0, arg1, arg2); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_Int32_GeneralGeneralInt32Int32: { int32_t ret = reinterpret_cast( nativeFn)(arg0, arg1, I32(arg2), I32(arg3)); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case js::jit::Args_Int32_GeneralInt64Int32Int32Int32: { int32_t ret = reinterpret_cast( nativeFn)(arg0, arg1, I32(arg2), I32(arg3), I32(arg4)); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case js::jit::Args_Int32_GeneralInt64Int32: { int32_t ret = reinterpret_cast( nativeFn)(arg0, arg1, I32(arg2)); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case js::jit::Args_Int32_GeneralInt64Int32Int64: { int32_t ret = reinterpret_cast( nativeFn)(arg0, arg1, I32(arg2), arg3); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case js::jit::Args_Int32_GeneralInt64Int32Int64General: { int32_t ret = reinterpret_cast( nativeFn)(arg0, arg1, I32(arg2), arg3, arg4); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case js::jit::Args_Int32_GeneralInt64Int64Int64: { int32_t ret = reinterpret_cast( nativeFn)(arg0, arg1, arg2, arg3); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case js::jit::Args_Int32_GeneralInt64Int64Int64General: { int32_t ret = reinterpret_cast( nativeFn)(arg0, arg1, arg2, arg3, arg4); if (FLAG_trace_sim) printf("ret %d\n", ret); setRegister(a0, I64(ret)); break; } case Args_General_GeneralInt32: { int64_t ret = reinterpret_cast( nativeFn)(arg0, I32(arg1)); if (FLAG_trace_sim) printf("ret %ld\n", ret); setRegister(a0, ret); break; } case Args_General_GeneralInt32Int32: { int64_t ret = reinterpret_cast( nativeFn)(arg0, I32(arg1), I32(arg2)); if (FLAG_trace_sim) printf("ret %ld\n", ret); setRegister(a0, ret); break; } case Args_General_GeneralInt32General: { int64_t ret = reinterpret_cast( nativeFn)(arg0, I32(arg1), arg2); if (FLAG_trace_sim) printf("ret %ld\n", ret); setRegister(a0, ret); break; } case js::jit::Args_General_GeneralInt32Int32GeneralInt32: { int64_t ret = reinterpret_cast( nativeFn)(arg0, I32(arg1), I32(arg2), arg3, I32(arg4)); setRegister(a0, ret); break; } case js::jit::Args_Int32_GeneralGeneralInt32General: { Prototype_Int32_GeneralGeneralInt32General target = reinterpret_cast( external); int64_t result = target(I32(arg0), I32(arg1), I32(arg2), I32(arg3)); setRegister(a0, I64(result)); break; } case js::jit::Args_Int32_GeneralGeneralInt32GeneralInt32Int32Int32: { int64_t arg6 = getRegister(a6); int32_t ret = reinterpret_cast< Prototype_Int32_GeneralGeneralInt32GeneralInt32Int32Int32>( nativeFn)(arg0, arg1, I32(arg2), arg3, I32(arg4), I32(arg5), I32(arg6)); setRegister(a0, I64(ret)); break; } case js::jit::Args_Int64_General: { int64_t ret = reinterpret_cast(nativeFn)(arg0); if (FLAG_trace_sim) printf("ret %ld\n", ret); setRegister(a0, ret); break; } case js::jit::Args_Int64_GeneralInt64: { int64_t ret = reinterpret_cast(nativeFn)( arg0, arg1); if (FLAG_trace_sim) printf("ret %ld\n", ret); setRegister(a0, ret); break; } case js::jit::Args_Int32_GeneralInt64Int64General: { int32_t ret = reinterpret_cast( nativeFn)(arg0, arg1, arg2, arg3); setRegister(a0, I64(ret)); break; } default: MOZ_CRASH("Unknown function type."); } if (single_stepping_) { single_step_callback_(single_step_callback_arg_, this, nullptr); } setRegister(ra, saved_ra); set_pc(getRegister(ra)); } else if (instr_.InstructionBits() == kBreakInstr && (get_ebreak_code(instr_.instr()) <= kMaxStopCode)) { uint32_t code = get_ebreak_code(instr_.instr()); if (isWatchpoint(code)) { printWatchpoint(code); } else if (IsTracepoint(code)) { if (!FLAG_debug_sim) { MOZ_CRASH("Add --debug-sim when tracepoint instruction is used.\n"); } // printf("%d %d %d %d %d %d %d\n", code, code & LOG_TRACE, code & // LOG_REGS, // code & kDebuggerTracingDirectivesMask, TRACE_ENABLE, // TRACE_DISABLE, kDebuggerTracingDirectivesMask); switch (code & kDebuggerTracingDirectivesMask) { case TRACE_ENABLE: if (code & LOG_TRACE) { FLAG_trace_sim = true; } if (code & LOG_REGS) { RiscvDebugger dbg(this); dbg.printAllRegs(); } break; case TRACE_DISABLE: if (code & LOG_TRACE) { FLAG_trace_sim = false; } break; default: UNREACHABLE(); } } else { increaseStopCounter(code); handleStop(code); } } else { // uint8_t code = get_ebreak_code(instr_.instr()) - kMaxStopCode - 1; // switch (LNode::Opcode(code)) { // #define EMIT_OP(OP, ...) \ // case LNode::Opcode::OP:\ // std::cout << #OP << std::endl; \ // break; // LIR_OPCODE_LIST(EMIT_OP); // #undef EMIT_OP // } DieOrDebug(); } } // Stop helper functions. bool Simulator::isWatchpoint(uint32_t code) { return (code <= kMaxWatchpointCode); } bool Simulator::IsTracepoint(uint32_t code) { return (code <= kMaxTracepointCode && code > kMaxWatchpointCode); } void Simulator::printWatchpoint(uint32_t code) { RiscvDebugger dbg(this); ++break_count_; if (FLAG_riscv_print_watchpoint) { printf("\n---- break %d marker: %20" PRIi64 " (instr count: %20" PRIi64 ") ----\n", code, break_count_, icount_); dbg.printAllRegs(); // Print registers and continue running. } } void Simulator::handleStop(uint32_t code) { // Stop if it is enabled, otherwise go on jumping over the stop // and the message address. if (isEnabledStop(code)) { RiscvDebugger dbg(this); dbg.Debug(); } else { set_pc(get_pc() + 2 * kInstrSize); } } bool Simulator::isStopInstruction(SimInstruction* instr) { if (instr->InstructionBits() != kBreakInstr) return false; int32_t code = get_ebreak_code(instr->instr()); return code != -1 && static_cast(code) > kMaxWatchpointCode && static_cast(code) <= kMaxStopCode; } bool Simulator::isEnabledStop(uint32_t code) { MOZ_ASSERT(code <= kMaxStopCode); MOZ_ASSERT(code > kMaxWatchpointCode); return !(watchedStops_[code].count_ & kStopDisabledBit); } void Simulator::enableStop(uint32_t code) { if (!isEnabledStop(code)) { watchedStops_[code].count_ &= ~kStopDisabledBit; } } void Simulator::disableStop(uint32_t code) { if (isEnabledStop(code)) { watchedStops_[code].count_ |= kStopDisabledBit; } } void Simulator::increaseStopCounter(uint32_t code) { MOZ_ASSERT(code <= kMaxStopCode); if ((watchedStops_[code].count_ & ~(1 << 31)) == 0x7fffffff) { printf( "Stop counter for code %i has overflowed.\n" "Enabling this code and reseting the counter to 0.\n", code); watchedStops_[code].count_ = 0; enableStop(code); } else { watchedStops_[code].count_++; } } // Print a stop status. void Simulator::printStopInfo(uint32_t code) { if (code <= kMaxWatchpointCode) { printf("That is a watchpoint, not a stop.\n"); return; } else if (code > kMaxStopCode) { printf("Code too large, only %u stops can be used\n", kMaxStopCode + 1); return; } const char* state = isEnabledStop(code) ? "Enabled" : "Disabled"; int32_t count = watchedStops_[code].count_ & ~kStopDisabledBit; // Don't print the state of unused breakpoints. if (count != 0) { if (watchedStops_[code].desc_) { printf("stop %i - 0x%x: \t%s, \tcounter = %i, \t%s\n", code, code, state, count, watchedStops_[code].desc_); } else { printf("stop %i - 0x%x: \t%s, \tcounter = %i\n", code, code, state, count); } } } void Simulator::SignalException(Exception e) { printf("Error: Exception %i raised.", static_cast(e)); MOZ_CRASH(); } // TODO(plind): refactor this messy debug code when we do unaligned access. void Simulator::DieOrDebug() { if (FLAG_riscv_trap_to_simulator_debugger) { RiscvDebugger dbg(this); dbg.Debug(); } else { MOZ_CRASH("Die"); } } // Executes the current instruction. void Simulator::InstructionDecode(Instruction* instr) { // if (FLAG_check_icache) { // CheckICache(SimulatorProcess::icache(), instr); // } pc_modified_ = false; EmbeddedVector buffer; if (FLAG_trace_sim || FLAG_debug_sim) { SNPrintF(trace_buf_, " "); disasm::NameConverter converter; disasm::Disassembler dasm(converter); // Use a reasonably large buffer. dasm.InstructionDecode(buffer, reinterpret_cast(instr)); // printf("EXECUTING 0x%08" PRIxPTR " %-44s\n", // reinterpret_cast(instr), buffer.begin()); } instr_ = instr; switch (instr_.InstructionType()) { case Instruction::kRType: DecodeRVRType(); break; case Instruction::kR4Type: DecodeRVR4Type(); break; case Instruction::kIType: DecodeRVIType(); break; case Instruction::kSType: DecodeRVSType(); break; case Instruction::kBType: DecodeRVBType(); break; case Instruction::kUType: DecodeRVUType(); break; case Instruction::kJType: DecodeRVJType(); break; case Instruction::kCRType: DecodeCRType(); break; case Instruction::kCAType: DecodeCAType(); break; case Instruction::kCJType: DecodeCJType(); break; case Instruction::kCBType: DecodeCBType(); break; case Instruction::kCIType: DecodeCIType(); break; case Instruction::kCIWType: DecodeCIWType(); break; case Instruction::kCSSType: DecodeCSSType(); break; case Instruction::kCLType: DecodeCLType(); break; case Instruction::kCSType: DecodeCSType(); break; # ifdef CAN_USE_RVV_INSTRUCTIONS case Instruction::kVType: DecodeVType(); break; # endif default: UNSUPPORTED(); } if (FLAG_trace_sim) { printf(" 0x%012" PRIxPTR " %-44s\t%s\n", reinterpret_cast(instr), buffer.start(), trace_buf_.start()); } if (!pc_modified_) { setRegister(pc, reinterpret_cast(instr) + instr->InstructionSize()); } if (watch_address_ != nullptr) { printf(" 0x%012" PRIxPTR " : 0x%016" REGIx_FORMAT " %14" REGId_FORMAT " \n", reinterpret_cast(watch_address_), *watch_address_, *watch_address_); if (watch_value_ != *watch_address_) { RiscvDebugger dbg(this); dbg.Debug(); watch_value_ = *watch_address_; } } } void Simulator::enable_single_stepping(SingleStepCallback cb, void* arg) { single_stepping_ = true; single_step_callback_ = cb; single_step_callback_arg_ = arg; single_step_callback_(single_step_callback_arg_, this, (void*)get_pc()); } void Simulator::disable_single_stepping() { if (!single_stepping_) { return; } single_step_callback_(single_step_callback_arg_, this, (void*)get_pc()); single_stepping_ = false; single_step_callback_ = nullptr; single_step_callback_arg_ = nullptr; } template void Simulator::execute() { if (single_stepping_) { single_step_callback_(single_step_callback_arg_, this, nullptr); } // Get the PC to simulate. Cannot use the accessor here as we need the // raw PC value and not the one used as input to arithmetic instructions. int64_t program_counter = get_pc(); while (program_counter != end_sim_pc) { if (enableStopSimAt && (icount_ == Simulator::StopSimAt)) { RiscvDebugger dbg(this); dbg.Debug(); } if (single_stepping_) { single_step_callback_(single_step_callback_arg_, this, (void*)program_counter); } Instruction* instr = reinterpret_cast(program_counter); InstructionDecode(instr); icount_++; program_counter = get_pc(); } if (single_stepping_) { single_step_callback_(single_step_callback_arg_, this, nullptr); } } // RISCV Instruction Decode Routine void Simulator::DecodeRVRType() { switch (instr_.InstructionBits() & kRTypeMask) { case RO_ADD: { set_rd(sext_xlen(rs1() + rs2())); break; } case RO_SUB: { set_rd(sext_xlen(rs1() - rs2())); break; } case RO_SLL: { set_rd(sext_xlen(rs1() << (rs2() & (xlen - 1)))); break; } case RO_SLT: { set_rd(sreg_t(rs1()) < sreg_t(rs2())); break; } case RO_SLTU: { set_rd(reg_t(rs1()) < reg_t(rs2())); break; } case RO_XOR: { set_rd(rs1() ^ rs2()); break; } case RO_SRL: { set_rd(sext_xlen(zext_xlen(rs1()) >> (rs2() & (xlen - 1)))); break; } case RO_SRA: { set_rd(sext_xlen(sext_xlen(rs1()) >> (rs2() & (xlen - 1)))); break; } case RO_OR: { set_rd(rs1() | rs2()); break; } case RO_AND: { set_rd(rs1() & rs2()); break; } # ifdef JS_CODEGEN_RISCV64 case RO_ADDW: { set_rd(sext32(rs1() + rs2())); break; } case RO_SUBW: { set_rd(sext32(rs1() - rs2())); break; } case RO_SLLW: { set_rd(sext32(rs1() << (rs2() & 0x1F))); break; } case RO_SRLW: { set_rd(sext32(uint32_t(rs1()) >> (rs2() & 0x1F))); break; } case RO_SRAW: { set_rd(sext32(int32_t(rs1()) >> (rs2() & 0x1F))); break; } # endif /* JS_CODEGEN_RISCV64 */ // TODO(riscv): Add RISCV M extension macro case RO_MUL: { set_rd(rs1() * rs2()); break; } case RO_MULH: { set_rd(mulh(rs1(), rs2())); break; } case RO_MULHSU: { set_rd(mulhsu(rs1(), rs2())); break; } case RO_MULHU: { set_rd(mulhu(rs1(), rs2())); break; } case RO_DIV: { sreg_t lhs = sext_xlen(rs1()); sreg_t rhs = sext_xlen(rs2()); if (rhs == 0) { set_rd(-1); } else if (lhs == INTPTR_MIN && rhs == -1) { set_rd(lhs); } else { set_rd(sext_xlen(lhs / rhs)); } break; } case RO_DIVU: { reg_t lhs = zext_xlen(rs1()); reg_t rhs = zext_xlen(rs2()); if (rhs == 0) { set_rd(UINTPTR_MAX); } else { set_rd(zext_xlen(lhs / rhs)); } break; } case RO_REM: { sreg_t lhs = sext_xlen(rs1()); sreg_t rhs = sext_xlen(rs2()); if (rhs == 0) { set_rd(lhs); } else if (lhs == INTPTR_MIN && rhs == -1) { set_rd(0); } else { set_rd(sext_xlen(lhs % rhs)); } break; } case RO_REMU: { reg_t lhs = zext_xlen(rs1()); reg_t rhs = zext_xlen(rs2()); if (rhs == 0) { set_rd(lhs); } else { set_rd(zext_xlen(lhs % rhs)); } break; } # ifdef JS_CODEGEN_RISCV64 case RO_MULW: { set_rd(sext32(sext32(rs1()) * sext32(rs2()))); break; } case RO_DIVW: { sreg_t lhs = sext32(rs1()); sreg_t rhs = sext32(rs2()); if (rhs == 0) { set_rd(-1); } else if (lhs == INT32_MIN && rhs == -1) { set_rd(lhs); } else { set_rd(sext32(lhs / rhs)); } break; } case RO_DIVUW: { reg_t lhs = zext32(rs1()); reg_t rhs = zext32(rs2()); if (rhs == 0) { set_rd(UINT32_MAX); } else { set_rd(zext32(lhs / rhs)); } break; } case RO_REMW: { sreg_t lhs = sext32(rs1()); sreg_t rhs = sext32(rs2()); if (rhs == 0) { set_rd(lhs); } else if (lhs == INT32_MIN && rhs == -1) { set_rd(0); } else { set_rd(sext32(lhs % rhs)); } break; } case RO_REMUW: { reg_t lhs = zext32(rs1()); reg_t rhs = zext32(rs2()); if (rhs == 0) { set_rd(zext32(lhs)); } else { set_rd(zext32(lhs % rhs)); } break; } # endif /*JS_CODEGEN_RISCV64*/ // TODO(riscv): End Add RISCV M extension macro default: { switch (instr_.BaseOpcode()) { case AMO: DecodeRVRAType(); break; case OP_FP: DecodeRVRFPType(); break; default: UNSUPPORTED(); } } } } template T Simulator::FMaxMinHelper(T a, T b, MaxMinKind kind) { // set invalid bit for signaling nan if ((a == std::numeric_limits::signaling_NaN()) || (b == std::numeric_limits::signaling_NaN())) { set_csr_bits(csr_fflags, kInvalidOperation); } T result = 0; if (std::isnan(a) && std::isnan(b)) { result = std::numeric_limits::quiet_NaN(); } else if (std::isnan(a)) { result = b; } else if (std::isnan(b)) { result = a; } else if (b == a) { // Handle -0.0 == 0.0 case. if (kind == MaxMinKind::kMax) { result = std::signbit(b) ? a : b; } else { result = std::signbit(b) ? b : a; } } else { result = (kind == MaxMinKind::kMax) ? fmax(a, b) : fmin(a, b); } return result; } float Simulator::RoundF2FHelper(float input_val, int rmode) { if (rmode == DYN) rmode = get_dynamic_rounding_mode(); float rounded = 0; switch (rmode) { case RNE: { // Round to Nearest, tiest to Even rounded = floorf(input_val); float error = input_val - rounded; // Take care of correctly handling the range [-0.5, -0.0], which must // yield -0.0. if ((-0.5 <= input_val) && (input_val < 0.0)) { rounded = -0.0; // If the error is greater than 0.5, or is equal to 0.5 and the integer // result is odd, round up. } else if ((error > 0.5) || ((error == 0.5) && (std::fmod(rounded, 2) != 0))) { rounded++; } break; } case RTZ: // Round towards Zero rounded = std::truncf(input_val); break; case RDN: // Round Down (towards -infinity) rounded = floorf(input_val); break; case RUP: // Round Up (towards +infinity) rounded = ceilf(input_val); break; case RMM: // Round to Nearest, tiest to Max Magnitude rounded = std::roundf(input_val); break; default: UNREACHABLE(); } return rounded; } double Simulator::RoundF2FHelper(double input_val, int rmode) { if (rmode == DYN) rmode = get_dynamic_rounding_mode(); double rounded = 0; switch (rmode) { case RNE: { // Round to Nearest, tiest to Even rounded = std::floor(input_val); double error = input_val - rounded; // Take care of correctly handling the range [-0.5, -0.0], which must // yield -0.0. if ((-0.5 <= input_val) && (input_val < 0.0)) { rounded = -0.0; // If the error is greater than 0.5, or is equal to 0.5 and the integer // result is odd, round up. } else if ((error > 0.5) || ((error == 0.5) && (std::fmod(rounded, 2) != 0))) { rounded++; } break; } case RTZ: // Round towards Zero rounded = std::trunc(input_val); break; case RDN: // Round Down (towards -infinity) rounded = std::floor(input_val); break; case RUP: // Round Up (towards +infinity) rounded = std::ceil(input_val); break; case RMM: // Round to Nearest, tiest to Max Magnitude rounded = std::round(input_val); break; default: UNREACHABLE(); } return rounded; } // convert rounded floating-point to integer types, handle input values that // are out-of-range, underflow, or NaN, and set appropriate fflags template I_TYPE Simulator::RoundF2IHelper(F_TYPE original, int rmode) { MOZ_ASSERT(std::is_integral::value); MOZ_ASSERT((std::is_same::value || std::is_same::value)); I_TYPE max_i = std::numeric_limits::max(); I_TYPE min_i = std::numeric_limits::min(); if (!std::isfinite(original)) { set_fflags(kInvalidOperation); if (std::isnan(original) || original == std::numeric_limits::infinity()) { return max_i; } else { MOZ_ASSERT(original == -std::numeric_limits::infinity()); return min_i; } } F_TYPE rounded = RoundF2FHelper(original, rmode); if (original != rounded) set_fflags(kInexact); if (!std::isfinite(rounded)) { set_fflags(kInvalidOperation); if (std::isnan(rounded) || rounded == std::numeric_limits::infinity()) { return max_i; } else { MOZ_ASSERT(rounded == -std::numeric_limits::infinity()); return min_i; } } // Since integer max values are either all 1s (for unsigned) or all 1s // except for sign-bit (for signed), they cannot be represented precisely in // floating point, in order to precisely tell whether the rounded floating // point is within the max range, we compare against (max_i+1) which would // have a single 1 w/ many trailing zeros float max_i_plus_1 = std::is_same::value ? 0x1p64f // uint64_t::max + 1 cannot be represented in integers, // so use its float representation directly : static_cast(static_cast(max_i) + 1); if (rounded >= max_i_plus_1) { set_fflags(kOverflow | kInvalidOperation); return max_i; } // Since min_i (either 0 for unsigned, or for signed) is represented // precisely in floating-point, comparing rounded directly against min_i if (rounded <= min_i) { if (rounded < min_i) set_fflags(kOverflow | kInvalidOperation); return min_i; } F_TYPE underflow_fval = std::is_same::value ? FLT_MIN : DBL_MIN; if (rounded < underflow_fval && rounded > -underflow_fval && rounded != 0) { set_fflags(kUnderflow); } return static_cast(rounded); } template static int64_t FclassHelper(T value) { switch (std::fpclassify(value)) { case FP_INFINITE: return (std::signbit(value) ? kNegativeInfinity : kPositiveInfinity); case FP_NAN: return (isSnan(value) ? kSignalingNaN : kQuietNaN); case FP_NORMAL: return (std::signbit(value) ? kNegativeNormalNumber : kPositiveNormalNumber); case FP_SUBNORMAL: return (std::signbit(value) ? kNegativeSubnormalNumber : kPositiveSubnormalNumber); case FP_ZERO: return (std::signbit(value) ? kNegativeZero : kPositiveZero); default: UNREACHABLE(); } UNREACHABLE(); return FP_ZERO; } template bool Simulator::CompareFHelper(T input1, T input2, FPUCondition cc) { MOZ_ASSERT(std::is_floating_point::value); bool result = false; switch (cc) { case LT: case LE: // FLT, FLE are signaling compares if (std::isnan(input1) || std::isnan(input2)) { set_fflags(kInvalidOperation); result = false; } else { result = (cc == LT) ? (input1 < input2) : (input1 <= input2); } break; case EQ: if (std::numeric_limits::signaling_NaN() == input1 || std::numeric_limits::signaling_NaN() == input2) { set_fflags(kInvalidOperation); } if (std::isnan(input1) || std::isnan(input2)) { result = false; } else { result = (input1 == input2); } break; case NE: if (std::numeric_limits::signaling_NaN() == input1 || std::numeric_limits::signaling_NaN() == input2) { set_fflags(kInvalidOperation); } if (std::isnan(input1) || std::isnan(input2)) { result = true; } else { result = (input1 != input2); } break; default: UNREACHABLE(); } return result; } template static inline bool is_invalid_fmul(T src1, T src2) { return (isinf(src1) && src2 == static_cast(0.0)) || (src1 == static_cast(0.0) && isinf(src2)); } template static inline bool is_invalid_fadd(T src1, T src2) { return (isinf(src1) && isinf(src2) && std::signbit(src1) != std::signbit(src2)); } template static inline bool is_invalid_fsub(T src1, T src2) { return (isinf(src1) && isinf(src2) && std::signbit(src1) == std::signbit(src2)); } template static inline bool is_invalid_fdiv(T src1, T src2) { return ((src1 == 0 && src2 == 0) || (isinf(src1) && isinf(src2))); } template static inline bool is_invalid_fsqrt(T src1) { return (src1 < 0); } int Simulator::loadLinkedW(uint64_t addr, SimInstruction* instr) { if ((addr & 3) == 0) { if (handleWasmSegFault(addr, 4)) { return -1; } volatile int32_t* ptr = reinterpret_cast(addr); int32_t value = *ptr; lastLLValue_ = value; LLAddr_ = addr; // Note that any memory write or "external" interrupt should reset this // value to false. LLBit_ = true; return value; } printf("Unaligned write at 0x%016" PRIx64 ", pc=0x%016" PRIxPTR "\n", addr, reinterpret_cast(instr)); MOZ_CRASH(); return 0; } int Simulator::storeConditionalW(uint64_t addr, int value, SimInstruction* instr) { // Correct behavior in this case, as defined by architecture, is to just // return 0, but there is no point at allowing that. It is certainly an // indicator of a bug. if (addr != LLAddr_) { printf("SC to bad address: 0x%016" PRIx64 ", pc=0x%016" PRIx64 ", expected: 0x%016" PRIx64 "\n", addr, reinterpret_cast(instr), LLAddr_); MOZ_CRASH(); } if ((addr & 3) == 0) { SharedMem ptr = SharedMem::shared(reinterpret_cast(addr)); if (!LLBit_) { return 1; } LLBit_ = false; LLAddr_ = 0; int32_t expected = int32_t(lastLLValue_); int32_t old = AtomicOperations::compareExchangeSeqCst(ptr, expected, int32_t(value)); return (old == expected) ? 0 : 1; } printf("Unaligned SC at 0x%016" PRIx64 ", pc=0x%016" PRIxPTR "\n", addr, reinterpret_cast(instr)); MOZ_CRASH(); return 0; } int64_t Simulator::loadLinkedD(uint64_t addr, SimInstruction* instr) { if ((addr & kPointerAlignmentMask) == 0) { if (handleWasmSegFault(addr, 8)) { return -1; } volatile int64_t* ptr = reinterpret_cast(addr); int64_t value = *ptr; lastLLValue_ = value; LLAddr_ = addr; // Note that any memory write or "external" interrupt should reset this // value to false. LLBit_ = true; return value; } printf("Unaligned write at 0x%016" PRIx64 ", pc=0x%016" PRIxPTR "\n", addr, reinterpret_cast(instr)); MOZ_CRASH(); return 0; } int Simulator::storeConditionalD(uint64_t addr, int64_t value, SimInstruction* instr) { // Correct behavior in this case, as defined by architecture, is to just // return 0, but there is no point at allowing that. It is certainly an // indicator of a bug. if (addr != LLAddr_) { printf("SC to bad address: 0x%016" PRIx64 ", pc=0x%016" PRIx64 ", expected: 0x%016" PRIx64 "\n", addr, reinterpret_cast(instr), LLAddr_); MOZ_CRASH(); } if ((addr & kPointerAlignmentMask) == 0) { SharedMem ptr = SharedMem::shared(reinterpret_cast(addr)); if (!LLBit_) { return 1; } LLBit_ = false; LLAddr_ = 0; int64_t expected = lastLLValue_; int64_t old = AtomicOperations::compareExchangeSeqCst(ptr, expected, int64_t(value)); return (old == expected) ? 0 : 1; } printf("Unaligned SC at 0x%016" PRIx64 ", pc=0x%016" PRIxPTR "\n", addr, reinterpret_cast(instr)); MOZ_CRASH(); return 0; } void Simulator::DecodeRVRAType() { // TODO(riscv): Add macro for RISCV A extension // Special handling for A extension instructions because it uses func5 // For all A extension instruction, V8 simulator is pure sequential. No // Memory address lock or other synchronizaiton behaviors. switch (instr_.InstructionBits() & kRATypeMask) { case RO_LR_W: { sreg_t addr = rs1(); set_rd(loadLinkedW(addr, &instr_)); TraceLr(addr, getRegister(rd_reg()), getRegister(rd_reg())); break; } case RO_SC_W: { sreg_t addr = rs1(); auto value = static_cast(rs2()); auto result = storeConditionalW(addr, static_cast(rs2()), &instr_); set_rd(result); if (!result) { TraceSc(addr, value); } break; } case RO_AMOSWAP_W: { if ((rs1() & 0x3) != 0) { DieOrDebug(); } set_rd(sext32(amo( rs1(), [&](uint32_t lhs) { return (uint32_t)rs2(); }, instr_.instr(), WORD))); break; } case RO_AMOADD_W: { if ((rs1() & 0x3) != 0) { DieOrDebug(); } set_rd(sext32(amo( rs1(), [&](uint32_t lhs) { return lhs + (uint32_t)rs2(); }, instr_.instr(), WORD))); break; } case RO_AMOXOR_W: { if ((rs1() & 0x3) != 0) { DieOrDebug(); } set_rd(sext32(amo( rs1(), [&](uint32_t lhs) { return lhs ^ (uint32_t)rs2(); }, instr_.instr(), WORD))); break; } case RO_AMOAND_W: { if ((rs1() & 0x3) != 0) { DieOrDebug(); } set_rd(sext32(amo( rs1(), [&](uint32_t lhs) { return lhs & (uint32_t)rs2(); }, instr_.instr(), WORD))); break; } case RO_AMOOR_W: { if ((rs1() & 0x3) != 0) { DieOrDebug(); } set_rd(sext32(amo( rs1(), [&](uint32_t lhs) { return lhs | (uint32_t)rs2(); }, instr_.instr(), WORD))); break; } case RO_AMOMIN_W: { if ((rs1() & 0x3) != 0) { DieOrDebug(); } set_rd(sext32(amo( rs1(), [&](int32_t lhs) { return std::min(lhs, (int32_t)rs2()); }, instr_.instr(), WORD))); break; } case RO_AMOMAX_W: { if ((rs1() & 0x3) != 0) { DieOrDebug(); } set_rd(sext32(amo( rs1(), [&](int32_t lhs) { return std::max(lhs, (int32_t)rs2()); }, instr_.instr(), WORD))); break; } case RO_AMOMINU_W: { if ((rs1() & 0x3) != 0) { DieOrDebug(); } set_rd(sext32(amo( rs1(), [&](uint32_t lhs) { return std::min(lhs, (uint32_t)rs2()); }, instr_.instr(), WORD))); break; } case RO_AMOMAXU_W: { if ((rs1() & 0x3) != 0) { DieOrDebug(); } set_rd(sext32(amo( rs1(), [&](uint32_t lhs) { return std::max(lhs, (uint32_t)rs2()); }, instr_.instr(), WORD))); break; } # ifdef JS_CODEGEN_RISCV64 case RO_LR_D: { sreg_t addr = rs1(); set_rd(loadLinkedD(addr, &instr_)); TraceLr(addr, getRegister(rd_reg()), getRegister(rd_reg())); break; } case RO_SC_D: { sreg_t addr = rs1(); auto value = static_cast(rs2()); auto result = storeConditionalD(addr, static_cast(rs2()), &instr_); set_rd(result); if (!result) { TraceSc(addr, value); } break; } case RO_AMOSWAP_D: { set_rd(amo( rs1(), [&](int64_t lhs) { return rs2(); }, instr_.instr(), DWORD)); break; } case RO_AMOADD_D: { set_rd(amo( rs1(), [&](int64_t lhs) { return lhs + rs2(); }, instr_.instr(), DWORD)); break; } case RO_AMOXOR_D: { set_rd(amo( rs1(), [&](int64_t lhs) { return lhs ^ rs2(); }, instr_.instr(), DWORD)); break; } case RO_AMOAND_D: { set_rd(amo( rs1(), [&](int64_t lhs) { return lhs & rs2(); }, instr_.instr(), DWORD)); break; } case RO_AMOOR_D: { set_rd(amo( rs1(), [&](int64_t lhs) { return lhs | rs2(); }, instr_.instr(), DWORD)); break; } case RO_AMOMIN_D: { set_rd(amo( rs1(), [&](int64_t lhs) { return std::min(lhs, rs2()); }, instr_.instr(), DWORD)); break; } case RO_AMOMAX_D: { set_rd(amo( rs1(), [&](int64_t lhs) { return std::max(lhs, rs2()); }, instr_.instr(), DWORD)); break; } case RO_AMOMINU_D: { set_rd(amo( rs1(), [&](uint64_t lhs) { return std::min(lhs, (uint64_t)rs2()); }, instr_.instr(), DWORD)); break; } case RO_AMOMAXU_D: { set_rd(amo( rs1(), [&](uint64_t lhs) { return std::max(lhs, (uint64_t)rs2()); }, instr_.instr(), DWORD)); break; } # endif /*JS_CODEGEN_RISCV64*/ // TODO(riscv): End Add macro for RISCV A extension default: { UNSUPPORTED(); } } } void Simulator::DecodeRVRFPType() { // OP_FP instructions (F/D) uses func7 first. Some further uses func3 and // rs2() // kRATypeMask is only for func7 switch (instr_.InstructionBits() & kRFPTypeMask) { // TODO(riscv): Add macro for RISCV F extension case RO_FADD_S: { // TODO(riscv): use rm value (round mode) auto fn = [this](float frs1, float frs2) { if (is_invalid_fadd(frs1, frs2)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return frs1 + frs2; } }; set_frd(CanonicalizeFPUOp2(fn)); break; } case RO_FSUB_S: { // TODO(riscv): use rm value (round mode) auto fn = [this](float frs1, float frs2) { if (is_invalid_fsub(frs1, frs2)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return frs1 - frs2; } }; set_frd(CanonicalizeFPUOp2(fn)); break; } case RO_FMUL_S: { // TODO(riscv): use rm value (round mode) auto fn = [this](float frs1, float frs2) { if (is_invalid_fmul(frs1, frs2)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return frs1 * frs2; } }; set_frd(CanonicalizeFPUOp2(fn)); break; } case RO_FDIV_S: { // TODO(riscv): use rm value (round mode) auto fn = [this](float frs1, float frs2) { if (is_invalid_fdiv(frs1, frs2)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else if (frs2 == 0.0f) { this->set_fflags(kDivideByZero); return (std::signbit(frs1) == std::signbit(frs2) ? std::numeric_limits::infinity() : -std::numeric_limits::infinity()); } else { return frs1 / frs2; } }; set_frd(CanonicalizeFPUOp2(fn)); break; } case RO_FSQRT_S: { if (instr_.Rs2Value() == 0b00000) { // TODO(riscv): use rm value (round mode) auto fn = [this](float frs) { if (is_invalid_fsqrt(frs)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return std::sqrt(frs); } }; set_frd(CanonicalizeFPUOp1(fn)); } else { UNSUPPORTED(); } break; } case RO_FSGNJ_S: { // RO_FSGNJN_S RO_FSQNJX_S switch (instr_.Funct3Value()) { case 0b000: { // RO_FSGNJ_S set_frd(fsgnj32(frs1_boxed(), frs2_boxed(), false, false)); break; } case 0b001: { // RO_FSGNJN_S set_frd(fsgnj32(frs1_boxed(), frs2_boxed(), true, false)); break; } case 0b010: { // RO_FSQNJX_S set_frd(fsgnj32(frs1_boxed(), frs2_boxed(), false, true)); break; } default: { UNSUPPORTED(); } } break; } case RO_FMIN_S: { // RO_FMAX_S switch (instr_.Funct3Value()) { case 0b000: { // RO_FMIN_S set_frd(FMaxMinHelper(frs1(), frs2(), MaxMinKind::kMin)); break; } case 0b001: { // RO_FMAX_S set_frd(FMaxMinHelper(frs1(), frs2(), MaxMinKind::kMax)); break; } default: { UNSUPPORTED(); } } break; } case RO_FCVT_W_S: { // RO_FCVT_WU_S , 64F RO_FCVT_L_S RO_FCVT_LU_S float original_val = frs1(); switch (instr_.Rs2Value()) { case 0b00000: { // RO_FCVT_W_S set_rd(RoundF2IHelper(original_val, instr_.RoundMode())); break; } case 0b00001: { // RO_FCVT_WU_S set_rd(sext32( RoundF2IHelper(original_val, instr_.RoundMode()))); break; } # ifdef JS_CODEGEN_RISCV64 case 0b00010: { // RO_FCVT_L_S set_rd(RoundF2IHelper(original_val, instr_.RoundMode())); break; } case 0b00011: { // RO_FCVT_LU_S set_rd(RoundF2IHelper(original_val, instr_.RoundMode())); break; } # endif /* JS_CODEGEN_RISCV64 */ default: { UNSUPPORTED(); } } break; } case RO_FMV: { // RO_FCLASS_S switch (instr_.Funct3Value()) { case 0b000: { if (instr_.Rs2Value() == 0b00000) { // RO_FMV_X_W set_rd(sext32(getFpuRegister(rs1_reg()))); } else { UNSUPPORTED(); } break; } case 0b001: { // RO_FCLASS_S set_rd(FclassHelper(frs1())); break; } default: { UNSUPPORTED(); } } break; } case RO_FLE_S: { // RO_FEQ_S RO_FLT_S RO_FLE_S switch (instr_.Funct3Value()) { case 0b010: { // RO_FEQ_S set_rd(CompareFHelper(frs1(), frs2(), EQ)); break; } case 0b001: { // RO_FLT_S set_rd(CompareFHelper(frs1(), frs2(), LT)); break; } case 0b000: { // RO_FLE_S set_rd(CompareFHelper(frs1(), frs2(), LE)); break; } default: { UNSUPPORTED(); } } break; } case RO_FCVT_S_W: { // RO_FCVT_S_WU , 64F RO_FCVT_S_L RO_FCVT_S_LU switch (instr_.Rs2Value()) { case 0b00000: { // RO_FCVT_S_W set_frd(static_cast((int32_t)rs1())); break; } case 0b00001: { // RO_FCVT_S_WU set_frd(static_cast((uint32_t)rs1())); break; } # ifdef JS_CODEGEN_RISCV64 case 0b00010: { // RO_FCVT_S_L set_frd(static_cast((int64_t)rs1())); break; } case 0b00011: { // RO_FCVT_S_LU set_frd(static_cast((uint64_t)rs1())); break; } # endif /* JS_CODEGEN_RISCV64 */ default: { UNSUPPORTED(); } } break; } case RO_FMV_W_X: { if (instr_.Funct3Value() == 0b000) { // since FMV preserves source bit-pattern, no need to canonize Float32 result = Float32::FromBits((uint32_t)rs1()); set_frd(result); } else { UNSUPPORTED(); } break; } // TODO(riscv): Add macro for RISCV D extension case RO_FADD_D: { // TODO(riscv): use rm value (round mode) auto fn = [this](double drs1, double drs2) { if (is_invalid_fadd(drs1, drs2)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return drs1 + drs2; } }; set_drd(CanonicalizeFPUOp2(fn)); break; } case RO_FSUB_D: { // TODO(riscv): use rm value (round mode) auto fn = [this](double drs1, double drs2) { if (is_invalid_fsub(drs1, drs2)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return drs1 - drs2; } }; set_drd(CanonicalizeFPUOp2(fn)); break; } case RO_FMUL_D: { // TODO(riscv): use rm value (round mode) auto fn = [this](double drs1, double drs2) { if (is_invalid_fmul(drs1, drs2)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return drs1 * drs2; } }; set_drd(CanonicalizeFPUOp2(fn)); break; } case RO_FDIV_D: { // TODO(riscv): use rm value (round mode) auto fn = [this](double drs1, double drs2) { if (is_invalid_fdiv(drs1, drs2)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else if (drs2 == 0.0) { this->set_fflags(kDivideByZero); return (std::signbit(drs1) == std::signbit(drs2) ? std::numeric_limits::infinity() : -std::numeric_limits::infinity()); } else { return drs1 / drs2; } }; set_drd(CanonicalizeFPUOp2(fn)); break; } case RO_FSQRT_D: { if (instr_.Rs2Value() == 0b00000) { // TODO(riscv): use rm value (round mode) auto fn = [this](double drs) { if (is_invalid_fsqrt(drs)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return std::sqrt(drs); } }; set_drd(CanonicalizeFPUOp1(fn)); } else { UNSUPPORTED(); } break; } case RO_FSGNJ_D: { // RO_FSGNJN_D RO_FSQNJX_D switch (instr_.Funct3Value()) { case 0b000: { // RO_FSGNJ_D set_drd(fsgnj64(drs1_boxed(), drs2_boxed(), false, false)); break; } case 0b001: { // RO_FSGNJN_D set_drd(fsgnj64(drs1_boxed(), drs2_boxed(), true, false)); break; } case 0b010: { // RO_FSQNJX_D set_drd(fsgnj64(drs1_boxed(), drs2_boxed(), false, true)); break; } default: { UNSUPPORTED(); } } break; } case RO_FMIN_D: { // RO_FMAX_D switch (instr_.Funct3Value()) { case 0b000: { // RO_FMIN_D set_drd(FMaxMinHelper(drs1(), drs2(), MaxMinKind::kMin)); break; } case 0b001: { // RO_FMAX_D set_drd(FMaxMinHelper(drs1(), drs2(), MaxMinKind::kMax)); break; } default: { UNSUPPORTED(); } } break; } case (RO_FCVT_S_D & kRFPTypeMask): { if (instr_.Rs2Value() == 0b00001) { auto fn = [](double drs) { return static_cast(drs); }; set_frd(CanonicalizeDoubleToFloatOperation(fn)); } else { UNSUPPORTED(); } break; } case RO_FCVT_D_S: { if (instr_.Rs2Value() == 0b00000) { auto fn = [](float frs) { return static_cast(frs); }; set_drd(CanonicalizeFloatToDoubleOperation(fn)); } else { UNSUPPORTED(); } break; } case RO_FLE_D: { // RO_FEQ_D RO_FLT_D RO_FLE_D switch (instr_.Funct3Value()) { case 0b010: { // RO_FEQ_S set_rd(CompareFHelper(drs1(), drs2(), EQ)); break; } case 0b001: { // RO_FLT_D set_rd(CompareFHelper(drs1(), drs2(), LT)); break; } case 0b000: { // RO_FLE_D set_rd(CompareFHelper(drs1(), drs2(), LE)); break; } default: { UNSUPPORTED(); } } break; } case (RO_FCLASS_D & kRFPTypeMask): { // RO_FCLASS_D , 64D RO_FMV_X_D if (instr_.Rs2Value() != 0b00000) { UNSUPPORTED(); } switch (instr_.Funct3Value()) { case 0b001: { // RO_FCLASS_D set_rd(FclassHelper(drs1())); break; } # ifdef JS_CODEGEN_RISCV64 case 0b000: { // RO_FMV_X_D set_rd(bit_cast(drs1())); break; } # endif /* JS_CODEGEN_RISCV64 */ default: { UNSUPPORTED(); } } break; } case RO_FCVT_W_D: { // RO_FCVT_WU_D , 64F RO_FCVT_L_D RO_FCVT_LU_D double original_val = drs1(); switch (instr_.Rs2Value()) { case 0b00000: { // RO_FCVT_W_D set_rd(RoundF2IHelper(original_val, instr_.RoundMode())); break; } case 0b00001: { // RO_FCVT_WU_D set_rd(sext32( RoundF2IHelper(original_val, instr_.RoundMode()))); break; } # ifdef JS_CODEGEN_RISCV64 case 0b00010: { // RO_FCVT_L_D set_rd(RoundF2IHelper(original_val, instr_.RoundMode())); break; } case 0b00011: { // RO_FCVT_LU_D set_rd(RoundF2IHelper(original_val, instr_.RoundMode())); break; } # endif /* JS_CODEGEN_RISCV64 */ default: { UNSUPPORTED(); } } break; } case RO_FCVT_D_W: { // RO_FCVT_D_WU , 64F RO_FCVT_D_L RO_FCVT_D_LU switch (instr_.Rs2Value()) { case 0b00000: { // RO_FCVT_D_W set_drd((int32_t)rs1()); break; } case 0b00001: { // RO_FCVT_D_WU set_drd((uint32_t)rs1()); break; } # ifdef JS_CODEGEN_RISCV64 case 0b00010: { // RO_FCVT_D_L set_drd((int64_t)rs1()); break; } case 0b00011: { // RO_FCVT_D_LU set_drd((uint64_t)rs1()); break; } # endif /* JS_CODEGEN_RISCV64 */ default: { UNSUPPORTED(); } } break; } # ifdef JS_CODEGEN_RISCV64 case RO_FMV_D_X: { if (instr_.Funct3Value() == 0b000 && instr_.Rs2Value() == 0b00000) { // Since FMV preserves source bit-pattern, no need to canonize set_drd(bit_cast(rs1())); } else { UNSUPPORTED(); } break; } # endif /* JS_CODEGEN_RISCV64 */ default: { UNSUPPORTED(); } } } void Simulator::DecodeRVR4Type() { switch (instr_.InstructionBits() & kR4TypeMask) { // TODO(riscv): use F Extension macro block case RO_FMADD_S: { // TODO(riscv): use rm value (round mode) auto fn = [this](float frs1, float frs2, float frs3) { if (is_invalid_fmul(frs1, frs2) || is_invalid_fadd(frs1 * frs2, frs3)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return std::fma(frs1, frs2, frs3); } }; set_frd(CanonicalizeFPUOp3(fn)); break; } case RO_FMSUB_S: { // TODO(riscv): use rm value (round mode) auto fn = [this](float frs1, float frs2, float frs3) { if (is_invalid_fmul(frs1, frs2) || is_invalid_fsub(frs1 * frs2, frs3)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return std::fma(frs1, frs2, -frs3); } }; set_frd(CanonicalizeFPUOp3(fn)); break; } case RO_FNMSUB_S: { // TODO(riscv): use rm value (round mode) auto fn = [this](float frs1, float frs2, float frs3) { if (is_invalid_fmul(frs1, frs2) || is_invalid_fsub(frs3, frs1 * frs2)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return -std::fma(frs1, frs2, -frs3); } }; set_frd(CanonicalizeFPUOp3(fn)); break; } case RO_FNMADD_S: { // TODO(riscv): use rm value (round mode) auto fn = [this](float frs1, float frs2, float frs3) { if (is_invalid_fmul(frs1, frs2) || is_invalid_fadd(frs1 * frs2, frs3)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return -std::fma(frs1, frs2, frs3); } }; set_frd(CanonicalizeFPUOp3(fn)); break; } // TODO(riscv): use F Extension macro block case RO_FMADD_D: { // TODO(riscv): use rm value (round mode) auto fn = [this](double drs1, double drs2, double drs3) { if (is_invalid_fmul(drs1, drs2) || is_invalid_fadd(drs1 * drs2, drs3)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return std::fma(drs1, drs2, drs3); } }; set_drd(CanonicalizeFPUOp3(fn)); break; } case RO_FMSUB_D: { // TODO(riscv): use rm value (round mode) auto fn = [this](double drs1, double drs2, double drs3) { if (is_invalid_fmul(drs1, drs2) || is_invalid_fsub(drs1 * drs2, drs3)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return std::fma(drs1, drs2, -drs3); } }; set_drd(CanonicalizeFPUOp3(fn)); break; } case RO_FNMSUB_D: { // TODO(riscv): use rm value (round mode) auto fn = [this](double drs1, double drs2, double drs3) { if (is_invalid_fmul(drs1, drs2) || is_invalid_fsub(drs3, drs1 * drs2)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return -std::fma(drs1, drs2, -drs3); } }; set_drd(CanonicalizeFPUOp3(fn)); break; } case RO_FNMADD_D: { // TODO(riscv): use rm value (round mode) auto fn = [this](double drs1, double drs2, double drs3) { if (is_invalid_fmul(drs1, drs2) || is_invalid_fadd(drs1 * drs2, drs3)) { this->set_fflags(kInvalidOperation); return std::numeric_limits::quiet_NaN(); } else { return -std::fma(drs1, drs2, drs3); } }; set_drd(CanonicalizeFPUOp3(fn)); break; } default: UNSUPPORTED(); } } # ifdef CAN_USE_RVV_INSTRUCTIONS bool Simulator::DecodeRvvVL() { uint32_t instr_temp = instr_.InstructionBits() & (kRvvMopMask | kRvvNfMask | kBaseOpcodeMask); if (RO_V_VL == instr_temp) { if (!(instr_.InstructionBits() & (kRvvRs2Mask))) { switch (instr_.vl_vs_width()) { case 8: { RVV_VI_LD(0, (i * nf + fn), int8, false); break; } case 16: { RVV_VI_LD(0, (i * nf + fn), int16, false); break; } case 32: { RVV_VI_LD(0, (i * nf + fn), int32, false); break; } case 64: { RVV_VI_LD(0, (i * nf + fn), int64, false); break; } default: UNIMPLEMENTED_RISCV(); break; } return true; } else { UNIMPLEMENTED_RISCV(); return true; } } else if (RO_V_VLS == instr_temp) { UNIMPLEMENTED_RISCV(); return true; } else if (RO_V_VLX == instr_temp) { UNIMPLEMENTED_RISCV(); return true; } else if (RO_V_VLSEG2 == instr_temp || RO_V_VLSEG3 == instr_temp || RO_V_VLSEG4 == instr_temp || RO_V_VLSEG5 == instr_temp || RO_V_VLSEG6 == instr_temp || RO_V_VLSEG7 == instr_temp || RO_V_VLSEG8 == instr_temp) { if (!(instr_.InstructionBits() & (kRvvRs2Mask))) { UNIMPLEMENTED_RISCV(); return true; } else { UNIMPLEMENTED_RISCV(); return true; } } else if (RO_V_VLSSEG2 == instr_temp || RO_V_VLSSEG3 == instr_temp || RO_V_VLSSEG4 == instr_temp || RO_V_VLSSEG5 == instr_temp || RO_V_VLSSEG6 == instr_temp || RO_V_VLSSEG7 == instr_temp || RO_V_VLSSEG8 == instr_temp) { UNIMPLEMENTED_RISCV(); return true; } else if (RO_V_VLXSEG2 == instr_temp || RO_V_VLXSEG3 == instr_temp || RO_V_VLXSEG4 == instr_temp || RO_V_VLXSEG5 == instr_temp || RO_V_VLXSEG6 == instr_temp || RO_V_VLXSEG7 == instr_temp || RO_V_VLXSEG8 == instr_temp) { UNIMPLEMENTED_RISCV(); return true; } else { return false; } } bool Simulator::DecodeRvvVS() { uint32_t instr_temp = instr_.InstructionBits() & (kRvvMopMask | kRvvNfMask | kBaseOpcodeMask); if (RO_V_VS == instr_temp) { if (!(instr_.InstructionBits() & (kRvvRs2Mask))) { switch (instr_.vl_vs_width()) { case 8: { RVV_VI_ST(0, (i * nf + fn), uint8, false); break; } case 16: { RVV_VI_ST(0, (i * nf + fn), uint16, false); break; } case 32: { RVV_VI_ST(0, (i * nf + fn), uint32, false); break; } case 64: { RVV_VI_ST(0, (i * nf + fn), uint64, false); break; } default: UNIMPLEMENTED_RISCV(); break; } } else { UNIMPLEMENTED_RISCV(); } return true; } else if (RO_V_VSS == instr_temp) { UNIMPLEMENTED_RISCV(); return true; } else if (RO_V_VSX == instr_temp) { UNIMPLEMENTED_RISCV(); return true; } else if (RO_V_VSU == instr_temp) { UNIMPLEMENTED_RISCV(); return true; } else if (RO_V_VSSEG2 == instr_temp || RO_V_VSSEG3 == instr_temp || RO_V_VSSEG4 == instr_temp || RO_V_VSSEG5 == instr_temp || RO_V_VSSEG6 == instr_temp || RO_V_VSSEG7 == instr_temp || RO_V_VSSEG8 == instr_temp) { UNIMPLEMENTED_RISCV(); return true; } else if (RO_V_VSSSEG2 == instr_temp || RO_V_VSSSEG3 == instr_temp || RO_V_VSSSEG4 == instr_temp || RO_V_VSSSEG5 == instr_temp || RO_V_VSSSEG6 == instr_temp || RO_V_VSSSEG7 == instr_temp || RO_V_VSSSEG8 == instr_temp) { UNIMPLEMENTED_RISCV(); return true; } else if (RO_V_VSXSEG2 == instr_temp || RO_V_VSXSEG3 == instr_temp || RO_V_VSXSEG4 == instr_temp || RO_V_VSXSEG5 == instr_temp || RO_V_VSXSEG6 == instr_temp || RO_V_VSXSEG7 == instr_temp || RO_V_VSXSEG8 == instr_temp) { UNIMPLEMENTED_RISCV(); return true; } else { return false; } } # endif void Simulator::DecodeRVIType() { switch (instr_.InstructionBits() & kITypeMask) { case RO_JALR: { set_rd(get_pc() + kInstrSize); // Note: No need to shift 2 for JALR's imm12, but set lowest bit to 0. sreg_t next_pc = (rs1() + imm12()) & ~sreg_t(1); set_pc(next_pc); break; } case RO_LB: { sreg_t addr = rs1() + imm12(); int8_t val = ReadMem(addr, instr_.instr()); set_rd(sext_xlen(val), false); TraceMemRd(addr, val, getRegister(rd_reg())); break; } case RO_LH: { sreg_t addr = rs1() + imm12(); int16_t val = ReadMem(addr, instr_.instr()); set_rd(sext_xlen(val), false); TraceMemRd(addr, val, getRegister(rd_reg())); break; } case RO_LW: { sreg_t addr = rs1() + imm12(); int32_t val = ReadMem(addr, instr_.instr()); set_rd(sext_xlen(val), false); TraceMemRd(addr, val, getRegister(rd_reg())); break; } case RO_LBU: { sreg_t addr = rs1() + imm12(); uint8_t val = ReadMem(addr, instr_.instr()); set_rd(zext_xlen(val), false); TraceMemRd(addr, val, getRegister(rd_reg())); break; } case RO_LHU: { sreg_t addr = rs1() + imm12(); uint16_t val = ReadMem(addr, instr_.instr()); set_rd(zext_xlen(val), false); TraceMemRd(addr, val, getRegister(rd_reg())); break; } # ifdef JS_CODEGEN_RISCV64 case RO_LWU: { int64_t addr = rs1() + imm12(); uint32_t val = ReadMem(addr, instr_.instr()); set_rd(zext_xlen(val), false); TraceMemRd(addr, val, getRegister(rd_reg())); break; } case RO_LD: { int64_t addr = rs1() + imm12(); int64_t val = ReadMem(addr, instr_.instr()); set_rd(sext_xlen(val), false); TraceMemRd(addr, val, getRegister(rd_reg())); break; } # endif /*JS_CODEGEN_RISCV64*/ case RO_ADDI: { set_rd(sext_xlen(rs1() + imm12())); break; } case RO_SLTI: { set_rd(sreg_t(rs1()) < sreg_t(imm12())); break; } case RO_SLTIU: { set_rd(reg_t(rs1()) < reg_t(imm12())); break; } case RO_XORI: { set_rd(imm12() ^ rs1()); break; } case RO_ORI: { set_rd(imm12() | rs1()); break; } case RO_ANDI: { set_rd(imm12() & rs1()); break; } case RO_SLLI: { require(shamt6() < xlen); set_rd(sext_xlen(rs1() << shamt6())); break; } case RO_SRLI: { // RO_SRAI if (!instr_.IsArithShift()) { require(shamt6() < xlen); set_rd(sext_xlen(zext_xlen(rs1()) >> shamt6())); } else { require(shamt6() < xlen); set_rd(sext_xlen(sext_xlen(rs1()) >> shamt6())); } break; } # ifdef JS_CODEGEN_RISCV64 case RO_ADDIW: { set_rd(sext32(rs1() + imm12())); break; } case RO_SLLIW: { set_rd(sext32(rs1() << shamt5())); break; } case RO_SRLIW: { // RO_SRAIW if (!instr_.IsArithShift()) { set_rd(sext32(uint32_t(rs1()) >> shamt5())); } else { set_rd(sext32(int32_t(rs1()) >> shamt5())); } break; } # endif /*JS_CODEGEN_RISCV64*/ case RO_FENCE: { // DO nothing in sumulator break; } case RO_ECALL: { // RO_EBREAK if (instr_.Imm12Value() == 0) { // ECALL SoftwareInterrupt(); } else if (instr_.Imm12Value() == 1) { // EBREAK uint8_t code = get_ebreak_code(instr_.instr()); if (code == kWasmTrapCode) { uint8_t* newPC; if (wasm::HandleIllegalInstruction(registerState(), &newPC)) { set_pc(int64_t(newPC)); return; } } SoftwareInterrupt(); } else { UNSUPPORTED(); } break; } // TODO(riscv): use Zifencei Standard Extension macro block case RO_FENCE_I: { // spike: flush icache. break; } // TODO(riscv): use Zicsr Standard Extension macro block case RO_CSRRW: { if (rd_reg() != zero_reg) { set_rd(zext_xlen(read_csr_value(csr_reg()))); } write_csr_value(csr_reg(), rs1()); break; } case RO_CSRRS: { set_rd(zext_xlen(read_csr_value(csr_reg()))); if (rs1_reg() != zero_reg) { set_csr_bits(csr_reg(), rs1()); } break; } case RO_CSRRC: { set_rd(zext_xlen(read_csr_value(csr_reg()))); if (rs1_reg() != zero_reg) { clear_csr_bits(csr_reg(), rs1()); } break; } case RO_CSRRWI: { if (rd_reg() != zero_reg) { set_rd(zext_xlen(read_csr_value(csr_reg()))); } write_csr_value(csr_reg(), imm5CSR()); break; } case RO_CSRRSI: { set_rd(zext_xlen(read_csr_value(csr_reg()))); if (imm5CSR() != 0) { set_csr_bits(csr_reg(), imm5CSR()); } break; } case RO_CSRRCI: { set_rd(zext_xlen(read_csr_value(csr_reg()))); if (imm5CSR() != 0) { clear_csr_bits(csr_reg(), imm5CSR()); } break; } // TODO(riscv): use F Extension macro block case RO_FLW: { sreg_t addr = rs1() + imm12(); uint32_t val = ReadMem(addr, instr_.instr()); set_frd(Float32::FromBits(val), false); TraceMemRdFloat(addr, Float32::FromBits(val), getFpuRegister(frd_reg())); break; } // TODO(riscv): use D Extension macro block case RO_FLD: { sreg_t addr = rs1() + imm12(); uint64_t val = ReadMem(addr, instr_.instr()); set_drd(Float64::FromBits(val), false); TraceMemRdDouble(addr, Float64::FromBits(val), getFpuRegister(frd_reg())); break; } default: { # ifdef CAN_USE_RVV_INSTRUCTIONS if (!DecodeRvvVL()) { UNSUPPORTED(); } break; # else UNSUPPORTED(); # endif } } } void Simulator::DecodeRVSType() { switch (instr_.InstructionBits() & kSTypeMask) { case RO_SB: WriteMem(rs1() + s_imm12(), (uint8_t)rs2(), instr_.instr()); break; case RO_SH: WriteMem(rs1() + s_imm12(), (uint16_t)rs2(), instr_.instr()); break; case RO_SW: WriteMem(rs1() + s_imm12(), (uint32_t)rs2(), instr_.instr()); break; # ifdef JS_CODEGEN_RISCV64 case RO_SD: WriteMem(rs1() + s_imm12(), (uint64_t)rs2(), instr_.instr()); break; # endif /*JS_CODEGEN_RISCV64*/ // TODO(riscv): use F Extension macro block case RO_FSW: { WriteMem(rs1() + s_imm12(), getFpuRegisterFloat32(rs2_reg()), instr_.instr()); break; } // TODO(riscv): use D Extension macro block case RO_FSD: { WriteMem(rs1() + s_imm12(), getFpuRegisterFloat64(rs2_reg()), instr_.instr()); break; } default: # ifdef CAN_USE_RVV_INSTRUCTIONS if (!DecodeRvvVS()) { UNSUPPORTED(); } break; # else UNSUPPORTED(); # endif } } void Simulator::DecodeRVBType() { switch (instr_.InstructionBits() & kBTypeMask) { case RO_BEQ: if (rs1() == rs2()) { int64_t next_pc = get_pc() + boffset(); set_pc(next_pc); } break; case RO_BNE: if (rs1() != rs2()) { int64_t next_pc = get_pc() + boffset(); set_pc(next_pc); } break; case RO_BLT: if (rs1() < rs2()) { int64_t next_pc = get_pc() + boffset(); set_pc(next_pc); } break; case RO_BGE: if (rs1() >= rs2()) { int64_t next_pc = get_pc() + boffset(); set_pc(next_pc); } break; case RO_BLTU: if ((reg_t)rs1() < (reg_t)rs2()) { int64_t next_pc = get_pc() + boffset(); set_pc(next_pc); } break; case RO_BGEU: if ((reg_t)rs1() >= (reg_t)rs2()) { int64_t next_pc = get_pc() + boffset(); set_pc(next_pc); } break; default: UNSUPPORTED(); } } void Simulator::DecodeRVUType() { // U Type doesn't have additoinal mask switch (instr_.BaseOpcodeFieldRaw()) { case LUI: set_rd(u_imm20()); break; case AUIPC: set_rd(sext_xlen(u_imm20() + get_pc())); break; default: UNSUPPORTED(); } } void Simulator::DecodeRVJType() { // J Type doesn't have additional mask switch (instr_.BaseOpcodeValue()) { case JAL: { set_rd(get_pc() + kInstrSize); int64_t next_pc = get_pc() + imm20J(); set_pc(next_pc); break; } default: UNSUPPORTED(); } } void Simulator::DecodeCRType() { switch (instr_.RvcFunct4Value()) { case 0b1000: if (instr_.RvcRs1Value() != 0 && instr_.RvcRs2Value() == 0) { // c.jr set_pc(rvc_rs1()); } else if (instr_.RvcRdValue() != 0 && instr_.RvcRs2Value() != 0) { // c.mv set_rvc_rd(sext_xlen(rvc_rs2())); } else { UNSUPPORTED(); } break; case 0b1001: if (instr_.RvcRs1Value() == 0 && instr_.RvcRs2Value() == 0) { // c.ebreak DieOrDebug(); } else if (instr_.RvcRdValue() != 0 && instr_.RvcRs2Value() == 0) { // c.jalr setRegister(ra, get_pc() + kShortInstrSize); set_pc(rvc_rs1()); } else if (instr_.RvcRdValue() != 0 && instr_.RvcRs2Value() != 0) { // c.add set_rvc_rd(sext_xlen(rvc_rs1() + rvc_rs2())); } else { UNSUPPORTED(); } break; default: UNSUPPORTED(); } } void Simulator::DecodeCAType() { switch (instr_.InstructionBits() & kCATypeMask) { case RO_C_SUB: set_rvc_rs1s(sext_xlen(rvc_rs1s() - rvc_rs2s())); break; case RO_C_XOR: set_rvc_rs1s(rvc_rs1s() ^ rvc_rs2s()); break; case RO_C_OR: set_rvc_rs1s(rvc_rs1s() | rvc_rs2s()); break; case RO_C_AND: set_rvc_rs1s(rvc_rs1s() & rvc_rs2s()); break; # if JS_CODEGEN_RISCV64 case RO_C_SUBW: set_rvc_rs1s(sext32(rvc_rs1s() - rvc_rs2s())); break; case RO_C_ADDW: set_rvc_rs1s(sext32(rvc_rs1s() + rvc_rs2s())); break; # endif default: UNSUPPORTED(); } } void Simulator::DecodeCIType() { switch (instr_.RvcOpcode()) { case RO_C_NOP_ADDI: if (instr_.RvcRdValue() == 0) // c.nop break; else // c.addi set_rvc_rd(sext_xlen(rvc_rs1() + rvc_imm6())); break; # if JS_CODEGEN_RISCV64 case RO_C_ADDIW: set_rvc_rd(sext32(rvc_rs1() + rvc_imm6())); break; # endif case RO_C_LI: set_rvc_rd(sext_xlen(rvc_imm6())); break; case RO_C_LUI_ADD: if (instr_.RvcRdValue() == 2) { // c.addi16sp int64_t value = getRegister(sp) + rvc_imm6_addi16sp(); setRegister(sp, value); } else if (instr_.RvcRdValue() != 0 && instr_.RvcRdValue() != 2) { // c.lui set_rvc_rd(rvc_u_imm6()); } else { UNSUPPORTED(); } break; case RO_C_SLLI: set_rvc_rd(sext_xlen(rvc_rs1() << rvc_shamt6())); break; case RO_C_FLDSP: { sreg_t addr = getRegister(sp) + rvc_imm6_ldsp(); uint64_t val = ReadMem(addr, instr_.instr()); set_rvc_drd(Float64::FromBits(val), false); TraceMemRdDouble(addr, Float64::FromBits(val), getFpuRegister(rvc_frd_reg())); break; } # if JS_CODEGEN_RISCV64 case RO_C_LWSP: { sreg_t addr = getRegister(sp) + rvc_imm6_lwsp(); int64_t val = ReadMem(addr, instr_.instr()); set_rvc_rd(sext_xlen(val), false); TraceMemRd(addr, val, getRegister(rvc_rd_reg())); break; } case RO_C_LDSP: { sreg_t addr = getRegister(sp) + rvc_imm6_ldsp(); int64_t val = ReadMem(addr, instr_.instr()); set_rvc_rd(sext_xlen(val), false); TraceMemRd(addr, val, getRegister(rvc_rd_reg())); break; } # elif JS_CODEGEN_RISCV32 case RO_C_FLWSP: { sreg_t addr = getRegister(sp) + rvc_imm6_ldsp(); uint32_t val = ReadMem(addr, instr_.instr()); set_rvc_frd(Float32::FromBits(val), false); TraceMemRdFloat(addr, Float32::FromBits(val), getFpuRegister(rvc_frd_reg())); break; } case RO_C_LWSP: { sreg_t addr = getRegister(sp) + rvc_imm6_lwsp(); int32_t val = ReadMem(addr, instr_.instr()); set_rvc_rd(sext_xlen(val), false); TraceMemRd(addr, val, getRegister(rvc_rd_reg())); break; } # endif default: UNSUPPORTED(); } } void Simulator::DecodeCIWType() { switch (instr_.RvcOpcode()) { case RO_C_ADDI4SPN: { set_rvc_rs2s(getRegister(sp) + rvc_imm8_addi4spn()); break; default: UNSUPPORTED(); } } } void Simulator::DecodeCSSType() { switch (instr_.RvcOpcode()) { case RO_C_FSDSP: { sreg_t addr = getRegister(sp) + rvc_imm6_sdsp(); WriteMem(addr, getFpuRegisterFloat64(rvc_rs2_reg()), instr_.instr()); break; } # if JS_CODEGEN_RISCV32 case RO_C_FSWSP: { sreg_t addr = getRegister(sp) + rvc_imm6_sdsp(); WriteMem(addr, getFpuRegisterFloat32(rvc_rs2_reg()), instr_.instr()); break; } # endif case RO_C_SWSP: { sreg_t addr = getRegister(sp) + rvc_imm6_swsp(); WriteMem(addr, (int32_t)rvc_rs2(), instr_.instr()); break; } # if JS_CODEGEN_RISCV64 case RO_C_SDSP: { sreg_t addr = getRegister(sp) + rvc_imm6_sdsp(); WriteMem(addr, (int64_t)rvc_rs2(), instr_.instr()); break; } # endif default: UNSUPPORTED(); } } void Simulator::DecodeCLType() { switch (instr_.RvcOpcode()) { case RO_C_LW: { sreg_t addr = rvc_rs1s() + rvc_imm5_w(); int64_t val = ReadMem(addr, instr_.instr()); set_rvc_rs2s(sext_xlen(val), false); TraceMemRd(addr, val, getRegister(rvc_rs2s_reg())); break; } case RO_C_FLD: { sreg_t addr = rvc_rs1s() + rvc_imm5_d(); uint64_t val = ReadMem(addr, instr_.instr()); set_rvc_drs2s(Float64::FromBits(val), false); break; } # if JS_CODEGEN_RISCV64 case RO_C_LD: { sreg_t addr = rvc_rs1s() + rvc_imm5_d(); int64_t val = ReadMem(addr, instr_.instr()); set_rvc_rs2s(sext_xlen(val), false); TraceMemRd(addr, val, getRegister(rvc_rs2s_reg())); break; } # elif JS_CODEGEN_RISCV32 case RO_C_FLW: { sreg_t addr = rvc_rs1s() + rvc_imm5_d(); uint32_t val = ReadMem(addr, instr_.instr()); set_rvc_frs2s(Float32::FromBits(val), false); break; } # endif default: UNSUPPORTED(); } } void Simulator::DecodeCSType() { switch (instr_.RvcOpcode()) { case RO_C_SW: { sreg_t addr = rvc_rs1s() + rvc_imm5_w(); WriteMem(addr, (int32_t)rvc_rs2s(), instr_.instr()); break; } # if JS_CODEGEN_RISCV64 case RO_C_SD: { sreg_t addr = rvc_rs1s() + rvc_imm5_d(); WriteMem(addr, (int64_t)rvc_rs2s(), instr_.instr()); break; } # endif case RO_C_FSD: { sreg_t addr = rvc_rs1s() + rvc_imm5_d(); WriteMem(addr, static_cast(rvc_drs2s()), instr_.instr()); break; } default: UNSUPPORTED(); } } void Simulator::DecodeCJType() { switch (instr_.RvcOpcode()) { case RO_C_J: { set_pc(get_pc() + instr_.RvcImm11CJValue()); break; } default: UNSUPPORTED(); } } void Simulator::DecodeCBType() { switch (instr_.RvcOpcode()) { case RO_C_BNEZ: if (rvc_rs1() != 0) { sreg_t next_pc = get_pc() + rvc_imm8_b(); set_pc(next_pc); } break; case RO_C_BEQZ: if (rvc_rs1() == 0) { sreg_t next_pc = get_pc() + rvc_imm8_b(); set_pc(next_pc); } break; case RO_C_MISC_ALU: if (instr_.RvcFunct2BValue() == 0b00) { // c.srli set_rvc_rs1s(sext_xlen(sext_xlen(rvc_rs1s()) >> rvc_shamt6())); } else if (instr_.RvcFunct2BValue() == 0b01) { // c.srai require(rvc_shamt6() < xlen); set_rvc_rs1s(sext_xlen(sext_xlen(rvc_rs1s()) >> rvc_shamt6())); } else if (instr_.RvcFunct2BValue() == 0b10) { // c.andi set_rvc_rs1s(rvc_imm6() & rvc_rs1s()); } else { UNSUPPORTED(); } break; default: UNSUPPORTED(); } } void Simulator::callInternal(uint8_t* entry) { // Prepare to execute the code at entry. setRegister(pc, reinterpret_cast(entry)); // Put down marker for end of simulation. The simulator will stop simulation // when the PC reaches this value. By saving the "end simulation" value into // the LR the simulation stops when returning to this call point. setRegister(ra, end_sim_pc); // Remember the values of callee-saved registers. intptr_t s0_val = getRegister(Simulator::Register::fp); intptr_t s1_val = getRegister(Simulator::Register::s1); intptr_t s2_val = getRegister(Simulator::Register::s2); intptr_t s3_val = getRegister(Simulator::Register::s3); intptr_t s4_val = getRegister(Simulator::Register::s4); intptr_t s5_val = getRegister(Simulator::Register::s5); intptr_t s6_val = getRegister(Simulator::Register::s6); intptr_t s7_val = getRegister(Simulator::Register::s7); intptr_t s8_val = getRegister(Simulator::Register::s8); intptr_t s9_val = getRegister(Simulator::Register::s9); intptr_t s10_val = getRegister(Simulator::Register::s10); intptr_t s11_val = getRegister(Simulator::Register::s11); intptr_t gp_val = getRegister(Simulator::Register::gp); intptr_t sp_val = getRegister(Simulator::Register::sp); // Set up the callee-saved registers with a known value. To be able to check // that they are preserved properly across JS execution. If this value is // small int, it should be SMI. intptr_t callee_saved_value = icount_; setRegister(Simulator::Register::fp, callee_saved_value); setRegister(Simulator::Register::s1, callee_saved_value); setRegister(Simulator::Register::s2, callee_saved_value); setRegister(Simulator::Register::s3, callee_saved_value); setRegister(Simulator::Register::s4, callee_saved_value); setRegister(Simulator::Register::s5, callee_saved_value); setRegister(Simulator::Register::s6, callee_saved_value); setRegister(Simulator::Register::s7, callee_saved_value); setRegister(Simulator::Register::s8, callee_saved_value); setRegister(Simulator::Register::s9, callee_saved_value); setRegister(Simulator::Register::s10, callee_saved_value); setRegister(Simulator::Register::s11, callee_saved_value); setRegister(Simulator::Register::gp, callee_saved_value); // Start the simulation. if (Simulator::StopSimAt != -1) { execute(); } else { execute(); } // Check that the callee-saved registers have been preserved. MOZ_ASSERT(callee_saved_value == getRegister(Simulator::Register::fp)); MOZ_ASSERT(callee_saved_value == getRegister(Simulator::Register::s1)); MOZ_ASSERT(callee_saved_value == getRegister(Simulator::Register::s2)); MOZ_ASSERT(callee_saved_value == getRegister(Simulator::Register::s3)); MOZ_ASSERT(callee_saved_value == getRegister(Simulator::Register::s4)); MOZ_ASSERT(callee_saved_value == getRegister(Simulator::Register::s5)); MOZ_ASSERT(callee_saved_value == getRegister(Simulator::Register::s6)); MOZ_ASSERT(callee_saved_value == getRegister(Simulator::Register::s7)); MOZ_ASSERT(callee_saved_value == getRegister(Simulator::Register::s8)); MOZ_ASSERT(callee_saved_value == getRegister(Simulator::Register::s9)); MOZ_ASSERT(callee_saved_value == getRegister(Simulator::Register::s10)); MOZ_ASSERT(callee_saved_value == getRegister(Simulator::Register::s11)); MOZ_ASSERT(callee_saved_value == getRegister(Simulator::Register::gp)); // Restore callee-saved registers with the original value. setRegister(Simulator::Register::fp, s0_val); setRegister(Simulator::Register::s1, s1_val); setRegister(Simulator::Register::s2, s2_val); setRegister(Simulator::Register::s3, s3_val); setRegister(Simulator::Register::s4, s4_val); setRegister(Simulator::Register::s5, s5_val); setRegister(Simulator::Register::s6, s6_val); setRegister(Simulator::Register::s7, s7_val); setRegister(Simulator::Register::s8, s8_val); setRegister(Simulator::Register::s9, s9_val); setRegister(Simulator::Register::s10, s10_val); setRegister(Simulator::Register::s11, s11_val); setRegister(Simulator::Register::gp, gp_val); setRegister(Simulator::Register::sp, sp_val); } int64_t Simulator::call(uint8_t* entry, int argument_count, ...) { va_list parameters; va_start(parameters, argument_count); int64_t original_stack = getRegister(sp); // Compute position of stack on entry to generated code. int64_t entry_stack = original_stack; if (argument_count > kCArgSlotCount) { entry_stack = entry_stack - argument_count * sizeof(int64_t); } else { entry_stack = entry_stack - kCArgsSlotsSize; } entry_stack &= ~U64(ABIStackAlignment - 1); intptr_t* stack_argument = reinterpret_cast(entry_stack); // Setup the arguments. for (int i = 0; i < argument_count; i++) { js::jit::Register argReg; if (GetIntArgReg(i, &argReg)) { setRegister(argReg.code(), va_arg(parameters, int64_t)); } else { stack_argument[i] = va_arg(parameters, int64_t); } } va_end(parameters); setRegister(sp, entry_stack); callInternal(entry); // Pop stack passed arguments. MOZ_ASSERT(entry_stack == getRegister(sp)); setRegister(sp, original_stack); int64_t result = getRegister(a0); return result; } uintptr_t Simulator::pushAddress(uintptr_t address) { int new_sp = getRegister(sp) - sizeof(uintptr_t); uintptr_t* stack_slot = reinterpret_cast(new_sp); *stack_slot = address; setRegister(sp, new_sp); return new_sp; } uintptr_t Simulator::popAddress() { int current_sp = getRegister(sp); uintptr_t* stack_slot = reinterpret_cast(current_sp); uintptr_t address = *stack_slot; setRegister(sp, current_sp + sizeof(uintptr_t)); return address; } } // namespace jit } // namespace js js::jit::Simulator* JSContext::simulator() const { return simulator_; } #endif // JS_SIMULATOR_RISCV64