/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80: */ // Copyright 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.
// ----------------------------------------------------------------------------- // 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<const Instr*>(this);
}
// Set the raw instruction bits to value. inlinevoid setInstructionBits(Instr value) {
*reinterpret_cast<Instr*>(this) = value;
}
// Read one particular bit out of the instruction bits. inlineint bit(int nr) const { return (instructionBits() >> nr) & 1; }
// Read a bit field out of the instruction bits. inlineint bits(int hi, int lo) const { return (instructionBits() >> lo) & ((2 << (hi - lo)) - 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 { returnstatic_cast<OpcodeField>(
bits(OpcodeShift + OpcodeBits - 1, OpcodeShift));
}
// Return the fields at their original place in the instruction encoding. inline OpcodeField opcodeFieldRaw() const { returnstatic_cast<OpcodeField>(instructionBits() & OpcodeMask);
}
// Get the secondary field according to the opcode. inlineint 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;
}
}
// 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;
bool SimInstruction::isForbiddenInBranchDelay() const { constint 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: returntrue; case op_regimm: switch (rtFieldRaw()) { case rt_bltz: case rt_bgez: case rt_bltzal: case rt_bgezal: returntrue; default: returnfalse;
}; break; case op_special: switch (functionFieldRaw()) { case ff_jr: case ff_jalr: returntrue; default: returnfalse;
}; break; default: returnfalse;
};
}
bool SimInstruction::isLinkingInstruction() const { constint op = opcodeFieldRaw(); switch (op) { case op_jal: returntrue; case op_regimm: switch (rtFieldRaw()) { case rt_bgezal: case rt_bltzal: returntrue; default: returnfalse;
}; case op_special: switch (functionFieldRaw()) { case ff_jalr: returntrue; default: returnfalse;
}; default: returnfalse;
};
}
bool SimInstruction::isTrap() const { if (opcodeFieldRaw() != op_special) { returnfalse;
} 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: returnfalse;
};
}
}
SimInstruction::Type SimInstruction::instructionType() const { switch (opcodeFieldRaw()) { case op_special: switch (functionFieldRaw()) { case ff_jr: case ff_jalr: case ff_sync: case ff_break: case ff_sll: case ff_dsll: case ff_dsll32: case ff_srl: case ff_dsrl: case ff_dsrl32: case ff_sra: case ff_dsra: case ff_dsra32: case ff_sllv: case ff_dsllv: case ff_srlv: case ff_dsrlv: case ff_srav: case ff_dsrav: case ff_mfhi: case ff_mflo: case ff_mult: case ff_dmult: case ff_multu: case ff_dmultu: case ff_div: case ff_ddiv: case ff_divu: case ff_ddivu: case ff_add: case ff_dadd: case ff_addu: case ff_daddu: case ff_sub: case ff_dsub: case ff_subu: case ff_dsubu: 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: return kRegisterType; default: return kUnsupported;
}; break; case op_special2: switch (functionFieldRaw()) { case ff_mul: case ff_clz: case ff_dclz: return kRegisterType; default: return kUnsupported;
}; break; case op_special3: switch (functionFieldRaw()) { case ff_ins: case ff_dins: case ff_dinsm: case ff_dinsu: case ff_ext: case ff_dext: case ff_dextm: case ff_dextu: case ff_bshfl: case ff_dbshfl: 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_daddi: case op_addiu: case op_daddiu: 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_lbu: case op_lh: case op_lhu: case op_lw: case op_lwu: case op_lwl: case op_lwr: case op_ll: case op_lld: case op_ld: case op_ldl: case op_ldr: case op_sb: case op_sh: case op_sw: case op_swl: case op_swr: case op_sc: case op_scd: case op_sd: case op_sdl: case op_sdr: case op_lwc1: case op_ldc1: case op_swc1: case op_sdc1: return kImmediateType; // 26 bits immediate type instructions. e.g.: j imm26. case op_j: case op_jal: return kJumpType; default: return kUnsupported;
}; return kUnsupported;
}
private: char data_[kPageSize]; // The cached data. staticconstint kValidityMapSize = kPageSize >> kLineShift; char validity_map_[kValidityMapSize]; // One byte per line.
};
// Protects the icache() and redirection() properties of the // Simulator. class AutoLockSimulatorCache : public LockGuard<Mutex> { using Base = LockGuard<Mutex>;
// 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. staticconst Instr kBreakpointInstr = op_special | ff_break | 0xfffff << 6; staticconst Instr kNopInstr = op_special | ff_sll;
// 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();
};
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<char**>(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();
}
bool MipsDebugger::setBreakpoint(SimInstruction* breakpc) { // Check if a breakpoint can be set. If not return without any side-effects. if (sim_->break_pc_ != nullptr) { returnfalse;
}
// 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. returntrue;
}
printf("\n\n"); // f0, f1, f2, ... f31. for (uint32_t i = 0; i < FloatRegisters::TotalPhys; i++) {
printf("%3s: 0x%016" PRIi64 "\tflt: %-8.4g\tdbl: %-16.4g\n",
FloatRegisters::GetName(i), getFPURegisterValueLong(i),
getFPURegisterValueFloat(i), getFPURegisterValueDouble(i));
}
}
staticchar* ReadLine(constchar* 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<char>(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<char>(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;
}
if (argc == 1) {
cur = reinterpret_cast<uint8_t*>(sim_->get_pc());
end = cur + (10 * SimInstruction::kInstrSize);
} elseif (argc == 2) { Register reg = Register::FromName(arg1); if (reg != InvalidReg || strncmp(arg1, "0x", 2) == 0) { // The argument is an address or a register name.
int64_t value; if (getValue(arg1, &value)) {
cur = reinterpret_cast<uint8_t*>(value); // Disassemble 10 instructions at <arg1>.
end = cur + (10 * SimInstruction::kInstrSize);
}
} else { // The argument is the number of instructions.
int64_t value; if (getValue(arg1, &value)) {
cur = reinterpret_cast<uint8_t*>(sim_->get_pc()); // Disassemble <arg1> instructions.
end = cur + (value * SimInstruction::kInstrSize);
}
}
} else {
int64_t value1;
int64_t value2; if (getValue(arg1, &value1) && getValue(arg2, &value2)) {
cur = reinterpret_cast<uint8_t*>(value1);
end = cur + (value2 * SimInstruction::kInstrSize);
}
}
while (cur < end) {
DisassembleInstruction(uint64_t(cur));
cur += SimInstruction::kInstrSize;
}
} elseif (strcmp(cmd, "gdb") == 0) {
printf("relinquishing control to gdb\n"); asm("int $3");
printf("regaining control from gdb\n");
} elseif (strcmp(cmd, "break") == 0) { if (argc == 2) {
int64_t value; if (getValue(arg1, &value)) { if (!setBreakpoint(reinterpret_cast<SimInstruction*>(value))) {
printf("setting breakpoint failed\n");
}
} else {
printf("%s unrecognized\n", arg1);
}
} else {
printf("break <address>\n");
}
} elseif (strcmp(cmd, "del") == 0) { if (!deleteBreakpoint(nullptr)) {
printf("deleting breakpoint failed\n");
}
} elseif (strcmp(cmd, "flags") == 0) {
printf("No flags on MIPS !\n");
} elseif (strcmp(cmd, "stop") == 0) {
int64_t value;
intptr_t stop_pc = sim_->get_pc() - 2 * SimInstruction::kInstrSize;
SimInstruction* stop_instr = reinterpret_cast<SimInstruction*>(stop_pc);
SimInstruction* msg_address = reinterpret_cast<SimInstruction*>(
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");
}
} elseif (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);
}
} elseif (getValue(arg2, &value)) {
sim_->printStopInfo(value);
} else {
printf("Unrecognized argument.\n");
}
} elseif (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);
}
} elseif (getValue(arg2, &value)) {
sim_->enableStop(value);
} else {
printf("Unrecognized argument.\n");
}
} elseif (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);
}
} elseif (getValue(arg2, &value)) {
sim_->disableStop(value);
} else {
printf("Unrecognized argument.\n");
}
}
} else {
printf("Wrong usage. Use help command for more information.\n");
}
} elseif ((strcmp(cmd, "h") == 0) || (strcmp(cmd, "help") == 0)) {
printf("cont\n");
printf(" continue execution (alias 'c')\n");
printf("stepi\n");
printf(" step one instruction (alias 'si')\n");
printf("print <register>\n");
printf(" print register content (alias 'p')\n");
printf(" use register name 'all' to print all registers\n");
printf("printobject <register>\n");
printf(" print an object from a register (alias 'po')\n");
printf("stack [<words>]\n");
printf(" dump stack content, default dump 10 words)\n");
printf("mem <address> [<words>]\n");
printf(" dump memory content, default dump 10 words)\n");
printf("flags\n");
printf(" print flags\n");
printf("disasm [<instructions>]\n");
printf("disasm [<address/register>]\n");
printf("disasm [[<address/register>] <instructions>]\n");
printf(" disassemble code, default is 10 instructions\n");
printf(" from pc (alias 'di')\n");
printf("gdb\n");
printf(" enter gdb\n");
printf("break <address>\n");
printf(" set a break point on the address\n");
printf("del\n");
printf(" delete the breakpoint\n");
printf("stop feature:\n");
printf(" Description:\n");
printf(" Stops are debug instructions inserted by\n");
printf(" the Assembler::stop() function.\n");
printf(" When hitting a stop, the Simulator will\n");
printf(" stop and and give control to the Debugger.\n");
printf(" All stop codes are watched:\n");
printf(" - They can be enabled / disabled: the Simulator\n");
printf(" will / won't stop when hitting them.\n");
printf(" - The Simulator keeps track of how many times they \n");
printf(" are met. (See the info command.) Going over a\n");
printf(" disabled stop still increases its counter. \n");
printf(" Commands:\n");
printf(" stop info all/<code> : print infos about number <code>\n");
printf(" or all stop(s).\n");
printf(" stop enable/disable all/<code> : enables / disables\n");
printf(" all or number <code> stop(s)\n");
printf(" stop unstop\n");
printf(" ignore the stop instruction at the current location\n");
printf(" from now on\n");
} else {
printf("Unknown command: %s\n", cmd);
}
}
}
// Add all the breakpoints back to stop execution and enter the debugger // shell when hit.
redoBreakpoints();
if (cache_hit) { // Check that the data in memory matches the contents of the I-cache. int cmpret =
memcmp(reinterpret_cast<void*>(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;
}
}
// 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. staticconst size_t stackSize = 2 * 1024 * 1024;
stack_ = js_pod_malloc<char>(stackSize); if (!stack_) { returnfalse;
}
// Leave a safety margin of 1MB to prevent overrunning the stack when // pushing values (total stack size is 2MB).
stackLimit_ = reinterpret_cast<uintptr_t>(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<int64_t>(stack_) + stackSize - 64;
returntrue;
}
// 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 { friendclass 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);
}
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<Redirection>(1); if (!redir) {
oomUnsafe.crash("Simulator redirection");
} new (redir) Redirection(nativeFunction, type); return redir;
}
// Get the active Simulator for the current thread.
Simulator* Simulator::Current() {
JSContext* cx = TlsContext.get();
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); return cx->simulator();
}
// Sets the register in the architecture state. It will also deal with updating // Simulator internal state for special registers such as PC. void Simulator::setRegister(int reg, int64_t value) {
MOZ_ASSERT((reg >= 0) && (reg < Register::kNumSimuRegisters)); if (reg == pc) {
pc_modified_ = true;
}
// Get the register from the architecture state. This function does handle // the special case of accessing the PC register.
int64_t Simulator::getRegister(int reg) const {
MOZ_ASSERT((reg >= 0) && (reg < Register::kNumSimuRegisters)); if (reg == 0) { return 0;
} return registers_[reg] + ((reg == pc) ? SimInstruction::kPCReadOffset : 0);
}
// Sets the rounding error codes in FCSR based on the result of the rounding. // Returns true if the operation was invalid. template <typename T> bool Simulator::setFCSRRoundError(double original, double rounded) { bool ret = false;
if ((longdouble)rounded > (longdouble)std::numeric_limits<T>::max() ||
(longdouble)rounded < (longdouble)std::numeric_limits<T>::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(int64_t value) {
pc_modified_ = true;
registers_[pc] = value;
}
// MIPS memory instructions (except lw(d)l/r , sw(d)l/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::loadLinkedW(uint64_t addr, SimInstruction* instr) { if ((addr & 3) == 0) { if (handleWasmSegFault(addr, 4)) { return -1;
}
volatile int32_t* ptr = reinterpret_cast<volatile int32_t*>(addr);
int32_t value = *ptr;
lastLLValue_ = value;
LLAddr_ = addr; // Note that any memory write or "external" interrupt should reset this // value to false.
LLBit_ = true; return value;
}
printf("Unaligned write at 0x%016" PRIx64 ", pc=0x%016" PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr));
MOZ_CRASH(); return 0;
}
int Simulator::storeConditionalW(uint64_t addr, int value,
SimInstruction* instr) { // Correct behavior in this case, as defined by architecture, is to just // return 0, but there is no point at allowing that. It is certainly an // indicator of a bug. if (addr != LLAddr_) {
printf("SC to bad address: 0x%016" PRIx64 ", pc=0x%016" PRIx64 ", expected: 0x%016" PRIx64 "\n",
addr, reinterpret_cast<intptr_t>(instr), LLAddr_);
MOZ_CRASH();
}
int64_t Simulator::loadLinkedD(uint64_t addr, SimInstruction* instr) { if ((addr & kPointerAlignmentMask) == 0) { if (handleWasmSegFault(addr, 8)) { return -1;
}
volatile int64_t* ptr = reinterpret_cast<volatile int64_t*>(addr);
int64_t value = *ptr;
lastLLValue_ = value;
LLAddr_ = addr; // Note that any memory write or "external" interrupt should reset this // value to false.
LLBit_ = true; return value;
}
printf("Unaligned write at 0x%016" PRIx64 ", pc=0x%016" PRIxPTR "\n", addr, reinterpret_cast<intptr_t>(instr));
MOZ_CRASH(); return 0;
}
int Simulator::storeConditionalD(uint64_t addr, int64_t value,
SimInstruction* instr) { // Correct behavior in this case, as defined by architecture, is to just // return 0, but there is no point at allowing that. It is certainly an // indicator of a bug. if (addr != LLAddr_) {
printf("SC to bad address: 0x%016" PRIx64 ", pc=0x%016" PRIx64 ", expected: 0x%016" PRIx64 "\n",
addr, reinterpret_cast<intptr_t>(instr), LLAddr_);
MOZ_CRASH();
}
// Unsupported instructions use format to print an error and stop execution. void Simulator::format(SimInstruction* instr, constchar* format) {
printf("Simulator found unsupported instruction:\n 0x%016lx: %s\n", reinterpret_cast<intptr_t>(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.
ABI_FUNCTION_TYPE_SIM_PROTOTYPES
// 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_N64_ABI)
MOZ_CRASH("Only N64 ABI supported."); #else
Redirection* redirection = Redirection::FromSwiInstruction(instr);
uintptr_t nativeFn = reinterpret_cast<uintptr_t>(redirection->nativeFunction());
// Get the SP for reading stack arguments
int64_t* sp_ = reinterpret_cast<int64_t*>(getRegister(sp));
// This is dodgy but it works because the C entry stubs are never moved. // See comment in codegen-arm.cc and bug 1242173.
int64_t saved_ra = getRegister(ra);
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);
}
}
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;
} elseif (code > kMaxStopCode) {
printf("Code too large, only %u stops can be used\n", kMaxStopCode + 1); return;
} constchar* 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.");
}
}
}
// Helper function for decodeTypeRegister. void Simulator::configureTypeRegister(SimInstruction* instr, int64_t& alu_out,
__int128& i128hilo, unsigned __int128& u128hilo,
int64_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.
// ALU output. // It should not be used as is. Instructions using it should always // initialize it first.
int64_t alu_out = 0x12345678;
// For break and trap instructions. bool do_interrupt = false;
// For jr and jalr. // Get current pc.
int64_t current_pc = get_pc(); // Next pc
int64_t next_pc = 0;
int32_t return_addr_reg = 31;
// Set up the variables if needed before executing the instruction.
configureTypeRegister(instr, alu_out, i128hilo, u128hilo, next_pc,
return_addr_reg, do_interrupt);
// ---------- Execution. switch (op) { case op_cop1: switch (instr->rsFieldRaw()) { case rs_bc1: // Branch on coprocessor condition.
MOZ_CRASH(); break; case rs_cfc1:
setRegister(rt_reg, alu_out);
[[fallthrough]]; case rs_mfc1:
setRegister(rt_reg, alu_out); break; case rs_dmfc1:
setRegister(rt_reg, alu_out); break; case rs_mfhc1:
setRegister(rt_reg, alu_out); break; case rs_ctc1: // At the moment only FCSR is supported.
MOZ_ASSERT(fs_reg == kFCSRRegister);
FCSR_ = registers_[rt_reg]; break; case rs_mtc1:
setFpuRegisterLo(fs_reg, registers_[rt_reg]); break; case rs_dmtc1:
setFpuRegister(fs_reg, registers_[rt_reg]); break; case rs_mthc1:
setFpuRegisterHi(fs_reg, registers_[rt_reg]); break; case rs_s: float f, ft_value, fs_value;
uint32_t cc, fcsr_cc;
int64_t i64;
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, std::isnan(fs_value) || std::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) ||
(std::isnan(fs_value) || std::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) ||
(std::isnan(fs_value) || std::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) ||
(std::isnan(fs_value) || std::isnan(ft_value))); break; case ff_cvt_d_fmt:
f = getFpuRegisterFloat(fs_reg);
setFpuRegisterDouble(fd_reg, static_cast<double>(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 = I32(rounded); if ((result & 1) != 0 && result - fs_value == 0.5) { // If the number is halfway between two integers, // round to the even one.
result--;
}
setFpuRegisterLo(fd_reg, result); if (setFCSRRoundError<int32_t>(fs_value, rounded)) {
setFpuRegisterLo(fd_reg, kFPUInvalidResult);
} break;
} case ff_trunc_w_fmt: { // Truncate float to word (round towards 0). float rounded = truncf(fs_value);
int32_t result = I32(rounded);
setFpuRegisterLo(fd_reg, result); if (setFCSRRoundError<int32_t>(fs_value, rounded)) {
setFpuRegisterLo(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 = I32(rounded);
setFpuRegisterLo(fd_reg, result); if (setFCSRRoundError<int32_t>(fs_value, rounded)) {
setFpuRegisterLo(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 = I32(rounded);
setFpuRegisterLo(fd_reg, result); if (setFCSRRoundError<int32_t>(fs_value, rounded)) {
setFpuRegisterLo(fd_reg, kFPUInvalidResult);
} break;
} case ff_cvt_l_fmt: // Mips64r2: Truncate float to 64-bit long-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_l_fmt: { // Mips64r2 instruction. float rounded = fs_value > 0 ? std::floor(fs_value + 0.5)
: std::ceil(fs_value - 0.5);
i64 = I64(rounded);
setFpuRegister(fd_reg, i64); if (setFCSRRoundError<int64_t>(fs_value, rounded)) {
setFpuRegister(fd_reg, kFPUInvalidResult64);
} break;
} case ff_trunc_l_fmt: { // Mips64r2 instruction. float rounded = truncf(fs_value);
i64 = I64(rounded);
setFpuRegister(fd_reg, i64); if (setFCSRRoundError<int64_t>(fs_value, rounded)) {
setFpuRegister(fd_reg, kFPUInvalidResult64);
} break;
} case ff_floor_l_fmt: { // Mips64r2 instruction. float rounded = std::floor(fs_value);
i64 = I64(rounded);
setFpuRegister(fd_reg, i64); if (setFCSRRoundError<int64_t>(fs_value, rounded)) {
setFpuRegister(fd_reg, kFPUInvalidResult64);
} break;
} case ff_ceil_l_fmt: { // Mips64r2 instruction. float rounded = std::ceil(fs_value);
i64 = I64(rounded);
setFpuRegister(fd_reg, i64); if (setFCSRRoundError<int64_t>(fs_value, rounded)) {
setFpuRegister(fd_reg, kFPUInvalidResult64);
} break;
} case ff_cvt_ps_s: case ff_c_f_fmt:
MOZ_CRASH(); break; case ff_movf_fmt: 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);
dt_value = getFpuRegisterDouble(ft_reg);
cc = instr->fcccValue();
fcsr_cc = GetFCSRConditionBit(cc); switch (instr->functionFieldRaw()) { case ff_add_fmt:
setFpuRegisterDouble(fd_reg, ds_value + dt_value); break; case ff_sub_fmt:
setFpuRegisterDouble(fd_reg, ds_value - dt_value); break; case ff_mul_fmt:
setFpuRegisterDouble(fd_reg, ds_value * dt_value); break; case ff_div_fmt:
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:
setFCSRBit(fcsr_cc, std::isnan(ds_value) || std::isnan(dt_value)); break; case ff_c_eq_fmt:
setFCSRBit(fcsr_cc, (ds_value == dt_value)); break; case ff_c_ueq_fmt:
setFCSRBit(fcsr_cc,
(ds_value == dt_value) ||
(std::isnan(ds_value) || std::isnan(dt_value))); break; case ff_c_olt_fmt:
setFCSRBit(fcsr_cc, (ds_value < dt_value)); break; case ff_c_ult_fmt:
setFCSRBit(fcsr_cc,
(ds_value < dt_value) ||
(std::isnan(ds_value) || std::isnan(dt_value))); break; case ff_c_ole_fmt:
setFCSRBit(fcsr_cc, (ds_value <= dt_value)); break; case ff_c_ule_fmt:
setFCSRBit(fcsr_cc,
(ds_value <= dt_value) ||
(std::isnan(ds_value) || std::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 = I32(rounded); if ((result & 1) != 0 && result - ds_value == 0.5) { // If the number is halfway between two integers, // round to the even one.
result--;
}
setFpuRegisterLo(fd_reg, result); if (setFCSRRoundError<int32_t>(ds_value, rounded)) {
setFpuRegisterLo(fd_reg, kFPUInvalidResult);
} break;
} case ff_trunc_w_fmt: { // Truncate double to word (round towards // 0). double rounded = trunc(ds_value);
int32_t result = I32(rounded);
setFpuRegisterLo(fd_reg, result); if (setFCSRRoundError<int32_t>(ds_value, rounded)) {
setFpuRegisterLo(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 = I32(rounded);
setFpuRegisterLo(fd_reg, result); if (setFCSRRoundError<int32_t>(ds_value, rounded)) {
setFpuRegisterLo(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 = I32(rounded);
setFpuRegisterLo(fd_reg, result); if (setFCSRRoundError<int32_t>(ds_value, rounded)) {
setFpuRegisterLo(fd_reg, kFPUInvalidResult);
} break;
} case ff_cvt_s_fmt: // Convert double to float (single).
setFpuRegisterFloat(fd_reg, static_cast<float>(ds_value)); break; case ff_cvt_l_fmt: // Mips64r2: Truncate double to 64-bit // long-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_l_fmt: { // Mips64r2 instruction. double rounded = ds_value > 0 ? std::floor(ds_value + 0.5)
: std::ceil(ds_value - 0.5);
i64 = I64(rounded);
setFpuRegister(fd_reg, i64); if (setFCSRRoundError<int64_t>(ds_value, rounded)) {
setFpuRegister(fd_reg, kFPUInvalidResult64);
} break;
} case ff_trunc_l_fmt: { // Mips64r2 instruction. double rounded = trunc(ds_value);
i64 = I64(rounded);
setFpuRegister(fd_reg, i64); if (setFCSRRoundError<int64_t>(ds_value, rounded)) {
setFpuRegister(fd_reg, kFPUInvalidResult64);
} break;
} case ff_floor_l_fmt: { // Mips64r2 instruction. double rounded = std::floor(ds_value);
i64 = I64(rounded);
setFpuRegister(fd_reg, i64); if (setFCSRRoundError<int64_t>(ds_value, rounded)) {
setFpuRegister(fd_reg, kFPUInvalidResult64);
} break;
} case ff_ceil_l_fmt: { // Mips64r2 instruction. double rounded = std::ceil(ds_value);
i64 = I64(rounded);
setFpuRegister(fd_reg, i64); if (setFCSRRoundError<int64_t>(ds_value, rounded)) {
setFpuRegister(fd_reg, kFPUInvalidResult64);
} break;
} case ff_c_f_fmt:
MOZ_CRASH(); 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; 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; default:
MOZ_CRASH();
} break; case rs_w: switch (instr->functionFieldRaw()) { case ff_cvt_s_fmt: // Convert word to float (single).
i64 = getFpuRegisterLo(fs_reg);
setFpuRegisterFloat(fd_reg, static_cast<float>(i64)); break; case ff_cvt_d_fmt: // Convert word to double.
i64 = getFpuRegisterLo(fs_reg);
setFpuRegisterDouble(fd_reg, static_cast<double>(i64)); break; default:
MOZ_CRASH();
}; break; case rs_l: switch (instr->functionFieldRaw()) { case ff_cvt_d_fmt: // Mips64r2 instruction.
i64 = getFpuRegister(fs_reg);
setFpuRegisterDouble(fd_reg, static_cast<double>(i64)); break; case ff_cvt_s_fmt:
i64 = getFpuRegister(fs_reg);
setFpuRegisterFloat(fd_reg, static_cast<float>(i64)); 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<SimInstruction*>(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<SimInstruction*>(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, I32(i128hilo & 0xffffffff));
setRegister(HI, I32(i128hilo >> 32)); break; case ff_dmult:
setRegister(LO, I64(i128hilo & 0xfffffffffffffffful));
setRegister(HI, I64(i128hilo >> 64)); break; case ff_multu:
setRegister(LO, I32(u128hilo & 0xffffffff));
setRegister(HI, I32(u128hilo >> 32)); break; case ff_dmultu:
setRegister(LO, I64(u128hilo & 0xfffffffffffffffful));
setRegister(HI, I64(u128hilo >> 64)); break; case ff_div: case ff_divu: // 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.
setRegister(LO, I32(i128hilo & 0xffffffff));
setRegister(HI, I32(i128hilo >> 32)); break; case ff_ddiv: case ff_ddivu: // 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.
setRegister(LO, I64(i128hilo & 0xfffffffffffffffful));
setRegister(HI, I64(i128hilo >> 64)); break; case ff_sync: 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; // 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; 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: case ff_dins: case ff_dinsm: case ff_dinsu: // Ins instr leaves result in Rt, rather than Rd.
setRegister(rt_reg, alu_out); break; case ff_ext: case ff_dext: case ff_dextm: case ff_dextu: // Ext instr leaves result in Rt, rather than Rd.
setRegister(rt_reg, alu_out); break; case ff_bshfl:
setRegister(rd_reg, alu_out); break; case ff_dbshfl:
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);
};
}
// ---------- 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_daddi: case op_addiu: case op_daddiu: 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_lbu: case op_lb: case op_lhu: case op_lh: case op_lwu: case op_lw: case op_lwl: case op_lwr: case op_ll: case op_lld: case op_ld: case op_ldl: case op_ldr:
setRegister(rt_reg, alu_out); break; case op_sb:
writeB(addr, I8(rt), instr); break; case op_sh:
writeH(addr, U16(rt), instr); break; case op_sw:
writeW(addr, I32(rt), instr); break; case op_swl:
writeW(addr, I32(mem_value), instr); break; case op_swr:
writeW(addr, I32(mem_value), instr); break; case op_sc:
setRegister(rt_reg, storeConditionalW(addr, I32(rt), instr)); break; case op_scd:
setRegister(rt_reg, storeConditionalD(addr, rt, instr)); break; case op_sd:
writeDW(addr, rt, instr); break; case op_sdl:
writeDW(addr, mem_value, instr); break; case op_sdr:
writeDW(addr, mem_value, instr); break; case op_lwc1:
setFpuRegisterLo(ft_reg, alu_out); break; case op_ldc1:
setFpuRegisterDouble(ft_reg, fp_out); break; case op_swc1:
writeW(addr, getFpuRegisterLo(ft_reg), instr); break; case op_sdc1:
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<SimInstruction*>(
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 bits immediate. (e.g. j, jal). void Simulator::decodeTypeJump(SimInstruction* instr) { // Get current pc.
int64_t current_pc = get_pc(); // Get unchanged bits of pc.
int64_t pc_high_bits = current_pc & 0xfffffffff0000000ul; // Next pc.
int64_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<SimInstruction*>(
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<int64_t>(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);
}
// Get the PC to simulate. Cannot use the accessor here as we need the // raw PC value and not the one used as input to arithmetic instructions.
int64_t program_counter = get_pc();
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<int64_t>(entry)); // Put down marker for end of simulation. The simulator will stop simulation // when the PC reaches this value. By saving the "end simulation" value into // the LR the simulation stops when returning to this call point.
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.
int64_t s0_val = getRegister(s0);
int64_t s1_val = getRegister(s1);
int64_t s2_val = getRegister(s2);
int64_t s3_val = getRegister(s3);
int64_t s4_val = getRegister(s4);
int64_t s5_val = getRegister(s5);
int64_t s6_val = getRegister(s6);
int64_t s7_val = getRegister(s7);
int64_t gp_val = getRegister(gp);
int64_t sp_val = getRegister(sp);
int64_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.
int64_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<true>();
} else {
execute<false>();
}
// 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));
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.99Bemerkung:
(vorverarbeitet am 2026-04-25)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.