/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ // 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 "jit/mips32/Simulator-mips32.h" #include "mozilla/Casting.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Likely.h" #include "mozilla/MathAlgorithms.h" #include #include "jit/AtomicOperations.h" #include "jit/mips32/Assembler-mips32.h" #include "js/UniquePtr.h" #include "js/Utility.h" #include "vm/Runtime.h" #include "wasm/WasmInstance.h" #include "wasm/WasmSignalHandlers.h" #define I8(v) static_cast(v) #define I16(v) static_cast(v) #define U16(v) static_cast(v) #define I32(v) static_cast(v) #define U32(v) static_cast(v) namespace js { namespace jit { static const Instr kCallRedirInstr = op_special | MAX_BREAK_CODE << FunctionBits | ff_break; // Utils functions. static bool HaveSameSign(int32_t a, int32_t b) { return ((a ^ b) >= 0); } static uint32_t GetFCSRConditionBit(uint32_t cc) { if (cc == 0) { return 23; } else { return 24 + cc; } } static const int32_t kRegisterskMaxValue = 0x7fffffff; static const int32_t kRegisterskMinValue = 0x80000000; // ----------------------------------------------------------------------------- // MIPS assembly various constants. class SimInstruction { public: enum { kInstrSize = 4, // On MIPS PC cannot actually be directly accessed. We behave as if PC was // always the value of the current instruction being executed. kPCReadOffset = 0 }; // Get the raw instruction bits. inline Instr instructionBits() const { return *reinterpret_cast(this); } // Set the raw instruction bits to value. inline void setInstructionBits(Instr value) { *reinterpret_cast(this) = value; } // Read one particular bit out of the instruction bits. inline int bit(int nr) const { return (instructionBits() >> nr) & 1; } // Read a bit field out of the instruction bits. inline int bits(int hi, int lo) const { return (instructionBits() >> lo) & ((2 << (hi - lo)) - 1); } // Instruction type. enum Type { kRegisterType, kImmediateType, kJumpType, kUnsupported = -1 }; // Get the encoding type of the instruction. Type instructionType() const; // Accessors for the different named fields used in the MIPS encoding. inline OpcodeField opcodeValue() const { return static_cast( bits(OpcodeShift + OpcodeBits - 1, OpcodeShift)); } inline int rsValue() const { MOZ_ASSERT(instructionType() == kRegisterType || instructionType() == kImmediateType); return bits(RSShift + RSBits - 1, RSShift); } inline int rtValue() const { MOZ_ASSERT(instructionType() == kRegisterType || instructionType() == kImmediateType); return bits(RTShift + RTBits - 1, RTShift); } inline int rdValue() const { MOZ_ASSERT(instructionType() == kRegisterType); return bits(RDShift + RDBits - 1, RDShift); } inline int saValue() const { MOZ_ASSERT(instructionType() == kRegisterType); return bits(SAShift + SABits - 1, SAShift); } inline int functionValue() const { MOZ_ASSERT(instructionType() == kRegisterType || instructionType() == kImmediateType); return bits(FunctionShift + FunctionBits - 1, FunctionShift); } inline int fdValue() const { return bits(FDShift + FDBits - 1, FDShift); } inline int fsValue() const { return bits(FSShift + FSBits - 1, FSShift); } inline int ftValue() const { return bits(FTShift + FTBits - 1, FTShift); } inline int frValue() const { return bits(FRShift + FRBits - 1, FRShift); } // Float Compare condition code instruction bits. inline int fcccValue() const { return bits(FCccShift + FCccBits - 1, FCccShift); } // Float Branch condition code instruction bits. inline int fbccValue() const { return bits(FBccShift + FBccBits - 1, FBccShift); } // Float Branch true/false instruction bit. inline int fbtrueValue() const { return bits(FBtrueShift + FBtrueBits - 1, FBtrueShift); } // Return the fields at their original place in the instruction encoding. inline OpcodeField opcodeFieldRaw() const { return static_cast(instructionBits() & OpcodeMask); } inline int rsFieldRaw() const { MOZ_ASSERT(instructionType() == kRegisterType || instructionType() == kImmediateType); return instructionBits() & RSMask; } // Same as above function, but safe to call within instructionType(). inline int rsFieldRawNoAssert() const { return instructionBits() & RSMask; } inline int rtFieldRaw() const { MOZ_ASSERT(instructionType() == kRegisterType || instructionType() == kImmediateType); return instructionBits() & RTMask; } inline int rdFieldRaw() const { MOZ_ASSERT(instructionType() == kRegisterType); return instructionBits() & RDMask; } inline int saFieldRaw() const { MOZ_ASSERT(instructionType() == kRegisterType); return instructionBits() & SAMask; } inline int functionFieldRaw() const { return instructionBits() & FunctionMask; } // Get the secondary field according to the opcode. inline int secondaryValue() const { OpcodeField op = opcodeFieldRaw(); switch (op) { case op_special: case op_special2: return functionValue(); case op_cop1: return rsValue(); case op_regimm: return rtValue(); default: return ff_null; } } inline int32_t imm16Value() const { MOZ_ASSERT(instructionType() == kImmediateType); return bits(Imm16Shift + Imm16Bits - 1, Imm16Shift); } inline int32_t imm26Value() const { MOZ_ASSERT(instructionType() == kJumpType); return bits(Imm26Shift + Imm26Bits - 1, Imm26Shift); } // Say if the instruction should not be used in a branch delay slot. bool isForbiddenInBranchDelay() const; // Say if the instruction 'links'. e.g. jal, bal. bool isLinkingInstruction() const; // Say if the instruction is a debugger break/trap. bool isTrap() const; private: SimInstruction() = delete; SimInstruction(const SimInstruction& other) = delete; void operator=(const SimInstruction& other) = delete; }; bool SimInstruction::isForbiddenInBranchDelay() const { const int op = opcodeFieldRaw(); switch (op) { case op_j: case op_jal: case op_beq: case op_bne: case op_blez: case op_bgtz: case op_beql: case op_bnel: case op_blezl: case op_bgtzl: return true; case op_regimm: switch (rtFieldRaw()) { case rt_bltz: case rt_bgez: case rt_bltzal: case rt_bgezal: return true; default: return false; }; break; case op_special: switch (functionFieldRaw()) { case ff_jr: case ff_jalr: return true; default: return false; }; break; default: return false; } } bool SimInstruction::isLinkingInstruction() const { const int op = opcodeFieldRaw(); switch (op) { case op_jal: return true; case op_regimm: switch (rtFieldRaw()) { case rt_bgezal: case rt_bltzal: return true; default: return false; }; case op_special: switch (functionFieldRaw()) { case ff_jalr: return true; default: return false; }; default: return false; }; } bool SimInstruction::isTrap() const { if (opcodeFieldRaw() != op_special) { return false; } else { switch (functionFieldRaw()) { case ff_break: return instructionBits() != kCallRedirInstr; case ff_tge: case ff_tgeu: case ff_tlt: case ff_tltu: case ff_teq: case ff_tne: return bits(15, 6) != kWasmTrapCode; default: return false; }; } } SimInstruction::Type SimInstruction::instructionType() const { switch (opcodeFieldRaw()) { case op_special: switch (functionFieldRaw()) { case ff_jr: case ff_jalr: case ff_break: case ff_sll: case ff_srl: case ff_sra: case ff_sllv: case ff_srlv: case ff_srav: case ff_mfhi: case ff_mflo: case ff_mult: case ff_multu: case ff_div: case ff_divu: case ff_add: case ff_addu: case ff_sub: case ff_subu: case ff_and: case ff_or: case ff_xor: case ff_nor: case ff_slt: case ff_sltu: case ff_tge: case ff_tgeu: case ff_tlt: case ff_tltu: case ff_teq: case ff_tne: case ff_movz: case ff_movn: case ff_movci: case ff_sync: return kRegisterType; default: return kUnsupported; }; break; case op_special2: switch (functionFieldRaw()) { case ff_mul: case ff_madd: case ff_maddu: case ff_clz: return kRegisterType; default: return kUnsupported; }; break; case op_special3: switch (functionFieldRaw()) { case ff_ins: case ff_ext: case ff_bshfl: return kRegisterType; default: return kUnsupported; }; break; case op_cop1: // Coprocessor instructions. switch (rsFieldRawNoAssert()) { case rs_bc1: // Branch on coprocessor condition. return kImmediateType; default: return kRegisterType; }; break; case op_cop1x: return kRegisterType; // 16 bits Immediate type instructions. e.g.: addi dest, src, imm16. case op_regimm: case op_beq: case op_bne: case op_blez: case op_bgtz: case op_addi: case op_addiu: case op_slti: case op_sltiu: case op_andi: case op_ori: case op_xori: case op_lui: case op_beql: case op_bnel: case op_blezl: case op_bgtzl: case op_lb: case op_lh: case op_lwl: case op_lw: case op_lbu: case op_lhu: case op_lwr: case op_sb: case op_sh: case op_swl: case op_sw: case op_swr: case op_lwc1: case op_ldc1: case op_swc1: case op_sdc1: case op_ll: case op_sc: return kImmediateType; // 26 bits immediate type instructions. e.g.: j imm26. case op_j: case op_jal: return kJumpType; default: return kUnsupported; } return kUnsupported; } // C/C++ argument slots size. const int kCArgSlotCount = 4; const int kCArgsSlotsSize = kCArgSlotCount * SimInstruction::kInstrSize; const int kBranchReturnOffset = 2 * SimInstruction::kInstrSize; class CachePage { public: static const int LINE_VALID = 0; static const int LINE_INVALID = 1; static const int kPageShift = 12; static const int kPageSize = 1 << kPageShift; static const int kPageMask = kPageSize - 1; static const int kLineShift = 2; // The cache line is only 4 bytes right now. static const int kLineLength = 1 << kLineShift; static const int kLineMask = kLineLength - 1; CachePage() { memset(&validity_map_, LINE_INVALID, sizeof(validity_map_)); } char* validityByte(int offset) { return &validity_map_[offset >> kLineShift]; } char* cachedData(int offset) { return &data_[offset]; } private: char data_[kPageSize]; // The cached data. static const int kValidityMapSize = kPageSize >> kLineShift; char validity_map_[kValidityMapSize]; // One byte per line. }; // Protects the icache() and redirection() properties of the // Simulator. class AutoLockSimulatorCache : public LockGuard { using Base = LockGuard; public: AutoLockSimulatorCache() : Base(SimulatorProcess::singleton_->cacheLock_) {} }; mozilla::Atomic SimulatorProcess::ICacheCheckingDisableCount( 1); // Checking is disabled by default. SimulatorProcess* SimulatorProcess::singleton_ = nullptr; int Simulator::StopSimAt = -1; Simulator* Simulator::Create() { auto sim = MakeUnique(); if (!sim) { return nullptr; } if (!sim->init()) { return nullptr; } char* stopAtStr = getenv("MIPS_SIM_STOP_AT"); int64_t stopAt; if (stopAtStr && sscanf(stopAtStr, "%lld", &stopAt) == 1) { fprintf(stderr, "\nStopping simulation at icount %lld\n", stopAt); Simulator::StopSimAt = stopAt; } return sim.release(); } void Simulator::Destroy(Simulator* sim) { js_delete(sim); } // The MipsDebugger class is used by the simulator while debugging simulated // code. class MipsDebugger { public: explicit MipsDebugger(Simulator* sim) : sim_(sim) {} void stop(SimInstruction* 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 = op_special | ff_break | 0xfffff << 6; static const Instr kNopInstr = op_special | ff_sll; 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(SimInstruction* breakpc); bool deleteBreakpoint(SimInstruction* 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(); }; static void UNSUPPORTED() { printf("Unsupported instruction.\n"); MOZ_CRASH(); } void MipsDebugger::stop(SimInstruction* 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(sim_->get_pc() + SimInstruction::kInstrSize); // Update this stop description. if (!sim_->watchedStops_[code].desc_) { sim_->watchedStops_[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 * SimInstruction::kInstrSize); debug(); } int32_t MipsDebugger::getRegisterValue(int regnum) { if (regnum == kPCRegister) { return sim_->get_pc(); } return sim_->getRegister(regnum); } int32_t MipsDebugger::getFPURegisterValueInt(int regnum) { return sim_->getFpuRegister(regnum); } int64_t MipsDebugger::getFPURegisterValueLong(int regnum) { return sim_->getFpuRegisterLong(regnum); } float MipsDebugger::getFPURegisterValueFloat(int regnum) { return sim_->getFpuRegisterFloat(regnum); } double MipsDebugger::getFPURegisterValueDouble(int regnum) { return sim_->getFpuRegisterDouble(regnum); } bool MipsDebugger::getValue(const char* desc, int32_t* value) { Register reg = Register::FromName(desc); if (reg != InvalidReg) { *value = getRegisterValue(reg.code()); return true; } if (strncmp(desc, "0x", 2) == 0) { return sscanf(desc, "%x", reinterpret_cast(value)) == 1; } return sscanf(desc, "%i", value) == 1; } bool MipsDebugger::setBreakpoint(SimInstruction* breakpc) { // Check if a breakpoint can be set. If not return without any side-effects. if (sim_->break_pc_ != nullptr) { 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(SimInstruction* breakpc) { if (sim_->break_pc_ != nullptr) { sim_->break_pc_->setInstructionBits(sim_->break_instr_); } sim_->break_pc_ = nullptr; sim_->break_instr_ = 0; return true; } void MipsDebugger::undoBreakpoints() { if (sim_->break_pc_) { sim_->break_pc_->setInstructionBits(sim_->break_instr_); } } void MipsDebugger::redoBreakpoints() { if (sim_->break_pc_) { sim_->break_pc_->setInstructionBits(kBreakpointInstr); } } void MipsDebugger::printAllRegs() { int32_t value; for (uint32_t i = 0; i < Registers::Total; i++) { value = getRegisterValue(i); printf("%3s: 0x%08x %10d ", Registers::GetName(i), value, value); if (i % 2) { printf("\n"); } } printf("\n"); value = getRegisterValue(Simulator::LO); printf(" LO: 0x%08x %10d ", value, value); value = getRegisterValue(Simulator::HI); printf(" HI: 0x%08x %10d\n", value, value); value = getRegisterValue(Simulator::pc); printf(" pc: 0x%08x\n", value); } void MipsDebugger::printAllRegsIncludingFPU() { printAllRegs(); printf("\n\n"); // f0, f1, f2, ... f31. for (uint32_t i = 0; i < FloatRegisters::RegisterIdLimit; i++) { if (i & 0x1) { printf("%3s: 0x%08x\tflt: %-8.4g\n", FloatRegisters::GetName(i), getFPURegisterValueInt(i), getFPURegisterValueFloat(i)); } else { printf("%3s: 0x%08x\tflt: %-8.4g\tdbl: %-16.4g\n", FloatRegisters::GetName(i), getFPURegisterValueInt(i), getFPURegisterValueFloat(i), getFPURegisterValueDouble(i)); } } } static char* ReadLine(const char* prompt) { UniqueChars result; char lineBuf[256]; int offset = 0; bool keepGoing = true; fprintf(stdout, "%s", prompt); fflush(stdout); while (keepGoing) { if (fgets(lineBuf, sizeof(lineBuf), stdin) == nullptr) { // fgets got an error. Just give up. return nullptr; } int len = strlen(lineBuf); if (len > 0 && lineBuf[len - 1] == '\n') { // Since we read a new line we are done reading the line. This // will exit the loop after copying this buffer into the result. keepGoing = false; } if (!result) { // Allocate the initial result and make room for the terminating '\0' result.reset(js_pod_malloc(len + 1)); if (!result) { return nullptr; } } else { // Allocate a new result with enough room for the new addition. int new_len = offset + len + 1; char* new_result = js_pod_malloc(new_len); if (!new_result) { return nullptr; } // Copy the existing input into the new array and set the new // array as the result. memcpy(new_result, result.get(), offset * sizeof(char)); result.reset(new_result); } // Copy the newly read line into the result. memcpy(result.get() + offset, lineBuf, len * sizeof(char)); offset += len; } MOZ_ASSERT(result); result[offset] = '\0'; return result.release(); } static void DisassembleInstruction(uint32_t pc) { uint8_t* bytes = reinterpret_cast(pc); char hexbytes[256]; sprintf(hexbytes, "0x%x 0x%x 0x%x 0x%x", bytes[0], bytes[1], bytes[2], bytes[3]); char llvmcmd[1024]; sprintf(llvmcmd, "bash -c \"echo -n '%p'; echo '%s' | " "llvm-mc -disassemble -arch=mipsel -mcpu=mips32r2 | " "grep -v pure_instructions | grep -v .text\"", static_cast(bytes), hexbytes); if (system(llvmcmd)) { printf("Cannot disassemble instruction.\n"); } } void MipsDebugger::debug() { intptr_t lastPC = -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 (lastPC != sim_->get_pc()) { DisassembleInstruction(sim_->get_pc()); lastPC = sim_->get_pc(); } char* line = ReadLine("sim> "); if (line == nullptr) { break; } else { char* last_input = sim_->lastDebuggerInput(); if (strcmp(line, "\n") == 0 && last_input != nullptr) { line = last_input; } else { // Ownership is transferred to sim_; sim_->setLastDebuggerInput(line); } // Use sscanf to parse the individual parts of the command line. At the // moment no command expects more than two parameters. int argc = sscanf(line, "%" XSTR(COMMAND_SIZE) "s " "%" XSTR(ARG_SIZE) "s " "%" XSTR(ARG_SIZE) "s", cmd, arg1, arg2); if ((strcmp(cmd, "si") == 0) || (strcmp(cmd, "stepi") == 0)) { SimInstruction* instr = reinterpret_cast(sim_->get_pc()); if (!instr->isTrap()) { sim_->instructionDecode( reinterpret_cast(sim_->get_pc())); } else { // Allow si to jump over generated breakpoints. printf("/!\\ Jumping over generated breakpoint.\n"); sim_->set_pc(sim_->get_pc() + SimInstruction::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(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; if (strcmp(arg1, "all") == 0) { printAllRegs(); } else if (strcmp(arg1, "allf") == 0) { printAllRegsIncludingFPU(); } else { Register reg = Register::FromName(arg1); FloatRegisters::Code fCode = FloatRegister::FromName(arg1); if (reg != InvalidReg) { value = getRegisterValue(reg.code()); printf("%s: 0x%08x %d \n", arg1, value, value); } else if (fCode != FloatRegisters::Invalid) { if (fCode & 0x1) { printf("%3s: 0x%08x\tflt: %-8.4g\n", FloatRegisters::GetName(fCode), getFPURegisterValueInt(fCode), getFPURegisterValueFloat(fCode)); } else { printf("%3s: 0x%08x\tflt: %-8.4g\tdbl: %-16.4g\n", FloatRegisters::GetName(fCode), getFPURegisterValueInt(fCode), getFPURegisterValueFloat(fCode), getFPURegisterValueDouble(fCode)); } } else { printf("%s unrecognized\n", arg1); } } } else { printf("print or print single\n"); } } else if (strcmp(cmd, "stack") == 0 || strcmp(cmd, "mem") == 0) { int32_t* cur = nullptr; int32_t* end = nullptr; int next_arg = 1; if (strcmp(cmd, "stack") == 0) { cur = reinterpret_cast(sim_->getRegister(Simulator::sp)); } else { // Command "mem". int32_t value; if (!getValue(arg1, &value)) { printf("%s unrecognized\n", arg1); continue; } cur = reinterpret_cast(value); next_arg++; } int32_t words; if (argc == next_arg) { words = 10; } else { if (!getValue(argv[next_arg], &words)) { words = 10; } } end = cur + words; while (cur < end) { printf(" %p: 0x%08x %10d", cur, *cur, *cur); printf("\n"); cur++; } } else if ((strcmp(cmd, "disasm") == 0) || (strcmp(cmd, "dpc") == 0) || (strcmp(cmd, "di") == 0)) { uint8_t* cur = nullptr; uint8_t* end = nullptr; if (argc == 1) { cur = reinterpret_cast(sim_->get_pc()); end = cur + (10 * SimInstruction::kInstrSize); } else if (argc == 2) { Register reg = Register::FromName(arg1); if (reg != InvalidReg || strncmp(arg1, "0x", 2) == 0) { // The argument is an address or a register name. int32_t value; if (getValue(arg1, &value)) { cur = reinterpret_cast(value); // Disassemble 10 instructions at . end = cur + (10 * SimInstruction::kInstrSize); } } else { // The argument is the number of instructions. int32_t value; if (getValue(arg1, &value)) { cur = reinterpret_cast(sim_->get_pc()); // Disassemble instructions. end = cur + (value * SimInstruction::kInstrSize); } } } else { int32_t value1; int32_t value2; if (getValue(arg1, &value1) && getValue(arg2, &value2)) { cur = reinterpret_cast(value1); end = cur + (value2 * SimInstruction::kInstrSize); } } while (cur < end) { DisassembleInstruction(uint32_t(cur)); cur += SimInstruction::kInstrSize; } } else if (strcmp(cmd, "gdb") == 0) { printf("relinquishing control to gdb\n"); asm("int $3"); 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(value))) { printf("setting breakpoint failed\n"); } } else { printf("%s unrecognized\n", arg1); } } else { printf("break
\n"); } } else if (strcmp(cmd, "del") == 0) { if (!deleteBreakpoint(nullptr)) { 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 * SimInstruction::kInstrSize; SimInstruction* stop_instr = reinterpret_cast(stop_pc); SimInstruction* msg_address = reinterpret_cast( stop_pc + SimInstruction::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, "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 \n"); printf(" print register content (alias 'p')\n"); printf(" use register name 'all' to print all registers\n"); printf("printobject \n"); printf(" print an object from a register (alias 'po')\n"); printf("stack []\n"); printf(" dump stack content, default dump 10 words)\n"); printf("mem
[]\n"); printf(" dump memory content, default dump 10 words)\n"); printf("flags\n"); printf(" print flags\n"); printf("disasm []\n"); printf("disasm [
]\n"); printf("disasm [[
] ]\n"); printf(" disassemble code, default is 10 instructions\n"); printf(" from pc (alias 'di')\n"); printf("gdb\n"); printf(" enter gdb\n"); printf("break
\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/ : print infos about number \n"); printf(" or all stop(s).\n"); printf(" stop enable/disable all/ : enables / disables\n"); printf(" all or number stop(s)\n"); 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); } } } // 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 AllOnOnePage(uintptr_t start, int size) { intptr_t start_page = (start & ~CachePage::kPageMask); intptr_t end_page = ((start + size) & ~CachePage::kPageMask); return start_page == end_page; } void Simulator::setLastDebuggerInput(char* input) { js_free(lastDebuggerInput_); lastDebuggerInput_ = input; } static CachePage* GetCachePageLocked(SimulatorProcess::ICacheMap& i_cache, void* page) { SimulatorProcess::ICacheMap::AddPtr p = i_cache.lookupForAdd(page); if (p) { return p->value(); } AutoEnterOOMUnsafeRegion oomUnsafe; CachePage* new_page = js_new(); if (!new_page || !i_cache.add(p, page, new_page)) { oomUnsafe.crash("Simulator CachePage"); } return new_page; } // Flush from start up to and not including start + size. static void FlushOnePageLocked(SimulatorProcess::ICacheMap& i_cache, intptr_t start, int size) { MOZ_ASSERT(size <= CachePage::kPageSize); MOZ_ASSERT(AllOnOnePage(start, size - 1)); MOZ_ASSERT((start & CachePage::kLineMask) == 0); MOZ_ASSERT((size & CachePage::kLineMask) == 0); void* page = reinterpret_cast(start & (~CachePage::kPageMask)); int offset = (start & CachePage::kPageMask); CachePage* cache_page = GetCachePageLocked(i_cache, page); char* valid_bytemap = cache_page->validityByte(offset); memset(valid_bytemap, CachePage::LINE_INVALID, size >> CachePage::kLineShift); } static void FlushICacheLocked(SimulatorProcess::ICacheMap& i_cache, void* start_addr, size_t size) { intptr_t start = reinterpret_cast(start_addr); int intra_line = (start & CachePage::kLineMask); start -= intra_line; size += intra_line; size = ((size - 1) | CachePage::kLineMask) + 1; int offset = (start & CachePage::kPageMask); while (!AllOnOnePage(start, size - 1)) { int bytes_to_flush = CachePage::kPageSize - offset; FlushOnePageLocked(i_cache, start, bytes_to_flush); start += bytes_to_flush; size -= bytes_to_flush; MOZ_ASSERT((start & CachePage::kPageMask) == 0); offset = 0; } if (size != 0) { FlushOnePageLocked(i_cache, start, size); } } /* static */ void SimulatorProcess::checkICacheLocked(SimInstruction* instr) { intptr_t address = reinterpret_cast(instr); void* page = reinterpret_cast(address & (~CachePage::kPageMask)); void* line = reinterpret_cast(address & (~CachePage::kLineMask)); int offset = (address & CachePage::kPageMask); CachePage* cache_page = GetCachePageLocked(icache(), page); char* cache_valid_byte = cache_page->validityByte(offset); bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID); char* cached_line = cache_page->cachedData(offset & ~CachePage::kLineMask); if (cache_hit) { // Check that the data in memory matches the contents of the I-cache. int cmpret = memcmp(reinterpret_cast(instr), cache_page->cachedData(offset), SimInstruction::kInstrSize); MOZ_ASSERT(cmpret == 0); } else { // Cache miss. Load memory into the cache. memcpy(cached_line, line, CachePage::kLineLength); *cache_valid_byte = CachePage::LINE_VALID; } } HashNumber SimulatorProcess::ICacheHasher::hash(const Lookup& l) { return static_cast(reinterpret_cast(l)) >> 2; } bool SimulatorProcess::ICacheHasher::match(const Key& k, const Lookup& l) { MOZ_ASSERT((reinterpret_cast(k) & CachePage::kPageMask) == 0); MOZ_ASSERT((reinterpret_cast(l) & CachePage::kPageMask) == 0); return k == l; } /* static */ void SimulatorProcess::FlushICache(void* start_addr, size_t size) { if (!ICacheCheckingDisableCount) { AutoLockSimulatorCache als; js::jit::FlushICacheLocked(icache(), start_addr, size); } } Simulator::Simulator() { // Set up simulator support first. Some of this information is needed to // setup the architecture state. // Note, allocation and anything that depends on allocated memory is // deferred until init(), in order to handle OOM properly. stack_ = nullptr; stackLimit_ = 0; pc_modified_ = false; icount_ = 0; break_count_ = 0; break_pc_ = nullptr; break_instr_ = 0; single_stepping_ = false; single_step_callback_ = nullptr; single_step_callback_arg_ = nullptr; // Set up architecture state. // All registers are initialized to zero to start with. for (int i = 0; i < Register::kNumSimuRegisters; i++) { registers_[i] = 0; } for (int i = 0; i < Simulator::FPURegister::kNumFPURegisters; i++) { FPUregisters_[i] = 0; } FCSR_ = 0; LLBit_ = false; LLAddr_ = 0; lastLLValue_ = 0; // The ra and pc are initialized to a known bad value that will cause an // access violation if the simulator ever tries to execute it. registers_[pc] = bad_ra; registers_[ra] = bad_ra; for (int i = 0; i < kNumExceptions; i++) { exceptions[i] = 0; } lastDebuggerInput_ = nullptr; } bool Simulator::init() { // Allocate 2MB for the stack. Note that we will only use 1MB, see below. static const size_t stackSize = 2 * 1024 * 1024; stack_ = js_pod_malloc(stackSize); if (!stack_) { return false; } // Leave a safety margin of 1MB to prevent overrunning the stack when // pushing values (total stack size is 2MB). stackLimit_ = reinterpret_cast(stack_) + 1024 * 1024; // The sp is initialized to point to the bottom (high address) of the // allocated stack area. To be safe in potential stack underflows we leave // some buffer below. registers_[sp] = reinterpret_cast(stack_) + stackSize - 64; return true; } // When the generated code calls an external reference we need to catch that in // the simulator. The external reference will be a function compiled for the // host architecture. We need to call that function instead of trying to // execute it with the simulator. We do that by redirecting the external // reference to a swi (software-interrupt) instruction that is handled by // the simulator. We write the original destination of the jump just at a known // offset from the swi instruction so the simulator knows what to call. class Redirection { friend class SimulatorProcess; // sim's lock must already be held. Redirection(void* nativeFunction, ABIFunctionType type) : nativeFunction_(nativeFunction), swiInstruction_(kCallRedirInstr), type_(type), next_(nullptr) { next_ = SimulatorProcess::redirection(); if (!SimulatorProcess::ICacheCheckingDisableCount) { FlushICacheLocked(SimulatorProcess::icache(), addressOfSwiInstruction(), SimInstruction::kInstrSize); } SimulatorProcess::setRedirection(this); } public: void* addressOfSwiInstruction() { return &swiInstruction_; } void* nativeFunction() const { return nativeFunction_; } ABIFunctionType type() const { return type_; } static Redirection* Get(void* nativeFunction, ABIFunctionType type) { AutoLockSimulatorCache als; Redirection* current = SimulatorProcess::redirection(); for (; current != nullptr; current = current->next_) { if (current->nativeFunction_ == nativeFunction) { MOZ_ASSERT(current->type() == type); return current; } } // Note: we can't use js_new here because the constructor is private. AutoEnterOOMUnsafeRegion oomUnsafe; Redirection* redir = js_pod_malloc(1); if (!redir) { oomUnsafe.crash("Simulator redirection"); } new (redir) Redirection(nativeFunction, type); return redir; } static Redirection* FromSwiInstruction(SimInstruction* swiInstruction) { uint8_t* addrOfSwi = reinterpret_cast(swiInstruction); uint8_t* addrOfRedirection = addrOfSwi - offsetof(Redirection, swiInstruction_); return reinterpret_cast(addrOfRedirection); } private: void* nativeFunction_; uint32_t swiInstruction_; ABIFunctionType type_; Redirection* next_; }; Simulator::~Simulator() { js_free(stack_); } SimulatorProcess::SimulatorProcess() : cacheLock_(mutexid::SimulatorCacheLock), redirection_(nullptr) { if (getenv("MIPS_SIM_ICACHE_CHECKS")) { ICacheCheckingDisableCount = 0; } } SimulatorProcess::~SimulatorProcess() { Redirection* r = redirection_; while (r) { Redirection* next = r->next_; js_delete(r); r = next; } } /* static */ void* Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type) { Redirection* redirection = Redirection::Get(nativeFunction, type); return redirection->addressOfSwiInstruction(); } // Get the active Simulator for the current thread. Simulator* Simulator::Current() { JSContext* cx = TlsContext.get(); MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); return cx->simulator(); } // Sets the register in the architecture state. It will also deal with updating // Simulator internal state for special registers such as PC. void Simulator::setRegister(int reg, int32_t value) { MOZ_ASSERT((reg >= 0) && (reg < Register::kNumSimuRegisters)); if (reg == pc) { pc_modified_ = true; } // Zero register always holds 0. registers_[reg] = (reg == 0) ? 0 : value; } void Simulator::setFpuRegister(int fpureg, int32_t value) { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); FPUregisters_[fpureg] = value; } void Simulator::setFpuRegisterFloat(int fpureg, float value) { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); *mozilla::BitwiseCast(&FPUregisters_[fpureg]) = value; } void Simulator::setFpuRegisterDouble(int fpureg, double value) { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters) && ((fpureg % 2) == 0)); *mozilla::BitwiseCast(&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::getRegister(int reg) const { MOZ_ASSERT((reg >= 0) && (reg < Register::kNumSimuRegisters)); if (reg == 0) { return 0; } return registers_[reg] + ((reg == pc) ? SimInstruction::kPCReadOffset : 0); } double Simulator::getDoubleFromRegisterPair(int reg) { MOZ_ASSERT((reg >= 0) && (reg < Register::kNumSimuRegisters) && ((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. memcpy(&dm_val, ®isters_[reg], sizeof(dm_val)); return (dm_val); } int32_t Simulator::getFpuRegister(int fpureg) const { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); return FPUregisters_[fpureg]; } int64_t Simulator::getFpuRegisterLong(int fpureg) const { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters) && ((fpureg % 2) == 0)); return *mozilla::BitwiseCast( const_cast(&FPUregisters_[fpureg])); } float Simulator::getFpuRegisterFloat(int fpureg) const { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); return *mozilla::BitwiseCast( const_cast(&FPUregisters_[fpureg])); } double Simulator::getFpuRegisterDouble(int fpureg) const { MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters) && ((fpureg % 2) == 0)); return *mozilla::BitwiseCast( const_cast(&FPUregisters_[fpureg])); } // Runtime FP routines take up to two double arguments and zero // or one integer arguments. All are constructed here, // from a0-a3 or f12 and f14. void Simulator::getFpArgs(double* x, double* y, int32_t* z) { *x = getFpuRegisterDouble(12); *y = getFpuRegisterDouble(14); *z = getRegister(a2); } void Simulator::getFpFromStack(int32_t* stack, double* x) { MOZ_ASSERT(stack); MOZ_ASSERT(x); memcpy(x, stack, sizeof(double)); } void Simulator::setCallResultDouble(double result) { setFpuRegisterDouble(f0, result); } void Simulator::setCallResultFloat(float result) { setFpuRegisterFloat(f0, result); } void Simulator::setCallResult(int64_t res) { setRegister(v0, static_cast(res)); setRegister(v1, static_cast(res >> 32)); } // Helper functions for setting and testing the FCSR register's bits. void Simulator::setFCSRBit(uint32_t cc, bool value) { if (value) { FCSR_ |= (1 << cc); } else { FCSR_ &= ~(1 << cc); } } bool Simulator::testFCSRBit(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::setFCSRRoundError(double original, double rounded) { bool ret = false; setFCSRBit(kFCSRInexactCauseBit, false); setFCSRBit(kFCSRUnderflowCauseBit, false); setFCSRBit(kFCSROverflowCauseBit, false); setFCSRBit(kFCSRInvalidOpCauseBit, false); if (!std::isfinite(original) || !std::isfinite(rounded)) { setFCSRBit(kFCSRInvalidOpFlagBit, true); setFCSRBit(kFCSRInvalidOpCauseBit, true); ret = true; } if (original != rounded) { setFCSRBit(kFCSRInexactFlagBit, true); setFCSRBit(kFCSRInexactCauseBit, true); } if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) { setFCSRBit(kFCSRUnderflowFlagBit, true); setFCSRBit(kFCSRUnderflowCauseBit, true); ret = true; } if (rounded > INT_MAX || rounded < INT_MIN) { setFCSRBit(kFCSROverflowFlagBit, true); setFCSRBit(kFCSROverflowCauseBit, true); // The reference is not really clear but it seems this is required: setFCSRBit(kFCSRInvalidOpFlagBit, true); setFCSRBit(kFCSRInvalidOpCauseBit, 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]; } JS::ProfilingFrameIterator::RegisterState Simulator::registerState() { wasm::RegisterState state; state.pc = (void*)get_pc(); state.fp = (void*)getRegister(fp); state.sp = (void*)getRegister(sp); state.lr = (void*)getRegister(ra); return state; } // MIPS memory instructions (except lwl/r and swl/r) trap on unaligned memory // access enabling the OS to handle them via trap-and-emulate. // 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 it will not trap on unaligned memory access. // We assume that that executing correct generated code will not produce // unaligned memory access, so we explicitly check for address alignment and // trap. Note that trapping does not occur when executing wasm code, which // requires that unaligned memory access provides correct result. int Simulator::readW(uint32_t addr, SimInstruction* instr) { if (handleWasmSegFault(addr, 4)) { return -1; } if ((addr & kPointerAlignmentMask) == 0 || wasm::InCompiledCode(reinterpret_cast(get_pc()))) { intptr_t* ptr = reinterpret_cast(addr); return *ptr; } printf("Unaligned read at 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MOZ_CRASH(); return 0; } void Simulator::writeW(uint32_t addr, int value, SimInstruction* instr) { if (handleWasmSegFault(addr, 4)) { return; } if ((addr & kPointerAlignmentMask) == 0 || wasm::InCompiledCode(reinterpret_cast(get_pc()))) { intptr_t* ptr = reinterpret_cast(addr); LLBit_ = false; *ptr = value; return; } printf("Unaligned write at 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MOZ_CRASH(); } double Simulator::readD(uint32_t addr, SimInstruction* instr) { if (handleWasmSegFault(addr, 8)) { return NAN; } if ((addr & kDoubleAlignmentMask) == 0 || wasm::InCompiledCode(reinterpret_cast(get_pc()))) { double* ptr = reinterpret_cast(addr); return *ptr; } printf("Unaligned (double) read at 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MOZ_CRASH(); return 0; } void Simulator::writeD(uint32_t addr, double value, SimInstruction* instr) { if (handleWasmSegFault(addr, 8)) { return; } if ((addr & kDoubleAlignmentMask) == 0 || wasm::InCompiledCode(reinterpret_cast(get_pc()))) { double* ptr = reinterpret_cast(addr); LLBit_ = false; *ptr = value; return; } printf("Unaligned (double) write at 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MOZ_CRASH(); } uint16_t Simulator::readHU(uint32_t addr, SimInstruction* instr) { if (handleWasmSegFault(addr, 2)) { return 0xffff; } if ((addr & 1) == 0 || wasm::InCompiledCode(reinterpret_cast(get_pc()))) { uint16_t* ptr = reinterpret_cast(addr); return *ptr; } printf("Unaligned unsigned halfword read at 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MOZ_CRASH(); return 0; } int16_t Simulator::readH(uint32_t addr, SimInstruction* instr) { if (handleWasmSegFault(addr, 2)) { return -1; } if ((addr & 1) == 0 || wasm::InCompiledCode(reinterpret_cast(get_pc()))) { int16_t* ptr = reinterpret_cast(addr); return *ptr; } printf("Unaligned signed halfword read at 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MOZ_CRASH(); return 0; } void Simulator::writeH(uint32_t addr, uint16_t value, SimInstruction* instr) { if (handleWasmSegFault(addr, 2)) { return; } if ((addr & 1) == 0 || wasm::InCompiledCode(reinterpret_cast(get_pc()))) { uint16_t* ptr = reinterpret_cast(addr); LLBit_ = false; *ptr = value; return; } printf("Unaligned unsigned halfword write at 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MOZ_CRASH(); } void Simulator::writeH(uint32_t addr, int16_t value, SimInstruction* instr) { if (handleWasmSegFault(addr, 2)) { return; } if ((addr & 1) == 0 || wasm::InCompiledCode(reinterpret_cast(get_pc()))) { int16_t* ptr = reinterpret_cast(addr); LLBit_ = false; *ptr = value; return; } printf("Unaligned halfword write at 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MOZ_CRASH(); } uint32_t Simulator::readBU(uint32_t addr) { if (handleWasmSegFault(addr, 1)) { return 0xff; } uint8_t* ptr = reinterpret_cast(addr); return *ptr; } int32_t Simulator::readB(uint32_t addr) { if (handleWasmSegFault(addr, 1)) { return -1; } int8_t* ptr = reinterpret_cast(addr); return *ptr; } void Simulator::writeB(uint32_t addr, uint8_t value) { if (handleWasmSegFault(addr, 1)) { return; } uint8_t* ptr = reinterpret_cast(addr); LLBit_ = false; *ptr = value; } void Simulator::writeB(uint32_t addr, int8_t value) { if (handleWasmSegFault(addr, 1)) { return; } int8_t* ptr = reinterpret_cast(addr); LLBit_ = false; *ptr = value; } int Simulator::loadLinkedW(uint32_t addr, SimInstruction* instr) { if ((addr & kPointerAlignmentMask) == 0) { if (handleWasmSegFault(addr, 1)) { return -1; } volatile int32_t* ptr = reinterpret_cast(addr); int32_t value = *ptr; lastLLValue_ = value; LLAddr_ = addr; // Note that any memory write or "external" interrupt should reset this // value to false. LLBit_ = true; return value; } printf("Unaligned read at 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MOZ_CRASH(); return 0; } int Simulator::storeConditionalW(uint32_t addr, int value, SimInstruction* instr) { // Correct behavior in this case, as defined by architecture, is to just // return 0, but there is no point at allowing that. It is certainly an // indicator of a bug. if (addr != LLAddr_) { printf("SC to bad address: 0x%08x, pc=0x%08" PRIxPTR ", expected: 0x%08x\n", addr, reinterpret_cast(instr), LLAddr_); MOZ_CRASH(); } if ((addr & kPointerAlignmentMask) == 0) { SharedMem ptr = SharedMem::shared(reinterpret_cast(addr)); if (!LLBit_) { return 0; } LLBit_ = false; LLAddr_ = 0; int32_t expected = lastLLValue_; int32_t old = AtomicOperations::compareExchangeSeqCst(ptr, expected, int32_t(value)); return (old == expected) ? 1 : 0; } printf("Unaligned SC at 0x%08x, pc=0x%08" PRIxPTR "\n", addr, reinterpret_cast(instr)); MOZ_CRASH(); return 0; } uintptr_t Simulator::stackLimit() const { return stackLimit_; } uintptr_t* Simulator::addressOfStackLimit() { return &stackLimit_; } bool Simulator::overRecursed(uintptr_t newsp) const { if (newsp == 0) { newsp = getRegister(sp); } return newsp <= stackLimit(); } bool Simulator::overRecursedWithExtra(uint32_t extra) const { uintptr_t newsp = getRegister(sp) - extra; return newsp <= stackLimit(); } // Unsupported instructions use format to print an error and stop execution. void Simulator::format(SimInstruction* instr, const char* format) { printf("Simulator found unsupported instruction:\n 0x%08" PRIxPTR ": %s\n", reinterpret_cast(instr), format); MOZ_CRASH(); } // Note: With the code below we assume that all runtime calls return a 64 bits // result. If they don't, the v1 result register contains a bogus value, which // is fine because it is caller-saved. typedef int64_t (*Prototype_General0)(); typedef int64_t (*Prototype_General1)(int32_t arg0); typedef int64_t (*Prototype_General2)(int32_t arg0, int32_t arg1); typedef int64_t (*Prototype_General3)(int32_t arg0, int32_t arg1, int32_t arg2); typedef int64_t (*Prototype_General4)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3); typedef int64_t (*Prototype_General5)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4); typedef int64_t (*Prototype_General6)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5); typedef int64_t (*Prototype_General7)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6); typedef int64_t (*Prototype_General8)(int32_t arg0, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4, int32_t arg5, int32_t arg6, int32_t arg7); typedef int64_t (*Prototype_GeneralGeneralGeneralInt64)(int32_t arg0, int32_t arg1, int32_t arg2, int64_t arg3); typedef int64_t (*Prototype_GeneralGeneralInt64Int64)(int32_t arg0, int32_t arg1, int64_t arg2, int64_t arg3); typedef double (*Prototype_Double_None)(); typedef double (*Prototype_Double_Double)(double arg0); typedef double (*Prototype_Double_Int)(int32_t arg0); typedef int32_t (*Prototype_Int_Double)(double arg0); typedef int64_t (*Prototype_Int64_Double)(double arg0); typedef int32_t (*Prototype_Int_DoubleIntInt)(double arg0, int32_t arg1, int32_t arg2); typedef int32_t (*Prototype_Int_IntDoubleIntInt)(int32_t arg0, double arg1, int32_t arg2, int32_t arg3); typedef float (*Prototype_Float32_Float32)(float arg0); typedef int32_t (*Prototype_Int_Float32)(float arg0); typedef float (*Prototype_Float32_Float32Float32)(float arg0, float arg1); typedef float (*Prototype_Float32_IntInt)(int arg0, int arg1); typedef double (*Prototype_Double_DoubleInt)(double arg0, int32_t arg1); typedef double (*Prototype_Double_IntInt)(int32_t arg0, int32_t arg1); typedef double (*Prototype_Double_IntDouble)(int32_t arg0, double arg1); typedef double (*Prototype_Double_DoubleDouble)(double arg0, double arg1); typedef int32_t (*Prototype_Int_IntDouble)(int32_t arg0, double arg1); typedef double (*Prototype_Double_DoubleDoubleDouble)(double arg0, double arg1, double arg2); typedef double (*Prototype_Double_DoubleDoubleDoubleDouble)(double arg0, double arg1, double arg2, double arg3); static int64_t MakeInt64(int32_t first, int32_t second) { // Little-endian order. return ((int64_t)second << 32) | (uint32_t)first; } // Software interrupt instructions are used by the simulator to call into C++. void Simulator::softwareInterrupt(SimInstruction* instr) { int32_t func = instr->functionFieldRaw(); uint32_t code = (func == ff_break) ? instr->bits(25, 6) : -1; // We first check if we met a call_rt_redirected. if (instr->instructionBits() == kCallRedirInstr) { #if !defined(USES_O32_ABI) MOZ_CRASH("Only O32 ABI supported."); #else Redirection* redirection = Redirection::FromSwiInstruction(instr); int32_t arg0 = getRegister(a0); int32_t arg1 = getRegister(a1); int32_t arg2 = getRegister(a2); int32_t arg3 = getRegister(a3); int32_t* stack_pointer = reinterpret_cast(getRegister(sp)); // Args 4 and 5 are on the stack after the reserved space for args 0..3. int32_t arg4 = stack_pointer[4]; int32_t arg5 = stack_pointer[5]; // 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 = getRegister(ra); intptr_t external = reinterpret_cast(redirection->nativeFunction()); bool stack_aligned = (getRegister(sp) & (ABIStackAlignment - 1)) == 0; if (!stack_aligned) { fprintf(stderr, "Runtime call with unaligned stack!\n"); MOZ_CRASH(); } if (single_stepping_) { single_step_callback_(single_step_callback_arg_, this, nullptr); } switch (redirection->type()) { case Args_General0: { Prototype_General0 target = reinterpret_cast(external); int64_t result = target(); setCallResult(result); break; } case Args_General1: { Prototype_General1 target = reinterpret_cast(external); int64_t result = target(arg0); setCallResult(result); break; } case Args_General2: { Prototype_General2 target = reinterpret_cast(external); int64_t result = target(arg0, arg1); setCallResult(result); break; } case Args_General3: { Prototype_General3 target = reinterpret_cast(external); int64_t result = target(arg0, arg1, arg2); setCallResult(result); break; } case Args_General4: { Prototype_General4 target = reinterpret_cast(external); int64_t result = target(arg0, arg1, arg2, arg3); setCallResult(result); break; } case Args_General5: { Prototype_General5 target = reinterpret_cast(external); int64_t result = target(arg0, arg1, arg2, arg3, arg4); setCallResult(result); break; } case Args_General6: { Prototype_General6 target = reinterpret_cast(external); int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5); setCallResult(result); break; } case Args_General7: { Prototype_General7 target = reinterpret_cast(external); int32_t arg6 = stack_pointer[6]; int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5, arg6); setCallResult(result); break; } case Args_General8: { Prototype_General8 target = reinterpret_cast(external); int32_t arg6 = stack_pointer[6]; int32_t arg7 = stack_pointer[7]; int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); setCallResult(result); break; } case Args_Double_None: { Prototype_Double_None target = reinterpret_cast(external); double dresult = target(); setCallResultDouble(dresult); break; } case Args_Int_Double: { double dval0, dval1; int32_t ival; getFpArgs(&dval0, &dval1, &ival); Prototype_Int_Double target = reinterpret_cast(external); int32_t res = target(dval0); setRegister(v0, res); break; } case Args_Int_GeneralGeneralGeneralInt64: { Prototype_GeneralGeneralGeneralInt64 target = reinterpret_cast(external); // The int64 arg is not split across register and stack int64_t result = target(arg0, arg1, arg2, MakeInt64(arg4, arg5)); setCallResult(result); break; } case Args_Int_GeneralGeneralInt64Int64: { Prototype_GeneralGeneralInt64Int64 target = reinterpret_cast(external); int64_t result = target(arg0, arg1, MakeInt64(arg2, arg3), MakeInt64(arg4, arg5)); setCallResult(result); break; } case Args_Int64_Double: { double dval0, dval1; int32_t ival; getFpArgs(&dval0, &dval1, &ival); Prototype_Int64_Double target = reinterpret_cast(external); int64_t result = target(dval0); setCallResult(result); break; } case Args_Int_DoubleIntInt: { double dval = getFpuRegisterDouble(12); Prototype_Int_DoubleIntInt target = reinterpret_cast(external); int32_t res = target(dval, arg2, arg3); setRegister(v0, res); break; } case Args_Int_IntDoubleIntInt: { double dval = getDoubleFromRegisterPair(a2); Prototype_Int_IntDoubleIntInt target = reinterpret_cast(external); int32_t res = target(arg0, dval, arg4, arg5); setRegister(v0, res); break; } case Args_Double_Double: { double dval0, dval1; int32_t ival; getFpArgs(&dval0, &dval1, &ival); Prototype_Double_Double target = reinterpret_cast(external); double dresult = target(dval0); setCallResultDouble(dresult); break; } case Args_Float32_Float32: { float fval0; fval0 = getFpuRegisterFloat(12); Prototype_Float32_Float32 target = reinterpret_cast(external); float fresult = target(fval0); setCallResultFloat(fresult); break; } case Args_Int_Float32: { float fval0; fval0 = getFpuRegisterFloat(12); Prototype_Int_Float32 target = reinterpret_cast(external); int32_t result = target(fval0); setRegister(v0, result); break; } case Args_Float32_Float32Float32: { float fval0; float fval1; fval0 = getFpuRegisterFloat(12); fval1 = getFpuRegisterFloat(14); Prototype_Float32_Float32Float32 target = reinterpret_cast(external); float fresult = target(fval0, fval1); setCallResultFloat(fresult); break; } case Args_Float32_IntInt: { Prototype_Float32_IntInt target = reinterpret_cast(external); float fresult = target(arg0, arg1); setCallResultFloat(fresult); break; } case Args_Double_Int: { Prototype_Double_Int target = reinterpret_cast(external); double dresult = target(arg0); setCallResultDouble(dresult); break; } case Args_Double_IntInt: { Prototype_Double_IntInt target = reinterpret_cast(external); double dresult = target(arg0, arg1); setCallResultDouble(dresult); break; } case Args_Double_DoubleInt: { double dval0, dval1; int32_t ival; getFpArgs(&dval0, &dval1, &ival); Prototype_Double_DoubleInt target = reinterpret_cast(external); double dresult = target(dval0, ival); setCallResultDouble(dresult); break; } case Args_Double_DoubleDouble: { double dval0, dval1; int32_t ival; getFpArgs(&dval0, &dval1, &ival); Prototype_Double_DoubleDouble target = reinterpret_cast(external); double dresult = target(dval0, dval1); setCallResultDouble(dresult); break; } case Args_Double_IntDouble: { int32_t ival = getRegister(a0); double dval0 = getDoubleFromRegisterPair(a2); Prototype_Double_IntDouble target = reinterpret_cast(external); double dresult = target(ival, dval0); setCallResultDouble(dresult); break; } case Args_Int_IntDouble: { int32_t ival = getRegister(a0); double dval0 = getDoubleFromRegisterPair(a2); Prototype_Int_IntDouble target = reinterpret_cast(external); int32_t result = target(ival, dval0); setRegister(v0, result); break; } case Args_Double_DoubleDoubleDouble: { double dval0, dval1, dval2; int32_t ival; getFpArgs(&dval0, &dval1, &ival); // the last argument is on stack getFpFromStack(stack_pointer + 4, &dval2); Prototype_Double_DoubleDoubleDouble target = reinterpret_cast(external); double dresult = target(dval0, dval1, dval2); setCallResultDouble(dresult); break; } case Args_Double_DoubleDoubleDoubleDouble: { double dval0, dval1, dval2, dval3; int32_t ival; getFpArgs(&dval0, &dval1, &ival); // the two last arguments are on stack getFpFromStack(stack_pointer + 4, &dval2); getFpFromStack(stack_pointer + 6, &dval3); Prototype_Double_DoubleDoubleDoubleDouble target = reinterpret_cast( external); double dresult = target(dval0, dval1, dval2, dval3); setCallResultDouble(dresult); break; } default: MOZ_CRASH("call"); } if (single_stepping_) { single_step_callback_(single_step_callback_arg_, this, nullptr); } setRegister(ra, saved_ra); set_pc(getRegister(ra)); #endif } else if (func == ff_break && code <= kMaxStopCode) { if (isWatchpoint(code)) { printWatchpoint(code); } else { increaseStopCounter(code); handleStop(code, instr); } } else { switch (func) { case ff_tge: case ff_tgeu: case ff_tlt: case ff_tltu: case ff_teq: case ff_tne: if (instr->bits(15, 6) == kWasmTrapCode) { uint8_t* newPC; if (wasm::HandleIllegalInstruction(registerState(), &newPC)) { set_pc(int32_t(newPC)); return; } } }; // 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, SimInstruction* 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 * SimInstruction::kInstrSize); } } bool Simulator::isStopInstruction(SimInstruction* instr) { int32_t func = instr->functionFieldRaw(); uint32_t code = static_cast(instr->bits(25, 6)); return (func == ff_break) && code > kMaxWatchpointCode && code <= kMaxStopCode; } bool Simulator::isEnabledStop(uint32_t code) { MOZ_ASSERT(code <= kMaxStopCode); MOZ_ASSERT(code > kMaxWatchpointCode); return !(watchedStops_[code].count_ & kStopDisabledBit); } void Simulator::enableStop(uint32_t code) { if (!isEnabledStop(code)) { watchedStops_[code].count_ &= ~kStopDisabledBit; } } void Simulator::disableStop(uint32_t code) { if (isEnabledStop(code)) { watchedStops_[code].count_ |= kStopDisabledBit; } } void Simulator::increaseStopCounter(uint32_t code) { MOZ_ASSERT(code <= kMaxStopCode); if ((watchedStops_[code].count_ & ~(1 << 31)) == 0x7fffffff) { printf( "Stop counter for code %i has overflowed.\n" "Enabling this code and reseting the counter to 0.\n", code); watchedStops_[code].count_ = 0; enableStop(code); } else { watchedStops_[code].count_++; } } // Print a stop status. void Simulator::printStopInfo(uint32_t code) { if (code <= kMaxWatchpointCode) { printf("That is a watchpoint, not a stop.\n"); return; } else if (code > kMaxStopCode) { printf("Code too large, only %u stops can be used\n", kMaxStopCode + 1); return; } const char* state = isEnabledStop(code) ? "Enabled" : "Disabled"; int32_t count = watchedStops_[code].count_ & ~kStopDisabledBit; // Don't print the state of unused breakpoints. if (count != 0) { if (watchedStops_[code].desc_) { printf("stop %i - 0x%x: \t%s, \tcounter = %i, \t%s\n", code, code, state, count, watchedStops_[code].desc_); } else { printf("stop %i - 0x%x: \t%s, \tcounter = %i\n", code, code, state, count); } } } void Simulator::signalExceptions() { for (int i = 1; i < kNumExceptions; i++) { if (exceptions[i] != 0) { MOZ_CRASH("Error: Exception raised."); } } } // Handle execution based on instruction types. void Simulator::configureTypeRegister(SimInstruction* instr, int32_t& alu_out, int64_t& i64hilo, uint64_t& u64hilo, int32_t& next_pc, int32_t& return_addr_reg, 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 OpcodeField op = instr->opcodeFieldRaw(); const int32_t rs_reg = instr->rsValue(); const int32_t rs = getRegister(rs_reg); const uint32_t rs_u = static_cast(rs); const int32_t rt_reg = instr->rtValue(); const int32_t rt = getRegister(rt_reg); const uint32_t rt_u = static_cast(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 op_cop1: // Coprocessor instructions. switch (instr->rsFieldRaw()) { case rs_bc1: // Handled in DecodeTypeImmed, should never come here. MOZ_CRASH(); break; case rs_cfc1: // At the moment only FCSR is supported. MOZ_ASSERT(fs_reg == kFCSRRegister); alu_out = FCSR_; break; case rs_mfc1: alu_out = getFpuRegister(fs_reg); break; case rs_mfhc1: MOZ_CRASH(); break; case rs_ctc1: case rs_mtc1: case rs_mthc1: // Do the store in the execution step. break; case rs_s: case rs_d: case rs_w: case rs_l: case rs_ps: // Do everything in the execution step. break; default: MOZ_CRASH(); } break; case op_cop1x: break; case op_special: switch (instr->functionFieldRaw()) { case ff_jr: case ff_jalr: next_pc = getRegister(instr->rsValue()); return_addr_reg = instr->rdValue(); break; case ff_sll: alu_out = rt << sa; break; case ff_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 ff_sra: alu_out = rt >> sa; break; case ff_sllv: alu_out = rt << rs; break; case ff_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 ff_srav: alu_out = rt >> rs; break; case ff_mfhi: alu_out = getRegister(HI); break; case ff_mflo: alu_out = getRegister(LO); break; case ff_mult: i64hilo = static_cast(rs) * static_cast(rt); break; case ff_multu: u64hilo = static_cast(rs_u) * static_cast(rt_u); break; case ff_add: if (HaveSameSign(rs, rt)) { if (rs > 0) { exceptions[kIntegerOverflow] = rs > (kRegisterskMaxValue - rt); } else if (rs < 0) { exceptions[kIntegerUnderflow] = rs < (kRegisterskMinValue - rt); } } alu_out = rs + rt; break; case ff_addu: alu_out = rs + rt; break; case ff_sub: if (!HaveSameSign(rs, rt)) { if (rs > 0) { exceptions[kIntegerOverflow] = rs > (kRegisterskMaxValue + rt); } else if (rs < 0) { exceptions[kIntegerUnderflow] = rs < (kRegisterskMinValue + rt); } } alu_out = rs - rt; break; case ff_subu: alu_out = rs - rt; break; case ff_and: alu_out = rs & rt; break; case ff_or: alu_out = rs | rt; break; case ff_xor: alu_out = rs ^ rt; break; case ff_nor: alu_out = ~(rs | rt); break; case ff_slt: alu_out = rs < rt ? 1 : 0; break; case ff_sltu: alu_out = rs_u < rt_u ? 1 : 0; break; // Break and trap instructions. case ff_break: do_interrupt = true; break; case ff_tge: do_interrupt = rs >= rt; break; case ff_tgeu: do_interrupt = rs_u >= rt_u; break; case ff_tlt: do_interrupt = rs < rt; break; case ff_tltu: do_interrupt = rs_u < rt_u; break; case ff_teq: do_interrupt = rs == rt; break; case ff_tne: do_interrupt = rs != rt; break; case ff_movn: case ff_movz: case ff_movci: case ff_sync: // No action taken on decode. break; case ff_div: case ff_divu: // div and divu never raise exceptions. break; default: MOZ_CRASH(); } break; case op_special2: switch (instr->functionFieldRaw()) { case ff_mul: alu_out = rs_u * rt_u; // Only the lower 32 bits are kept. break; case ff_mult: i64hilo = static_cast(rs) * static_cast(rt); break; case ff_multu: u64hilo = static_cast(rs_u) * static_cast(rt_u); break; case ff_madd: i64hilo += static_cast(rs) * static_cast(rt); break; case ff_maddu: u64hilo += static_cast(rs_u) * static_cast(rt_u); break; case ff_clz: alu_out = rs_u ? __builtin_clz(rs_u) : 32; break; default: MOZ_CRASH(); } break; case op_special3: switch (instr->functionFieldRaw()) { case ff_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 ff_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; } case ff_bshfl: { // Mips32r2 instruction. if (16 == sa) { // seb alu_out = I32(I8(rt)); } else if (24 == sa) { // seh alu_out = I32(I16(rt)); } else { MOZ_CRASH(); } break; } default: MOZ_CRASH(); } break; default: MOZ_CRASH(); } } void Simulator::decodeTypeRegister(SimInstruction* instr) { // Instruction fields. const OpcodeField op = instr->opcodeFieldRaw(); const int32_t rs_reg = instr->rsValue(); const int32_t rs = getRegister(rs_reg); const uint32_t rs_u = static_cast(rs); const int32_t rt_reg = instr->rtValue(); const int32_t rt = getRegister(rt_reg); const uint32_t rt_u = static_cast(rt); const int32_t rd_reg = instr->rdValue(); const int32_t fr_reg = instr->frValue(); 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; int32_t return_addr_reg = 31; // Set up the variables if needed before executing the instruction. configureTypeRegister(instr, alu_out, i64hilo, u64hilo, next_pc, return_addr_reg, do_interrupt); // ---------- Raise exceptions triggered. signalExceptions(); // ---------- Execution. switch (op) { case op_cop1: switch (instr->rsFieldRaw()) { case rs_bc1: // Branch on coprocessor condition. MOZ_CRASH(); break; case rs_cfc1: case rs_mfc1: setRegister(rt_reg, alu_out); break; case rs_mfhc1: MOZ_CRASH(); break; case rs_ctc1: // At the moment only FCSR is supported. MOZ_ASSERT(fs_reg == kFCSRRegister); FCSR_ = registers_[rt_reg]; break; case rs_mtc1: FPUregisters_[fs_reg] = registers_[rt_reg]; break; case rs_mthc1: MOZ_CRASH(); break; case rs_s: float f, ft_value, fs_value; uint32_t cc, fcsr_cc; fs_value = getFpuRegisterFloat(fs_reg); ft_value = getFpuRegisterFloat(ft_reg); cc = instr->fcccValue(); fcsr_cc = GetFCSRConditionBit(cc); switch (instr->functionFieldRaw()) { case ff_add_fmt: setFpuRegisterFloat(fd_reg, fs_value + ft_value); break; case ff_sub_fmt: setFpuRegisterFloat(fd_reg, fs_value - ft_value); break; case ff_mul_fmt: setFpuRegisterFloat(fd_reg, fs_value * ft_value); break; case ff_div_fmt: setFpuRegisterFloat(fd_reg, fs_value / ft_value); break; case ff_abs_fmt: setFpuRegisterFloat(fd_reg, fabsf(fs_value)); break; case ff_mov_fmt: setFpuRegisterFloat(fd_reg, fs_value); break; case ff_neg_fmt: setFpuRegisterFloat(fd_reg, -fs_value); break; case ff_sqrt_fmt: setFpuRegisterFloat(fd_reg, sqrtf(fs_value)); break; case ff_c_un_fmt: setFCSRBit(fcsr_cc, mozilla::IsNaN(fs_value) || mozilla::IsNaN(ft_value)); break; case ff_c_eq_fmt: setFCSRBit(fcsr_cc, (fs_value == ft_value)); break; case ff_c_ueq_fmt: setFCSRBit(fcsr_cc, (fs_value == ft_value) || (mozilla::IsNaN(fs_value) || mozilla::IsNaN(ft_value))); break; case ff_c_olt_fmt: setFCSRBit(fcsr_cc, (fs_value < ft_value)); break; case ff_c_ult_fmt: setFCSRBit(fcsr_cc, (fs_value < ft_value) || (mozilla::IsNaN(fs_value) || mozilla::IsNaN(ft_value))); break; case ff_c_ole_fmt: setFCSRBit(fcsr_cc, (fs_value <= ft_value)); break; case ff_c_ule_fmt: setFCSRBit(fcsr_cc, (fs_value <= ft_value) || (mozilla::IsNaN(fs_value) || mozilla::IsNaN(ft_value))); break; case ff_cvt_d_fmt: f = getFpuRegisterFloat(fs_reg); setFpuRegisterDouble(fd_reg, static_cast(f)); break; case ff_cvt_w_fmt: // Convert float to word. // Rounding modes are not yet supported. MOZ_ASSERT((FCSR_ & 3) == 0); // In rounding mode 0 it should behave like ROUND. [[fallthrough]]; case ff_round_w_fmt: { // Round double to word (round half to // even). float rounded = std::floor(fs_value + 0.5); int32_t result = static_cast(rounded); if ((result & 1) != 0 && result - fs_value == 0.5) { // If the number is halfway between two integers, // round to the even one. result--; } setFpuRegister(fd_reg, result); if (setFCSRRoundError(fs_value, rounded)) { setFpuRegister(fd_reg, kFPUInvalidResult); } break; } case ff_trunc_w_fmt: { // Truncate float to word (round towards 0). float rounded = truncf(fs_value); int32_t result = static_cast(rounded); setFpuRegister(fd_reg, result); if (setFCSRRoundError(fs_value, rounded)) { setFpuRegister(fd_reg, kFPUInvalidResult); } break; } case ff_floor_w_fmt: { // Round float to word towards negative // infinity. float rounded = std::floor(fs_value); int32_t result = static_cast(rounded); setFpuRegister(fd_reg, result); if (setFCSRRoundError(fs_value, rounded)) { setFpuRegister(fd_reg, kFPUInvalidResult); } break; } case ff_ceil_w_fmt: { // Round double to word towards positive // infinity. float rounded = std::ceil(fs_value); int32_t result = static_cast(rounded); setFpuRegister(fd_reg, result); if (setFCSRRoundError(fs_value, rounded)) { setFpuRegister(fd_reg, kFPUInvalidResult); } break; } case ff_cvt_l_fmt: case ff_round_l_fmt: case ff_trunc_l_fmt: case ff_floor_l_fmt: case ff_ceil_l_fmt: case ff_cvt_ps_s: case ff_c_f_fmt: MOZ_CRASH(); break; case ff_movf_fmt: // location of cc field in MOVF is equal to float branch // instructions cc = instr->fbccValue(); fcsr_cc = GetFCSRConditionBit(cc); if (testFCSRBit(fcsr_cc)) { setFpuRegisterFloat(fd_reg, getFpuRegisterFloat(fs_reg)); } break; case ff_movz_fmt: if (rt == 0) { setFpuRegisterFloat(fd_reg, getFpuRegisterFloat(fs_reg)); } break; case ff_movn_fmt: if (rt != 0) { setFpuRegisterFloat(fd_reg, getFpuRegisterFloat(fs_reg)); } break; default: MOZ_CRASH(); } break; case rs_d: double dt_value, ds_value; ds_value = getFpuRegisterDouble(fs_reg); cc = instr->fcccValue(); fcsr_cc = GetFCSRConditionBit(cc); switch (instr->functionFieldRaw()) { case ff_add_fmt: dt_value = getFpuRegisterDouble(ft_reg); setFpuRegisterDouble(fd_reg, ds_value + dt_value); break; case ff_sub_fmt: dt_value = getFpuRegisterDouble(ft_reg); setFpuRegisterDouble(fd_reg, ds_value - dt_value); break; case ff_mul_fmt: dt_value = getFpuRegisterDouble(ft_reg); setFpuRegisterDouble(fd_reg, ds_value * dt_value); break; case ff_div_fmt: dt_value = getFpuRegisterDouble(ft_reg); setFpuRegisterDouble(fd_reg, ds_value / dt_value); break; case ff_abs_fmt: setFpuRegisterDouble(fd_reg, fabs(ds_value)); break; case ff_mov_fmt: setFpuRegisterDouble(fd_reg, ds_value); break; case ff_neg_fmt: setFpuRegisterDouble(fd_reg, -ds_value); break; case ff_sqrt_fmt: setFpuRegisterDouble(fd_reg, sqrt(ds_value)); break; case ff_c_un_fmt: dt_value = getFpuRegisterDouble(ft_reg); setFCSRBit(fcsr_cc, mozilla::IsNaN(ds_value) || mozilla::IsNaN(dt_value)); break; case ff_c_eq_fmt: dt_value = getFpuRegisterDouble(ft_reg); setFCSRBit(fcsr_cc, (ds_value == dt_value)); break; case ff_c_ueq_fmt: dt_value = getFpuRegisterDouble(ft_reg); setFCSRBit(fcsr_cc, (ds_value == dt_value) || (mozilla::IsNaN(ds_value) || mozilla::IsNaN(dt_value))); break; case ff_c_olt_fmt: dt_value = getFpuRegisterDouble(ft_reg); setFCSRBit(fcsr_cc, (ds_value < dt_value)); break; case ff_c_ult_fmt: dt_value = getFpuRegisterDouble(ft_reg); setFCSRBit(fcsr_cc, (ds_value < dt_value) || (mozilla::IsNaN(ds_value) || mozilla::IsNaN(dt_value))); break; case ff_c_ole_fmt: dt_value = getFpuRegisterDouble(ft_reg); setFCSRBit(fcsr_cc, (ds_value <= dt_value)); break; case ff_c_ule_fmt: dt_value = getFpuRegisterDouble(ft_reg); setFCSRBit(fcsr_cc, (ds_value <= dt_value) || (mozilla::IsNaN(ds_value) || mozilla::IsNaN(dt_value))); break; case ff_cvt_w_fmt: // Convert double to word. // Rounding modes are not yet supported. MOZ_ASSERT((FCSR_ & 3) == 0); // In rounding mode 0 it should behave like ROUND. [[fallthrough]]; case ff_round_w_fmt: { // Round double to word (round half to // even). double rounded = std::floor(ds_value + 0.5); int32_t result = static_cast(rounded); if ((result & 1) != 0 && result - ds_value == 0.5) { // If the number is halfway between two integers, // round to the even one. result--; } setFpuRegister(fd_reg, result); if (setFCSRRoundError(ds_value, rounded)) { setFpuRegister(fd_reg, kFPUInvalidResult); } break; } case ff_trunc_w_fmt: { // Truncate double to word (round towards // 0). double rounded = trunc(ds_value); int32_t result = static_cast(rounded); setFpuRegister(fd_reg, result); if (setFCSRRoundError(ds_value, rounded)) { setFpuRegister(fd_reg, kFPUInvalidResult); } break; } case ff_floor_w_fmt: { // Round double to word towards negative // infinity. double rounded = std::floor(ds_value); int32_t result = static_cast(rounded); setFpuRegister(fd_reg, result); if (setFCSRRoundError(ds_value, rounded)) { setFpuRegister(fd_reg, kFPUInvalidResult); } break; } case ff_ceil_w_fmt: { // Round double to word towards positive // infinity. double rounded = std::ceil(ds_value); int32_t result = static_cast(rounded); setFpuRegister(fd_reg, result); if (setFCSRRoundError(ds_value, rounded)) { setFpuRegister(fd_reg, kFPUInvalidResult); } break; } case ff_cvt_s_fmt: // Convert double to float (single). setFpuRegisterFloat(fd_reg, static_cast(ds_value)); break; case ff_cvt_l_fmt: case ff_trunc_l_fmt: case ff_round_l_fmt: case ff_floor_l_fmt: case ff_ceil_l_fmt: case ff_c_f_fmt: MOZ_CRASH(); break; case ff_movf_fmt: // location of cc field in MOVF is equal to float branch // instructions cc = instr->fbccValue(); fcsr_cc = GetFCSRConditionBit(cc); if (testFCSRBit(fcsr_cc)) { setFpuRegisterDouble(fd_reg, getFpuRegisterDouble(fs_reg)); } break; case ff_movz_fmt: if (rt == 0) { setFpuRegisterDouble(fd_reg, getFpuRegisterDouble(fs_reg)); } break; case ff_movn_fmt: if (rt != 0) { setFpuRegisterDouble(fd_reg, getFpuRegisterDouble(fs_reg)); } break; default: MOZ_CRASH(); } break; case rs_w: switch (instr->functionFieldRaw()) { case ff_cvt_s_fmt: // Convert word to float (single). alu_out = getFpuRegister(fs_reg); setFpuRegisterFloat(fd_reg, static_cast(alu_out)); break; case ff_cvt_d_fmt: // Convert word to double. alu_out = getFpuRegister(fs_reg); setFpuRegisterDouble(fd_reg, static_cast(alu_out)); break; default: MOZ_CRASH(); } break; case rs_l: switch (instr->functionFieldRaw()) { case ff_cvt_d_fmt: case ff_cvt_s_fmt: MOZ_CRASH(); break; default: MOZ_CRASH(); } break; case rs_ps: break; default: MOZ_CRASH(); } break; case op_cop1x: switch (instr->functionFieldRaw()) { case ff_madd_s: float fr, ft, fs; fr = getFpuRegisterFloat(fr_reg); fs = getFpuRegisterFloat(fs_reg); ft = getFpuRegisterFloat(ft_reg); setFpuRegisterFloat(fd_reg, fs * ft + fr); break; case ff_madd_d: double dr, dt, ds; dr = getFpuRegisterDouble(fr_reg); ds = getFpuRegisterDouble(fs_reg); dt = getFpuRegisterDouble(ft_reg); setFpuRegisterDouble(fd_reg, ds * dt + dr); break; default: MOZ_CRASH(); } break; case op_special: switch (instr->functionFieldRaw()) { case ff_jr: { SimInstruction* branch_delay_instr = reinterpret_cast(current_pc + SimInstruction::kInstrSize); branchDelayInstructionDecode(branch_delay_instr); set_pc(next_pc); pc_modified_ = true; break; } case ff_jalr: { SimInstruction* branch_delay_instr = reinterpret_cast(current_pc + SimInstruction::kInstrSize); setRegister(return_addr_reg, current_pc + 2 * SimInstruction::kInstrSize); branchDelayInstructionDecode(branch_delay_instr); set_pc(next_pc); pc_modified_ = true; break; } // Instructions using HI and LO registers. case ff_mult: setRegister(LO, static_cast(i64hilo & 0xffffffff)); setRegister(HI, static_cast(i64hilo >> 32)); break; case ff_multu: setRegister(LO, static_cast(u64hilo & 0xffffffff)); setRegister(HI, static_cast(u64hilo >> 32)); break; case ff_div: // Divide by zero and overflow was not checked in the configuration // step - div and divu do not raise exceptions. On division by 0 // the result will be UNPREDICTABLE. On overflow (INT_MIN/-1), // return INT_MIN which is what the hardware does. if (rs == INT_MIN && rt == -1) { setRegister(LO, INT_MIN); setRegister(HI, 0); } else if (rt != 0) { setRegister(LO, rs / rt); setRegister(HI, rs % rt); } break; case ff_divu: if (rt_u != 0) { setRegister(LO, rs_u / rt_u); setRegister(HI, rs_u % rt_u); } break; // Break and trap instructions. case ff_break: case ff_tge: case ff_tgeu: case ff_tlt: case ff_tltu: case ff_teq: case ff_tne: if (do_interrupt) { softwareInterrupt(instr); } break; case ff_sync: switch (instr->bits(10, 6)) { case 0x0: case 0x4: case 0x10: case 0x11: case 0x12: case 0x13: AtomicOperations::fenceSeqCst(); break; default: MOZ_CRASH(); } break; // Conditional moves. case ff_movn: if (rt) setRegister(rd_reg, rs); break; case ff_movci: { uint32_t cc = instr->fbccValue(); uint32_t fcsr_cc = GetFCSRConditionBit(cc); if (instr->bit(16)) { // Read Tf bit. if (testFCSRBit(fcsr_cc)) setRegister(rd_reg, rs); } else { if (!testFCSRBit(fcsr_cc)) setRegister(rd_reg, rs); } break; } case ff_movz: if (!rt) setRegister(rd_reg, rs); break; default: // For other special opcodes we do the default operation. setRegister(rd_reg, alu_out); } break; case op_special2: switch (instr->functionFieldRaw()) { case ff_mul: setRegister(rd_reg, alu_out); // HI and LO are UNPREDICTABLE after the operation. setRegister(LO, Unpredictable); setRegister(HI, Unpredictable); break; case ff_madd: setRegister( LO, getRegister(LO) + static_cast(i64hilo & 0xffffffff)); setRegister(HI, getRegister(HI) + static_cast(i64hilo >> 32)); break; case ff_maddu: setRegister( LO, getRegister(LO) + static_cast(u64hilo & 0xffffffff)); setRegister(HI, getRegister(HI) + static_cast(u64hilo >> 32)); break; default: // For other special2 opcodes we do the default operation. setRegister(rd_reg, alu_out); } break; case op_special3: switch (instr->functionFieldRaw()) { case ff_ins: // Ins instr leaves result in Rt, rather than Rd. setRegister(rt_reg, alu_out); break; case ff_ext: // Ext instr leaves result in Rt, rather than Rd. setRegister(rt_reg, alu_out); break; case ff_bshfl: setRegister(rd_reg, alu_out); break; default: MOZ_CRASH(); } 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: setRegister(rd_reg, alu_out); } } // Type 2: instructions using a 16 bytes immediate. (e.g. addi, beq). void Simulator::decodeTypeImmediate(SimInstruction* instr) { // Instruction fields. OpcodeField op = instr->opcodeFieldRaw(); int32_t rs = getRegister(instr->rsValue()); uint32_t rs_u = static_cast(rs); int32_t rt_reg = instr->rtValue(); // Destination register. int32_t rt = getRegister(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. uint32_t addr = 0x0; // Value to be written in memory. uint32_t mem_value = 0x0; // ---------- Configuration (and execution for op_regimm). switch (op) { // ------------- op_cop1. Coprocessor instructions. case op_cop1: switch (instr->rsFieldRaw()) { case rs_bc1: // Branch on coprocessor condition. cc = instr->fbccValue(); fcsr_cc = GetFCSRConditionBit(cc); cc_value = testFCSRBit(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) + SimInstruction::kInstrSize; } else { next_pc = current_pc + kBranchReturnOffset; } break; default: MOZ_CRASH(); } break; // ------------- op_regimm class. case op_regimm: switch (instr->rtFieldRaw()) { case rt_bltz: do_branch = (rs < 0); break; case rt_bltzal: do_branch = rs < 0; break; case rt_bgez: do_branch = rs >= 0; break; case rt_bgezal: do_branch = rs >= 0; break; default: MOZ_CRASH(); } switch (instr->rtFieldRaw()) { case rt_bltz: case rt_bltzal: case rt_bgez: case rt_bgezal: // Branch instructions common part. execute_branch_delay_instruction = true; // Set next_pc. if (do_branch) { next_pc = current_pc + (imm16 << 2) + SimInstruction::kInstrSize; if (instr->isLinkingInstruction()) { setRegister(31, current_pc + kBranchReturnOffset); } } else { next_pc = current_pc + kBranchReturnOffset; } break; default: break; } break; // case op_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 op_beq: do_branch = (rs == rt); break; case op_bne: do_branch = rs != rt; break; case op_blez: do_branch = rs <= 0; break; case op_bgtz: do_branch = rs > 0; break; // ------------- Arithmetic instructions. case op_addi: if (HaveSameSign(rs, se_imm16)) { if (rs > 0) { exceptions[kIntegerOverflow] = rs > (kRegisterskMaxValue - se_imm16); } else if (rs < 0) { exceptions[kIntegerUnderflow] = rs < (kRegisterskMinValue - se_imm16); } } alu_out = rs + se_imm16; break; case op_addiu: alu_out = rs + se_imm16; break; case op_slti: alu_out = (rs < se_imm16) ? 1 : 0; break; case op_sltiu: alu_out = (rs_u < static_cast(se_imm16)) ? 1 : 0; break; case op_andi: alu_out = rs & oe_imm16; break; case op_ori: alu_out = rs | oe_imm16; break; case op_xori: alu_out = rs ^ oe_imm16; break; case op_lui: alu_out = (oe_imm16 << 16); break; // ------------- Memory instructions. case op_lb: addr = rs + se_imm16; alu_out = readB(addr); break; case op_lh: addr = rs + se_imm16; alu_out = readH(addr, instr); break; case op_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 op_lw: addr = rs + se_imm16; alu_out = readW(addr, instr); break; case op_lbu: addr = rs + se_imm16; alu_out = readBU(addr); break; case op_lhu: addr = rs + se_imm16; alu_out = readHU(addr, instr); break; case op_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(alu_out) >> al_offset * 8; alu_out |= rt & mask; break; } case op_sb: addr = rs + se_imm16; break; case op_sh: addr = rs + se_imm16; break; case op_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(rt) >> byte_shift * 8; break; } case op_sw: addr = rs + se_imm16; break; case op_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 op_lwc1: addr = rs + se_imm16; alu_out = readW(addr, instr); break; case op_ldc1: addr = rs + se_imm16; fp_out = readD(addr, instr); break; case op_swc1: case op_sdc1: addr = rs + se_imm16; break; case op_ll: addr = rs + se_imm16; alu_out = loadLinkedW(addr, instr); break; case op_sc: addr = rs + se_imm16; alu_out = storeConditionalW(addr, rt, instr); break; default: MOZ_CRASH(); } // ---------- Raise exceptions triggered. signalExceptions(); // ---------- Execution. switch (op) { // ------------- Branch instructions. case op_beq: case op_bne: case op_blez: case op_bgtz: // Branch instructions common part. execute_branch_delay_instruction = true; // Set next_pc. if (do_branch) { next_pc = current_pc + (imm16 << 2) + SimInstruction::kInstrSize; if (instr->isLinkingInstruction()) { setRegister(31, current_pc + 2 * SimInstruction::kInstrSize); } } else { next_pc = current_pc + 2 * SimInstruction::kInstrSize; } break; // ------------- Arithmetic instructions. case op_addi: case op_addiu: case op_slti: case op_sltiu: case op_andi: case op_ori: case op_xori: case op_lui: setRegister(rt_reg, alu_out); break; // ------------- Memory instructions. case op_lb: case op_lh: case op_lwl: case op_lw: case op_lbu: case op_lhu: case op_lwr: case op_ll: case op_sc: setRegister(rt_reg, alu_out); break; case op_sb: writeB(addr, static_cast(rt)); break; case op_sh: writeH(addr, static_cast(rt), instr); break; case op_swl: writeW(addr, mem_value, instr); break; case op_sw: writeW(addr, rt, instr); break; case op_swr: writeW(addr, mem_value, instr); break; case op_lwc1: setFpuRegister(ft_reg, alu_out); break; case op_ldc1: setFpuRegisterDouble(ft_reg, fp_out); break; case op_swc1: addr = rs + se_imm16; writeW(addr, getFpuRegister(ft_reg), instr); break; case op_sdc1: addr = rs + se_imm16; writeD(addr, getFpuRegisterDouble(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. SimInstruction* branch_delay_instr = reinterpret_cast( current_pc + SimInstruction::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. (e.g. j, jal). void Simulator::decodeTypeJump(SimInstruction* 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. SimInstruction* branch_delay_instr = reinterpret_cast( current_pc + SimInstruction::kInstrSize); branchDelayInstructionDecode(branch_delay_instr); // Update pc and ra if necessary. // Do this after the branch delay execution. if (instr->isLinkingInstruction()) { setRegister(31, current_pc + 2 * SimInstruction::kInstrSize); } set_pc(next_pc); pc_modified_ = true; } // Executes the current instruction. void Simulator::instructionDecode(SimInstruction* instr) { if (!SimulatorProcess::ICacheCheckingDisableCount) { AutoLockSimulatorCache als; SimulatorProcess::checkICacheLocked(instr); } pc_modified_ = false; switch (instr->instructionType()) { case SimInstruction::kRegisterType: decodeTypeRegister(instr); break; case SimInstruction::kImmediateType: decodeTypeImmediate(instr); break; case SimInstruction::kJumpType: decodeTypeJump(instr); break; default: UNSUPPORTED(); } if (!pc_modified_) { setRegister(pc, reinterpret_cast(instr) + SimInstruction::kInstrSize); } } void Simulator::branchDelayInstructionDecode(SimInstruction* instr) { if (instr->instructionBits() == NopInst) { // Short-cut generic nop instructions. They are always valid and they // never change the simulator state. return; } if (instr->isForbiddenInBranchDelay()) { MOZ_CRASH("Eror:Unexpected opcode in a branch delay slot."); } instructionDecode(instr); } void Simulator::enable_single_stepping(SingleStepCallback cb, void* arg) { single_stepping_ = true; single_step_callback_ = cb; single_step_callback_arg_ = arg; single_step_callback_(single_step_callback_arg_, this, (void*)get_pc()); } void Simulator::disable_single_stepping() { if (!single_stepping_) { return; } single_step_callback_(single_step_callback_arg_, this, (void*)get_pc()); single_stepping_ = false; single_step_callback_ = nullptr; single_step_callback_arg_ = nullptr; } template void Simulator::execute() { if (single_stepping_) { single_step_callback_(single_step_callback_arg_, this, nullptr); } // Get the PC to simulate. Cannot use the accessor here as we need the // raw PC value and not the one used as input to arithmetic instructions. int program_counter = get_pc(); while (program_counter != end_sim_pc) { if (enableStopSimAt && (icount_ == Simulator::StopSimAt)) { MipsDebugger dbg(this); dbg.debug(); } else { if (single_stepping_) { single_step_callback_(single_step_callback_arg_, this, (void*)program_counter); } SimInstruction* instr = reinterpret_cast(program_counter); instructionDecode(instr); icount_++; } program_counter = get_pc(); } if (single_stepping_) { single_step_callback_(single_step_callback_arg_, this, nullptr); } } void Simulator::callInternal(uint8_t* entry) { // Prepare to execute the code at entry. setRegister(pc, reinterpret_cast(entry)); // Put down marker for end of simulation. The simulator will stop simulation // when the PC reaches this value. By saving the "end simulation" value into // the LR the simulation stops when returning to this call point. setRegister(ra, end_sim_pc); // Remember the values of callee-saved registers. // 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 = getRegister(s0); int32_t s1_val = getRegister(s1); int32_t s2_val = getRegister(s2); int32_t s3_val = getRegister(s3); int32_t s4_val = getRegister(s4); int32_t s5_val = getRegister(s5); int32_t s6_val = getRegister(s6); int32_t s7_val = getRegister(s7); int32_t gp_val = getRegister(gp); int32_t sp_val = getRegister(sp); int32_t fp_val = getRegister(fp); // Set up 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_; setRegister(s0, callee_saved_value); setRegister(s1, callee_saved_value); setRegister(s2, callee_saved_value); setRegister(s3, callee_saved_value); setRegister(s4, callee_saved_value); setRegister(s5, callee_saved_value); setRegister(s6, callee_saved_value); setRegister(s7, callee_saved_value); setRegister(gp, callee_saved_value); setRegister(fp, callee_saved_value); // Start the simulation. if (Simulator::StopSimAt != -1) { execute(); } else { execute(); } // Check that the callee-saved registers have been preserved. MOZ_ASSERT(callee_saved_value == getRegister(s0)); MOZ_ASSERT(callee_saved_value == getRegister(s1)); MOZ_ASSERT(callee_saved_value == getRegister(s2)); MOZ_ASSERT(callee_saved_value == getRegister(s3)); MOZ_ASSERT(callee_saved_value == getRegister(s4)); MOZ_ASSERT(callee_saved_value == getRegister(s5)); MOZ_ASSERT(callee_saved_value == getRegister(s6)); MOZ_ASSERT(callee_saved_value == getRegister(s7)); MOZ_ASSERT(callee_saved_value == getRegister(gp)); MOZ_ASSERT(callee_saved_value == getRegister(fp)); // Restore callee-saved registers with the original value. setRegister(s0, s0_val); setRegister(s1, s1_val); setRegister(s2, s2_val); setRegister(s3, s3_val); setRegister(s4, s4_val); setRegister(s5, s5_val); setRegister(s6, s6_val); setRegister(s7, s7_val); setRegister(gp, gp_val); setRegister(sp, sp_val); setRegister(fp, fp_val); } int32_t Simulator::call(uint8_t* entry, int argument_count, ...) { va_list parameters; va_start(parameters, argument_count); int original_stack = getRegister(sp); // Compute position of stack on entry to generated code. int entry_stack = original_stack; if (argument_count > kCArgSlotCount) { entry_stack = entry_stack - argument_count * sizeof(int32_t); } else { entry_stack = entry_stack - kCArgsSlotsSize; } entry_stack &= ~(ABIStackAlignment - 1); intptr_t* stack_argument = reinterpret_cast(entry_stack); // Setup the arguments. for (int i = 0; i < argument_count; i++) { js::jit::Register argReg; if (GetIntArgReg(i, &argReg)) { setRegister(argReg.code(), va_arg(parameters, int32_t)); } else { stack_argument[i] = va_arg(parameters, int32_t); } } va_end(parameters); setRegister(sp, entry_stack); callInternal(entry); // Pop stack passed arguments. MOZ_ASSERT(entry_stack == getRegister(sp)); setRegister(sp, original_stack); int32_t result = getRegister(v0); return result; } uintptr_t Simulator::pushAddress(uintptr_t address) { int new_sp = getRegister(sp) - sizeof(uintptr_t); uintptr_t* stack_slot = reinterpret_cast(new_sp); *stack_slot = address; setRegister(sp, new_sp); return new_sp; } uintptr_t Simulator::popAddress() { int current_sp = getRegister(sp); uintptr_t* stack_slot = reinterpret_cast(current_sp); uintptr_t address = *stack_slot; setRegister(sp, current_sp + sizeof(uintptr_t)); return address; } } // namespace jit } // namespace js js::jit::Simulator* JSContext::simulator() const { return simulator_; }