blob: 424de985349c2036b79570d5fb56f2043949aee6 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
// 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 <float.h>
#include "asmjs/AsmJSValidate.h"
#include "jit/mips32/Assembler-mips32.h"
#include "vm/Runtime.h"
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<const Instr*>(this);
}
// Set the raw instruction bits to value.
inline void setInstructionBits(Instr value) {
*reinterpret_cast<Instr*>(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 Opcode opcodeValue() const {
return static_cast<Opcode>(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 Opcode opcodeFieldRaw() const {
return static_cast<Opcode>(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 {
Opcode 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 break or a 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:
case ff_tge:
case ff_tgeu:
case ff_tlt:
case ff_tltu:
case ff_teq:
case ff_tne:
return true;
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:
return kRegisterType;
default:
return kUnsupported;
};
break;
case op_special2:
switch (functionFieldRaw()) {
case ff_mul:
case ff_clz:
return kRegisterType;
default:
return kUnsupported;
};
break;
case op_special3:
switch (functionFieldRaw()) {
case ff_ins:
case ff_ext:
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:
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:
explicit AutoLockSimulatorCache(Simulator* sim) : sim_(sim) {
PR_Lock(sim_->cacheLock_);
MOZ_ASSERT(!sim_->cacheLockHolder_);
#ifdef DEBUG
sim_->cacheLockHolder_ = PR_GetCurrentThread();
#endif
}
~AutoLockSimulatorCache() {
MOZ_ASSERT(sim_->cacheLockHolder_);
#ifdef DEBUG
sim_->cacheLockHolder_ = nullptr;
#endif
PR_Unlock(sim_->cacheLock_);
}
private:
Simulator* const sim_;
};
bool Simulator::ICacheCheckingEnabled = false;
int Simulator::StopSimAt = -1;
Simulator*
Simulator::Create()
{
Simulator* sim = js_new<Simulator>();
if (!sim)
return nullptr;
if (!sim->init()) {
js_delete(sim);
return nullptr;
}
if (js_sb_getenv("MIPS_SIM_ICACHE_CHECKS"))
Simulator::ICacheCheckingEnabled = true;
char* stopAtStr = js_sb_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;
}
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<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();
}
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<uint32_t*>(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)
{
char* result = nullptr;
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.
if (result)
js_delete(result);
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 = (char*)js_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 = (char*)js_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, offset * sizeof(char));
js_free(result);
result = new_result;
}
// Copy the newly read line into the result.
memcpy(result + offset, lineBuf, len * sizeof(char));
offset += len;
}
MOZ_ASSERT(result);
result[offset] = '\0';
return result;
}
static void
DisassembleInstruction(uint32_t pc)
{
uint8_t* bytes = reinterpret_cast<uint8_t*>(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<void*>(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<SimInstruction*>(sim_->get_pc());
if (!(instr->isTrap()) ||
instr->instructionBits() == kCallRedirInstr) {
sim_->instructionDecode(
reinterpret_cast<SimInstruction*>(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<SimInstruction*>(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 <register> or print <fpu register> 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<int32_t*>(sim_->getRegister(Simulator::sp));
} else { // Command "mem".
int32_t value;
if (!getValue(arg1, &value)) {
printf("%s unrecognized\n", arg1);
continue;
}
cur = reinterpret_cast<int32_t*>(value);
next_arg++;
}
int32_t words;
if (argc == next_arg) {
words = 10;
} else {
if (!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<uint8_t*>(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<uint8_t*>(value);
// Disassemble 10 instructions at <arg1>.
end = cur + (10 * SimInstruction::kInstrSize);
}
} else {
// The argument is the number of instructions.
int32_t value;
if (getValue(arg1, &value)) {
cur = reinterpret_cast<uint8_t*>(sim_->get_pc());
// Disassemble <arg1> instructions.
end = cur + (value * SimInstruction::kInstrSize);
}
}
} else {
int32_t value1;
int32_t value2;
if (getValue(arg1, &value1) && getValue(arg2, &value2)) {
cur = reinterpret_cast<uint8_t*>(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<SimInstruction*>(value)))
printf("setting breakpoint failed\n");
} else {
printf("%s unrecognized\n", arg1);
}
} else {
printf("break <address>\n");
}
} else if (strcmp(cmd, "del") == 0) {
if (!deleteBreakpoint(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<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");
}
} 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 <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();
#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(Simulator::ICacheMap& i_cache, void* page)
{
Simulator::ICacheMap::AddPtr p = i_cache.lookupForAdd(page);
if (p)
return p->value();
CachePage* new_page = js_new<CachePage>();
if (!i_cache.add(p, page, new_page))
return nullptr;
return new_page;
}
// Flush from start up to and not including start + size.
static void
FlushOnePageLocked(Simulator::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<void*>(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(Simulator::ICacheMap& i_cache, void* start_addr, size_t size)
{
intptr_t start = reinterpret_cast<intptr_t>(start_addr);
int intra_line = (start & CachePage::kLineMask);
start -= intra_line;
size += intra_line;
size = ((size - 1) | CachePage::kLineMask) + 1;
int offset = (start & CachePage::kPageMask);
while (!AllOnOnePage(start, size - 1)) {
int bytes_to_flush = CachePage::kPageSize - offset;
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
CheckICacheLocked(Simulator::ICacheMap& i_cache, SimInstruction* instr)
{
intptr_t address = reinterpret_cast<intptr_t>(instr);
void* page = reinterpret_cast<void*>(address & (~CachePage::kPageMask));
void* line = reinterpret_cast<void*>(address & (~CachePage::kLineMask));
int offset = (address & CachePage::kPageMask);
CachePage* cache_page = GetCachePageLocked(i_cache, page);
char* cache_valid_byte = cache_page->validityByte(offset);
bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID);
char* cached_line = cache_page->cachedData(offset & ~CachePage::kLineMask);
if (cache_hit) {
// Check that the data in memory matches the contents of the I-cache.
MOZ_ASSERT(memcmp(reinterpret_cast<void*>(instr),
cache_page->cachedData(offset),
SimInstruction::kInstrSize) == 0);
} else {
// Cache miss. Load memory into the cache.
memcpy(cached_line, line, CachePage::kLineLength);
*cache_valid_byte = CachePage::LINE_VALID;
}
}
HashNumber
Simulator::ICacheHasher::hash(const Lookup& l)
{
return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(l)) >> 2;
}
bool
Simulator::ICacheHasher::match(const Key& k, const Lookup& l)
{
MOZ_ASSERT((reinterpret_cast<intptr_t>(k) & CachePage::kPageMask) == 0);
MOZ_ASSERT((reinterpret_cast<intptr_t>(l) & CachePage::kPageMask) == 0);
return k == l;
}
void
Simulator::FlushICache(void* start_addr, size_t size)
{
if (Simulator::ICacheCheckingEnabled) {
Simulator* sim = Simulator::Current();
AutoLockSimulatorCache als(sim);
js::jit::FlushICacheLocked(sim->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;
resume_pc_ = 0;
break_pc_ = nullptr;
break_instr_ = 0;
// 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;
// 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;
cacheLock_ = nullptr;
#ifdef DEBUG
cacheLockHolder_ = nullptr;
#endif
redirection_ = nullptr;
}
bool
Simulator::init()
{
cacheLock_ = PR_NewLock();
if (!cacheLock_)
return false;
if (!icache_.init())
return false;
// Allocate 2MB for the stack. Note that we will only use 1MB, see below.
static const size_t stackSize = 2 * 1024 * 1024;
stack_ = static_cast<char*>(js_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<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<int32_t>(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 Simulator;
// sim's lock must already be held.
Redirection(void* nativeFunction, ABIFunctionType type, Simulator* sim)
: nativeFunction_(nativeFunction),
swiInstruction_(kCallRedirInstr),
type_(type),
next_(nullptr)
{
next_ = sim->redirection();
if (Simulator::ICacheCheckingEnabled)
FlushICacheLocked(sim->icache(), addressOfSwiInstruction(), SimInstruction::kInstrSize);
sim->setRedirection(this);
}
public:
void* addressOfSwiInstruction() { return &swiInstruction_; }
void* nativeFunction() const { return nativeFunction_; }
ABIFunctionType type() const { return type_; }
static Redirection* Get(void* nativeFunction, ABIFunctionType type) {
Simulator* sim = Simulator::Current();
AutoLockSimulatorCache als(sim);
Redirection* current = sim->redirection();
for (; current != nullptr; current = current->next_) {
if (current->nativeFunction_ == nativeFunction) {
MOZ_ASSERT(current->type() == type);
return current;
}
}
Redirection* redir = (Redirection*)js_malloc(sizeof(Redirection));
if (!redir) {
MOZ_ReportAssertionFailure("[unhandlable oom] Simulator redirection",
__FILE__, __LINE__);
MOZ_CRASH();
}
new(redir) Redirection(nativeFunction, type, sim);
return redir;
}
static Redirection* FromSwiInstruction(SimInstruction* swiInstruction) {
uint8_t* addrOfSwi = reinterpret_cast<uint8_t*>(swiInstruction);
uint8_t* addrOfRedirection = addrOfSwi - offsetof(Redirection, swiInstruction_);
return reinterpret_cast<Redirection*>(addrOfRedirection);
}
private:
void* nativeFunction_;
uint32_t swiInstruction_;
ABIFunctionType type_;
Redirection* next_;
};
Simulator::~Simulator()
{
js_free(stack_);
PR_DestroyLock(cacheLock_);
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()
{
return TlsPerThreadData.get()->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<float*>(&FPUregisters_[fpureg]) = value;
}
void
Simulator::setFpuRegisterFloat(int fpureg, int64_t value)
{
setFpuRegister(fpureg, value & 0xffffffff);
setFpuRegister(fpureg + 1, value >> 32);
}
void
Simulator::setFpuRegisterDouble(int fpureg, double value)
{
MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)
&& ((fpureg % 2) == 0));
*mozilla::BitwiseCast<double*>(&FPUregisters_[fpureg]) = value;
}
void
Simulator::setFpuRegisterDouble(int fpureg, int64_t value)
{
setFpuRegister(fpureg, value & 0xffffffff);
setFpuRegister(fpureg + 1, value >> 32);
}
// 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, &registers_[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<int64_t*>(const_cast<int32_t*>(&FPUregisters_[fpureg]));
}
float
Simulator::getFpuRegisterFloat(int fpureg) const
{
MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters));
return *mozilla::BitwiseCast<float*>(const_cast<int32_t*>(&FPUregisters_[fpureg]));
}
double
Simulator::getFpuRegisterDouble(int fpureg) const
{
MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)
&& ((fpureg % 2) == 0));
return *mozilla::BitwiseCast<double*>(const_cast<int32_t*>(&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<int32_t>(res));
setRegister(v1, static_cast<int32_t>(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;
if (!std::isfinite(original) || !std::isfinite(rounded)) {
setFCSRBit(kFCSRInvalidOpFlagBit, true);
ret = true;
}
if (original != rounded) {
setFCSRBit(kFCSRInexactFlagBit, true);
}
if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) {
setFCSRBit(kFCSRUnderflowFlagBit, true);
ret = true;
}
if (rounded > INT_MAX || rounded < INT_MIN) {
setFCSRBit(kFCSROverflowFlagBit, true);
// The reference is not really clear but it seems this is required:
setFCSRBit(kFCSRInvalidOpFlagBit, true);
ret = true;
}
return ret;
}
// Raw access to the PC register.
void
Simulator::set_pc(int32_t value)
{
pc_modified_ = true;
registers_[pc] = value;
}
bool
Simulator::has_bad_pc() const
{
return ((registers_[pc] == bad_ra) || (registers_[pc] == end_sim_pc));
}
// Raw access to the PC register without the special adjustment when reading.
int32_t
Simulator::get_pc() const
{
return registers_[pc];
}
// The MIPS cannot do unaligned reads and writes. On some MIPS platforms an
// interrupt is caused. On others it does a funky rotation thing. For now we
// simply disallow unaligned reads, but at some point we may want to move to
// emulating the rotate behaviour. Note that simulator runs have the runtime
// system running directly on the host system and only generated code is
// executed in the simulator. Since the host is typically IA32 we will not
// get the correct MIPS-like behaviour on unaligned accesses.
int
Simulator::readW(uint32_t addr, SimInstruction* instr)
{
if (addr < 0x400) {
// This has to be a NULL-dereference, drop into debugger.
printf("Memory read from bad address: 0x%08x, pc=0x%08x\n",
addr, reinterpret_cast<intptr_t>(instr));
MOZ_CRASH();
}
if ((addr & kPointerAlignmentMask) == 0) {
intptr_t* ptr = reinterpret_cast<intptr_t*>(addr);
return *ptr;
}
printf("Unaligned read at 0x%08x, pc=0x%08x\n",
addr,
reinterpret_cast<intptr_t>(instr));
MOZ_CRASH();
return 0;
}
void
Simulator::writeW(uint32_t addr, int value, SimInstruction* instr)
{
if (addr < 0x400) {
// This has to be a NULL-dereference, drop into debugger.
printf("Memory write to bad address: 0x%08x, pc=0x%08x\n",
addr, reinterpret_cast<intptr_t>(instr));
MOZ_CRASH();
}
if ((addr & kPointerAlignmentMask) == 0) {
intptr_t* ptr = reinterpret_cast<intptr_t*>(addr);
*ptr = value;
return;
}
printf("Unaligned write at 0x%08x, pc=0x%08x\n",
addr,
reinterpret_cast<intptr_t>(instr));
MOZ_CRASH();
}
double
Simulator::readD(uint32_t addr, SimInstruction* instr)
{
if ((addr & kDoubleAlignmentMask) == 0) {
double* ptr = reinterpret_cast<double*>(addr);
return *ptr;
}
printf("Unaligned (double) read at 0x%08x, pc=0x%08x\n",
addr,
reinterpret_cast<intptr_t>(instr));
MOZ_CRASH();
return 0;
}
void
Simulator::writeD(uint32_t addr, double value, SimInstruction* instr)
{
if ((addr & kDoubleAlignmentMask) == 0) {
double* ptr = reinterpret_cast<double*>(addr);
*ptr = value;
return;
}
printf("Unaligned (double) write at 0x%08x, pc=0x%08x\n",
addr,
reinterpret_cast<intptr_t>(instr));
MOZ_CRASH();
}
uint16_t
Simulator::readHU(uint32_t addr, SimInstruction* instr)
{
if ((addr & 1) == 0) {
uint16_t* ptr = reinterpret_cast<uint16_t*>(addr);
return *ptr;
}
printf("Unaligned unsigned halfword read at 0x%08x, pc=0x%08x\n",
addr,
reinterpret_cast<intptr_t>(instr));
MOZ_CRASH();
return 0;
}
int16_t
Simulator::readH(uint32_t addr, SimInstruction* instr)
{
if ((addr & 1) == 0) {
int16_t* ptr = reinterpret_cast<int16_t*>(addr);
return *ptr;
}
printf("Unaligned signed halfword read at 0x%08x, pc=0x%08x\n",
addr,
reinterpret_cast<intptr_t>(instr));
MOZ_CRASH();
return 0;
}
void
Simulator::writeH(uint32_t addr, uint16_t value, SimInstruction* instr)
{
if ((addr & 1) == 0) {
uint16_t* ptr = reinterpret_cast<uint16_t*>(addr);
*ptr = value;
return;
}
printf("Unaligned unsigned halfword write at 0x%08x, pc=0x%08x\n",
addr,
reinterpret_cast<intptr_t>(instr));
MOZ_CRASH();
}
void
Simulator::writeH(uint32_t addr, int16_t value, SimInstruction* instr)
{
if ((addr & 1) == 0) {
int16_t* ptr = reinterpret_cast<int16_t*>(addr);
*ptr = value;
return;
}
printf("Unaligned halfword write at 0x%08x, pc=0x%08x\n",
addr,
reinterpret_cast<intptr_t>(instr));
MOZ_CRASH();
}
uint32_t
Simulator::readBU(uint32_t addr)
{
uint8_t* ptr = reinterpret_cast<uint8_t*>(addr);
return *ptr;
}
int32_t
Simulator::readB(uint32_t addr)
{
int8_t* ptr = reinterpret_cast<int8_t*>(addr);
return *ptr;
}
void
Simulator::writeB(uint32_t addr, uint8_t value)
{
uint8_t* ptr = reinterpret_cast<uint8_t*>(addr);
*ptr = value;
}
void
Simulator::writeB(uint32_t addr, int8_t value)
{
int8_t* ptr = reinterpret_cast<int8_t*>(addr);
*ptr = value;
}
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%08x: %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.
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 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 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 double (*Prototype_DoubleInt)(double 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);
// 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<int32_t*>(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<intptr_t>(redirection->nativeFunction());
bool stack_aligned = (getRegister(sp) & (ABIStackAlignment - 1)) == 0;
if (!stack_aligned) {
fprintf(stderr, "Runtime call with unaligned stack!\n");
MOZ_CRASH();
}
switch (redirection->type()) {
case Args_General0: {
Prototype_General0 target = reinterpret_cast<Prototype_General0>(external);
int64_t result = target();
setCallResult(result);
break;
}
case Args_General1: {
Prototype_General1 target = reinterpret_cast<Prototype_General1>(external);
int64_t result = target(arg0);
setCallResult(result);
break;
}
case Args_General2: {
Prototype_General2 target = reinterpret_cast<Prototype_General2>(external);
int64_t result = target(arg0, arg1);
setCallResult(result);
break;
}
case Args_General3: {
Prototype_General3 target = reinterpret_cast<Prototype_General3>(external);
int64_t result = target(arg0, arg1, arg2);
setCallResult(result);
break;
}
case Args_General4: {
Prototype_General4 target = reinterpret_cast<Prototype_General4>(external);
int64_t result = target(arg0, arg1, arg2, arg3);
setCallResult(result);
break;
}
case Args_General5: {
Prototype_General5 target = reinterpret_cast<Prototype_General5>(external);
int64_t result = target(arg0, arg1, arg2, arg3, arg4);
setCallResult(result);
break;
}
case Args_General6: {
Prototype_General6 target = reinterpret_cast<Prototype_General6>(external);
int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5);
setCallResult(result);
break;
}
case Args_General7: {
Prototype_General7 target = reinterpret_cast<Prototype_General7>(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<Prototype_General8>(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<Prototype_Double_None>(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<Prototype_Int_Double>(external);
int32_t res = target(dval0);
setRegister(v0, res);
break;
}
case Args_Int_DoubleIntInt: {
double dval = getFpuRegisterDouble(12);
Prototype_Int_DoubleIntInt target = reinterpret_cast<Prototype_Int_DoubleIntInt>(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<Prototype_Int_IntDoubleIntInt>(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<Prototype_Double_Double>(external);
double dresult = target(dval0);
setCallResultDouble(dresult);
break;
}
case Args_Float32_Float32: {
float fval0;
fval0 = getFpuRegisterFloat(12);
Prototype_Float32_Float32 target = reinterpret_cast<Prototype_Float32_Float32>(external);
float fresult = target(fval0);
setCallResultFloat(fresult);
break;
}
case Args_Double_Int: {
Prototype_Double_Int target = reinterpret_cast<Prototype_Double_Int>(external);
double dresult = target(arg0);
setCallResultDouble(dresult);
break;
}
case Args_Double_DoubleInt: {
double dval0, dval1;
int32_t ival;
getFpArgs(&dval0, &dval1, &ival);
Prototype_DoubleInt target = reinterpret_cast<Prototype_DoubleInt>(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<Prototype_Double_DoubleDouble>(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<Prototype_Double_IntDouble>(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<Prototype_Int_IntDouble>(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<Prototype_Double_DoubleDoubleDouble>(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<Prototype_Double_DoubleDoubleDoubleDouble>(external);
double dresult = target(dval0, dval1, dval2, dval3);
setCallResultDouble(dresult);
break;
}
default:
MOZ_CRASH("call");
}
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 {
// 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<uint32_t>(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 Opcode op = instr->opcodeFieldRaw();
const int32_t rs_reg = instr->rsValue();
const int32_t rs = getRegister(rs_reg);
const uint32_t rs_u = static_cast<uint32_t>(rs);
const int32_t rt_reg = instr->rtValue();
const int32_t rt = getRegister(rt_reg);
const uint32_t rt_u = static_cast<uint32_t>(rt);
const int32_t rd_reg = instr->rdValue();
const uint32_t sa = instr->saValue();
const int32_t fs_reg = instr->fsValue();
// ---------- Configuration.
switch (op) {
case 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<int64_t>(rs) * static_cast<int64_t>(rt);
break;
case ff_multu:
u64hilo = static_cast<uint64_t>(rs_u) * static_cast<uint64_t>(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:
// 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_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;
}
default:
MOZ_CRASH();
};
break;
default:
MOZ_CRASH();
};
}
void
Simulator::decodeTypeRegister(SimInstruction* instr)
{
// Instruction fields.
const Opcode op = instr->opcodeFieldRaw();
const int32_t rs_reg = instr->rsValue();
const int32_t rs = getRegister(rs_reg);
const uint32_t rs_u = static_cast<uint32_t>(rs);
const int32_t rt_reg = instr->rtValue();
const int32_t rt = getRegister(rt_reg);
const uint32_t rt_u = static_cast<uint32_t>(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:
setRegister(rt_reg, alu_out);
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;
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, 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<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.
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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(rounded);
setFpuRegister(fd_reg, result);
if (setFCSRRoundError(fs_value, rounded)) {
setFpuRegister(fd_reg, kFPUInvalidResult);
}
break;
}
case ff_cvt_l_fmt: { // Mips32r2: Truncate float to 64-bit long-word.
float rounded = truncf(fs_value);
i64 = static_cast<int64_t>(rounded);
setFpuRegisterFloat(fd_reg, i64);
break;
}
case ff_round_l_fmt: { // Mips32r2 instruction.
float rounded =
fs_value > 0 ? std::floor(fs_value + 0.5) : std::ceil(fs_value - 0.5);
i64 = static_cast<int64_t>(rounded);
setFpuRegisterFloat(fd_reg, i64);
break;
}
case ff_trunc_l_fmt: { // Mips32r2 instruction.
float rounded = truncf(fs_value);
i64 = static_cast<int64_t>(rounded);
setFpuRegisterFloat(fd_reg, i64);
break;
}
case ff_floor_l_fmt: // Mips32r2 instruction.
i64 = static_cast<int64_t>(std::floor(fs_value));
setFpuRegisterFloat(fd_reg, i64);
break;
case ff_ceil_l_fmt: // Mips32r2 instruction.
i64 = static_cast<int64_t>(std::ceil(fs_value));
setFpuRegisterFloat(fd_reg, i64);
break;
case ff_cvt_ps_s:
case ff_c_f_fmt:
MOZ_CRASH();
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, mozilla::IsNaN(ds_value) || mozilla::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) || (mozilla::IsNaN(ds_value) || mozilla::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) || (mozilla::IsNaN(ds_value) || mozilla::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) || (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.
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<int32_t>(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<int32_t>(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<int32_t>(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<int32_t>(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<float>(ds_value));
break;
case ff_cvt_l_fmt: { // Mips32r2: Truncate double to 64-bit long-word.
double rounded = trunc(ds_value);
i64 = static_cast<int64_t>(rounded);
setFpuRegisterDouble(fd_reg, i64);
break;
}
case ff_trunc_l_fmt: { // Mips32r2 instruction.
double rounded = trunc(ds_value);
i64 = static_cast<int64_t>(rounded);
setFpuRegisterDouble(fd_reg, i64);
break;
}
case ff_round_l_fmt: { // Mips32r2 instruction.
double rounded =
ds_value > 0 ? std::floor(ds_value + 0.5) : std::ceil(ds_value - 0.5);
i64 = static_cast<int64_t>(rounded);
setFpuRegisterDouble(fd_reg, i64);
break;
}
case ff_floor_l_fmt: // Mips32r2 instruction.
i64 = static_cast<int64_t>(std::floor(ds_value));
setFpuRegisterDouble(fd_reg, i64);
break;
case ff_ceil_l_fmt: // Mips32r2 instruction.
i64 = static_cast<int64_t>(std::ceil(ds_value));
setFpuRegisterDouble(fd_reg, i64);
break;
case ff_c_f_fmt:
MOZ_CRASH();
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<float>(alu_out));
break;
case ff_cvt_d_fmt: // Convert word to double.
alu_out = getFpuRegister(fs_reg);
setFpuRegisterDouble(fd_reg, static_cast<double>(alu_out));
break;
default:
MOZ_CRASH();
};
break;
case rs_l:
switch (instr->functionFieldRaw()) {
case ff_cvt_d_fmt: // Mips32r2 instruction.
// Watch the signs here, we want 2 32-bit vals
// to make a sign-64.
i64 = static_cast<uint32_t>(getFpuRegister(fs_reg));
i64 |= static_cast<int64_t>(getFpuRegister(fs_reg + 1)) << 32;
setFpuRegisterDouble(fd_reg, static_cast<double>(i64));
break;
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<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, static_cast<int32_t>(i64hilo & 0xffffffff));
setRegister(HI, static_cast<int32_t>(i64hilo >> 32));
break;
case ff_multu:
setRegister(LO, static_cast<int32_t>(u64hilo & 0xffffffff));
setRegister(HI, static_cast<int32_t>(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;
// 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;