// Copyright 2011 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. #include <stdlib.h> #include <math.h> #include <limits.h> #include <cstdarg> #include "v8.h" #if defined(V8_TARGET_ARCH_MIPS) #include "disasm.h" #include "assembler.h" #include "globals.h" // Need the BitCast. #include "mips/constants-mips.h" #include "mips/simulator-mips.h" // Only build the simulator if not compiling for real MIPS hardware. #if defined(USE_SIMULATOR) namespace v8 { namespace internal { // Utils functions. bool HaveSameSign(int32_t a, int32_t b) { return ((a ^ b) >= 0); } uint32_t get_fcsr_condition_bit(uint32_t cc) { if (cc == 0) { return 23; } else { return 24 + cc; } } // This macro provides a platform independent use of sscanf. The reason for // SScanF not being implemented in a platform independent was through // ::v8::internal::OS in the same way as SNPrintF is that the Windows C Run-Time // Library does not provide vsscanf. #define SScanF sscanf // NOLINT // The MipsDebugger class is used by the simulator while debugging simulated // code. class MipsDebugger { public: explicit MipsDebugger(Simulator* sim); ~MipsDebugger(); void Stop(Instruction* instr); void Debug(); // Print all registers with a nice formatting. void PrintAllRegs(); void PrintAllRegsIncludingFPU(); private: // We set the breakpoint code to 0xfffff to easily recognize it. static const Instr kBreakpointInstr = SPECIAL | BREAK | 0xfffff << 6; static const Instr kNopInstr = 0x0; Simulator* sim_; int32_t GetRegisterValue(int regnum); int32_t GetFPURegisterValueInt(int regnum); int64_t GetFPURegisterValueLong(int regnum); float GetFPURegisterValueFloat(int regnum); double GetFPURegisterValueDouble(int regnum); bool GetValue(const char* desc, int32_t* value); // Set or delete a breakpoint. Returns true if successful. bool SetBreakpoint(Instruction* breakpc); bool DeleteBreakpoint(Instruction* breakpc); // Undo and redo all breakpoints. This is needed to bracket disassembly and // execution to skip past breakpoints when run from the debugger. void UndoBreakpoints(); void RedoBreakpoints(); }; MipsDebugger::MipsDebugger(Simulator* sim) { sim_ = sim; } MipsDebugger::~MipsDebugger() { } #ifdef GENERATED_CODE_COVERAGE static FILE* coverage_log = NULL; static void InitializeCoverage() { char* file_name = getenv("V8_GENERATED_CODE_COVERAGE_LOG"); if (file_name != NULL) { coverage_log = fopen(file_name, "aw+"); } } void MipsDebugger::Stop(Instruction* instr) { // Get the stop code. uint32_t code = instr->Bits(25, 6); // Retrieve the encoded address, which comes just after this stop. char** msg_address = reinterpret_cast<char**>(sim_->get_pc() + Instr::kInstrSize); char* msg = *msg_address; ASSERT(msg != NULL); // Update this stop description. if (!watched_stops[code].desc) { watched_stops[code].desc = msg; } if (strlen(msg) > 0) { if (coverage_log != NULL) { fprintf(coverage_log, "%s\n", str); fflush(coverage_log); } // Overwrite the instruction and address with nops. instr->SetInstructionBits(kNopInstr); reinterpret_cast<Instr*>(msg_address)->SetInstructionBits(kNopInstr); } sim_->set_pc(sim_->get_pc() + 2 * Instruction::kInstructionSize); } #else // GENERATED_CODE_COVERAGE #define UNSUPPORTED() printf("Unsupported instruction.\n"); static void InitializeCoverage() {} void MipsDebugger::Stop(Instruction* instr) { // Get the stop code. uint32_t code = instr->Bits(25, 6); // Retrieve the encoded address, which comes just after this stop. char* msg = *reinterpret_cast<char**>(sim_->get_pc() + Instruction::kInstrSize); // Update this stop description. if (!sim_->watched_stops[code].desc) { sim_->watched_stops[code].desc = msg; } PrintF("Simulator hit %s (%u)\n", msg, code); sim_->set_pc(sim_->get_pc() + 2 * Instruction::kInstrSize); Debug(); } #endif // GENERATED_CODE_COVERAGE int32_t MipsDebugger::GetRegisterValue(int regnum) { if (regnum == kNumSimuRegisters) { return sim_->get_pc(); } else { return sim_->get_register(regnum); } } int32_t MipsDebugger::GetFPURegisterValueInt(int regnum) { if (regnum == kNumFPURegisters) { return sim_->get_pc(); } else { return sim_->get_fpu_register(regnum); } } int64_t MipsDebugger::GetFPURegisterValueLong(int regnum) { if (regnum == kNumFPURegisters) { return sim_->get_pc(); } else { return sim_->get_fpu_register_long(regnum); } } float MipsDebugger::GetFPURegisterValueFloat(int regnum) { if (regnum == kNumFPURegisters) { return sim_->get_pc(); } else { return sim_->get_fpu_register_float(regnum); } } double MipsDebugger::GetFPURegisterValueDouble(int regnum) { if (regnum == kNumFPURegisters) { return sim_->get_pc(); } else { return sim_->get_fpu_register_double(regnum); } } bool MipsDebugger::GetValue(const char* desc, int32_t* value) { int regnum = Registers::Number(desc); int fpuregnum = FPURegisters::Number(desc); if (regnum != kInvalidRegister) { *value = GetRegisterValue(regnum); return true; } else if (fpuregnum != kInvalidFPURegister) { *value = GetFPURegisterValueInt(fpuregnum); return true; } else if (strncmp(desc, "0x", 2) == 0) { return SScanF(desc, "%x", reinterpret_cast<uint32_t*>(value)) == 1; } else { return SScanF(desc, "%i", value) == 1; } return false; } bool MipsDebugger::SetBreakpoint(Instruction* breakpc) { // Check if a breakpoint can be set. If not return without any side-effects. if (sim_->break_pc_ != NULL) { return false; } // Set the breakpoint. sim_->break_pc_ = breakpc; sim_->break_instr_ = breakpc->InstructionBits(); // Not setting the breakpoint instruction in the code itself. It will be set // when the debugger shell continues. return true; } bool MipsDebugger::DeleteBreakpoint(Instruction* breakpc) { if (sim_->break_pc_ != NULL) { sim_->break_pc_->SetInstructionBits(sim_->break_instr_); } sim_->break_pc_ = NULL; sim_->break_instr_ = 0; return true; } void MipsDebugger::UndoBreakpoints() { if (sim_->break_pc_ != NULL) { sim_->break_pc_->SetInstructionBits(sim_->break_instr_); } } void MipsDebugger::RedoBreakpoints() { if (sim_->break_pc_ != NULL) { sim_->break_pc_->SetInstructionBits(kBreakpointInstr); } } void MipsDebugger::PrintAllRegs() { #define REG_INFO(n) Registers::Name(n), GetRegisterValue(n), GetRegisterValue(n) PrintF("\n"); // at, v0, a0. PrintF("%3s: 0x%08x %10d\t%3s: 0x%08x %10d\t%3s: 0x%08x %10d\n", REG_INFO(1), REG_INFO(2), REG_INFO(4)); // v1, a1. PrintF("%26s\t%3s: 0x%08x %10d\t%3s: 0x%08x %10d\n", "", REG_INFO(3), REG_INFO(5)); // a2. PrintF("%26s\t%26s\t%3s: 0x%08x %10d\n", "", "", REG_INFO(6)); // a3. PrintF("%26s\t%26s\t%3s: 0x%08x %10d\n", "", "", REG_INFO(7)); PrintF("\n"); // t0-t7, s0-s7 for (int i = 0; i < 8; i++) { PrintF("%3s: 0x%08x %10d\t%3s: 0x%08x %10d\n", REG_INFO(8+i), REG_INFO(16+i)); } PrintF("\n"); // t8, k0, LO. PrintF("%3s: 0x%08x %10d\t%3s: 0x%08x %10d\t%3s: 0x%08x %10d\n", REG_INFO(24), REG_INFO(26), REG_INFO(32)); // t9, k1, HI. PrintF("%3s: 0x%08x %10d\t%3s: 0x%08x %10d\t%3s: 0x%08x %10d\n", REG_INFO(25), REG_INFO(27), REG_INFO(33)); // sp, fp, gp. PrintF("%3s: 0x%08x %10d\t%3s: 0x%08x %10d\t%3s: 0x%08x %10d\n", REG_INFO(29), REG_INFO(30), REG_INFO(28)); // pc. PrintF("%3s: 0x%08x %10d\t%3s: 0x%08x %10d\n", REG_INFO(31), REG_INFO(34)); #undef REG_INFO #undef FPU_REG_INFO } void MipsDebugger::PrintAllRegsIncludingFPU() { #define FPU_REG_INFO(n) FPURegisters::Name(n), FPURegisters::Name(n+1), \ GetFPURegisterValueInt(n+1), \ GetFPURegisterValueInt(n), \ GetFPURegisterValueDouble(n) PrintAllRegs(); PrintF("\n\n"); // f0, f1, f2, ... f31. PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(0) ); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(2) ); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(4) ); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(6) ); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(8) ); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(10)); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(12)); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(14)); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(16)); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(18)); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(20)); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(22)); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(24)); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(26)); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(28)); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPU_REG_INFO(30)); #undef REG_INFO #undef FPU_REG_INFO } void MipsDebugger::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; // Undo all set breakpoints while running in the debugger shell. This will // make them invisible to all commands. UndoBreakpoints(); 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. v8::internal::EmbeddedVector<char, 256> buffer; dasm.InstructionDecode(buffer, reinterpret_cast<byte*>(sim_->get_pc())); PrintF(" 0x%08x %s\n", sim_->get_pc(), buffer.start()); last_pc = sim_->get_pc(); } char* line = ReadLine("sim> "); if (line == NULL) { break; } else { // 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)) { Instruction* instr = reinterpret_cast<Instruction*>(sim_->get_pc()); if (!(instr->IsTrap()) || instr->InstructionBits() == rtCallRedirInstr) { sim_->InstructionDecode( reinterpret_cast<Instruction*>(sim_->get_pc())); } else { // Allow si to jump over generated breakpoints. PrintF("/!\\ Jumping over generated breakpoint.\n"); sim_->set_pc(sim_->get_pc() + Instruction::kInstrSize); } } else if ((strcmp(cmd, "c") == 0) || (strcmp(cmd, "cont") == 0)) { // Execute the one instruction we broke at with breakpoints disabled. sim_->InstructionDecode(reinterpret_cast<Instruction*>(sim_->get_pc())); // Leave the debugger shell. done = true; } else if ((strcmp(cmd, "p") == 0) || (strcmp(cmd, "print") == 0)) { if (argc == 2) { int32_t value; float fvalue; if (strcmp(arg1, "all") == 0) { PrintAllRegs(); } else if (strcmp(arg1, "allf") == 0) { PrintAllRegsIncludingFPU(); } else { int regnum = Registers::Number(arg1); int fpuregnum = FPURegisters::Number(arg1); if (regnum != kInvalidRegister) { value = GetRegisterValue(regnum); PrintF("%s: 0x%08x %d \n", arg1, value, value); } else if (fpuregnum != kInvalidFPURegister) { if (fpuregnum % 2 == 1) { value = GetFPURegisterValueInt(fpuregnum); fvalue = GetFPURegisterValueFloat(fpuregnum); PrintF("%s: 0x%08x %11.4e\n", arg1, value, fvalue); } else { double dfvalue; int32_t lvalue1 = GetFPURegisterValueInt(fpuregnum); int32_t lvalue2 = GetFPURegisterValueInt(fpuregnum + 1); dfvalue = GetFPURegisterValueDouble(fpuregnum); PrintF("%3s,%3s: 0x%08x%08x %16.4e\n", FPURegisters::Name(fpuregnum+1), FPURegisters::Name(fpuregnum), lvalue1, lvalue2, dfvalue); } } else { PrintF("%s unrecognized\n", arg1); } } } else { if (argc == 3) { if (strcmp(arg2, "single") == 0) { int32_t value; float fvalue; int fpuregnum = FPURegisters::Number(arg1); if (fpuregnum != kInvalidFPURegister) { value = GetFPURegisterValueInt(fpuregnum); fvalue = GetFPURegisterValueFloat(fpuregnum); PrintF("%s: 0x%08x %11.4e\n", arg1, value, fvalue); } else { PrintF("%s unrecognized\n", arg1); } } else { PrintF("print <fpu register> single\n"); } } else { PrintF("print <register> or print <fpu register> single\n"); } } } else if ((strcmp(cmd, "po") == 0) || (strcmp(cmd, "printobject") == 0)) { if (argc == 2) { int32_t value; if (GetValue(arg1, &value)) { Object* obj = reinterpret_cast<Object*>(value); PrintF("%s: \n", arg1); #ifdef DEBUG obj->PrintLn(); #else obj->ShortPrint(); PrintF("\n"); #endif } else { PrintF("%s unrecognized\n", arg1); } } else { PrintF("printobject <value>\n"); } } else if (strcmp(cmd, "stack") == 0 || strcmp(cmd, "mem") == 0) { int32_t* cur = NULL; int32_t* end = NULL; int next_arg = 1; if (strcmp(cmd, "stack") == 0) { cur = reinterpret_cast<int32_t*>(sim_->get_register(Simulator::sp)); } else { // Command "mem". int32_t value; if (!GetValue(arg1, &value)) { PrintF("%s unrecognized\n", arg1); continue; } cur = reinterpret_cast<int32_t*>(value); next_arg++; } int32_t words; if (argc == next_arg) { words = 10; } else if (argc == next_arg + 1) { if (!GetValue(argv[next_arg], &words)) { words = 10; } } end = cur + words; while (cur < end) { PrintF(" 0x%08x: 0x%08x %10d", reinterpret_cast<intptr_t>(cur), *cur, *cur); HeapObject* obj = reinterpret_cast<HeapObject*>(*cur); int value = *cur; Heap* current_heap = v8::internal::Isolate::Current()->heap(); if (current_heap->Contains(obj) || ((value & 1) == 0)) { PrintF(" ("); if ((value & 1) == 0) { PrintF("smi %d", value / 2); } else { obj->ShortPrint(); } PrintF(")"); } PrintF("\n"); cur++; } } 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. v8::internal::EmbeddedVector<char, 256> buffer; byte* cur = NULL; byte* end = NULL; if (argc == 1) { cur = reinterpret_cast<byte*>(sim_->get_pc()); end = cur + (10 * Instruction::kInstrSize); } else if (argc == 2) { int regnum = Registers::Number(arg1); if (regnum != kInvalidRegister || strncmp(arg1, "0x", 2) == 0) { // The argument is an address or a register name. int32_t value; if (GetValue(arg1, &value)) { cur = reinterpret_cast<byte*>(value); // Disassemble 10 instructions at <arg1>. end = cur + (10 * Instruction::kInstrSize); } } else { // The argument is the number of instructions. int32_t value; if (GetValue(arg1, &value)) { cur = reinterpret_cast<byte*>(sim_->get_pc()); // Disassemble <arg1> instructions. end = cur + (value * Instruction::kInstrSize); } } } else { int32_t value1; int32_t value2; if (GetValue(arg1, &value1) && GetValue(arg2, &value2)) { cur = reinterpret_cast<byte*>(value1); end = cur + (value2 * Instruction::kInstrSize); } } while (cur < end) { dasm.InstructionDecode(buffer, cur); PrintF(" 0x%08x %s\n", reinterpret_cast<intptr_t>(cur), buffer.start()); cur += Instruction::kInstrSize; } } else if (strcmp(cmd, "gdb") == 0) { PrintF("relinquishing control to gdb\n"); v8::internal::OS::DebugBreak(); PrintF("regaining control from gdb\n"); } else if (strcmp(cmd, "break") == 0) { if (argc == 2) { int32_t value; if (GetValue(arg1, &value)) { if (!SetBreakpoint(reinterpret_cast<Instruction*>(value))) { PrintF("setting breakpoint failed\n"); } } else { PrintF("%s unrecognized\n", arg1); } } else { PrintF("break <address>\n"); } } else if (strcmp(cmd, "del") == 0) { if (!DeleteBreakpoint(NULL)) { PrintF("deleting breakpoint failed\n"); } } else if (strcmp(cmd, "flags") == 0) { PrintF("No flags on MIPS !\n"); } else if (strcmp(cmd, "stop") == 0) { int32_t value; intptr_t stop_pc = sim_->get_pc() - 2 * Instruction::kInstrSize; Instruction* stop_instr = reinterpret_cast<Instruction*>(stop_pc); Instruction* msg_address = reinterpret_cast<Instruction*>(stop_pc + Instruction::kInstrSize); if ((argc == 2) && (strcmp(arg1, "unstop") == 0)) { // Remove the current stop. if (sim_->IsStopInstruction(stop_instr)) { stop_instr->SetInstructionBits(kNopInstr); msg_address->SetInstructionBits(kNopInstr); } else { PrintF("Not at debugger stop.\n"); } } else 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)) { // Print registers and disassemble. PrintAllRegs(); PrintF("\n"); disasm::NameConverter converter; disasm::Disassembler dasm(converter); // Use a reasonably large buffer. v8::internal::EmbeddedVector<char, 256> buffer; byte* cur = NULL; byte* end = NULL; if (argc == 1) { cur = reinterpret_cast<byte*>(sim_->get_pc()); end = cur + (10 * Instruction::kInstrSize); } else if (argc == 2) { int32_t value; if (GetValue(arg1, &value)) { cur = reinterpret_cast<byte*>(value); // no length parameter passed, assume 10 instructions end = cur + (10 * Instruction::kInstrSize); } } else { int32_t value1; int32_t value2; if (GetValue(arg1, &value1) && GetValue(arg2, &value2)) { cur = reinterpret_cast<byte*>(value1); end = cur + (value2 * Instruction::kInstrSize); } } while (cur < end) { dasm.InstructionDecode(buffer, cur); PrintF(" 0x%08x %s\n", reinterpret_cast<intptr_t>(cur), buffer.start()); cur += Instruction::kInstrSize; } } else if ((strcmp(cmd, "h") == 0) || (strcmp(cmd, "help") == 0)) { PrintF("cont\n"); PrintF(" continue execution (alias 'c')\n"); PrintF("stepi\n"); PrintF(" step one instruction (alias 'si')\n"); PrintF("print <register>\n"); PrintF(" print register content (alias 'p')\n"); PrintF(" use register name 'all' to print all registers\n"); PrintF("printobject <register>\n"); PrintF(" print an object from a register (alias 'po')\n"); PrintF("stack [<words>]\n"); PrintF(" dump stack content, default dump 10 words)\n"); PrintF("mem <address> [<words>]\n"); PrintF(" dump memory content, default dump 10 words)\n"); PrintF("flags\n"); PrintF(" print flags\n"); PrintF("disasm [<instructions>]\n"); PrintF("disasm [<address/register>]\n"); PrintF("disasm [[<address/register>] <instructions>]\n"); PrintF(" disassemble code, default is 10 instructions\n"); PrintF(" from pc (alias 'di')\n"); PrintF("gdb\n"); PrintF(" enter gdb\n"); PrintF("break <address>\n"); PrintF(" set a break point on the address\n"); PrintF("del\n"); PrintF(" delete the breakpoint\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 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/<code> : print infos about number <code>\n"); PrintF(" or all stop(s).\n"); PrintF(" stop enable/disable all/<code> : enables / disables\n"); PrintF(" all or number <code> stop(s)\n"); PrintF(" stop unstop\n"); PrintF(" ignore the stop instruction at the current location\n"); PrintF(" from now on\n"); } else { PrintF("Unknown command: %s\n", cmd); } } DeleteArray(line); } // Add all the breakpoints back to stop execution and enter the debugger // shell when hit. RedoBreakpoints(); #undef COMMAND_SIZE #undef ARG_SIZE #undef STR #undef XSTR } static bool ICacheMatch(void* one, void* two) { ASSERT((reinterpret_cast<intptr_t>(one) & CachePage::kPageMask) == 0); ASSERT((reinterpret_cast<intptr_t>(two) & CachePage::kPageMask) == 0); return one == two; } static uint32_t ICacheHash(void* key) { return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(key)) >> 2; } 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::FlushICache(v8::internal::HashMap* i_cache, void* start_addr, size_t size) { intptr_t start = reinterpret_cast<intptr_t>(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; FlushOnePage(i_cache, start, bytes_to_flush); start += bytes_to_flush; size -= bytes_to_flush; ASSERT_EQ(0, start & CachePage::kPageMask); offset = 0; } if (size != 0) { FlushOnePage(i_cache, start, size); } } CachePage* Simulator::GetCachePage(v8::internal::HashMap* i_cache, void* page) { v8::internal::HashMap::Entry* entry = i_cache->Lookup(page, ICacheHash(page), true); if (entry->value == NULL) { CachePage* new_page = new CachePage(); entry->value = new_page; } return reinterpret_cast<CachePage*>(entry->value); } // Flush from start up to and not including start + size. void Simulator::FlushOnePage(v8::internal::HashMap* i_cache, intptr_t start, int size) { ASSERT(size <= CachePage::kPageSize); ASSERT(AllOnOnePage(start, size - 1)); ASSERT((start & CachePage::kLineMask) == 0); ASSERT((size & CachePage::kLineMask) == 0); void* page = reinterpret_cast<void*>(start & (~CachePage::kPageMask)); int offset = (start & CachePage::kPageMask); CachePage* cache_page = GetCachePage(i_cache, page); char* valid_bytemap = cache_page->ValidityByte(offset); memset(valid_bytemap, CachePage::LINE_INVALID, size >> CachePage::kLineShift); } void Simulator::CheckICache(v8::internal::HashMap* i_cache, Instruction* instr) { intptr_t address = reinterpret_cast<intptr_t>(instr); void* page = reinterpret_cast<void*>(address & (~CachePage::kPageMask)); void* line = reinterpret_cast<void*>(address & (~CachePage::kLineMask)); int offset = (address & CachePage::kPageMask); CachePage* cache_page = GetCachePage(i_cache, 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. CHECK(memcmp(reinterpret_cast<void*>(instr), cache_page->CachedData(offset), Instruction::kInstrSize) == 0); } else { // Cache miss. Load memory into the cache. memcpy(cached_line, line, CachePage::kLineLength); *cache_valid_byte = CachePage::LINE_VALID; } } void Simulator::Initialize(Isolate* isolate) { if (isolate->simulator_initialized()) return; isolate->set_simulator_initialized(true); ::v8::internal::ExternalReference::set_redirector(isolate, &RedirectExternalReference); } Simulator::Simulator(Isolate* isolate) : isolate_(isolate) { i_cache_ = isolate_->simulator_i_cache(); if (i_cache_ == NULL) { i_cache_ = new v8::internal::HashMap(&ICacheMatch); isolate_->set_simulator_i_cache(i_cache_); } Initialize(isolate); // Setup simulator support first. Some of this information is needed to // setup the architecture state. stack_ = reinterpret_cast<char*>(malloc(stack_size_)); pc_modified_ = false; icount_ = 0; break_count_ = 0; break_pc_ = NULL; break_instr_ = 0; // Setup architecture state. // All registers are initialized to zero to start with. for (int i = 0; i < kNumSimuRegisters; i++) { registers_[i] = 0; } for (int i = 0; i < kNumFPURegisters; i++) { FPUregisters_[i] = 0; } FCSR_ = 0; // 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<int32_t>(stack_) + stack_size_ - 64; // 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; InitializeCoverage(); for (int i = 0; i < kNumExceptions; i++) { exceptions[i] = 0; } } // 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 { public: Redirection(void* external_function, ExternalReference::Type type) : external_function_(external_function), swi_instruction_(rtCallRedirInstr), type_(type), next_(NULL) { Isolate* isolate = Isolate::Current(); next_ = isolate->simulator_redirection(); Simulator::current(isolate)-> FlushICache(isolate->simulator_i_cache(), reinterpret_cast<void*>(&swi_instruction_), Instruction::kInstrSize); isolate->set_simulator_redirection(this); } void* address_of_swi_instruction() { return reinterpret_cast<void*>(&swi_instruction_); } void* external_function() { return external_function_; } ExternalReference::Type type() { return type_; } static Redirection* Get(void* external_function, ExternalReference::Type type) { Isolate* isolate = Isolate::Current(); Redirection* current = isolate->simulator_redirection(); for (; current != NULL; current = current->next_) { if (current->external_function_ == external_function) return current; } return new Redirection(external_function, type); } static Redirection* FromSwiInstruction(Instruction* swi_instruction) { char* addr_of_swi = reinterpret_cast<char*>(swi_instruction); char* addr_of_redirection = addr_of_swi - OFFSET_OF(Redirection, swi_instruction_); return reinterpret_cast<Redirection*>(addr_of_redirection); } private: void* external_function_; uint32_t swi_instruction_; ExternalReference::Type type_; Redirection* next_; }; void* Simulator::RedirectExternalReference(void* external_function, ExternalReference::Type type) { Redirection* redirection = Redirection::Get(external_function, type); return redirection->address_of_swi_instruction(); } // Get the active Simulator for the current thread. Simulator* Simulator::current(Isolate* isolate) { v8::internal::Isolate::PerIsolateThreadData* isolate_data = isolate->FindOrAllocatePerThreadDataForThisThread(); ASSERT(isolate_data != NULL); ASSERT(isolate_data != NULL); Simulator* sim = isolate_data->simulator(); if (sim == NULL) { // TODO(146): delete the simulator object when a thread/isolate goes away. sim = new Simulator(isolate); isolate_data->set_simulator(sim); } return sim; } // Sets the register in the architecture state. It will also deal with updating // Simulator internal state for special registers such as PC. void Simulator::set_register(int reg, int32_t value) { ASSERT((reg >= 0) && (reg < kNumSimuRegisters)); if (reg == pc) { pc_modified_ = true; } // Zero register always holds 0. registers_[reg] = (reg == 0) ? 0 : value; } void Simulator::set_fpu_register(int fpureg, int32_t value) { ASSERT((fpureg >= 0) && (fpureg < kNumFPURegisters)); FPUregisters_[fpureg] = value; } void Simulator::set_fpu_register_float(int fpureg, float value) { ASSERT((fpureg >= 0) && (fpureg < kNumFPURegisters)); *BitCast<float*>(&FPUregisters_[fpureg]) = value; } void Simulator::set_fpu_register_double(int fpureg, double value) { ASSERT((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); *BitCast<double*>(&FPUregisters_[fpureg]) = value; } // Get the register from the architecture state. This function does handle // the special case of accessing the PC register. int32_t Simulator::get_register(int reg) const { ASSERT((reg >= 0) && (reg < kNumSimuRegisters)); if (reg == 0) return 0; else return registers_[reg] + ((reg == pc) ? Instruction::kPCReadOffset : 0); } int32_t Simulator::get_fpu_register(int fpureg) const { ASSERT((fpureg >= 0) && (fpureg < kNumFPURegisters)); return FPUregisters_[fpureg]; } int64_t Simulator::get_fpu_register_long(int fpureg) const { ASSERT((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); return *BitCast<int64_t*>( const_cast<int32_t*>(&FPUregisters_[fpureg])); } float Simulator::get_fpu_register_float(int fpureg) const { ASSERT((fpureg >= 0) && (fpureg < kNumFPURegisters)); return *BitCast<float*>( const_cast<int32_t*>(&FPUregisters_[fpureg])); } double Simulator::get_fpu_register_double(int fpureg) const { ASSERT((fpureg >= 0) && (fpureg < kNumFPURegisters) && ((fpureg % 2) == 0)); return *BitCast<double*>(const_cast<int32_t*>(&FPUregisters_[fpureg])); } // For use in calls that take two double values, constructed either // from a0-a3 or f12 and f14. void Simulator::GetFpArgs(double* x, double* y) { if (!IsMipsSoftFloatABI) { *x = get_fpu_register_double(12); *y = get_fpu_register_double(14); } else { // We use a char buffer to get around the strict-aliasing rules which // otherwise allow the compiler to optimize away the copy. char buffer[sizeof(*x)]; int32_t* reg_buffer = reinterpret_cast<int32_t*>(buffer); // Registers a0 and a1 -> x. reg_buffer[0] = get_register(a0); reg_buffer[1] = get_register(a1); memcpy(x, buffer, sizeof(buffer)); // Registers a2 and a3 -> y. reg_buffer[0] = get_register(a2); reg_buffer[1] = get_register(a3); memcpy(y, buffer, sizeof(buffer)); } } // For use in calls that take one double value, constructed either // from a0 and a1 or f12. void Simulator::GetFpArgs(double* x) { if (!IsMipsSoftFloatABI) { *x = get_fpu_register_double(12); } else { // We use a char buffer to get around the strict-aliasing rules which // otherwise allow the compiler to optimize away the copy. char buffer[sizeof(*x)]; int32_t* reg_buffer = reinterpret_cast<int32_t*>(buffer); // Registers a0 and a1 -> x. reg_buffer[0] = get_register(a0); reg_buffer[1] = get_register(a1); memcpy(x, buffer, sizeof(buffer)); } } // For use in calls that take one double value constructed either // from a0 and a1 or f12 and one integer value. void Simulator::GetFpArgs(double* x, int32_t* y) { if (!IsMipsSoftFloatABI) { *x = get_fpu_register_double(12); *y = get_register(a2); } else { // We use a char buffer to get around the strict-aliasing rules which // otherwise allow the compiler to optimize away the copy. char buffer[sizeof(*x)]; int32_t* reg_buffer = reinterpret_cast<int32_t*>(buffer); // Registers 0 and 1 -> x. reg_buffer[0] = get_register(a0); reg_buffer[1] = get_register(a1); memcpy(x, buffer, sizeof(buffer)); // Register 2 -> y. reg_buffer[0] = get_register(a2); memcpy(y, buffer, sizeof(*y)); } } // The return value is either in v0/v1 or f0. void Simulator::SetFpResult(const double& result) { if (!IsMipsSoftFloatABI) { set_fpu_register_double(0, result); } else { char buffer[2 * sizeof(registers_[0])]; int32_t* reg_buffer = reinterpret_cast<int32_t*>(buffer); memcpy(buffer, &result, sizeof(buffer)); // Copy result to v0 and v1. set_register(v0, reg_buffer[0]); set_register(v1, reg_buffer[1]); } } // Helper functions for setting and testing the FCSR register's bits. void Simulator::set_fcsr_bit(uint32_t cc, bool value) { if (value) { FCSR_ |= (1 << cc); } else { FCSR_ &= ~(1 << cc); } } bool Simulator::test_fcsr_bit(uint32_t cc) { return FCSR_ & (1 << cc); } // Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. bool Simulator::set_fcsr_round_error(double original, double rounded) { bool ret = false; if (!isfinite(original) || !isfinite(rounded)) { set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } if (original != rounded) { set_fcsr_bit(kFCSRInexactFlagBit, true); } if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) { set_fcsr_bit(kFCSRUnderflowFlagBit, true); ret = true; } if (rounded > INT_MAX || rounded < INT_MIN) { set_fcsr_bit(kFCSROverflowFlagBit, true); // The reference is not really clear but it seems this is required: set_fcsr_bit(kFCSRInvalidOpFlagBit, true); ret = true; } return ret; } // Raw access to the PC register. void Simulator::set_pc(int32_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. int32_t Simulator::get_pc() const { return registers_[pc]; } // The MIPS cannot do unaligned reads and writes. On some MIPS platforms an // interrupt is caused. On others it does a funky rotation thing. For now we // simply disallow unaligned reads, but at some point we may want to move to // emulating the rotate behaviour. Note that simulator runs have the runtime // system running directly on the host system and only generated code is // executed in the simulator. Since the host is typically IA32 we will not // get the correct MIPS-like behaviour on unaligned accesses. int Simulator::ReadW(int32_t addr, Instruction* instr) { if (addr >=0 && addr < 0x400) { // This has to be a NULL-dereference, drop into debugger. MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0) { intptr_t* ptr = reinterpret_cast<intptr_t*>(addr); return *ptr; } PrintF("Unaligned read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr)); MipsDebugger dbg(this); dbg.Debug(); return 0; } void Simulator::WriteW(int32_t addr, int value, Instruction* instr) { if (addr >= 0 && addr < 0x400) { // This has to be a NULL-dereference, drop into debugger. MipsDebugger dbg(this); dbg.Debug(); } if ((addr & kPointerAlignmentMask) == 0) { intptr_t* ptr = reinterpret_cast<intptr_t*>(addr); *ptr = value; return; } PrintF("Unaligned write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr)); MipsDebugger dbg(this); dbg.Debug(); } double Simulator::ReadD(int32_t addr, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0) { double* ptr = reinterpret_cast<double*>(addr); return *ptr; } PrintF("Unaligned (double) read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr)); OS::Abort(); return 0; } void Simulator::WriteD(int32_t addr, double value, Instruction* instr) { if ((addr & kDoubleAlignmentMask) == 0) { double* ptr = reinterpret_cast<double*>(addr); *ptr = value; return; } PrintF("Unaligned (double) write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr)); OS::Abort(); } uint16_t Simulator::ReadHU(int32_t addr, Instruction* instr) { if ((addr & 1) == 0) { uint16_t* ptr = reinterpret_cast<uint16_t*>(addr); return *ptr; } PrintF("Unaligned unsigned halfword read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr)); OS::Abort(); return 0; } int16_t Simulator::ReadH(int32_t addr, Instruction* instr) { if ((addr & 1) == 0) { int16_t* ptr = reinterpret_cast<int16_t*>(addr); return *ptr; } PrintF("Unaligned signed halfword read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr)); OS::Abort(); return 0; } void Simulator::WriteH(int32_t addr, uint16_t value, Instruction* instr) { if ((addr & 1) == 0) { uint16_t* ptr = reinterpret_cast<uint16_t*>(addr); *ptr = value; return; } PrintF("Unaligned unsigned halfword write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr)); OS::Abort(); } void Simulator::WriteH(int32_t addr, int16_t value, Instruction* instr) { if ((addr & 1) == 0) { int16_t* ptr = reinterpret_cast<int16_t*>(addr); *ptr = value; return; } PrintF("Unaligned halfword write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr)); OS::Abort(); } uint32_t Simulator::ReadBU(int32_t addr) { uint8_t* ptr = reinterpret_cast<uint8_t*>(addr); return *ptr & 0xff; } int32_t Simulator::ReadB(int32_t addr) { int8_t* ptr = reinterpret_cast<int8_t*>(addr); return *ptr; } void Simulator::WriteB(int32_t addr, uint8_t value) { uint8_t* ptr = reinterpret_cast<uint8_t*>(addr); *ptr = value; } void Simulator::WriteB(int32_t addr, int8_t value) { int8_t* ptr = reinterpret_cast<int8_t*>(addr); *ptr = value; } // Returns the limit of the stack area to enable checking for stack overflows. uintptr_t Simulator::StackLimit() const { // Leave a safety margin of 256 bytes to prevent overrunning the stack when // pushing values. return reinterpret_cast<uintptr_t>(stack_) + 256; } // Unsupported instructions use Format to print an error and stop execution. void Simulator::Format(Instruction* instr, const char* format) { PrintF("Simulator found unsupported instruction:\n 0x%08x: %s\n", reinterpret_cast<intptr_t>(instr), format); UNIMPLEMENTED_MIPS(); } // Calls into the V8 runtime are based on this very simple interface. // Note: To be able to return two values from some calls the code in runtime.cc // uses the ObjectPair which is essentially two 32-bit values stuffed into a // 64-bit value. With the code below we assume that all runtime calls return // 64 bits of result. If they don't, the v1 result register contains a bogus // value, which is fine because it is caller-saved. typedef int64_t (*SimulatorRuntimeCall)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5); typedef double (*SimulatorRuntimeFPCall)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3); // This signature supports direct call in to API function native callback // (refer to InvocationCallback in v8.h). typedef v8::Handle<v8::Value> (*SimulatorRuntimeDirectApiCall)(int32_t arg0); // This signature supports direct call to accessor getter callback. typedef v8::Handle<v8::Value> (*SimulatorRuntimeDirectGetterCall)(int32_t arg0, int32_t arg1); // Software interrupt instructions are used by the simulator to call into the // C-based V8 runtime. They are also used for debugging with simulator. void Simulator::SoftwareInterrupt(Instruction* instr) { // There are several instructions that could get us here, // the break_ instruction, or several variants of traps. All // Are "SPECIAL" class opcode, and are distinuished by function. int32_t func = instr->FunctionFieldRaw(); uint32_t code = (func == BREAK) ? instr->Bits(25, 6) : -1; // We first check if we met a call_rt_redirected. if (instr->InstructionBits() == rtCallRedirInstr) { Redirection* redirection = Redirection::FromSwiInstruction(instr); int32_t arg0 = get_register(a0); int32_t arg1 = get_register(a1); int32_t arg2 = get_register(a2); int32_t arg3 = get_register(a3); int32_t arg4 = 0; int32_t arg5 = 0; // Need to check if sp is valid before assigning arg4, arg5. // This is a fix for cctest test-api/CatchStackOverflow which causes // the stack to overflow. For some reason arm doesn't need this // stack check here. int32_t* stack_pointer = reinterpret_cast<int32_t*>(get_register(sp)); int32_t* stack = reinterpret_cast<int32_t*>(stack_); if (stack_pointer >= stack && stack_pointer < stack + stack_size_ - 5) { // Args 4 and 5 are on the stack after the reserved space for args 0..3. arg4 = stack_pointer[4]; arg5 = stack_pointer[5]; } bool fp_call = (redirection->type() == ExternalReference::BUILTIN_FP_FP_CALL) || (redirection->type() == ExternalReference::BUILTIN_COMPARE_CALL) || (redirection->type() == ExternalReference::BUILTIN_FP_CALL) || (redirection->type() == ExternalReference::BUILTIN_FP_INT_CALL); if (!IsMipsSoftFloatABI) { // With the hard floating point calling convention, double // arguments are passed in FPU registers. Fetch the arguments // from there and call the builtin using soft floating point // convention. switch (redirection->type()) { case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_COMPARE_CALL: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(f13); arg2 = get_fpu_register(f14); arg3 = get_fpu_register(f15); break; case ExternalReference::BUILTIN_FP_CALL: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(f13); break; case ExternalReference::BUILTIN_FP_INT_CALL: arg0 = get_fpu_register(f12); arg1 = get_fpu_register(f13); arg2 = get_register(a2); break; default: break; } } // This is dodgy but it works because the C entry stubs are never moved. // See comment in codegen-arm.cc and bug 1242173. int32_t saved_ra = get_register(ra); intptr_t external = reinterpret_cast<intptr_t>(redirection->external_function()); // Based on CpuFeatures::IsSupported(FPU), Mips will use either hardware // FPU, or gcc soft-float routines. Hardware FPU is simulated in this // simulator. Soft-float has additional abstraction of ExternalReference, // to support serialization. if (fp_call) { SimulatorRuntimeFPCall target = reinterpret_cast<SimulatorRuntimeFPCall>(external); if (::v8::internal::FLAG_trace_sim) { double dval0, dval1; int32_t ival; switch (redirection->type()) { case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_COMPARE_CALL: GetFpArgs(&dval0, &dval1); PrintF("Call to host function at %p with args %f, %f", FUNCTION_ADDR(target), dval0, dval1); break; case ExternalReference::BUILTIN_FP_CALL: GetFpArgs(&dval0); PrintF("Call to host function at %p with arg %f", FUNCTION_ADDR(target), dval0); break; case ExternalReference::BUILTIN_FP_INT_CALL: GetFpArgs(&dval0, &ival); PrintF("Call to host function at %p with args %f, %d", FUNCTION_ADDR(target), dval0, ival); break; default: UNREACHABLE(); break; } } double result = target(arg0, arg1, arg2, arg3); if (redirection->type() != ExternalReference::BUILTIN_COMPARE_CALL) { SetFpResult(result); } else { int32_t gpreg_pair[2]; memcpy(&gpreg_pair[0], &result, 2 * sizeof(int32_t)); set_register(v0, gpreg_pair[0]); set_register(v1, gpreg_pair[1]); } } else if (redirection->type() == ExternalReference::DIRECT_API_CALL) { // See DirectCEntryStub::GenerateCall for explanation of register usage. SimulatorRuntimeDirectApiCall target = reinterpret_cast<SimulatorRuntimeDirectApiCall>(external); if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x\n", FUNCTION_ADDR(target), arg1); } v8::Handle<v8::Value> result = target(arg1); *(reinterpret_cast<int*>(arg0)) = (int32_t) *result; set_register(v0, arg0); } else if (redirection->type() == ExternalReference::DIRECT_GETTER_CALL) { // See DirectCEntryStub::GenerateCall for explanation of register usage. SimulatorRuntimeDirectGetterCall target = reinterpret_cast<SimulatorRuntimeDirectGetterCall>(external); if (::v8::internal::FLAG_trace_sim) { PrintF("Call to host function at %p args %08x %08x\n", FUNCTION_ADDR(target), arg1, arg2); } v8::Handle<v8::Value> result = target(arg1, arg2); *(reinterpret_cast<int*>(arg0)) = (int32_t) *result; set_register(v0, arg0); } else { SimulatorRuntimeCall target = reinterpret_cast<SimulatorRuntimeCall>(external); if (::v8::internal::FLAG_trace_sim) { PrintF( "Call to host function at %p " "args %08x, %08x, %08x, %08x, %08x, %08x\n", FUNCTION_ADDR(target), arg0, arg1, arg2, arg3, arg4, arg5); } int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5); set_register(v0, static_cast<int32_t>(result)); set_register(v1, static_cast<int32_t>(result >> 32)); } if (::v8::internal::FLAG_trace_sim) { PrintF("Returned %08x : %08x\n", get_register(v1), get_register(v0)); } set_register(ra, saved_ra); set_pc(get_register(ra)); } else if (func == BREAK && code <= kMaxStopCode) { if (IsWatchpoint(code)) { PrintWatchpoint(code); } else { IncreaseStopCounter(code); HandleStop(code, instr); } } else { // All remaining break_ codes, and all traps are handled here. MipsDebugger dbg(this); dbg.Debug(); } } // Stop helper functions. bool Simulator::IsWatchpoint(uint32_t code) { return (code <= kMaxWatchpointCode); } void Simulator::PrintWatchpoint(uint32_t code) { MipsDebugger dbg(this); ++break_count_; PrintF("\n---- break %d marker: %3d (instr count: %8d) ----------" "----------------------------------", code, break_count_, icount_); dbg.PrintAllRegs(); // Print registers and continue running. } void Simulator::HandleStop(uint32_t code, Instruction* instr) { // Stop if it is enabled, otherwise go on jumping over the stop // and the message address. if (IsEnabledStop(code)) { MipsDebugger dbg(this); dbg.Stop(instr); } else { set_pc(get_pc() + 2 * Instruction::kInstrSize); } } bool Simulator::IsStopInstruction(Instruction* instr) { int32_t func = instr->FunctionFieldRaw(); uint32_t code = static_cast<uint32_t>(instr->Bits(25, 6)); return (func == BREAK) && code > kMaxWatchpointCode && code <= kMaxStopCode; } bool Simulator::IsEnabledStop(uint32_t code) { ASSERT(code <= kMaxStopCode); ASSERT(code > kMaxWatchpointCode); return !(watched_stops[code].count & kStopDisabledBit); } void Simulator::EnableStop(uint32_t code) { if (!IsEnabledStop(code)) { watched_stops[code].count &= ~kStopDisabledBit; } } void Simulator::DisableStop(uint32_t code) { if (IsEnabledStop(code)) { watched_stops[code].count |= kStopDisabledBit; } } void Simulator::IncreaseStopCounter(uint32_t code) { ASSERT(code <= kMaxStopCode); if ((watched_stops[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); watched_stops[code].count = 0; EnableStop(code); } else { watched_stops[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 = watched_stops[code].count & ~kStopDisabledBit; // Don't print the state of unused breakpoints. if (count != 0) { if (watched_stops[code].desc) { PrintF("stop %i - 0x%x: \t%s, \tcounter = %i, \t%s\n", code, code, state, count, watched_stops[code].desc); } else { PrintF("stop %i - 0x%x: \t%s, \tcounter = %i\n", code, code, state, count); } } } void Simulator::SignalExceptions() { for (int i = 1; i < kNumExceptions; i++) { if (exceptions[i] != 0) { V8_Fatal(__FILE__, __LINE__, "Error: Exception %i raised.", i); } } } // Handle execution based on instruction types. void Simulator::ConfigureTypeRegister(Instruction* instr, int32_t& alu_out, int64_t& i64hilo, uint64_t& u64hilo, int32_t& next_pc, bool& do_interrupt) { // Every local variable declared here needs to be const. // This is to make sure that changed values are sent back to // DecodeTypeRegister correctly. // Instruction fields. const Opcode op = instr->OpcodeFieldRaw(); const int32_t rs_reg = instr->RsValue(); const int32_t rs = get_register(rs_reg); const uint32_t rs_u = static_cast<uint32_t>(rs); const int32_t rt_reg = instr->RtValue(); const int32_t rt = get_register(rt_reg); const uint32_t rt_u = static_cast<uint32_t>(rt); const int32_t rd_reg = instr->RdValue(); const uint32_t sa = instr->SaValue(); const int32_t fs_reg = instr->FsValue(); // ---------- Configuration. switch (op) { case COP1: // Coprocessor instructions. switch (instr->RsFieldRaw()) { case BC1: // Handled in DecodeTypeImmed, should never come here. UNREACHABLE(); break; case CFC1: // At the moment only FCSR is supported. ASSERT(fs_reg == kFCSRRegister); alu_out = FCSR_; break; case MFC1: alu_out = get_fpu_register(fs_reg); break; case MFHC1: UNIMPLEMENTED_MIPS(); break; case CTC1: case MTC1: case MTHC1: // Do the store in the execution step. break; case S: case D: case W: case L: case PS: // Do everything in the execution step. break; default: UNIMPLEMENTED_MIPS(); }; break; case SPECIAL: switch (instr->FunctionFieldRaw()) { case JR: case JALR: next_pc = get_register(instr->RsValue()); break; case SLL: alu_out = rt << sa; break; case SRL: if (rs_reg == 0) { // Regular logical right shift of a word by a fixed number of // bits instruction. RS field is always equal to 0. alu_out = rt_u >> sa; } else { // Logical right-rotate of a word by a fixed number of bits. This // is special case of SRL instruction, added in MIPS32 Release 2. // RS field is equal to 00001. alu_out = (rt_u >> sa) | (rt_u << (32 - sa)); } break; case SRA: alu_out = rt >> sa; break; case SLLV: alu_out = rt << rs; break; case SRLV: if (sa == 0) { // Regular logical right-shift of a word by a variable number of // bits instruction. SA field is always equal to 0. alu_out = rt_u >> rs; } else { // Logical right-rotate of a word by a variable number of bits. // This is special case od SRLV instruction, added in MIPS32 // Release 2. SA field is equal to 00001. alu_out = (rt_u >> rs_u) | (rt_u << (32 - rs_u)); } break; case SRAV: alu_out = rt >> rs; break; case MFHI: alu_out = get_register(HI); break; case MFLO: alu_out = get_register(LO); break; case MULT: i64hilo = static_cast<int64_t>(rs) * static_cast<int64_t>(rt); break; case MULTU: u64hilo = static_cast<uint64_t>(rs_u) * static_cast<uint64_t>(rt_u); break; case ADD: if (HaveSameSign(rs, rt)) { if (rs > 0) { exceptions[kIntegerOverflow] = rs > (Registers::kMaxValue - rt); } else if (rs < 0) { exceptions[kIntegerUnderflow] = rs < (Registers::kMinValue - rt); } } alu_out = rs + rt; break; case ADDU: alu_out = rs + rt; break; case SUB: if (!HaveSameSign(rs, rt)) { if (rs > 0) { exceptions[kIntegerOverflow] = rs > (Registers::kMaxValue + rt); } else if (rs < 0) { exceptions[kIntegerUnderflow] = rs < (Registers::kMinValue + rt); } } alu_out = rs - rt; break; case SUBU: alu_out = rs - rt; break; case AND: alu_out = rs & rt; break; case OR: alu_out = rs | rt; break; case XOR: alu_out = rs ^ rt; break; case NOR: alu_out = ~(rs | rt); break; case SLT: alu_out = rs < rt ? 1 : 0; break; case SLTU: alu_out = rs_u < rt_u ? 1 : 0; break; // Break and trap instructions. case BREAK: do_interrupt = true; break; case TGE: do_interrupt = rs >= rt; break; case TGEU: do_interrupt = rs_u >= rt_u; break; case TLT: do_interrupt = rs < rt; break; case TLTU: do_interrupt = rs_u < rt_u; break; case TEQ: do_interrupt = rs == rt; break; case TNE: do_interrupt = rs != rt; break; case MOVN: case MOVZ: case MOVCI: // No action taken on decode. break; case DIV: case DIVU: // div and divu never raise exceptions. break; default: UNREACHABLE(); }; break; case SPECIAL2: switch (instr->FunctionFieldRaw()) { case MUL: alu_out = rs_u * rt_u; // Only the lower 32 bits are kept. break; case CLZ: alu_out = __builtin_clz(rs_u); break; default: UNREACHABLE(); }; break; case SPECIAL3: switch (instr->FunctionFieldRaw()) { case INS: { // Mips32r2 instruction. // Interpret rd field as 5-bit msb of insert. uint16_t msb = rd_reg; // Interpret sa field as 5-bit lsb of insert. uint16_t lsb = sa; uint16_t size = msb - lsb + 1; uint32_t mask = (1 << size) - 1; alu_out = (rt_u & ~(mask << lsb)) | ((rs_u & mask) << lsb); break; } case EXT: { // Mips32r2 instruction. // Interpret rd field as 5-bit msb of extract. uint16_t msb = rd_reg; // Interpret sa field as 5-bit lsb of extract. uint16_t lsb = sa; uint16_t size = msb + 1; uint32_t mask = (1 << size) - 1; alu_out = (rs_u & (mask << lsb)) >> lsb; break; } default: UNREACHABLE(); }; break; default: UNREACHABLE(); }; } void Simulator::DecodeTypeRegister(Instruction* instr) { // Instruction fields. const Opcode op = instr->OpcodeFieldRaw(); const int32_t rs_reg = instr->RsValue(); const int32_t rs = get_register(rs_reg); const uint32_t rs_u = static_cast<uint32_t>(rs); const int32_t rt_reg = instr->RtValue(); const int32_t rt = get_register(rt_reg); const uint32_t rt_u = static_cast<uint32_t>(rt); const int32_t rd_reg = instr->RdValue(); const int32_t fs_reg = instr->FsValue(); const int32_t ft_reg = instr->FtValue(); const int32_t fd_reg = instr->FdValue(); int64_t i64hilo = 0; uint64_t u64hilo = 0; // ALU output. // It should not be used as is. Instructions using it should always // initialize it first. int32_t alu_out = 0x12345678; // For break and trap instructions. bool do_interrupt = false; // For jr and jalr. // Get current pc. int32_t current_pc = get_pc(); // Next pc int32_t next_pc = 0; // Setup the variables if needed before executing the instruction. ConfigureTypeRegister(instr, alu_out, i64hilo, u64hilo, next_pc, do_interrupt); // ---------- Raise exceptions triggered. SignalExceptions(); // ---------- Execution. switch (op) { case COP1: switch (instr->RsFieldRaw()) { case BC1: // Branch on coprocessor condition. UNREACHABLE(); break; case CFC1: set_register(rt_reg, alu_out); case MFC1: set_register(rt_reg, alu_out); break; case MFHC1: UNIMPLEMENTED_MIPS(); break; case CTC1: // At the moment only FCSR is supported. ASSERT(fs_reg == kFCSRRegister); FCSR_ = registers_[rt_reg]; break; case MTC1: FPUregisters_[fs_reg] = registers_[rt_reg]; break; case MTHC1: UNIMPLEMENTED_MIPS(); break; case S: float f; switch (instr->FunctionFieldRaw()) { case CVT_D_S: f = get_fpu_register_float(fs_reg); set_fpu_register_double(fd_reg, static_cast<double>(f)); break; case CVT_W_S: case CVT_L_S: case TRUNC_W_S: case TRUNC_L_S: case ROUND_W_S: case ROUND_L_S: case FLOOR_W_S: case FLOOR_L_S: case CEIL_W_S: case CEIL_L_S: case CVT_PS_S: UNIMPLEMENTED_MIPS(); break; default: UNREACHABLE(); } break; case D: double ft, fs; uint32_t cc, fcsr_cc; int64_t i64; fs = get_fpu_register_double(fs_reg); ft = get_fpu_register_double(ft_reg); cc = instr->FCccValue(); fcsr_cc = get_fcsr_condition_bit(cc); switch (instr->FunctionFieldRaw()) { case ADD_D: set_fpu_register_double(fd_reg, fs + ft); break; case SUB_D: set_fpu_register_double(fd_reg, fs - ft); break; case MUL_D: set_fpu_register_double(fd_reg, fs * ft); break; case DIV_D: set_fpu_register_double(fd_reg, fs / ft); break; case ABS_D: set_fpu_register_double(fd_reg, fs < 0 ? -fs : fs); break; case MOV_D: set_fpu_register_double(fd_reg, fs); break; case NEG_D: set_fpu_register_double(fd_reg, -fs); break; case SQRT_D: set_fpu_register_double(fd_reg, sqrt(fs)); break; case C_UN_D: set_fcsr_bit(fcsr_cc, isnan(fs) || isnan(ft)); break; case C_EQ_D: set_fcsr_bit(fcsr_cc, (fs == ft)); break; case C_UEQ_D: set_fcsr_bit(fcsr_cc, (fs == ft) || (isnan(fs) || isnan(ft))); break; case C_OLT_D: set_fcsr_bit(fcsr_cc, (fs < ft)); break; case C_ULT_D: set_fcsr_bit(fcsr_cc, (fs < ft) || (isnan(fs) || isnan(ft))); break; case C_OLE_D: set_fcsr_bit(fcsr_cc, (fs <= ft)); break; case C_ULE_D: set_fcsr_bit(fcsr_cc, (fs <= ft) || (isnan(fs) || isnan(ft))); break; case CVT_W_D: // Convert double to word. // Rounding modes are not yet supported. ASSERT((FCSR_ & 3) == 0); // In rounding mode 0 it should behave like ROUND. case ROUND_W_D: // Round double to word. { double rounded = fs > 0 ? floor(fs + 0.5) : ceil(fs - 0.5); int32_t result = static_cast<int32_t>(rounded); set_fpu_register(fd_reg, result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register(fd_reg, kFPUInvalidResult); } } break; case TRUNC_W_D: // Truncate double to word (round towards 0). { double rounded = trunc(fs); int32_t result = static_cast<int32_t>(rounded); set_fpu_register(fd_reg, result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register(fd_reg, kFPUInvalidResult); } } break; case FLOOR_W_D: // Round double to word towards negative infinity. { double rounded = floor(fs); int32_t result = static_cast<int32_t>(rounded); set_fpu_register(fd_reg, result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register(fd_reg, kFPUInvalidResult); } } break; case CEIL_W_D: // Round double to word towards positive infinity. { double rounded = ceil(fs); int32_t result = static_cast<int32_t>(rounded); set_fpu_register(fd_reg, result); if (set_fcsr_round_error(fs, rounded)) { set_fpu_register(fd_reg, kFPUInvalidResult); } } break; case CVT_S_D: // Convert double to float (single). set_fpu_register_float(fd_reg, static_cast<float>(fs)); break; case CVT_L_D: { // Mips32r2: Truncate double to 64-bit long-word. double rounded = trunc(fs); i64 = static_cast<int64_t>(rounded); set_fpu_register(fd_reg, i64 & 0xffffffff); set_fpu_register(fd_reg + 1, i64 >> 32); break; } case TRUNC_L_D: { // Mips32r2 instruction. double rounded = trunc(fs); i64 = static_cast<int64_t>(rounded); set_fpu_register(fd_reg, i64 & 0xffffffff); set_fpu_register(fd_reg + 1, i64 >> 32); break; } case ROUND_L_D: { // Mips32r2 instruction. double rounded = fs > 0 ? floor(fs + 0.5) : ceil(fs - 0.5); i64 = static_cast<int64_t>(rounded); set_fpu_register(fd_reg, i64 & 0xffffffff); set_fpu_register(fd_reg + 1, i64 >> 32); break; } case FLOOR_L_D: // Mips32r2 instruction. i64 = static_cast<int64_t>(floor(fs)); set_fpu_register(fd_reg, i64 & 0xffffffff); set_fpu_register(fd_reg + 1, i64 >> 32); break; case CEIL_L_D: // Mips32r2 instruction. i64 = static_cast<int64_t>(ceil(fs)); set_fpu_register(fd_reg, i64 & 0xffffffff); set_fpu_register(fd_reg + 1, i64 >> 32); break; case C_F_D: UNIMPLEMENTED_MIPS(); break; default: UNREACHABLE(); } break; case W: switch (instr->FunctionFieldRaw()) { case CVT_S_W: // Convert word to float (single). alu_out = get_fpu_register(fs_reg); set_fpu_register_float(fd_reg, static_cast<float>(alu_out)); break; case CVT_D_W: // Convert word to double. alu_out = get_fpu_register(fs_reg); set_fpu_register_double(fd_reg, static_cast<double>(alu_out)); break; default: UNREACHABLE(); }; break; case L: switch (instr->FunctionFieldRaw()) { case CVT_D_L: // Mips32r2 instruction. // Watch the signs here, we want 2 32-bit vals // to make a sign-64. i64 = (uint32_t) get_fpu_register(fs_reg); i64 |= ((int64_t) get_fpu_register(fs_reg + 1) << 32); set_fpu_register_double(fd_reg, static_cast<double>(i64)); break; case CVT_S_L: UNIMPLEMENTED_MIPS(); break; default: UNREACHABLE(); } break; case PS: break; default: UNREACHABLE(); }; break; case SPECIAL: switch (instr->FunctionFieldRaw()) { case JR: { Instruction* branch_delay_instr = reinterpret_cast<Instruction*>( current_pc+Instruction::kInstrSize); BranchDelayInstructionDecode(branch_delay_instr); set_pc(next_pc); pc_modified_ = true; break; } case JALR: { Instruction* branch_delay_instr = reinterpret_cast<Instruction*>( current_pc+Instruction::kInstrSize); BranchDelayInstructionDecode(branch_delay_instr); set_register(31, current_pc + 2 * Instruction::kInstrSize); set_pc(next_pc); pc_modified_ = true; break; } // Instructions using HI and LO registers. case MULT: set_register(LO, static_cast<int32_t>(i64hilo & 0xffffffff)); set_register(HI, static_cast<int32_t>(i64hilo >> 32)); break; case MULTU: set_register(LO, static_cast<int32_t>(u64hilo & 0xffffffff)); set_register(HI, static_cast<int32_t>(u64hilo >> 32)); break; case DIV: // Divide by zero was not checked in the configuration step - div and // divu do not raise exceptions. On division by 0, the result will // be UNPREDICTABLE. if (rt != 0) { set_register(LO, rs / rt); set_register(HI, rs % rt); } break; case DIVU: if (rt_u != 0) { set_register(LO, rs_u / rt_u); set_register(HI, rs_u % rt_u); } break; // Break and trap instructions. case BREAK: case TGE: case TGEU: case TLT: case TLTU: case TEQ: case TNE: if (do_interrupt) { SoftwareInterrupt(instr); } break; // Conditional moves. case MOVN: if (rt) set_register(rd_reg, rs); break; case MOVCI: { uint32_t cc = instr->FBccValue(); uint32_t fcsr_cc = get_fcsr_condition_bit(cc); if (instr->Bit(16)) { // Read Tf bit. if (test_fcsr_bit(fcsr_cc)) set_register(rd_reg, rs); } else { if (!test_fcsr_bit(fcsr_cc)) set_register(rd_reg, rs); } break; } case MOVZ: if (!rt) set_register(rd_reg, rs); break; default: // For other special opcodes we do the default operation. set_register(rd_reg, alu_out); }; break; case SPECIAL2: switch (instr->FunctionFieldRaw()) { case MUL: set_register(rd_reg, alu_out); // HI and LO are UNPREDICTABLE after the operation. set_register(LO, Unpredictable); set_register(HI, Unpredictable); break; default: // For other special2 opcodes we do the default operation. set_register(rd_reg, alu_out); } break; case SPECIAL3: switch (instr->FunctionFieldRaw()) { case INS: // Ins instr leaves result in Rt, rather than Rd. set_register(rt_reg, alu_out); break; case EXT: // Ext instr leaves result in Rt, rather than Rd. set_register(rt_reg, alu_out); break; default: UNREACHABLE(); }; break; // Unimplemented opcodes raised an error in the configuration step before, // so we can use the default here to set the destination register in common // cases. default: set_register(rd_reg, alu_out); }; } // Type 2: instructions using a 16 bytes immediate. (eg: addi, beq). void Simulator::DecodeTypeImmediate(Instruction* instr) { // Instruction fields. Opcode op = instr->OpcodeFieldRaw(); int32_t rs = get_register(instr->RsValue()); uint32_t rs_u = static_cast<uint32_t>(rs); int32_t rt_reg = instr->RtValue(); // Destination register. int32_t rt = get_register(rt_reg); int16_t imm16 = instr->Imm16Value(); int32_t ft_reg = instr->FtValue(); // Destination register. // Zero extended immediate. uint32_t oe_imm16 = 0xffff & imm16; // Sign extended immediate. int32_t se_imm16 = imm16; // Get current pc. int32_t current_pc = get_pc(); // Next pc. int32_t next_pc = bad_ra; // Used for conditional branch instructions. bool do_branch = false; bool execute_branch_delay_instruction = false; // Used for arithmetic instructions. int32_t alu_out = 0; // Floating point. double fp_out = 0.0; uint32_t cc, cc_value, fcsr_cc; // Used for memory instructions. int32_t addr = 0x0; // Value to be written in memory. uint32_t mem_value = 0x0; // ---------- Configuration (and execution for REGIMM). switch (op) { // ------------- COP1. Coprocessor instructions. case COP1: switch (instr->RsFieldRaw()) { case BC1: // Branch on coprocessor condition. cc = instr->FBccValue(); fcsr_cc = get_fcsr_condition_bit(cc); cc_value = test_fcsr_bit(fcsr_cc); do_branch = (instr->FBtrueValue()) ? cc_value : !cc_value; execute_branch_delay_instruction = true; // Set next_pc. if (do_branch) { next_pc = current_pc + (imm16 << 2) + Instruction::kInstrSize; } else { next_pc = current_pc + kBranchReturnOffset; } break; default: UNREACHABLE(); }; break; // ------------- REGIMM class. case REGIMM: switch (instr->RtFieldRaw()) { case BLTZ: do_branch = (rs < 0); break; case BLTZAL: do_branch = rs < 0; break; case BGEZ: do_branch = rs >= 0; break; case BGEZAL: do_branch = rs >= 0; break; default: UNREACHABLE(); }; switch (instr->RtFieldRaw()) { case BLTZ: case BLTZAL: case BGEZ: case BGEZAL: // Branch instructions common part. execute_branch_delay_instruction = true; // Set next_pc. if (do_branch) { next_pc = current_pc + (imm16 << 2) + Instruction::kInstrSize; if (instr->IsLinkingInstruction()) { set_register(31, current_pc + kBranchReturnOffset); } } else { next_pc = current_pc + kBranchReturnOffset; } default: break; }; break; // case REGIMM. // ------------- Branch instructions. // When comparing to zero, the encoding of rt field is always 0, so we don't // need to replace rt with zero. case BEQ: do_branch = (rs == rt); break; case BNE: do_branch = rs != rt; break; case BLEZ: do_branch = rs <= 0; break; case BGTZ: do_branch = rs > 0; break; // ------------- Arithmetic instructions. case ADDI: if (HaveSameSign(rs, se_imm16)) { if (rs > 0) { exceptions[kIntegerOverflow] = rs > (Registers::kMaxValue - se_imm16); } else if (rs < 0) { exceptions[kIntegerUnderflow] = rs < (Registers::kMinValue - se_imm16); } } alu_out = rs + se_imm16; break; case ADDIU: alu_out = rs + se_imm16; break; case SLTI: alu_out = (rs < se_imm16) ? 1 : 0; break; case SLTIU: alu_out = (rs_u < static_cast<uint32_t>(se_imm16)) ? 1 : 0; break; case ANDI: alu_out = rs & oe_imm16; break; case ORI: alu_out = rs | oe_imm16; break; case XORI: alu_out = rs ^ oe_imm16; break; case LUI: alu_out = (oe_imm16 << 16); break; // ------------- Memory instructions. case LB: addr = rs + se_imm16; alu_out = ReadB(addr); break; case LH: addr = rs + se_imm16; alu_out = ReadH(addr, instr); break; case LWL: { // al_offset is offset of the effective address within an aligned word. uint8_t al_offset = (rs + se_imm16) & kPointerAlignmentMask; uint8_t byte_shift = kPointerAlignmentMask - al_offset; uint32_t mask = (1 << byte_shift * 8) - 1; addr = rs + se_imm16 - al_offset; alu_out = ReadW(addr, instr); alu_out <<= byte_shift * 8; alu_out |= rt & mask; break; } case LW: addr = rs + se_imm16; alu_out = ReadW(addr, instr); break; case LBU: addr = rs + se_imm16; alu_out = ReadBU(addr); break; case LHU: addr = rs + se_imm16; alu_out = ReadHU(addr, instr); break; case LWR: { // al_offset is offset of the effective address within an aligned word. uint8_t al_offset = (rs + se_imm16) & kPointerAlignmentMask; uint8_t byte_shift = kPointerAlignmentMask - al_offset; uint32_t mask = al_offset ? (~0 << (byte_shift + 1) * 8) : 0; addr = rs + se_imm16 - al_offset; alu_out = ReadW(addr, instr); alu_out = static_cast<uint32_t> (alu_out) >> al_offset * 8; alu_out |= rt & mask; break; } case SB: addr = rs + se_imm16; break; case SH: addr = rs + se_imm16; break; case SWL: { uint8_t al_offset = (rs + se_imm16) & kPointerAlignmentMask; uint8_t byte_shift = kPointerAlignmentMask - al_offset; uint32_t mask = byte_shift ? (~0 << (al_offset + 1) * 8) : 0; addr = rs + se_imm16 - al_offset; mem_value = ReadW(addr, instr) & mask; mem_value |= static_cast<uint32_t>(rt) >> byte_shift * 8; break; } case SW: addr = rs + se_imm16; break; case SWR: { uint8_t al_offset = (rs + se_imm16) & kPointerAlignmentMask; uint32_t mask = (1 << al_offset * 8) - 1; addr = rs + se_imm16 - al_offset; mem_value = ReadW(addr, instr); mem_value = (rt << al_offset * 8) | (mem_value & mask); break; } case LWC1: addr = rs + se_imm16; alu_out = ReadW(addr, instr); break; case LDC1: addr = rs + se_imm16; fp_out = ReadD(addr, instr); break; case SWC1: case SDC1: addr = rs + se_imm16; break; default: UNREACHABLE(); }; // ---------- Raise exceptions triggered. SignalExceptions(); // ---------- Execution. switch (op) { // ------------- Branch instructions. case BEQ: case BNE: case BLEZ: case BGTZ: // Branch instructions common part. execute_branch_delay_instruction = true; // Set next_pc. if (do_branch) { next_pc = current_pc + (imm16 << 2) + Instruction::kInstrSize; if (instr->IsLinkingInstruction()) { set_register(31, current_pc + 2* Instruction::kInstrSize); } } else { next_pc = current_pc + 2 * Instruction::kInstrSize; } break; // ------------- Arithmetic instructions. case ADDI: case ADDIU: case SLTI: case SLTIU: case ANDI: case ORI: case XORI: case LUI: set_register(rt_reg, alu_out); break; // ------------- Memory instructions. case LB: case LH: case LWL: case LW: case LBU: case LHU: case LWR: set_register(rt_reg, alu_out); break; case SB: WriteB(addr, static_cast<int8_t>(rt)); break; case SH: WriteH(addr, static_cast<uint16_t>(rt), instr); break; case SWL: WriteW(addr, mem_value, instr); break; case SW: WriteW(addr, rt, instr); break; case SWR: WriteW(addr, mem_value, instr); break; case LWC1: set_fpu_register(ft_reg, alu_out); break; case LDC1: set_fpu_register_double(ft_reg, fp_out); break; case SWC1: addr = rs + se_imm16; WriteW(addr, get_fpu_register(ft_reg), instr); break; case SDC1: addr = rs + se_imm16; WriteD(addr, get_fpu_register_double(ft_reg), instr); break; default: break; }; if (execute_branch_delay_instruction) { // Execute branch delay slot // We don't check for end_sim_pc. First it should not be met as the current // pc is valid. Secondly a jump should always execute its branch delay slot. Instruction* branch_delay_instr = reinterpret_cast<Instruction*>(current_pc+Instruction::kInstrSize); BranchDelayInstructionDecode(branch_delay_instr); } // If needed update pc after the branch delay execution. if (next_pc != bad_ra) { set_pc(next_pc); } } // Type 3: instructions using a 26 bytes immediate. (eg: j, jal). void Simulator::DecodeTypeJump(Instruction* instr) { // Get current pc. int32_t current_pc = get_pc(); // Get unchanged bits of pc. int32_t pc_high_bits = current_pc & 0xf0000000; // Next pc. int32_t next_pc = pc_high_bits | (instr->Imm26Value() << 2); // Execute branch delay slot. // We don't check for end_sim_pc. First it should not be met as the current pc // is valid. Secondly a jump should always execute its branch delay slot. Instruction* branch_delay_instr = reinterpret_cast<Instruction*>(current_pc + Instruction::kInstrSize); BranchDelayInstructionDecode(branch_delay_instr); // Update pc and ra if necessary. // Do this after the branch delay execution. if (instr->IsLinkingInstruction()) { set_register(31, current_pc + 2 * Instruction::kInstrSize); } set_pc(next_pc); pc_modified_ = true; } // Executes the current instruction. void Simulator::InstructionDecode(Instruction* instr) { if (v8::internal::FLAG_check_icache) { CheckICache(isolate_->simulator_i_cache(), instr); } pc_modified_ = false; if (::v8::internal::FLAG_trace_sim) { disasm::NameConverter converter; disasm::Disassembler dasm(converter); // Use a reasonably large buffer. v8::internal::EmbeddedVector<char, 256> buffer; dasm.InstructionDecode(buffer, reinterpret_cast<byte*>(instr)); PrintF(" 0x%08x %s\n", reinterpret_cast<intptr_t>(instr), buffer.start()); } switch (instr->InstructionType()) { case Instruction::kRegisterType: DecodeTypeRegister(instr); break; case Instruction::kImmediateType: DecodeTypeImmediate(instr); break; case Instruction::kJumpType: DecodeTypeJump(instr); break; default: UNSUPPORTED(); } if (!pc_modified_) { set_register(pc, reinterpret_cast<int32_t>(instr) + Instruction::kInstrSize); } } void Simulator::Execute() { // 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. int program_counter = get_pc(); if (::v8::internal::FLAG_stop_sim_at == 0) { // Fast version of the dispatch loop without checking whether the simulator // should be stopping at a particular executed instruction. while (program_counter != end_sim_pc) { Instruction* instr = reinterpret_cast<Instruction*>(program_counter); icount_++; InstructionDecode(instr); program_counter = get_pc(); } } else { // FLAG_stop_sim_at is at the non-default value. Stop in the debugger when // we reach the particular instuction count. while (program_counter != end_sim_pc) { Instruction* instr = reinterpret_cast<Instruction*>(program_counter); icount_++; if (icount_ == ::v8::internal::FLAG_stop_sim_at) { MipsDebugger dbg(this); dbg.Debug(); } else { InstructionDecode(instr); } program_counter = get_pc(); } } } int32_t Simulator::Call(byte* entry, int argument_count, ...) { va_list parameters; va_start(parameters, argument_count); // Setup arguments. // First four arguments passed in registers. ASSERT(argument_count >= 4); set_register(a0, va_arg(parameters, int32_t)); set_register(a1, va_arg(parameters, int32_t)); set_register(a2, va_arg(parameters, int32_t)); set_register(a3, va_arg(parameters, int32_t)); // Remaining arguments passed on stack. int original_stack = get_register(sp); // Compute position of stack on entry to generated code. int entry_stack = (original_stack - (argument_count - 4) * sizeof(int32_t) - kCArgsSlotsSize); if (OS::ActivationFrameAlignment() != 0) { entry_stack &= -OS::ActivationFrameAlignment(); } // Store remaining arguments on stack, from low to high memory. intptr_t* stack_argument = reinterpret_cast<intptr_t*>(entry_stack); for (int i = 4; i < argument_count; i++) { stack_argument[i - 4 + kArgsSlotsNum] = va_arg(parameters, int32_t); } va_end(parameters); set_register(sp, entry_stack); // Prepare to execute the code at entry. set_register(pc, reinterpret_cast<int32_t>(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. set_register(ra, end_sim_pc); // Remember the values of callee-saved registers. // The code below assumes that r9 is not used as sb (static base) in // simulator code and therefore is regarded as a callee-saved register. int32_t s0_val = get_register(s0); int32_t s1_val = get_register(s1); int32_t s2_val = get_register(s2); int32_t s3_val = get_register(s3); int32_t s4_val = get_register(s4); int32_t s5_val = get_register(s5); int32_t s6_val = get_register(s6); int32_t s7_val = get_register(s7); int32_t gp_val = get_register(gp); int32_t sp_val = get_register(sp); int32_t fp_val = get_register(fp); // Setup the callee-saved registers with a known value. To be able to check // that they are preserved properly across JS execution. int32_t callee_saved_value = icount_; set_register(s0, callee_saved_value); set_register(s1, callee_saved_value); set_register(s2, callee_saved_value); set_register(s3, callee_saved_value); set_register(s4, callee_saved_value); set_register(s5, callee_saved_value); set_register(s6, callee_saved_value); set_register(s7, callee_saved_value); set_register(gp, callee_saved_value); set_register(fp, callee_saved_value); // Start the simulation. Execute(); // Check that the callee-saved registers have been preserved. CHECK_EQ(callee_saved_value, get_register(s0)); CHECK_EQ(callee_saved_value, get_register(s1)); CHECK_EQ(callee_saved_value, get_register(s2)); CHECK_EQ(callee_saved_value, get_register(s3)); CHECK_EQ(callee_saved_value, get_register(s4)); CHECK_EQ(callee_saved_value, get_register(s5)); CHECK_EQ(callee_saved_value, get_register(s6)); CHECK_EQ(callee_saved_value, get_register(s7)); CHECK_EQ(callee_saved_value, get_register(gp)); CHECK_EQ(callee_saved_value, get_register(fp)); // Restore callee-saved registers with the original value. set_register(s0, s0_val); set_register(s1, s1_val); set_register(s2, s2_val); set_register(s3, s3_val); set_register(s4, s4_val); set_register(s5, s5_val); set_register(s6, s6_val); set_register(s7, s7_val); set_register(gp, gp_val); set_register(sp, sp_val); set_register(fp, fp_val); // Pop stack passed arguments. CHECK_EQ(entry_stack, get_register(sp)); set_register(sp, original_stack); int32_t result = get_register(v0); return result; } uintptr_t Simulator::PushAddress(uintptr_t address) { int new_sp = get_register(sp) - sizeof(uintptr_t); uintptr_t* stack_slot = reinterpret_cast<uintptr_t*>(new_sp); *stack_slot = address; set_register(sp, new_sp); return new_sp; } uintptr_t Simulator::PopAddress() { int current_sp = get_register(sp); uintptr_t* stack_slot = reinterpret_cast<uintptr_t*>(current_sp); uintptr_t address = *stack_slot; set_register(sp, current_sp + sizeof(uintptr_t)); return address; } #undef UNSUPPORTED } } // namespace v8::internal #endif // USE_SIMULATOR #endif // V8_TARGET_ARCH_MIPS
// Copyright 2011 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. #include <stdlib.h> #include <math.h> #include <cstdarg> #include "v8.h" #if defined(V8_TARGET_ARCH_ARM) #include "disasm.h" #include "assembler.h" #include "arm/constants-arm.h" #include "arm/simulator-arm.h" #if defined(USE_SIMULATOR) // Only build the simulator if not compiling for real ARM hardware. namespace v8 { namespace internal { // This macro provides a platform independent use of sscanf. The reason for // SScanF not being implemented in a platform independent way through // ::v8::internal::OS in the same way as SNPrintF is that the // Windows C Run-Time Library does not provide vsscanf. #define SScanF sscanf // NOLINT // The ArmDebugger class is used by the simulator while debugging simulated ARM // code. class ArmDebugger { public: explicit ArmDebugger(Simulator* sim); ~ArmDebugger(); void Stop(Instruction* instr); void Debug(); private: static const Instr kBreakpointInstr = (al | (7*B25) | (1*B24) | kBreakpoint); static const Instr kNopInstr = (al | (13*B21)); Simulator* sim_; int32_t GetRegisterValue(int regnum); double GetRegisterPairDoubleValue(int regnum); double GetVFPDoubleRegisterValue(int regnum); bool GetValue(const char* desc, int32_t* value); bool GetVFPSingleValue(const char* desc, float* value); bool GetVFPDoubleValue(const char* desc, double* value); // Set or delete a breakpoint. Returns true if successful. bool SetBreakpoint(Instruction* breakpc); bool DeleteBreakpoint(Instruction* breakpc); // Undo and redo all breakpoints. This is needed to bracket disassembly and // execution to skip past breakpoints when run from the debugger. void UndoBreakpoints(); void RedoBreakpoints(); }; ArmDebugger::ArmDebugger(Simulator* sim) { sim_ = sim; } ArmDebugger::~ArmDebugger() { } #ifdef GENERATED_CODE_COVERAGE static FILE* coverage_log = NULL; static void InitializeCoverage() { char* file_name = getenv("V8_GENERATED_CODE_COVERAGE_LOG"); if (file_name != NULL) { coverage_log = fopen(file_name, "aw+"); } } void ArmDebugger::Stop(Instruction* instr) { // Get the stop code. uint32_t code = instr->SvcValue() & kStopCodeMask; // Retrieve the encoded address, which comes just after this stop. char** msg_address = reinterpret_cast<char**>(sim_->get_pc() + Instruction::kInstrSize); char* msg = *msg_address; ASSERT(msg != NULL); // Update this stop description. if (isWatchedStop(code) && !watched_stops[code].desc) { watched_stops[code].desc = msg; } if (strlen(msg) > 0) { if (coverage_log != NULL) { fprintf(coverage_log, "%s\n", msg); fflush(coverage_log); } // Overwrite the instruction and address with nops. instr->SetInstructionBits(kNopInstr); reinterpret_cast<Instruction*>(msg_address)->SetInstructionBits(kNopInstr); } sim_->set_pc(sim_->get_pc() + 2 * Instruction::kInstrSize); } #else // ndef GENERATED_CODE_COVERAGE static void InitializeCoverage() { } void ArmDebugger::Stop(Instruction* instr) { // Get the stop code. uint32_t code = instr->SvcValue() & kStopCodeMask; // Retrieve the encoded address, which comes just after this stop. char* msg = *reinterpret_cast<char**>(sim_->get_pc() + Instruction::kInstrSize); // Update this stop description. if (sim_->isWatchedStop(code) && !sim_->watched_stops[code].desc) { sim_->watched_stops[code].desc = msg; } // Print the stop message and code if it is not the default code. if (code != kMaxStopCode) { PrintF("Simulator hit stop %u: %s\n", code, msg); } else { PrintF("Simulator hit %s\n", msg); } sim_->set_pc(sim_->get_pc() + 2 * Instruction::kInstrSize); Debug(); } #endif int32_t ArmDebugger::GetRegisterValue(int regnum) { if (regnum == kPCRegister) { return sim_->get_pc(); } else { return sim_->get_register(regnum); } } double ArmDebugger::GetRegisterPairDoubleValue(int regnum) { return sim_->get_double_from_register_pair(regnum); } double ArmDebugger::GetVFPDoubleRegisterValue(int regnum) { return sim_->get_double_from_d_register(regnum); } bool ArmDebugger::GetValue(const char* desc, int32_t* value) { int regnum = Registers::Number(desc); if (regnum != kNoRegister) { *value = GetRegisterValue(regnum); return true; } else { if (strncmp(desc, "0x", 2) == 0) { return SScanF(desc + 2, "%x", reinterpret_cast<uint32_t*>(value)) == 1; } else { return SScanF(desc, "%u", reinterpret_cast<uint32_t*>(value)) == 1; } } return false; } bool ArmDebugger::GetVFPSingleValue(const char* desc, float* value) { bool is_double; int regnum = VFPRegisters::Number(desc, &is_double); if (regnum != kNoRegister && !is_double) { *value = sim_->get_float_from_s_register(regnum); return true; } return false; } bool ArmDebugger::GetVFPDoubleValue(const char* desc, double* value) { bool is_double; int regnum = VFPRegisters::Number(desc, &is_double); if (regnum != kNoRegister && is_double) { *value = sim_->get_double_from_d_register(regnum); return true; } return false; } bool ArmDebugger::SetBreakpoint(Instruction* breakpc) { // Check if a breakpoint can be set. If not return without any side-effects. if (sim_->break_pc_ != NULL) { return false; } // Set the breakpoint. sim_->break_pc_ = breakpc; sim_->break_instr_ = breakpc->InstructionBits(); // Not setting the breakpoint instruction in the code itself. It will be set // when the debugger shell continues. return true; } bool ArmDebugger::DeleteBreakpoint(Instruction* breakpc) { if (sim_->break_pc_ != NULL) { sim_->break_pc_->SetInstructionBits(sim_->break_instr_); } sim_->break_pc_ = NULL; sim_->break_instr_ = 0; return true; } void ArmDebugger::UndoBreakpoints() { if (sim_->break_pc_ != NULL) { sim_->break_pc_->SetInstructionBits(sim_->break_instr_); } } void ArmDebugger::RedoBreakpoints() { if (sim_->break_pc_ != NULL) { sim_->break_pc_->SetInstructionBits(kBreakpointInstr); } } void ArmDebugger::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; // Undo all set breakpoints while running in the debugger shell. This will // make them invisible to all commands. UndoBreakpoints(); while (!done) { if (last_pc != sim_->get_pc()) { disasm::NameConverter converter; disasm::Disassembler dasm(converter); // use a reasonably large buffer v8::internal::EmbeddedVector<char, 256> buffer; dasm.InstructionDecode(buffer, reinterpret_cast<byte*>(sim_->get_pc())); PrintF(" 0x%08x %s\n", sim_->get_pc(), buffer.start()); last_pc = sim_->get_pc(); } char* line = ReadLine("sim> "); if (line == NULL) { break; } else { // 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)) { sim_->InstructionDecode(reinterpret_cast<Instruction*>(sim_->get_pc())); } else if ((strcmp(cmd, "c") == 0) || (strcmp(cmd, "cont") == 0)) { // Execute the one instruction we broke at with breakpoints disabled. sim_->InstructionDecode(reinterpret_cast<Instruction*>(sim_->get_pc())); // Leave the debugger shell. done = true; } else if ((strcmp(cmd, "p") == 0) || (strcmp(cmd, "print") == 0)) { if (argc == 2 || (argc == 3 && strcmp(arg2, "fp") == 0)) { int32_t value; float svalue; double dvalue; if (strcmp(arg1, "all") == 0) { for (int i = 0; i < kNumRegisters; i++) { value = GetRegisterValue(i); PrintF("%3s: 0x%08x %10d", Registers::Name(i), value, value); if ((argc == 3 && strcmp(arg2, "fp") == 0) && i < 8 && (i % 2) == 0) { dvalue = GetRegisterPairDoubleValue(i); PrintF(" (%f)\n", dvalue); } else { PrintF("\n"); } } for (int i = 0; i < kNumVFPDoubleRegisters; i++) { dvalue = GetVFPDoubleRegisterValue(i); uint64_t as_words = BitCast<uint64_t>(dvalue); PrintF("%3s: %f 0x%08x %08x\n", VFPRegisters::Name(i, true), dvalue, static_cast<uint32_t>(as_words >> 32), static_cast<uint32_t>(as_words & 0xffffffff)); } } else { if (GetValue(arg1, &value)) { PrintF("%s: 0x%08x %d \n", arg1, value, value); } else if (GetVFPSingleValue(arg1, &svalue)) { uint32_t as_word = BitCast<uint32_t>(svalue); PrintF("%s: %f 0x%08x\n", arg1, svalue, as_word); } else if (GetVFPDoubleValue(arg1, &dvalue)) { uint64_t as_words = BitCast<uint64_t>(dvalue); PrintF("%s: %f 0x%08x %08x\n", arg1, dvalue, static_cast<uint32_t>(as_words >> 32), static_cast<uint32_t>(as_words & 0xffffffff)); } else { PrintF("%s unrecognized\n", arg1); } } } else { PrintF("print <register>\n"); } } else if ((strcmp(cmd, "po") == 0) || (strcmp(cmd, "printobject") == 0)) { if (argc == 2) { int32_t value; if (GetValue(arg1, &value)) { Object* obj = reinterpret_cast<Object*>(value); PrintF("%s: \n", arg1); #ifdef DEBUG obj->PrintLn(); #else obj->ShortPrint(); PrintF("\n"); #endif } else { PrintF("%s unrecognized\n", arg1); } } else { PrintF("printobject <value>\n"); } } else if (strcmp(cmd, "stack") == 0 || strcmp(cmd, "mem") == 0) { int32_t* cur = NULL; int32_t* end = NULL; int next_arg = 1; if (strcmp(cmd, "stack") == 0) { cur = reinterpret_cast<int32_t*>(sim_->get_register(Simulator::sp)); } else { // "mem" int32_t value; if (!GetValue(arg1, &value)) { PrintF("%s unrecognized\n", arg1); continue; } cur = reinterpret_cast<int32_t*>(value); next_arg++; } int32_t words; if (argc == next_arg) { words = 10; } else if (argc == next_arg + 1) { if (!GetValue(argv[next_arg], &words)) { words = 10; } } end = cur + words; while (cur < end) { PrintF(" 0x%08x: 0x%08x %10d", reinterpret_cast<intptr_t>(cur), *cur, *cur); HeapObject* obj = reinterpret_cast<HeapObject*>(*cur); int value = *cur; Heap* current_heap = v8::internal::Isolate::Current()->heap(); if (current_heap->Contains(obj) || ((value & 1) == 0)) { PrintF(" ("); if ((value & 1) == 0) { PrintF("smi %d", value / 2); } else { obj->ShortPrint(); } PrintF(")"); } PrintF("\n"); cur++; } } else if (strcmp(cmd, "disasm") == 0 || strcmp(cmd, "di") == 0) { disasm::NameConverter converter; disasm::Disassembler dasm(converter); // use a reasonably large buffer v8::internal::EmbeddedVector<char, 256> buffer; byte* prev = NULL; byte* cur = NULL; byte* end = NULL; if (argc == 1) { cur = reinterpret_cast<byte*>(sim_->get_pc()); end = cur + (10 * Instruction::kInstrSize); } else if (argc == 2) { int regnum = Registers::Number(arg1); if (regnum != kNoRegister || strncmp(arg1, "0x", 2) == 0) { // The argument is an address or a register name. int32_t value; if (GetValue(arg1, &value)) { cur = reinterpret_cast<byte*>(value); // Disassemble 10 instructions at <arg1>. end = cur + (10 * Instruction::kInstrSize); } } else { // The argument is the number of instructions. int32_t value; if (GetValue(arg1, &value)) { cur = reinterpret_cast<byte*>(sim_->get_pc()); // Disassemble <arg1> instructions. end = cur + (value * Instruction::kInstrSize); } } } else { int32_t value1; int32_t value2; if (GetValue(arg1, &value1) && GetValue(arg2, &value2)) { cur = reinterpret_cast<byte*>(value1); end = cur + (value2 * Instruction::kInstrSize); } } while (cur < end) { prev = cur; cur += dasm.InstructionDecode(buffer, cur); PrintF(" 0x%08x %s\n", reinterpret_cast<intptr_t>(prev), buffer.start()); } } else if (strcmp(cmd, "gdb") == 0) { PrintF("relinquishing control to gdb\n"); v8::internal::OS::DebugBreak(); PrintF("regaining control from gdb\n"); } else if (strcmp(cmd, "break") == 0) { if (argc == 2) { int32_t value; if (GetValue(arg1, &value)) { if (!SetBreakpoint(reinterpret_cast<Instruction*>(value))) { PrintF("setting breakpoint failed\n"); } } else { PrintF("%s unrecognized\n", arg1); } } else { PrintF("break <address>\n"); } } else if (strcmp(cmd, "del") == 0) { if (!DeleteBreakpoint(NULL)) { PrintF("deleting breakpoint failed\n"); } } else if (strcmp(cmd, "flags") == 0) { PrintF("N flag: %d; ", sim_->n_flag_); PrintF("Z flag: %d; ", sim_->z_flag_); PrintF("C flag: %d; ", sim_->c_flag_); PrintF("V flag: %d\n", sim_->v_flag_); PrintF("INVALID OP flag: %d; ", sim_->inv_op_vfp_flag_); PrintF("DIV BY ZERO flag: %d; ", sim_->div_zero_vfp_flag_); PrintF("OVERFLOW flag: %d; ", sim_->overflow_vfp_flag_); PrintF("UNDERFLOW flag: %d; ", sim_->underflow_vfp_flag_); PrintF("INEXACT flag: %d;\n", sim_->inexact_vfp_flag_); } else if (strcmp(cmd, "stop") == 0) { int32_t value; intptr_t stop_pc = sim_->get_pc() - 2 * Instruction::kInstrSize; Instruction* stop_instr = reinterpret_cast<Instruction*>(stop_pc); Instruction* msg_address = reinterpret_cast<Instruction*>(stop_pc + Instruction::kInstrSize); if ((argc == 2) && (strcmp(arg1, "unstop") == 0)) { // Remove the current stop. if (sim_->isStopInstruction(stop_instr)) { stop_instr->SetInstructionBits(kNopInstr); msg_address->SetInstructionBits(kNopInstr); } else { PrintF("Not at debugger stop.\n"); } } else 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 = 0; i < sim_->kNumOfWatchedStops; 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 = 0; i < sim_->kNumOfWatchedStops; 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 = 0; i < sim_->kNumOfWatchedStops; 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, "t") == 0) || strcmp(cmd, "trace") == 0) { ::v8::internal::FLAG_trace_sim = !::v8::internal::FLAG_trace_sim; PrintF("Trace of executed instructions is %s\n", ::v8::internal::FLAG_trace_sim ? "on" : "off"); } else if ((strcmp(cmd, "h") == 0) || (strcmp(cmd, "help") == 0)) { PrintF("cont\n"); PrintF(" continue execution (alias 'c')\n"); PrintF("stepi\n"); PrintF(" step one instruction (alias 'si')\n"); PrintF("print <register>\n"); PrintF(" print register content (alias 'p')\n"); PrintF(" use register name 'all' to print all registers\n"); PrintF(" add argument 'fp' to print register pair double values\n"); PrintF("printobject <register>\n"); PrintF(" print an object from a register (alias 'po')\n"); PrintF("flags\n"); PrintF(" print flags\n"); PrintF("stack [<words>]\n"); PrintF(" dump stack content, default dump 10 words)\n"); PrintF("mem <address> [<words>]\n"); PrintF(" dump memory content, default dump 10 words)\n"); PrintF("disasm [<instructions>]\n"); PrintF("disasm [<address/register>]\n"); PrintF("disasm [[<address/register>] <instructions>]\n"); PrintF(" disassemble code, default is 10 instructions\n"); PrintF(" from pc (alias 'di')\n"); PrintF("gdb\n"); PrintF(" enter gdb\n"); PrintF("break <address>\n"); PrintF(" set a break point on the address\n"); PrintF("del\n"); PrintF(" delete the breakpoint\n"); PrintF("trace (alias 't')\n"); PrintF(" toogle the tracing of all executed statements\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 and give control to the ArmDebugger.\n"); PrintF(" The first %d stop codes are watched:\n", Simulator::kNumOfWatchedStops); 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/<code> : print infos about number <code>\n"); PrintF(" or all stop(s).\n"); PrintF(" stop enable/disable all/<code> : enables / disables\n"); PrintF(" all or number <code> stop(s)\n"); PrintF(" stop unstop\n"); PrintF(" ignore the stop instruction at the current location\n"); PrintF(" from now on\n"); } else { PrintF("Unknown command: %s\n", cmd); } } DeleteArray(line); } // Add all the breakpoints back to stop execution and enter the debugger // shell when hit. RedoBreakpoints(); #undef COMMAND_SIZE #undef ARG_SIZE #undef STR #undef XSTR } static bool ICacheMatch(void* one, void* two) { ASSERT((reinterpret_cast<intptr_t>(one) & CachePage::kPageMask) == 0); ASSERT((reinterpret_cast<intptr_t>(two) & CachePage::kPageMask) == 0); return one == two; } static uint32_t ICacheHash(void* key) { return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(key)) >> 2; } 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::FlushICache(v8::internal::HashMap* i_cache, void* start_addr, size_t size) { intptr_t start = reinterpret_cast<intptr_t>(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; FlushOnePage(i_cache, start, bytes_to_flush); start += bytes_to_flush; size -= bytes_to_flush; ASSERT_EQ(0, start & CachePage::kPageMask); offset = 0; } if (size != 0) { FlushOnePage(i_cache, start, size); } } CachePage* Simulator::GetCachePage(v8::internal::HashMap* i_cache, void* page) { v8::internal::HashMap::Entry* entry = i_cache->Lookup(page, ICacheHash(page), true); if (entry->value == NULL) { CachePage* new_page = new CachePage(); entry->value = new_page; } return reinterpret_cast<CachePage*>(entry->value); } // Flush from start up to and not including start + size. void Simulator::FlushOnePage(v8::internal::HashMap* i_cache, intptr_t start, int size) { ASSERT(size <= CachePage::kPageSize); ASSERT(AllOnOnePage(start, size - 1)); ASSERT((start & CachePage::kLineMask) == 0); ASSERT((size & CachePage::kLineMask) == 0); void* page = reinterpret_cast<void*>(start & (~CachePage::kPageMask)); int offset = (start & CachePage::kPageMask); CachePage* cache_page = GetCachePage(i_cache, page); char* valid_bytemap = cache_page->ValidityByte(offset); memset(valid_bytemap, CachePage::LINE_INVALID, size >> CachePage::kLineShift); } void Simulator::CheckICache(v8::internal::HashMap* i_cache, Instruction* instr) { intptr_t address = reinterpret_cast<intptr_t>(instr); void* page = reinterpret_cast<void*>(address & (~CachePage::kPageMask)); void* line = reinterpret_cast<void*>(address & (~CachePage::kLineMask)); int offset = (address & CachePage::kPageMask); CachePage* cache_page = GetCachePage(i_cache, 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. CHECK(memcmp(reinterpret_cast<void*>(instr), cache_page->CachedData(offset), Instruction::kInstrSize) == 0); } else { // Cache miss. Load memory into the cache. memcpy(cached_line, line, CachePage::kLineLength); *cache_valid_byte = CachePage::LINE_VALID; } } void Simulator::Initialize(Isolate* isolate) { if (isolate->simulator_initialized()) return; isolate->set_simulator_initialized(true); ::v8::internal::ExternalReference::set_redirector(isolate, &RedirectExternalReference); } Simulator::Simulator(Isolate* isolate) : isolate_(isolate) { i_cache_ = isolate_->simulator_i_cache(); if (i_cache_ == NULL) { i_cache_ = new v8::internal::HashMap(&ICacheMatch); isolate_->set_simulator_i_cache(i_cache_); } Initialize(isolate); // Setup simulator support first. Some of this information is needed to // setup the architecture state. size_t stack_size = 1 * 1024*1024; // allocate 1MB for stack stack_ = reinterpret_cast<char*>(malloc(stack_size)); pc_modified_ = false; icount_ = 0; break_pc_ = NULL; break_instr_ = 0; // Setup architecture state. // All registers are initialized to zero to start with. for (int i = 0; i < num_registers; i++) { registers_[i] = 0; } n_flag_ = false; z_flag_ = false; c_flag_ = false; v_flag_ = false; // Initializing VFP registers. // All registers are initialized to zero to start with // even though s_registers_ & d_registers_ share the same // physical registers in the target. for (int i = 0; i < num_s_registers; i++) { vfp_register[i] = 0; } n_flag_FPSCR_ = false; z_flag_FPSCR_ = false; c_flag_FPSCR_ = false; v_flag_FPSCR_ = false; FPSCR_rounding_mode_ = RZ; inv_op_vfp_flag_ = false; div_zero_vfp_flag_ = false; overflow_vfp_flag_ = false; underflow_vfp_flag_ = false; inexact_vfp_flag_ = false; // 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<int32_t>(stack_) + stack_size - 64; // The lr 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_lr; registers_[lr] = bad_lr; InitializeCoverage(); } // 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 svc (Supervisor Call) instruction that is handled by // the simulator. We write the original destination of the jump just at a known // offset from the svc instruction so the simulator knows what to call. class Redirection { public: Redirection(void* external_function, ExternalReference::Type type) : external_function_(external_function), swi_instruction_(al | (0xf*B24) | kCallRtRedirected), type_(type), next_(NULL) { Isolate* isolate = Isolate::Current(); next_ = isolate->simulator_redirection(); Simulator::current(isolate)-> FlushICache(isolate->simulator_i_cache(), reinterpret_cast<void*>(&swi_instruction_), Instruction::kInstrSize); isolate->set_simulator_redirection(this); } void* address_of_swi_instruction() { return reinterpret_cast<void*>(&swi_instruction_); } void* external_function() { return external_function_; } ExternalReference::Type type() { return type_; } static Redirection* Get(void* external_function, ExternalReference::Type type) { Isolate* isolate = Isolate::Current(); Redirection* current = isolate->simulator_redirection(); for (; current != NULL; current = current->next_) { if (current->external_function_ == external_function) return current; } return new Redirection(external_function, type); } static Redirection* FromSwiInstruction(Instruction* swi_instruction) { char* addr_of_swi = reinterpret_cast<char*>(swi_instruction); char* addr_of_redirection = addr_of_swi - OFFSET_OF(Redirection, swi_instruction_); return reinterpret_cast<Redirection*>(addr_of_redirection); } private: void* external_function_; uint32_t swi_instruction_; ExternalReference::Type type_; Redirection* next_; }; void* Simulator::RedirectExternalReference(void* external_function, ExternalReference::Type type) { Redirection* redirection = Redirection::Get(external_function, type); return redirection->address_of_swi_instruction(); } // Get the active Simulator for the current thread. Simulator* Simulator::current(Isolate* isolate) { v8::internal::Isolate::PerIsolateThreadData* isolate_data = isolate->FindOrAllocatePerThreadDataForThisThread(); ASSERT(isolate_data != NULL); Simulator* sim = isolate_data->simulator(); if (sim == NULL) { // TODO(146): delete the simulator object when a thread/isolate goes away. sim = new Simulator(isolate); isolate_data->set_simulator(sim); } return sim; } // Sets the register in the architecture state. It will also deal with updating // Simulator internal state for special registers such as PC. void Simulator::set_register(int reg, int32_t value) { ASSERT((reg >= 0) && (reg < num_registers)); if (reg == pc) { pc_modified_ = true; } registers_[reg] = value; } // Get the register from the architecture state. This function does handle // the special case of accessing the PC register. int32_t Simulator::get_register(int reg) const { ASSERT((reg >= 0) && (reg < num_registers)); // Stupid code added to avoid bug in GCC. // See: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=43949 if (reg >= num_registers) return 0; // End stupid code. return registers_[reg] + ((reg == pc) ? Instruction::kPCReadOffset : 0); } double Simulator::get_double_from_register_pair(int reg) { ASSERT((reg >= 0) && (reg < num_registers) && ((reg % 2) == 0)); double dm_val = 0.0; // Read the bits from the unsigned integer register_[] array // into the double precision floating point value and return it. char buffer[2 * sizeof(vfp_register[0])]; memcpy(buffer, ®isters_[reg], 2 * sizeof(registers_[0])); memcpy(&dm_val, buffer, 2 * sizeof(registers_[0])); return(dm_val); } void Simulator::set_dw_register(int dreg, const int* dbl) { ASSERT((dreg >= 0) && (dreg < num_d_registers)); registers_[dreg] = dbl[0]; registers_[dreg + 1] = dbl[1]; } // Raw access to the PC register. void Simulator::set_pc(int32_t value) { pc_modified_ = true; registers_[pc] = value; } bool Simulator::has_bad_pc() const { return ((registers_[pc] == bad_lr) || (registers_[pc] == end_sim_pc)); } // Raw access to the PC register without the special adjustment when reading. int32_t Simulator::get_pc() const { return registers_[pc]; } // Getting from and setting into VFP registers. void Simulator::set_s_register(int sreg, unsigned int value) { ASSERT((sreg >= 0) && (sreg < num_s_registers)); vfp_register[sreg] = value; } unsigned int Simulator::get_s_register(int sreg) const { ASSERT((sreg >= 0) && (sreg < num_s_registers)); return vfp_register[sreg]; } void Simulator::set_s_register_from_float(int sreg, const float flt) { ASSERT((sreg >= 0) && (sreg < num_s_registers)); // Read the bits from the single precision floating point value // into the unsigned integer element of vfp_register[] given by index=sreg. char buffer[sizeof(vfp_register[0])]; memcpy(buffer, &flt, sizeof(vfp_register[0])); memcpy(&vfp_register[sreg], buffer, sizeof(vfp_register[0])); } void Simulator::set_s_register_from_sinteger(int sreg, const int sint) { ASSERT((sreg >= 0) && (sreg < num_s_registers)); // Read the bits from the integer value into the unsigned integer element of // vfp_register[] given by index=sreg. char buffer[sizeof(vfp_register[0])]; memcpy(buffer, &sint, sizeof(vfp_register[0])); memcpy(&vfp_register[sreg], buffer, sizeof(vfp_register[0])); } void Simulator::set_d_register_from_double(int dreg, const double& dbl) { ASSERT((dreg >= 0) && (dreg < num_d_registers)); // Read the bits from the double precision floating point value into the two // consecutive unsigned integer elements of vfp_register[] given by index // 2*sreg and 2*sreg+1. char buffer[2 * sizeof(vfp_register[0])]; memcpy(buffer, &dbl, 2 * sizeof(vfp_register[0])); memcpy(&vfp_register[dreg * 2], buffer, 2 * sizeof(vfp_register[0])); } float Simulator::get_float_from_s_register(int sreg) { ASSERT((sreg >= 0) && (sreg < num_s_registers)); float sm_val = 0.0; // Read the bits from the unsigned integer vfp_register[] array // into the single precision floating point value and return it. char buffer[sizeof(vfp_register[0])]; memcpy(buffer, &vfp_register[sreg], sizeof(vfp_register[0])); memcpy(&sm_val, buffer, sizeof(vfp_register[0])); return(sm_val); } int Simulator::get_sinteger_from_s_register(int sreg) { ASSERT((sreg >= 0) && (sreg < num_s_registers)); int sm_val = 0; // Read the bits from the unsigned integer vfp_register[] array // into the single precision floating point value and return it. char buffer[sizeof(vfp_register[0])]; memcpy(buffer, &vfp_register[sreg], sizeof(vfp_register[0])); memcpy(&sm_val, buffer, sizeof(vfp_register[0])); return(sm_val); } double Simulator::get_double_from_d_register(int dreg) { ASSERT((dreg >= 0) && (dreg < num_d_registers)); double dm_val = 0.0; // Read the bits from the unsigned integer vfp_register[] array // into the double precision floating point value and return it. char buffer[2 * sizeof(vfp_register[0])]; memcpy(buffer, &vfp_register[2 * dreg], 2 * sizeof(vfp_register[0])); memcpy(&dm_val, buffer, 2 * sizeof(vfp_register[0])); return(dm_val); } // For use in calls that take two double values, constructed either // from r0-r3 or d0 and d1. void Simulator::GetFpArgs(double* x, double* y) { if (use_eabi_hardfloat()) { *x = vfp_register[0]; *y = vfp_register[1]; } else { // We use a char buffer to get around the strict-aliasing rules which // otherwise allow the compiler to optimize away the copy. char buffer[sizeof(*x)]; // Registers 0 and 1 -> x. memcpy(buffer, registers_, sizeof(*x)); memcpy(x, buffer, sizeof(*x)); // Registers 2 and 3 -> y. memcpy(buffer, registers_ + 2, sizeof(*y)); memcpy(y, buffer, sizeof(*y)); } } // For use in calls that take one double value, constructed either // from r0 and r1 or d0. void Simulator::GetFpArgs(double* x) { if (use_eabi_hardfloat()) { *x = vfp_register[0]; } else { // We use a char buffer to get around the strict-aliasing rules which // otherwise allow the compiler to optimize away the copy. char buffer[sizeof(*x)]; // Registers 0 and 1 -> x. memcpy(buffer, registers_, sizeof(*x)); memcpy(x, buffer, sizeof(*x)); } } // For use in calls that take one double value constructed either // from r0 and r1 or d0 and one integer value. void Simulator::GetFpArgs(double* x, int32_t* y) { if (use_eabi_hardfloat()) { *x = vfp_register[0]; *y = registers_[1]; } else { // We use a char buffer to get around the strict-aliasing rules which // otherwise allow the compiler to optimize away the copy. char buffer[sizeof(*x)]; // Registers 0 and 1 -> x. memcpy(buffer, registers_, sizeof(*x)); memcpy(x, buffer, sizeof(*x)); // Register 2 -> y. memcpy(buffer, registers_ + 2, sizeof(*y)); memcpy(y, buffer, sizeof(*y)); } } // The return value is either in r0/r1 or d0. void Simulator::SetFpResult(const double& result) { if (use_eabi_hardfloat()) { char buffer[2 * sizeof(vfp_register[0])]; memcpy(buffer, &result, sizeof(buffer)); // Copy result to d0. memcpy(vfp_register, buffer, sizeof(buffer)); } else { char buffer[2 * sizeof(registers_[0])]; memcpy(buffer, &result, sizeof(buffer)); // Copy result to r0 and r1. memcpy(registers_, buffer, sizeof(buffer)); } } void Simulator::TrashCallerSaveRegisters() { // We don't trash the registers with the return value. registers_[2] = 0x50Bad4U; registers_[3] = 0x50Bad4U; registers_[12] = 0x50Bad4U; } // Some Operating Systems allow unaligned access on ARMv7 targets. We // assume that unaligned accesses are not allowed unless the v8 build system // defines the CAN_USE_UNALIGNED_ACCESSES macro to be non-zero. // The following statements below describes the behavior of the ARM CPUs // that don't support unaligned access. // Some ARM platforms raise an interrupt on detecting unaligned access. // On others it does a funky rotation thing. For now we // simply disallow unaligned reads. Note that simulator runs have the runtime // system running directly on the host system and only generated code is // executed in the simulator. Since the host is typically IA32 we will not // get the correct ARM-like behaviour on unaligned accesses for those ARM // targets that don't support unaligned loads and stores. int Simulator::ReadW(int32_t addr, Instruction* instr) { #if V8_TARGET_CAN_READ_UNALIGNED intptr_t* ptr = reinterpret_cast<intptr_t*>(addr); return *ptr; #else if ((addr & 3) == 0) { intptr_t* ptr = reinterpret_cast<intptr_t*>(addr); return *ptr; } PrintF("Unaligned read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr)); UNIMPLEMENTED(); return 0; #endif } void Simulator::WriteW(int32_t addr, int value, Instruction* instr) { #if V8_TARGET_CAN_READ_UNALIGNED intptr_t* ptr = reinterpret_cast<intptr_t*>(addr); *ptr = value; return; #else if ((addr & 3) == 0) { intptr_t* ptr = reinterpret_cast<intptr_t*>(addr); *ptr = value; return; } PrintF("Unaligned write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr)); UNIMPLEMENTED(); #endif } uint16_t Simulator::ReadHU(int32_t addr, Instruction* instr) { #if V8_TARGET_CAN_READ_UNALIGNED uint16_t* ptr = reinterpret_cast<uint16_t*>(addr); return *ptr; #else if ((addr & 1) == 0) { uint16_t* ptr = reinterpret_cast<uint16_t*>(addr); return *ptr; } PrintF("Unaligned unsigned halfword read at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr)); UNIMPLEMENTED(); return 0; #endif } int16_t Simulator::ReadH(int32_t addr, Instruction* instr) { #if V8_TARGET_CAN_READ_UNALIGNED int16_t* ptr = reinterpret_cast<int16_t*>(addr); return *ptr; #else if ((addr & 1) == 0) { int16_t* ptr = reinterpret_cast<int16_t*>(addr); return *ptr; } PrintF("Unaligned signed halfword read at 0x%08x\n", addr); UNIMPLEMENTED(); return 0; #endif } void Simulator::WriteH(int32_t addr, uint16_t value, Instruction* instr) { #if V8_TARGET_CAN_READ_UNALIGNED uint16_t* ptr = reinterpret_cast<uint16_t*>(addr); *ptr = value; return; #else if ((addr & 1) == 0) { uint16_t* ptr = reinterpret_cast<uint16_t*>(addr); *ptr = value; return; } PrintF("Unaligned unsigned halfword write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr)); UNIMPLEMENTED(); #endif } void Simulator::WriteH(int32_t addr, int16_t value, Instruction* instr) { #if V8_TARGET_CAN_READ_UNALIGNED int16_t* ptr = reinterpret_cast<int16_t*>(addr); *ptr = value; return; #else if ((addr & 1) == 0) { int16_t* ptr = reinterpret_cast<int16_t*>(addr); *ptr = value; return; } PrintF("Unaligned halfword write at 0x%08x, pc=0x%08" V8PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr)); UNIMPLEMENTED(); #endif } uint8_t Simulator::ReadBU(int32_t addr) { uint8_t* ptr = reinterpret_cast<uint8_t*>(addr); return *ptr; } int8_t Simulator::ReadB(int32_t addr) { int8_t* ptr = reinterpret_cast<int8_t*>(addr); return *ptr; } void Simulator::WriteB(int32_t addr, uint8_t value) { uint8_t* ptr = reinterpret_cast<uint8_t*>(addr); *ptr = value; } void Simulator::WriteB(int32_t addr, int8_t value) { int8_t* ptr = reinterpret_cast<int8_t*>(addr); *ptr = value; } int32_t* Simulator::ReadDW(int32_t addr) { #if V8_TARGET_CAN_READ_UNALIGNED int32_t* ptr = reinterpret_cast<int32_t*>(addr); return ptr; #else if ((addr & 3) == 0) { int32_t* ptr = reinterpret_cast<int32_t*>(addr); return ptr; } PrintF("Unaligned read at 0x%08x\n", addr); UNIMPLEMENTED(); return 0; #endif } void Simulator::WriteDW(int32_t addr, int32_t value1, int32_t value2) { #if V8_TARGET_CAN_READ_UNALIGNED int32_t* ptr = reinterpret_cast<int32_t*>(addr); *ptr++ = value1; *ptr = value2; return; #else if ((addr & 3) == 0) { int32_t* ptr = reinterpret_cast<int32_t*>(addr); *ptr++ = value1; *ptr = value2; return; } PrintF("Unaligned write at 0x%08x\n", addr); UNIMPLEMENTED(); #endif } // Returns the limit of the stack area to enable checking for stack overflows. uintptr_t Simulator::StackLimit() const { // Leave a safety margin of 256 bytes to prevent overrunning the stack when // pushing values. return reinterpret_cast<uintptr_t>(stack_) + 256; } // Unsupported instructions use Format to print an error and stop execution. void Simulator::Format(Instruction* instr, const char* format) { PrintF("Simulator found unsupported instruction:\n 0x%08x: %s\n", reinterpret_cast<intptr_t>(instr), format); UNIMPLEMENTED(); } // Checks if the current instruction should be executed based on its // condition bits. bool Simulator::ConditionallyExecute(Instruction* instr) { switch (instr->ConditionField()) { case eq: return z_flag_; case ne: return !z_flag_; case cs: return c_flag_; case cc: return !c_flag_; case mi: return n_flag_; case pl: return !n_flag_; case vs: return v_flag_; case vc: return !v_flag_; case hi: return c_flag_ && !z_flag_; case ls: return !c_flag_ || z_flag_; case ge: return n_flag_ == v_flag_; case lt: return n_flag_ != v_flag_; case gt: return !z_flag_ && (n_flag_ == v_flag_); case le: return z_flag_ || (n_flag_ != v_flag_); case al: return true; default: UNREACHABLE(); } return false; } // Calculate and set the Negative and Zero flags. void Simulator::SetNZFlags(int32_t val) { n_flag_ = (val < 0); z_flag_ = (val == 0); } // Set the Carry flag. void Simulator::SetCFlag(bool val) { c_flag_ = val; } // Set the oVerflow flag. void Simulator::SetVFlag(bool val) { v_flag_ = val; } // Calculate C flag value for additions. bool Simulator::CarryFrom(int32_t left, int32_t right, int32_t carry) { uint32_t uleft = static_cast<uint32_t>(left); uint32_t uright = static_cast<uint32_t>(right); uint32_t urest = 0xffffffffU - uleft; return (uright > urest) || (carry && (((uright + 1) > urest) || (uright > (urest - 1)))); } // Calculate C flag value for subtractions. bool Simulator::BorrowFrom(int32_t left, int32_t right) { uint32_t uleft = static_cast<uint32_t>(left); uint32_t uright = static_cast<uint32_t>(right); return (uright > uleft); } // Calculate V flag value for additions and subtractions. bool Simulator::OverflowFrom(int32_t alu_out, int32_t left, int32_t right, bool addition) { bool overflow; if (addition) { // operands have the same sign overflow = ((left >= 0 && right >= 0) || (left < 0 && right < 0)) // and operands and result have different sign && ((left < 0 && alu_out >= 0) || (left >= 0 && alu_out < 0)); } else { // operands have different signs overflow = ((left < 0 && right >= 0) || (left >= 0 && right < 0)) // and first operand and result have different signs && ((left < 0 && alu_out >= 0) || (left >= 0 && alu_out < 0)); } return overflow; } // Support for VFP comparisons. void Simulator::Compute_FPSCR_Flags(double val1, double val2) { if (isnan(val1) || isnan(val2)) { n_flag_FPSCR_ = false; z_flag_FPSCR_ = false; c_flag_FPSCR_ = true; v_flag_FPSCR_ = true; // All non-NaN cases. } else if (val1 == val2) { n_flag_FPSCR_ = false; z_flag_FPSCR_ = true; c_flag_FPSCR_ = true; v_flag_FPSCR_ = false; } else if (val1 < val2) { n_flag_FPSCR_ = true; z_flag_FPSCR_ = false; c_flag_FPSCR_ = false; v_flag_FPSCR_ = false; } else { // Case when (val1 > val2). n_flag_FPSCR_ = false; z_flag_FPSCR_ = false; c_flag_FPSCR_ = true; v_flag_FPSCR_ = false; } } void Simulator::Copy_FPSCR_to_APSR() { n_flag_ = n_flag_FPSCR_; z_flag_ = z_flag_FPSCR_; c_flag_ = c_flag_FPSCR_; v_flag_ = v_flag_FPSCR_; } // Addressing Mode 1 - Data-processing operands: // Get the value based on the shifter_operand with register. int32_t Simulator::GetShiftRm(Instruction* instr, bool* carry_out) { ShiftOp shift = instr->ShiftField(); int shift_amount = instr->ShiftAmountValue(); int32_t result = get_register(instr->RmValue()); if (instr->Bit(4) == 0) { // by immediate if ((shift == ROR) && (shift_amount == 0)) { UNIMPLEMENTED(); return result; } else if (((shift == LSR) || (shift == ASR)) && (shift_amount == 0)) { shift_amount = 32; } switch (shift) { case ASR: { if (shift_amount == 0) { if (result < 0) { result = 0xffffffff; *carry_out = true; } else { result = 0; *carry_out = false; } } else { result >>= (shift_amount - 1); *carry_out = (result & 1) == 1; result >>= 1; } break; } case LSL: { if (shift_amount == 0) { *carry_out = c_flag_; } else { result <<= (shift_amount - 1); *carry_out = (result < 0); result <<= 1; } break; } case LSR: { if (shift_amount == 0) { result = 0; *carry_out = c_flag_; } else { uint32_t uresult = static_cast<uint32_t>(result); uresult >>= (shift_amount - 1); *carry_out = (uresult & 1) == 1; uresult >>= 1; result = static_cast<int32_t>(uresult); } break; } case ROR: { UNIMPLEMENTED(); break; } default: { UNREACHABLE(); break; } } } else { // by register int rs = instr->RsValue(); shift_amount = get_register(rs) &0xff; switch (shift) { case ASR: { if (shift_amount == 0) { *carry_out = c_flag_; } else if (shift_amount < 32) { result >>= (shift_amount - 1); *carry_out = (result & 1) == 1; result >>= 1; } else { ASSERT(shift_amount >= 32); if (result < 0) { *carry_out = true; result = 0xffffffff; } else { *carry_out = false; result = 0; } } break; } case LSL: { if (shift_amount == 0) { *carry_out = c_flag_; } else if (shift_amount < 32) { result <<= (shift_amount - 1); *carry_out = (result < 0); result <<= 1; } else if (shift_amount == 32) { *carry_out = (result & 1) == 1; result = 0; } else { ASSERT(shift_amount > 32); *carry_out = false; result = 0; } break; } case LSR: { if (shift_amount == 0) { *carry_out = c_flag_; } else if (shift_amount < 32) { uint32_t uresult = static_cast<uint32_t>(result); uresult >>= (shift_amount - 1); *carry_out = (uresult & 1) == 1; uresult >>= 1; result = static_cast<int32_t>(uresult); } else if (shift_amount == 32) { *carry_out = (result < 0); result = 0; } else { *carry_out = false; result = 0; } break; } case ROR: { UNIMPLEMENTED(); break; } default: { UNREACHABLE(); break; } } } return result; } // Addressing Mode 1 - Data-processing operands: // Get the value based on the shifter_operand with immediate. int32_t Simulator::GetImm(Instruction* instr, bool* carry_out) { int rotate = instr->RotateValue() * 2; int immed8 = instr->Immed8Value(); int imm = (immed8 >> rotate) | (immed8 << (32 - rotate)); *carry_out = (rotate == 0) ? c_flag_ : (imm < 0); return imm; } static int count_bits(int bit_vector) { int count = 0; while (bit_vector != 0) { if ((bit_vector & 1) != 0) { count++; } bit_vector >>= 1; } return count; } void Simulator::ProcessPUW(Instruction* instr, int num_regs, int reg_size, intptr_t* start_address, intptr_t* end_address) { int rn = instr->RnValue(); int32_t rn_val = get_register(rn); switch (instr->PUField()) { case da_x: { UNIMPLEMENTED(); break; } case ia_x: { *start_address = rn_val; *end_address = rn_val + (num_regs * reg_size) - reg_size; rn_val = rn_val + (num_regs * reg_size); break; } case db_x: { *start_address = rn_val - (num_regs * reg_size); *end_address = rn_val - reg_size; rn_val = *start_address; break; } case ib_x: { *start_address = rn_val + reg_size; *end_address = rn_val + (num_regs * reg_size); rn_val = *end_address; break; } default: { UNREACHABLE(); break; } } if (instr->HasW()) { set_register(rn, rn_val); } } // Addressing Mode 4 - Load and Store Multiple void Simulator::HandleRList(Instruction* instr, bool load) { int rlist = instr->RlistValue(); int num_regs = count_bits(rlist); intptr_t start_address = 0; intptr_t end_address = 0; ProcessPUW(instr, num_regs, kPointerSize, &start_address, &end_address); intptr_t* address = reinterpret_cast<intptr_t*>(start_address); int reg = 0; while (rlist != 0) { if ((rlist & 1) != 0) { if (load) { set_register(reg, *address); } else { *address = get_register(reg); } address += 1; } reg++; rlist >>= 1; } ASSERT(end_address == ((intptr_t)address) - 4); } // Addressing Mode 6 - Load and Store Multiple Coprocessor registers. void Simulator::HandleVList(Instruction* instr) { VFPRegPrecision precision = (instr->SzValue() == 0) ? kSinglePrecision : kDoublePrecision; int operand_size = (precision == kSinglePrecision) ? 4 : 8; bool load = (instr->VLValue() == 0x1); int vd; int num_regs; vd = instr->VFPDRegValue(precision); if (precision == kSinglePrecision) { num_regs = instr->Immed8Value(); } else { num_regs = instr->Immed8Value() / 2; } intptr_t start_address = 0; intptr_t end_address = 0; ProcessPUW(instr, num_regs, operand_size, &start_address, &end_address); intptr_t* address = reinterpret_cast<intptr_t*>(start_address); for (int reg = vd; reg < vd + num_regs; reg++) { if (precision == kSinglePrecision) { if (load) { set_s_register_from_sinteger( reg, ReadW(reinterpret_cast<int32_t>(address), instr)); } else { WriteW(reinterpret_cast<int32_t>(address), get_sinteger_from_s_register(reg), instr); } address += 1; } else { if (load) { set_s_register_from_sinteger( 2 * reg, ReadW(reinterpret_cast<int32_t>(address), instr)); set_s_register_from_sinteger( 2 * reg + 1, ReadW(reinterpret_cast<int32_t>(address + 1), instr)); } else { WriteW(reinterpret_cast<int32_t>(address), get_sinteger_from_s_register(2 * reg), instr); WriteW(reinterpret_cast<int32_t>(address + 1), get_sinteger_from_s_register(2 * reg + 1), instr); } address += 2; } } ASSERT(reinterpret_cast<intptr_t>(address) - operand_size == end_address); } // Calls into the V8 runtime are based on this very simple interface. // Note: To be able to return two values from some calls the code in runtime.cc // uses the ObjectPair which is essentially two 32-bit values stuffed into a // 64-bit value. With the code below we assume that all runtime calls return // 64 bits of result. If they don't, the r1 result register contains a bogus // value, which is fine because it is caller-saved. typedef int64_t (*SimulatorRuntimeCall)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5); typedef double (*SimulatorRuntimeFPCall)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3); // This signature supports direct call in to API function native callback // (refer to InvocationCallback in v8.h). typedef v8::Handle<v8::Value> (*SimulatorRuntimeDirectApiCall)(int32_t arg0); // This signature supports direct call to accessor getter callback. typedef v8::Handle<v8::Value> (*SimulatorRuntimeDirectGetterCall)(int32_t arg0, int32_t arg1); // Software interrupt instructions are used by the simulator to call into the // C-based V8 runtime. void Simulator::SoftwareInterrupt(Instruction* instr) { int svc = instr->SvcValue(); switch (svc) { case kCallRtRedirected: { // Check if stack is aligned. Error if not aligned is reported below to // include information on the function called. bool stack_aligned = (get_register(sp) & (::v8::internal::FLAG_sim_stack_alignment - 1)) == 0; Redirection* redirection = Redirection::FromSwiInstruction(instr); int32_t arg0 = get_register(r0); int32_t arg1 = get_register(r1); int32_t arg2 = get_register(r2); int32_t arg3 = get_register(r3); int32_t* stack_pointer = reinterpret_cast<int32_t*>(get_register(sp)); int32_t arg4 = stack_pointer[0]; int32_t arg5 = stack_pointer[1]; bool fp_call = (redirection->type() == ExternalReference::BUILTIN_FP_FP_CALL) || (redirection->type() == ExternalReference::BUILTIN_COMPARE_CALL) || (redirection->type() == ExternalReference::BUILTIN_FP_CALL) || (redirection->type() == ExternalReference::BUILTIN_FP_INT_CALL); if (use_eabi_hardfloat()) { // With the hard floating point calling convention, double // arguments are passed in VFP registers. Fetch the arguments // from there and call the builtin using soft floating point // convention. switch (redirection->type()) { case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_COMPARE_CALL: arg0 = vfp_register[0]; arg1 = vfp_register[1]; arg2 = vfp_register[2]; arg3 = vfp_register[3]; break; case ExternalReference::BUILTIN_FP_CALL: arg0 = vfp_register[0]; arg1 = vfp_register[1]; break; case ExternalReference::BUILTIN_FP_INT_CALL: arg0 = vfp_register[0]; arg1 = vfp_register[1]; arg2 = get_register(0); break; default: break; } } // This is dodgy but it works because the C entry stubs are never moved. // See comment in codegen-arm.cc and bug 1242173. int32_t saved_lr = get_register(lr); intptr_t external = reinterpret_cast<intptr_t>(redirection->external_function()); if (fp_call) { if (::v8::internal::FLAG_trace_sim || !stack_aligned) { SimulatorRuntimeFPCall target = reinterpret_cast<SimulatorRuntimeFPCall>(external); double dval0, dval1; int32_t ival; switch (redirection->type()) { case ExternalReference::BUILTIN_FP_FP_CALL: case ExternalReference::BUILTIN_COMPARE_CALL: GetFpArgs(&dval0, &dval1); PrintF("Call to host function at %p with args %f, %f", FUNCTION_ADDR(target), dval0, dval1); break; case ExternalReference::BUILTIN_FP_CALL: GetFpArgs(&dval0); PrintF("Call to host function at %p with arg %f", FUNCTION_ADDR(target), dval0); break; case ExternalReference::BUILTIN_FP_INT_CALL: GetFpArgs(&dval0, &ival); PrintF("Call to host function at %p with args %f, %d", FUNCTION_ADDR(target), dval0, ival); break; default: UNREACHABLE(); break; } if (!stack_aligned) { PrintF(" with unaligned stack %08x\n", get_register(sp)); } PrintF("\n"); } CHECK(stack_aligned); if (redirection->type() != ExternalReference::BUILTIN_COMPARE_CALL) { SimulatorRuntimeFPCall target = reinterpret_cast<SimulatorRuntimeFPCall>(external); double result = target(arg0, arg1, arg2, arg3); SetFpResult(result); } else { SimulatorRuntimeCall target = reinterpret_cast<SimulatorRuntimeCall>(external); int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5); int32_t lo_res = static_cast<int32_t>(result); int32_t hi_res = static_cast<int32_t>(result >> 32); if (::v8::internal::FLAG_trace_sim) { PrintF("Returned %08x\n", lo_res); } set_register(r0, lo_res); set_register(r1, hi_res); } } else if (redirection->type() == ExternalReference::DIRECT_API_CALL) { SimulatorRuntimeDirectApiCall target = reinterpret_cast<SimulatorRuntimeDirectApiCall>(external); if (::v8::internal::FLAG_trace_sim || !stack_aligned) { PrintF("Call to host function at %p args %08x", FUNCTION_ADDR(target), arg0); if (!stack_aligned) { PrintF(" with unaligned stack %08x\n", get_register(sp)); } PrintF("\n"); } CHECK(stack_aligned); v8::Handle<v8::Value> result = target(arg0); if (::v8::internal::FLAG_trace_sim) { PrintF("Returned %p\n", reinterpret_cast<void *>(*result)); } set_register(r0, (int32_t) *result); } else if (redirection->type() == ExternalReference::DIRECT_GETTER_CALL) { SimulatorRuntimeDirectGetterCall target = reinterpret_cast<SimulatorRuntimeDirectGetterCall>(external); if (::v8::internal::FLAG_trace_sim || !stack_aligned) { PrintF("Call to host function at %p args %08x %08x", FUNCTION_ADDR(target), arg0, arg1); if (!stack_aligned) { PrintF(" with unaligned stack %08x\n", get_register(sp)); } PrintF("\n"); } CHECK(stack_aligned); v8::Handle<v8::Value> result = target(arg0, arg1); if (::v8::internal::FLAG_trace_sim) { PrintF("Returned %p\n", reinterpret_cast<void *>(*result)); } set_register(r0, (int32_t) *result); } else { // builtin call. ASSERT(redirection->type() == ExternalReference::BUILTIN_CALL); SimulatorRuntimeCall target = reinterpret_cast<SimulatorRuntimeCall>(external); if (::v8::internal::FLAG_trace_sim || !stack_aligned) { PrintF( "Call to host function at %p" "args %08x, %08x, %08x, %08x, %08x, %08x", FUNCTION_ADDR(target), arg0, arg1, arg2, arg3, arg4, arg5); if (!stack_aligned) { PrintF(" with unaligned stack %08x\n", get_register(sp)); } PrintF("\n"); } CHECK(stack_aligned); int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5); int32_t lo_res = static_cast<int32_t>(result); int32_t hi_res = static_cast<int32_t>(result >> 32); if (::v8::internal::FLAG_trace_sim) { PrintF("Returned %08x\n", lo_res); } set_register(r0, lo_res); set_register(r1, hi_res); } set_register(lr, saved_lr); set_pc(get_register(lr)); break; } case kBreakpoint: { ArmDebugger dbg(this); dbg.Debug(); break; } // stop uses all codes greater than 1 << 23. default: { if (svc >= (1 << 23)) { uint32_t code = svc & kStopCodeMask; if (isWatchedStop(code)) { IncreaseStopCounter(code); } // Stop if it is enabled, otherwise go on jumping over the stop // and the message address. if (isEnabledStop(code)) { ArmDebugger dbg(this); dbg.Stop(instr); } else { set_pc(get_pc() + 2 * Instruction::kInstrSize); } } else { // This is not a valid svc code. UNREACHABLE(); break; } } } } // Stop helper functions. bool Simulator::isStopInstruction(Instruction* instr) { return (instr->Bits(27, 24) == 0xF) && (instr->SvcValue() >= kStopCode); } bool Simulator::isWatchedStop(uint32_t code) { ASSERT(code <= kMaxStopCode); return code < kNumOfWatchedStops; } bool Simulator::isEnabledStop(uint32_t code) { ASSERT(code <= kMaxStopCode); // Unwatched stops are always enabled. return !isWatchedStop(code) || !(watched_stops[code].count & kStopDisabledBit); } void Simulator::EnableStop(uint32_t code) { ASSERT(isWatchedStop(code)); if (!isEnabledStop(code)) { watched_stops[code].count &= ~kStopDisabledBit; } } void Simulator::DisableStop(uint32_t code) { ASSERT(isWatchedStop(code)); if (isEnabledStop(code)) { watched_stops[code].count |= kStopDisabledBit; } } void Simulator::IncreaseStopCounter(uint32_t code) { ASSERT(code <= kMaxStopCode); ASSERT(isWatchedStop(code)); if ((watched_stops[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); watched_stops[code].count = 0; EnableStop(code); } else { watched_stops[code].count++; } } // Print a stop status. void Simulator::PrintStopInfo(uint32_t code) { ASSERT(code <= kMaxStopCode); if (!isWatchedStop(code)) { PrintF("Stop not watched."); } else { const char* state = isEnabledStop(code) ? "Enabled" : "Disabled"; int32_t count = watched_stops[code].count & ~kStopDisabledBit; // Don't print the state of unused breakpoints. if (count != 0) { if (watched_stops[code].desc) { PrintF("stop %i - 0x%x: \t%s, \tcounter = %i, \t%s\n", code, code, state, count, watched_stops[code].desc); } else { PrintF("stop %i - 0x%x: \t%s, \tcounter = %i\n", code, code, state, count); } } } } // Handle execution based on instruction types. // Instruction types 0 and 1 are both rolled into one function because they // only differ in the handling of the shifter_operand. void Simulator::DecodeType01(Instruction* instr) { int type = instr->TypeValue(); if ((type == 0) && instr->IsSpecialType0()) { // multiply instruction or extra loads and stores if (instr->Bits(7, 4) == 9) { if (instr->Bit(24) == 0) { // Raw field decoding here. Multiply instructions have their Rd in // funny places. int rn = instr->RnValue(); int rm = instr->RmValue(); int rs = instr->RsValue(); int32_t rs_val = get_register(rs); int32_t rm_val = get_register(rm); if (instr->Bit(23) == 0) { if (instr->Bit(21) == 0) { // The MUL instruction description (A 4.1.33) refers to Rd as being // the destination for the operation, but it confusingly uses the // Rn field to encode it. // Format(instr, "mul'cond's 'rn, 'rm, 'rs"); int rd = rn; // Remap the rn field to the Rd register. int32_t alu_out = rm_val * rs_val; set_register(rd, alu_out); if (instr->HasS()) { SetNZFlags(alu_out); } } else { // The MLA instruction description (A 4.1.28) refers to the order // of registers as "Rd, Rm, Rs, Rn". But confusingly it uses the // Rn field to encode the Rd register and the Rd field to encode // the Rn register. Format(instr, "mla'cond's 'rn, 'rm, 'rs, 'rd"); } } else { // The signed/long multiply instructions use the terms RdHi and RdLo // when referring to the target registers. They are mapped to the Rn // and Rd fields as follows: // RdLo == Rd // RdHi == Rn (This is confusingly stored in variable rd here // because the mul instruction from above uses the // Rn field to encode the Rd register. Good luck figuring // this out without reading the ARM instruction manual // at a very detailed level.) // Format(instr, "'um'al'cond's 'rd, 'rn, 'rs, 'rm"); int rd_hi = rn; // Remap the rn field to the RdHi register. int rd_lo = instr->RdValue(); int32_t hi_res = 0; int32_t lo_res = 0; if (instr->Bit(22) == 1) { int64_t left_op = static_cast<int32_t>(rm_val); int64_t right_op = static_cast<int32_t>(rs_val); uint64_t result = left_op * right_op; hi_res = static_cast<int32_t>(result >> 32); lo_res = static_cast<int32_t>(result & 0xffffffff); } else { // unsigned multiply uint64_t left_op = static_cast<uint32_t>(rm_val); uint64_t right_op = static_cast<uint32_t>(rs_val); uint64_t result = left_op * right_op; hi_res = static_cast<int32_t>(result >> 32); lo_res = static_cast<int32_t>(result & 0xffffffff); } set_register(rd_lo, lo_res); set_register(rd_hi, hi_res); if (instr->HasS()) { UNIMPLEMENTED(); } } } else { UNIMPLEMENTED(); // Not used by V8. } } else { // extra load/store instructions int rd = instr->RdValue(); int rn = instr->RnValue(); int32_t rn_val = get_register(rn); int32_t addr = 0; if (instr->Bit(22) == 0) { int rm = instr->RmValue(); int32_t rm_val = get_register(rm); switch (instr->PUField()) { case da_x: { // Format(instr, "'memop'cond'sign'h 'rd, ['rn], -'rm"); ASSERT(!instr->HasW()); addr = rn_val; rn_val -= rm_val; set_register(rn, rn_val); break; } case ia_x: { // Format(instr, "'memop'cond'sign'h 'rd, ['rn], +'rm"); ASSERT(!instr->HasW()); addr = rn_val; rn_val += rm_val; set_register(rn, rn_val); break; } case db_x: { // Format(instr, "'memop'cond'sign'h 'rd, ['rn, -'rm]'w"); rn_val -= rm_val; addr = rn_val; if (instr->HasW()) { set_register(rn, rn_val); } break; } case ib_x: { // Format(instr, "'memop'cond'sign'h 'rd, ['rn, +'rm]'w"); rn_val += rm_val; addr = rn_val; if (instr->HasW()) { set_register(rn, rn_val); } break; } default: { // The PU field is a 2-bit field. UNREACHABLE(); break; } } } else { int32_t imm_val = (instr->ImmedHValue() << 4) | instr->ImmedLValue(); switch (instr->PUField()) { case da_x: { // Format(instr, "'memop'cond'sign'h 'rd, ['rn], #-'off8"); ASSERT(!instr->HasW()); addr = rn_val; rn_val -= imm_val; set_register(rn, rn_val); break; } case ia_x: { // Format(instr, "'memop'cond'sign'h 'rd, ['rn], #+'off8"); ASSERT(!instr->HasW()); addr = rn_val; rn_val += imm_val; set_register(rn, rn_val); break; } case db_x: { // Format(instr, "'memop'cond'sign'h 'rd, ['rn, #-'off8]'w"); rn_val -= imm_val; addr = rn_val; if (instr->HasW()) { set_register(rn, rn_val); } break; } case ib_x: { // Format(instr, "'memop'cond'sign'h 'rd, ['rn, #+'off8]'w"); rn_val += imm_val; addr = rn_val; if (instr->HasW()) { set_register(rn, rn_val); } break; } default: { // The PU field is a 2-bit field. UNREACHABLE(); break; } } } if (((instr->Bits(7, 4) & 0xd) == 0xd) && (instr->Bit(20) == 0)) { ASSERT((rd % 2) == 0); if (instr->HasH()) { // The strd instruction. int32_t value1 = get_register(rd); int32_t value2 = get_register(rd+1); WriteDW(addr, value1, value2); } else { // The ldrd instruction. int* rn_data = ReadDW(addr); set_dw_register(rd, rn_data); } } else if (instr->HasH()) { if (instr->HasSign()) { if (instr->HasL()) { int16_t val = ReadH(addr, instr); set_register(rd, val); } else { int16_t val = get_register(rd); WriteH(addr, val, instr); } } else { if (instr->HasL()) { uint16_t val = ReadHU(addr, instr); set_register(rd, val); } else { uint16_t val = get_register(rd); WriteH(addr, val, instr); } } } else { // signed byte loads ASSERT(instr->HasSign()); ASSERT(instr->HasL()); int8_t val = ReadB(addr); set_register(rd, val); } return; } } else if ((type == 0) && instr->IsMiscType0()) { if (instr->Bits(22, 21) == 1) { int rm = instr->RmValue(); switch (instr->BitField(7, 4)) { case BX: set_pc(get_register(rm)); break; case BLX: { uint32_t old_pc = get_pc(); set_pc(get_register(rm)); set_register(lr, old_pc + Instruction::kInstrSize); break; } case BKPT: { ArmDebugger dbg(this); PrintF("Simulator hit BKPT.\n"); dbg.Debug(); break; } default: UNIMPLEMENTED(); } } else if (instr->Bits(22, 21) == 3) { int rm = instr->RmValue(); int rd = instr->RdValue(); switch (instr->BitField(7, 4)) { case CLZ: { uint32_t bits = get_register(rm); int leading_zeros = 0; if (bits == 0) { leading_zeros = 32; } else { while ((bits & 0x80000000u) == 0) { bits <<= 1; leading_zeros++; } } set_register(rd, leading_zeros); break; } default: UNIMPLEMENTED(); } } else { PrintF("%08x\n", instr->InstructionBits()); UNIMPLEMENTED(); } } else { int rd = instr->RdValue(); int rn = instr->RnValue(); int32_t rn_val = get_register(rn); int32_t shifter_operand = 0; bool shifter_carry_out = 0; if (type == 0) { shifter_operand = GetShiftRm(instr, &shifter_carry_out); } else { ASSERT(instr->TypeValue() == 1); shifter_operand = GetImm(instr, &shifter_carry_out); } int32_t alu_out; switch (instr->OpcodeField()) { case AND: { // Format(instr, "and'cond's 'rd, 'rn, 'shift_rm"); // Format(instr, "and'cond's 'rd, 'rn, 'imm"); alu_out = rn_val & shifter_operand; set_register(rd, alu_out); if (instr->HasS()) { SetNZFlags(alu_out); SetCFlag(shifter_carry_out); } break; } case EOR: { // Format(instr, "eor'cond's 'rd, 'rn, 'shift_rm"); // Format(instr, "eor'cond's 'rd, 'rn, 'imm"); alu_out = rn_val ^ shifter_operand; set_register(rd, alu_out); if (instr->HasS()) { SetNZFlags(alu_out); SetCFlag(shifter_carry_out); } break; } case SUB: { // Format(instr, "sub'cond's 'rd, 'rn, 'shift_rm"); // Format(instr, "sub'cond's 'rd, 'rn, 'imm"); alu_out = rn_val - shifter_operand; set_register(rd, alu_out); if (instr->HasS()) { SetNZFlags(alu_out); SetCFlag(!BorrowFrom(rn_val, shifter_operand)); SetVFlag(OverflowFrom(alu_out, rn_val, shifter_operand, false)); } break; } case RSB: { // Format(instr, "rsb'cond's 'rd, 'rn, 'shift_rm"); // Format(instr, "rsb'cond's 'rd, 'rn, 'imm"); alu_out = shifter_operand - rn_val; set_register(rd, alu_out); if (instr->HasS()) { SetNZFlags(alu_out); SetCFlag(!BorrowFrom(shifter_operand, rn_val)); SetVFlag(OverflowFrom(alu_out, shifter_operand, rn_val, false)); } break; } case ADD: { // Format(instr, "add'cond's 'rd, 'rn, 'shift_rm"); // Format(instr, "add'cond's 'rd, 'rn, 'imm"); alu_out = rn_val + shifter_operand; set_register(rd, alu_out); if (instr->HasS()) { SetNZFlags(alu_out); SetCFlag(CarryFrom(rn_val, shifter_operand)); SetVFlag(OverflowFrom(alu_out, rn_val, shifter_operand, true)); } break; } case ADC: { // Format(instr, "adc'cond's 'rd, 'rn, 'shift_rm"); // Format(instr, "adc'cond's 'rd, 'rn, 'imm"); alu_out = rn_val + shifter_operand + GetCarry(); set_register(rd, alu_out); if (instr->HasS()) { SetNZFlags(alu_out); SetCFlag(CarryFrom(rn_val, shifter_operand, GetCarry())); SetVFlag(OverflowFrom(alu_out, rn_val, shifter_operand, true)); } break; } case SBC: { Format(instr, "sbc'cond's 'rd, 'rn, 'shift_rm"); Format(instr, "sbc'cond's 'rd, 'rn, 'imm"); break; } case RSC: { Format(instr, "rsc'cond's 'rd, 'rn, 'shift_rm"); Format(instr, "rsc'cond's 'rd, 'rn, 'imm"); break; } case TST: { if (instr->HasS()) { // Format(instr, "tst'cond 'rn, 'shift_rm"); // Format(instr, "tst'cond 'rn, 'imm"); alu_out = rn_val & shifter_operand; SetNZFlags(alu_out); SetCFlag(shifter_carry_out); } else { // Format(instr, "movw'cond 'rd, 'imm"). alu_out = instr->ImmedMovwMovtValue(); set_register(rd, alu_out); } break; } case TEQ: { if (instr->HasS()) { // Format(instr, "teq'cond 'rn, 'shift_rm"); // Format(instr, "teq'cond 'rn, 'imm"); alu_out = rn_val ^ shifter_operand; SetNZFlags(alu_out); SetCFlag(shifter_carry_out); } else { // Other instructions matching this pattern are handled in the // miscellaneous instructions part above. UNREACHABLE(); } break; } case CMP: { if (instr->HasS()) { // Format(instr, "cmp'cond 'rn, 'shift_rm"); // Format(instr, "cmp'cond 'rn, 'imm"); alu_out = rn_val - shifter_operand; SetNZFlags(alu_out); SetCFlag(!BorrowFrom(rn_val, shifter_operand)); SetVFlag(OverflowFrom(alu_out, rn_val, shifter_operand, false)); } else { // Format(instr, "movt'cond 'rd, 'imm"). alu_out = (get_register(rd) & 0xffff) | (instr->ImmedMovwMovtValue() << 16); set_register(rd, alu_out); } break; } case CMN: { if (instr->HasS()) { // Format(instr, "cmn'cond 'rn, 'shift_rm"); // Format(instr, "cmn'cond 'rn, 'imm"); alu_out = rn_val + shifter_operand; SetNZFlags(alu_out); SetCFlag(!CarryFrom(rn_val, shifter_operand)); SetVFlag(OverflowFrom(alu_out, rn_val, shifter_operand, true)); } else { // Other instructions matching this pattern are handled in the // miscellaneous instructions part above. UNREACHABLE(); } break; } case ORR: { // Format(instr, "orr'cond's 'rd, 'rn, 'shift_rm"); // Format(instr, "orr'cond's 'rd, 'rn, 'imm"); alu_out = rn_val | shifter_operand; set_register(rd, alu_out); if (instr->HasS()) { SetNZFlags(alu_out); SetCFlag(shifter_carry_out); } break; } case MOV: { // Format(instr, "mov'cond's 'rd, 'shift_rm"); // Format(instr, "mov'cond's 'rd, 'imm"); alu_out = shifter_operand; set_register(rd, alu_out); if (instr->HasS()) { SetNZFlags(alu_out); SetCFlag(shifter_carry_out); } break; } case BIC: { // Format(instr, "bic'cond's 'rd, 'rn, 'shift_rm"); // Format(instr, "bic'cond's 'rd, 'rn, 'imm"); alu_out = rn_val & ~shifter_operand; set_register(rd, alu_out); if (instr->HasS()) { SetNZFlags(alu_out); SetCFlag(shifter_carry_out); } break; } case MVN: { // Format(instr, "mvn'cond's 'rd, 'shift_rm"); // Format(instr, "mvn'cond's 'rd, 'imm"); alu_out = ~shifter_operand; set_register(rd, alu_out); if (instr->HasS()) { SetNZFlags(alu_out); SetCFlag(shifter_carry_out); } break; } default: { UNREACHABLE(); break; } } } } void Simulator::DecodeType2(Instruction* instr) { int rd = instr->RdValue(); int rn = instr->RnValue(); int32_t rn_val = get_register(rn); int32_t im_val = instr->Offset12Value(); int32_t addr = 0; switch (instr->PUField()) { case da_x: { // Format(instr, "'memop'cond'b 'rd, ['rn], #-'off12"); ASSERT(!instr->HasW()); addr = rn_val; rn_val -= im_val; set_register(rn, rn_val); break; } case ia_x: { // Format(instr, "'memop'cond'b 'rd, ['rn], #+'off12"); ASSERT(!instr->HasW()); addr = rn_val; rn_val += im_val; set_register(rn, rn_val); break; } case db_x: { // Format(instr, "'memop'cond'b 'rd, ['rn, #-'off12]'w"); rn_val -= im_val; addr = rn_val; if (instr->HasW()) { set_register(rn, rn_val); } break; } case ib_x: { // Format(instr, "'memop'cond'b 'rd, ['rn, #+'off12]'w"); rn_val += im_val; addr = rn_val; if (instr->HasW()) { set_register(rn, rn_val); } break; } default: { UNREACHABLE(); break; } } if (instr->HasB()) { if (instr->HasL()) { byte val = ReadBU(addr); set_register(rd, val); } else { byte val = get_register(rd); WriteB(addr, val); } } else { if (instr->HasL()) { set_register(rd, ReadW(addr, instr)); } else { WriteW(addr, get_register(rd), instr); } } } void Simulator::DecodeType3(Instruction* instr) { int rd = instr->RdValue(); int rn = instr->RnValue(); int32_t rn_val = get_register(rn); bool shifter_carry_out = 0; int32_t shifter_operand = GetShiftRm(instr, &shifter_carry_out); int32_t addr = 0; switch (instr->PUField()) { case da_x: { ASSERT(!instr->HasW()); Format(instr, "'memop'cond'b 'rd, ['rn], -'shift_rm"); UNIMPLEMENTED(); break; } case ia_x: { if (instr->HasW()) { ASSERT(instr->Bits(5, 4) == 0x1); if (instr->Bit(22) == 0x1) { // USAT. int32_t sat_pos = instr->Bits(20, 16); int32_t sat_val = (1 << sat_pos) - 1; int32_t shift = instr->Bits(11, 7); int32_t shift_type = instr->Bit(6); int32_t rm_val = get_register(instr->RmValue()); if (shift_type == 0) { // LSL rm_val <<= shift; } else { // ASR rm_val >>= shift; } // If saturation occurs, the Q flag should be set in the CPSR. // There is no Q flag yet, and no instruction (MRS) to read the // CPSR directly. if (rm_val > sat_val) { rm_val = sat_val; } else if (rm_val < 0) { rm_val = 0; } set_register(rd, rm_val); } else { // SSAT. UNIMPLEMENTED(); } return; } else { Format(instr, "'memop'cond'b 'rd, ['rn], +'shift_rm"); UNIMPLEMENTED(); } break; } case db_x: { // Format(instr, "'memop'cond'b 'rd, ['rn, -'shift_rm]'w"); addr = rn_val - shifter_operand; if (instr->HasW()) { set_register(rn, addr); } break; } case ib_x: { if (instr->HasW() && (instr->Bits(6, 4) == 0x5)) { uint32_t widthminus1 = static_cast<uint32_t>(instr->Bits(20, 16)); uint32_t lsbit = static_cast<uint32_t>(instr->Bits(11, 7)); uint32_t msbit = widthminus1 + lsbit; if (msbit <= 31) { if (instr->Bit(22)) { // ubfx - unsigned bitfield extract. uint32_t rm_val = static_cast<uint32_t>(get_register(instr->RmValue())); uint32_t extr_val = rm_val << (31 - msbit); extr_val = extr_val >> (31 - widthminus1); set_register(instr->RdValue(), extr_val); } else { // sbfx - signed bitfield extract. int32_t rm_val = get_register(instr->RmValue()); int32_t extr_val = rm_val << (31 - msbit); extr_val = extr_val >> (31 - widthminus1); set_register(instr->RdValue(), extr_val); } } else { UNREACHABLE(); } return; } else if (!instr->HasW() && (instr->Bits(6, 4) == 0x1)) { uint32_t lsbit = static_cast<uint32_t>(instr->Bits(11, 7)); uint32_t msbit = static_cast<uint32_t>(instr->Bits(20, 16)); if (msbit >= lsbit) { // bfc or bfi - bitfield clear/insert. uint32_t rd_val = static_cast<uint32_t>(get_register(instr->RdValue())); uint32_t bitcount = msbit - lsbit + 1; uint32_t mask = (1 << bitcount) - 1; rd_val &= ~(mask << lsbit); if (instr->RmValue() != 15) { // bfi - bitfield insert. uint32_t rm_val = static_cast<uint32_t>(get_register(instr->RmValue())); rm_val &= mask; rd_val |= rm_val << lsbit; } set_register(instr->RdValue(), rd_val); } else { UNREACHABLE(); } return; } else { // Format(instr, "'memop'cond'b 'rd, ['rn, +'shift_rm]'w"); addr = rn_val + shifter_operand; if (instr->HasW()) { set_register(rn, addr); } } break; } default: { UNREACHABLE(); break; } } if (instr->HasB()) { if (instr->HasL()) { uint8_t byte = ReadB(addr); set_register(rd, byte); } else { uint8_t byte = get_register(rd); WriteB(addr, byte); } } else { if (instr->HasL()) { set_register(rd, ReadW(addr, instr)); } else { WriteW(addr, get_register(rd), instr); } } } void Simulator::DecodeType4(Instruction* instr) { ASSERT(instr->Bit(22) == 0); // only allowed to be set in privileged mode if (instr->HasL()) { // Format(instr, "ldm'cond'pu 'rn'w, 'rlist"); HandleRList(instr, true); } else { // Format(instr, "stm'cond'pu 'rn'w, 'rlist"); HandleRList(instr, false); } } void Simulator::DecodeType5(Instruction* instr) { // Format(instr, "b'l'cond 'target"); int off = (instr->SImmed24Value() << 2); intptr_t pc_address = get_pc(); if (instr->HasLink()) { set_register(lr, pc_address + Instruction::kInstrSize); } int pc_reg = get_register(pc); set_pc(pc_reg + off); } void Simulator::DecodeType6(Instruction* instr) { DecodeType6CoprocessorIns(instr); } void Simulator::DecodeType7(Instruction* instr) { if (instr->Bit(24) == 1) { SoftwareInterrupt(instr); } else { DecodeTypeVFP(instr); } } // void Simulator::DecodeTypeVFP(Instruction* instr) // The Following ARMv7 VFPv instructions are currently supported. // vmov :Sn = Rt // vmov :Rt = Sn // vcvt: Dd = Sm // vcvt: Sd = Dm // Dd = vabs(Dm) // Dd = vneg(Dm) // Dd = vadd(Dn, Dm) // Dd = vsub(Dn, Dm) // Dd = vmul(Dn, Dm) // Dd = vdiv(Dn, Dm) // vcmp(Dd, Dm) // vmrs // Dd = vsqrt(Dm) void Simulator::DecodeTypeVFP(Instruction* instr) { ASSERT((instr->TypeValue() == 7) && (instr->Bit(24) == 0x0) ); ASSERT(instr->Bits(11, 9) == 0x5); // Obtain double precision register codes. int vm = instr->VFPMRegValue(kDoublePrecision); int vd = instr->VFPDRegValue(kDoublePrecision); int vn = instr->VFPNRegValue(kDoublePrecision); if (instr->Bit(4) == 0) { if (instr->Opc1Value() == 0x7) { // Other data processing instructions if ((instr->Opc2Value() == 0x0) && (instr->Opc3Value() == 0x1)) { // vmov register to register. if (instr->SzValue() == 0x1) { int m = instr->VFPMRegValue(kDoublePrecision); int d = instr->VFPDRegValue(kDoublePrecision); set_d_register_from_double(d, get_double_from_d_register(m)); } else { int m = instr->VFPMRegValue(kSinglePrecision); int d = instr->VFPDRegValue(kSinglePrecision); set_s_register_from_float(d, get_float_from_s_register(m)); } } else if ((instr->Opc2Value() == 0x0) && (instr->Opc3Value() == 0x3)) { // vabs double dm_value = get_double_from_d_register(vm); double dd_value = fabs(dm_value); set_d_register_from_double(vd, dd_value); } else if ((instr->Opc2Value() == 0x1) && (instr->Opc3Value() == 0x1)) { // vneg double dm_value = get_double_from_d_register(vm); double dd_value = -dm_value; set_d_register_from_double(vd, dd_value); } else if ((instr->Opc2Value() == 0x7) && (instr->Opc3Value() == 0x3)) { DecodeVCVTBetweenDoubleAndSingle(instr); } else if ((instr->Opc2Value() == 0x8) && (instr->Opc3Value() & 0x1)) { DecodeVCVTBetweenFloatingPointAndInteger(instr); } else if (((instr->Opc2Value() >> 1) == 0x6) && (instr->Opc3Value() & 0x1)) { DecodeVCVTBetweenFloatingPointAndInteger(instr); } else if (((instr->Opc2Value() == 0x4) || (instr->Opc2Value() == 0x5)) && (instr->Opc3Value() & 0x1)) { DecodeVCMP(instr); } else if (((instr->Opc2Value() == 0x1)) && (instr->Opc3Value() == 0x3)) { // vsqrt double dm_value = get_double_from_d_register(vm); double dd_value = sqrt(dm_value); set_d_register_from_double(vd, dd_value); } else if (instr->Opc3Value() == 0x0) { // vmov immediate. if (instr->SzValue() == 0x1) { set_d_register_from_double(vd, instr->DoubleImmedVmov()); } else { UNREACHABLE(); // Not used by v8. } } else { UNREACHABLE(); // Not used by V8. } } else if (instr->Opc1Value() == 0x3) { if (instr->SzValue() != 0x1) { UNREACHABLE(); // Not used by V8. } if (instr->Opc3Value() & 0x1) { // vsub double dn_value = get_double_from_d_register(vn); double dm_value = get_double_from_d_register(vm); double dd_value = dn_value - dm_value; set_d_register_from_double(vd, dd_value); } else { // vadd double dn_value = get_double_from_d_register(vn); double dm_value = get_double_from_d_register(vm); double dd_value = dn_value + dm_value; set_d_register_from_double(vd, dd_value); } } else if ((instr->Opc1Value() == 0x2) && !(instr->Opc3Value() & 0x1)) { // vmul if (instr->SzValue() != 0x1) { UNREACHABLE(); // Not used by V8. } double dn_value = get_double_from_d_register(vn); double dm_value = get_double_from_d_register(vm); double dd_value = dn_value * dm_value; set_d_register_from_double(vd, dd_value); } else if ((instr->Opc1Value() == 0x4) && !(instr->Opc3Value() & 0x1)) { // vdiv if (instr->SzValue() != 0x1) { UNREACHABLE(); // Not used by V8. } double dn_value = get_double_from_d_register(vn); double dm_value = get_double_from_d_register(vm); double dd_value = dn_value / dm_value; div_zero_vfp_flag_ = (dm_value == 0); set_d_register_from_double(vd, dd_value); } else { UNIMPLEMENTED(); // Not used by V8. } } else { if ((instr->VCValue() == 0x0) && (instr->VAValue() == 0x0)) { DecodeVMOVBetweenCoreAndSinglePrecisionRegisters(instr); } else if ((instr->VLValue() == 0x1) && (instr->VCValue() == 0x0) && (instr->VAValue() == 0x7) && (instr->Bits(19, 16) == 0x1)) { // vmrs uint32_t rt = instr->RtValue(); if (rt == 0xF) { Copy_FPSCR_to_APSR(); } else { // Emulate FPSCR from the Simulator flags. uint32_t fpscr = (n_flag_FPSCR_ << 31) | (z_flag_FPSCR_ << 30) | (c_flag_FPSCR_ << 29) | (v_flag_FPSCR_ << 28) | (inexact_vfp_flag_ << 4) | (underflow_vfp_flag_ << 3) | (overflow_vfp_flag_ << 2) | (div_zero_vfp_flag_ << 1) | (inv_op_vfp_flag_ << 0) | (FPSCR_rounding_mode_); set_register(rt, fpscr); } } else if ((instr->VLValue() == 0x0) && (instr->VCValue() == 0x0) && (instr->VAValue() == 0x7) && (instr->Bits(19, 16) == 0x1)) { // vmsr uint32_t rt = instr->RtValue(); if (rt == pc) { UNREACHABLE(); } else { uint32_t rt_value = get_register(rt); n_flag_FPSCR_ = (rt_value >> 31) & 1; z_flag_FPSCR_ = (rt_value >> 30) & 1; c_flag_FPSCR_ = (rt_value >> 29) & 1; v_flag_FPSCR_ = (rt_value >> 28) & 1; inexact_vfp_flag_ = (rt_value >> 4) & 1; underflow_vfp_flag_ = (rt_value >> 3) & 1; overflow_vfp_flag_ = (rt_value >> 2) & 1; div_zero_vfp_flag_ = (rt_value >> 1) & 1; inv_op_vfp_flag_ = (rt_value >> 0) & 1; FPSCR_rounding_mode_ = static_cast<VFPRoundingMode>((rt_value) & kVFPRoundingModeMask); } } else { UNIMPLEMENTED(); // Not used by V8. } } } void Simulator::DecodeVMOVBetweenCoreAndSinglePrecisionRegisters( Instruction* instr) { ASSERT((instr->Bit(4) == 1) && (instr->VCValue() == 0x0) && (instr->VAValue() == 0x0)); int t = instr->RtValue(); int n = instr->VFPNRegValue(kSinglePrecision); bool to_arm_register = (instr->VLValue() == 0x1); if (to_arm_register) { int32_t int_value = get_sinteger_from_s_register(n); set_register(t, int_value); } else { int32_t rs_val = get_register(t); set_s_register_from_sinteger(n, rs_val); } } void Simulator::DecodeVCMP(Instruction* instr) { ASSERT((instr->Bit(4) == 0) && (instr->Opc1Value() == 0x7)); ASSERT(((instr->Opc2Value() == 0x4) || (instr->Opc2Value() == 0x5)) && (instr->Opc3Value() & 0x1)); // Comparison. VFPRegPrecision precision = kSinglePrecision; if (instr->SzValue() == 1) { precision = kDoublePrecision; } int d = instr->VFPDRegValue(precision); int m = 0; if (instr->Opc2Value() == 0x4) { m = instr->VFPMRegValue(precision); } if (precision == kDoublePrecision) { double dd_value = get_double_from_d_register(d); double dm_value = 0.0; if (instr->Opc2Value() == 0x4) { dm_value = get_double_from_d_register(m); } // Raise exceptions for quiet NaNs if necessary. if (instr->Bit(7) == 1) { if (isnan(dd_value)) { inv_op_vfp_flag_ = true; } } Compute_FPSCR_Flags(dd_value, dm_value); } else { UNIMPLEMENTED(); // Not used by V8. } } void Simulator::DecodeVCVTBetweenDoubleAndSingle(Instruction* instr) { ASSERT((instr->Bit(4) == 0) && (instr->Opc1Value() == 0x7)); ASSERT((instr->Opc2Value() == 0x7) && (instr->Opc3Value() == 0x3)); VFPRegPrecision dst_precision = kDoublePrecision; VFPRegPrecision src_precision = kSinglePrecision; if (instr->SzValue() == 1) { dst_precision = kSinglePrecision; src_precision = kDoublePrecision; } int dst = instr->VFPDRegValue(dst_precision); int src = instr->VFPMRegValue(src_precision); if (dst_precision == kSinglePrecision) { double val = get_double_from_d_register(src); set_s_register_from_float(dst, static_cast<float>(val)); } else { float val = get_float_from_s_register(src); set_d_register_from_double(dst, static_cast<double>(val)); } } bool get_inv_op_vfp_flag(VFPRoundingMode mode, double val, bool unsigned_) { ASSERT((mode == RN) || (mode == RM) || (mode == RZ)); double max_uint = static_cast<double>(0xffffffffu); double max_int = static_cast<double>(kMaxInt); double min_int = static_cast<double>(kMinInt); // Check for NaN. if (val != val) { return true; } // Check for overflow. This code works because 32bit integers can be // exactly represented by ieee-754 64bit floating-point values. switch (mode) { case RN: return unsigned_ ? (val >= (max_uint + 0.5)) || (val < -0.5) : (val >= (max_int + 0.5)) || (val < (min_int - 0.5)); case RM: return unsigned_ ? (val >= (max_uint + 1.0)) || (val < 0) : (val >= (max_int + 1.0)) || (val < min_int); case RZ: return unsigned_ ? (val >= (max_uint + 1.0)) || (val <= -1) : (val >= (max_int + 1.0)) || (val <= (min_int - 1.0)); default: UNREACHABLE(); return true; } } // We call this function only if we had a vfp invalid exception. // It returns the correct saturated value. int VFPConversionSaturate(double val, bool unsigned_res) { if (val != val) { return 0; } else { if (unsigned_res) { return (val < 0) ? 0 : 0xffffffffu; } else { return (val < 0) ? kMinInt : kMaxInt; } } } void Simulator::DecodeVCVTBetweenFloatingPointAndInteger(Instruction* instr) { ASSERT((instr->Bit(4) == 0) && (instr->Opc1Value() == 0x7) && (instr->Bits(27, 23) == 0x1D)); ASSERT(((instr->Opc2Value() == 0x8) && (instr->Opc3Value() & 0x1)) || (((instr->Opc2Value() >> 1) == 0x6) && (instr->Opc3Value() & 0x1))); // Conversion between floating-point and integer. bool to_integer = (instr->Bit(18) == 1); VFPRegPrecision src_precision = (instr->SzValue() == 1) ? kDoublePrecision : kSinglePrecision; if (to_integer) { // We are playing with code close to the C++ standard's limits below, // hence the very simple code and heavy checks. // // Note: // C++ defines default type casting from floating point to integer as // (close to) rounding toward zero ("fractional part discarded"). int dst = instr->VFPDRegValue(kSinglePrecision); int src = instr->VFPMRegValue(src_precision); // Bit 7 in vcvt instructions indicates if we should use the FPSCR rounding // mode or the default Round to Zero mode. VFPRoundingMode mode = (instr->Bit(7) != 1) ? FPSCR_rounding_mode_ : RZ; ASSERT((mode == RM) || (mode == RZ) || (mode == RN)); bool unsigned_integer = (instr->Bit(16) == 0); bool double_precision = (src_precision == kDoublePrecision); double val = double_precision ? get_double_from_d_register(src) : get_float_from_s_register(src); int temp = unsigned_integer ? static_cast<uint32_t>(val) : static_cast<int32_t>(val); inv_op_vfp_flag_ = get_inv_op_vfp_flag(mode, val, unsigned_integer); double abs_diff = unsigned_integer ? fabs(val - static_cast<uint32_t>(temp)) : fabs(val - temp); inexact_vfp_flag_ = (abs_diff != 0); if (inv_op_vfp_flag_) { temp = VFPConversionSaturate(val, unsigned_integer); } else { switch (mode) { case RN: { int val_sign = (val > 0) ? 1 : -1; if (abs_diff > 0.5) { temp += val_sign; } else if (abs_diff == 0.5) { // Round to even if exactly halfway. temp = ((temp % 2) == 0) ? temp : temp + val_sign; } break; } case RM: temp = temp > val ? temp - 1 : temp; break; case RZ: // Nothing to do. break; default: UNREACHABLE(); } } // Update the destination register. set_s_register_from_sinteger(dst, temp); } else { bool unsigned_integer = (instr->Bit(7) == 0); int dst = instr->VFPDRegValue(src_precision); int src = instr->VFPMRegValue(kSinglePrecision); int val = get_sinteger_from_s_register(src); if (src_precision == kDoublePrecision) { if (unsigned_integer) { set_d_register_from_double(dst, static_cast<double>((uint32_t)val)); } else { set_d_register_from_double(dst, static_cast<double>(val)); } } else { if (unsigned_integer) { set_s_register_from_float(dst, static_cast<float>((uint32_t)val)); } else { set_s_register_from_float(dst, static_cast<float>(val)); } } } } // void Simulator::DecodeType6CoprocessorIns(Instruction* instr) // Decode Type 6 coprocessor instructions. // Dm = vmov(Rt, Rt2) // <Rt, Rt2> = vmov(Dm) // Ddst = MEM(Rbase + 4*offset). // MEM(Rbase + 4*offset) = Dsrc. void Simulator::DecodeType6CoprocessorIns(Instruction* instr) { ASSERT((instr->TypeValue() == 6)); if (instr->CoprocessorValue() == 0xA) { switch (instr->OpcodeValue()) { case 0x8: case 0xA: case 0xC: case 0xE: { // Load and store single precision float to memory. int rn = instr->RnValue(); int vd = instr->VFPDRegValue(kSinglePrecision); int offset = instr->Immed8Value(); if (!instr->HasU()) { offset = -offset; } int32_t address = get_register(rn) + 4 * offset; if (instr->HasL()) { // Load double from memory: vldr. set_s_register_from_sinteger(vd, ReadW(address, instr)); } else { // Store double to memory: vstr. WriteW(address, get_sinteger_from_s_register(vd), instr); } break; } case 0x4: case 0x5: case 0x6: case 0x7: case 0x9: case 0xB: // Load/store multiple single from memory: vldm/vstm. HandleVList(instr); break; default: UNIMPLEMENTED(); // Not used by V8. } } else if (instr->CoprocessorValue() == 0xB) { switch (instr->OpcodeValue()) { case 0x2: // Load and store double to two GP registers if (instr->Bits(7, 4) != 0x1) { UNIMPLEMENTED(); // Not used by V8. } else { int rt = instr->RtValue(); int rn = instr->RnValue(); int vm = instr->VmValue(); if (instr->HasL()) { int32_t rt_int_value = get_sinteger_from_s_register(2*vm); int32_t rn_int_value = get_sinteger_from_s_register(2*vm+1); set_register(rt, rt_int_value); set_register(rn, rn_int_value); } else { int32_t rs_val = get_register(rt); int32_t rn_val = get_register(rn); set_s_register_from_sinteger(2*vm, rs_val); set_s_register_from_sinteger((2*vm+1), rn_val); } } break; case 0x8: case 0xC: { // Load and store double to memory. int rn = instr->RnValue(); int vd = instr->VdValue(); int offset = instr->Immed8Value(); if (!instr->HasU()) { offset = -offset; } int32_t address = get_register(rn) + 4 * offset; if (instr->HasL()) { // Load double from memory: vldr. set_s_register_from_sinteger(2*vd, ReadW(address, instr)); set_s_register_from_sinteger(2*vd + 1, ReadW(address + 4, instr)); } else { // Store double to memory: vstr. WriteW(address, get_sinteger_from_s_register(2*vd), instr); WriteW(address + 4, get_sinteger_from_s_register(2*vd + 1), instr); } break; } case 0x4: case 0x5: case 0x9: // Load/store multiple double from memory: vldm/vstm. HandleVList(instr); break; default: UNIMPLEMENTED(); // Not used by V8. } } else { UNIMPLEMENTED(); // Not used by V8. } } // Executes the current instruction. void Simulator::InstructionDecode(Instruction* instr) { if (v8::internal::FLAG_check_icache) { CheckICache(isolate_->simulator_i_cache(), instr); } pc_modified_ = false; if (::v8::internal::FLAG_trace_sim) { disasm::NameConverter converter; disasm::Disassembler dasm(converter); // use a reasonably large buffer v8::internal::EmbeddedVector<char, 256> buffer; dasm.InstructionDecode(buffer, reinterpret_cast<byte*>(instr)); PrintF(" 0x%08x %s\n", reinterpret_cast<intptr_t>(instr), buffer.start()); } if (instr->ConditionField() == kSpecialCondition) { UNIMPLEMENTED(); } else if (ConditionallyExecute(instr)) { switch (instr->TypeValue()) { case 0: case 1: { DecodeType01(instr); break; } case 2: { DecodeType2(instr); break; } case 3: { DecodeType3(instr); break; } case 4: { DecodeType4(instr); break; } case 5: { DecodeType5(instr); break; } case 6: { DecodeType6(instr); break; } case 7: { DecodeType7(instr); break; } default: { UNIMPLEMENTED(); break; } } // If the instruction is a non taken conditional stop, we need to skip the // inlined message address. } else if (instr->IsStop()) { set_pc(get_pc() + 2 * Instruction::kInstrSize); } if (!pc_modified_) { set_register(pc, reinterpret_cast<int32_t>(instr) + Instruction::kInstrSize); } } void Simulator::Execute() { // 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. int program_counter = get_pc(); if (::v8::internal::FLAG_stop_sim_at == 0) { // Fast version of the dispatch loop without checking whether the simulator // should be stopping at a particular executed instruction. while (program_counter != end_sim_pc) { Instruction* instr = reinterpret_cast<Instruction*>(program_counter); icount_++; InstructionDecode(instr); program_counter = get_pc(); } } else { // FLAG_stop_sim_at is at the non-default value. Stop in the debugger when // we reach the particular instuction count. while (program_counter != end_sim_pc) { Instruction* instr = reinterpret_cast<Instruction*>(program_counter); icount_++; if (icount_ == ::v8::internal::FLAG_stop_sim_at) { ArmDebugger dbg(this); dbg.Debug(); } else { InstructionDecode(instr); } program_counter = get_pc(); } } } int32_t Simulator::Call(byte* entry, int argument_count, ...) { va_list parameters; va_start(parameters, argument_count); // Setup arguments // First four arguments passed in registers. ASSERT(argument_count >= 4); set_register(r0, va_arg(parameters, int32_t)); set_register(r1, va_arg(parameters, int32_t)); set_register(r2, va_arg(parameters, int32_t)); set_register(r3, va_arg(parameters, int32_t)); // Remaining arguments passed on stack. int original_stack = get_register(sp); // Compute position of stack on entry to generated code. int entry_stack = (original_stack - (argument_count - 4) * sizeof(int32_t)); if (OS::ActivationFrameAlignment() != 0) { entry_stack &= -OS::ActivationFrameAlignment(); } // Store remaining arguments on stack, from low to high memory. intptr_t* stack_argument = reinterpret_cast<intptr_t*>(entry_stack); for (int i = 4; i < argument_count; i++) { stack_argument[i - 4] = va_arg(parameters, int32_t); } va_end(parameters); set_register(sp, entry_stack); // Prepare to execute the code at entry set_register(pc, reinterpret_cast<int32_t>(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. set_register(lr, end_sim_pc); // Remember the values of callee-saved registers. // The code below assumes that r9 is not used as sb (static base) in // simulator code and therefore is regarded as a callee-saved register. int32_t r4_val = get_register(r4); int32_t r5_val = get_register(r5); int32_t r6_val = get_register(r6); int32_t r7_val = get_register(r7); int32_t r8_val = get_register(r8); int32_t r9_val = get_register(r9); int32_t r10_val = get_register(r10); int32_t r11_val = get_register(r11); // Setup the callee-saved registers with a known value. To be able to check // that they are preserved properly across JS execution. int32_t callee_saved_value = icount_; set_register(r4, callee_saved_value); set_register(r5, callee_saved_value); set_register(r6, callee_saved_value); set_register(r7, callee_saved_value); set_register(r8, callee_saved_value); set_register(r9, callee_saved_value); set_register(r10, callee_saved_value); set_register(r11, callee_saved_value); // Start the simulation Execute(); // Check that the callee-saved registers have been preserved. CHECK_EQ(callee_saved_value, get_register(r4)); CHECK_EQ(callee_saved_value, get_register(r5)); CHECK_EQ(callee_saved_value, get_register(r6)); CHECK_EQ(callee_saved_value, get_register(r7)); CHECK_EQ(callee_saved_value, get_register(r8)); CHECK_EQ(callee_saved_value, get_register(r9)); CHECK_EQ(callee_saved_value, get_register(r10)); CHECK_EQ(callee_saved_value, get_register(r11)); // Restore callee-saved registers with the original value. set_register(r4, r4_val); set_register(r5, r5_val); set_register(r6, r6_val); set_register(r7, r7_val); set_register(r8, r8_val); set_register(r9, r9_val); set_register(r10, r10_val); set_register(r11, r11_val); // Pop stack passed arguments. CHECK_EQ(entry_stack, get_register(sp)); set_register(sp, original_stack); int32_t result = get_register(r0); return result; } uintptr_t Simulator::PushAddress(uintptr_t address) { int new_sp = get_register(sp) - sizeof(uintptr_t); uintptr_t* stack_slot = reinterpret_cast<uintptr_t*>(new_sp); *stack_slot = address; set_register(sp, new_sp); return new_sp; } uintptr_t Simulator::PopAddress() { int current_sp = get_register(sp); uintptr_t* stack_slot = reinterpret_cast<uintptr_t*>(current_sp); uintptr_t address = *stack_slot; set_register(sp, current_sp + sizeof(uintptr_t)); return address; } } } // namespace v8::internal #endif // USE_SIMULATOR #endif // V8_TARGET_ARCH_ARM