/* -*- 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 2020 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/loong64/Simulator-loong64.h"
#include <
float.h>
#include <limits>
#include "jit/AtomicOperations.h"
#include "jit/loong64/Assembler-loong64.h"
#include "js/Conversions.h"
#include "threading/LockGuard.h"
#include "vm/JSContext.h"
#include "vm/Runtime.h"
#include "wasm/WasmInstance.h"
#include "wasm/WasmSignalHandlers.h"
#define I8(v)
static_cast<int8_t>(v)
#define I16(v)
static_cast<int16_t>(v)
#define U16(v)
static_cast<uint16_t>(v)
#define I32(v)
static_cast<int32_t>(v)
#define U32(v)
static_cast<uint32_t>(v)
#define I64(v)
static_cast<int64_t>(v)
#define U64(v)
static_cast<uint64_t>(v)
#define I128(v)
static_cast<__int128_t>(v)
#define U128(v)
static_cast<__uint128_t>(v)
#define I32_CHECK(v) \
({ \
MOZ_ASSERT(I64(I32(v)) == I64(v)); \
I32((v)); \
})
namespace js {
namespace jit {
static int64_t MultiplyHighSigned(int64_t u, int64_t v) {
uint64_t u0, v0, w0;
int64_t u1, v1, w1, w2, t;
u0 = u & 0xFFFFFFFFL;
u1 = u >> 32;
v0 = v & 0xFFFFFFFFL;
v1 = v >> 32;
w0 = u0 * v0;
t = u1 * v0 + (w0 >> 32);
w1 = t & 0xFFFFFFFFL;
w2 = t >> 32;
w1 = u0 * v1 + w1;
return u1 * v1 + w2 + (w1 >> 32);
}
static uint64_t MultiplyHighUnsigned(uint64_t u, uint64_t v) {
uint64_t u0, v0, w0;
uint64_t u1, v1, w1, w2, t;
u0 = u & 0xFFFFFFFFL;
u1 = u >> 32;
v0 = v & 0xFFFFFFFFL;
v1 = v >> 32;
w0 = u0 * v0;
t = u1 * v0 + (w0 >> 32);
w1 = t & 0xFFFFFFFFL;
w2 = t >> 32;
w1 = u0 * v1 + w1;
return u1 * v1 + w2 + (w1 >> 32);
}
// Precondition: 0 <= shift < 32
inline constexpr uint32_t RotateRight32(uint32_t value, uint32_t shift) {
return (value >> shift) | (value << ((32 - shift) & 31));
}
// Precondition: 0 <= shift < 32
inline constexpr uint32_t RotateLeft32(uint32_t value, uint32_t shift) {
return (value << shift) | (value >> ((32 - shift) & 31));
}
// Precondition: 0 <= shift < 64
inline constexpr uint64_t RotateRight64(uint64_t value, uint64_t shift) {
return (value >> shift) | (value << ((64 - shift) & 63));
}
// Precondition: 0 <= shift < 64
inline constexpr uint64_t RotateLeft64(uint64_t value, uint64_t shift) {
return (value << shift) | (value >> ((64 - shift) & 63));
}
// break instr with MAX_BREAK_CODE.
static const Instr kCallRedirInstr = op_break | CODEMask;
// -----------------------------------------------------------------------------
// LoongArch64 assembly various constants.
class SimInstruction {
public:
enum {
kInstrSize = 4,
// On LoongArch, 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 {
kUnsupported = -1,
kOp6Type,
kOp7Type,
kOp8Type,
kOp10Type,
kOp11Type,
kOp12Type,
kOp14Type,
kOp15Type,
kOp16Type,
kOp17Type,
kOp22Type,
kOp24Type
};
// Get the encoding type of the instruction.
Type instructionType()
const;
inline int rjValue()
const {
return bits(RJShift + RJBits - 1, RJShift); }
inline int rkValue()
const {
return bits(RKShift + RKBits - 1, RKShift); }
inline int rdValue()
const {
return bits(RDShift + RDBits - 1, RDShift); }
inline int sa2Value()
const {
return bits(SAShift + SA2Bits - 1, SAShift); }
inline int sa3Value()
const {
return bits(SAShift + SA3Bits - 1, SAShift); }
inline int lsbwValue()
const {
return bits(LSBWShift + LSBWBits - 1, LSBWShift);
}
inline int msbwValue()
const {
return bits(MSBWShift + MSBWBits - 1, MSBWShift);
}
inline int lsbdValue()
const {
return bits(LSBDShift + LSBDBits - 1, LSBDShift);
}
inline int msbdValue()
const {
return bits(MSBDShift + MSBDBits - 1, MSBDShift);
}
inline int fdValue()
const {
return bits(FDShift + FDBits - 1, FDShift); }
inline int fjValue()
const {
return bits(FJShift + FJBits - 1, FJShift); }
inline int fkValue()
const {
return bits(FKShift + FKBits - 1, FKShift); }
inline int faValue()
const {
return bits(FAShift + FABits - 1, FAShift); }
inline int cdValue()
const {
return bits(CDShift + CDBits - 1, CDShift); }
inline int cjValue()
const {
return bits(CJShift + CJBits - 1, CJShift); }
inline int caValue()
const {
return bits(CAShift + CABits - 1, CAShift); }
inline int condValue()
const {
return bits(CONDShift + CONDBits - 1, CONDShift);
}
inline int imm5Value()
const {
return bits(Imm5Shift + Imm5Bits - 1, Imm5Shift);
}
inline int imm6Value()
const {
return bits(Imm6Shift + Imm6Bits - 1, Imm6Shift);
}
inline int imm12Value()
const {
return bits(Imm12Shift + Imm12Bits - 1, Imm12Shift);
}
inline int imm14Value()
const {
return bits(Imm14Shift + Imm14Bits - 1, Imm14Shift);
}
inline int imm16Value()
const {
return bits(Imm16Shift + Imm16Bits - 1, Imm16Shift);
}
inline int imm20Value()
const {
return bits(Imm20Shift + Imm20Bits - 1, Imm20Shift);
}
inline int32_t imm26Value()
const {
return bits(Imm26Shift + Imm26Bits - 1, Imm26Shift);
}
// Say if the instruction is a debugger break/trap.
bool isTrap()
const;
private:
SimInstruction() =
delete;
SimInstruction(
const SimInstruction& other) =
delete;
void operator=(
const SimInstruction& other) =
delete;
};
bool SimInstruction::isTrap()
const {
// is break??
switch (bits(31, 15) << 15) {
case op_break:
return (instructionBits() != kCallRedirInstr) && (bits(15, 0) != 6);
default:
return false;
};
}
SimInstruction::Type SimInstruction::instructionType()
const {
SimInstruction::Type kType = kUnsupported;
// Check for kOp6Type
switch (bits(31, 26) << 26) {
case op_beqz:
case op_bnez:
case op_bcz:
case op_jirl:
case op_b:
case op_bl:
case op_beq:
case op_bne:
case op_blt:
case op_bge:
case op_bltu:
case op_bgeu:
case op_addu16i_d:
kType = kOp6Type;
break;
default:
kType = kUnsupported;
}
if (kType == kUnsupported) {
// Check for kOp7Type
switch (bits(31, 25) << 25) {
case op_lu12i_w:
case op_lu32i_d:
case op_pcaddi:
case op_pcalau12i:
case op_pcaddu12i:
case op_pcaddu18i:
kType = kOp7Type;
break;
default:
kType = kUnsupported;
}
}
if (kType == kUnsupported) {
// Check for kOp8Type
switch (bits(31, 24) << 24) {
case op_ll_w:
case op_sc_w:
case op_ll_d:
case op_sc_d:
case op_ldptr_w:
case op_stptr_w:
case op_ldptr_d:
case op_stptr_d:
kType = kOp8Type;
break;
default:
kType = kUnsupported;
}
}
if (kType == kUnsupported) {
// Check for kOp10Type
switch (bits(31, 22) << 22) {
case op_bstrins_d:
case op_bstrpick_d:
case op_slti:
case op_sltui:
case op_addi_w:
case op_addi_d:
case op_lu52i_d:
case op_andi:
case op_ori:
case op_xori:
case op_ld_b:
case op_ld_h:
case op_ld_w:
case op_ld_d:
case op_st_b:
case op_st_h:
case op_st_w:
case op_st_d:
case op_ld_bu:
case op_ld_hu:
case op_ld_wu:
case op_preld:
case op_fld_s:
case op_fst_s:
case op_fld_d:
case op_fst_d:
case op_bstr_w:
// BSTRINS_W & BSTRPICK_W
kType = kOp10Type;
break;
default:
kType = kUnsupported;
}
}
if (kType == kUnsupported) {
// Check for kOp11Type
switch (bits(31, 21) << 21) {
case op_bstr_w:
kType = kOp11Type;
break;
default:
kType = kUnsupported;
}
}
if (kType == kUnsupported) {
// Check for kOp12Type
switch (bits(31, 20) << 20) {
case op_fmadd_s:
case op_fmadd_d:
case op_fmsub_s:
case op_fmsub_d:
case op_fnmadd_s:
case op_fnmadd_d:
case op_fnmsub_s:
case op_fnmsub_d:
case op_fcmp_cond_s:
case op_fcmp_cond_d:
kType = kOp12Type;
break;
default:
kType = kUnsupported;
}
}
if (kType == kUnsupported) {
// Check for kOp14Type
switch (bits(31, 18) << 18) {
case op_bytepick_d:
case op_fsel:
kType = kOp14Type;
break;
default:
kType = kUnsupported;
}
}
if (kType == kUnsupported) {
// Check for kOp15Type
switch (bits(31, 17) << 17) {
case op_bytepick_w:
case op_alsl_w:
case op_alsl_wu:
case op_alsl_d:
kType = kOp15Type;
break;
default:
kType = kUnsupported;
}
}
if (kType == kUnsupported) {
// Check for kOp16Type
switch (bits(31, 16) << 16) {
case op_slli_d:
case op_srli_d:
case op_srai_d:
case op_rotri_d:
kType = kOp16Type;
break;
default:
kType = kUnsupported;
}
}
if (kType == kUnsupported) {
// Check for kOp17Type
switch (bits(31, 15) << 15) {
case op_slli_w:
case op_srli_w:
case op_srai_w:
case op_rotri_w:
case op_add_w:
case op_add_d:
case op_sub_w:
case op_sub_d:
case op_slt:
case op_sltu:
case op_maskeqz:
case op_masknez:
case op_nor:
case op_and:
case op_or:
case op_xor:
case op_orn:
case op_andn:
case op_sll_w:
case op_srl_w:
case op_sra_w:
case op_sll_d:
case op_srl_d:
case op_sra_d:
case op_rotr_w:
case op_rotr_d:
case op_mul_w:
case op_mul_d:
case op_mulh_d:
case op_mulh_du:
case op_mulh_w:
case op_mulh_wu:
case op_mulw_d_w:
case op_mulw_d_wu:
case op_div_w:
case op_mod_w:
case op_div_wu:
case op_mod_wu:
case op_div_d:
case op_mod_d:
case op_div_du:
case op_mod_du:
case op_break:
case op_fadd_s:
case op_fadd_d:
case op_fsub_s:
case op_fsub_d:
case op_fmul_s:
case op_fmul_d:
case op_fdiv_s:
case op_fdiv_d:
case op_fmax_s:
case op_fmax_d:
case op_fmin_s:
case op_fmin_d:
case op_fmaxa_s:
case op_fmaxa_d:
case op_fmina_s:
case op_fmina_d:
case op_fcopysign_s:
case op_fcopysign_d:
case op_ldx_b:
case op_ldx_h:
case op_ldx_w:
case op_ldx_d:
case op_stx_b:
case op_stx_h:
case op_stx_w:
case op_stx_d:
case op_ldx_bu:
case op_ldx_hu:
case op_ldx_wu:
case op_fldx_s:
case op_fldx_d:
case op_fstx_s:
case op_fstx_d:
case op_amswap_w:
case op_amswap_d:
case op_amadd_w:
case op_amadd_d:
case op_amand_w:
case op_amand_d:
case op_amor_w:
case op_amor_d:
case op_amxor_w:
case op_amxor_d:
case op_ammax_w:
case op_ammax_d:
case op_ammin_w:
case op_ammin_d:
case op_ammax_wu:
case op_ammax_du:
case op_ammin_wu:
case op_ammin_du:
case op_amswap_db_w:
case op_amswap_db_d:
case op_amadd_db_w:
case op_amadd_db_d:
case op_amand_db_w:
case op_amand_db_d:
case op_amor_db_w:
case op_amor_db_d:
case op_amxor_db_w:
case op_amxor_db_d:
case op_ammax_db_w:
case op_ammax_db_d:
case op_ammin_db_w:
case op_ammin_db_d:
case op_ammax_db_wu:
case op_ammax_db_du:
case op_ammin_db_wu:
case op_ammin_db_du:
case op_dbar:
case op_ibar:
kType = kOp17Type;
break;
default:
kType = kUnsupported;
}
}
if (kType == kUnsupported) {
// Check for kOp22Type
switch (bits(31, 10) << 10) {
case op_clo_w:
case op_clz_w:
case op_cto_w:
case op_ctz_w:
case op_clo_d:
case op_clz_d:
case op_cto_d:
case op_ctz_d:
case op_revb_2h:
case op_revb_4h:
case op_revb_2w:
case op_revb_d:
case op_revh_2w:
case op_revh_d:
case op_bitrev_4b:
case op_bitrev_8b:
case op_bitrev_w:
case op_bitrev_d:
case op_ext_w_h:
case op_ext_w_b:
case op_fabs_s:
case op_fabs_d:
case op_fneg_s:
case op_fneg_d:
case op_fsqrt_s:
case op_fsqrt_d:
case op_fmov_s:
case op_fmov_d:
case op_movgr2fr_w:
case op_movgr2fr_d:
case op_movgr2frh_w:
case op_movfr2gr_s:
case op_movfr2gr_d:
case op_movfrh2gr_s:
case op_movfcsr2gr:
case op_movfr2cf:
case op_movgr2cf:
case op_fcvt_s_d:
case op_fcvt_d_s:
case op_ftintrm_w_s:
case op_ftintrm_w_d:
case op_ftintrm_l_s:
case op_ftintrm_l_d:
case op_ftintrp_w_s:
case op_ftintrp_w_d:
case op_ftintrp_l_s:
case op_ftintrp_l_d:
case op_ftintrz_w_s:
case op_ftintrz_w_d:
case op_ftintrz_l_s:
case op_ftintrz_l_d:
case op_ftintrne_w_s:
case op_ftintrne_w_d:
case op_ftintrne_l_s:
case op_ftintrne_l_d:
case op_ftint_w_s:
case op_ftint_w_d:
case op_ftint_l_s:
case op_ftint_l_d:
case op_ffint_s_w:
case op_ffint_s_l:
case op_ffint_d_w:
case op_ffint_d_l:
case op_frint_s:
case op_frint_d:
kType = kOp22Type;
break;
default:
kType = kUnsupported;
}
}
if (kType == kUnsupported) {
// Check for kOp24Type
switch (bits(31, 8) << 8) {
case op_movcf2fr:
case op_movcf2gr:
kType = kOp24Type;
break;
default:
kType = kUnsupported;
}
}
return kType;
}
// C/C++ argument slots size.
const int kCArgSlotCount = 0;
const int kCArgsSlotsSize = kCArgSlotCount *
sizeof(uintptr_t);
class CachePage {
public:
static const int LINE_VALID = 0;
static const int LINE_INVALID = 1;
static const int kPageShift = 12;
static const int kPageSize = 1 << kPageShift;
static const int kPageMask = kPageSize - 1;
static const int kLineShift = 2;
// The cache line is only 4 bytes right now.
static const int kLineLength = 1 << kLineShift;
static const int kLineMask = kLineLength - 1;
CachePage() { memset(&validity_map_, LINE_INVALID,
sizeof(validity_map_)); }
char* validityByte(
int offset) {
return &validity_map_[offset >> kLineShift];
}
char* cachedData(
int offset) {
return &data_[offset]; }
private:
char data_[kPageSize];
// The cached data.
static const int kValidityMapSize = kPageSize >> kLineShift;
char validity_map_[kValidityMapSize];
// One byte per line.
};
// Protects the icache() and redirection() properties of the
// Simulator.
class AutoLockSimulatorCache :
public LockGuard<Mutex> {
using Base = LockGuard<Mutex>;
public:
explicit AutoLockSimulatorCache()
: Base(SimulatorProcess::singleton_->cacheLock_) {}
};
mozilla::Atomic<size_t, mozilla::ReleaseAcquire>
SimulatorProcess::ICacheCheckingDisableCount(
1);
// Checking is disabled by default.
SimulatorProcess* SimulatorProcess::singleton_ = nullptr;
int64_t Simulator::StopSimAt = -1;
Simulator* Simulator::Create() {
auto sim = MakeUnique<Simulator>();
if (!sim) {
return nullptr;
}
if (!sim->init()) {
return nullptr;
}
int64_t stopAt;
char* stopAtStr = getenv(
"LOONG64_SIM_STOP_AT");
if (stopAtStr && sscanf(stopAtStr,
"%" PRIi64, &stopAt) == 1) {
fprintf(stderr,
"\nStopping simulation at icount %" PRIi64
"\n", stopAt);
Simulator::StopSimAt = stopAt;
}
return sim.release();
}
void Simulator::Destroy(Simulator* sim) { js_delete(sim); }
// The loong64Debugger class is used by the simulator while debugging simulated
// code.
class loong64Debugger {
public:
explicit loong64Debugger(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 0x7fff to easily recognize it.
static const Instr kBreakpointInstr = op_break | (0x7fff & CODEMask);
static const Instr kNopInstr = 0x0;
Simulator* sim_;
int64_t getRegisterValue(
int regnum);
int64_t getFPURegisterValueLong(
int regnum);
float getFPURegisterValueFloat(
int regnum);
double getFPURegisterValueDouble(
int regnum);
bool getValue(
const char* desc, int64_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 UNIMPLEMENTED() {
printf(
"UNIMPLEMENTED instruction.\n");
MOZ_CRASH();
}
static void UNREACHABLE() {
printf(
"UNREACHABLE instruction.\n");
MOZ_CRASH();
}
static void UNSUPPORTED() {
printf(
"Unsupported instruction.\n");
MOZ_CRASH();
}
void loong64Debugger::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();
}
int64_t loong64Debugger::getRegisterValue(
int regnum) {
if (regnum == kPCRegister) {
return sim_->get_pc();
}
return sim_->getRegister(regnum);
}
int64_t loong64Debugger::getFPURegisterValueLong(
int regnum) {
return sim_->getFpuRegister(regnum);
}
float loong64Debugger::getFPURegisterValueFloat(
int regnum) {
return sim_->getFpuRegisterFloat(regnum);
}
double loong64Debugger::getFPURegisterValueDouble(
int regnum) {
return sim_->getFpuRegisterDouble(regnum);
}
bool loong64Debugger::getValue(
const char* desc, int64_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 + 2,
"%lx",
reinterpret_cast<uint64_t*>(value)) == 1;
}
return sscanf(desc,
"%lu",
reinterpret_cast<uint64_t*>(value)) == 1;
}
bool loong64Debugger::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 loong64Debugger::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 loong64Debugger::undoBreakpoints() {
if (sim_->break_pc_) {
sim_->break_pc_->setInstructionBits(sim_->break_instr_);
}
}
void loong64Debugger::redoBreakpoints() {
if (sim_->break_pc_) {
sim_->break_pc_->setInstructionBits(kBreakpointInstr);
}
}
void loong64Debugger::printAllRegs() {
int64_t value;
for (uint32_t i = 0; i < Registers::Total; i++) {
value = getRegisterValue(i);
printf(
"%3s: 0x%016" PRIx64
" %20" PRIi64
" ", Registers::GetName(i),
value, value);
if (i % 2) {
printf(
"\n");
}
}
printf(
"\n");
value = getRegisterValue(Simulator::pc);
printf(
" pc: 0x%016" PRIx64
"\n", value);
}
void loong64Debugger::printAllRegsIncludingFPU() {
printAllRegs();
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));
}
}
static char* ReadLine(
const char* prompt) {
UniqueChars result;
char lineBuf[256];
int offset = 0;
bool keepGoing =
true;
fprintf(stdout,
"%s", prompt);
fflush(stdout);
while (keepGoing) {
if (fgets(lineBuf,
sizeof(lineBuf), stdin) == nullptr) {
// fgets got an error. Just give up.
return nullptr;
}
int len = strlen(lineBuf);
if (len > 0 && lineBuf[len - 1] ==
'\n') {
// Since we read a new line we are done reading the line. This
// will exit the loop after copying this buffer into the result.
keepGoing =
false;
}
if (!result) {
// Allocate the initial result and make room for the terminating '\0'
result.reset(js_pod_malloc<
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;
}
MOZ_ASSERT(result);
result[offset] =
'\0';
return result.release();
}
static void DisassembleInstruction(uint64_t pc) {
printf(
"Not supported on loongarch64 yet\n");
}
void loong64Debugger::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());
printf(
" 0x%016" PRIi64
" \n", 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()) {
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);
}
sim_->icount_++;
}
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()));
sim_->icount_++;
// Leave the debugger shell.
done =
true;
}
else if ((strcmp(cmd,
"p") == 0) || (strcmp(cmd,
"print") == 0)) {
if (argc == 2) {
int64_t value;
if (strcmp(arg1,
"all") == 0) {
printAllRegs();
}
else if (strcmp(arg1,
"allf") == 0) {
printAllRegsIncludingFPU();
}
else {
Register reg =
Register::FromName(arg1);
FloatRegisters::Code fReg = FloatRegisters::FromName(arg1);
if (reg != InvalidReg) {
value = getRegisterValue(reg.code());
printf(
"%s: 0x%016" PRIi64
" %20" PRIi64
" \n", arg1, value,
value);
}
else if (fReg != FloatRegisters::Invalid) {
printf(
"%3s: 0x%016" PRIi64
"\tflt: %-8.4g\tdbl: %-16.4g\n",
FloatRegisters::GetName(fReg),
getFPURegisterValueLong(fReg),
getFPURegisterValueFloat(fReg),
getFPURegisterValueDouble(fReg));
}
else {
printf(
"%s unrecognized\n", arg1);
}
}
}
else {
printf(
"print or print single\n");
}
}
else if (strcmp(cmd,
"stack") == 0 || strcmp(cmd,
"mem") == 0) {
int64_t* cur = nullptr;
int64_t* end = nullptr;
int next_arg = 1;
if (strcmp(cmd,
"stack") == 0) {
cur =
reinterpret_cast<int64_t*>(sim_->getRegister(Simulator::sp));
}
else {
// Command "mem".
int64_t value;
if (!getValue(arg1, &value)) {
printf(
"%s unrecognized\n", arg1);
continue;
}
cur =
reinterpret_cast<int64_t*>(value);
next_arg++;
}
int64_t words;
if (argc == next_arg) {
words = 10;
}
else {
if (!getValue(argv[next_arg], &words)) {
words = 10;
}
}
end = cur + words;
while (cur < end) {
printf(
" %p: 0x%016" PRIx64
" %20" PRIi64, 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.
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;
}
}
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) {
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 \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 LOONG64 !\n");
}
else if (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");
}
}
else if (argc == 3) {
// Print information about all/the specified breakpoint(s).
if (strcmp(arg1,
"info") == 0) {
if (strcmp(arg2,
"all") == 0) {
printf(
"Stop information:\n");
for (uint32_t i = kMaxWatchpointCode + 1; i <= kMaxStopCode;
i++) {
sim_->printStopInfo(i);
}
}
else if (getValue(arg2, &value)) {
sim_->printStopInfo(value);
}
else {
printf(
"Unrecognized argument.\n");
}
}
else if (strcmp(arg1,
"enable") == 0) {
// Enable all/the specified breakpoint(s).
if (strcmp(arg2,
"all") == 0) {
for (uint32_t i = kMaxWatchpointCode + 1; i <= kMaxStopCode;
i++) {
sim_->enableStop(i);
}
}
else if (getValue(arg2, &value)) {
sim_->enableStop(value);
}
else {
printf(
"Unrecognized argument.\n");
}
}
else if (strcmp(arg1,
"disable") == 0) {
// Disable all/the specified breakpoint(s).
if (strcmp(arg2,
"all") == 0) {
for (uint32_t i = kMaxWatchpointCode + 1; i <= kMaxStopCode;
i++) {
sim_->disableStop(i);
}
}
else if (getValue(arg2, &value)) {
sim_->disableStop(value);
}
else {
printf(
"Unrecognized argument.\n");
}
}
}
else {
printf(
"Wrong usage. Use help command for more information.\n");
}
}
else if ((strcmp(cmd,
"h") == 0) || (strcmp(cmd,
"help") == 0)) {
printf(
"cont\n");
printf(
" continue execution (alias 'c')\n");
printf(
"stepi\n");
printf(
" step one instruction (alias 'si')\n");
printf(
"print \n");
printf(
" print register content (alias 'p')\n");
printf(
" use register name 'all' to print all registers\n");
printf(
"printobject \n");
printf(
" print an object from a register (alias 'po')\n");
printf(
"stack []\n");
printf(
" dump stack content, default dump 10 words)\n");
printf(
"mem []\n");
printf(
" dump memory content, default dump 10 words)\n");
printf(
"flags\n");
printf(
" print flags\n");
printf(
"disasm []\n");
printf(
"disasm []\n");
printf(
"disasm [[] ]\n");
printf(
" disassemble code, default is 10 instructions\n");
printf(
" from pc (alias 'di')\n");
printf(
"gdb\n");
printf(
" enter gdb\n");
printf(
"break \n");
printf(
" set a break point on the address\n");
printf(
"del\n");
printf(
" delete the breakpoint\n");
printf(
"stop feature:\n");
printf(
" Description:\n");
printf(
" Stops are debug instructions inserted by\n");
printf(
" the Assembler::stop() function.\n");
printf(
" When hitting a stop, the Simulator will\n");
printf(
" stop and and give control to the Debugger.\n");
printf(
" All stop codes are watched:\n");
printf(
" - They can be enabled / disabled: the Simulator\n");
printf(
" will / won't stop when hitting them.\n");
printf(
" - The Simulator keeps track of how many times they \n");
printf(
" are met. (See the info command.) Going over a\n");
printf(
" disabled stop still increases its counter. \n");
printf(
" Commands:\n");
printf(
" stop info all/ : print infos about number \n");
printf(
" or all stop(s).\n");
printf(
" stop enable/disable all/ : enables / disables\n");
printf(
" all or number stop(s)\n");
printf(
" stop unstop\n");
printf(
" ignore the stop instruction at the current location\n");
printf(
" from now on\n");
}
else {
printf(
"Unknown command: %s\n", cmd);
}
}
}
// Add all the breakpoints back to stop execution and enter the debugger
// shell when hit.
redoBreakpoints();
#undef COMMAND_SIZE
#undef ARG_SIZE
#undef STR
#undef XSTR
}
static bool AllOnOnePage(uintptr_t start,
int size) {
intptr_t start_page = (start & ~CachePage::kPageMask);
intptr_t end_page = ((start + size) & ~CachePage::kPageMask);
return start_page == end_page;
}
void Simulator::setLastDebuggerInput(
char* input) {
js_free(lastDebuggerInput_);
lastDebuggerInput_ = input;
}
static CachePage* GetCachePageLocked(SimulatorProcess::ICacheMap& i_cache,
void* page) {
SimulatorProcess::ICacheMap::AddPtr p = i_cache.lookupForAdd(page);
if (p) {
return p->value();
}
AutoEnterOOMUnsafeRegion oomUnsafe;
CachePage* new_page = js_new<CachePage>();
if (!new_page || !i_cache.add(p, page, new_page)) {
oomUnsafe.crash(
"Simulator CachePage");
}
return new_page;
}
// Flush from start up to and not including start + size.
static void FlushOnePageLocked(SimulatorProcess::ICacheMap& i_cache,
intptr_t start,
int size) {
MOZ_ASSERT(size <= CachePage::kPageSize);
MOZ_ASSERT(AllOnOnePage(start, size - 1));
MOZ_ASSERT((start & CachePage::kLineMask) == 0);
MOZ_ASSERT((size & CachePage::kLineMask) == 0);
void* page =
reinterpret_cast<
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(SimulatorProcess::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 SimulatorProcess::checkICacheLocked(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(icache(), page);
char* cache_valid_byte = cache_page->validityByte(offset);
bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID);
char* cached_line = cache_page->cachedData(offset & ~CachePage::kLineMask);
if (cache_hit) {
// Check that the data in memory matches the contents of the I-cache.
mozilla::DebugOnly<
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;
}
}
HashNumber SimulatorProcess::ICacheHasher::hash(
const Lookup& l) {
return U32(
reinterpret_cast<uintptr_t>(l)) >> 2;
}
bool SimulatorProcess::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;
}
/* static */
void SimulatorProcess::FlushICache(
void* start_addr, size_t size) {
if (!ICacheCheckingDisableCount) {
AutoLockSimulatorCache als;
js::jit::FlushICacheLocked(icache(), start_addr, size);
}
}
Simulator::Simulator() {
// Set up simulator support first. Some of this information is needed to
// setup the architecture state.
// Note, allocation and anything that depends on allocated memory is
// deferred until init(), in order to handle OOM properly.
stack_ = nullptr;
stackLimit_ = 0;
pc_modified_ =
false;
icount_ = 0;
break_count_ = 0;
break_pc_ = nullptr;
break_instr_ = 0;
single_stepping_ =
false;
single_step_callback_ = nullptr;
single_step_callback_arg_ = nullptr;
// Set up architecture state.
// All registers are initialized to zero to start with.
for (
int i = 0; i <
Register::kNumSimuRegisters; i++) {
registers_[i] = 0;
}
for (
int i = 0; i < Simulator::FPURegister::kNumFPURegisters; i++) {
FPUregisters_[i] = 0;
}
for (
int i = 0; i < kNumCFRegisters; i++) {
CFregisters_[i] = 0;
}
FCSR_ = 0;
LLBit_ =
false;
LLAddr_ = 0;
lastLLValue_ = 0;
// The ra and pc are initialized to a known bad value that will cause an
// access violation if the simulator ever tries to execute it.
registers_[pc] = bad_ra;
registers_[ra] = bad_ra;
for (
int i = 0; i < kNumExceptions; i++) {
exceptions[i] = 0;
}
lastDebuggerInput_ = nullptr;
}
bool Simulator::init() {
// Allocate 2MB for the stack. Note that we will only use 1MB, see below.
static const size_t stackSize = 2 * 1024 * 1024;
stack_ = js_pod_malloc<
char>(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<int64_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 SimulatorProcess;
// sim's lock must already be held.
Redirection(
void* nativeFunction, ABIFunctionType type)
: nativeFunction_(nativeFunction),
swiInstruction_(kCallRedirInstr),
type_(type),
next_(nullptr) {
next_ = SimulatorProcess::redirection();
if (!SimulatorProcess::ICacheCheckingDisableCount) {
FlushICacheLocked(SimulatorProcess::icache(), addressOfSwiInstruction(),
SimInstruction::kInstrSize);
}
SimulatorProcess::setRedirection(
this);
}
public:
void* addressOfSwiInstruction() {
return &swiInstruction_; }
void* nativeFunction()
const {
return nativeFunction_; }
ABIFunctionType type()
const {
return type_; }
static Redirection* Get(
void* nativeFunction, ABIFunctionType type) {
AutoLockSimulatorCache als;
Redirection* current = SimulatorProcess::redirection();
for (; current != nullptr; current = current->next_) {
if (current->nativeFunction_ == nativeFunction) {
MOZ_ASSERT(current->type() == type);
return current;
}
}
// Note: we can't use js_new here because the constructor is private.
AutoEnterOOMUnsafeRegion oomUnsafe;
Redirection* redir = js_pod_malloc<Redirection>(1);
if (!redir) {
oomUnsafe.crash(
"Simulator redirection");
}
new (redir) Redirection(nativeFunction, type);
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_); }
SimulatorProcess::SimulatorProcess()
: cacheLock_(mutexid::SimulatorCacheLock), redirection_(nullptr) {
if (getenv(
"LOONG64_SIM_ICACHE_CHECKS")) {
ICacheCheckingDisableCount = 0;
}
}
SimulatorProcess::~SimulatorProcess() {
Redirection* r = redirection_;
while (r) {
Redirection* next = r->next_;
js_delete(r);
r = next;
}
}
/* static */
void* Simulator::RedirectNativeFunction(
void* nativeFunction,
ABIFunctionType type) {
Redirection* redirection = Redirection::Get(nativeFunction, type);
return redirection->addressOfSwiInstruction();
}
// Get the active Simulator for the current thread.
Simulator* Simulator::Current() {
JSContext* cx = TlsContext.get();
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
return cx->simulator();
}
// Sets the register in the architecture state. It will also deal with updating
// Simulator internal state for special registers such as PC.
void Simulator::setRegister(
int reg, int64_t value) {
MOZ_ASSERT((reg >= 0) && (reg <
Register::kNumSimuRegisters));
if (reg == pc) {
pc_modified_ =
true;
}
// Zero register always holds 0.
registers_[reg] = (reg == 0) ? 0 : value;
}
void Simulator::setFpuRegister(
int fpureg, int64_t value) {
MOZ_ASSERT((fpureg >= 0) &&
(fpureg < Simulator::FPURegister::kNumFPURegisters));
FPUregisters_[fpureg] = value;
}
void Simulator::setFpuRegisterHiWord(
int fpureg, int32_t value) {
// Set ONLY upper 32-bits, leaving lower bits untouched.
MOZ_ASSERT((fpureg >= 0) &&
(fpureg < Simulator::FPURegister::kNumFPURegisters));
int32_t* phiword;
phiword = (
reinterpret_cast<int32_t*>(&FPUregisters_[fpureg])) + 1;
*phiword = value;
}
void Simulator::setFpuRegisterWord(
int fpureg, int32_t value) {
// Set ONLY lower 32-bits, leaving upper bits untouched.
MOZ_ASSERT((fpureg >= 0) &&
(fpureg < Simulator::FPURegister::kNumFPURegisters));
int32_t* pword;
pword =
reinterpret_cast<int32_t*>(&FPUregisters_[fpureg]);
*pword = value;
}
void Simulator::setFpuRegisterWordInvalidResult(
float original,
float rounded,
int fpureg) {
double max_int32 =
static_cast<
double>(INT32_MAX);
double min_int32 =
static_cast<
double>(INT32_MIN);
if (std::isnan(original)) {
setFpuRegisterWord(fpureg, 0);
}
else if (rounded > max_int32) {
setFpuRegister(fpureg, kFPUInvalidResult);
}
else if (rounded < min_int32) {
setFpuRegister(fpureg, kFPUInvalidResultNegative);
}
else {
UNREACHABLE();
}
}
void Simulator::setFpuRegisterWordInvalidResult(
double original,
double rounded,
int fpureg) {
double max_int32 =
static_cast<
double>(INT32_MAX);
double min_int32 =
static_cast<
double>(INT32_MIN);
if (std::isnan(original)) {
setFpuRegisterWord(fpureg, 0);
}
else if (rounded > max_int32) {
setFpuRegisterWord(fpureg, kFPUInvalidResult);
}
else if (rounded < min_int32) {
setFpuRegisterWord(fpureg, kFPUInvalidResultNegative);
}
else {
UNREACHABLE();
}
}
void Simulator::setFpuRegisterInvalidResult(
float original,
float rounded,
int fpureg) {
double max_int32 =
static_cast<
double>(INT32_MAX);
double min_int32 =
static_cast<
double>(INT32_MIN);
if (std::isnan(original)) {
setFpuRegister(fpureg, 0);
}
else if (rounded > max_int32) {
setFpuRegister(fpureg, kFPUInvalidResult);
}
else if (rounded < min_int32) {
setFpuRegister(fpureg, kFPUInvalidResultNegative);
}
else {
UNREACHABLE();
}
}
void Simulator::setFpuRegisterInvalidResult(
double original,
double rounded,
int fpureg) {
double max_int32 =
static_cast<
double>(INT32_MAX);
double min_int32 =
static_cast<
double>(INT32_MIN);
if (std::isnan(original)) {
setFpuRegister(fpureg, 0);
}
else if (rounded > max_int32) {
setFpuRegister(fpureg, kFPUInvalidResult);
}
else if (rounded < min_int32) {
setFpuRegister(fpureg, kFPUInvalidResultNegative);
}
else {
UNREACHABLE();
}
}
void Simulator::setFpuRegisterInvalidResult64(
float original,
float rounded,
int fpureg) {
// The value of INT64_MAX (2^63-1) can't be represented as double exactly,
// loading the most accurate representation into max_int64, which is 2^63.
double max_int64 =
static_cast<
double>(INT64_MAX);
double min_int64 =
static_cast<
double>(INT64_MIN);
if (std::isnan(original)) {
setFpuRegister(fpureg, 0);
}
else if (rounded >= max_int64) {
setFpuRegister(fpureg, kFPU64InvalidResult);
}
else if (rounded < min_int64) {
setFpuRegister(fpureg, kFPU64InvalidResultNegative);
}
else {
UNREACHABLE();
}
}
void Simulator::setFpuRegisterInvalidResult64(
double original,
double rounded,
int fpureg) {
// The value of INT64_MAX (2^63-1) can't be represented as double exactly,
// loading the most accurate representation into max_int64, which is 2^63.
double max_int64 =
static_cast<
double>(INT64_MAX);
double min_int64 =
static_cast<
double>(INT64_MIN);
if (std::isnan(original)) {
setFpuRegister(fpureg, 0);
}
else if (rounded >= max_int64) {
setFpuRegister(fpureg, kFPU64InvalidResult);
}
else if (rounded < min_int64) {
setFpuRegister(fpureg, kFPU64InvalidResultNegative);
}
else {
UNREACHABLE();
}
}
void Simulator::setFpuRegisterFloat(
int fpureg,
float value) {
MOZ_ASSERT((fpureg >= 0) &&
(fpureg < Simulator::FPURegister::kNumFPURegisters));
*mozilla::BitwiseCast<
float*>(&FPUregisters_[fpureg]) = value;
}
void Simulator::setFpuRegisterDouble(
int fpureg,
double value) {
MOZ_ASSERT((fpureg >= 0) &&
(fpureg < Simulator::FPURegister::kNumFPURegisters));
*mozilla::BitwiseCast<
double*>(&FPUregisters_[fpureg]) = value;
}
void Simulator::setCFRegister(
int cfreg,
bool value) {
MOZ_ASSERT((cfreg >= 0) && (cfreg < kNumCFRegisters));
CFregisters_[cfreg] = value;
}
bool Simulator::getCFRegister(
int cfreg)
const {
MOZ_ASSERT((cfreg >= 0) && (cfreg < kNumCFRegisters));
return CFregisters_[cfreg];
}
// 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);
}
int64_t Simulator::getFpuRegister(
int fpureg)
const {
MOZ_ASSERT((fpureg >= 0) &&
(fpureg < Simulator::FPURegister::kNumFPURegisters));
return FPUregisters_[fpureg];
}
int32_t Simulator::getFpuRegisterWord(
int fpureg)
const {
MOZ_ASSERT((fpureg >= 0) &&
(fpureg < Simulator::FPURegister::kNumFPURegisters));
return *mozilla::BitwiseCast<int32_t*>(&FPUregisters_[fpureg]);
}
int32_t Simulator::getFpuRegisterSignedWord(
int fpureg)
const {
MOZ_ASSERT((fpureg >= 0) &&
(fpureg < Simulator::FPURegister::kNumFPURegisters));
return *mozilla::BitwiseCast<int32_t*>(&FPUregisters_[fpureg]);
}
int32_t Simulator::getFpuRegisterHiWord(
int fpureg)
const {
MOZ_ASSERT((fpureg >= 0) &&
(fpureg < Simulator::FPURegister::kNumFPURegisters));
return *((mozilla::BitwiseCast<int32_t*>(&FPUregisters_[fpureg])) + 1);
}
float Simulator::getFpuRegisterFloat(
int fpureg)
const {
MOZ_ASSERT((fpureg >= 0) &&
(fpureg < Simulator::FPURegister::kNumFPURegisters));
return *mozilla::BitwiseCast<
float*>(&FPUregisters_[fpureg]);
}
double Simulator::getFpuRegisterDouble(
int fpureg)
const {
MOZ_ASSERT((fpureg >= 0) &&
(fpureg < Simulator::FPURegister::kNumFPURegisters));
return *mozilla::BitwiseCast<
double*>(&FPUregisters_[fpureg]);
}
void Simulator::setCallResultDouble(
double result) {
setFpuRegisterDouble(f0, result);
}
void Simulator::setCallResultFloat(
float result) {
setFpuRegisterFloat(f0, result);
}
void Simulator::setCallResult(int64_t res) { setRegister(a0, res); }
void Simulator::setCallResult(__int128_t res) {
setRegister(a0, I64(res));
setRegister(a1, I64(res >> 64));
}
// 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); }
unsigned int Simulator::getFCSRRoundingMode() {
return FCSR_ & kFPURoundingModeMask;
}
// 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;
setFCSRBit(kFCSRInexactCauseBit,
false);
setFCSRBit(kFCSRUnderflowCauseBit,
false);
setFCSRBit(kFCSROverflowCauseBit,
false);
setFCSRBit(kFCSRInvalidOpCauseBit,
false);
if (!std::isfinite(original) || !std::isfinite(rounded)) {
setFCSRBit(kFCSRInvalidOpFlagBit,
true);
setFCSRBit(kFCSRInvalidOpCauseBit,
true);
ret =
true;
}
if (original != rounded) {
setFCSRBit(kFCSRInexactFlagBit,
true);
setFCSRBit(kFCSRInexactCauseBit,
true);
}
if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) {
setFCSRBit(kFCSRUnderflowFlagBit,
true);
setFCSRBit(kFCSRUnderflowCauseBit,
true);
ret =
true;
}
if ((
long double)rounded > (
long double)std::numeric_limits<T>::max() ||
(
long double)rounded < (
long double)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;
}
// For cvt instructions only
template <
typename T>
void Simulator::roundAccordingToFCSR(T toRound, T* rounded,
int32_t* rounded_int) {
switch ((FCSR_ >> 8) & 3) {
case kRoundToNearest:
*rounded = std::floor(toRound + 0.5);
*rounded_int =
static_cast<int32_t>(*rounded);
if ((*rounded_int & 1) != 0 && *rounded_int - toRound == 0.5) {
// If the number is halfway between two integers,
// round to the even one.
*rounded_int -= 1;
*rounded -= 1.;
}
break;
case kRoundToZero:
*rounded = trunc(toRound);
*rounded_int =
static_cast<int32_t>(*rounded);
break;
case kRoundToPlusInf:
*rounded = std::ceil(toRound);
*rounded_int =
static_cast<int32_t>(*rounded);
break;
case kRoundToMinusInf:
*rounded = std::floor(toRound);
*rounded_int =
static_cast<int32_t>(*rounded);
break;
}
}
template <
typename T>
void Simulator::round64AccordingToFCSR(T toRound, T* rounded,
int64_t* rounded_int) {
switch ((FCSR_ >> 8) & 3) {
case kRoundToNearest:
*rounded = std::floor(toRound + 0.5);
*rounded_int =
static_cast<int64_t>(*rounded);
if ((*rounded_int & 1) != 0 && *rounded_int - toRound == 0.5) {
// If the number is halfway between two integers,
// round to the even one.
*rounded_int -= 1;
*rounded -= 1.;
}
break;
case kRoundToZero:
*rounded = trunc(toRound);
*rounded_int =
static_cast<int64_t>(*rounded);
break;
case kRoundToPlusInf:
*rounded = std::ceil(toRound);
*rounded_int =
static_cast<int64_t>(*rounded);
break;
case kRoundToMinusInf:
*rounded = std::floor(toRound);
*rounded_int =
static_cast<int64_t>(*rounded);
break;
}
}
// Raw access to the PC register.
void Simulator::set_pc(int64_t value) {
pc_modified_ =
true;
registers_[pc] = value;
}
bool Simulator::has_bad_pc()
const {
return ((registers_[pc] == bad_ra) || (registers_[pc] == end_sim_pc));
}
// Raw access to the PC register without the special adjustment when reading.
int64_t Simulator::get_pc()
const {
return registers_[pc]; }
JS::ProfilingFrameIterator::RegisterState Simulator::registerState() {
wasm::RegisterState state;
state.pc = (
void*)get_pc();
state.fp = (
void*)getRegister(fp);
state.sp = (
void*)getRegister(sp);
state.lr = (
void*)getRegister(ra);
return state;
}
uint8_t Simulator::readBU(uint64_t addr) {
if (handleWasmSegFault(addr, 1)) {
return 0xff;
}
uint8_t* ptr =
reinterpret_cast<uint8_t*>(addr);
return *ptr;
}
int8_t Simulator::readB(uint64_t addr) {
if (handleWasmSegFault(addr, 1)) {
return -1;
}
int8_t* ptr =
reinterpret_cast<int8_t*>(addr);
return *ptr;
}
void Simulator::writeB(uint64_t addr, uint8_t value) {
if (handleWasmSegFault(addr, 1)) {
return;
}
uint8_t* ptr =
reinterpret_cast<uint8_t*>(addr);
*ptr = value;
}
void Simulator::writeB(uint64_t addr, int8_t value) {
if (handleWasmSegFault(addr, 1)) {
return;
}
int8_t* ptr =
reinterpret_cast<int8_t*>(addr);
*ptr = value;
}
uint16_t Simulator::readHU(uint64_t addr, SimInstruction* instr) {
if (handleWasmSegFault(addr, 2)) {
return 0xffff;
}
uint16_t* ptr =
reinterpret_cast<uint16_t*>(addr);
return *ptr;
}
int16_t Simulator::readH(uint64_t addr, SimInstruction* instr) {
if (handleWasmSegFault(addr, 2)) {
return -1;
}
int16_t* ptr =
reinterpret_cast<int16_t*>(addr);
return *ptr;
}
void Simulator::writeH(uint64_t addr, uint16_t value, SimInstruction* instr) {
if (handleWasmSegFault(addr, 2)) {
return;
}
uint16_t* ptr =
reinterpret_cast<uint16_t*>(addr);
LLBit_ =
false;
*ptr = value;
return;
}
void Simulator::writeH(uint64_t addr, int16_t value, SimInstruction* instr) {
if (handleWasmSegFault(addr, 2)) {
return;
}
int16_t* ptr =
reinterpret_cast<int16_t*>(addr);
LLBit_ =
false;
*ptr = value;
return;
}
uint32_t Simulator::readWU(uint64_t addr, SimInstruction* instr) {
if (handleWasmSegFault(addr, 4)) {
return -1;
}
uint32_t* ptr =
reinterpret_cast<uint32_t*>(addr);
return *ptr;
}
int32_t Simulator::readW(uint64_t addr, SimInstruction* instr) {
if (handleWasmSegFault(addr, 4)) {
return -1;
}
int32_t* ptr =
reinterpret_cast<int32_t*>(addr);
return *ptr;
}
void Simulator::writeW(uint64_t addr, uint32_t value, SimInstruction* instr) {
if (handleWasmSegFault(addr, 4)) {
return;
}
uint32_t* ptr =
reinterpret_cast<uint32_t*>(addr);
LLBit_ =
false;
*ptr = value;
return;
}
void Simulator::writeW(uint64_t addr, int32_t value, SimInstruction* instr) {
if (handleWasmSegFault(addr, 4)) {
return;
}
int32_t* ptr =
reinterpret_cast<int32_t*>(addr);
LLBit_ =
false;
*ptr = value;
return;
}
int64_t Simulator::readDW(uint64_t addr, SimInstruction* instr) {
if (handleWasmSegFault(addr, 8)) {
return -1;
}
intptr_t* ptr =
reinterpret_cast<intptr_t*>(addr);
return *ptr;
}
void Simulator::writeDW(uint64_t addr, int64_t value, SimInstruction* instr) {
if (handleWasmSegFault(addr, 8)) {
return;
}
int64_t* ptr =
reinterpret_cast<int64_t*>(addr);
LLBit_ =
false;
*ptr = value;
return;
}
double Simulator::readD(uint64_t addr, SimInstruction* instr) {
if (handleWasmSegFault(addr, 8)) {
return NAN;
}
double* ptr =
reinterpret_cast<
double*>(addr);
return *ptr;
}
void Simulator::writeD(uint64_t addr,
double value, SimInstruction* instr) {
if (handleWasmSegFault(addr, 8)) {
return;
}
double* ptr =
reinterpret_cast<
double*>(addr);
LLBit_ =
false;
*ptr = value;
return;
}
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();
}
if ((addr & 3) == 0) {
SharedMem<int32_t*> ptr =
SharedMem<int32_t*>::shared(
reinterpret_cast<int32_t*>(addr));
if (!LLBit_) {
return 0;
}
LLBit_ =
false;
LLAddr_ = 0;
int32_t expected = int32_t(lastLLValue_);
int32_t old =
AtomicOperations::compareExchangeSeqCst(ptr, expected, int32_t(value));
return (old == expected) ? 1 : 0;
}
printf(
"Unaligned SC at 0x%016" PRIx64
", pc=0x%016" PRIxPTR
"\n", addr,
reinterpret_cast<intptr_t>(instr));
MOZ_CRASH();
return 0;
}
int64_t Simulator::loadLinkedD(uint64_t addr, SimInstruction* instr) {
if ((addr & kPointerAlignmentMask) == 0) {
if (handleWasmSegFault(addr, 8)) {
return -1;
}
volatile int64_t* ptr =
reinterpret_cast<
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();
}
if ((addr & kPointerAlignmentMask) == 0) {
SharedMem<int64_t*> ptr =
SharedMem<int64_t*>::shared(
reinterpret_cast<int64_t*>(addr));
if (!LLBit_) {
return 0;
}
LLBit_ =
false;
LLAddr_ = 0;
int64_t expected = lastLLValue_;
int64_t old =
AtomicOperations::compareExchangeSeqCst(ptr, expected, int64_t(value));
return (old == expected) ? 1 : 0;
}
printf(
"Unaligned SC at 0x%016" PRIx64
", pc=0x%016" PRIxPTR
"\n", addr,
reinterpret_cast<intptr_t>(instr));
MOZ_CRASH();
return 0;
}
uintptr_t Simulator::stackLimit()
const {
return stackLimit_; }
uintptr_t* Simulator::addressOfStackLimit() {
return &stackLimit_; }
bool Simulator::overRecursed(uintptr_t newsp)
const {
if (newsp == 0) {
newsp = getRegister(sp);
}
return newsp <= stackLimit();
}
bool Simulator::overRecursedWithExtra(uint32_t extra)
const {
uintptr_t newsp = getRegister(sp) - extra;
return newsp <= stackLimit();
}
// Unsupported instructions use format to print an error and stop execution.
void Simulator::format(SimInstruction* instr,
const char* format) {
printf(
"Simulator found unsupported instruction:\n 0x%016lx: %s\n",
reinterpret_cast<intptr_t>(instr), format);
MOZ_CRASH();
}
inline int32_t Simulator::rj_reg(SimInstruction* instr)
const {
return instr->rjValue();
}
inline int64_t Simulator::rj(SimInstruction* instr)
const {
return getRegister(rj_reg(instr));
}
inline uint64_t Simulator::rj_u(SimInstruction* instr)
const {
return static_cast<uint64_t>(getRegister(rj_reg(instr)));
}
inline int32_t Simulator::rk_reg(SimInstruction* instr)
const {
return instr->rkValue();
}
inline int64_t Simulator::rk(SimInstruction* instr)
const {
return getRegister(rk_reg(instr));
}
inline uint64_t Simulator::rk_u(SimInstruction* instr)
const {
return static_cast<uint64_t>(getRegister(rk_reg(instr)));
}
inline int32_t Simulator::rd_reg(SimInstruction* instr)
const {
return instr->rdValue();
}
inline int64_t Simulator::rd(SimInstruction* instr)
const {
return getRegister(rd_reg(instr));
}
inline uint64_t Simulator::rd_u(SimInstruction* instr)
const {
return static_cast<uint64_t>(getRegister(rd_reg(instr)));
}
inline int32_t Simulator::fa_reg(SimInstruction* instr)
const {
return instr->faValue();
}
inline float Simulator::fa_float(SimInstruction* instr)
const {
return getFpuRegisterFloat(fa_reg(instr));
}
inline double Simulator::fa_double(SimInstruction* instr)
const {
return getFpuRegisterDouble(fa_reg(instr));
}
inline int32_t Simulator::fj_reg(SimInstruction* instr)
const {
return instr->fjValue();
}
--> --------------------
--> maximum size reached
--> --------------------